github.com/q45/go@v0.0.0-20151101211701-a4fb8c13db3f/src/go/types/resolver.go (about)

     1  // Copyright 2013 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package types
     6  
     7  import (
     8  	"fmt"
     9  	"go/ast"
    10  	"go/constant"
    11  	"go/token"
    12  	pathLib "path"
    13  	"strconv"
    14  	"strings"
    15  	"unicode"
    16  )
    17  
    18  // A declInfo describes a package-level const, type, var, or func declaration.
    19  type declInfo struct {
    20  	file  *Scope        // scope of file containing this declaration
    21  	lhs   []*Var        // lhs of n:1 variable declarations, or nil
    22  	typ   ast.Expr      // type, or nil
    23  	init  ast.Expr      // init expression, or nil
    24  	fdecl *ast.FuncDecl // func declaration, or nil
    25  
    26  	deps map[Object]bool // type and init dependencies; lazily allocated
    27  	mark int             // for dependency analysis
    28  }
    29  
    30  // hasInitializer reports whether the declared object has an initialization
    31  // expression or function body.
    32  func (d *declInfo) hasInitializer() bool {
    33  	return d.init != nil || d.fdecl != nil && d.fdecl.Body != nil
    34  }
    35  
    36  // addDep adds obj as a dependency to d.
    37  func (d *declInfo) addDep(obj Object) {
    38  	m := d.deps
    39  	if m == nil {
    40  		m = make(map[Object]bool)
    41  		d.deps = m
    42  	}
    43  	m[obj] = true
    44  }
    45  
    46  // arityMatch checks that the lhs and rhs of a const or var decl
    47  // have the appropriate number of names and init exprs. For const
    48  // decls, init is the value spec providing the init exprs; for
    49  // var decls, init is nil (the init exprs are in s in this case).
    50  func (check *Checker) arityMatch(s, init *ast.ValueSpec) {
    51  	l := len(s.Names)
    52  	r := len(s.Values)
    53  	if init != nil {
    54  		r = len(init.Values)
    55  	}
    56  
    57  	switch {
    58  	case init == nil && r == 0:
    59  		// var decl w/o init expr
    60  		if s.Type == nil {
    61  			check.errorf(s.Pos(), "missing type or init expr")
    62  		}
    63  	case l < r:
    64  		if l < len(s.Values) {
    65  			// init exprs from s
    66  			n := s.Values[l]
    67  			check.errorf(n.Pos(), "extra init expr %s", n)
    68  			// TODO(gri) avoid declared but not used error here
    69  		} else {
    70  			// init exprs "inherited"
    71  			check.errorf(s.Pos(), "extra init expr at %s", init.Pos())
    72  			// TODO(gri) avoid declared but not used error here
    73  		}
    74  	case l > r && (init != nil || r != 1):
    75  		n := s.Names[r]
    76  		check.errorf(n.Pos(), "missing init expr for %s", n)
    77  	}
    78  }
    79  
    80  func validatedImportPath(path string) (string, error) {
    81  	s, err := strconv.Unquote(path)
    82  	if err != nil {
    83  		return "", err
    84  	}
    85  	if s == "" {
    86  		return "", fmt.Errorf("empty string")
    87  	}
    88  	const illegalChars = `!"#$%&'()*,:;<=>?[\]^{|}` + "`\uFFFD"
    89  	for _, r := range s {
    90  		if !unicode.IsGraphic(r) || unicode.IsSpace(r) || strings.ContainsRune(illegalChars, r) {
    91  			return s, fmt.Errorf("invalid character %#U", r)
    92  		}
    93  	}
    94  	return s, nil
    95  }
    96  
    97  // declarePkgObj declares obj in the package scope, records its ident -> obj mapping,
    98  // and updates check.objMap. The object must not be a function or method.
    99  func (check *Checker) declarePkgObj(ident *ast.Ident, obj Object, d *declInfo) {
   100  	assert(ident.Name == obj.Name())
   101  
   102  	// spec: "A package-scope or file-scope identifier with name init
   103  	// may only be declared to be a function with this (func()) signature."
   104  	if ident.Name == "init" {
   105  		check.errorf(ident.Pos(), "cannot declare init - must be func")
   106  		return
   107  	}
   108  
   109  	check.declare(check.pkg.scope, ident, obj, token.NoPos)
   110  	check.objMap[obj] = d
   111  	obj.setOrder(uint32(len(check.objMap)))
   112  }
   113  
   114  // filename returns a filename suitable for debugging output.
   115  func (check *Checker) filename(fileNo int) string {
   116  	file := check.files[fileNo]
   117  	if pos := file.Pos(); pos.IsValid() {
   118  		return check.fset.File(pos).Name()
   119  	}
   120  	return fmt.Sprintf("file[%d]", fileNo)
   121  }
   122  
   123  // collectObjects collects all file and package objects and inserts them
   124  // into their respective scopes. It also performs imports and associates
   125  // methods with receiver base type names.
   126  func (check *Checker) collectObjects() {
   127  	pkg := check.pkg
   128  
   129  	// pkgImports is the set of packages already imported by any package file seen
   130  	// so far. Used to avoid duplicate entries in pkg.imports. Allocate and populate
   131  	// it (pkg.imports may not be empty if we are checking test files incrementally).
   132  	var pkgImports = make(map[*Package]bool)
   133  	for _, imp := range pkg.imports {
   134  		pkgImports[imp] = true
   135  	}
   136  
   137  	for fileNo, file := range check.files {
   138  		// The package identifier denotes the current package,
   139  		// but there is no corresponding package object.
   140  		check.recordDef(file.Name, nil)
   141  
   142  		// Use the actual source file extent rather than *ast.File extent since the
   143  		// latter doesn't include comments which appear at the start or end of the file.
   144  		// Be conservative and use the *ast.File extent if we don't have a *token.File.
   145  		pos, end := file.Pos(), file.End()
   146  		if f := check.fset.File(file.Pos()); f != nil {
   147  			pos, end = token.Pos(f.Base()), token.Pos(f.Base()+f.Size())
   148  		}
   149  		fileScope := NewScope(check.pkg.scope, pos, end, check.filename(fileNo))
   150  		check.recordScope(file, fileScope)
   151  
   152  		for _, decl := range file.Decls {
   153  			switch d := decl.(type) {
   154  			case *ast.BadDecl:
   155  				// ignore
   156  
   157  			case *ast.GenDecl:
   158  				var last *ast.ValueSpec // last ValueSpec with type or init exprs seen
   159  				for iota, spec := range d.Specs {
   160  					switch s := spec.(type) {
   161  					case *ast.ImportSpec:
   162  						// import package
   163  						var imp *Package
   164  						path, err := validatedImportPath(s.Path.Value)
   165  						if err != nil {
   166  							check.errorf(s.Path.Pos(), "invalid import path (%s)", err)
   167  							continue
   168  						}
   169  						if path == "C" && check.conf.FakeImportC {
   170  							// TODO(gri) shouldn't create a new one each time
   171  							imp = NewPackage("C", "C")
   172  							imp.fake = true
   173  						} else if path == "unsafe" {
   174  							// package "unsafe" is known to the language
   175  							imp = Unsafe
   176  						} else {
   177  							if importer := check.conf.Importer; importer != nil {
   178  								imp, err = importer.Import(path)
   179  								if imp == nil && err == nil {
   180  									err = fmt.Errorf("Config.Importer.Import(%s) returned nil but no error", path)
   181  								}
   182  							} else {
   183  								err = fmt.Errorf("Config.Importer not installed")
   184  							}
   185  							if err != nil {
   186  								check.errorf(s.Path.Pos(), "could not import %s (%s)", path, err)
   187  								continue
   188  							}
   189  						}
   190  
   191  						// add package to list of explicit imports
   192  						// (this functionality is provided as a convenience
   193  						// for clients; it is not needed for type-checking)
   194  						if !pkgImports[imp] {
   195  							pkgImports[imp] = true
   196  							if imp != Unsafe {
   197  								pkg.imports = append(pkg.imports, imp)
   198  							}
   199  						}
   200  
   201  						// local name overrides imported package name
   202  						name := imp.name
   203  						if s.Name != nil {
   204  							name = s.Name.Name
   205  							if path == "C" {
   206  								// match cmd/compile (not prescribed by spec)
   207  								check.errorf(s.Name.Pos(), `cannot rename import "C"`)
   208  								continue
   209  							}
   210  							if name == "init" {
   211  								check.errorf(s.Name.Pos(), "cannot declare init - must be func")
   212  								continue
   213  							}
   214  						}
   215  
   216  						obj := NewPkgName(s.Pos(), pkg, name, imp)
   217  						if s.Name != nil {
   218  							// in a dot-import, the dot represents the package
   219  							check.recordDef(s.Name, obj)
   220  						} else {
   221  							check.recordImplicit(s, obj)
   222  						}
   223  
   224  						if path == "C" {
   225  							// match cmd/compile (not prescribed by spec)
   226  							obj.used = true
   227  						}
   228  
   229  						// add import to file scope
   230  						if name == "." {
   231  							// merge imported scope with file scope
   232  							for _, obj := range imp.scope.elems {
   233  								// A package scope may contain non-exported objects,
   234  								// do not import them!
   235  								if obj.Exported() {
   236  									// TODO(gri) When we import a package, we create
   237  									// a new local package object. We should do the
   238  									// same for each dot-imported object. That way
   239  									// they can have correct position information.
   240  									// (We must not modify their existing position
   241  									// information because the same package - found
   242  									// via Config.Packages - may be dot-imported in
   243  									// another package!)
   244  									check.declare(fileScope, nil, obj, token.NoPos)
   245  									check.recordImplicit(s, obj)
   246  								}
   247  							}
   248  							// add position to set of dot-import positions for this file
   249  							// (this is only needed for "imported but not used" errors)
   250  							check.addUnusedDotImport(fileScope, imp, s.Pos())
   251  						} else {
   252  							// declare imported package object in file scope
   253  							check.declare(fileScope, nil, obj, token.NoPos)
   254  						}
   255  
   256  					case *ast.ValueSpec:
   257  						switch d.Tok {
   258  						case token.CONST:
   259  							// determine which initialization expressions to use
   260  							switch {
   261  							case s.Type != nil || len(s.Values) > 0:
   262  								last = s
   263  							case last == nil:
   264  								last = new(ast.ValueSpec) // make sure last exists
   265  							}
   266  
   267  							// declare all constants
   268  							for i, name := range s.Names {
   269  								obj := NewConst(name.Pos(), pkg, name.Name, nil, constant.MakeInt64(int64(iota)))
   270  
   271  								var init ast.Expr
   272  								if i < len(last.Values) {
   273  									init = last.Values[i]
   274  								}
   275  
   276  								d := &declInfo{file: fileScope, typ: last.Type, init: init}
   277  								check.declarePkgObj(name, obj, d)
   278  							}
   279  
   280  							check.arityMatch(s, last)
   281  
   282  						case token.VAR:
   283  							lhs := make([]*Var, len(s.Names))
   284  							// If there's exactly one rhs initializer, use
   285  							// the same declInfo d1 for all lhs variables
   286  							// so that each lhs variable depends on the same
   287  							// rhs initializer (n:1 var declaration).
   288  							var d1 *declInfo
   289  							if len(s.Values) == 1 {
   290  								// The lhs elements are only set up after the for loop below,
   291  								// but that's ok because declareVar only collects the declInfo
   292  								// for a later phase.
   293  								d1 = &declInfo{file: fileScope, lhs: lhs, typ: s.Type, init: s.Values[0]}
   294  							}
   295  
   296  							// declare all variables
   297  							for i, name := range s.Names {
   298  								obj := NewVar(name.Pos(), pkg, name.Name, nil)
   299  								lhs[i] = obj
   300  
   301  								d := d1
   302  								if d == nil {
   303  									// individual assignments
   304  									var init ast.Expr
   305  									if i < len(s.Values) {
   306  										init = s.Values[i]
   307  									}
   308  									d = &declInfo{file: fileScope, typ: s.Type, init: init}
   309  								}
   310  
   311  								check.declarePkgObj(name, obj, d)
   312  							}
   313  
   314  							check.arityMatch(s, nil)
   315  
   316  						default:
   317  							check.invalidAST(s.Pos(), "invalid token %s", d.Tok)
   318  						}
   319  
   320  					case *ast.TypeSpec:
   321  						obj := NewTypeName(s.Name.Pos(), pkg, s.Name.Name, nil)
   322  						check.declarePkgObj(s.Name, obj, &declInfo{file: fileScope, typ: s.Type})
   323  
   324  					default:
   325  						check.invalidAST(s.Pos(), "unknown ast.Spec node %T", s)
   326  					}
   327  				}
   328  
   329  			case *ast.FuncDecl:
   330  				name := d.Name.Name
   331  				obj := NewFunc(d.Name.Pos(), pkg, name, nil)
   332  				if d.Recv == nil {
   333  					// regular function
   334  					if name == "init" {
   335  						// don't declare init functions in the package scope - they are invisible
   336  						obj.parent = pkg.scope
   337  						check.recordDef(d.Name, obj)
   338  						// init functions must have a body
   339  						if d.Body == nil {
   340  							check.softErrorf(obj.pos, "missing function body")
   341  						}
   342  					} else {
   343  						check.declare(pkg.scope, d.Name, obj, token.NoPos)
   344  					}
   345  				} else {
   346  					// method
   347  					check.recordDef(d.Name, obj)
   348  					// Associate method with receiver base type name, if possible.
   349  					// Ignore methods that have an invalid receiver, or a blank _
   350  					// receiver name. They will be type-checked later, with regular
   351  					// functions.
   352  					if list := d.Recv.List; len(list) > 0 {
   353  						typ := list[0].Type
   354  						if ptr, _ := typ.(*ast.StarExpr); ptr != nil {
   355  							typ = ptr.X
   356  						}
   357  						if base, _ := typ.(*ast.Ident); base != nil && base.Name != "_" {
   358  							check.assocMethod(base.Name, obj)
   359  						}
   360  					}
   361  				}
   362  				info := &declInfo{file: fileScope, fdecl: d}
   363  				check.objMap[obj] = info
   364  				obj.setOrder(uint32(len(check.objMap)))
   365  
   366  			default:
   367  				check.invalidAST(d.Pos(), "unknown ast.Decl node %T", d)
   368  			}
   369  		}
   370  	}
   371  
   372  	// verify that objects in package and file scopes have different names
   373  	for _, scope := range check.pkg.scope.children /* file scopes */ {
   374  		for _, obj := range scope.elems {
   375  			if alt := pkg.scope.Lookup(obj.Name()); alt != nil {
   376  				if pkg, ok := obj.(*PkgName); ok {
   377  					check.errorf(alt.Pos(), "%s already declared through import of %s", alt.Name(), pkg.Imported())
   378  					check.reportAltDecl(pkg)
   379  				} else {
   380  					check.errorf(alt.Pos(), "%s already declared through dot-import of %s", alt.Name(), obj.Pkg())
   381  					// TODO(gri) dot-imported objects don't have a position; reportAltDecl won't print anything
   382  					check.reportAltDecl(obj)
   383  				}
   384  			}
   385  		}
   386  	}
   387  }
   388  
   389  // packageObjects typechecks all package objects in objList, but not function bodies.
   390  func (check *Checker) packageObjects(objList []Object) {
   391  	// add new methods to already type-checked types (from a prior Checker.Files call)
   392  	for _, obj := range objList {
   393  		if obj, _ := obj.(*TypeName); obj != nil && obj.typ != nil {
   394  			check.addMethodDecls(obj)
   395  		}
   396  	}
   397  
   398  	// pre-allocate space for type declaration paths so that the underlying array is reused
   399  	typePath := make([]*TypeName, 0, 8)
   400  
   401  	for _, obj := range objList {
   402  		check.objDecl(obj, nil, typePath)
   403  	}
   404  
   405  	// At this point we may have a non-empty check.methods map; this means that not all
   406  	// entries were deleted at the end of typeDecl because the respective receiver base
   407  	// types were not found. In that case, an error was reported when declaring those
   408  	// methods. We can now safely discard this map.
   409  	check.methods = nil
   410  }
   411  
   412  // functionBodies typechecks all function bodies.
   413  func (check *Checker) functionBodies() {
   414  	for _, f := range check.funcs {
   415  		check.funcBody(f.decl, f.name, f.sig, f.body)
   416  	}
   417  }
   418  
   419  // unusedImports checks for unused imports.
   420  func (check *Checker) unusedImports() {
   421  	// if function bodies are not checked, packages' uses are likely missing - don't check
   422  	if check.conf.IgnoreFuncBodies {
   423  		return
   424  	}
   425  
   426  	// spec: "It is illegal (...) to directly import a package without referring to
   427  	// any of its exported identifiers. To import a package solely for its side-effects
   428  	// (initialization), use the blank identifier as explicit package name."
   429  
   430  	// check use of regular imported packages
   431  	for _, scope := range check.pkg.scope.children /* file scopes */ {
   432  		for _, obj := range scope.elems {
   433  			if obj, ok := obj.(*PkgName); ok {
   434  				// Unused "blank imports" are automatically ignored
   435  				// since _ identifiers are not entered into scopes.
   436  				if !obj.used {
   437  					path := obj.imported.path
   438  					base := pathLib.Base(path)
   439  					if obj.name == base {
   440  						check.softErrorf(obj.pos, "%q imported but not used", path)
   441  					} else {
   442  						check.softErrorf(obj.pos, "%q imported but not used as %s", path, obj.name)
   443  					}
   444  				}
   445  			}
   446  		}
   447  	}
   448  
   449  	// check use of dot-imported packages
   450  	for _, unusedDotImports := range check.unusedDotImports {
   451  		for pkg, pos := range unusedDotImports {
   452  			check.softErrorf(pos, "%q imported but not used", pkg.path)
   453  		}
   454  	}
   455  }