Source File
check.go
Belonging Package
go/types
// Copyright 2011 The Go Authors. All rights reserved.// Use of this source code is governed by a BSD-style// license that can be found in the LICENSE file.// This file implements the Check function, which drives type-checking.package typesimport (.)// nopos, noposn indicate an unknown positionvar nopos token.Posvar noposn = atPos(nopos)// debugging/development supportconst debug = false // leave on during development// position tracing for panics during type checkingconst tracePos = false // TODO(markfreeman): check performance implications// gotypesalias controls the use of Alias types.// As of Apr 16 2024 they are used by default.// To disable their use, set GODEBUG to gotypesalias=0.// This GODEBUG flag will be removed in the near future (tentatively Go 1.24).var gotypesalias = godebug.New("gotypesalias")// _aliasAny changes the behavior of [Scope.Lookup] for "any" in the// [Universe] scope.//// This is necessary because while Alias creation is controlled by// [Config._EnableAlias], based on the gotypealias variable, the representation// of "any" is a global. In [Scope.Lookup], we select this global// representation based on the result of [aliasAny], but as a result need to// guard against this behavior changing during the type checking pass.// Therefore we implement the following rule: any number of goroutines can type// check concurrently with the same EnableAlias value, but if any goroutine// tries to type check concurrently with a different EnableAlias value, we// panic.//// To achieve this, _aliasAny is a state machine://// 0: no type checking is occurring// negative: type checking is occurring without _EnableAlias set// positive: type checking is occurring with _EnableAlias setvar _aliasAny int32func aliasAny() bool {:= gotypesalias.Value():= != "0":= atomic.LoadInt32(&_aliasAny)if != 0 && != ( > 0) {panic(fmt.Sprintf("gotypealias mutated during type checking, gotypesalias=%s, inuse=%d", , ))}return}// exprInfo stores information about an untyped expression.type exprInfo struct {isLhs bool // expression is lhs operand of a shift with delayed type-checkmode operandModetyp *Basicval constant.Value // constant value; or nil (if not a constant)}// An environment represents the environment within which an object is// type-checked.type environment struct {decl *declInfo // package-level declaration whose init expression/function body is checkedscope *Scope // top-most scope for lookupsversion goVersion // current accepted language version; changes across filesiota constant.Value // value of iota in a constant declaration; nil otherwiseerrpos positioner // if set, identifier position of a constant with inherited initializerinTParamList bool // set if inside a type parameter listsig *Signature // function signature if inside a function; nil otherwiseisPanic map[*ast.CallExpr]bool // set of panic call expressions (used for termination check)hasLabel bool // set if a function makes use of labels (only ~1% of functions); unused outside functionshasCallOrRecv bool // set if an expression contains a function call or channel receive operation// go/types onlyexprPos token.Pos // if valid, identifiers are looked up as if at position pos (used by CheckExpr, Eval)}// lookupScope looks up name in the current environment and if an object// is found it returns the scope containing the object and the object.// Otherwise it returns (nil, nil).//// Note that obj.Parent() may be different from the returned scope if the// object was inserted into the scope and already had a parent at that// time (see Scope.Insert). This can only happen for dot-imported objects// whose parent is the scope of the package that exported them.func ( *environment) ( string) (*Scope, Object) {for := .scope; != nil; = .parent {if := .Lookup(); != nil && (!.exprPos.IsValid() || cmpPos(.scopePos(), .exprPos) <= 0) {return ,}}return nil, nil}// lookup is like lookupScope but it only returns the object (or nil).func ( *environment) ( string) Object {, := .lookupScope()return}// An importKey identifies an imported package by import path and source directory// (directory containing the file containing the import). In practice, the directory// may always be the same, or may not matter. Given an (import path, directory), an// importer must always return the same package (but given two different import paths,// an importer may still return the same package by mapping them to the same package// paths).type importKey struct {path, dir string}// A dotImportKey describes a dot-imported object in the given scope.type dotImportKey struct {scope *Scopename string}// An action describes a (delayed) action.type action struct {version goVersion // applicable language versionf func() // action to be executeddesc *actionDesc // action description; may be nil, requires debug to be set}// If debug is set, describef sets a printf-formatted description for action a.// Otherwise, it is a no-op.func ( *action) ( positioner, string, ...any) {if debug {.desc = &actionDesc{, , }}}// An actionDesc provides information on an action.// For debugging only.type actionDesc struct {pos positionerformat stringargs []any}// A Checker maintains the state of the type checker.// It must be created with [NewChecker].type Checker struct {// package information// (initialized by NewChecker, valid for the life-time of checker)conf *Configctxt *Context // context for de-duplicating instancesfset *token.FileSetpkg *Package*InfonextID uint64 // unique Id for type parameters (first valid Id is 1)objMap map[Object]*declInfo // maps package-level objects and (non-interface) methods to declaration infoimpMap map[importKey]*Package // maps (import path, source directory) to (complete or fake) package// see TODO in validtype.go// valids instanceLookup // valid *Named (incl. instantiated) types per the validType check// pkgPathMap maps package names to the set of distinct import paths we've// seen for that name, anywhere in the import graph. It is used for// disambiguating package names in error messages.//// pkgPathMap is allocated lazily, so that we don't pay the price of building// it on the happy path. seenPkgMap tracks the packages that we've already// walked.pkgPathMap map[string]map[string]boolseenPkgMap map[*Package]bool// information collected during type-checking of a set of package files// (initialized by Files, valid only for the duration of check.Files;// maps and lists are allocated on demand)files []*ast.File // package filesversions map[*ast.File]string // maps files to goVersion strings (each file has an entry); shared with Info.FileVersions if present; may be unaltered Config.GoVersionimports []*PkgName // list of imported packagesdotImportMap map[dotImportKey]*PkgName // maps dot-imported objects to the package they were dot-imported throughbrokenAliases map[*TypeName]bool // set of aliases with broken (not yet determined) typesunionTypeSets map[*Union]*_TypeSet // computed type sets for union typesusedVars map[*Var]bool // set of used variablesusedPkgNames map[*PkgName]bool // set of used package namesmono monoGraph // graph for detecting non-monomorphizable instantiation loopsfirstErr error // first error encounteredmethods map[*TypeName][]*Func // maps package scope type names to associated non-blank (non-interface) methodsuntyped map[ast.Expr]exprInfo // map of expressions without final typedelayed []action // stack of delayed action segments; segments are processed in FIFO orderobjPath []Object // path of object dependencies during type inference (for cycle reporting)cleaners []cleaner // list of types that may need a final cleanup at the end of type-checking// environment within which the current object is type-checked (valid only// for the duration of type-checking a specific object)environment// debuggingposStack []positioner // stack of source positions seen; used for panic tracingindent int // indentation for tracing}// addDeclDep adds the dependency edge (check.decl -> to) if check.decl existsfunc ( *Checker) ( Object) {:= .declif == nil {return // not in a package-level init expression}if , := .objMap[]; ! {return // to is not a package-level object}.addDep()}// Note: The following three alias-related functions are only used// when Alias types are not enabled.// brokenAlias records that alias doesn't have a determined type yet.// It also sets alias.typ to Typ[Invalid].// Not used if check.conf._EnableAlias is set.func ( *Checker) ( *TypeName) {assert(!.conf._EnableAlias)if .brokenAliases == nil {.brokenAliases = make(map[*TypeName]bool)}.brokenAliases[] = true.typ = Typ[Invalid]}// validAlias records that alias has the valid type typ (possibly Typ[Invalid]).func ( *Checker) ( *TypeName, Type) {assert(!.conf._EnableAlias)delete(.brokenAliases, ).typ =}// isBrokenAlias reports whether alias doesn't have a determined type yet.func ( *Checker) ( *TypeName) bool {assert(!.conf._EnableAlias)return .brokenAliases[]}func ( *Checker) ( ast.Expr, bool, operandMode, *Basic, constant.Value) {:= .untypedif == nil {= make(map[ast.Expr]exprInfo).untyped =}[] = exprInfo{, , , }}// later pushes f on to the stack of actions that will be processed later;// either at the end of the current statement, or in case of a local constant// or variable declaration, before the constant or variable is in scope// (so that f still sees the scope before any new declarations).// later returns the pushed action so one can provide a description// via action.describef for debugging, if desired.func ( *Checker) ( func()) *action {:= len(.delayed).delayed = append(.delayed, action{version: .version, f: })return &.delayed[]}// push pushes obj onto the object path and returns its index in the path.func ( *Checker) ( Object) int {.objPath = append(.objPath, )return len(.objPath) - 1}// pop pops and returns the topmost object from the object path.func ( *Checker) () Object {:= len(.objPath) - 1:= .objPath[].objPath[] = nil.objPath = .objPath[:]return}type cleaner interface {cleanup()}// needsCleanup records objects/types that implement the cleanup method// which will be called at the end of type-checking.func ( *Checker) ( cleaner) {.cleaners = append(.cleaners, )}// NewChecker returns a new [Checker] instance for a given package.// [Package] files may be added incrementally via checker.Files.func ( *Config, *token.FileSet, *Package, *Info) *Checker {// make sure we have a configurationif == nil {= new(Config)}// make sure we have an info structif == nil {= new(Info)}// Note: clients may call NewChecker with the Unsafe package, which is// globally shared and must not be mutated. Therefore NewChecker must not// mutate *pkg.//// (previously, pkg.goVersion was mutated here: go.dev/issue/61212)// In go/types, conf._EnableAlias is controlled by gotypesalias.._EnableAlias = gotypesalias.Value() != "0"return &Checker{conf: ,ctxt: .Context,fset: ,pkg: ,Info: ,objMap: make(map[Object]*declInfo),impMap: make(map[importKey]*Package),usedVars: make(map[*Var]bool),usedPkgNames: make(map[*PkgName]bool),}}// initFiles initializes the files-specific portion of checker.// The provided files must all belong to the same package.func ( *Checker) ( []*ast.File) {// start with a clean slate (check.Files may be called multiple times)// TODO(gri): what determines which fields are zeroed out here, vs at the end// of checkFiles?.files = nil.imports = nil.dotImportMap = nil.firstErr = nil.methods = nil.untyped = nil.delayed = nil.objPath = nil.cleaners = nil// We must initialize usedVars and usedPkgNames both here and in NewChecker,// because initFiles is not called in the CheckExpr or Eval codepaths, yet we// want to free this memory at the end of Files ('used' predicates are// only needed in the context of a given file)..usedVars = make(map[*Var]bool).usedPkgNames = make(map[*PkgName]bool)// determine package name and collect valid files:= .pkgfor , := range {switch := .Name.Name; .name {case "":if != "_" {.name =} else {.error(.Name, BlankPkgName, "invalid package name _")}fallthroughcase :.files = append(.files, )default:.errorf(atPos(.Package), MismatchedPkgName, "package %s; expected package %s", , .name)// ignore this file}}// reuse Info.FileVersions if provided:= .Info.FileVersionsif == nil {= make(map[*ast.File]string)}.versions =:= asGoVersion(.conf.GoVersion)if .isValid() && len() > 0 && .cmp(go_current) > 0 {.errorf([0], TooNew, "package requires newer Go version %v (application built with %v)",, go_current)}// determine Go version for each filefor , := range .files {// use unaltered Config.GoVersion by default// (This version string may contain dot-release numbers as in go1.20.1,// unlike file versions which are Go language versions only, if valid.):= .conf.GoVersion// If the file specifies a version, use max(fileVersion, go1.21).if := asGoVersion(.GoVersion); .isValid() {// Go 1.21 introduced the feature of setting the go.mod// go line to an early version of Go and allowing //go:build lines// to set the Go version in a given file. Versions Go 1.21 and later// can be set backwards compatibly as that was the first version// files with go1.21 or later build tags could be built with.//// Set the version to max(fileVersion, go1.21): That will allow a// downgrade to a version before go1.22, where the for loop semantics// change was made, while being backwards compatible with versions of// go before the new //go:build semantics were introduced.= string(versionMax(, go1_21))// Report a specific error for each tagged file that's too new.// (Normally the build system will have filtered files by version,// but clients can present arbitrary files to the type checker.)if .cmp(go_current) > 0 {// Use position of 'package [p]' for types/types2 consistency.// (Ideally we would use the //build tag itself.).errorf(.Name, TooNew, "file requires newer Go version %v (application built with %v)", , go_current)}}[] =}}func versionMax(, goVersion) goVersion {if .cmp() < 0 {return}return}// pushPos pushes pos onto the pos stack.func ( *Checker) ( positioner) {.posStack = append(.posStack, )}// popPos pops from the pos stack.func ( *Checker) () {.posStack = .posStack[:len(.posStack)-1]}// A bailout panic is used for early termination.type bailout struct{}func ( *Checker) ( *error) {switch p := recover().(type) {case nil, bailout:// normal return or early exit* = .firstErrdefault:if len(.posStack) > 0 {:= func( []positioner) {for := len() - 1; >= 0; -- {fmt.Fprintf(os.Stderr, "\t%v\n", .fset.Position([].Pos()))}}fmt.Fprintln(os.Stderr, "The following panic happened checking types near:")if len(.posStack) <= 10 {(.posStack)} else {// if it's long, truncate the middle; it's least likely to help(.posStack[len(.posStack)-5:])fmt.Fprintln(os.Stderr, "\t...")(.posStack[:5])}}// re-panicpanic()}}// Files checks the provided files as part of the checker's package.func ( *Checker) ( []*ast.File) ( error) {if .pkg == Unsafe {// Defensive handling for Unsafe, which cannot be type checked, and must// not be mutated. See https://go.dev/issue/61212 for an example of where// Unsafe is passed to NewChecker.return nil}// Avoid early returns here! Nearly all errors can be// localized to a piece of syntax and needn't prevent// type-checking of the rest of the package.defer .handleBailout(&).checkFiles()return}// checkFiles type-checks the specified files. Errors are reported as// a side effect, not by returning early, to ensure that well-formed// syntax is properly type annotated even in a package containing// errors.func ( *Checker) ( []*ast.File) {// Ensure that _EnableAlias is consistent among concurrent type checking// operations. See the documentation of [_aliasAny] for details.if .conf._EnableAlias {if atomic.AddInt32(&_aliasAny, 1) <= 0 {panic("EnableAlias set while !EnableAlias type checking is ongoing")}defer atomic.AddInt32(&_aliasAny, -1)} else {if atomic.AddInt32(&_aliasAny, -1) >= 0 {panic("!EnableAlias set while EnableAlias type checking is ongoing")}defer atomic.AddInt32(&_aliasAny, 1)}:= func( string) {if .conf._Trace {fmt.Println()fmt.Println()}}("== initFiles ==").initFiles()("== collectObjects ==").collectObjects()("== packageObjects ==").packageObjects()("== processDelayed ==").processDelayed(0) // incl. all functions("== cleanup ==").cleanup()("== initOrder ==").initOrder()if !.conf.DisableUnusedImportCheck {("== unusedImports ==").unusedImports()}("== recordUntyped ==").recordUntyped()if .firstErr == nil {// TODO(mdempsky): Ensure monomorph is safe when errors exist..monomorph()}.pkg.goVersion = .conf.GoVersion.pkg.complete = true// no longer needed - release memory.imports = nil.dotImportMap = nil.pkgPathMap = nil.seenPkgMap = nil.brokenAliases = nil.unionTypeSets = nil.usedVars = nil.usedPkgNames = nil.ctxt = nil// TODO(gri): shouldn't the cleanup above occur after the bailout?// TODO(gri) There's more memory we should release at this point.}// processDelayed processes all delayed actions pushed after top.func ( *Checker) ( int) {// If each delayed action pushes a new action, the// stack will continue to grow during this loop.// However, it is only processing functions (which// are processed in a delayed fashion) that may// add more actions (such as nested functions), so// this is a sufficiently bounded process.:= .versionfor := ; < len(.delayed); ++ {:= &.delayed[]if .conf._Trace {if .desc != nil {.trace(.desc.pos.Pos(), "-- "+.desc.format, .desc.args...)} else {.trace(nopos, "-- delayed %p", .f)}}.version = .version // reestablish the effective Go version captured earlier.f() // may append to check.delayedif .conf._Trace {fmt.Println()}}assert( <= len(.delayed)) // stack must not have shrunk.delayed = .delayed[:].version =}// cleanup runs cleanup for all collected cleaners.func ( *Checker) () {// Don't use a range clause since Named.cleanup may add more cleaners.for := 0; < len(.cleaners); ++ {.cleaners[].cleanup()}.cleaners = nil}// go/types doesn't support recording of types directly in the AST.// dummy function to match types2 code.func ( *Checker) ( ast.Expr, operandMode, Type, constant.Value) {// nothing to do}// go/types doesn't support recording of types directly in the AST.// dummy function to match types2 code.func ( *Checker) ( ast.Expr, , Type) {// nothing to do}// instantiatedIdent determines the identifier of the type instantiated in expr.// Helper function for recordInstance in recording.go.func instantiatedIdent( ast.Expr) *ast.Ident {var ast.Exprswitch e := .(type) {case *ast.IndexExpr:= .Xcase *ast.IndexListExpr: // only exists in go/ast, not syntax= .Xcase *ast.SelectorExpr, *ast.Ident:=}switch x := .(type) {case *ast.Ident:returncase *ast.SelectorExpr:return .Sel}// extra debugging of go.dev/issue/63933panic(sprintf(nil, nil, true, "instantiated ident not found; please report: %s", ))}
![]() |
The pages are generated with Golds v0.7.9-preview. (GOOS=linux GOARCH=amd64) Golds is a Go 101 project developed by Tapir Liu. PR and bug reports are welcome and can be submitted to the issue list. Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds. |