M pkg/semantic/testdata/check/interface_satisfied.snow.err => pkg/semantic/testdata/check/interface_satisfied.snow.err +5 -3
@@ 1,3 1,5 @@
-testdata/interface_satisfied.snow:31:11: cannot assign type int to variable of type interface Empty
-testdata/interface_satisfied.snow:34:7: cannot assign type struct S2 to variable of type interface Foo
-testdata/interface_satisfied.snow:35:7: cannot assign type struct S3 to variable of type interface Foo
+testdata/interface_satisfied.snow:35:11: cannot assign type int to variable of type interface Empty
+testdata/interface_satisfied.snow:38:7: cannot assign type struct S2 to variable of type interface Foo
+testdata/interface_satisfied.snow:39:7: cannot assign type struct S3 to variable of type interface Foo
+testdata/interface_satisfied.snow:47:7: type struct S4 does not satisfy type interface Foo: missing [foo] (ref methods can only be used in a mutable context)
+testdata/interface_satisfied.snow:49:7: type struct S4 does not satisfy type interface Foo: missing [foo] (ref methods can only be used in a mutable context)
M pkg/semantic/testdata/check/interface_satisfied.snow.want => pkg/semantic/testdata/check/interface_satisfied.snow.want +27 -2
@@ 1,6 1,6 @@
-file testdata/interface_satisfied.snow [0, 1, 3, 2]
+file testdata/interface_satisfied.snow [0, 1, 4, 2]
fn main [let: () -> void]
- block [11]
+ block [16]
var: empty [var: interface Empty]
ident Empty [type: interface Empty]
var: f [var: interface Foo]
@@ 36,6 36,25 @@ file testdata/interface_satisfied.snow [0, 1, 3, 2]
ident f [var: interface Foo]
call [0] [value: struct S3]
ident S3 [type: struct S3]
+ var= vs4 [var: struct S4]
+ call [0] [value: struct S4]
+ ident S4 [type: struct S4]
+ let= ls4 [let: struct S4]
+ call [0] [value: struct S4]
+ ident S4 [type: struct S4]
+ assign
+ ident f [var: interface Foo]
+ implicit conv [value: interface Foo]
+ ident vs4 [var: struct S4]
+ assign
+ ident f [var: interface Foo]
+ implicit conv [value: interface Foo]
+ ident ls4 [let: struct S4]
+ assign
+ ident f [var: interface Foo]
+ implicit conv [value: interface Foo]
+ call [0] [value: struct S4]
+ ident S4 [type: struct S4]
struct S1 [0, 2, 0, 0] [type: struct S1]
fn foo [let: (int) -> int]
let: x [let: int]
@@ 61,6 80,12 @@ file testdata/interface_satisfied.snow [0, 1, 3, 2]
ident uint [type: uint]
ident int [type: int]
block [0]
+ struct S4 [0, 1, 0, 0] [type: struct S4]
+ ref fn foo [let: (int) -> int]
+ let: i [let: int]
+ ident int [type: int]
+ ident int [type: int]
+ block [0]
interface Empty [0] [type: interface Empty]
interface Foo [1] [type: interface Foo]
fn foo [let: (int) -> int]
M pkg/semantic/testdata/interface_satisfied.snow => pkg/semantic/testdata/interface_satisfied.snow +14 -0
@@ 19,6 19,10 @@ struct S3 {
fn foo(u: uint) -> int {}
}
+struct S4 {
+ ref fn foo(i: int) -> int {}
+}
+
fn main() {
var empty: Empty
var f: Foo
@@ 33,5 37,15 @@ fn main() {
f = S1() # all good, S1.foo exists and same types
f = S2() # nope
f = S3() # types don't match
+
+ var vs4 = S4()
+ let ls4 = S4()
+
+ # this works, vs4 is a var
+ f = vs4
+ # not this, ls4 is immutable
+ f = ls4
+ # not this either, rhs is a value
+ f = S4()
}
M pkg/semantic/testdata/scopes/interface_satisfied.snow.want => pkg/semantic/testdata/scopes/interface_satisfied.snow.want +11 -1
@@ 29,6 29,7 @@
. . S1
. . S2
. . S3
+. . S4
. . main
. . 4 *semantic.Interface {
. . }
@@ 69,11 70,20 @@
. . . . u
. . . }
. . }
-. . 15 *semantic.Fn {
+. . 15 *semantic.Struct {
+. . . foo
+. . . 16 *semantic.Fn {
+. . . . i
+. . . . self
+. . . }
+. . }
+. . 17 *semantic.Fn {
. . . empty
. . . f
. . . integer
+. . . ls4
. . . s1
+. . . vs4
. . }
. }
}
M pkg/semantic/testdata/static/interface_satisfied.snow.err => pkg/semantic/testdata/static/interface_satisfied.snow.err +5 -3
@@ 1,3 1,5 @@
-testdata/interface_satisfied.snow:31:11: cannot assign type int to variable of type interface Empty
-testdata/interface_satisfied.snow:34:7: cannot assign type struct S2 to variable of type interface Foo
-testdata/interface_satisfied.snow:35:7: cannot assign type struct S3 to variable of type interface Foo
+testdata/interface_satisfied.snow:35:11: cannot assign type int to variable of type interface Empty
+testdata/interface_satisfied.snow:38:7: cannot assign type struct S2 to variable of type interface Foo
+testdata/interface_satisfied.snow:39:7: cannot assign type struct S3 to variable of type interface Foo
+testdata/interface_satisfied.snow:47:7: type struct S4 does not satisfy type interface Foo: missing [foo] (ref methods can only be used in a mutable context)
+testdata/interface_satisfied.snow:49:7: type struct S4 does not satisfy type interface Foo: missing [foo] (ref methods can only be used in a mutable context)
M pkg/semantic/testdata/static/interface_satisfied.snow.want => pkg/semantic/testdata/static/interface_satisfied.snow.want +12 -7
@@ 1,7 1,12 @@
-testdata/interface_satisfied.snow:28:11: empty
-testdata/interface_satisfied.snow:29:11: f
-testdata/interface_satisfied.snow:30:11: s1
-testdata/interface_satisfied.snow:31:11: integer
-testdata/interface_satisfied.snow:33:7: S1
-testdata/interface_satisfied.snow:34:7: S2
-testdata/interface_satisfied.snow:35:7: S3
+testdata/interface_satisfied.snow:32:11: empty
+testdata/interface_satisfied.snow:33:11: f
+testdata/interface_satisfied.snow:34:11: s1
+testdata/interface_satisfied.snow:35:11: integer
+testdata/interface_satisfied.snow:37:7: S1
+testdata/interface_satisfied.snow:38:7: S2
+testdata/interface_satisfied.snow:39:7: S3
+testdata/interface_satisfied.snow:41:13: S4
+testdata/interface_satisfied.snow:42:13: S4
+testdata/interface_satisfied.snow:45:7: vs4
+testdata/interface_satisfied.snow:47:7: ls4
+testdata/interface_satisfied.snow:49:7: S4
M pkg/semantic/testdata/types/interface_satisfied.snow.want => pkg/semantic/testdata/types/interface_satisfied.snow.want +24 -2
@@ 1,6 1,6 @@
-file testdata/interface_satisfied.snow [0, 1, 3, 2]
+file testdata/interface_satisfied.snow [0, 1, 4, 2]
fn main [let: () -> void]
- block [11]
+ block [16]
var: empty [var: interface Empty]
ident Empty [type: interface Empty]
var: f [var: interface Foo]
@@ 33,6 33,22 @@ file testdata/interface_satisfied.snow [0, 1, 3, 2]
ident f [var: interface Foo]
call [0] [value: struct S3]
ident S3 [type: struct S3]
+ var= vs4 [var: struct S4]
+ call [0] [value: struct S4]
+ ident S4 [type: struct S4]
+ let= ls4 [let: struct S4]
+ call [0] [value: struct S4]
+ ident S4 [type: struct S4]
+ assign
+ ident f [var: interface Foo]
+ ident vs4 [var: struct S4]
+ assign
+ ident f [var: interface Foo]
+ ident ls4 [let: struct S4]
+ assign
+ ident f [var: interface Foo]
+ call [0] [value: struct S4]
+ ident S4 [type: struct S4]
struct S1 [0, 2, 0, 0] [type: struct S1]
fn foo [let: (int) -> int]
let: x [let: int]
@@ 58,6 74,12 @@ file testdata/interface_satisfied.snow [0, 1, 3, 2]
ident uint [type: uint]
ident int [type: int]
block [0]
+ struct S4 [0, 1, 0, 0] [type: struct S4]
+ ref fn foo [let: (int) -> int]
+ let: i [let: int]
+ ident int [type: int]
+ ident int [type: int]
+ block [0]
interface Empty [0] [type: interface Empty]
interface Foo [1] [type: interface Foo]
fn foo [let: (int) -> int]
M pkg/semantic/type.go => pkg/semantic/type.go +52 -8
@@ 395,7 395,7 @@ func (s *StructType) AssignableTo(T Type) bool {
case *StructType:
return s.IdenticalTo(T)
case *InterfaceType:
- return T.isSatisfiedBy(s)
+ return T.mightBeSatisfiedBy(s)
}
return false
}
@@ 501,7 501,7 @@ func makeGenericResolveMap(gc *GenericClause, types []Type) map[*GenericType]Typ
// identical).
func (i *InterfaceType) AssignableTo(T Type) bool {
if i2 := AsInterfaceType(T); i2 != nil {
- return i2.isSatisfiedBy(i)
+ return i2.mightBeSatisfiedBy(i)
}
return false
}
@@ 599,17 599,15 @@ func (i *InterfaceType) typeOfSel(T Type) Type {
return T.ResolveGeneric(i.lookup)
}
-// TODO: eventually, for better error reporting, should return the missing method names
-// or the invalid method types. Could also return the mapping of functions on T that
-// satisfy each required method of i, might be useful in later stages.
-func (i *InterfaceType) isSatisfiedBy(T Type) bool {
+// mightBeSatisfiedBy makes incomplete checks that type T satisfies interface
+// i. If false, T definitely does not satisfy i, but if true, it might still
+// not satisfy it. Further checks are done afterwards in the type-check pass.
+func (i *InterfaceType) mightBeSatisfiedBy(T Type) bool {
var (
tfns []*Fn
resolve func(Type) Type
)
- // TODO: for struct to satisfy an interface, if a method is ref, it should only be able
- // to satisfy if the struct value is mutable (type context should come into play).
switch T := T.(type) {
case *StructType:
tfns = T.Decl.Fns
@@ 639,6 637,52 @@ func (i *InterfaceType) isSatisfiedBy(T Type) bool {
return len(req) == 0
}
+// isSatisfiedBy is the complete, thorough interface satisfiability check version of
+// mightBeSatisfiedBy. It is based on both the type T and the type context Tctx, so that
+// ref functions can only be used to satisfy an interface's method if the context allows
+// mutating the corresponding value.
+//
+// It returns true if the interface is satisfied by T and false otherwise. In any case,
+// the mappings map will be filled with the interface's methods and the matching T's
+// method that satisfies this method. If it returns false, then mappings has some
+// nil values.
+func (i *InterfaceType) isSatisfiedBy(T Type, Tctx TypeContext, mappings map[*Fn]*Fn) bool {
+ var (
+ tfns []*Fn
+ resolve func(Type) Type
+ )
+
+ req := make(map[string]*Fn, len(i.Decl.Methods))
+ for _, m := range i.Decl.Methods {
+ if mappings != nil {
+ mappings[m] = nil
+ }
+ req[m.Ident()] = m
+ }
+
+ switch T := T.(type) {
+ case *StructType:
+ tfns = T.Decl.Fns
+ resolve = T.typeOfSel
+ default:
+ return false
+ }
+
+ for _, fn := range tfns {
+ if ifaceFn := req[fn.Ident()]; ifaceFn != nil {
+ if fn.IsRef && Tctx != Mutable {
+ continue
+ }
+ reqt := i.typeOfSel(ifaceFn.Type())
+ if reqt.IdenticalTo(resolve(fn.Type())) {
+ delete(req, fn.Ident())
+ mappings[ifaceFn] = fn
+ }
+ }
+ }
+ return len(req) == 0
+}
+
// ========> implement Type for GenericType
func (g *GenericType) String() string { return g.Name }
M pkg/semantic/typecheck_pass.go => pkg/semantic/typecheck_pass.go +16 -7
@@ 781,15 781,24 @@ func asIdentDeclRef(expr Expr) Decl {
}
func (t *typecheckVisitor) createImplicitConv(expr Expr, toType Type) *ImplicitConv {
- // TODO: store the mapping of methods when converting to an interface type
- // TODO: this might be a good place too to typecheck e.g. that expr has the right
- // type context to access a ref method.
- // TODO: maybe have `mightBeSatisfiedBy` when checking AssignableTo, and then here
- // use the more thorough isSatisfiedBy, which builds the method mapping, and then here
- // we can check with the type context if it is ok.
var ms map[*Fn]*Fn
- if it := AsInterfaceType(toType); it != nil {
+ // if the expression to convert from is an interface, no need to do the more thorough
+ // check if it satisfies the target interface - that's only required for non-interface
+ // to interface.
+ if it := AsInterfaceType(toType); it != nil && AsInterfaceType(expr.Type()) == nil {
ms = make(map[*Fn]*Fn, len(it.Decl.Methods))
+ if !it.isSatisfiedBy(expr.Type(), expr.TypeContext(), ms) {
+ // due to the weaker mightBeSatisfiedBy check done earlier, the only unsatisfied methods
+ // will be because the corresponding method is a ref and the type context is not mutable.
+ var missing []string
+ for k, v := range ms {
+ if v == nil {
+ missing = append(missing, k.Ident())
+ }
+ }
+ sort.Strings(missing)
+ t.errh(expr.Pos(), fmt.Sprintf("type %s does not satisfy type %s: missing %v (ref methods can only be used in a mutable context)", expr.Type(), toType, missing))
+ }
}
var ic ImplicitConv