benchMatches []string
)
-func (t *tester) registerStdTest(pkg string) {
- testName := "go_test:" + pkg
+func (t *tester) registerStdTest(pkg string, useG3 bool) {
+ heading := "Testing packages."
+ testPrefix := "go_test:"
+ gcflags := gogcflags
+ if useG3 {
+ heading = "Testing packages with -G=3."
+ testPrefix = "go_test_g3:"
+ gcflags += " -G=3"
+ }
+
+ testName := testPrefix + pkg
if t.runRx == nil || t.runRx.MatchString(testName) == t.runRxWant {
stdMatches = append(stdMatches, pkg)
}
+
t.tests = append(t.tests, distTest{
name: testName,
- heading: "Testing packages.",
+ heading: heading,
fn: func(dt *distTest) error {
if ranGoTest {
return nil
"-short=" + short(),
t.tags(),
t.timeout(timeoutSec),
- "-gcflags=all=" + gogcflags,
+ "-gcflags=all=" + gcflags,
}
if t.race {
args = append(args, "-race")
if len(t.runNames) > 0 {
for _, name := range t.runNames {
if strings.HasPrefix(name, "go_test:") {
- t.registerStdTest(strings.TrimPrefix(name, "go_test:"))
+ t.registerStdTest(strings.TrimPrefix(name, "go_test:"), false)
+ }
+ if strings.HasPrefix(name, "go_test_g3:") {
+ t.registerStdTest(strings.TrimPrefix(name, "go_test_g3:"), true)
}
if strings.HasPrefix(name, "go_test_bench:") {
t.registerRaceBenchTest(strings.TrimPrefix(name, "go_test_bench:"))
fatalf("Error running go list std cmd: %v:\n%s", err, cmd.Stderr)
}
pkgs := strings.Fields(string(all))
+ if false {
+ // Disable -G=3 option for standard tests for now, since
+ // they are flaky on the builder.
+ for _, pkg := range pkgs {
+ t.registerStdTest(pkg, true /* -G=3 flag */)
+ }
+ }
for _, pkg := range pkgs {
- t.registerStdTest(pkg)
+ t.registerStdTest(pkg, false)
}
if t.race {
for _, pkg := range pkgs {
}
}
- // Doc tests only run on builders.
- // They find problems approximately never.
- if goos != "js" && goos != "android" && !t.iOS() && os.Getenv("GO_BUILDER_NAME") != "" {
- t.registerTest("doc_progs", "../doc/progs", "go", "run", "run.go")
- t.registerTest("wiki", "../doc/articles/wiki", t.goTest(), ".")
- t.registerTest("codewalk", "../doc/codewalk", t.goTest(), "codewalk_test.go")
- }
-
if goos != "android" && !t.iOS() {
// There are no tests in this directory, only benchmarks.
// Check that the test binary builds but don't bother running it.
// A Config specifies the configuration for type checking.
// The zero value for Config is a ready-to-use default configuration.
type Config struct {
+ // GoVersion describes the accepted Go language version. The string
+ // must follow the format "go%d.%d" (e.g. "go1.12") or it must be
+ // empty; an empty string indicates the latest language version.
+ // If the format is invalid, invoking the type checker will cause a
+ // panic.
+ GoVersion string
+
// If IgnoreFuncBodies is set, function bodies are not
// type-checked.
IgnoreFuncBodies bool
// qualified identifiers are collected in the Uses map.
Types map[ast.Expr]TypeAndValue
+ // Inferred maps calls of parameterized functions that use
+ // type inference to the inferred type arguments and signature
+ // of the function called. The recorded "call" expression may be
+ // an *ast.CallExpr (as in f(x)), or an *ast.IndexExpr (s in f[T]).
+ Inferred map[ast.Expr]Inferred
+
// Defs maps identifiers to the objects they define (including
// package names, dots "." of dot-imports, and blank "_" identifiers).
// For identifiers that do not denote objects (e.g., the package name
return tv.mode == commaok || tv.mode == mapindex
}
+// Inferred reports the inferred type arguments and signature
+// for a parameterized function call that uses type inference.
+type Inferred struct {
+ Targs []Type
+ Sig *Signature
+}
+
// An Initializer describes a package-level variable, or a list of variables in case
// of a multi-valued initialization expression, and the corresponding initialization
// expression.
package types
import (
- "errors"
"go/ast"
"go/token"
)
case constant_, variable, mapindex, value, commaok, commaerr:
// ok
default:
- unreachable()
+ // we may get here because of other problems (issue #39634, crash 12)
+ check.errorf(x, 0, "cannot assign %s to %s in %s", x, T, context)
+ return
}
if isUntyped(x.typ) {
}
target = Default(x.typ)
}
- if err := check.canConvertUntyped(x, target); err != nil {
+ newType, val, code := check.implicitTypeAndValue(x, target)
+ if code != 0 {
msg := check.sprintf("cannot use %s as %s value in %s", x, target, context)
- code := _IncompatibleAssign
- var ierr Error
- if errors.As(err, &ierr) {
- // Preserve these inner errors, as they are informative.
- switch ierr.go116code {
- case _TruncatedFloat:
- msg += " (truncated)"
- code = ierr.go116code
- case _NumericOverflow:
- msg += " (overflows)"
- code = ierr.go116code
- }
+ switch code {
+ case _TruncatedFloat:
+ msg += " (truncated)"
+ case _NumericOverflow:
+ msg += " (overflows)"
+ default:
+ code = _IncompatibleAssign
}
check.error(x, code, msg)
x.mode = invalid
return
}
+ if val != nil {
+ x.val = val
+ check.updateExprVal(x.expr, val)
+ }
+ if newType != x.typ {
+ x.typ = newType
+ check.updateExprType(x.expr, newType, false)
+ }
+ }
+
+ // A generic (non-instantiated) function value cannot be assigned to a variable.
+ if sig := asSignature(x.typ); sig != nil && len(sig.tparams) > 0 {
+ check.errorf(x, _Todo, "cannot use generic function %s without instantiation in %s", x, context)
}
- // x.typ is typed
// spec: "If a left-hand side is the blank identifier, any typed or
// non-constant value except for the predeclared identifier nil may
if lhs.typ == nil {
lhs.typ = Typ[Invalid]
}
- lhs.used = true
return nil
}
func (check *Checker) assignVar(lhs ast.Expr, x *operand) Type {
if x.mode == invalid || x.typ == Typ[Invalid] {
+ check.useLHS(lhs)
return nil
}
// If returnPos is valid, initVars is called to type-check the assignment of
// return expressions, and returnPos is the position of the return statement.
-func (check *Checker) initVars(lhs []*Var, rhs []ast.Expr, returnPos token.Pos) {
- l := len(lhs)
- get, r, commaOk := unpack(func(x *operand, i int) { check.multiExpr(x, rhs[i]) }, len(rhs), l == 2 && !returnPos.IsValid())
- if get == nil || l != r {
- // invalidate lhs and use rhs
+func (check *Checker) initVars(lhs []*Var, origRHS []ast.Expr, returnPos token.Pos) {
+ rhs, commaOk := check.exprList(origRHS, len(lhs) == 2 && !returnPos.IsValid())
+
+ if len(lhs) != len(rhs) {
+ // invalidate lhs
for _, obj := range lhs {
if obj.typ == nil {
obj.typ = Typ[Invalid]
}
}
- if get == nil {
- return // error reported by unpack
+ // don't report an error if we already reported one
+ for _, x := range rhs {
+ if x.mode == invalid {
+ return
+ }
}
- check.useGetter(get, r)
if returnPos.IsValid() {
- check.errorf(atPos(returnPos), _WrongResultCount, "wrong number of return values (want %d, got %d)", l, r)
+ check.errorf(atPos(returnPos), _WrongResultCount, "wrong number of return values (want %d, got %d)", len(lhs), len(rhs))
return
}
- check.errorf(rhs[0], _WrongAssignCount, "cannot initialize %d variables with %d values", l, r)
+ check.errorf(rhs[0], _WrongAssignCount, "cannot initialize %d variables with %d values", len(lhs), len(rhs))
return
}
context = "return statement"
}
- var x operand
if commaOk {
var a [2]Type
for i := range a {
- get(&x, i)
- a[i] = check.initVar(lhs[i], &x, context)
+ a[i] = check.initVar(lhs[i], rhs[i], context)
}
- check.recordCommaOkTypes(rhs[0], a)
+ check.recordCommaOkTypes(origRHS[0], a)
return
}
for i, lhs := range lhs {
- get(&x, i)
- check.initVar(lhs, &x, context)
+ check.initVar(lhs, rhs[i], context)
}
}
-func (check *Checker) assignVars(lhs, rhs []ast.Expr) {
- l := len(lhs)
- get, r, commaOk := unpack(func(x *operand, i int) { check.multiExpr(x, rhs[i]) }, len(rhs), l == 2)
- if get == nil {
+func (check *Checker) assignVars(lhs, origRHS []ast.Expr) {
+ rhs, commaOk := check.exprList(origRHS, len(lhs) == 2)
+
+ if len(lhs) != len(rhs) {
check.useLHS(lhs...)
- return // error reported by unpack
- }
- if l != r {
- check.useGetter(get, r)
- check.errorf(rhs[0], _WrongAssignCount, "cannot assign %d values to %d variables", r, l)
+ // don't report an error if we already reported one
+ for _, x := range rhs {
+ if x.mode == invalid {
+ return
+ }
+ }
+ check.errorf(rhs[0], _WrongAssignCount, "cannot assign %d values to %d variables", len(rhs), len(lhs))
return
}
- var x operand
if commaOk {
var a [2]Type
for i := range a {
- get(&x, i)
- a[i] = check.assignVar(lhs[i], &x)
+ a[i] = check.assignVar(lhs[i], rhs[i])
}
- check.recordCommaOkTypes(rhs[0], a)
+ check.recordCommaOkTypes(origRHS[0], a)
return
}
for i, lhs := range lhs {
- get(&x, i)
- check.assignVar(lhs, &x)
+ check.assignVar(lhs, rhs[i])
}
}
// For len(x) and cap(x) we need to know if x contains any function calls or
// receive operations. Save/restore current setting and set hasCallOrRecv to
// false for the evaluation of x so that we can check it afterwards.
- // Note: We must do this _before_ calling unpack because unpack evaluates the
- // first argument before we even call arg(x, 0)!
+ // Note: We must do this _before_ calling exprList because exprList evaluates
+ // all arguments.
if id == _Len || id == _Cap {
defer func(b bool) {
check.hasCallOrRecv = b
}
// determine actual arguments
- var arg getter
+ var arg func(*operand, int) // TODO(gri) remove use of arg getter in favor of using xlist directly
nargs := len(call.Args)
switch id {
default:
// make argument getter
- arg, nargs, _ = unpack(func(x *operand, i int) { check.multiExpr(x, call.Args[i]) }, nargs, false)
- if arg == nil {
- return
- }
+ xlist, _ := check.exprList(call.Args, false)
+ arg = func(x *operand, i int) { *x = *xlist[i]; x.typ = expand(x.typ) }
+ nargs = len(xlist)
// evaluate first argument, if present
if nargs > 0 {
arg(x, 0)
// of S and the respective parameter passing rules apply."
S := x.typ
var T Type
- if s, _ := S.Underlying().(*Slice); s != nil {
+ if s := asSlice(S); s != nil {
T = s.elem
} else {
check.invalidArg(x, _InvalidAppend, "%s is not a slice", x)
// check general case by creating custom signature
sig := makeSig(S, S, NewSlice(T)) // []T required for variadic signature
sig.variadic = true
- check.arguments(x, call, sig, func(x *operand, i int) {
- // only evaluate arguments that have not been evaluated before
- if i < len(alist) {
- *x = alist[i]
- return
- }
- arg(x, i)
- }, nargs)
+ var xlist []*operand
+ // convert []operand to []*operand
+ for i := range alist {
+ xlist = append(xlist, &alist[i])
+ }
+ for i := len(alist); i < nargs; i++ {
+ var x operand
+ arg(&x, i)
+ xlist = append(xlist, &x)
+ }
+ check.arguments(call, sig, xlist) // discard result (we know the result type)
// ok to continue even if check.arguments reported errors
x.mode = value
mode := invalid
var typ Type
var val constant.Value
- switch typ = implicitArrayDeref(x.typ.Underlying()); t := typ.(type) {
+ switch typ = implicitArrayDeref(optype(x.typ)); t := typ.(type) {
case *Basic:
if isString(t) && id == _Len {
if x.mode == constant_ {
if id == _Len {
mode = value
}
+
+ case *Sum:
+ if t.is(func(t Type) bool {
+ switch t := under(t).(type) {
+ case *Basic:
+ if isString(t) && id == _Len {
+ return true
+ }
+ case *Array, *Slice, *Chan:
+ return true
+ case *Map:
+ if id == _Len {
+ return true
+ }
+ }
+ return false
+ }) {
+ mode = value
+ }
}
if mode == invalid && typ != Typ[Invalid] {
case _Close:
// close(c)
- c, _ := x.typ.Underlying().(*Chan)
+ c := asChan(x.typ)
if c == nil {
check.invalidArg(x, _InvalidClose, "%s is not a channel", x)
return
}
// the argument types must be of floating-point type
- if !isFloat(x.typ) {
+ f := func(x Type) Type {
+ if t := asBasic(x); t != nil {
+ switch t.kind {
+ case Float32:
+ return Typ[Complex64]
+ case Float64:
+ return Typ[Complex128]
+ case UntypedFloat:
+ return Typ[UntypedComplex]
+ }
+ }
+ return nil
+ }
+ resTyp := check.applyTypeFunc(f, x.typ)
+ if resTyp == nil {
check.invalidArg(x, _InvalidComplex, "arguments have type %s, expected floating-point", x.typ)
return
}
x.mode = value
}
- // determine result type
- var res BasicKind
- switch x.typ.Underlying().(*Basic).kind {
- case Float32:
- res = Complex64
- case Float64:
- res = Complex128
- case UntypedFloat:
- res = UntypedComplex
- default:
- unreachable()
- }
- resTyp := Typ[res]
-
if check.Types != nil && x.mode != constant_ {
check.recordBuiltinType(call.Fun, makeSig(resTyp, x.typ, x.typ))
}
case _Copy:
// copy(x, y []T) int
var dst Type
- if t, _ := x.typ.Underlying().(*Slice); t != nil {
+ if t := asSlice(x.typ); t != nil {
dst = t.elem
}
return
}
var src Type
- switch t := y.typ.Underlying().(type) {
+ switch t := optype(y.typ).(type) {
case *Basic:
if isString(y.typ) {
src = universeByte
case _Delete:
// delete(m, k)
- m, _ := x.typ.Underlying().(*Map)
+ m := asMap(x.typ)
if m == nil {
check.invalidArg(x, _InvalidDelete, "%s is not a map", x)
return
return
}
- if ok, code := x.assignableTo(check, m.key, nil); !ok {
- check.invalidArg(x, code, "%s is not assignable to %s", x, m.key)
+ check.assignment(x, m.key, "argument to delete")
+ if x.mode == invalid {
return
}
}
// the argument must be of complex type
- if !isComplex(x.typ) {
+ f := func(x Type) Type {
+ if t := asBasic(x); t != nil {
+ switch t.kind {
+ case Complex64:
+ return Typ[Float32]
+ case Complex128:
+ return Typ[Float64]
+ case UntypedComplex:
+ return Typ[UntypedFloat]
+ }
+ }
+ return nil
+ }
+ resTyp := check.applyTypeFunc(f, x.typ)
+ if resTyp == nil {
code := _InvalidImag
if id == _Real {
code = _InvalidReal
x.mode = value
}
- // determine result type
- var res BasicKind
- switch x.typ.Underlying().(*Basic).kind {
- case Complex64:
- res = Float32
- case Complex128:
- res = Float64
- case UntypedComplex:
- res = UntypedFloat
- default:
- unreachable()
- }
- resTyp := Typ[res]
-
if check.Types != nil && x.mode != constant_ {
check.recordBuiltinType(call.Fun, makeSig(resTyp, x.typ))
}
// make(T, n, m)
// (no argument evaluated yet)
arg0 := call.Args[0]
- T := check.typ(arg0)
+ T := check.varType(arg0)
if T == Typ[Invalid] {
return
}
- var min int // minimum number of arguments
- switch T.Underlying().(type) {
- case *Slice:
- min = 2
- case *Map, *Chan:
- min = 1
- default:
+ min, max := -1, 10
+ var valid func(t Type) bool
+ valid = func(t Type) bool {
+ var m int
+ switch t := optype(t).(type) {
+ case *Slice:
+ m = 2
+ case *Map, *Chan:
+ m = 1
+ case *Sum:
+ return t.is(valid)
+ default:
+ return false
+ }
+ if m > min {
+ min = m
+ }
+ if m+1 < max {
+ max = m + 1
+ }
+ return true
+ }
+
+ if !valid(T) {
check.invalidArg(arg0, _InvalidMake, "cannot make %s; type must be slice, map, or channel", arg0)
return
}
- if nargs < min || min+1 < nargs {
- check.errorf(call, _WrongArgCount, "%v expects %d or %d arguments; found %d", call, min, min+1, nargs)
+ if nargs < min || max < nargs {
+ if min == max {
+ check.errorf(call, _WrongArgCount, "%v expects %d arguments; found %d", call, min, nargs)
+ } else {
+ check.errorf(call, _WrongArgCount, "%v expects %d or %d arguments; found %d", call, min, max, nargs)
+ }
return
}
+
types := []Type{T}
var sizes []int64 // constant integer arguments, if any
for _, arg := range call.Args[1:] {
case _New:
// new(T)
// (no argument evaluated yet)
- T := check.typ(call.Args[0])
+ T := check.varType(call.Args[0])
if T == Typ[Invalid] {
return
}
case _Alignof:
// unsafe.Alignof(x T) uintptr
+ if asTypeParam(x.typ) != nil {
+ check.invalidOp(call, _Todo, "unsafe.Alignof undefined for %s", x)
+ return
+ }
check.assignment(x, nil, "argument to unsafe.Alignof")
if x.mode == invalid {
return
case _Sizeof:
// unsafe.Sizeof(x T) uintptr
+ if asTypeParam(x.typ) != nil {
+ check.invalidOp(call, _Todo, "unsafe.Sizeof undefined for %s", x)
+ return
+ }
check.assignment(x, nil, "argument to unsafe.Sizeof")
if x.mode == invalid {
return
return true
}
+// applyTypeFunc applies f to x. If x is a type parameter,
+// the result is a type parameter constrained by an new
+// interface bound. The type bounds for that interface
+// are computed by applying f to each of the type bounds
+// of x. If any of these applications of f return nil,
+// applyTypeFunc returns nil.
+// If x is not a type parameter, the result is f(x).
+func (check *Checker) applyTypeFunc(f func(Type) Type, x Type) Type {
+ if tp := asTypeParam(x); tp != nil {
+ // Test if t satisfies the requirements for the argument
+ // type and collect possible result types at the same time.
+ var rtypes []Type
+ if !tp.Bound().is(func(x Type) bool {
+ if r := f(x); r != nil {
+ rtypes = append(rtypes, r)
+ return true
+ }
+ return false
+ }) {
+ return nil
+ }
+
+ // construct a suitable new type parameter
+ tpar := NewTypeName(token.NoPos, nil /* = Universe pkg */, "<type parameter>", nil)
+ ptyp := check.NewTypeParam(tpar, 0, &emptyInterface) // assigns type to tpar as a side-effect
+ tsum := NewSum(rtypes)
+ ptyp.bound = &Interface{types: tsum, allMethods: markComplete, allTypes: tsum}
+
+ return ptyp
+ }
+
+ return f(x)
+}
+
// makeSig makes a signature for the given argument and result types.
// Default types are used for untyped arguments, and res may be nil.
func makeSig(res Type, args ...Type) *Signature {
//
func implicitArrayDeref(typ Type) Type {
if p, ok := typ.(*Pointer); ok {
- if a, ok := p.base.Underlying().(*Array); ok {
+ if a := asArray(p.base); a != nil {
return a
}
}
import (
"errors"
+ "fmt"
"go/ast"
"go/constant"
"go/token"
trace = false // turn on for detailed type resolution traces
)
-// If Strict is set, the type-checker enforces additional
+// If forceStrict is set, the type-checker enforces additional
// rules not specified by the Go 1 spec, but which will
// catch guaranteed run-time errors if the respective
// code is executed. In other words, programs passing in
-// Strict mode are Go 1 compliant, but not all Go 1 programs
-// will pass in Strict mode. The additional rules are:
+// strict mode are Go 1 compliant, but not all Go 1 programs
+// will pass in strict mode. The additional rules are:
//
// - A type assertion x.(T) where T is an interface type
// is invalid if any (statically known) method that exists
// for both x and T have different signatures.
//
-const strict = false
+const forceStrict = false
// exprInfo stores information about an untyped expression.
type exprInfo struct {
path, dir string
}
+ // A dotImportKey describes a dot-imported object in the given scope.
+ type dotImportKey struct {
+ scope *Scope
+ obj Object
+ }
+
// A Checker maintains the state of the type checker.
// It must be created with NewChecker.
type Checker struct {
fset *token.FileSet
pkg *Package
*Info
- nextId 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 info
- impMap map[importKey]*Package // maps (import path, source directory) to (complete or fake) package
- posMap map[*Interface][]token.Pos // maps interface types to lists of embedded interface positions
- typMap map[string]*Named // maps an instantiated named type hash to a *Named type
- pkgCnt map[string]int // counts number of imported packages with a given name (for better error messages)
+ version version // accepted language version
++ nextId 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 info
+ impMap map[importKey]*Package // maps (import path, source directory) to (complete or fake) package
+ posMap map[*Interface][]token.Pos // maps interface types to lists of embedded interface positions
++ typMap map[string]*Named // maps an instantiated named type hash to a *Named type
+ pkgCnt map[string]int // counts number of imported packages with a given name (for better error messages)
// 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
- unusedDotImports map[*Scope]map[*Package]*ast.ImportSpec // unused dot-imported packages
+ files []*ast.File // package files
+ imports []*PkgName // list of imported packages
+ dotImportMap map[dotImportKey]*PkgName // maps dot-imported objects to the package they were dot-imported through
firstErr error // first error encountered
methods map[*TypeName][]*Func // maps package scope type names to associated non-blank (non-interface) methods
indent int // indentation for tracing
}
- // addUnusedImport adds the position of a dot-imported package
- // pkg to the map of dot imports for the given file scope.
- func (check *Checker) addUnusedDotImport(scope *Scope, pkg *Package, spec *ast.ImportSpec) {
- mm := check.unusedDotImports
- if mm == nil {
- mm = make(map[*Scope]map[*Package]*ast.ImportSpec)
- check.unusedDotImports = mm
- }
- m := mm[scope]
- if m == nil {
- m = make(map[*Package]*ast.ImportSpec)
- mm[scope] = m
- }
- m[pkg] = spec
- }
-
// addDeclDep adds the dependency edge (check.decl -> to) if check.decl exists
func (check *Checker) addDeclDep(to Object) {
from := check.decl
info = new(Info)
}
+ version, err := parseGoVersion(conf.GoVersion)
+ if err != nil {
+ panic(fmt.Sprintf("invalid Go version %q (%v)", conf.GoVersion, err))
+ }
+
return &Checker{
- conf: conf,
- fset: fset,
- pkg: pkg,
- Info: info,
- nextId: 1,
- objMap: make(map[Object]*declInfo),
- impMap: make(map[importKey]*Package),
- posMap: make(map[*Interface][]token.Pos),
- typMap: make(map[string]*Named),
- pkgCnt: make(map[string]int),
+ conf: conf,
+ fset: fset,
+ pkg: pkg,
+ Info: info,
+ version: version,
++ nextId: 1,
+ objMap: make(map[Object]*declInfo),
+ impMap: make(map[importKey]*Package),
+ posMap: make(map[*Interface][]token.Pos),
++ typMap: make(map[string]*Named),
+ pkgCnt: make(map[string]int),
}
}
func (check *Checker) initFiles(files []*ast.File) {
// start with a clean slate (check.Files may be called multiple times)
check.files = nil
- check.unusedDotImports = nil
+ check.imports = nil
+ check.dotImportMap = nil
check.firstErr = nil
check.methods = nil
if !check.conf.DisableUnusedImportCheck {
check.unusedImports()
}
+ // no longer needed - release memory
+ check.imports = nil
+ check.dotImportMap = nil
check.recordUntyped()
+ if check.Info != nil {
+ sanitizeInfo(check.Info)
+ }
+
check.pkg.complete = true
+
+ // TODO(rFindley) There's more memory we should release at this point.
+
return
}
}
}
+func (check *Checker) recordInferred(call ast.Expr, targs []Type, sig *Signature) {
+ assert(call != nil)
+ assert(sig != nil)
+ if m := check.Inferred; m != nil {
+ m[call] = Inferred{targs, sig}
+ }
+}
+
func (check *Checker) recordDef(id *ast.Ident, obj Object) {
assert(id != nil)
if m := check.Defs; m != nil {
var (
haltOnError = flag.Bool("halt", false, "halt on error")
listErrors = flag.Bool("errlist", false, "list errors")
- testFiles = flag.String("files", "", "space-separated list of test files")
+ testFiles = flag.String("files", "", "comma-separated list of test files")
+ goVersion = flag.String("lang", "", "Go language version (e.g. \"go1.12\"")
)
var fset = token.NewFileSet()
return
}
- func parseFiles(t *testing.T, filenames []string, mode parser.Mode) ([]*ast.File, []error) {
-func parseFiles(t *testing.T, filenames []string, srcs [][]byte) ([]*ast.File, []error) {
++func parseFiles(t *testing.T, filenames []string, srcs [][]byte, mode parser.Mode) ([]*ast.File, []error) {
var files []*ast.File
var errlist []error
- for _, filename := range filenames {
- file, err := parser.ParseFile(fset, filename, nil, mode)
+ for i, filename := range filenames {
- file, err := parser.ParseFile(fset, filename, srcs[i], parser.AllErrors)
++ file, err := parser.ParseFile(fset, filename, srcs[i], mode)
if file == nil {
t.Fatalf("%s: %s", filename, err)
}
// errMap collects the regular expressions of ERROR comments found
// in files and returns them as a map of error positions to error messages.
//
- func errMap(t *testing.T, testname string, files []*ast.File) map[string][]string {
+ // srcs must be a slice of the same length as files, containing the original
+ // source for the parsed AST.
+ func errMap(t *testing.T, files []*ast.File, srcs [][]byte) map[string][]string {
// map of position strings to lists of error message patterns
errmap := make(map[string][]string)
- for _, file := range files {
- filename := fset.Position(file.Package).Filename
- src, err := os.ReadFile(filename)
- if err != nil {
- t.Fatalf("%s: could not read %s", testname, filename)
- }
-
+ for i, file := range files {
+ tok := fset.File(file.Package)
+ src := srcs[i]
var s scanner.Scanner
- s.Init(fset.AddFile(filename, -1, len(src)), src, nil, scanner.ScanComments)
+ s.Init(tok, src, nil, scanner.ScanComments)
var prev token.Pos // position of last non-comment, non-semicolon token
var here token.Pos // position immediately after the token at position prev
}
}
- func checkFiles(t *testing.T, sources []string) {
- if len(sources) == 0 {
+ // goVersionRx matches a Go version string using '_', e.g. "go1_12".
+ var goVersionRx = regexp.MustCompile(`^go[1-9][0-9]*_(0|[1-9][0-9]*)$`)
+
+ // asGoVersion returns a regular Go language version string
+ // if s is a Go version string using '_' rather than '.' to
+ // separate the major and minor version numbers (e.g. "go1_12").
+ // Otherwise it returns the empty string.
+ func asGoVersion(s string) string {
+ if goVersionRx.MatchString(s) {
+ return strings.Replace(s, "_", ".", 1)
+ }
+ return ""
+ }
+
+ func checkFiles(t *testing.T, goVersion string, filenames []string, srcs [][]byte) {
+ if len(filenames) == 0 {
t.Fatal("no source files")
}
- if strings.HasSuffix(sources[0], ".go2") {
+ mode := parser.AllErrors
++ if strings.HasSuffix(filenames[0], ".go2") {
+ mode |= parser.ParseTypeParams
+ }
+
// parse files and collect parser errors
- files, errlist := parseFiles(t, sources, mode)
- files, errlist := parseFiles(t, filenames, srcs)
++ files, errlist := parseFiles(t, filenames, srcs, mode)
pkgName := "<no package>"
if len(files) > 0 {
pkgName = files[0].Name.Name
}
+ // if no Go version is given, consider the package name
+ if goVersion == "" {
+ goVersion = asGoVersion(pkgName)
+ }
+
if *listErrors && len(errlist) > 0 {
t.Errorf("--- %s:", pkgName)
for _, err := range errlist {
// typecheck and collect typechecker errors
var conf Config
+ conf.GoVersion = goVersion
// special case for importC.src
- if len(sources) == 1 && strings.HasSuffix(sources[0], "importC.src") {
- conf.FakeImportC = true
+ if len(filenames) == 1 {
+ if strings.HasSuffix(filenames[0], "importC.src") {
+ conf.FakeImportC = true
+ }
}
- // TODO(rFindley) we may need to use the source importer when adding generics
- // tests.
+
conf.Importer = importer.Default()
conf.Error = func(err error) {
if *haltOnError {
// match and eliminate errors;
// we are expecting the following errors
- errmap := errMap(t, pkgName, files)
+ errmap := errMap(t, files, srcs)
eliminate(t, errmap, errlist)
// there should be no expected errors left
}
// TestCheck is for manual testing of selected input files, provided with -files.
+ // The accepted Go language version can be controlled with the -lang flag.
func TestCheck(t *testing.T) {
if *testFiles == "" {
return
}
testenv.MustHaveGoBuild(t)
DefPredeclaredTestFuncs()
- checkFiles(t, strings.Split(*testFiles, " "))
+ testPkg(t, strings.Split(*testFiles, ","), *goVersion)
+ }
+
+ func TestLongConstants(t *testing.T) {
+ format := "package longconst\n\nconst _ = %s\nconst _ = %s // ERROR excessively long constant"
+ src := fmt.Sprintf(format, strings.Repeat("1", 9999), strings.Repeat("1", 10001))
+ checkFiles(t, "", []string{"longconst.go"}, [][]byte{[]byte(src)})
}
func TestTestdata(t *testing.T) { DefPredeclaredTestFuncs(); testDir(t, "testdata") }
+func TestExamples(t *testing.T) { testDir(t, "examples") }
func TestFixedbugs(t *testing.T) { testDir(t, "fixedbugs") }
func testDir(t *testing.T, dir string) {
path := filepath.Join(dir, fi.Name())
// if fi is a directory, its files make up a single package
- var files []string
+ var filenames []string
if fi.IsDir() {
fis, err := ioutil.ReadDir(path)
if err != nil {
t.Error(err)
continue
}
- files = make([]string, len(fis))
- for i, fi := range fis {
- // if fi is a directory, checkFiles below will complain
- files[i] = filepath.Join(path, fi.Name())
- if testing.Verbose() {
- fmt.Printf("\t%s\n", files[i])
- }
+ for _, fi := range fis {
+ filenames = append(filenames, filepath.Join(path, fi.Name()))
}
} else {
- files = []string{path}
+ filenames = []string{path}
}
t.Run(filepath.Base(path), func(t *testing.T) {
- checkFiles(t, files)
+ testPkg(t, filenames, "")
})
}
}
+
+ func testPkg(t *testing.T, filenames []string, goVersion string) {
+ srcs := make([][]byte, len(filenames))
+ for i, filename := range filenames {
+ src, err := os.ReadFile(filename)
+ if err != nil {
+ t.Fatalf("could not read %s: %v", filename, err)
+ }
+ srcs[i] = src
+ }
+ checkFiles(t, goVersion, filenames, srcs)
+ }
package types
import (
+ "fmt"
"go/ast"
"go/constant"
"go/token"
// objDecl type-checks the declaration of obj in its respective (file) context.
// For the meaning of def, see Checker.definedType, in typexpr.go.
func (check *Checker) objDecl(obj Object, def *Named) {
- if trace {
+ if trace && obj.Type() == nil {
+ if check.indent == 0 {
+ fmt.Println() // empty line between top-level objects for readability
+ }
check.trace(obj.Pos(), "-- checking %s (%s, objPath = %s)", obj, obj.color(), pathString(check.objPath))
check.indent++
defer func() {
switch obj := obj.(type) {
case *Const:
check.decl = d // new package-level const decl
- check.constDecl(obj, d.typ, d.init, d.inherited)
+ check.constDecl(obj, d.vtyp, d.init, d.inherited)
case *Var:
check.decl = d // new package-level var decl
- check.varDecl(obj, d.lhs, d.typ, d.init)
+ check.varDecl(obj, d.lhs, d.vtyp, d.init)
case *TypeName:
// invalid recursive types are detected via path
- check.typeDecl(obj, d.typ, def, d.aliasPos)
+ check.typeDecl(obj, d.tdecl, def)
+ check.collectMethods(obj) // methods can only be added to top-level types
case *Func:
// functions may be recursive - no need to track dependencies
check.funcDecl(obj, d)
// this information explicitly in the object.
var alias bool
if d := check.objMap[obj]; d != nil {
- alias = d.aliasPos.IsValid() // package-level object
+ alias = d.tdecl.Assign.IsValid() // package-level object
} else {
alias = obj.IsAlias() // function local object
}
}
// don't report a 2nd error if we already know the type is invalid
- // (e.g., if a cycle was detected earlier, via Checker.underlying).
+ // (e.g., if a cycle was detected earlier, via under).
if t.underlying == Typ[Invalid] {
t.info = invalid
return invalid
if tn == t.obj {
check.cycleError(path[i:])
t.info = invalid
- t.underlying = Typ[Invalid]
return t.info
}
}
panic("internal error: cycle start not found")
}
return t.info
+
+ case *instance:
+ return check.validType(t.expand(), path)
}
return valid
if !isConstType(t) {
// don't report an error if the type is an invalid C (defined) type
// (issue #22090)
- if t.Underlying() != Typ[Invalid] {
+ if under(t) != Typ[Invalid] {
check.errorf(typ, _InvalidConstType, "invalid constant type %s", t)
}
obj.typ = Typ[Invalid]
func (check *Checker) varDecl(obj *Var, lhs []*Var, typ, init ast.Expr) {
assert(obj.typ == nil)
- // If we have undefined variable types due to errors,
- // mark variables as used to avoid follow-on errors.
- // Matches compiler behavior.
- defer func() {
- if obj.typ == Typ[Invalid] {
- obj.used = true
- }
- for _, lhs := range lhs {
- if lhs.typ == Typ[Invalid] {
- lhs.used = true
- }
- }
- }()
-
// determine type, if any
if typ != nil {
- obj.typ = check.typ(typ)
+ obj.typ = check.varType(typ)
// We cannot spread the type to all lhs variables if there
// are more than one since that would mark them as checked
// (see Checker.objDecl) and the assignment of init exprs,
check.initVars(lhs, []ast.Expr{init}, token.NoPos)
}
-// underlying returns the underlying type of typ; possibly by following
-// forward chains of named types. Such chains only exist while named types
-// are incomplete. If an underlying type is found, resolve the chain by
-// setting the underlying type for each defined type in the chain before
-// returning it.
-//
-// If no underlying type is found, a cycle error is reported and Typ[Invalid]
-// is used as underlying type for each defined type in the chain and returned
-// as result.
-func (check *Checker) underlying(typ Type) Type {
- // If typ is not a defined type, its underlying type is itself.
- n0, _ := typ.(*Named)
- if n0 == nil {
- return typ // nothing to do
+// under returns the expanded underlying type of n0; possibly by following
+// forward chains of named types. If an underlying type is found, resolve
+// the chain by setting the underlying type for each defined type in the
+// chain before returning it. If no underlying type is found or a cycle
+// is detected, the result is Typ[Invalid]. If a cycle is detected and
+// n0.check != nil, the cycle is reported.
+func (n0 *Named) under() Type {
+ u := n0.underlying
+ if u == nil {
+ return Typ[Invalid]
}
// If the underlying type of a defined type is not a defined
// type, then that is the desired underlying type.
- typ = n0.underlying
- n, _ := typ.(*Named)
+ n := asNamed(u)
if n == nil {
- return typ // common case
+ return u // common case
}
// Otherwise, follow the forward chain.
seen := map[*Named]int{n0: 0}
path := []Object{n0.obj}
for {
- typ = n.underlying
- n1, _ := typ.(*Named)
+ u = n.underlying
+ if u == nil {
+ u = Typ[Invalid]
+ break
+ }
+ n1 := asNamed(u)
if n1 == nil {
break // end of chain
}
if i, ok := seen[n]; ok {
// cycle
- check.cycleError(path[i:])
- typ = Typ[Invalid]
+ // TODO(rFindley) revert this to a method on Checker. Having a possibly
+ // nil Checker on Named and TypeParam is too subtle.
+ if n0.check != nil {
+ n0.check.cycleError(path[i:])
+ }
+ u = Typ[Invalid]
break
}
}
// We should never have to update the underlying type of an imported type;
// those underlying types should have been resolved during the import.
// Also, doing so would lead to a race condition (was issue #31749).
- if n.obj.pkg != check.pkg {
+ // Do this check always, not just in debug more (it's cheap).
+ if n0.check != nil && n.obj.pkg != n0.check.pkg {
panic("internal error: imported type with unresolved underlying type")
}
- n.underlying = typ
+ n.underlying = u
}
- return typ
+ return u
}
func (n *Named) setUnderlying(typ Type) {
}
}
-func (check *Checker) typeDecl(obj *TypeName, typ ast.Expr, def *Named, aliasPos token.Pos) {
+func (check *Checker) typeDecl(obj *TypeName, tdecl *ast.TypeSpec, def *Named) {
assert(obj.typ == nil)
check.later(func() {
check.validType(obj.typ, nil)
})
- if aliasPos.IsValid() {
+ alias := tdecl.Assign.IsValid()
+ if alias && tdecl.TParams != nil {
+ // The parser will ensure this but we may still get an invalid AST.
+ // Complain and continue as regular type definition.
+ check.error(atPos(tdecl.Assign), 0, "generic type cannot be alias")
+ alias = false
+ }
+
+ if alias {
+ // type alias declaration
+ if !check.allowVersion(obj.pkg, 1, 9) {
- check.errorf(atPos(aliasPos), _BadDecl, "type aliases requires go1.9 or later")
++ check.errorf(atPos(tdecl.Assign), _BadDecl, "type aliases requires go1.9 or later")
+ }
obj.typ = Typ[Invalid]
- obj.typ = check.typ(typ)
+ obj.typ = check.anyType(tdecl.Type)
} else {
+ // defined type declaration
- named := &Named{obj: obj}
+ named := &Named{check: check, obj: obj}
def.setUnderlying(named)
obj.typ = named // make sure recursive type declarations terminate
+ if tdecl.TParams != nil {
+ check.openScope(tdecl, "type parameters")
+ defer check.closeScope()
+ named.tparams = check.collectTypeParams(tdecl.TParams)
+ }
+
// determine underlying type of named
- named.orig = check.definedType(typ, named)
+ named.orig = check.definedType(tdecl.Type, named)
// The underlying type of named may be itself a named type that is
// incomplete:
// and which has as its underlying type the named type B.
// Determine the (final, unnamed) underlying type by resolving
// any forward chain.
- named.underlying = check.underlying(named)
+ // TODO(gri) Investigate if we can just use named.origin here
+ // and rely on lazy computation of the underlying type.
+ named.underlying = under(named)
+ }
+
+}
+
+func (check *Checker) collectTypeParams(list *ast.FieldList) (tparams []*TypeName) {
+ // Type parameter lists should not be empty. The parser will
+ // complain but we still may get an incorrect AST: ignore it.
+ if list.NumFields() == 0 {
+ return
+ }
+
+ // Declare type parameters up-front, with empty interface as type bound.
+ // The scope of type parameters starts at the beginning of the type parameter
+ // list (so we can have mutually recursive parameterized interfaces).
+ for _, f := range list.List {
+ tparams = check.declareTypeParams(tparams, f.Names)
+ }
+ setBoundAt := func(at int, bound Type) {
+ assert(IsInterface(bound))
+ tparams[at].typ.(*TypeParam).bound = bound
}
- // TODO(rFindley): move to the callsite, as this is only needed for top-level
- // decls.
- check.addMethodDecls(obj)
+ index := 0
+ var bound Type
+ for _, f := range list.List {
+ if f.Type == nil {
+ goto next
+ }
+
+ // The predeclared identifier "any" is visible only as a constraint
+ // in a type parameter list. Look for it before general constraint
+ // resolution.
+ if tident, _ := f.Type.(*ast.Ident); tident != nil && tident.Name == "any" && check.lookup("any") == nil {
+ bound = universeAny
+ } else {
+ bound = check.typ(f.Type)
+ }
+
+ // type bound must be an interface
+ // TODO(gri) We should delay the interface check because
+ // we may not have a complete interface yet:
+ // type C(type T C) interface {}
+ // (issue #39724).
+ if _, ok := under(bound).(*Interface); ok {
+ // Otherwise, set the bound for each type parameter.
+ for i := range f.Names {
+ setBoundAt(index+i, bound)
+ }
+ } else if bound != Typ[Invalid] {
+ check.errorf(f.Type, _Todo, "%s is not an interface", bound)
+ }
+
+ next:
+ index += len(f.Names)
+ }
+
+ return
}
-// TODO(rFindley): rename to collectMethods, to be consistent with types2.
-func (check *Checker) addMethodDecls(obj *TypeName) {
+func (check *Checker) declareTypeParams(tparams []*TypeName, names []*ast.Ident) []*TypeName {
+ for _, name := range names {
+ tpar := NewTypeName(name.Pos(), check.pkg, name.Name, nil)
+ check.NewTypeParam(tpar, len(tparams), &emptyInterface) // assigns type to tpar as a side-effect
+ check.declare(check.scope, name, tpar, check.scope.pos) // TODO(gri) check scope position
+ tparams = append(tparams, tpar)
+ }
+
+ if trace && len(names) > 0 {
+ check.trace(names[0].Pos(), "type params = %v", tparams[len(tparams)-len(names):])
+ }
+
+ return tparams
+}
+
+func (check *Checker) collectMethods(obj *TypeName) {
// get associated methods
// (Checker.collectObjects only collects methods with non-blank names;
// Checker.resolveBaseTypeName ensures that obj is not an alias name
return
}
delete(check.methods, obj)
- assert(!check.objMap[obj].aliasPos.IsValid()) // don't use TypeName.IsAlias (requires fully set up object)
+ assert(!check.objMap[obj].tdecl.Assign.IsValid()) // don't use TypeName.IsAlias (requires fully set up object)
// use an objset to check for name conflicts
var mset objset
// spec: "If the base type is a struct type, the non-blank method
// and field names must be distinct."
- base, _ := obj.typ.(*Named) // shouldn't fail but be conservative
+ base := asNamed(obj.typ) // shouldn't fail but be conservative
if base != nil {
if t, _ := base.underlying.(*Struct); t != nil {
for _, fld := range t.fields {
sig := new(Signature)
obj.typ = sig // guard against cycles
+
+ // Avoid cycle error when referring to method while type-checking the signature.
+ // This avoids a nuisance in the best case (non-parameterized receiver type) and
+ // since the method is not a type, we get an error. If we have a parameterized
+ // receiver type, instantiating the receiver type leads to the instantiation of
+ // its methods, and we don't want a cycle error in that case.
+ // TODO(gri) review if this is correct and/or whether we still need this?
+ saved := obj.color_
+ obj.color_ = black
fdecl := decl.fdecl
check.funcType(sig, fdecl.Recv, fdecl.Type)
- if sig.recv == nil {
- if obj.name == "init" && (sig.params.Len() > 0 || sig.results.Len() > 0) {
- check.errorf(fdecl, _InvalidInitDecl, "func init must have no arguments and no return values")
- } else if obj.name == "main" && check.pkg.name == "main" && (sig.params.Len() > 0 || sig.results.Len() > 0) {
- check.errorf(fdecl, _InvalidMainDecl, "func main must have no arguments and no return values")
- }
- // ok to continue
- }
+ obj.color_ = saved
// function body must be type-checked after global declarations
// (functions implemented elsewhere have no body)
check.declare(check.scope, d.spec.Name, obj, scopePos)
// mark and unmark type before calling typeDecl; its type is still nil (see Checker.objDecl)
obj.setColor(grey + color(check.push(obj)))
- check.typeDecl(obj, d.spec.Type, nil, d.spec.Assign)
+ check.typeDecl(obj, d.spec, nil)
check.pop().setColor(black)
default:
check.invalidAST(d.node(), "unknown ast.Decl node %T", d.node())
type errorCode int
++// TODO(rFindley): ensure that existing error codes do not change in the
++// dev.typeparams branch.
++
// This file defines the error codes that can be produced during type-checking.
// Collectively, these codes provide an identifier that may be used to
// implement special handling for certain types of errors.
_NonVariadicDotDotDot
// _MisplacedDotDotDot occurs when a "..." is used somewhere other than the
- // final argument to a function call.
+ // final argument in a function declaration.
//
// Example:
- // func printArgs(args ...int) {
- // for _, a := range args {
- // println(a)
- // }
- // }
- //
- // func f() {
- // a := []int{1,2,3}
- // printArgs(0, a...)
- // }
+ // func f(...int, int)
_MisplacedDotDotDot
- // _InvalidDotDotDotOperand occurs when a "..." operator is applied to a
- // single-valued operand.
- //
- // Example:
- // func printArgs(args ...int) {
- // for _, a := range args {
- // println(a)
- // }
- // }
- //
- // func f() {
- // a := 1
- // printArgs(a...)
- // }
- //
- // Example:
- // func args() (int, int) {
- // return 1, 2
- // }
- //
- // func printArgs(args ...int) {
- // for _, a := range args {
- // println(a)
- // }
- // }
- //
- // func g() {
- // printArgs(args()...)
- // }
- _InvalidDotDotDotOperand
-
// _InvalidDotDotDot occurs when a "..." is used in a non-variadic built-in
// function.
//
// }
_InvalidPostDecl
- // _InvalidChanRange occurs when a send-only channel used in a range
- // expression.
- //
- // Example:
- // func sum(c chan<- int) {
- // s := 0
- // for i := range c {
- // s += i
- // }
- // }
- _InvalidChanRange
-
// _InvalidIterVar occurs when two iteration variables are used while ranging
// over a channel.
//
// }
_InvalidGo
+ // _BadDecl occurs when a declaration has invalid syntax.
+ _BadDecl
++
+ // _Todo is a placeholder for error codes that have not been decided.
+ // TODO(rFindley) remove this error code after deciding on errors for generics code.
+ _Todo
)
`false == false`,
`12345678 + 87654321 == 99999999`,
`10 * 20 == 200`,
- `(1<<1000)*2 >> 100 == 2<<900`,
+ `(1<<500)*2 >> 100 == 2<<400`,
`"foo" + "bar" == "foobar"`,
`"abc" <= "bcd"`,
`len([10]struct{}{}) == 2*5`,
import "io"
type R = io.Reader
func _() {
- /* interface{R}.Read => , func(interface{io.Reader}, p []byte) (n int, err error) */
+ /* interface{R}.Read => , func(_ interface{io.Reader}, p []byte) (n int, err error) */
_ = func() {
- /* interface{io.Writer}.Write => , func(interface{io.Writer}, p []byte) (n int, err error) */
+ /* interface{io.Writer}.Write => , func(_ interface{io.Writer}, p []byte) (n int, err error) */
type io interface {} // must not shadow io in line above
}
type R interface {} // must not shadow R in first line of this function body
+// REVIEW INCOMPLETE
// Copyright 2012 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.
type opPredicates map[token.Token]func(Type) bool
-var unaryOpPredicates = opPredicates{
- token.ADD: isNumeric,
- token.SUB: isNumeric,
- token.XOR: isInteger,
- token.NOT: isBoolean,
+var unaryOpPredicates opPredicates
+
+func init() {
+ // Setting unaryOpPredicates in init avoids declaration cycles.
+ unaryOpPredicates = opPredicates{
+ token.ADD: isNumeric,
+ token.SUB: isNumeric,
+ token.XOR: isInteger,
+ token.NOT: isBoolean,
+ }
}
func (check *Checker) op(m opPredicates, x *operand, op token.Token) bool {
return true
}
- if typ, ok := x.typ.Underlying().(*Basic); ok && isTyped(typ) {
+ // overflow checks that the constant x is representable by its type.
+ // For untyped constants, it checks that the value doesn't become
+ // arbitrarily large.
+ func (check *Checker) overflow(x *operand, op token.Token, opPos token.Pos) {
+ assert(x.mode == constant_)
+
+ if x.val.Kind() == constant.Unknown {
+ // TODO(gri) We should report exactly what went wrong. At the
+ // moment we don't have the (go/constant) API for that.
+ // See also TODO in go/constant/value.go.
+ check.errorf(atPos(opPos), _InvalidConstVal, "constant result is not representable")
+ return
+ }
+
+ // Typed constants must be representable in
+ // their type after each constant operation.
++ if typ := asBasic(x.typ); typ != nil && isTyped(typ) {
+ check.representable(x, typ)
+ return
+ }
+
+ // Untyped integer values must not grow arbitrarily.
+ const prec = 512 // 512 is the constant precision
+ if x.val.Kind() == constant.Int && constant.BitLen(x.val) > prec {
+ check.errorf(atPos(opPos), _InvalidConstVal, "constant %s overflow", opName(x.expr))
+ x.val = constant.MakeUnknown()
+ }
+ }
+
+ // opName returns the name of an operation, or the empty string.
+ // For now, only operations that might overflow are handled.
+ // TODO(gri) Expand this to a general mechanism giving names to
+ // nodes?
+ func opName(e ast.Expr) string {
+ switch e := e.(type) {
+ case *ast.BinaryExpr:
+ if int(e.Op) < len(op2str2) {
+ return op2str2[e.Op]
+ }
+ case *ast.UnaryExpr:
+ if int(e.Op) < len(op2str1) {
+ return op2str1[e.Op]
+ }
+ }
+ return ""
+ }
+
+ var op2str1 = [...]string{
+ token.XOR: "bitwise complement",
+ }
+
+ // This is only used for operations that may cause overflow.
+ var op2str2 = [...]string{
+ token.ADD: "addition",
+ token.SUB: "subtraction",
+ token.XOR: "bitwise XOR",
+ token.MUL: "multiplication",
+ token.SHL: "shift",
+ }
+
// The unary expression e may be nil. It's passed in for better error messages only.
- func (check *Checker) unary(x *operand, e *ast.UnaryExpr, op token.Token) {
- switch op {
+ func (check *Checker) unary(x *operand, e *ast.UnaryExpr) {
+ check.expr(x, e.X)
+ if x.mode == invalid {
+ return
+ }
+ switch e.Op {
case token.AND:
// spec: "As an exception to the addressability
// requirement x may also be a composite literal."
- if _, ok := unparen(x.expr).(*ast.CompositeLit); !ok && x.mode != variable {
+ if _, ok := unparen(e.X).(*ast.CompositeLit); !ok && x.mode != variable {
check.invalidOp(x, _UnaddressableOperand, "cannot take address of %s", x)
x.mode = invalid
return
return
case token.ARROW:
- typ, ok := x.typ.Underlying().(*Chan)
- if !ok {
+ typ := asChan(x.typ)
+ if typ == nil {
check.invalidOp(x, _InvalidReceive, "cannot receive from non-channel %s", x)
x.mode = invalid
return
return
}
- if !check.op(unaryOpPredicates, x, op) {
+ if !check.op(unaryOpPredicates, x, e.Op) {
x.mode = invalid
return
}
if x.mode == constant_ {
+ if x.val.Kind() == constant.Unknown {
+ // nothing to do (and don't cause an error below in the overflow check)
+ return
+ }
+ typ := asBasic(x.typ)
var prec uint
- if isUnsigned(x.typ) {
- prec = uint(check.conf.sizeof(x.typ) * 8)
+ if isUnsigned(typ) {
+ prec = uint(check.conf.sizeof(typ) * 8)
}
- x.val = constant.UnaryOp(op, x.val, prec)
- // Typed constants must be representable in
- // their type after each constant operation.
- if isTyped(typ) {
- if e != nil {
- x.expr = e // for better error message
- }
- check.representable(x, typ)
- }
+ x.val = constant.UnaryOp(e.Op, x.val, prec)
+ x.expr = e
+ check.overflow(x, e.Op, x.Pos())
return
}
// representable checks that a constant operand is representable in the given
// basic type.
func (check *Checker) representable(x *operand, typ *Basic) {
- if err := check.isRepresentable(x, typ); err != nil {
+ if v, code := check.representation(x, typ); code != 0 {
+ check.invalidConversion(code, x, typ)
x.mode = invalid
- check.err(err)
+ } else if v != nil {
+ x.val = v
}
}
-func (check *Checker) isRepresentable(x *operand, typ *Basic) error {
+func (check *Checker) representation(x *operand, typ *Basic) (constant.Value, errorCode) {
assert(x.mode == constant_)
- if !representableConst(x.val, check, typ, &x.val) {
- var msg string
- var code errorCode
+ v := x.val
+ if !representableConst(x.val, check, typ, &v) {
if isNumeric(x.typ) && isNumeric(typ) {
// numeric conversion : error msg
//
// float -> float : overflows
//
if !isInteger(x.typ) && isInteger(typ) {
- msg = "%s truncated to %s"
- code = _TruncatedFloat
+ return nil, _TruncatedFloat
} else {
- msg = "%s overflows %s"
- code = _NumericOverflow
+ return nil, _NumericOverflow
}
- } else {
- msg = "cannot convert %s to %s"
- code = _InvalidConstVal
}
- return check.newErrorf(x, code, false, msg, x, typ)
+ return nil, _InvalidConstVal
}
- return nil
+ return v, 0
+}
+
+func (check *Checker) invalidConversion(code errorCode, x *operand, target Type) {
+ msg := "cannot convert %s to %s"
+ switch code {
+ case _TruncatedFloat:
+ msg = "%s truncated to %s"
+ case _NumericOverflow:
+ msg = "%s overflows %s"
+ }
+ check.errorf(x, code, msg, x, target)
}
// updateExprType updates the type of x to typ and invokes itself
// If the new type is not final and still untyped, just
// update the recorded type.
if !final && isUntyped(typ) {
- old.typ = typ.Underlying().(*Basic)
+ old.typ = asBasic(typ)
check.untyped[x] = old
return
}
// convertUntyped attempts to set the type of an untyped value to the target type.
func (check *Checker) convertUntyped(x *operand, target Type) {
- if err := check.canConvertUntyped(x, target); err != nil {
+ newType, val, code := check.implicitTypeAndValue(x, target)
+ if code != 0 {
+ check.invalidConversion(code, x, target.Underlying())
x.mode = invalid
- check.err(err)
+ return
+ }
+ if val != nil {
+ x.val = val
+ check.updateExprVal(x.expr, val)
+ }
+ if newType != x.typ {
+ x.typ = newType
+ check.updateExprType(x.expr, newType, false)
}
}
-func (check *Checker) canConvertUntyped(x *operand, target Type) error {
+// implicitTypeAndValue returns the implicit type of x when used in a context
+// where the target type is expected. If no such implicit conversion is
+// possible, it returns a nil Type.
+func (check *Checker) implicitTypeAndValue(x *operand, target Type) (Type, constant.Value, errorCode) {
+ target = expand(target)
if x.mode == invalid || isTyped(x.typ) || target == Typ[Invalid] {
- return nil
+ return x.typ, nil, 0
}
if isUntyped(target) {
tkind := target.(*Basic).kind
if isNumeric(x.typ) && isNumeric(target) {
if xkind < tkind {
- x.typ = target
- check.updateExprType(x.expr, target, false)
+ return target, nil, 0
}
} else if xkind != tkind {
- return check.newErrorf(x, _InvalidUntypedConversion, false, "cannot convert %s to %s", x, target)
+ return nil, nil, _InvalidUntypedConversion
}
- return nil
+ return x.typ, nil, 0
}
- if t, ok := target.Underlying().(*Basic); ok && x.mode == constant_ {
- if err := check.isRepresentable(x, t); err != nil {
- return err
- }
- // Expression value may have been rounded - update if needed.
- check.updateExprVal(x.expr, x.val)
- } else {
- newTarget := check.implicitType(x, target)
- if newTarget == nil {
- return check.newErrorf(x, _InvalidUntypedConversion, false, "cannot convert %s to %s", x, target)
- }
- target = newTarget
- }
- x.typ = target
- // Even though implicitType can return UntypedNil, this value is final: the
- // predeclared identifier nil has no type.
- check.updateExprType(x.expr, target, true)
- return nil
-}
-
-// implicitType returns the implicit type of x when used in a context where the
-// target type is expected. If no such implicit conversion is possible, it
-// returns nil.
-func (check *Checker) implicitType(x *operand, target Type) Type {
- assert(isUntyped(x.typ))
- switch t := target.Underlying().(type) {
+ switch t := optype(target).(type) {
case *Basic:
- assert(x.mode != constant_)
+ if x.mode == constant_ {
+ v, code := check.representation(x, t)
+ if code != 0 {
+ return nil, nil, code
+ }
+ return target, v, code
+ }
// Non-constant untyped values may appear as the
// result of comparisons (untyped bool), intermediate
// (delayed-checked) rhs operands of shifts, and as
switch x.typ.(*Basic).kind {
case UntypedBool:
if !isBoolean(target) {
- return nil
+ return nil, nil, _InvalidUntypedConversion
}
case UntypedInt, UntypedRune, UntypedFloat, UntypedComplex:
if !isNumeric(target) {
- return nil
+ return nil, nil, _InvalidUntypedConversion
}
case UntypedString:
// Non-constant untyped string values are not permitted by the spec and
// should not occur during normal typechecking passes, but this path is
// reachable via the AssignableTo API.
if !isString(target) {
- return nil
+ return nil, nil, _InvalidUntypedConversion
}
case UntypedNil:
// Unsafe.Pointer is a basic type that includes nil.
if !hasNil(target) {
- return nil
+ return nil, nil, _InvalidUntypedConversion
}
// Preserve the type of nil as UntypedNil: see #13061.
- return Typ[UntypedNil]
+ return Typ[UntypedNil], nil, 0
default:
- return nil
+ return nil, nil, _InvalidUntypedConversion
+ }
+ case *Sum:
+ ok := t.is(func(t Type) bool {
+ target, _, _ := check.implicitTypeAndValue(x, t)
+ return target != nil
+ })
+ if !ok {
+ return nil, nil, _InvalidUntypedConversion
+ }
+ // keep nil untyped (was bug #39755)
+ if x.isNil() {
+ return Typ[UntypedNil], nil, 0
}
case *Interface:
// Values must have concrete dynamic types. If the value is nil,
// need the dynamic type for argument checking of say, print
// functions)
if x.isNil() {
- return Typ[UntypedNil]
+ return Typ[UntypedNil], nil, 0
}
// cannot assign untyped values to non-empty interfaces
- check.completeInterface(t)
+ check.completeInterface(token.NoPos, t)
if !t.Empty() {
- return nil
+ return nil, nil, _InvalidUntypedConversion
}
- return Default(x.typ)
+ return Default(x.typ), nil, 0
case *Pointer, *Signature, *Slice, *Map, *Chan:
if !x.isNil() {
- return nil
+ return nil, nil, _InvalidUntypedConversion
}
// Keep nil untyped - see comment for interfaces, above.
- return Typ[UntypedNil]
+ return Typ[UntypedNil], nil, 0
default:
- return nil
+ return nil, nil, _InvalidUntypedConversion
}
- return target
+ return target, nil, 0
}
func (check *Checker) comparison(x, y *operand, op token.Token) {
x.typ = Typ[UntypedBool]
}
- func (check *Checker) shift(x, y *operand, e *ast.BinaryExpr, op token.Token) {
- untypedx := isUntyped(x.typ)
+ // If e != nil, it must be the shift expression; it may be nil for non-constant shifts.
+ func (check *Checker) shift(x, y *operand, e ast.Expr, op token.Token) {
+ // TODO(gri) This function seems overly complex. Revisit.
var xval constant.Value
if x.mode == constant_ {
xval = constant.ToInt(x.val)
}
- if isInteger(x.typ) || untypedx && xval != nil && xval.Kind() == constant.Int {
+ if isInteger(x.typ) || isUntyped(x.typ) && xval != nil && xval.Kind() == constant.Int {
// The lhs is of integer type or an untyped constant representable
// as an integer. Nothing to do.
} else {
// spec: "The right operand in a shift expression must have integer type
// or be an untyped constant representable by a value of type uint."
- switch {
- case isInteger(y.typ):
- // nothing to do
- case isUntyped(y.typ):
+
+ // Provide a good error message for negative shift counts.
+ if y.mode == constant_ {
+ yval := constant.ToInt(y.val) // consider -1, 1.0, but not -1.1
+ if yval.Kind() == constant.Int && constant.Sign(yval) < 0 {
+ check.invalidOp(y, _InvalidShiftCount, "negative shift count %s", y)
+ x.mode = invalid
+ return
+ }
+ }
+
+ // Caution: Check for isUntyped first because isInteger includes untyped
+ // integers (was bug #43697).
+ if isUntyped(y.typ) {
check.convertUntyped(y, Typ[Uint])
if y.mode == invalid {
x.mode = invalid
return
}
- default:
+ } else if !isInteger(y.typ) {
check.invalidOp(y, _InvalidShiftCount, "shift count %s must be integer", y)
x.mode = invalid
return
+ } else if !isUnsigned(y.typ) && !check.allowVersion(check.pkg, 1, 13) {
+ check.invalidOp(y, _InvalidShiftCount, "signed shift count %s requires go1.13 or later", y)
+ x.mode = invalid
+ return
}
var yval constant.Value
if x.mode == constant_ {
if y.mode == constant_ {
+ // if either x or y has an unknown value, the result is unknown
+ if x.val.Kind() == constant.Unknown || y.val.Kind() == constant.Unknown {
+ x.val = constant.MakeUnknown()
+ // ensure the correct type - see comment below
+ if !isInteger(x.typ) {
+ x.typ = Typ[UntypedInt]
+ }
+ return
+ }
// rhs must be within reasonable bounds in constant shifts
- const shiftBound = 1023 - 1 + 52 // so we can express smallestFloat64
+ const shiftBound = 1023 - 1 + 52 // so we can express smallestFloat64 (see issue #44057)
s, ok := constant.Uint64Val(yval)
if !ok || s > shiftBound {
check.invalidOp(y, _InvalidShiftCount, "invalid shift count %s", y)
}
// x is a constant so xval != nil and it must be of Int kind.
x.val = constant.Shift(xval, op, uint(s))
- // Typed constants must be representable in
- // their type after each constant operation.
- if isTyped(x.typ) {
- if e != nil {
- x.expr = e // for better error message
- }
- check.representable(x, asBasic(x.typ))
+ x.expr = e
+ opPos := x.Pos()
+ if b, _ := e.(*ast.BinaryExpr); b != nil {
+ opPos = b.OpPos
}
+ check.overflow(x, op, opPos)
return
}
// non-constant shift with constant lhs
- if untypedx {
+ if isUntyped(x.typ) {
// spec: "If the left operand of a non-constant shift
// expression is an untyped constant, the type of the
// constant is what it would be if the shift expression
x.mode = value
}
-var binaryOpPredicates = opPredicates{
- token.ADD: func(typ Type) bool { return isNumeric(typ) || isString(typ) },
- token.SUB: isNumeric,
- token.MUL: isNumeric,
- token.QUO: isNumeric,
- token.REM: isInteger,
+var binaryOpPredicates opPredicates
+
+func init() {
+ // Setting binaryOpPredicates in init avoids declaration cycles.
+ binaryOpPredicates = opPredicates{
+ token.ADD: isNumericOrString,
+ token.SUB: isNumeric,
+ token.MUL: isNumeric,
+ token.QUO: isNumeric,
+ token.REM: isInteger,
- token.AND: isInteger,
- token.OR: isInteger,
- token.XOR: isInteger,
- token.AND_NOT: isInteger,
+ token.AND: isInteger,
+ token.OR: isInteger,
+ token.XOR: isInteger,
+ token.AND_NOT: isInteger,
- token.LAND: isBoolean,
- token.LOR: isBoolean,
+ token.LAND: isBoolean,
+ token.LOR: isBoolean,
+ }
}
- // The binary expression e may be nil. It's passed in for better error messages only.
- func (check *Checker) binary(x *operand, e *ast.BinaryExpr, lhs, rhs ast.Expr, op token.Token, opPos token.Pos) {
+ // If e != nil, it must be the binary expression; it may be nil for non-constant expressions
+ // (when invoked for an assignment operation where the binary expression is implicit).
+ func (check *Checker) binary(x *operand, e ast.Expr, lhs, rhs ast.Expr, op token.Token, opPos token.Pos) {
var y operand
check.expr(x, lhs)
}
if x.mode == constant_ && y.mode == constant_ {
- xval := x.val
- yval := y.val
+ // if either x or y has an unknown value, the result is unknown
+ if x.val.Kind() == constant.Unknown || y.val.Kind() == constant.Unknown {
+ x.val = constant.MakeUnknown()
+ // x.typ is unchanged
+ return
+ }
+ typ := asBasic(x.typ)
// force integer division of integer operands
- if op == token.QUO && isInteger(x.typ) {
+ if op == token.QUO && isInteger(typ) {
op = token.QUO_ASSIGN
}
- x.val = constant.BinaryOp(xval, op, yval)
- // report error if valid operands lead to an invalid result
- if xval.Kind() != constant.Unknown && yval.Kind() != constant.Unknown && x.val.Kind() == constant.Unknown {
- // TODO(gri) We should report exactly what went wrong. At the
- // moment we don't have the (go/constant) API for that.
- // See also TODO in go/constant/value.go.
- check.errorf(atPos(opPos), _InvalidConstVal, "constant result is not representable")
- // TODO(gri) Should we mark operands with unknown values as invalid?
- }
- // Typed constants must be representable in
- // their type after each constant operation.
- if isTyped(typ) {
- if e != nil {
- x.expr = e // for better error message
- }
- check.representable(x, typ)
- }
+ x.val = constant.BinaryOp(x.val, op, y.val)
+ x.expr = e
+ check.overflow(x, op, opPos)
return
}
//
func (check *Checker) rawExpr(x *operand, e ast.Expr, hint Type) exprKind {
if trace {
- check.trace(e.Pos(), "%s", e)
+ check.trace(e.Pos(), "expr %s", e)
check.indent++
defer func() {
check.indent--
goto Error
case *ast.BasicLit:
+ switch e.Kind {
+ case token.INT, token.FLOAT, token.IMAG:
+ check.langCompat(e)
+ // The max. mantissa precision for untyped numeric values
+ // is 512 bits, or 4048 bits for each of the two integer
+ // parts of a fraction for floating-point numbers that are
+ // represented accurately in the go/constant package.
+ // Constant literals that are longer than this many bits
+ // are not meaningful; and excessively long constants may
+ // consume a lot of space and time for a useless conversion.
+ // Cap constant length with a generous upper limit that also
+ // allows for separators between all digits.
+ const limit = 10000
+ if len(e.Value) > limit {
+ check.errorf(e, _InvalidConstVal, "excessively long constant: %s... (%d chars)", e.Value[:10], len(e.Value))
+ goto Error
+ }
+ }
x.setConst(e.Kind, e.Value)
if x.mode == invalid {
// The parser already establishes syntactic correctness.
// We have an "open" [...]T array type.
// Create a new ArrayType with unknown length (-1)
// and finish setting it up after analyzing the literal.
- typ = &Array{len: -1, elem: check.typ(atyp.Elt)}
+ typ = &Array{len: -1, elem: check.varType(atyp.Elt)}
base = typ
break
}
case hint != nil:
// no composite literal type present - use hint (element type of enclosing type)
typ = hint
- base, _ = deref(typ.Underlying()) // *T implies &T{}
+ base, _ = deref(under(typ)) // *T implies &T{}
default:
// TODO(gri) provide better error messages depending on context
goto Error
}
- switch utyp := base.Underlying().(type) {
+ switch utyp := optype(base).(type) {
case *Struct:
if len(e.Elts) == 0 {
break
duplicate := false
// if the key is of interface type, the type is also significant when checking for duplicates
xkey := keyVal(x.val)
- if _, ok := utyp.key.Underlying().(*Interface); ok {
+ if asInterface(utyp.key) != nil {
for _, vtyp := range visited[xkey] {
if check.identical(vtyp, x.typ) {
duplicate = true
check.selector(x, e)
case *ast.IndexExpr:
- check.expr(x, e.X)
+ check.exprOrType(x, e.X)
if x.mode == invalid {
check.use(e.Index)
goto Error
}
+ if x.mode == typexpr {
+ // type instantiation
+ x.mode = invalid
+ x.typ = check.varType(e)
+ if x.typ != Typ[Invalid] {
+ x.mode = typexpr
+ }
+ return expression
+ }
+
+ if x.mode == value {
+ if sig := asSignature(x.typ); sig != nil && len(sig.tparams) > 0 {
+ return check.call(x, nil, e)
+ }
+ }
+
valid := false
length := int64(-1) // valid if >= 0
- switch typ := x.typ.Underlying().(type) {
+ switch typ := optype(x.typ).(type) {
case *Basic:
if isString(typ) {
valid = true
x.typ = typ.elem
case *Pointer:
- if typ, _ := typ.base.Underlying().(*Array); typ != nil {
+ if typ := asArray(typ.base); typ != nil {
valid = true
length = typ.len
x.mode = variable
x.typ = typ.elem
x.expr = e
return expression
+
+ case *Sum:
+ // A sum type can be indexed if all of the sum's types
+ // support indexing and have the same index and element
+ // type. Special rules apply for maps in the sum type.
+ var tkey, telem Type // key is for map types only
+ nmaps := 0 // number of map types in sum type
+ if typ.is(func(t Type) bool {
+ var e Type
+ switch t := under(t).(type) {
+ case *Basic:
+ if isString(t) {
+ e = universeByte
+ }
+ case *Array:
+ e = t.elem
+ case *Pointer:
+ if t := asArray(t.base); t != nil {
+ e = t.elem
+ }
+ case *Slice:
+ e = t.elem
+ case *Map:
+ // If there are multiple maps in the sum type,
+ // they must have identical key types.
+ // TODO(gri) We may be able to relax this rule
+ // but it becomes complicated very quickly.
+ if tkey != nil && !Identical(t.key, tkey) {
+ return false
+ }
+ tkey = t.key
+ e = t.elem
+ nmaps++
+ case *TypeParam:
+ check.errorf(x, 0, "type of %s contains a type parameter - cannot index (implementation restriction)", x)
+ case *instance:
+ panic("unimplemented")
+ }
+ if e == nil || telem != nil && !Identical(e, telem) {
+ return false
+ }
+ telem = e
+ return true
+ }) {
+ // If there are maps, the index expression must be assignable
+ // to the map key type (as for simple map index expressions).
+ if nmaps > 0 {
+ var key operand
+ check.expr(&key, e.Index)
+ check.assignment(&key, tkey, "map index")
+ // ok to continue even if indexing failed - map element type is known
+
+ // If there are only maps, we are done.
+ if nmaps == len(typ.types) {
+ x.mode = mapindex
+ x.typ = telem
+ x.expr = e
+ return expression
+ }
+
+ // Otherwise we have mix of maps and other types. For
+ // now we require that the map key be an integer type.
+ // TODO(gri) This is probably not good enough.
+ valid = isInteger(tkey)
+ // avoid 2nd indexing error if indexing failed above
+ if !valid && key.mode == invalid {
+ goto Error
+ }
+ x.mode = value // map index expressions are not addressable
+ } else {
+ // no maps
+ valid = true
+ x.mode = variable
+ }
+ x.typ = telem
+ }
}
if !valid {
goto Error
}
+ // In pathological (invalid) cases (e.g.: type T1 [][[]T1{}[0][0]]T0)
+ // the element type may be accessed before it's set. Make sure we have
+ // a valid type.
+ if x.typ == nil {
+ x.typ = Typ[Invalid]
+ }
+
check.index(e.Index, length)
// ok to continue
valid := false
length := int64(-1) // valid if >= 0
- switch typ := x.typ.Underlying().(type) {
+ switch typ := optype(x.typ).(type) {
case *Basic:
if isString(typ) {
if e.Slice3 {
x.typ = &Slice{elem: typ.elem}
case *Pointer:
- if typ, _ := typ.base.Underlying().(*Array); typ != nil {
+ if typ := asArray(typ.base); typ != nil {
valid = true
length = typ.len
x.typ = &Slice{elem: typ.elem}
case *Slice:
valid = true
// x.typ doesn't change
+
+ case *Sum, *TypeParam:
+ check.errorf(x, 0, "generic slice expressions not yet implemented")
+ goto Error
}
if !valid {
if x.mode == invalid {
goto Error
}
- xtyp, _ := x.typ.Underlying().(*Interface)
+ xtyp, _ := under(x.typ).(*Interface)
if xtyp == nil {
check.invalidOp(x, _InvalidAssert, "%s is not an interface", x)
goto Error
}
+ check.ordinaryType(x, xtyp)
// x.(type) expressions are handled explicitly in type switches
if e.Type == nil {
// Don't use invalidAST because this can occur in the AST produced by
check.error(e, _BadTypeKeyword, "use of .(type) outside type switch")
goto Error
}
- T := check.typ(e.Type)
+ T := check.varType(e.Type)
if T == Typ[Invalid] {
goto Error
}
x.typ = T
case *ast.CallExpr:
- return check.call(x, e)
+ return check.call(x, e, e)
case *ast.StarExpr:
check.exprOrType(x, e.X)
case typexpr:
x.typ = &Pointer{base: x.typ}
default:
- if typ, ok := x.typ.Underlying().(*Pointer); ok {
+ if typ := asPointer(x.typ); typ != nil {
x.mode = variable
x.typ = typ.base
} else {
}
case *ast.UnaryExpr:
- check.expr(x, e.X)
- if x.mode == invalid {
- goto Error
- }
- check.unary(x, e, e.Op)
+ check.unary(x, e)
if x.mode == invalid {
goto Error
}
check.errorf(at, _ImpossibleAssert, "%s cannot have dynamic type %s (%s)", x, T, msg)
}
-func (check *Checker) singleValue(x *operand) {
- if x.mode == value {
- // tuple types are never named - no need for underlying type below
- if t, ok := x.typ.(*Tuple); ok {
- assert(t.Len() != 1)
- check.errorf(x, _TooManyValues, "%d-valued %s where single value is expected", t.Len(), x)
- x.mode = invalid
- }
- }
-}
-
// expr typechecks expression e and initializes x with the expression value.
// The result must be a single value.
// If an error occurred, x.mode is set to invalid.
//
func (check *Checker) expr(x *operand, e ast.Expr) {
- check.multiExpr(x, e)
+ check.rawExpr(x, e, nil)
+ check.exclude(x, 1<<novalue|1<<builtin|1<<typexpr)
check.singleValue(x)
}
-// multiExpr is like expr but the result may be a multi-value.
+// multiExpr is like expr but the result may also be a multi-value.
func (check *Checker) multiExpr(x *operand, e ast.Expr) {
check.rawExpr(x, e, nil)
- var msg string
- var code errorCode
- switch x.mode {
- default:
- return
- case novalue:
- msg = "%s used as value"
- code = _TooManyValues
- case builtin:
- msg = "%s must be called"
- code = _UncalledBuiltin
- case typexpr:
- msg = "%s is not an expression"
- code = _NotAnExpr
- }
- check.errorf(x, code, msg, x)
- x.mode = invalid
+ check.exclude(x, 1<<novalue|1<<builtin|1<<typexpr)
+}
+
+// multiExprOrType is like multiExpr but the result may also be a type.
+func (check *Checker) multiExprOrType(x *operand, e ast.Expr) {
+ check.rawExpr(x, e, nil)
+ check.exclude(x, 1<<novalue|1<<builtin)
}
// exprWithHint typechecks expression e and initializes x with the expression value;
func (check *Checker) exprWithHint(x *operand, e ast.Expr, hint Type) {
assert(hint != nil)
check.rawExpr(x, e, hint)
+ check.exclude(x, 1<<novalue|1<<builtin|1<<typexpr)
check.singleValue(x)
- var msg string
- var code errorCode
- switch x.mode {
- default:
- return
- case novalue:
- msg = "%s used as value"
- code = _TooManyValues
- case builtin:
- msg = "%s must be called"
- code = _UncalledBuiltin
- case typexpr:
- msg = "%s is not an expression"
- code = _NotAnExpr
- }
- check.errorf(x, code, msg, x)
- x.mode = invalid
}
// exprOrType typechecks expression or type e and initializes x with the expression value or type.
//
func (check *Checker) exprOrType(x *operand, e ast.Expr) {
check.rawExpr(x, e, nil)
+ check.exclude(x, 1<<novalue)
check.singleValue(x)
- if x.mode == novalue {
- check.errorf(x, _NotAnExpr, "%s used as value or type", x)
+}
+
+// exclude reports an error if x.mode is in modeset and sets x.mode to invalid.
+// The modeset may contain any of 1<<novalue, 1<<builtin, 1<<typexpr.
+func (check *Checker) exclude(x *operand, modeset uint) {
+ if modeset&(1<<x.mode) != 0 {
+ var msg string
+ var code errorCode
+ switch x.mode {
+ case novalue:
+ if modeset&(1<<typexpr) != 0 {
+ msg = "%s used as value"
+ } else {
+ msg = "%s used as value or type"
+ }
+ code = _TooManyValues
+ case builtin:
+ msg = "%s must be called"
+ code = _UncalledBuiltin
+ case typexpr:
+ msg = "%s is not an expression"
+ code = _NotAnExpr
+ default:
+ unreachable()
+ }
+ check.errorf(x, code, msg, x)
x.mode = invalid
}
}
+
+// singleValue reports an error if x describes a tuple and sets x.mode to invalid.
+func (check *Checker) singleValue(x *operand) {
+ if x.mode == value {
+ // tuple types are never named - no need for underlying type below
+ if t, ok := x.typ.(*Tuple); ok {
+ assert(t.Len() != 1)
+ check.errorf(x, _TooManyValues, "%d-valued %s where single value is expected", t.Len(), x)
+ x.mode = invalid
+ }
+ }
+}
package types
- "sort"
+import (
+ "go/token"
+)
+
+// isNamed reports whether typ has a name.
+// isNamed may be called with types that are not fully set up.
func isNamed(typ Type) bool {
- if _, ok := typ.(*Basic); ok {
- return ok
+ switch typ.(type) {
+ case *Basic, *Named, *TypeParam, *instance:
+ return true
}
- _, ok := typ.(*Named)
- return ok
-}
-
-func isBoolean(typ Type) bool {
- t, ok := typ.Underlying().(*Basic)
- return ok && t.info&IsBoolean != 0
-}
-
-func isInteger(typ Type) bool {
- t, ok := typ.Underlying().(*Basic)
- return ok && t.info&IsInteger != 0
-}
-
-func isUnsigned(typ Type) bool {
- t, ok := typ.Underlying().(*Basic)
- return ok && t.info&IsUnsigned != 0
-}
-
-func isFloat(typ Type) bool {
- t, ok := typ.Underlying().(*Basic)
- return ok && t.info&IsFloat != 0
-}
-
-func isComplex(typ Type) bool {
- t, ok := typ.Underlying().(*Basic)
- return ok && t.info&IsComplex != 0
+ return false
}
-func isNumeric(typ Type) bool {
- t, ok := typ.Underlying().(*Basic)
- return ok && t.info&IsNumeric != 0
+// isGeneric reports whether a type is a generic, uninstantiated type (generic
+// signatures are not included).
+func isGeneric(typ Type) bool {
+ // A parameterized type is only instantiated if it doesn't have an instantiation already.
+ named, _ := typ.(*Named)
+ return named != nil && named.obj != nil && named.tparams != nil && named.targs == nil
}
-func isString(typ Type) bool {
- t, ok := typ.Underlying().(*Basic)
- return ok && t.info&IsString != 0
+func is(typ Type, what BasicInfo) bool {
+ switch t := optype(typ).(type) {
+ case *Basic:
+ return t.info&what != 0
+ case *Sum:
+ return t.is(func(typ Type) bool { return is(typ, what) })
+ }
+ return false
}
+func isBoolean(typ Type) bool { return is(typ, IsBoolean) }
+func isInteger(typ Type) bool { return is(typ, IsInteger) }
+func isUnsigned(typ Type) bool { return is(typ, IsUnsigned) }
+func isFloat(typ Type) bool { return is(typ, IsFloat) }
+func isComplex(typ Type) bool { return is(typ, IsComplex) }
+func isNumeric(typ Type) bool { return is(typ, IsNumeric) }
+func isString(typ Type) bool { return is(typ, IsString) }
+
+// Note that if typ is a type parameter, isInteger(typ) || isFloat(typ) does not
+// produce the expected result because a type list that contains both an integer
+// and a floating-point type is neither (all) integers, nor (all) floats.
+// Use isIntegerOrFloat instead.
+func isIntegerOrFloat(typ Type) bool { return is(typ, IsInteger|IsFloat) }
+
+// isNumericOrString is the equivalent of isIntegerOrFloat for isNumeric(typ) || isString(typ).
+func isNumericOrString(typ Type) bool { return is(typ, IsNumeric|IsString) }
+
+// isTyped reports whether typ is typed; i.e., not an untyped
+// constant or boolean. isTyped may be called with types that
+// are not fully set up.
func isTyped(typ Type) bool {
- t, ok := typ.Underlying().(*Basic)
- return !ok || t.info&IsUntyped == 0
+ // isTyped is called with types that are not fully
+ // set up. Must not call asBasic()!
+ // A *Named or *instance type is always typed, so
+ // we only need to check if we have a true *Basic
+ // type.
+ t, _ := typ.(*Basic)
+ return t == nil || t.info&IsUntyped == 0
}
+// isUntyped(typ) is the same as !isTyped(typ).
func isUntyped(typ Type) bool {
- t, ok := typ.Underlying().(*Basic)
- return ok && t.info&IsUntyped != 0
-}
-
-func isOrdered(typ Type) bool {
- t, ok := typ.Underlying().(*Basic)
- return ok && t.info&IsOrdered != 0
+ return !isTyped(typ)
}
-func isConstType(typ Type) bool {
- t, ok := typ.Underlying().(*Basic)
- return ok && t.info&IsConstType != 0
-}
+func isOrdered(typ Type) bool { return is(typ, IsOrdered) }
+func isConstType(typ Type) bool { return is(typ, IsConstType) }
// IsInterface reports whether typ is an interface type.
func IsInterface(typ Type) bool {
- _, ok := typ.Underlying().(*Interface)
- return ok
+ return asInterface(typ) != nil
}
// Comparable reports whether values of type T are comparable.
}
seen[T] = true
- switch t := T.Underlying().(type) {
+ // If T is a type parameter not constrained by any type
+ // list (i.e., it's underlying type is the top type),
+ // T is comparable if it has the == method. Otherwise,
+ // the underlying type "wins". For instance
+ //
+ // interface{ comparable; type []byte }
+ //
+ // is not comparable because []byte is not comparable.
+ if t := asTypeParam(T); t != nil && optype(t) == theTop {
+ return t.Bound().IsComparable()
+ }
+
+ switch t := optype(T).(type) {
case *Basic:
// assume invalid types to be comparable
// to avoid follow-up errors
return true
case *Array:
return comparable(t.elem, seen)
+ case *Sum:
+ pred := func(t Type) bool {
+ return comparable(t, seen)
+ }
+ return t.is(pred)
+ case *TypeParam:
+ return t.Bound().IsComparable()
}
return false
}
// hasNil reports whether a type includes the nil value.
func hasNil(typ Type) bool {
- switch t := typ.Underlying().(type) {
+ switch t := optype(typ).(type) {
case *Basic:
return t.kind == UnsafePointer
case *Slice, *Pointer, *Signature, *Interface, *Map, *Chan:
return true
+ case *Sum:
+ return t.is(hasNil)
}
return false
}
return p.x == q.x && p.y == q.y || p.x == q.y && p.y == q.x
}
+// For changes to this code the corresponding changes should be made to unifier.nify.
func (check *Checker) identical0(x, y Type, cmpTags bool, p *ifacePair) bool {
+ // types must be expanded for comparison
+ x = expandf(x)
+ y = expandf(y)
+
if x == y {
return true
}
// and result values, corresponding parameter and result types are identical,
// and either both functions are variadic or neither is. Parameter and result
// names are not required to match.
+ // Generic functions must also have matching type parameter lists, but for the
+ // parameter names.
if y, ok := y.(*Signature); ok {
return x.variadic == y.variadic &&
+ check.identicalTParams(x.tparams, y.tparams, cmpTags, p) &&
check.identical0(x.params, y.params, cmpTags, p) &&
check.identical0(x.results, y.results, cmpTags, p)
}
+ case *Sum:
+ // Two sum types are identical if they contain the same types.
+ // (Sum types always consist of at least two types. Also, the
+ // the set (list) of types in a sum type consists of unique
+ // types - each type appears exactly once. Thus, two sum types
+ // must contain the same number of types to have chance of
+ // being equal.
+ if y, ok := y.(*Sum); ok && len(x.types) == len(y.types) {
+ // Every type in x.types must be in y.types.
+ // Quadratic algorithm, but probably good enough for now.
+ // TODO(gri) we need a fast quick type ID/hash for all types.
+ L:
+ for _, x := range x.types {
+ for _, y := range y.types {
+ if Identical(x, y) {
+ continue L // x is in y.types
+ }
+ }
+ return false // x is not in y.types
+ }
+ return true
+ }
+
case *Interface:
// Two interface types are identical if they have the same set of methods with
// the same names and identical function types. Lower-case method names from
// that case, interfaces are expected to be complete and lazy completion
// here is not needed.
if check != nil {
- check.completeInterface(x)
- check.completeInterface(y)
+ check.completeInterface(token.NoPos, x)
+ check.completeInterface(token.NoPos, y)
}
a := x.allMethods
b := y.allMethods
p = p.prev
}
if debug {
- assert(sort.IsSorted(byUniqueMethodName(a)))
- assert(sort.IsSorted(byUniqueMethodName(b)))
+ assertSortedMethods(a)
+ assertSortedMethods(b)
}
for i, f := range a {
g := b[i]
// Two named types are identical if their type names originate
// in the same type declaration.
if y, ok := y.(*Named); ok {
+ // TODO(gri) Why is x == y not sufficient? And if it is,
+ // we can just return false here because x == y
+ // is caught in the very beginning of this function.
return x.obj == y.obj
}
+ case *TypeParam:
+ // nothing to do (x and y being equal is caught in the very beginning of this function)
+
+ // case *instance:
+ // unreachable since types are expanded
+
+ case *bottom, *top:
+ // Either both types are theBottom, or both are theTop in which
+ // case the initial x == y check will have caught them. Otherwise
+ // they are not identical.
+
case nil:
+ // avoid a crash in case of nil type
default:
unreachable()
return false
}
+func (check *Checker) identicalTParams(x, y []*TypeName, cmpTags bool, p *ifacePair) bool {
+ if len(x) != len(y) {
+ return false
+ }
+ for i, x := range x {
+ y := y[i]
+ if !check.identical0(x.typ.(*TypeParam).bound, y.typ.(*TypeParam).bound, cmpTags, p) {
+ return false
+ }
+ }
+ return true
+}
+
// Default returns the default "typed" type for an "untyped" type;
// it returns the incoming type for all other types. The default type
// for untyped nil is untyped nil.
type declInfo struct {
file *Scope // scope of file containing this declaration
lhs []*Var // lhs of n:1 variable declarations, or nil
- typ ast.Expr // type, or nil
- init ast.Expr // init/orig expression, or nil
+ vtyp ast.Expr // type, or nil (for const and var declarations only)
+ init ast.Expr // init/orig expression, or nil (for const and var declarations only)
inherited bool // if set, the init expression is inherited from a previous constant declaration
+ tdecl *ast.TypeSpec // type declaration, or nil
fdecl *ast.FuncDecl // func declaration, or nil
- aliasPos token.Pos // If valid, the decl is a type alias and aliasPos is the position of '='.
// The deps field tracks initialization expression dependencies.
deps map[Object]bool // lazily initialized
pkgImports[imp] = true
}
- var methods []*Func // list of methods with non-blank _ names
+ type methodInfo struct {
+ obj *Func // method
+ ptr bool // true if pointer receiver
+ recv *ast.Ident // receiver type name
+ }
+ var methods []methodInfo // collected methods with valid receivers and non-blank _ names
+ var fileScopes []*Scope
for fileNo, file := range check.files {
// The package identifier denotes the current package,
// but there is no corresponding package object.
pos, end = token.Pos(f.Base()), token.Pos(f.Base()+f.Size())
}
fileScope := NewScope(check.pkg.scope, pos, end, check.filename(fileNo))
+ fileScopes = append(fileScopes, fileScope)
check.recordScope(file, fileScope)
// determine file directory, necessary to resolve imports
return
}
- // add package to list of explicit imports
- // (this functionality is provided as a convenience
- // for clients; it is not needed for type-checking)
- if !pkgImports[imp] {
- pkgImports[imp] = true
- pkg.imports = append(pkg.imports, imp)
- }
-
// local name overrides imported package name
name := imp.name
if d.spec.Name != nil {
check.errorf(d.spec.Name, _ImportCRenamed, `cannot rename import "C"`)
return
}
- if name == "init" {
- check.errorf(d.spec.Name, _InvalidInitDecl, "cannot declare init - must be func")
- return
- }
}
- obj := NewPkgName(d.spec.Pos(), pkg, name, imp)
+ if name == "init" {
+ check.errorf(d.spec.Name, _InvalidInitDecl, "cannot import package as init - init must be a func")
+ return
+ }
+
+ // add package to list of explicit imports
+ // (this functionality is provided as a convenience
+ // for clients; it is not needed for type-checking)
+ if !pkgImports[imp] {
+ pkgImports[imp] = true
+ pkg.imports = append(pkg.imports, imp)
+ }
+
+ pkgName := NewPkgName(d.spec.Pos(), pkg, name, imp)
if d.spec.Name != nil {
// in a dot-import, the dot represents the package
- check.recordDef(d.spec.Name, obj)
+ check.recordDef(d.spec.Name, pkgName)
} else {
- check.recordImplicit(d.spec, obj)
+ check.recordImplicit(d.spec, pkgName)
}
if path == "C" {
// match cmd/compile (not prescribed by spec)
- obj.used = true
+ pkgName.used = true
}
// add import to file scope
+ check.imports = append(check.imports, pkgName)
if name == "." {
+ // dot-import
+ if check.dotImportMap == nil {
+ check.dotImportMap = make(map[dotImportKey]*PkgName)
+ }
// merge imported scope with file scope
for _, obj := range imp.scope.elems {
// A package scope may contain non-exported objects,
if alt := fileScope.Insert(obj); alt != nil {
check.errorf(d.spec.Name, _DuplicateDecl, "%s redeclared in this block", obj.Name())
check.reportAltDecl(alt)
+ } else {
+ check.dotImportMap[dotImportKey{fileScope, obj}] = pkgName
}
}
}
- // add position to set of dot-import positions for this file
- // (this is only needed for "imported but not used" errors)
- check.addUnusedDotImport(fileScope, imp, d.spec)
} else {
// declare imported package object in file scope
// (no need to provide s.Name since we called check.recordDef earlier)
- check.declare(fileScope, nil, obj, token.NoPos)
+ check.declare(fileScope, nil, pkgName, token.NoPos)
}
case constDecl:
// declare all constants
init = d.init[i]
}
- d := &declInfo{file: fileScope, typ: d.typ, init: init, inherited: d.inherited}
+ d := &declInfo{file: fileScope, vtyp: d.typ, init: init, inherited: d.inherited}
check.declarePkgObj(name, obj, d)
}
// The lhs elements are only set up after the for loop below,
// but that's ok because declareVar only collects the declInfo
// for a later phase.
- d1 = &declInfo{file: fileScope, lhs: lhs, typ: d.spec.Type, init: d.spec.Values[0]}
+ d1 = &declInfo{file: fileScope, lhs: lhs, vtyp: d.spec.Type, init: d.spec.Values[0]}
}
// declare all variables
if i < len(d.spec.Values) {
init = d.spec.Values[i]
}
- di = &declInfo{file: fileScope, typ: d.spec.Type, init: init}
+ di = &declInfo{file: fileScope, vtyp: d.spec.Type, init: init}
}
check.declarePkgObj(name, obj, di)
}
case typeDecl:
obj := NewTypeName(d.spec.Name.Pos(), pkg, d.spec.Name.Name, nil)
- check.declarePkgObj(d.spec.Name, obj, &declInfo{file: fileScope, typ: d.spec.Type, aliasPos: d.spec.Assign})
+ check.declarePkgObj(d.spec.Name, obj, &declInfo{file: fileScope, tdecl: d.spec})
case funcDecl:
info := &declInfo{file: fileScope, fdecl: d.decl}
name := d.decl.Name.Name
obj := NewFunc(d.decl.Name.Pos(), pkg, name, nil)
- if d.decl.Recv == nil {
+ if !d.decl.IsMethod() {
// regular function
+ if d.decl.Recv != nil {
+ check.error(d.decl.Recv, _BadRecv, "method is missing receiver")
+ // treat as function
+ }
+ if name == "init" || (name == "main" && check.pkg.name == "main") {
+ code := _InvalidInitDecl
+ if name == "main" {
+ code = _InvalidMainDecl
+ }
+ if d.decl.Type.TParams != nil {
+ check.softErrorf(d.decl.Type.TParams, code, "func %s must have no type parameters", name)
+ }
+ if t := d.decl.Type; t.Params.NumFields() != 0 || t.Results != nil {
+ // TODO(rFindley) Should this be a hard error?
+ check.softErrorf(d.decl, code, "func %s must have no arguments and no return values", name)
+ }
+ }
if name == "init" {
// don't declare init functions in the package scope - they are invisible
obj.parent = pkg.scope
check.recordDef(d.decl.Name, obj)
// init functions must have a body
if d.decl.Body == nil {
+ // TODO(gri) make this error message consistent with the others above
check.softErrorf(obj, _MissingInitBody, "missing function body")
}
} else {
}
} else {
// method
- // (Methods with blank _ names are never found; no need to collect
- // them for later type association. They will still be type-checked
- // with all the other functions.)
- if name != "_" {
- methods = append(methods, obj)
+
+ // TODO(rFindley) earlier versions of this code checked that methods
+ // have no type parameters, but this is checked later
+ // when type checking the function type. Confirm that
+ // we don't need to check tparams here.
+
+ ptr, recv, _ := check.unpackRecv(d.decl.Recv.List[0].Type, false)
+ // (Methods with invalid receiver cannot be associated to a type, and
+ // methods with blank _ names are never found; no need to collect any
+ // of them. They will still be type-checked with all the other functions.)
+ if recv != nil && name != "_" {
+ methods = append(methods, methodInfo{obj, ptr, recv})
}
check.recordDef(d.decl.Name, obj)
}
}
// verify that objects in package and file scopes have different names
- for _, scope := range check.pkg.scope.children /* file scopes */ {
+ for _, scope := range fileScopes {
for _, obj := range scope.elems {
if alt := pkg.scope.Lookup(obj.Name()); alt != nil {
if pkg, ok := obj.(*PkgName); ok {
return // nothing to do
}
check.methods = make(map[*TypeName][]*Func)
- for _, f := range methods {
- fdecl := check.objMap[f].fdecl
- if list := fdecl.Recv.List; len(list) > 0 {
- // f is a method.
- // Determine the receiver base type and associate f with it.
- ptr, base := check.resolveBaseTypeName(list[0].Type)
- if base != nil {
- f.hasPtrRecv = ptr
- check.methods[base] = append(check.methods[base], f)
+ for i := range methods {
+ m := &methods[i]
+ // Determine the receiver base type and associate m with it.
+ ptr, base := check.resolveBaseTypeName(m.ptr, m.recv)
+ if base != nil {
+ m.obj.hasPtrRecv = ptr
+ check.methods[base] = append(check.methods[base], m.obj)
+ }
+ }
+}
+
+// unpackRecv unpacks a receiver type and returns its components: ptr indicates whether
+// rtyp is a pointer receiver, rname is the receiver type name, and tparams are its
+// type parameters, if any. The type parameters are only unpacked if unpackParams is
+// set. If rname is nil, the receiver is unusable (i.e., the source has a bug which we
+// cannot easily work around).
+func (check *Checker) unpackRecv(rtyp ast.Expr, unpackParams bool) (ptr bool, rname *ast.Ident, tparams []*ast.Ident) {
+L: // unpack receiver type
+ // This accepts invalid receivers such as ***T and does not
+ // work for other invalid receivers, but we don't care. The
+ // validity of receiver expressions is checked elsewhere.
+ for {
+ switch t := rtyp.(type) {
+ case *ast.ParenExpr:
+ rtyp = t.X
+ case *ast.StarExpr:
+ ptr = true
+ rtyp = t.X
+ default:
+ break L
+ }
+ }
+
+ // unpack type parameters, if any
+ switch ptyp := rtyp.(type) {
+ case *ast.IndexExpr:
+ panic("unimplemented")
+ case *ast.CallExpr:
+ rtyp = ptyp.Fun
+ if unpackParams {
+ for _, arg := range ptyp.Args {
+ var par *ast.Ident
+ switch arg := arg.(type) {
+ case *ast.Ident:
+ par = arg
+ case *ast.BadExpr:
+ // ignore - error already reported by parser
+ case nil:
+ check.invalidAST(ptyp, "parameterized receiver contains nil parameters")
+ default:
+ check.errorf(arg, _Todo, "receiver type parameter %s must be an identifier", arg)
+ }
+ if par == nil {
+ par = &ast.Ident{NamePos: arg.Pos(), Name: "_"}
+ }
+ tparams = append(tparams, par)
}
}
}
+
+ // unpack receiver name
+ if name, _ := rtyp.(*ast.Ident); name != nil {
+ rname = name
+ }
+
+ return
}
// resolveBaseTypeName returns the non-alias base type name for typ, and whether
// there was a pointer indirection to get to it. The base type name must be declared
// in package scope, and there can be at most one pointer indirection. If no such type
// name exists, the returned base is nil.
-func (check *Checker) resolveBaseTypeName(typ ast.Expr) (ptr bool, base *TypeName) {
+func (check *Checker) resolveBaseTypeName(seenPtr bool, name *ast.Ident) (ptr bool, base *TypeName) {
// Algorithm: Starting from a type expression, which may be a name,
// we follow that type through alias declarations until we reach a
// non-alias type name. If we encounter anything but pointer types or
// parentheses we're done. If we encounter more than one pointer type
// we're done.
+ ptr = seenPtr
var seen map[*TypeName]bool
+ var typ ast.Expr = name
for {
typ = unparen(typ)
// we're done if tdecl defined tname as a new type
// (rather than an alias)
- tdecl := check.objMap[tname] // must exist for objects in package scope
- if !tdecl.aliasPos.IsValid() {
+ tdecl := check.objMap[tname].tdecl // must exist for objects in package scope
+ if !tdecl.Assign.IsValid() {
return ptr, tname
}
// otherwise, continue resolving
- typ = tdecl.typ
+ typ = tdecl.Type
if seen == nil {
seen = make(map[*TypeName]bool)
}
// add new methods to already type-checked types (from a prior Checker.Files call)
for _, obj := range objList {
if obj, _ := obj.(*TypeName); obj != nil && obj.typ != nil {
- check.addMethodDecls(obj)
+ check.collectMethods(obj)
}
}
// phase 1
for _, obj := range objList {
// If we have a type alias, collect it for the 2nd phase.
- if tname, _ := obj.(*TypeName); tname != nil && check.objMap[tname].aliasPos.IsValid() {
+ if tname, _ := obj.(*TypeName); tname != nil && check.objMap[tname].tdecl.Assign.IsValid() {
aliasList = append(aliasList, tname)
continue
}
// any of its exported identifiers. To import a package solely for its side-effects
// (initialization), use the blank identifier as explicit package name."
- // check use of regular imported packages
- for _, scope := range check.pkg.scope.children /* file scopes */ {
- for _, obj := range scope.elems {
- if obj, ok := obj.(*PkgName); ok {
- // Unused "blank imports" are automatically ignored
- // since _ identifiers are not entered into scopes.
- if !obj.used {
- path := obj.imported.path
- base := pkgName(path)
- if obj.name == base {
- check.softErrorf(obj, _UnusedImport, "%q imported but not used", path)
- } else {
- check.softErrorf(obj, _UnusedImport, "%q imported but not used as %s", path, obj.name)
- }
- }
- }
- }
- }
-
- // check use of dot-imported packages
- for _, unusedDotImports := range check.unusedDotImports {
- for pkg, pos := range unusedDotImports {
- check.softErrorf(pos, _UnusedImport, "%q imported but not used", pkg.path)
+ for _, obj := range check.imports {
+ if !obj.used && obj.name != "_" {
+ check.errorUnusedPkg(obj)
}
}
}
- // pkgName returns the package name (last element) of an import path.
- func pkgName(path string) string {
- if i := strings.LastIndex(path, "/"); i >= 0 {
- path = path[i+1:]
+ func (check *Checker) errorUnusedPkg(obj *PkgName) {
+ // If the package was imported with a name other than the final
+ // import path element, show it explicitly in the error message.
+ // Note that this handles both renamed imports and imports of
+ // packages containing unconventional package declarations.
+ // Note that this uses / always, even on Windows, because Go import
+ // paths always use forward slashes.
+ path := obj.imported.path
+ elem := path
+ if i := strings.LastIndex(elem, "/"); i >= 0 {
+ elem = elem[i+1:]
+ }
+ if obj.name == "" || obj.name == "." || obj.name == elem {
+ check.softErrorf(obj, _UnusedImport, "%q imported but not used", path)
+ } else {
+ check.softErrorf(obj, _UnusedImport, "%q imported but not used as %s", path, obj.name)
}
- return path
}
// dir makes a good-faith attempt to return the directory
_ = append(s, b)
_ = append(s, x /* ERROR cannot use x */ )
_ = append(s, s /* ERROR cannot use s */ )
- _ = append(s... /* ERROR can only use ... with matching parameter */ )
- _ = append(s, b, s... /* ERROR can only use ... with matching parameter */ )
+ _ = append(s...) /* ERROR not enough arguments */
+ _ = append(s, b, s /* ERROR too many arguments */ ...)
_ = append(s, 1, 2, 3)
_ = append(s, 1, 2, 3, x /* ERROR cannot use x */ , 5, 6, 6)
- _ = append(s, 1, 2, s... /* ERROR can only use ... with matching parameter */ )
+ _ = append(s, 1, 2 /* ERROR too many arguments */, s...)
_ = append([]interface{}(nil), 1, 2, "foo", x, 3.1425, false)
type S []byte
delete() // ERROR not enough arguments
delete(1) // ERROR not enough arguments
delete(1, 2, 3) // ERROR too many arguments
- delete(m, 0 /* ERROR not assignable */)
+ delete(m, 0 /* ERROR cannot use */)
delete(m, s)
_ = delete /* ERROR used as value */ (m, s)
panic("foo")
panic(false)
panic(1<<10)
- panic(1 /* ERROR overflows */ <<1000)
+ panic(1 << /* ERROR constant shift overflow */ 1000)
_ = panic /* ERROR used as value */ (0)
var s []byte
print(2.718281828)
print(false)
print(1<<10)
- print(1 /* ERROR overflows */ <<1000)
+ print(1 << /* ERROR constant shift overflow */ 1000)
println(nil /* ERROR untyped nil */ )
var s []int
println(2.718281828)
println(false)
println(1<<10)
- println(1 /* ERROR overflows */ <<1000)
+ println(1 << /* ERROR constant shift overflow */ 1000)
println(nil /* ERROR untyped nil */ )
var s []int
_ = unsafe.Alignof(42)
_ = unsafe.Alignof(new(struct{}))
_ = unsafe.Alignof(1<<10)
- _ = unsafe.Alignof(1 /* ERROR overflows */ <<1000)
+ _ = unsafe.Alignof(1 << /* ERROR constant shift overflow */ 1000)
_ = unsafe.Alignof(nil /* ERROR "untyped nil */ )
unsafe /* ERROR not used */ .Alignof(x)
_ = unsafe.Sizeof(42)
_ = unsafe.Sizeof(new(complex128))
_ = unsafe.Sizeof(1<<10)
- _ = unsafe.Sizeof(1 /* ERROR overflows */ <<1000)
+ _ = unsafe.Sizeof(1 << /* ERROR constant shift overflow */ 1000)
_ = unsafe.Sizeof(nil /* ERROR untyped nil */ )
unsafe /* ERROR not used */ .Sizeof(x)
g := func(int, bool){}
var m map[int]int
- g(m[0]) /* ERROR "too few arguments" */
+ g(m[0]) /* ERROR "not enough arguments" */
// assignments to _
_ = nil /* ERROR "use of untyped nil" */
- _ = 1 /* ERROR overflow */ <<1000
+ _ = 1 << /* ERROR constant shift overflow */ 1000
(_) = 0
}
ee = e
_ = ee
}
- for _ = range sc /* ERROR "cannot range over send-only channel" */ {}
+ for _ = range sc /* ERROR "cannot range over" */ {}
for _ = range rc {}
// constant strings
package types
- "sort"
+import (
+ "fmt"
+ "go/token"
+)
+
// A Type represents a type of Go.
// All types implement the Type interface.
type Type interface {
- // Underlying returns the underlying type of a type.
+ // Underlying returns the underlying type of a type
+ // w/o following forwarding chains. Only used by
+ // client packages (here for backward-compatibility).
Underlying() Type
// String returns a string representation of a type.
// NewArray returns a new array type for the given element type and length.
// A negative length indicates an unknown length.
-func NewArray(elem Type, len int64) *Array { return &Array{len, elem} }
+func NewArray(elem Type, len int64) *Array { return &Array{len: len, elem: elem} }
// Len returns the length of array a.
// A negative result indicates an unknown length.
}
// NewSlice returns a new slice type for the given element type.
-func NewSlice(elem Type) *Slice { return &Slice{elem} }
+func NewSlice(elem Type) *Slice { return &Slice{elem: elem} }
// Elem returns the element type of slice s.
func (s *Slice) Elem() Type { return s.elem }
// NewTuple returns a new tuple for the given variables.
func NewTuple(x ...*Var) *Tuple {
if len(x) > 0 {
- return &Tuple{x}
+ return &Tuple{vars: x}
}
+ // TODO(gri) Don't represent empty tuples with a (*Tuple)(nil) pointer;
+ // it's too subtle and causes problems.
return nil
}
// and store it in the Func Object) because when type-checking a function
// literal we call the general type checker which returns a general Type.
// We then unpack the *Signature and use the scope for the literal body.
- scope *Scope // function scope, present for package-local signatures
- recv *Var // nil if not a method
- params *Tuple // (incoming) parameters from left to right; or nil
- results *Tuple // (outgoing) results from left to right; or nil
- variadic bool // true if the last parameter's type is of the form ...T (or string, for append built-in only)
+ rparams []*TypeName // receiver type parameters from left to right, or nil
+ tparams []*TypeName // type parameters from left to right, or nil
+ scope *Scope // function scope, present for package-local signatures
+ recv *Var // nil if not a method
+ params *Tuple // (incoming) parameters from left to right; or nil
+ results *Tuple // (outgoing) results from left to right; or nil
+ variadic bool // true if the last parameter's type is of the form ...T (or string, for append built-in only)
}
// NewSignature returns a new function type for the given receiver, parameters,
panic("types.NewSignature: variadic parameter must be of unnamed slice type")
}
}
- return &Signature{nil, recv, params, results, variadic}
+ return &Signature{recv: recv, params: params, results: results, variadic: variadic}
}
// Recv returns the receiver of signature s (if a method), or nil if a
// contain methods whose receiver type is a different interface.
func (s *Signature) Recv() *Var { return s.recv }
+// TParams returns the type parameters of signature s, or nil.
+func (s *Signature) TParams() []*TypeName { return s.tparams }
+
+// SetTParams sets the type parameters of signature s.
+func (s *Signature) SetTParams(tparams []*TypeName) { s.tparams = tparams }
+
// Params returns the parameters of signature s, or nil.
func (s *Signature) Params() *Tuple { return s.params }
// Variadic reports whether the signature s is variadic.
func (s *Signature) Variadic() bool { return s.variadic }
+// A Sum represents a set of possible types.
+// Sums are currently used to represent type lists of interfaces
+// and thus the underlying types of type parameters; they are not
+// first class types of Go.
+type Sum struct {
+ types []Type // types are unique
+}
+
+// NewSum returns a new Sum type consisting of the provided
+// types if there are more than one. If there is exactly one
+// type, it returns that type. If the list of types is empty
+// the result is nil.
+func NewSum(types []Type) Type {
+ if len(types) == 0 {
+ return nil
+ }
+
+ // What should happen if types contains a sum type?
+ // Do we flatten the types list? For now we check
+ // and panic. This should not be possible for the
+ // current use case of type lists.
+ // TODO(gri) Come up with the rules for sum types.
+ for _, t := range types {
+ if _, ok := t.(*Sum); ok {
+ panic("sum type contains sum type - unimplemented")
+ }
+ }
+
+ if len(types) == 1 {
+ return types[0]
+ }
+ return &Sum{types: types}
+}
+
+// is reports whether all types in t satisfy pred.
+func (s *Sum) is(pred func(Type) bool) bool {
+ if s == nil {
+ return false
+ }
+ for _, t := range s.types {
+ if !pred(t) {
+ return false
+ }
+ }
+ return true
+}
+
// An Interface represents an interface type.
type Interface struct {
methods []*Func // ordered list of explicitly declared methods
+ types Type // (possibly a Sum) type declared with a type list (TODO(gri) need better field name)
embeddeds []Type // ordered list of explicitly embedded types
allMethods []*Func // ordered list of methods declared with or embedded in this interface (TODO(gri): replace with mset)
+ allTypes Type // intersection of all embedded and locally declared types (TODO(gri) need better field name)
+
+ obj Object // type declaration defining this interface; or nil (for better error messages)
+}
+
+// unpack unpacks a type into a list of types.
+// TODO(gri) Try to eliminate the need for this function.
+func unpackType(typ Type) []Type {
+ if typ == nil {
+ return nil
+ }
+ if sum := asSum(typ); sum != nil {
+ return sum.types
+ }
+ return []Type{typ}
+}
+
+// is reports whether interface t represents types that all satisfy pred.
+func (t *Interface) is(pred func(Type) bool) bool {
+ if t.allTypes == nil {
+ return false // we must have at least one type! (was bug)
+ }
+ for _, t := range unpackType(t.allTypes) {
+ if !pred(t) {
+ return false
+ }
+ }
+ return true
}
// emptyInterface represents the empty (completed) interface
}
// sort for API stability
- sort.Sort(byUniqueMethodName(methods))
- sort.Stable(byUniqueTypeName(embeddeds))
+ sortMethods(methods)
+ sortTypes(embeddeds)
typ.methods = methods
typ.embeddeds = embeddeds
func (t *Interface) Method(i int) *Func { t.assertCompleteness(); return t.allMethods[i] }
// Empty reports whether t is the empty interface.
-// The interface must have been completed.
-func (t *Interface) Empty() bool { t.assertCompleteness(); return len(t.allMethods) == 0 }
+func (t *Interface) Empty() bool {
+ if t.allMethods != nil {
+ // interface is complete - quick test
+ // A non-nil allTypes may still be empty and represents the bottom type.
+ return len(t.allMethods) == 0 && t.allTypes == nil
+ }
+ return !t.iterate(func(t *Interface) bool {
+ return len(t.methods) > 0 || t.types != nil
+ }, nil)
+}
+
+// HasTypeList reports whether interface t has a type list, possibly from an embedded type.
+func (t *Interface) HasTypeList() bool {
+ if t.allMethods != nil {
+ // interface is complete - quick test
+ return t.allTypes != nil
+ }
+
+ return t.iterate(func(t *Interface) bool {
+ return t.types != nil
+ }, nil)
+}
+
+// IsComparable reports whether interface t is or embeds the predeclared interface "comparable".
+func (t *Interface) IsComparable() bool {
+ if t.allMethods != nil {
+ // interface is complete - quick test
+ _, m := lookupMethod(t.allMethods, nil, "==")
+ return m != nil
+ }
+
+ return t.iterate(func(t *Interface) bool {
+ _, m := lookupMethod(t.methods, nil, "==")
+ return m != nil
+ }, nil)
+}
+
+// IsConstraint reports t.HasTypeList() || t.IsComparable().
+func (t *Interface) IsConstraint() bool {
+ if t.allMethods != nil {
+ // interface is complete - quick test
+ if t.allTypes != nil {
+ return true
+ }
+ _, m := lookupMethod(t.allMethods, nil, "==")
+ return m != nil
+ }
+
+ return t.iterate(func(t *Interface) bool {
+ if t.types != nil {
+ return true
+ }
+ _, m := lookupMethod(t.methods, nil, "==")
+ return m != nil
+ }, nil)
+}
+
+// iterate calls f with t and then with any embedded interface of t, recursively, until f returns true.
+// iterate reports whether any call to f returned true.
+func (t *Interface) iterate(f func(*Interface) bool, seen map[*Interface]bool) bool {
+ if f(t) {
+ return true
+ }
+ for _, e := range t.embeddeds {
+ // e should be an interface but be careful (it may be invalid)
+ if e := asInterface(e); e != nil {
+ // Cyclic interfaces such as "type E interface { E }" are not permitted
+ // but they are still constructed and we need to detect such cycles.
+ if seen[e] {
+ continue
+ }
+ if seen == nil {
+ seen = make(map[*Interface]bool)
+ }
+ seen[e] = true
+ if e.iterate(f, seen) {
+ return true
+ }
+ }
+ }
+ return false
+}
+
+// isSatisfiedBy reports whether interface t's type list is satisfied by the type typ.
+// If the the type list is empty (absent), typ trivially satisfies the interface.
+// TODO(gri) This is not a great name. Eventually, we should have a more comprehensive
+// "implements" predicate.
+func (t *Interface) isSatisfiedBy(typ Type) bool {
+ t.Complete()
+ if t.allTypes == nil {
+ return true
+ }
+ types := unpackType(t.allTypes)
+ return includes(types, typ) || includes(types, under(typ))
+}
// Complete computes the interface's method set. It must be called by users of
// NewInterfaceType and NewInterface after the interface's embedded types are
addMethod(m, true)
}
+ allTypes := t.types
+
for _, typ := range t.embeddeds {
- typ := typ.Underlying().(*Interface)
- typ.Complete()
- for _, m := range typ.allMethods {
+ utyp := under(typ)
+ etyp := asInterface(utyp)
+ if etyp == nil {
+ if utyp != Typ[Invalid] {
+ panic(fmt.Sprintf("%s is not an interface", typ))
+ }
+ continue
+ }
+ etyp.Complete()
+ for _, m := range etyp.allMethods {
addMethod(m, false)
}
+ allTypes = intersect(allTypes, etyp.allTypes)
}
for i := 0; i < len(todo); i += 2 {
}
if methods != nil {
- sort.Sort(byUniqueMethodName(methods))
+ sortMethods(methods)
t.allMethods = methods
}
+ t.allTypes = allTypes
return t
}
// NewMap returns a new map for the given key and element types.
func NewMap(key, elem Type) *Map {
- return &Map{key, elem}
+ return &Map{key: key, elem: elem}
}
// Key returns the key type of map m.
// NewChan returns a new channel type for the given direction and element type.
func NewChan(dir ChanDir, elem Type) *Chan {
- return &Chan{dir, elem}
+ return &Chan{dir: dir, elem: elem}
}
// Dir returns the direction of channel c.
// Elem returns the element type of channel c.
func (c *Chan) Elem() Type { return c.elem }
-// A Named represents a named type.
+// A Named represents a named (defined) type.
type Named struct {
- info typeInfo // for cycle detection
- obj *TypeName // corresponding declared object
- orig Type // type (on RHS of declaration) this *Named type is derived of (for cycle reporting)
- underlying Type // possibly a *Named during setup; never a *Named once set up completely
- methods []*Func // methods declared for this type (not the method set of this type); signatures are type-checked lazily
+ check *Checker // for Named.under implementation
+ info typeInfo // for cycle detection
+ obj *TypeName // corresponding declared object
+ orig Type // type (on RHS of declaration) this *Named type is derived of (for cycle reporting)
+ underlying Type // possibly a *Named during setup; never a *Named once set up completely
+ tparams []*TypeName // type parameters, or nil
+ targs []Type // type arguments (after instantiation), or nil
+ methods []*Func // methods declared for this type (not the method set of this type); signatures are type-checked lazily
}
// NewNamed returns a new named type for the given type name, underlying type, and associated methods.
return typ
}
+func (check *Checker) NewNamed(obj *TypeName, underlying Type, methods []*Func) *Named {
+ typ := &Named{check: check, obj: obj, orig: underlying, underlying: underlying, methods: methods}
+ if obj.typ == nil {
+ obj.typ = typ
+ }
+ return typ
+}
+
// Obj returns the type name for the named type t.
func (t *Named) Obj() *TypeName { return t.obj }
+// TODO(gri) Come up with a better representation and API to distinguish
+// between parameterized instantiated and non-instantiated types.
+
+// TParams returns the type parameters of the named type t, or nil.
+// The result is non-nil for an (originally) parameterized type even if it is instantiated.
+func (t *Named) TParams() []*TypeName { return t.tparams }
+
+// TArgs returns the type arguments after instantiation of the named type t, or nil if not instantiated.
+func (t *Named) TArgs() []Type { return t.targs }
+
+// SetTArgs sets the type arguments of Named.
+func (t *Named) SetTArgs(args []Type) { t.targs = args }
+
// NumMethods returns the number of explicit methods whose receiver is named type t.
func (t *Named) NumMethods() int { return len(t.methods) }
}
}
-// Implementations for Type methods.
+// A TypeParam represents a type parameter type.
+type TypeParam struct {
+ check *Checker // for lazy type bound completion
+ id uint64 // unique id
+ obj *TypeName // corresponding type name
+ index int // parameter index
+ bound Type // *Named or *Interface; underlying type is always *Interface
+}
-func (b *Basic) Underlying() Type { return b }
-func (a *Array) Underlying() Type { return a }
-func (s *Slice) Underlying() Type { return s }
-func (s *Struct) Underlying() Type { return s }
-func (p *Pointer) Underlying() Type { return p }
+// NewTypeParam returns a new TypeParam.
+func (check *Checker) NewTypeParam(obj *TypeName, index int, bound Type) *TypeParam {
+ assert(bound != nil)
+ typ := &TypeParam{check: check, id: check.nextId, obj: obj, index: index, bound: bound}
+ check.nextId++
+ if obj.typ == nil {
+ obj.typ = typ
+ }
+ return typ
+}
+
+func (t *TypeParam) Bound() *Interface {
+ iface := asInterface(t.bound)
+ // use the type bound position if we have one
+ pos := token.NoPos
+ if n, _ := t.bound.(*Named); n != nil {
+ pos = n.obj.pos
+ }
+ // TODO(rFindley) switch this to an unexported method on Checker.
+ t.check.completeInterface(pos, iface)
+ return iface
+}
+
+// optype returns a type's operational type. Except for
+// type parameters, the operational type is the same
+// as the underlying type (as returned by under). For
+// Type parameters, the operational type is determined
+// by the corresponding type bound's type list. The
+// result may be the bottom or top type, but it is never
+// the incoming type parameter.
+func optype(typ Type) Type {
+ if t := asTypeParam(typ); t != nil {
+ // If the optype is typ, return the top type as we have
+ // no information. It also prevents infinite recursion
+ // via the asTypeParam converter function. This can happen
+ // for a type parameter list of the form:
+ // (type T interface { type T }).
+ // See also issue #39680.
+ if u := t.Bound().allTypes; u != nil && u != typ {
+ // u != typ and u is a type parameter => under(u) != typ, so this is ok
+ return under(u)
+ }
+ return theTop
+ }
+ return under(typ)
+}
+
+// An instance represents an instantiated generic type syntactically
+// (without expanding the instantiation). Type instances appear only
+// during type-checking and are replaced by their fully instantiated
+// (expanded) types before the end of type-checking.
+type instance struct {
+ check *Checker // for lazy instantiation
+ pos token.Pos // position of type instantiation; for error reporting only
+ base *Named // parameterized type to be instantiated
+ targs []Type // type arguments
+ poslist []token.Pos // position of each targ; for error reporting only
+ value Type // base(targs...) after instantiation or Typ[Invalid]; nil if not yet set
+}
+
+// expand returns the instantiated (= expanded) type of t.
+// The result is either an instantiated *Named type, or
+// Typ[Invalid] if there was an error.
+func (t *instance) expand() Type {
+ v := t.value
+ if v == nil {
+ v = t.check.instantiate(t.pos, t.base, t.targs, t.poslist)
+ if v == nil {
+ v = Typ[Invalid]
+ }
+ t.value = v
+ }
+ // After instantiation we must have an invalid or a *Named type.
+ if debug && v != Typ[Invalid] {
+ _ = v.(*Named)
+ }
+ return v
+}
+
+// expand expands a type instance into its instantiated
+// type and leaves all other types alone. expand does
+// not recurse.
+func expand(typ Type) Type {
+ if t, _ := typ.(*instance); t != nil {
+ return t.expand()
+ }
+ return typ
+}
+
+// expandf is set to expand.
+// Call expandf when calling expand causes compile-time cycle error.
+var expandf func(Type) Type
+
+func init() { expandf = expand }
+
+// bottom represents the bottom of the type lattice.
+// It is the underlying type of a type parameter that
+// cannot be satisfied by any type, usually because
+// the intersection of type constraints left nothing).
+type bottom struct{}
+
+// theBottom is the singleton bottom type.
+var theBottom = &bottom{}
+
+// top represents the top of the type lattice.
+// It is the underlying type of a type parameter that
+// can be satisfied by any type (ignoring methods),
+// usually because the type constraint has no type
+// list.
+type top struct{}
+
+// theTop is the singleton top type.
+var theTop = &top{}
+
+// Type-specific implementations of Underlying.
+func (t *Basic) Underlying() Type { return t }
+func (t *Array) Underlying() Type { return t }
+func (t *Slice) Underlying() Type { return t }
+func (t *Struct) Underlying() Type { return t }
+func (t *Pointer) Underlying() Type { return t }
func (t *Tuple) Underlying() Type { return t }
-func (s *Signature) Underlying() Type { return s }
+func (t *Signature) Underlying() Type { return t }
+func (t *Sum) Underlying() Type { return t }
func (t *Interface) Underlying() Type { return t }
-func (m *Map) Underlying() Type { return m }
-func (c *Chan) Underlying() Type { return c }
+func (t *Map) Underlying() Type { return t }
+func (t *Chan) Underlying() Type { return t }
func (t *Named) Underlying() Type { return t.underlying }
-
-func (b *Basic) String() string { return TypeString(b, nil) }
-func (a *Array) String() string { return TypeString(a, nil) }
-func (s *Slice) String() string { return TypeString(s, nil) }
-func (s *Struct) String() string { return TypeString(s, nil) }
-func (p *Pointer) String() string { return TypeString(p, nil) }
+func (t *TypeParam) Underlying() Type { return t }
+func (t *instance) Underlying() Type { return t }
+func (t *bottom) Underlying() Type { return t }
+func (t *top) Underlying() Type { return t }
+
+// Type-specific implementations of String.
+func (t *Basic) String() string { return TypeString(t, nil) }
+func (t *Array) String() string { return TypeString(t, nil) }
+func (t *Slice) String() string { return TypeString(t, nil) }
+func (t *Struct) String() string { return TypeString(t, nil) }
+func (t *Pointer) String() string { return TypeString(t, nil) }
func (t *Tuple) String() string { return TypeString(t, nil) }
-func (s *Signature) String() string { return TypeString(s, nil) }
+func (t *Signature) String() string { return TypeString(t, nil) }
+func (t *Sum) String() string { return TypeString(t, nil) }
func (t *Interface) String() string { return TypeString(t, nil) }
-func (m *Map) String() string { return TypeString(m, nil) }
-func (c *Chan) String() string { return TypeString(c, nil) }
+func (t *Map) String() string { return TypeString(t, nil) }
+func (t *Chan) String() string { return TypeString(t, nil) }
func (t *Named) String() string { return TypeString(t, nil) }
+func (t *TypeParam) String() string { return TypeString(t, nil) }
+func (t *instance) String() string { return TypeString(t, nil) }
+func (t *bottom) String() string { return TypeString(t, nil) }
+func (t *top) String() string { return TypeString(t, nil) }
+
+// under returns the true expanded underlying type.
+// If it doesn't exist, the result is Typ[Invalid].
+// under must only be called when a type is known
+// to be fully set up.
+func under(t Type) Type {
+ // TODO(gri) is this correct for *Sum?
+ if n := asNamed(t); n != nil {
+ return n.under()
+ }
+ return t
+}
+
+// Converters
+//
+// A converter must only be called when a type is
+// known to be fully set up. A converter returns
+// a type's operational type (see comment for optype)
+// or nil if the type argument is not of the
+// respective type.
+
+func asBasic(t Type) *Basic {
+ op, _ := optype(t).(*Basic)
+ return op
+}
+
+func asArray(t Type) *Array {
+ op, _ := optype(t).(*Array)
+ return op
+}
+
+func asSlice(t Type) *Slice {
+ op, _ := optype(t).(*Slice)
+ return op
+}
+
+// TODO (rFindley) delete this on the dev.typeparams branch. This is only
+// exported in the prototype for legacy compatibility.
+func AsStruct(t Type) *Struct {
+ return asStruct(t)
+}
+
+func asStruct(t Type) *Struct {
+ op, _ := optype(t).(*Struct)
+ return op
+}
+
+// TODO(rFindley) delete this on the dev.typeparams branch (see ToStruct).
+func AsPointer(t Type) *Pointer {
+ return asPointer(t)
+}
+
+func asPointer(t Type) *Pointer {
+ op, _ := optype(t).(*Pointer)
+ return op
+}
+
+func asTuple(t Type) *Tuple {
+ op, _ := optype(t).(*Tuple)
+ return op
+}
+
+func asSignature(t Type) *Signature {
+ op, _ := optype(t).(*Signature)
+ return op
+}
+
+func asSum(t Type) *Sum {
+ op, _ := optype(t).(*Sum)
+ return op
+}
+
+func asInterface(t Type) *Interface {
+ op, _ := optype(t).(*Interface)
+ return op
+}
+
+func asMap(t Type) *Map {
+ op, _ := optype(t).(*Map)
+ return op
+}
+
+func asChan(t Type) *Chan {
+ op, _ := optype(t).(*Chan)
+ return op
+}
+
+// If the argument to asNamed and asTypeParam is of the respective types
+// (possibly after expanding an instance type), these methods return that type.
+// Otherwise the result is nil.
+
+func asNamed(t Type) *Named {
+ e, _ := expand(t).(*Named)
+ return e
+}
+
+func asTypeParam(t Type) *TypeParam {
+ u, _ := under(t).(*TypeParam)
+ return u
+}
package types
import (
+ "fmt"
"go/ast"
"go/constant"
"go/token"
"sort"
"strconv"
+ "strings"
)
// ident type-checks identifier e and initializes x with the value or type of e.
}
assert(typ != nil)
- // The object may be dot-imported: If so, remove its package from
- // the map of unused dot imports for the respective file scope.
+ // The object may have been dot-imported.
+ // If so, mark the respective package as used.
// (This code is only needed for dot-imports. Without them,
// we only have to mark variables, see *Var case below).
- if pkg := obj.Pkg(); pkg != check.pkg && pkg != nil {
- delete(check.unusedDotImports[scope], pkg)
+ if pkgName := check.dotImportMap[dotImportKey{scope, obj}]; pkgName != nil {
+ pkgName.used = true
}
switch obj := obj.(type) {
}
// typ type-checks the type expression e and returns its type, or Typ[Invalid].
+// The type must not be an (uninstantiated) generic type.
func (check *Checker) typ(e ast.Expr) Type {
return check.definedType(e, nil)
}
+// varType type-checks the type expression e and returns its type, or Typ[Invalid].
+// The type must not be an (uninstantiated) generic type and it must be ordinary
+// (see ordinaryType).
+func (check *Checker) varType(e ast.Expr) Type {
+ typ := check.definedType(e, nil)
+ check.ordinaryType(e, typ)
+ return typ
+}
+
+// ordinaryType reports an error if typ is an interface type containing
+// type lists or is (or embeds) the predeclared type comparable.
+func (check *Checker) ordinaryType(pos positioner, typ Type) {
+ // We don't want to call under() (via asInterface) or complete interfaces
+ // while we are in the middle of type-checking parameter declarations that
+ // might belong to interface methods. Delay this check to the end of
+ // type-checking.
+ check.atEnd(func() {
+ if t := asInterface(typ); t != nil {
+ check.completeInterface(pos.Pos(), t) // TODO(gri) is this the correct position?
+ if t.allTypes != nil {
+ check.softErrorf(pos, _Todo, "interface contains type constraints (%s)", t.allTypes)
+ return
+ }
+ if t.IsComparable() {
+ check.softErrorf(pos, _Todo, "interface is (or embeds) comparable")
+ }
+ }
+ })
+}
+
+// anyType type-checks the type expression e and returns its type, or Typ[Invalid].
+// The type may be generic or instantiated.
+func (check *Checker) anyType(e ast.Expr) Type {
+ typ := check.typInternal(e, nil)
+ assert(isTyped(typ))
+ check.recordTypeAndValue(e, typexpr, typ, nil)
+ return typ
+}
+
// definedType is like typ but also accepts a type name def.
// If def != nil, e is the type specification for the defined type def, declared
// in a type declaration, and def.underlying will be set to the type of e before
// any components of e are type-checked.
//
-func (check *Checker) definedType(e ast.Expr, def *Named) (T Type) {
- if trace {
- check.trace(e.Pos(), "%s", e)
- check.indent++
- defer func() {
- check.indent--
- check.trace(e.Pos(), "=> %s", T)
- }()
+func (check *Checker) definedType(e ast.Expr, def *Named) Type {
+ typ := check.typInternal(e, def)
+ assert(isTyped(typ))
+ if isGeneric(typ) {
+ check.errorf(e, _Todo, "cannot use generic type %s without instantiation", typ)
+ typ = Typ[Invalid]
}
+ check.recordTypeAndValue(e, typexpr, typ, nil)
+ return typ
+}
- T = check.typInternal(e, def)
- assert(isTyped(T))
- check.recordTypeAndValue(e, typexpr, T, nil)
+// genericType is like typ but the type must be an (uninstantiated) generic type.
+func (check *Checker) genericType(e ast.Expr, reportErr bool) Type {
+ typ := check.typInternal(e, nil)
+ assert(isTyped(typ))
+ if typ != Typ[Invalid] && !isGeneric(typ) {
+ if reportErr {
+ check.errorf(e, _Todo, "%s is not a generic type", typ)
+ }
+ typ = Typ[Invalid]
+ }
+ // TODO(gri) what is the correct call below?
+ check.recordTypeAndValue(e, typexpr, typ, nil)
+ return typ
+}
- return
+// isubst returns an x with identifiers substituted per the substitution map smap.
+// isubst only handles the case of (valid) method receiver type expressions correctly.
+func isubst(x ast.Expr, smap map[*ast.Ident]*ast.Ident) ast.Expr {
+ switch n := x.(type) {
+ case *ast.Ident:
+ if alt := smap[n]; alt != nil {
+ return alt
+ }
+ case *ast.StarExpr:
+ X := isubst(n.X, smap)
+ if X != n.X {
+ new := *n
+ new.X = X
+ return &new
+ }
+ case *ast.CallExpr:
+ var args []ast.Expr
+ for i, arg := range n.Args {
+ new := isubst(arg, smap)
+ if new != arg {
+ if args == nil {
+ args = make([]ast.Expr, len(n.Args))
+ copy(args, n.Args)
+ }
+ args[i] = new
+ }
+ }
+ if args != nil {
+ new := *n
+ new.Args = args
+ return &new
+ }
+ case *ast.ParenExpr:
+ return isubst(n.X, smap) // no need to keep parentheses
+ default:
+ // Other receiver type expressions are invalid.
+ // It's fine to ignore those here as they will
+ // be checked elsewhere.
+ }
+ return x
}
// funcType type-checks a function or method type.
func (check *Checker) funcType(sig *Signature, recvPar *ast.FieldList, ftyp *ast.FuncType) {
- scope := NewScope(check.scope, token.NoPos, token.NoPos, "function")
- scope.isFunc = true
- check.recordScope(ftyp, scope)
+ check.openScope(ftyp, "function")
+ check.scope.isFunc = true
+ check.recordScope(ftyp, check.scope)
+ sig.scope = check.scope
+ defer check.closeScope()
+
+ var recvTyp ast.Expr // rewritten receiver type; valid if != nil
+ if recvPar != nil && len(recvPar.List) > 0 {
+ // collect generic receiver type parameters, if any
+ // - a receiver type parameter is like any other type parameter, except that it is declared implicitly
+ // - the receiver specification acts as local declaration for its type parameters, which may be blank
+ _, rname, rparams := check.unpackRecv(recvPar.List[0].Type, true)
+ if len(rparams) > 0 {
+ // Blank identifiers don't get declared and regular type-checking of the instantiated
+ // parameterized receiver type expression fails in Checker.collectParams of receiver.
+ // Identify blank type parameters and substitute each with a unique new identifier named
+ // "n_" (where n is the parameter index) and which cannot conflict with any user-defined
+ // name.
+ var smap map[*ast.Ident]*ast.Ident // substitution map from "_" to "n_" identifiers
+ for i, p := range rparams {
+ if p.Name == "_" {
+ new := *p
+ new.Name = fmt.Sprintf("%d_", i)
+ rparams[i] = &new // use n_ identifier instead of _ so it can be looked up
+ if smap == nil {
+ smap = make(map[*ast.Ident]*ast.Ident)
+ }
+ smap[p] = &new
+ }
+ }
+ if smap != nil {
+ // blank identifiers were found => use rewritten receiver type
+ recvTyp = isubst(recvPar.List[0].Type, smap)
+ }
+ sig.rparams = check.declareTypeParams(nil, rparams)
+ // determine receiver type to get its type parameters
+ // and the respective type parameter bounds
+ var recvTParams []*TypeName
+ if rname != nil {
+ // recv should be a Named type (otherwise an error is reported elsewhere)
+ // Also: Don't report an error via genericType since it will be reported
+ // again when we type-check the signature.
+ // TODO(gri) maybe the receiver should be marked as invalid instead?
+ if recv := asNamed(check.genericType(rname, false)); recv != nil {
+ recvTParams = recv.tparams
+ }
+ }
+ // provide type parameter bounds
+ // - only do this if we have the right number (otherwise an error is reported elsewhere)
+ if len(sig.rparams) == len(recvTParams) {
+ // We have a list of *TypeNames but we need a list of Types.
+ list := make([]Type, len(sig.rparams))
+ for i, t := range sig.rparams {
+ list[i] = t.typ
+ }
+ smap := makeSubstMap(recvTParams, list)
+ for i, tname := range sig.rparams {
+ bound := recvTParams[i].typ.(*TypeParam).bound
+ // bound is (possibly) parameterized in the context of the
+ // receiver type declaration. Substitute parameters for the
+ // current context.
+ // TODO(gri) should we assume now that bounds always exist?
+ // (no bound == empty interface)
+ if bound != nil {
+ bound = check.subst(tname.pos, bound, smap)
+ tname.typ.(*TypeParam).bound = bound
+ }
+ }
+ }
+ }
+ }
+
+ if ftyp.TParams != nil {
+ sig.tparams = check.collectTypeParams(ftyp.TParams)
+ // Always type-check method type parameters but complain that they are not allowed.
+ // (A separate check is needed when type-checking interface method signatures because
+ // they don't have a receiver specification.)
+ if recvPar != nil {
+ check.errorf(ftyp.TParams, _Todo, "methods cannot have type parameters")
+ }
+ }
- recvList, _ := check.collectParams(scope, recvPar, false)
- params, variadic := check.collectParams(scope, ftyp.Params, true)
- results, _ := check.collectParams(scope, ftyp.Results, false)
+ // Value (non-type) parameters' scope starts in the function body. Use a temporary scope for their
+ // declarations and then squash that scope into the parent scope (and report any redeclarations at
+ // that time).
+ scope := NewScope(check.scope, token.NoPos, token.NoPos, "function body (temp. scope)")
+ recvList, _ := check.collectParams(scope, recvPar, recvTyp, false) // use rewritten receiver type, if any
+ params, variadic := check.collectParams(scope, ftyp.Params, nil, true)
+ results, _ := check.collectParams(scope, ftyp.Results, nil, false)
+ scope.Squash(func(obj, alt Object) {
+ check.errorf(obj, _DuplicateDecl, "%s redeclared in this block", obj.Name())
+ check.reportAltDecl(alt)
+ })
if recvPar != nil {
// recv parameter list present (may be empty)
var recv *Var
switch len(recvList) {
case 0:
- check.error(recvPar, _BadRecv, "method is missing receiver")
+ // error reported by resolver
recv = NewParam(0, nil, "", Typ[Invalid]) // ignore recv below
default:
// more than one receiver
case 1:
recv = recvList[0]
}
+
+ // TODO(gri) We should delay rtyp expansion to when we actually need the
+ // receiver; thus all checks here should be delayed to later.
+ rtyp, _ := deref(recv.typ)
+ rtyp = expand(rtyp)
+
// spec: "The receiver type must be of the form T or *T where T is a type name."
// (ignore invalid types - error was reported before)
- if t, _ := deref(recv.typ); t != Typ[Invalid] {
+ if t := rtyp; t != Typ[Invalid] {
var err string
- if T, _ := t.(*Named); T != nil {
+ if T := asNamed(t); T != nil {
// spec: "The type denoted by T is called the receiver base type; it must not
// be a pointer or interface type and it must be declared in the same package
// as the method."
if T.obj.pkg != check.pkg {
err = "type not defined in this package"
} else {
- // TODO(gri) This is not correct if the underlying type is unknown yet.
- switch u := T.underlying.(type) {
+ switch u := optype(T).(type) {
case *Basic:
// unsafe.Pointer is treated like a regular pointer
if u.kind == UnsafePointer {
sig.recv = recv
}
- sig.scope = scope
sig.params = NewTuple(params...)
sig.results = NewTuple(results...)
sig.variadic = variadic
}
+// goTypeName returns the Go type name for typ and
+// removes any occurences of "types." from that name.
+func goTypeName(typ Type) string {
+ return strings.ReplaceAll(fmt.Sprintf("%T", typ), "types.", "")
+}
+
// typInternal drives type checking of types.
-// Must only be called by definedType.
+// Must only be called by definedType or genericType.
//
-func (check *Checker) typInternal(e ast.Expr, def *Named) Type {
- switch e := e.(type) {
+func (check *Checker) typInternal(e0 ast.Expr, def *Named) (T Type) {
+ if trace {
+ check.trace(e0.Pos(), "type %s", e0)
+ check.indent++
+ defer func() {
+ check.indent--
+ var under Type
+ if T != nil {
+ // Calling under() here may lead to endless instantiations.
+ // Test case: type T[P any] *T[P]
+ // TODO(gri) investigate if that's a bug or to be expected
+ // (see also analogous comment in Checker.instantiate).
+ under = T.Underlying()
+ }
+ if T == under {
+ check.trace(e0.Pos(), "=> %s // %s", T, goTypeName(T))
+ } else {
+ check.trace(e0.Pos(), "=> %s (under = %s) // %s", T, under, goTypeName(T))
+ }
+ }()
+ }
+
+ switch e := e0.(type) {
case *ast.BadExpr:
// ignore - error reported before
check.errorf(&x, _NotAType, "%s is not a type", &x)
}
+ case *ast.IndexExpr:
+ return check.instantiatedType(e.X, []ast.Expr{e.Index}, def)
+
+ case *ast.CallExpr:
+ if e.Brackets {
+ return check.instantiatedType(e.Fun, e.Args, def)
+ } else {
+ check.errorf(e0, _NotAType, "%s is not a type", e0)
+ }
+
case *ast.ParenExpr:
+ // Generic types must be instantiated before they can be used in any form.
+ // Consequently, generic types cannot be parenthesized.
return check.definedType(e.X, def)
case *ast.ArrayType:
typ := new(Array)
def.setUnderlying(typ)
typ.len = check.arrayLength(e.Len)
- typ.elem = check.typ(e.Elt)
- return typ
-
- } else {
- typ := new(Slice)
- def.setUnderlying(typ)
- typ.elem = check.typ(e.Elt)
+ typ.elem = check.varType(e.Elt)
return typ
}
+ typ := new(Slice)
+ def.setUnderlying(typ)
+ typ.elem = check.varType(e.Elt)
+ return typ
+
+ case *ast.Ellipsis:
+ // dots are handled explicitly where they are legal
+ // (array composite literals and parameter lists)
+ check.error(e, _InvalidDotDotDot, "invalid use of '...'")
+ check.use(e.Elt)
+
case *ast.StructType:
typ := new(Struct)
def.setUnderlying(typ)
case *ast.StarExpr:
typ := new(Pointer)
def.setUnderlying(typ)
- typ.base = check.typ(e.X)
+ typ.base = check.varType(e.X)
return typ
case *ast.FuncType:
case *ast.InterfaceType:
typ := new(Interface)
def.setUnderlying(typ)
+ if def != nil {
+ typ.obj = def.obj
+ }
check.interfaceType(typ, e, def)
return typ
typ := new(Map)
def.setUnderlying(typ)
- typ.key = check.typ(e.Key)
- typ.elem = check.typ(e.Value)
+ typ.key = check.varType(e.Key)
+ typ.elem = check.varType(e.Value)
// spec: "The comparison operators == and != must be fully defined
// for operands of the key type; thus the key type must not be a
// it is safe to continue in any case (was issue 6667).
check.atEnd(func() {
if !Comparable(typ.key) {
- check.errorf(e.Key, _IncomparableMapKey, "incomparable map key type %s", typ.key)
+ var why string
+ if asTypeParam(typ.key) != nil {
+ why = " (missing comparable constraint)"
+ }
+ check.errorf(e.Key, _IncomparableMapKey, "incomparable map key type %s%s", typ.key, why)
}
})
}
typ.dir = dir
- typ.elem = check.typ(e.Value)
+ typ.elem = check.varType(e.Value)
return typ
default:
- check.errorf(e, _NotAType, "%s is not a type", e)
+ check.errorf(e0, _NotAType, "%s is not a type", e0)
}
typ := Typ[Invalid]
}
// typeOrNil type-checks the type expression (or nil value) e
-// and returns the typ of e, or nil.
-// If e is neither a type nor nil, typOrNil returns Typ[Invalid].
-//
-func (check *Checker) typOrNil(e ast.Expr) Type {
+// and returns the type of e, or nil. If e is a type, it must
+// not be an (uninstantiated) generic type.
+// If e is neither a type nor nil, typeOrNil returns Typ[Invalid].
+// TODO(gri) should we also disallow non-var types?
+func (check *Checker) typeOrNil(e ast.Expr) Type {
var x operand
check.rawExpr(&x, e, nil)
switch x.mode {
case novalue:
check.errorf(&x, _NotAType, "%s used as type", &x)
case typexpr:
+ check.instantiatedOperand(&x)
return x.typ
case value:
if x.isNil() {
return Typ[Invalid]
}
+func (check *Checker) instantiatedType(x ast.Expr, targs []ast.Expr, def *Named) Type {
+ b := check.genericType(x, true) // TODO(gri) what about cycles?
+ if b == Typ[Invalid] {
+ return b // error already reported
+ }
+ base := asNamed(b)
+ if base == nil {
+ unreachable() // should have been caught by genericType
+ }
+
+ // create a new type instance rather than instantiate the type
+ // TODO(gri) should do argument number check here rather than
+ // when instantiating the type?
+ typ := new(instance)
+ def.setUnderlying(typ)
+
+ typ.check = check
+ typ.pos = x.Pos()
+ typ.base = base
+
+ // evaluate arguments (always)
+ typ.targs = check.typeList(targs)
+ if typ.targs == nil {
+ def.setUnderlying(Typ[Invalid]) // avoid later errors due to lazy instantiation
+ return Typ[Invalid]
+ }
+
+ // determine argument positions (for error reporting)
+ typ.poslist = make([]token.Pos, len(targs))
+ for i, arg := range targs {
+ typ.poslist[i] = arg.Pos()
+ }
+
+ // make sure we check instantiation works at least once
+ // and that the resulting type is valid
+ check.atEnd(func() {
+ t := typ.expand()
+ check.validType(t, nil)
+ })
+
+ return typ
+}
+
// arrayLength type-checks the array length expression e
// and returns the constant length >= 0, or a value < 0
// to indicate an error (and thus an unknown length).
return -1
}
-func (check *Checker) collectParams(scope *Scope, list *ast.FieldList, variadicOk bool) (params []*Var, variadic bool) {
+// typeList provides the list of types corresponding to the incoming expression list.
+// If an error occured, the result is nil, but all list elements were type-checked.
+func (check *Checker) typeList(list []ast.Expr) []Type {
+ res := make([]Type, len(list)) // res != nil even if len(list) == 0
+ for i, x := range list {
+ t := check.varType(x)
+ if t == Typ[Invalid] {
+ res = nil
+ }
+ if res != nil {
+ res[i] = t
+ }
+ }
+ return res
+}
+
+// collectParams declares the parameters of list in scope and returns the corresponding
+// variable list. If type0 != nil, it is used instead of the the first type in list.
+func (check *Checker) collectParams(scope *Scope, list *ast.FieldList, type0 ast.Expr, variadicOk bool) (params []*Var, variadic bool) {
if list == nil {
return
}
var named, anonymous bool
for i, field := range list.List {
ftype := field.Type
+ if i == 0 && type0 != nil {
+ ftype = type0
+ }
if t, _ := ftype.(*ast.Ellipsis); t != nil {
ftype = t.Elt
if variadicOk && i == len(list.List)-1 && len(field.Names) <= 1 {
// ignore ... and continue
}
}
- typ := check.typ(ftype)
+ typ := check.varType(ftype)
// The parser ensures that f.Tag is nil and we don't
// care if a constructed AST contains a non-nil tag.
if len(field.Names) > 0 {
}
func (check *Checker) interfaceType(ityp *Interface, iface *ast.InterfaceType, def *Named) {
+ var tlist *ast.Ident // "type" name of first entry in a type list declaration
+ var types []ast.Expr
for _, f := range iface.Methods.List {
if len(f.Names) > 0 {
- // We have a method with name f.Names[0].
+ // We have a method with name f.Names[0], or a type
+ // of a type list (name.Name == "type").
// (The parser ensures that there's only one method
// and we don't care if a constructed AST has more.)
name := f.Names[0]
continue // ignore
}
+ if name.Name == "type" {
+ // Always collect all type list entries, even from
+ // different type lists, under the assumption that
+ // the author intended to include all types.
+ types = append(types, f.Type)
+ if tlist != nil && tlist != name {
+ check.errorf(name, _Todo, "cannot have multiple type lists in an interface")
+ }
+ tlist = name
+ continue
+ }
+
typ := check.typ(f.Type)
sig, _ := typ.(*Signature)
if sig == nil {
continue // ignore
}
+ // Always type-check method type parameters but complain if they are not enabled.
+ // (This extra check is needed here because interface method signatures don't have
+ // a receiver specification.)
+ if sig.tparams != nil {
+ check.errorf(f.Type.(*ast.FuncType).TParams, _Todo, "methods cannot have type parameters")
+ }
+
// use named receiver type if available (for better error messages)
var recvTyp Type = ityp
if def != nil {
check.recordDef(name, m)
ityp.methods = append(ityp.methods, m)
} else {
- // We have an embedded interface and f.Type is its
- // (possibly qualified) embedded type name. Collect
- // it if it's a valid interface.
- typ := check.typ(f.Type)
-
- utyp := check.underlying(typ)
- if _, ok := utyp.(*Interface); !ok {
- if utyp != Typ[Invalid] {
- check.errorf(f.Type, _InvalidIfaceEmbed, "%s is not an interface", typ)
- }
- continue
- }
-
- ityp.embeddeds = append(ityp.embeddeds, typ)
+ // We have an embedded type. completeInterface will
+ // eventually verify that we have an interface.
+ ityp.embeddeds = append(ityp.embeddeds, check.typ(f.Type))
check.posMap[ityp] = append(check.posMap[ityp], f.Type.Pos())
}
}
- if len(ityp.methods) == 0 && len(ityp.embeddeds) == 0 {
+ // type constraints
+ ityp.types = NewSum(check.collectTypeConstraints(iface.Pos(), types))
+
+ if len(ityp.methods) == 0 && ityp.types == nil && len(ityp.embeddeds) == 0 {
// empty interface
ityp.allMethods = markComplete
return
}
// sort for API stability
- sort.Sort(byUniqueMethodName(ityp.methods))
- sort.Stable(byUniqueTypeName(ityp.embeddeds))
+ sortMethods(ityp.methods)
+ sortTypes(ityp.embeddeds)
- check.later(func() { check.completeInterface(ityp) })
+ check.later(func() { check.completeInterface(iface.Pos(), ityp) })
}
-func (check *Checker) completeInterface(ityp *Interface) {
+func (check *Checker) completeInterface(pos token.Pos, ityp *Interface) {
if ityp.allMethods != nil {
return
}
}
if trace {
- check.trace(token.NoPos, "complete %s", ityp)
+ // Types don't generally have position information.
+ // If we don't have a valid pos provided, try to use
+ // one close enough.
+ if !pos.IsValid() && len(ityp.methods) > 0 {
+ pos = ityp.methods[0].pos
+ }
+
+ check.trace(pos, "complete %s", ityp)
check.indent++
defer func() {
check.indent--
- check.trace(token.NoPos, "=> %s", ityp)
+ check.trace(pos, "=> %s (methods = %v, types = %v)", ityp, ityp.allMethods, ityp.allTypes)
}()
}
check.errorf(atPos(pos), _DuplicateDecl, "duplicate method %s", m.name)
check.errorf(atPos(mpos[other.(*Func)]), _DuplicateDecl, "\tother declaration of %s", m.name) // secondary error, \t indented
default:
- // check method signatures after all types are computed (issue #33656)
+ // We have a duplicate method name in an embedded (not explicitly declared) method.
+ // Check method signatures after all types are computed (issue #33656).
+ // If we're pre-go1.14 (overlapping embeddings are not permitted), report that
+ // error here as well (even though we could do it eagerly) because it's the same
+ // error message.
check.atEnd(func() {
- if !check.identical(m.typ, other.Type()) {
+ if !check.allowVersion(m.pkg, 1, 14) || !check.identical(m.typ, other.Type()) {
check.errorf(atPos(pos), _DuplicateDecl, "duplicate method %s", m.name)
check.errorf(atPos(mpos[other.(*Func)]), _DuplicateDecl, "\tother declaration of %s", m.name) // secondary error, \t indented
}
addMethod(m.pos, m, true)
}
+ // collect types
+ allTypes := ityp.types
+
posList := check.posMap[ityp]
for i, typ := range ityp.embeddeds {
pos := posList[i] // embedding position
- typ, ok := check.underlying(typ).(*Interface)
- if !ok {
- // An error was reported when collecting the embedded types.
- // Ignore it.
+ utyp := under(typ)
+ etyp := asInterface(utyp)
+ if etyp == nil {
+ if utyp != Typ[Invalid] {
+ var format string
+ if _, ok := utyp.(*TypeParam); ok {
+ format = "%s is a type parameter, not an interface"
+ } else {
+ format = "%s is not an interface"
+ }
+ // TODO: correct error code.
+ check.errorf(atPos(pos), _InvalidIfaceEmbed, format, typ)
+ }
continue
}
- check.completeInterface(typ)
- for _, m := range typ.allMethods {
+ check.completeInterface(pos, etyp)
+ for _, m := range etyp.allMethods {
addMethod(pos, m, false) // use embedding position pos rather than m.pos
}
+ allTypes = intersect(allTypes, etyp.allTypes)
}
if methods != nil {
sort.Sort(byUniqueMethodName(methods))
ityp.allMethods = methods
}
+ ityp.allTypes = allTypes
+}
+
+// intersect computes the intersection of the types x and y.
+// Note: A incomming nil type stands for the top type. A top
+// type result is returned as nil.
+func intersect(x, y Type) (r Type) {
+ defer func() {
+ if r == theTop {
+ r = nil
+ }
+ }()
+
+ switch {
+ case x == theBottom || y == theBottom:
+ return theBottom
+ case x == nil || x == theTop:
+ return y
+ case y == nil || x == theTop:
+ return x
+ }
+
+ xtypes := unpackType(x)
+ ytypes := unpackType(y)
+ // Compute the list rtypes which includes only
+ // types that are in both xtypes and ytypes.
+ // Quadratic algorithm, but good enough for now.
+ // TODO(gri) fix this
+ var rtypes []Type
+ for _, x := range xtypes {
+ if includes(ytypes, x) {
+ rtypes = append(rtypes, x)
+ }
+ }
+
+ if rtypes == nil {
+ return theBottom
+ }
+ return NewSum(rtypes)
}
+ func sortTypes(list []Type) {
+ sort.Stable(byUniqueTypeName(list))
+ }
+
// byUniqueTypeName named type lists can be sorted by their unique type names.
type byUniqueTypeName []Type
func (a byUniqueTypeName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func sortName(t Type) string {
- if named, _ := t.(*Named); named != nil {
+ if named := asNamed(t); named != nil {
return named.obj.Id()
}
return ""
}
+ func sortMethods(list []*Func) {
+ sort.Sort(byUniqueMethodName(list))
+ }
+
+ func assertSortedMethods(list []*Func) {
+ if !debug {
+ panic("internal error: assertSortedMethods called outside debug mode")
+ }
+ if !sort.IsSorted(byUniqueMethodName(list)) {
+ panic("internal error: methods not sorted")
+ }
+ }
+
// byUniqueMethodName method lists can be sorted by their unique method names.
type byUniqueMethodName []*Func
}
for _, f := range list.List {
- typ = check.typ(f.Type)
+ typ = check.varType(f.Type)
tag = check.tag(f.Tag)
if len(f.Names) > 0 {
// named fields
}
} else {
// embedded field
- // spec: "An embedded type must be specified as a type name T or as a pointer
- // to a non-interface type name *T, and T itself may not be a pointer type."
+ // spec: "An embedded type must be specified as a type name T or as a
+ // pointer to a non-interface type name *T, and T itself may not be a
+ // pointer type."
pos := f.Type.Pos()
name := embeddedFieldIdent(f.Type)
if name == nil {
- check.invalidAST(f.Type, "embedded field type %s has no name", f.Type)
+ // TODO(rFindley): using invalidAST here causes test failures (all
+ // errors should have codes). Clean this up.
+ check.errorf(f.Type, _Todo, "invalid AST: embedded field type %s has no name", f.Type)
name = ast.NewIdent("_")
name.NamePos = pos
addInvalid(name, pos)
continue
}
- t, isPtr := deref(typ)
+ add(name, true, pos)
+
// Because we have a name, typ must be of the form T or *T, where T is the name
// of a (named or alias) type, and t (= deref(typ)) must be the type of T.
- switch t := t.Underlying().(type) {
- case *Basic:
- if t == Typ[Invalid] {
- // error was reported before
- addInvalid(name, pos)
- continue
- }
-
- // unsafe.Pointer is treated like a regular pointer
- if t.kind == UnsafePointer {
- check.errorf(f.Type, _InvalidPtrEmbed, "embedded field type cannot be unsafe.Pointer")
- addInvalid(name, pos)
- continue
- }
+ // We must delay this check to the end because we don't want to instantiate
+ // (via under(t)) a possibly incomplete type.
- case *Pointer:
- check.errorf(f.Type, _InvalidPtrEmbed, "embedded field type cannot be a pointer")
- addInvalid(name, pos)
- continue
+ // for use in the closure below
+ embeddedTyp := typ
+ embeddedPos := f.Type
- case *Interface:
- if isPtr {
- check.errorf(f.Type, _InvalidPtrEmbed, "embedded field type cannot be a pointer to an interface")
- addInvalid(name, pos)
- continue
+ check.atEnd(func() {
+ t, isPtr := deref(embeddedTyp)
+ switch t := optype(t).(type) {
+ case *Basic:
+ if t == Typ[Invalid] {
+ // error was reported before
+ return
+ }
+ // unsafe.Pointer is treated like a regular pointer
+ if t.kind == UnsafePointer {
+ check.errorf(embeddedPos, _InvalidPtrEmbed, "embedded field type cannot be unsafe.Pointer")
+ }
+ case *Pointer:
+ check.errorf(embeddedPos, _InvalidPtrEmbed, "embedded field type cannot be a pointer")
+ case *Interface:
+ if isPtr {
+ check.errorf(embeddedPos, _InvalidPtrEmbed, "embedded field type cannot be a pointer to an interface")
+ }
}
- }
- add(name, true, pos)
+ })
}
}
}
case *ast.SelectorExpr:
return e.Sel
+ case *ast.IndexExpr:
+ return embeddedFieldIdent(e.X)
+ case *ast.CallExpr:
+ if e.Brackets {
+ return embeddedFieldIdent(e.Fun)
+ }
}
return nil // invalid embedded field
}
+
+func (check *Checker) collectTypeConstraints(pos token.Pos, types []ast.Expr) []Type {
+ list := make([]Type, 0, len(types)) // assume all types are correct
+ for _, texpr := range types {
+ if texpr == nil {
+ check.invalidAST(atPos(pos), "missing type constraint")
+ continue
+ }
+ list = append(list, check.varType(texpr))
+ }
+
+ // Ensure that each type is only present once in the type list. Types may be
+ // interfaces, which may not be complete yet. It's ok to do this check at the
+ // end because it's not a requirement for correctness of the code.
+ // Note: This is a quadratic algorithm, but type lists tend to be short.
+ check.atEnd(func() {
+ for i, t := range list {
+ if t := asInterface(t); t != nil {
+ check.completeInterface(types[i].Pos(), t)
+ }
+ if includes(list[:i], t) {
+ check.softErrorf(types[i], _Todo, "duplicate type %s in type list", t)
+ }
+ }
+ })
+
+ return list
+}
+
+// includes reports whether typ is in list.
+func includes(list []Type, typ Type) bool {
+ for _, e := range list {
+ if Identical(typ, e) {
+ return true
+ }
+ }
+ return false
+}
// dirs are the directories to look for *.go files in.
// TODO(bradfitz): just use all directories?
- dirs = []string{".", "ken", "chan", "interface", "syntax", "dwarf", "fixedbugs", "codegen", "runtime", "abi"}
+ dirs = []string{".", "ken", "chan", "interface", "syntax", "dwarf", "fixedbugs", "codegen", "runtime", "abi", "typeparam"}
// ratec controls the max number of tests running at a time.
ratec chan bool
t.updateErrors(string(out), long)
}
t.err = t.errorCheck(string(out), wantAuto, long, t.gofile)
- return
+ if t.err != nil {
+ return // don't hide error if run below succeeds
+ }
+
+ // The following is temporary scaffolding to get types2 typechecker
+ // up and running against the existing test cases. The explicitly
+ // listed files don't pass yet, usually because the error messages
+ // are slightly different (this list is not complete). Any errorcheck
+ // tests that require output from analysis phases past intial type-
+ // checking are also excluded since these phases are not running yet.
+ // We can get rid of this code once types2 is fully plugged in.
+
+ // For now we're done when we can't handle the file or some of the flags.
+ // The first goal is to eliminate the excluded list; the second goal is to
+ // eliminate the flag list.
+
+ // Excluded files.
+ filename := strings.Replace(t.goFileName(), "\\", "/", -1) // goFileName() uses \ on Windows
+ if excluded[filename] {
+ if *verbose {
+ fmt.Printf("excl\t%s\n", filename)
+ }
+ return // cannot handle file yet
+ }
+
+ // Excluded flags.
+ for _, flag := range flags {
+ for _, pattern := range []string{
+ "-m",
+ } {
+ if strings.Contains(flag, pattern) {
+ if *verbose {
+ fmt.Printf("excl\t%s\t%s\n", filename, flags)
+ }
+ return // cannot handle flag
+ }
+ }
+ }
+
+ // Run errorcheck again with -G option (new typechecker).
+ cmdline = []string{goTool(), "tool", "compile", "-G=3", "-C", "-e", "-o", "a.o"}
+ // No need to add -dynlink even if linkshared if we're just checking for errors...
+ cmdline = append(cmdline, flags...)
+ cmdline = append(cmdline, long)
+ out, err = runcmd(cmdline...)
+ if wantError {
+ if err == nil {
+ t.err = fmt.Errorf("compilation succeeded unexpectedly\n%s", out)
+ return
+ }
+ } else {
+ if err != nil {
+ t.err = err
+ return
+ }
+ }
+ if *updateErrors {
+ t.updateErrors(string(out), long)
+ }
+ t.err = t.errorCheck(string(out), wantAuto, long, t.gofile)
case "compile":
// Compile Go file.
if *linkshared {
cmd = append(cmd, "-linkshared")
}
+ cmd = append(cmd, flags...)
cmd = append(cmd, ".")
out, err := runcmd(cmd...)
if err != nil {
return err
})
}
+
+// List of files that the compiler cannot errorcheck with the new typechecker (compiler -G option).
+// Temporary scaffolding until we pass all the tests at which point this map can be removed.
+var excluded = map[string]bool{
+ "complit1.go": true, // types2 reports extra errors
+ "const2.go": true, // types2 not run after syntax errors
+ "ddd1.go": true, // issue #42987
+ "directive.go": true, // misplaced compiler directive checks
+ "float_lit3.go": true, // types2 reports extra errors
+ "import1.go": true, // types2 reports extra errors
+ "import5.go": true, // issue #42988
+ "import6.go": true, // issue #43109
+ "initializerr.go": true, // types2 reports extra errors
+ "linkname2.go": true, // error reported by noder (not running for types2 errorcheck test)
+ "notinheap.go": true, // types2 doesn't report errors about conversions that are invalid due to //go:notinheap
+ "shift1.go": true, // issue #42989
+ "typecheck.go": true, // invalid function is not causing errors when called
+ "writebarrier.go": true, // correct diagnostics, but different lines (probably irgen's fault)
+
+ "fixedbugs/bug176.go": true, // types2 reports all errors (pref: types2)
+ "fixedbugs/bug193.go": true, // types2 bug: shift error not reported (fixed in go/types)
+ "fixedbugs/bug195.go": true, // types2 reports slightly different (but correct) bugs
+ "fixedbugs/bug228.go": true, // types2 not run after syntax errors
+ "fixedbugs/bug231.go": true, // types2 bug? (same error reported twice)
+ "fixedbugs/bug255.go": true, // types2 reports extra errors
+ "fixedbugs/bug351.go": true, // types2 reports extra errors
+ "fixedbugs/bug374.go": true, // types2 reports extra errors
+ "fixedbugs/bug385_32.go": true, // types2 doesn't produce missing error "type .* too large" (32-bit specific)
+ "fixedbugs/bug388.go": true, // types2 not run due to syntax errors
+ "fixedbugs/bug412.go": true, // types2 produces a follow-on error
+
+ "fixedbugs/issue11590.go": true, // types2 doesn't report a follow-on error (pref: types2)
+ "fixedbugs/issue11610.go": true, // types2 not run after syntax errors
+ "fixedbugs/issue11614.go": true, // types2 reports an extra error
+ "fixedbugs/issue13415.go": true, // declared but not used conflict
+ "fixedbugs/issue14520.go": true, // missing import path error by types2
+ "fixedbugs/issue16428.go": true, // types2 reports two instead of one error
+ "fixedbugs/issue17038.go": true, // types2 doesn't report a follow-on error (pref: types2)
+ "fixedbugs/issue17645.go": true, // multiple errors on same line
+ "fixedbugs/issue18331.go": true, // missing error about misuse of //go:noescape (irgen needs code from noder)
+ "fixedbugs/issue18393.go": true, // types2 not run after syntax errors
+ "fixedbugs/issue19012.go": true, // multiple errors on same line
+ "fixedbugs/issue20233.go": true, // types2 reports two instead of one error (pref: compiler)
+ "fixedbugs/issue20245.go": true, // types2 reports two instead of one error (pref: compiler)
+ "fixedbugs/issue20250.go": true, // correct diagnostics, but different lines (probably irgen's fault)
+ "fixedbugs/issue21979.go": true, // types2 doesn't report a follow-on error (pref: types2)
+ "fixedbugs/issue23732.go": true, // types2 reports different (but ok) line numbers
+ "fixedbugs/issue25958.go": true, // types2 doesn't report a follow-on error (pref: types2)
+ "fixedbugs/issue28079b.go": true, // types2 reports follow-on errors
+ "fixedbugs/issue28268.go": true, // types2 reports follow-on errors
+ "fixedbugs/issue33460.go": true, // types2 reports alternative positions in separate error
+ "fixedbugs/issue41575.go": true, // types2 reports alternative positions in separate error
+ "fixedbugs/issue42058a.go": true, // types2 doesn't report "channel element type too large"
+ "fixedbugs/issue42058b.go": true, // types2 doesn't report "channel element type too large"
+ "fixedbugs/issue4232.go": true, // types2 reports (correct) extra errors
+ "fixedbugs/issue4452.go": true, // types2 reports (correct) extra errors
+ "fixedbugs/issue5609.go": true, // types2 needs a better error message
+ "fixedbugs/issue6889.go": true, // types2 can handle this without constant overflow
+ "fixedbugs/issue7525.go": true, // types2 reports init cycle error on different line - ok otherwise
+ "fixedbugs/issue7525b.go": true, // types2 reports init cycle error on different line - ok otherwise
+ "fixedbugs/issue7525c.go": true, // types2 reports init cycle error on different line - ok otherwise
+ "fixedbugs/issue7525d.go": true, // types2 reports init cycle error on different line - ok otherwise
+ "fixedbugs/issue7525e.go": true, // types2 reports init cycle error on different line - ok otherwise
+}