src/cmd/compile/internal/types2/resolver.go GO 758 lines View on github.com → Search inside
1// Copyright 2013 The Go Authors. All rights reserved.2// Use of this source code is governed by a BSD-style3// license that can be found in the LICENSE file.45package types267import (8	"cmd/compile/internal/syntax"9	"cmp"10	"fmt"11	"go/constant"12	. "internal/types/errors"13	"slices"14	"strconv"15	"strings"16	"unicode"17)1819// A declInfo describes a package-level const, type, var, or func declaration.20type declInfo struct {21	file      *Scope           // scope of file containing this declaration22	version   goVersion        // Go version of file containing this declaration23	lhs       []*Var           // lhs of n:1 variable declarations, or nil24	vtyp      syntax.Expr      // type, or nil (for const and var declarations only)25	init      syntax.Expr      // init/orig expression, or nil (for const and var declarations only)26	inherited bool             // if set, the init expression is inherited from a previous constant declaration27	tdecl     *syntax.TypeDecl // type declaration, or nil28	fdecl     *syntax.FuncDecl // func declaration, or nil2930	// The deps field tracks initialization expression dependencies.31	deps map[Object]bool // lazily initialized32}3334// hasInitializer reports whether the declared object has an initialization35// expression or function body.36func (d *declInfo) hasInitializer() bool {37	return d.init != nil || d.fdecl != nil && d.fdecl.Body != nil38}3940// addDep adds obj to the set of objects d's init expression depends on.41func (d *declInfo) addDep(obj Object) {42	m := d.deps43	if m == nil {44		m = make(map[Object]bool)45		d.deps = m46	}47	m[obj] = true48}4950// arity checks that the lhs and rhs of a const or var decl51// have a matching number of names and initialization values.52// If inherited is set, the initialization values are from53// another (constant) declaration.54func (check *Checker) arity(pos syntax.Pos, names []*syntax.Name, inits []syntax.Expr, constDecl, inherited bool) {55	l := len(names)56	r := len(inits)5758	const code = WrongAssignCount59	switch {60	case l < r:61		n := inits[l]62		if inherited {63			check.errorf(pos, code, "extra init expr at %s", n.Pos())64		} else {65			check.errorf(n, code, "extra init expr %s", n)66		}67	case l > r && (constDecl || r != 1): // if r == 1 it may be a multi-valued function and we can't say anything yet68		n := names[r]69		check.errorf(n, code, "missing init expr for %s", n.Value)70	}71}7273func validatedImportPath(path string) (string, error) {74	s, err := strconv.Unquote(path)75	if err != nil {76		return "", err77	}78	if s == "" {79		return "", fmt.Errorf("empty string")80	}81	const illegalChars = `!"#$%&'()*,:;<=>?[\]^{|}` + "`\uFFFD"82	for _, r := range s {83		if !unicode.IsGraphic(r) || unicode.IsSpace(r) || strings.ContainsRune(illegalChars, r) {84			return s, fmt.Errorf("invalid character %#U", r)85		}86	}87	return s, nil88}8990// declarePkgObj declares obj in the package scope, records its ident -> obj mapping,91// and updates check.objMap. The object must not be a function or method.92func (check *Checker) declarePkgObj(ident *syntax.Name, obj Object, d *declInfo) {93	assert(ident.Value == obj.Name())9495	// spec: "A package-scope or file-scope identifier with name init96	// may only be declared to be a function with this (func()) signature."97	if ident.Value == "init" {98		check.error(ident, InvalidInitDecl, "cannot declare init - must be func")99		return100	}101102	// spec: "The main package must have package name main and declare103	// a function main that takes no arguments and returns no value."104	if ident.Value == "main" && check.pkg.name == "main" {105		check.error(ident, InvalidMainDecl, "cannot declare main - must be func")106		return107	}108109	check.declare(check.pkg.scope, ident, obj, nopos)110	check.objMap[obj] = d111	obj.setOrder(uint32(len(check.objMap)))112}113114// filename returns a filename suitable for debugging output.115func (check *Checker) filename(fileNo int) string {116	file := check.files[fileNo]117	if pos := file.Pos(); pos.IsKnown() {118		// return check.fset.File(pos).Name()119		// TODO(gri) do we need the actual file name here?120		return pos.RelFilename()121	}122	return fmt.Sprintf("file[%d]", fileNo)123}124125func (check *Checker) importPackage(pos syntax.Pos, path, dir string) *Package {126	// If we already have a package for the given (path, dir)127	// pair, use it instead of doing a full import.128	// Checker.impMap only caches packages that are marked Complete129	// or fake (dummy packages for failed imports). Incomplete but130	// non-fake packages do require an import to complete them.131	key := importKey{path, dir}132	imp := check.impMap[key]133	if imp != nil {134		return imp135	}136137	// no package yet => import it138	if path == "C" && (check.conf.FakeImportC || check.conf.go115UsesCgo) {139		if check.conf.FakeImportC && check.conf.go115UsesCgo {140			check.error(pos, BadImportPath, "cannot use FakeImportC and go115UsesCgo together")141		}142		imp = NewPackage("C", "C")143		imp.fake = true // package scope is not populated144		imp.cgo = check.conf.go115UsesCgo145	} else {146		// ordinary import147		var err error148		if importer := check.conf.Importer; importer == nil {149			err = fmt.Errorf("Config.Importer not installed")150		} else if importerFrom, ok := importer.(ImporterFrom); ok {151			imp, err = importerFrom.ImportFrom(path, dir, 0)152			if imp == nil && err == nil {153				err = fmt.Errorf("Config.Importer.ImportFrom(%s, %s, 0) returned nil but no error", path, dir)154			}155		} else {156			imp, err = importer.Import(path)157			if imp == nil && err == nil {158				err = fmt.Errorf("Config.Importer.Import(%s) returned nil but no error", path)159			}160		}161		// make sure we have a valid package name162		// (errors here can only happen through manipulation of packages after creation)163		if err == nil && imp != nil && (imp.name == "_" || imp.name == "") {164			err = fmt.Errorf("invalid package name: %q", imp.name)165			imp = nil // create fake package below166		}167		if err != nil {168			check.errorf(pos, BrokenImport, "could not import %s (%s)", path, err)169			if imp == nil {170				// create a new fake package171				// come up with a sensible package name (heuristic)172				name := strings.TrimSuffix(path, "/")173				if i := strings.LastIndex(name, "/"); i >= 0 {174					name = name[i+1:]175				}176				imp = NewPackage(path, name)177			}178			// continue to use the package as best as we can179			imp.fake = true // avoid follow-up lookup failures180		}181	}182183	// package should be complete or marked fake, but be cautious184	if imp.complete || imp.fake {185		check.impMap[key] = imp186		// Once we've formatted an error message, keep the pkgPathMap187		// up-to-date on subsequent imports. It is used for package188		// qualification in error messages.189		if check.pkgPathMap != nil {190			check.markImports(imp)191		}192		return imp193	}194195	// something went wrong (importer may have returned incomplete package without error)196	return nil197}198199// collectObjects collects all file and package objects and inserts them200// into their respective scopes. It also performs imports and associates201// methods with receiver base type names.202func (check *Checker) collectObjects() {203	pkg := check.pkg204205	// pkgImports is the set of packages already imported by any package file seen206	// so far. Used to avoid duplicate entries in pkg.imports. Allocate and populate207	// it (pkg.imports may not be empty if we are checking test files incrementally).208	// Note that pkgImports is keyed by package (and thus package path), not by an209	// importKey value. Two different importKey values may map to the same package210	// which is why we cannot use the check.impMap here.211	var pkgImports = make(map[*Package]bool)212	for _, imp := range pkg.imports {213		pkgImports[imp] = true214	}215216	type methodInfo struct {217		obj  *Func        // method218		ptr  bool         // true if pointer receiver219		recv *syntax.Name // receiver type name220	}221	var methods []methodInfo // collected methods with valid receivers and non-blank _ names222223	fileScopes := make([]*Scope, len(check.files)) // fileScopes[i] corresponds to check.files[i]224	for fileNo, file := range check.files {225		check.version = asGoVersion(check.versions[file.Pos().FileBase()])226227		// The package identifier denotes the current package,228		// but there is no corresponding package object.229		check.recordDef(file.PkgName, nil)230231		fileScope := NewScope(pkg.scope, syntax.StartPos(file), syntax.EndPos(file), check.filename(fileNo))232		fileScopes[fileNo] = fileScope233		check.recordScope(file, fileScope)234235		// determine file directory, necessary to resolve imports236		// FileName may be "" (typically for tests) in which case237		// we get "." as the directory which is what we would want.238		fileDir := dir(file.PkgName.Pos().RelFilename()) // TODO(gri) should this be filename?239240		first := -1                // index of first ConstDecl in the current group, or -1241		var last *syntax.ConstDecl // last ConstDecl with init expressions, or nil242		for index, decl := range file.DeclList {243			if _, ok := decl.(*syntax.ConstDecl); !ok {244				first = -1 // we're not in a constant declaration245			}246247			switch s := decl.(type) {248			case *syntax.ImportDecl:249				// import package250				if s.Path == nil || s.Path.Bad {251					continue // error reported during parsing252				}253				path, err := validatedImportPath(s.Path.Value)254				if err != nil {255					check.errorf(s.Path, BadImportPath, "invalid import path (%s)", err)256					continue257				}258259				imp := check.importPackage(s.Path.Pos(), path, fileDir)260				if imp == nil {261					continue262				}263264				// local name overrides imported package name265				name := imp.name266				if s.LocalPkgName != nil {267					name = s.LocalPkgName.Value268					if path == "C" {269						// match 1.17 cmd/compile (not prescribed by spec)270						check.error(s.LocalPkgName, ImportCRenamed, `cannot rename import "C"`)271						continue272					}273				}274275				if name == "init" {276					check.error(s, InvalidInitDecl, "cannot import package as init - init must be a func")277					continue278				}279280				// add package to list of explicit imports281				// (this functionality is provided as a convenience282				// for clients; it is not needed for type-checking)283				if !pkgImports[imp] {284					pkgImports[imp] = true285					pkg.imports = append(pkg.imports, imp)286				}287288				pkgName := NewPkgName(s.Pos(), pkg, name, imp)289				if s.LocalPkgName != nil {290					// in a dot-import, the dot represents the package291					check.recordDef(s.LocalPkgName, pkgName)292				} else {293					check.recordImplicit(s, pkgName)294				}295296				if imp.fake {297					// match 1.17 cmd/compile (not prescribed by spec)298					check.usedPkgNames[pkgName] = true299				}300301				// add import to file scope302				check.imports = append(check.imports, pkgName)303				if name == "." {304					// dot-import305					if check.dotImportMap == nil {306						check.dotImportMap = make(map[dotImportKey]*PkgName)307					}308					// merge imported scope with file scope309					for name, obj := range imp.scope.elems {310						// Note: Avoid eager resolve(name, obj) here, so we only311						// resolve dot-imported objects as needed.312313						// A package scope may contain non-exported objects,314						// do not import them!315						if isExported(name) {316							// declare dot-imported object317							// (Do not use check.declare because it modifies the object318							// via Object.setScopePos, which leads to a race condition;319							// the object may be imported into more than one file scope320							// concurrently. See go.dev/issue/32154.)321							if alt := fileScope.Lookup(name); alt != nil {322								err := check.newError(DuplicateDecl)323								err.addf(s.LocalPkgName, "%s redeclared in this block", alt.Name())324								err.addAltDecl(alt)325								err.report()326							} else {327								fileScope.insert(name, obj)328								check.dotImportMap[dotImportKey{fileScope, name}] = pkgName329							}330						}331					}332				} else {333					// declare imported package object in file scope334					// (no need to provide s.LocalPkgName since we called check.recordDef earlier)335					check.declare(fileScope, nil, pkgName, nopos)336				}337338			case *syntax.ConstDecl:339				// iota is the index of the current constDecl within the group340				if first < 0 || s.Group == nil || file.DeclList[index-1].(*syntax.ConstDecl).Group != s.Group {341					first = index342					last = nil343				}344				iota := constant.MakeInt64(int64(index - first))345346				// determine which initialization expressions to use347				inherited := true348				switch {349				case s.Type != nil || s.Values != nil:350					last = s351					inherited = false352				case last == nil:353					last = new(syntax.ConstDecl) // make sure last exists354					inherited = false355				}356357				// declare all constants358				values := syntax.UnpackListExpr(last.Values)359				for i, name := range s.NameList {360					obj := NewConst(name.Pos(), pkg, name.Value, nil, iota)361362					var init syntax.Expr363					if i < len(values) {364						init = values[i]365					}366367					d := &declInfo{file: fileScope, version: check.version, vtyp: last.Type, init: init, inherited: inherited}368					check.declarePkgObj(name, obj, d)369				}370371				// Constants must always have init values.372				check.arity(s.Pos(), s.NameList, values, true, inherited)373374			case *syntax.VarDecl:375				lhs := make([]*Var, len(s.NameList))376				// If there's exactly one rhs initializer, use377				// the same declInfo d1 for all lhs variables378				// so that each lhs variable depends on the same379				// rhs initializer (n:1 var declaration).380				var d1 *declInfo381				if _, ok := s.Values.(*syntax.ListExpr); !ok {382					// The lhs elements are only set up after the for loop below,383					// but that's ok because declarePkgObj only collects the declInfo384					// for a later phase.385					d1 = &declInfo{file: fileScope, version: check.version, lhs: lhs, vtyp: s.Type, init: s.Values}386				}387388				// declare all variables389				values := syntax.UnpackListExpr(s.Values)390				for i, name := range s.NameList {391					obj := newVar(PackageVar, name.Pos(), pkg, name.Value, nil)392					lhs[i] = obj393394					d := d1395					if d == nil {396						// individual assignments397						var init syntax.Expr398						if i < len(values) {399							init = values[i]400						}401						d = &declInfo{file: fileScope, version: check.version, vtyp: s.Type, init: init}402					}403404					check.declarePkgObj(name, obj, d)405				}406407				// If we have no type, we must have values.408				if s.Type == nil || values != nil {409					check.arity(s.Pos(), s.NameList, values, false, false)410				}411412			case *syntax.TypeDecl:413				obj := NewTypeName(s.Name.Pos(), pkg, s.Name.Value, nil)414				check.declarePkgObj(s.Name, obj, &declInfo{file: fileScope, version: check.version, tdecl: s})415416			case *syntax.FuncDecl:417				name := s.Name.Value418				obj := NewFunc(s.Name.Pos(), pkg, name, nil) // signature set later419				var tparam0 *syntax.Field420				if len(s.TParamList) > 0 {421					tparam0 = s.TParamList[0]422				}423				if s.Recv == nil {424					// regular function425					if name == "init" || name == "main" && pkg.name == "main" {426						// init and main functions must not declare type and ordinary parameters or results427						code := InvalidInitDecl428						if name == "main" {429							code = InvalidMainDecl430						}431						if tparam0 != nil {432							check.softErrorf(tparam0, code, "func %s must have no type parameters", name)433						}434						if t := s.Type; len(t.ParamList) != 0 || len(t.ResultList) != 0 {435							check.softErrorf(s.Name, code, "func %s must have no arguments and no return values", name)436						}437					} else {438						_ = tparam0 != nil && check.verifyVersionf(tparam0, go1_18, "type parameter")439					}440					// don't declare init functions in the package scope - they are invisible441					if name == "init" {442						obj.parent = pkg.scope443						check.recordDef(s.Name, obj)444						if s.Body == nil {445							check.softErrorf(obj.pos, MissingInitBody, "func init must have a body")446						}447					} else {448						check.declare(pkg.scope, s.Name, obj, nopos)449					}450				} else {451					// method452					// d.Recv != nil453					ptr, base, _ := check.unpackRecv(s.Recv.Type, false)454					// Methods with invalid receiver cannot be associated to a type, and455					// methods with blank _ names are never found; no need to collect any456					// of them. They will still be type-checked with all the other functions.457					if recv, _ := base.(*syntax.Name); recv != nil && name != "_" {458						methods = append(methods, methodInfo{obj, ptr, recv})459					}460					_ = tparam0 != nil && check.verifyVersionf(tparam0, go1_27, "generic method")461					check.recordDef(s.Name, obj)462				}463				info := &declInfo{file: fileScope, version: check.version, fdecl: s}464				// Methods are not package-level objects but we still track them in the465				// object map so that we can handle them like regular functions (if the466				// receiver is invalid); also we need their fdecl info when associating467				// them with their receiver base type, below.468				check.objMap[obj] = info469				obj.setOrder(uint32(len(check.objMap)))470471			default:472				check.errorf(s, InvalidSyntaxTree, "unknown syntax.Decl node %T", s)473			}474		}475	}476477	// verify that objects in package and file scopes have different names478	for _, scope := range fileScopes {479		for name, obj := range scope.elems {480			if alt := pkg.scope.Lookup(name); alt != nil {481				obj = resolve(name, obj)482				err := check.newError(DuplicateDecl)483				if pkg, ok := obj.(*PkgName); ok {484					err.addf(alt, "%s already declared through import of %s", alt.Name(), pkg.Imported())485					err.addAltDecl(pkg)486				} else {487					err.addf(alt, "%s already declared through dot-import of %s", alt.Name(), obj.Pkg())488					// TODO(gri) dot-imported objects don't have a position; addAltDecl won't print anything489					err.addAltDecl(obj)490				}491				err.report()492			}493		}494	}495496	// Now that we have all package scope objects and all methods,497	// associate methods with receiver base type name where possible.498	// Ignore methods that have an invalid receiver. They will be499	// type-checked later, with regular functions.500	if methods == nil {501		return502	}503504	check.methods = make(map[*TypeName][]*Func)505	for i := range methods {506		m := &methods[i]507		// Determine the receiver base type and associate m with it.508		ptr, base := check.resolveBaseTypeName(m.ptr, m.recv)509		if base != nil {510			m.obj.hasPtrRecv_ = ptr511			check.methods[base] = append(check.methods[base], m.obj)512		}513	}514}515516// sortObjects sorts package-level objects by source-order for reproducible processing517func (check *Checker) sortObjects() {518	check.objList = make([]Object, len(check.objMap))519	i := 0520	for obj := range check.objMap {521		check.objList[i] = obj522		i++523	}524	slices.SortFunc(check.objList, func(a, b Object) int {525		return cmp.Compare(a.order(), b.order())526	})527}528529// unpackRecv unpacks a receiver type expression and returns its components: ptr indicates530// whether rtyp is a pointer receiver, base is the receiver base type expression stripped531// of its type parameters (if any), and tparams are its type parameter names, if any. The532// type parameters are only unpacked if unpackParams is set. For instance, given the rtyp533//534//	*T[A, _]535//536// ptr is true, base is T, and tparams is [A, _] (assuming unpackParams is set).537// Note that base may not be a *syntax.Name for erroneous programs.538func (check *Checker) unpackRecv(rtyp syntax.Expr, unpackParams bool) (ptr bool, base syntax.Expr, tparams []*syntax.Name) {539	// unpack receiver type540	base = syntax.Unparen(rtyp)541	if t, _ := base.(*syntax.Operation); t != nil && t.Op == syntax.Mul && t.Y == nil {542		ptr = true543		base = syntax.Unparen(t.X)544	}545546	// unpack type parameters, if any547	if ptyp, _ := base.(*syntax.IndexExpr); ptyp != nil {548		base = ptyp.X549		if unpackParams {550			for _, arg := range syntax.UnpackListExpr(ptyp.Index) {551				var par *syntax.Name552				switch arg := arg.(type) {553				case *syntax.Name:554					par = arg555				case *syntax.BadExpr:556					// ignore - error already reported by parser557				case nil:558					check.error(ptyp, InvalidSyntaxTree, "parameterized receiver contains nil parameters")559				default:560					check.errorf(arg, BadDecl, "receiver type parameter %s must be an identifier", arg)561				}562				if par == nil {563					par = syntax.NewName(arg.Pos(), "_")564				}565				tparams = append(tparams, par)566			}567568		}569	}570571	return572}573574// resolveBaseTypeName returns the non-alias base type name for the given name, and whether575// there was a pointer indirection to get to it. The base type name must be declared576// in package scope, and there can be at most one pointer indirection. Traversals577// through generic alias types are not permitted. If no such type name exists, the578// returned base is nil.579func (check *Checker) resolveBaseTypeName(ptr bool, name *syntax.Name) (ptr_ bool, base *TypeName) {580	// Algorithm: Starting from name, which is expected to denote a type,581	// we follow that type through non-generic alias declarations until582	// we reach a non-alias type name.583	var seen map[*TypeName]bool584	for name != nil {585		// name must denote an object found in the current package scope586		// (note that dot-imported objects are not in the package scope!)587		obj := check.pkg.scope.Lookup(name.Value)588		if obj == nil {589			break590		}591592		// the object must be a type name...593		tname, _ := obj.(*TypeName)594		if tname == nil {595			break596		}597598		// ... which we have not seen before599		if seen[tname] {600			break601		}602603		// we're done if tdecl describes a defined type (not an alias)604		tdecl := check.objMap[tname].tdecl // must exist for objects in package scope605		if !tdecl.Alias {606			return ptr, tname607		}608609		// an alias must not be generic610		// (importantly, we must not collect such methods - was https://go.dev/issue/70417)611		if tdecl.TParamList != nil {612			break613		}614615		// otherwise, remember this type name and continue resolving616		if seen == nil {617			seen = make(map[*TypeName]bool)618		}619		seen[tname] = true620621		// The syntax parser strips unnecessary parentheses; call Unparen for consistency with go/types.622		typ := syntax.Unparen(tdecl.Type)623624		// dereference a pointer type625		if pexpr, _ := typ.(*syntax.Operation); pexpr != nil && pexpr.Op == syntax.Mul && pexpr.Y == nil {626			// if we've already seen a pointer, we're done627			if ptr {628				break629			}630			ptr = true631			typ = syntax.Unparen(pexpr.X) // continue with pointer base type632		}633634		// After dereferencing, typ must be a locally defined type name.635		// Referring to other packages (qualified identifiers) or going636		// through instantiated types (index expressions) is not permitted,637		// so we can ignore those.638		name, _ = typ.(*syntax.Name)639	}640641	// no base type found642	return false, nil643}644645// packageObjects typechecks all package objects, but not function bodies.646func (check *Checker) packageObjects() {647	// add new methods to already type-checked types (from a prior Checker.Files call)648	for _, obj := range check.objList {649		if obj, _ := obj.(*TypeName); obj != nil && obj.typ != nil {650			check.collectMethods(obj)651		}652	}653654	if false {655		// TODO: determine if we can enable this code now or656		//       if there are still problems with cycles and657		//       aliases.658		//659		// For example, in GOROOT/test/typeparam/issue50259.go,660		//661		// 	type T[_ any] struct{}662		// 	type A T[B]663		// 	type B = T[A]664		//665		// TypeName A has Type Named during checking, but by666		// the time the unified export data is written out,667		// its Type is Invalid.668		//669		// Investigate and reenable this branch.670		for _, obj := range check.objList {671			check.objDecl(obj)672		}673	} else {674		// To avoid problems with cycles, process non-alias type declarations first, followed by675		// alias declarations, and then everything else. This appears to avoid most situations676		// where the type of an alias is needed before it is available.677		// There may still be cases where this is not good enough (see also go.dev/issue/25838).678		// In those cases Checker.ident will report an error ("invalid use of type alias").679		var aliasList []*TypeName680		var othersList []Object // everything that's not a type681		// phase 1: non-alias type declarations682		for _, obj := range check.objList {683			if tname, _ := obj.(*TypeName); tname != nil {684				if check.objMap[tname].tdecl.Alias {685					aliasList = append(aliasList, tname)686				} else {687					check.objDecl(obj)688				}689			} else {690				othersList = append(othersList, obj)691			}692		}693		// phase 2: alias type declarations694		for _, obj := range aliasList {695			check.objDecl(obj)696		}697		// phase 3: all other declarations698		for _, obj := range othersList {699			check.objDecl(obj)700		}701	}702703	// At this point we may have a non-empty check.methods map; this means that not all704	// entries were deleted at the end of typeDecl because the respective receiver base705	// types were not found. In that case, an error was reported when declaring those706	// methods. We can now safely discard this map.707	check.methods = nil708}709710// unusedImports checks for unused imports.711func (check *Checker) unusedImports() {712	// If function bodies are not checked, packages' uses are likely missing - don't check.713	if check.conf.IgnoreFuncBodies {714		return715	}716717	// spec: "It is illegal (...) to directly import a package without referring to718	// any of its exported identifiers. To import a package solely for its side-effects719	// (initialization), use the blank identifier as explicit package name."720721	for _, obj := range check.imports {722		if obj.name != "_" && !check.usedPkgNames[obj] {723			check.errorUnusedPkg(obj)724		}725	}726}727728func (check *Checker) errorUnusedPkg(obj *PkgName) {729	// If the package was imported with a name other than the final730	// import path element, show it explicitly in the error message.731	// Note that this handles both renamed imports and imports of732	// packages containing unconventional package declarations.733	// Note that this uses / always, even on Windows, because Go import734	// paths always use forward slashes.735	path := obj.imported.path736	elem := path737	if i := strings.LastIndex(elem, "/"); i >= 0 {738		elem = elem[i+1:]739	}740	if obj.name == "" || obj.name == "." || obj.name == elem {741		check.softErrorf(obj, UnusedImport, "%q imported and not used", path)742	} else {743		check.softErrorf(obj, UnusedImport, "%q imported as %s and not used", path, obj.name)744	}745}746747// dir makes a good-faith attempt to return the directory748// portion of path. If path is empty, the result is ".".749// (Per the go/build package dependency tests, we cannot import750// path/filepath and simply use filepath.Dir.)751func dir(path string) string {752	if i := strings.LastIndexAny(path, `/\`); i > 0 {753		return path[:i]754	}755	// i <= 0756	return "."757}

