1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339
|
// 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 types
import (
"fmt"
"go/ast"
"go/token"
"code.google.com/p/go.tools/go/exact"
)
// debugging/development support
const (
debug = false // leave on during development
trace = false // turn on for detailed type resolution traces
)
// exprInfo stores type and constant value for an untyped expression.
type exprInfo struct {
isLhs bool // expression is lhs operand of a shift with delayed type-check
typ *Basic
val exact.Value // constant value; or nil (if not a constant)
}
// funcInfo stores the information required for type-checking a function.
type funcInfo struct {
name string // for debugging/tracing only
decl *declInfo // for cycle detection
sig *Signature
body *ast.BlockStmt
}
// A context represents the context within which an object is type-checked.
type context struct {
decl *declInfo // package-level declaration whose init expression/function body is checked
scope *Scope // top-most scope for lookups
iota exact.Value // value of iota in a constant declaration; nil otherwise
sig *Signature // function signature if inside a function; nil otherwise
hasLabel bool // set if a function makes use of labels (only ~1% of functions); unused outside functions
hasCallOrRecv bool // set if an expression contains a function call or channel receive operation
}
// 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 *Config
fset *token.FileSet
pkg *Package
*Info
objMap map[Object]*declInfo // maps package-level object to declaration info
// 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 files
fileScopes []*Scope // file scope for each file
dotImports []map[*Package]token.Pos // positions of dot-imports for each file
firstErr error // first error encountered
methods map[string][]*Func // maps type names to associated methods
untyped map[ast.Expr]exprInfo // map of expressions without final type
funcs []funcInfo // list of functions to type-check
delayed []func() // delayed checks requiring fully setup types
// context within which the current object is type-checked
// (valid only for the duration of type-checking a specific object)
context
// debugging
indent int // indentation for tracing
}
// addDeclDep adds the dependency edge (check.decl -> to)
// if check.decl exists and to has an initializer.
func (check *checker) addDeclDep(to Object) {
from := check.decl
if from == nil {
return // not in a package-level init expression
}
if decl := check.objMap[to]; decl == nil || !decl.hasInitializer() {
return // to is not a package-level object or has no initializer
}
from.addDep(to)
}
func (check *checker) assocMethod(tname string, meth *Func) {
m := check.methods
if m == nil {
m = make(map[string][]*Func)
check.methods = m
}
m[tname] = append(m[tname], meth)
}
func (check *checker) rememberUntyped(e ast.Expr, lhs bool, typ *Basic, val exact.Value) {
m := check.untyped
if m == nil {
m = make(map[ast.Expr]exprInfo)
check.untyped = m
}
m[e] = exprInfo{lhs, typ, val}
}
func (check *checker) later(name string, decl *declInfo, sig *Signature, body *ast.BlockStmt) {
check.funcs = append(check.funcs, funcInfo{name, decl, sig, body})
}
func (check *checker) delay(f func()) {
check.delayed = append(check.delayed, f)
}
// NewChecker returns a new Checker instance for a given package.
// Package files may be incrementally added via checker.Files.
func NewChecker(conf *Config, fset *token.FileSet, pkg *Package, info *Info) *checker {
// make sure we have a configuration
if conf == nil {
conf = new(Config)
}
// make sure we have a package canonicalization map
if conf.Packages == nil {
conf.Packages = make(map[string]*Package)
}
// make sure we have an info struct
if info == nil {
info = new(Info)
}
return &checker{
conf: conf,
fset: fset,
pkg: pkg,
Info: info,
objMap: make(map[Object]*declInfo),
}
}
// initFiles initializes the files-specific portion of checker.
// The provided files must all belong to the same package.
func (check *checker) initFiles(files []*ast.File) {
// start with a clean slate (check.Files may be called multiple times)
check.files = nil
check.fileScopes = nil
check.dotImports = nil
check.firstErr = nil
check.methods = nil
check.untyped = nil
check.funcs = nil
check.delayed = nil
// determine package name, files, and set up file scopes, dotImports maps
pkg := check.pkg
for i, file := range files {
switch name := file.Name.Name; pkg.name {
case "":
if name != "_" {
pkg.name = name
} else {
check.errorf(file.Name.Pos(), "invalid package name _")
}
fallthrough
case name:
check.files = append(check.files, file)
var comment string
if pos := file.Pos(); pos.IsValid() {
comment = "file " + check.fset.File(pos).Name()
} else {
comment = fmt.Sprintf("file[%d]", i)
}
fileScope := NewScope(pkg.scope, comment)
check.recordScope(file, fileScope)
check.fileScopes = append(check.fileScopes, fileScope)
check.dotImports = append(check.dotImports, nil) // element (map) is lazily allocated
default:
check.errorf(file.Package, "package %s; expected %s", name, pkg.name)
// ignore this file
}
}
}
// A bailout panic is raised to indicate early termination.
type bailout struct{}
func (check *checker) handleBailout(err *error) {
switch p := recover().(type) {
case nil, bailout:
// normal return or early exit
*err = check.firstErr
default:
// re-panic
panic(p)
}
}
// Files checks the provided files as part of the checker's package.
func (check *checker) Files(files []*ast.File) (err error) {
defer check.handleBailout(&err)
check.initFiles(files)
check.collectObjects()
objList := check.resolveOrder()
check.packageObjects(objList)
check.functionBodies()
check.initDependencies(objList)
check.unusedImports()
// perform delayed checks
for _, f := range check.delayed {
f()
}
check.recordUntyped()
check.pkg.complete = true
return
}
func (check *checker) recordUntyped() {
if !debug && check.Types == nil {
return // nothing to do
}
for x, info := range check.untyped {
if debug && isTyped(info.typ) {
check.dump("%s: %s (type %s) is typed", x.Pos(), x, info.typ)
unreachable()
}
check.recordTypeAndValue(x, info.typ, info.val)
}
}
func (check *checker) recordTypeAndValue(x ast.Expr, typ Type, val exact.Value) {
assert(x != nil && typ != nil)
if val != nil {
assert(isConstType(typ))
}
if m := check.Types; m != nil {
m[x] = TypeAndValue{typ, val}
}
}
func (check *checker) recordBuiltinType(f ast.Expr, sig *Signature) {
// f must be a (possibly parenthesized) identifier denoting a built-in
// (built-ins in package unsafe always produce a constant result and
// we don't record their signatures, so we don't see qualified idents
// here): record the signature for f and possible children.
for {
check.recordTypeAndValue(f, sig, nil)
switch p := f.(type) {
case *ast.Ident:
return // we're done
case *ast.ParenExpr:
f = p.X
default:
unreachable()
}
}
}
func (check *checker) recordCommaOkTypes(x ast.Expr, a [2]Type) {
assert(x != nil)
if a[0] == nil || a[1] == nil {
return
}
assert(isTyped(a[0]) && isTyped(a[1]) && isBoolean(a[1]))
if m := check.Types; m != nil {
for {
tv := m[x]
assert(tv.Type != nil) // should have been recorded already
pos := x.Pos()
tv.Type = NewTuple(
NewVar(pos, check.pkg, "", a[0]),
NewVar(pos, check.pkg, "", a[1]),
)
m[x] = tv
// if x is a parenthesized expression (p.X), update p.X
p, _ := x.(*ast.ParenExpr)
if p == nil {
break
}
x = p.X
}
}
}
func (check *checker) recordDef(id *ast.Ident, obj Object) {
assert(id != nil)
if m := check.Defs; m != nil {
m[id] = obj
}
}
func (check *checker) recordUse(id *ast.Ident, obj Object) {
assert(id != nil)
assert(obj != nil)
if m := check.Uses; m != nil {
m[id] = obj
}
}
func (check *checker) recordImplicit(node ast.Node, obj Object) {
assert(node != nil && obj != nil)
if m := check.Implicits; m != nil {
m[node] = obj
}
}
func (check *checker) recordSelection(x *ast.SelectorExpr, kind SelectionKind, recv Type, obj Object, index []int, indirect bool) {
assert(obj != nil && (recv == nil || len(index) > 0))
check.recordUse(x.Sel, obj)
// TODO(gri) Should we also call recordTypeAndValue?
if m := check.Selections; m != nil {
m[x] = &Selection{kind, recv, obj, index, indirect}
}
}
func (check *checker) recordScope(node ast.Node, scope *Scope) {
assert(node != nil && scope != nil)
if m := check.Scopes; m != nil {
m[node] = scope
}
}
|