~mna/snow unlisted

22baf717b3265638e993a1c6a68f6107b142fd83 — Martin Angers 9 months ago 1554728
pkg/semantic: fix interface satisfied by for ref methods
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