Code quality findings 24

Ensure errors are handled or logged
warning correctness unhandled-error
if err != nil {
Blank identifier discarding results; verify intentional ignoring of return values
warning correctness blank-identifier-discard
_ = tparam0 != nil && check.verifyVersionf(tparam0, go1_18, "type parameter")
Blank identifier discarding results; verify intentional ignoring of return values
warning correctness blank-identifier-discard
_ = tparam0 != nil && check.verifyVersionf(tparam0, go1_27, "generic method")
Blank identifier discarding results; verify intentional ignoring of return values
warning correctness blank-identifier-discard
m.obj.hasPtrRecv_ = ptr
Blank identifier discarding results; verify intentional ignoring of return values
warning correctness blank-identifier-discard
name, _ = typ.(*syntax.Name)
Error string starts with uppercase; per Go convention error strings should not be capitalized or end with punctuation
info maintainability error-string-format
err = fmt.Errorf("Config.Importer not installed")
Error string starts with uppercase; per Go convention error strings should not be capitalized or end with punctuation
info maintainability error-string-format
err = fmt.Errorf("Config.Importer.ImportFrom(%s, %s, 0) returned nil but no error", path, dir)
Error string starts with uppercase; per Go convention error strings should not be capitalized or end with punctuation
info maintainability error-string-format
err = fmt.Errorf("Config.Importer.Import(%s) returned nil but no error", path)
Map created without size hint before being populated in a loop; provide capacity hint to reduce allocations
info performance map-without-size-hint
var pkgImports = make(map[*Package]bool)
Range over slice copies each element by value; use index or pointer receiver for large structs to avoid copies
info performance copy-large-struct
for fileNo, file := range check.files {
Range over slice copies each element by value; use index or pointer receiver for large structs to avoid copies
info performance copy-large-struct
for index, decl := range file.DeclList {
Type switch without default case; unhandled types will silently do nothing. Add a default case for safety
info correctness unchecked-type-switch
switch s := decl.(type) {
Multiple appends without pre-allocation; use make() with capacity when size is known
info performance append-without-prealloc
pkg.imports = append(pkg.imports, imp)
Map created without size hint before being populated in a loop; provide capacity hint to reduce allocations
info performance map-without-size-hint
check.dotImportMap = make(map[dotImportKey]*PkgName)
Range over slice copies each element by value; use index or pointer receiver for large structs to avoid copies
info performance copy-large-struct
for name, obj := range imp.scope.elems {
Range over slice copies each element by value; use index or pointer receiver for large structs to avoid copies
info performance copy-large-struct
for i, name := range s.NameList {
Range over slice copies each element by value; use index or pointer receiver for large structs to avoid copies
info performance copy-large-struct
for i, name := range s.NameList {
Range over slice copies each element by value; use index or pointer receiver for large structs to avoid copies
info performance copy-large-struct
for name, obj := range scope.elems {
Map created without size hint before being populated in a loop; provide capacity hint to reduce allocations
info performance map-without-size-hint
check.methods = make(map[*TypeName][]*Func)
Deeply nested control structures reduce readability; consider extracting to functions or using early returns
info maintainability deep-nesting
// unpack type parameters, if any
Deeply nested control structures reduce readability; consider extracting to functions or using early returns
info maintainability deep-nesting
if ptyp, _ := base.(*syntax.IndexExpr); ptyp != nil {
Multiple appends without pre-allocation; use make() with capacity when size is known
info performance append-without-prealloc
tparams = append(tparams, par)
Multiple appends without pre-allocation; use make() with capacity when size is known
info performance append-without-prealloc
aliasList = append(aliasList, tname)
Multiple appends without pre-allocation; use make() with capacity when size is known
info performance append-without-prealloc
othersList = append(othersList, obj)

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.