diff --git a/go/types/expr.go b/go/types/expr.go index 72e79c9e..6ef878fe 100644 --- a/go/types/expr.go +++ b/go/types/expr.go @@ -1152,8 +1152,8 @@ func (check *checker) expr0(x *operand, e ast.Expr, hint Type) { if x.mode == invalid { goto Error } - var T *Interface - if T, _ = x.typ.Underlying().(*Interface); T == nil { + xtyp, _ := x.typ.Underlying().(*Interface) + if xtyp == nil { check.invalidOp(x.pos(), "%s is not an interface", x) goto Error } @@ -1162,27 +1162,13 @@ func (check *checker) expr0(x *operand, e ast.Expr, hint Type) { check.invalidAST(e.Pos(), "use of .(type) outside type switch") goto Error } - typ := check.typ(e.Type, nil, false) - if typ == Typ[Invalid] { + T := check.typ(e.Type, nil, false) + if T == Typ[Invalid] { goto Error } - if method, wrongType := MissingMethod(typ, T); method != nil { - var msg string - if wrongType { - msg = "%s cannot have dynamic type %s (wrong type for method %s)" - } else if _, ok := typ.Underlying().(*Interface); !ok { - // Concrete types must have all methods of T. Interfaces - // may not have all methods of T statically, but may have - // them dynamically; don't report an error in that case. - msg = "%s cannot have dynamic type %s (missing method %s)" - } - if msg != "" { - check.errorf(e.Type.Pos(), msg, x, typ, method.name) - } - // ok to continue - } + check.typeAssertion(x.pos(), x, xtyp, T) x.mode = valueok - x.typ = typ + x.typ = T case *ast.CallExpr: check.call(x, e) @@ -1251,6 +1237,21 @@ Error: x.expr = e } +// typeAssertion checks that x.(T) is legal; xtyp must be the type of x. +func (check *checker) typeAssertion(pos token.Pos, x *operand, xtyp *Interface, T Type) { + method, wrongType := MissingMethod(T, xtyp, false) + if method == nil { + return + } + var msg string + if wrongType { + msg = "%s cannot have dynamic type %s (wrong type for method %s)" + } else { + msg = "%s cannot have dynamic type %s (missing method %s)" + } + check.errorf(pos, msg, x, T, method.name) +} + // expr typechecks expression e and initializes x with the expression value. // If an error occurred, x.mode is set to invalid. // diff --git a/go/types/lookup.go b/go/types/lookup.go index dc2eaf14..d7ebd38b 100644 --- a/go/types/lookup.go +++ b/go/types/lookup.go @@ -220,23 +220,33 @@ func consolidateMultiples(list []embeddedType) []embeddedType { return list[:n] } -// MissingMethod returns (nil, false) if typ implements T, otherwise -// it returns the first missing method required by T and whether it -// is missing or simply has the wrong type. +// MissingMethod returns (nil, false) if typ implements T, otherwise it +// returns a missing method required by T and whether it is missing or +// just has the wrong type. // -func MissingMethod(typ Type, T *Interface) (method *Func, wrongType bool) { +// For typ of interface type, if static is set, implements checks that all +// methods of T are present in typ (e.g., for a conversion T(x) where x is +// of type typ). Otherwise, implements only checks that methods of T which +// are also present in typ have matching types (e.g., for a type assertion +// x.(T) where x is of interface type typ). +// +func MissingMethod(typ Type, T *Interface, static bool) (method *Func, wrongType bool) { // fast path for common case if T.NumMethods() == 0 { return } - // The dynamic type of a value stored in an interface may implement T, - // but only if all the shared interface methods have matching signatures. - // Note: This is stronger than the current spec. Should the spec require this? + // TODO(gri) Consider using methods sets here. Might be more efficient. + if ityp, _ := typ.Underlying().(*Interface); ityp != nil { for _, m := range T.methods { _, obj := lookupMethod(ityp.methods, m.pkg, m.name) - if obj != nil && !IsIdentical(obj.Type(), m.typ) { + switch { + case obj == nil: + if static { + return m, false + } + case !IsIdentical(obj.Type(), m.typ): return m, true } } diff --git a/go/types/operand.go b/go/types/operand.go index a7e82c62..c08a7bc7 100644 --- a/go/types/operand.go +++ b/go/types/operand.go @@ -143,7 +143,7 @@ func (x *operand) isAssignableTo(conf *Config, T Type) bool { // T is an interface type and x implements T // (Do this check first as it might succeed early.) if Ti, ok := Tu.(*Interface); ok { - if m, _ := MissingMethod(x.typ, Ti); m == nil { + if m, _ := MissingMethod(x.typ, Ti, true); m == nil { return true } } diff --git a/go/types/stmt.go b/go/types/stmt.go index 03a8950f..4d45b14c 100644 --- a/go/types/stmt.go +++ b/go/types/stmt.go @@ -393,8 +393,8 @@ func (check *checker) stmt(s ast.Stmt, fallthroughOk bool) { if x.mode == invalid { return } - var T *Interface - if T, _ = x.typ.Underlying().(*Interface); T == nil { + xtyp, _ := x.typ.Underlying().(*Interface) + if xtyp == nil { check.errorf(x.pos(), "%s is not an interface", &x) return } @@ -406,20 +406,11 @@ func (check *checker) stmt(s ast.Stmt, fallthroughOk bool) { continue // error reported before } // Check each type in this type switch case. - var typ Type + var T Type for _, expr := range clause.List { - typ = check.typOrNil(expr) - if typ != nil && typ != Typ[Invalid] { - if method, wrongType := MissingMethod(typ, T); method != nil { - var msg string - if wrongType { - msg = "%s cannot have dynamic type %s (wrong type for method %s)" - } else { - msg = "%s cannot have dynamic type %s (missing method %s)" - } - check.errorf(expr.Pos(), msg, &x, typ, method.name) - // ok to continue - } + T = check.typOrNil(expr) + if T != nil && T != Typ[Invalid] { + check.typeAssertion(expr.Pos(), &x, xtyp, T) } } check.openScope(clause) @@ -430,10 +421,10 @@ func (check *checker) stmt(s ast.Stmt, fallthroughOk bool) { // the implicit block in each clause. In clauses with a case listing // exactly one type, the variable has that type; otherwise, the variable // has the type of the expression in the TypeSwitchGuard." - if len(clause.List) != 1 || typ == nil { - typ = x.typ + if len(clause.List) != 1 || T == nil { + T = x.typ } - obj := NewVar(lhs.Pos(), check.pkg, lhs.Name, typ) + obj := NewVar(lhs.Pos(), check.pkg, lhs.Name, T) check.declareObj(check.topScope, nil, obj) check.recordImplicit(clause, obj) } diff --git a/go/types/testdata/conversions.src b/go/types/testdata/conversions.src index 7cf36f0d..f6fa1bae 100644 --- a/go/types/testdata/conversions.src +++ b/go/types/testdata/conversions.src @@ -66,16 +66,16 @@ func interface_conversions() { _ = I1 /* ERROR "cannot convert" */ (0) _ = I1(nil) _ = I1(i1) - _ = I1(e) + _ = I1 /* ERROR "cannot convert" */ (e) _ = I1(i2) _ = I2(nil) - _ = I2(i1) + _ = I2 /* ERROR "cannot convert" */ (i1) _ = I2(i2) _ = I2 /* ERROR "cannot convert" */ (i3) _ = I3(nil) - _ = I3(i1) + _ = I3 /* ERROR "cannot convert" */ (i1) _ = I3 /* ERROR "cannot convert" */ (i2) _ = I3(i3) diff --git a/go/types/testdata/expr3.src b/go/types/testdata/expr3.src index bec1ad16..596a6043 100644 --- a/go/types/testdata/expr3.src +++ b/go/types/testdata/expr3.src @@ -282,11 +282,11 @@ func type_asserts() { var t I _ = t /* ERROR "use of .* outside type switch" */ .(type) - _ = t.(T /* ERROR "missing method m" */ ) + _ = t /* ERROR "missing method m" */ .(T) _ = t.(*T) - _ = t.(T1 /* ERROR "missing method m" */ ) - _ = t.(T2 /* ERROR "wrong type for method m" */ ) - _ = t.(I2 /* ERROR "wrong type for method m" */ ) + _ = t /* ERROR "missing method m" */ .(T1) + _ = t /* ERROR "wrong type for method m" */ .(T2) + _ = t /* ERROR "wrong type for method m" */ .(I2) // e doesn't statically have an m, but may have one dynamically. _ = e.(I2) diff --git a/ssa/interp/ops.go b/ssa/interp/ops.go index a2b260db..95c0a68b 100644 --- a/ssa/interp/ops.go +++ b/ssa/interp/ops.go @@ -1328,7 +1328,7 @@ func conv(t_dst, t_src types.Type, x value) value { // On success it returns "", on failure, an error message. // func checkInterface(i *interpreter, itype *types.Interface, x iface) string { - if meth, wrongType := types.MissingMethod(x.t, itype); meth != nil { + if meth, wrongType := types.MissingMethod(x.t, itype, true); meth != nil { reason := "is missing" if wrongType { reason = "has wrong type"