~mna/snow unlisted

e5f558499c13cbdadb350de44eca089d233d01b5 — Martin Angers 9 months ago 9192873
pkg/semantic: fix nested assignment of non-mutable structs to interfaces
M pkg/semantic/semantic.go => pkg/semantic/semantic.go +3 -6
@@ 55,6 55,7 @@ type Typed interface {
	Node
	Type() Type
	TypeContext() TypeContext
	setType(T Type)
}

type Expr interface {


@@ 71,6 72,7 @@ type commonExpr struct {
func (c commonExpr) Type() Type               { return c.typ }
func (c commonExpr) TypeContext() TypeContext { return c.ctx }
func (c commonExpr) expr()                    {}
func (c *commonExpr) setType(T Type)          { c.typ = T }

type Decl interface {
	Typed


@@ 110,6 112,7 @@ func (c commonDecl) Ident() string   { return c.ident }
func (c commonDecl) Type() Type      { return c.typ }
func (c commonDecl) IsGeneric() bool { return false }
func (c commonDecl) decl()           {}
func (c *commonDecl) setType(T Type) { c.typ = T }

func AsFnDecl(n Node) *Fn {
	if fn, ok := n.(*Fn); ok {


@@ 396,12 399,6 @@ type Ident struct {
// type.
type ImplicitConv struct {
	Value Expr
	// MethodMapping is set when the ImplicitConv targets an interface. It maps
	// the methods of the target interface as keys to the methods of the Value
	// expression (which has necessarily either a *StructType or an *InterfaceType
	// at the moment) as values.
	MethodMapping map[*Fn]*Fn

	commonExpr
}


M pkg/semantic/testdata/check/interface_satisfied.snow.err => pkg/semantic/testdata/check/interface_satisfied.snow.err +7 -5
@@ 1,5 1,7 @@
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)
testdata/interface_satisfied.snow:39:11: cannot assign type int to variable of type interface Empty
testdata/interface_satisfied.snow:42:7: cannot assign type struct S2 to variable of type interface Foo
testdata/interface_satisfied.snow:43:7: cannot assign type struct S3 to variable of type interface Foo
testdata/interface_satisfied.snow:51:7: cannot assign type struct S4 to variable of type interface Foo
testdata/interface_satisfied.snow:53:7: cannot assign type struct S4 to variable of type interface Foo
testdata/interface_satisfied.snow:59:9: cannot assign type (int, struct S4) to variable of type (int, interface Foo)
testdata/interface_satisfied.snow:63:10: cannot assign type (int, struct S4, (bool, struct S4)) to variable of type (int, interface Foo, (bool, interface Foo))

M pkg/semantic/testdata/check/interface_satisfied.snow.want => pkg/semantic/testdata/check/interface_satisfied.snow.want +77 -7
@@ 1,6 1,12 @@
file testdata/interface_satisfied.snow [0, 1, 4, 2]
file testdata/interface_satisfied.snow [0, 2, 4, 2]
  fn get_s4 [let: () -> struct S4]
    ident S4 [type: struct S4]
    block [1]
      return
        call [0] [value: struct S4]
          ident S4 [type: struct S4]
  fn main [let: () -> void]
    block [16]
    block [25]
      var: empty [var: interface Empty]
        ident Empty [type: interface Empty]
      var: f [var: interface Foo]


@@ 48,13 54,77 @@ file testdata/interface_satisfied.snow [0, 1, 4, 2]
          ident vs4 [var: struct S4]
      assign
        ident f [var: interface Foo]
        implicit conv [value: interface Foo]
          ident ls4 [let: struct S4]
        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]
        call [0] [value: struct S4]
          ident S4 [type: struct S4]
      var: tup [var: (int, interface Foo)]
        tuple type [2] [type: (int, interface Foo)]
          ident int [type: int]
          ident Foo [type: interface Foo]
      assign
        ident tup [var: (int, interface Foo)]
        implicit conv [value: (int, interface Foo)]
          tuple value [2] [value: (int, struct S4)]
            int [1] [const: int]
            ident vs4 [var: struct S4]
      assign
        ident tup [var: (int, interface Foo)]
        tuple value [2] [value: (int, struct S4)]
          int [1] [const: int]
          ident ls4 [let: struct S4]
      var: tup2 [var: (int, interface Foo, (bool, interface Foo))]
        tuple type [3] [type: (int, interface Foo, (bool, interface Foo))]
          ident int [type: int]
          ident Foo [type: interface Foo]
          tuple type [2] [type: (bool, interface Foo)]
            ident bool [type: bool]
            ident Foo [type: interface Foo]
      assign
        ident tup2 [var: (int, interface Foo, (bool, interface Foo))]
        implicit conv [value: (int, interface Foo, (bool, interface Foo))]
          tuple value [3] [value: (int, struct S4, (bool, struct S4))]
            int [1] [const: int]
            ident vs4 [var: struct S4]
            tuple value [2] [value: (bool, struct S4)]
              ident true [let: bool]
              ident vs4 [var: struct S4]
      assign
        ident tup2 [var: (int, interface Foo, (bool, interface Foo))]
        tuple value [3] [value: (int, struct S4, (bool, struct S4))]
          int [1] [const: int]
          ident ls4 [let: struct S4]
          tuple value [2] [value: (bool, struct S4)]
            ident true [let: bool]
            call [0] [value: struct S4]
              ident get_s4 [let: () -> struct S4]
      var: tup3 [var: (int, (interface Foo, interface Empty))]
        tuple type [2] [type: (int, (interface Foo, interface Empty))]
          ident int [type: int]
          tuple type [2] [type: (interface Foo, interface Empty)]
            ident Foo [type: interface Foo]
            paren [type: interface Empty]
              ident Empty [type: interface Empty]
      assign
        ident tup3 [var: (int, (interface Foo, interface Empty))]
        implicit conv [value: (int, (interface Foo, interface Empty))]
          tuple value [2] [value: (int, (struct S4, struct S4))]
            int [3] [const: int]
            tuple value [2] [value: (struct S4, struct S4)]
              ident vs4 [var: struct S4]
              paren [let: struct S4]
                ident ls4 [let: struct S4]
      assign
        ident tup3 [var: (int, (interface Foo, interface Empty))]
        implicit conv [value: (int, (interface Foo, interface Empty))]
          tuple value [2] [value: (int, (struct S4, struct S4))]
            int [3] [const: int]
            tuple value [2] [value: (struct S4, struct S4)]
              ident vs4 [var: struct S4]
              paren [value: struct S4]
                call [0] [value: struct S4]
                  ident get_s4 [let: () -> struct S4]
  struct S1 [0, 2, 0, 0] [type: struct S1]
    fn foo [let: (int) -> int]
      let: x [let: int]

M pkg/semantic/testdata/interface_satisfied.snow => pkg/semantic/testdata/interface_satisfied.snow +3 -2
@@ 59,9 59,10 @@ fn main() {
  tup = (1, ls4)

  var tup2: (int, Foo, (bool, Foo))
  tup2 = (1, vs4, (true, vs4))
  tup2 = (1, ls4, (true, get_s4()))
  tup2 = (1, vs4, (true, vs4)) # works, var s4 in both Foo places
  tup2 = (1, ls4, (true, get_s4())) # fails, immutable S4 and value S4

  # those all work (var S4 for Foo, and whatever for Empty)
  var tup3: (int, (Foo, (Empty)))
  tup3 = (3, (vs4, (ls4)))
  tup3 = (3, (vs4, (get_s4())))

M pkg/semantic/testdata/scopes/interface_satisfied.snow.want => pkg/semantic/testdata/scopes/interface_satisfied.snow.want +1 -0
@@ 88,6 88,7 @@
.  .  .  s1
.  .  .  tup
.  .  .  tup2
.  .  .  tup3
.  .  .  vs4
.  .  }
.  }

M pkg/semantic/testdata/static/interface_satisfied.snow.err => pkg/semantic/testdata/static/interface_satisfied.snow.err +7 -5
@@ 1,5 1,7 @@
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)
testdata/interface_satisfied.snow:39:11: cannot assign type int to variable of type interface Empty
testdata/interface_satisfied.snow:42:7: cannot assign type struct S2 to variable of type interface Foo
testdata/interface_satisfied.snow:43:7: cannot assign type struct S3 to variable of type interface Foo
testdata/interface_satisfied.snow:51:7: cannot assign type struct S4 to variable of type interface Foo
testdata/interface_satisfied.snow:53:7: cannot assign type struct S4 to variable of type interface Foo
testdata/interface_satisfied.snow:59:9: cannot assign type (int, struct S4) to variable of type (int, interface Foo)
testdata/interface_satisfied.snow:63:10: cannot assign type (int, struct S4, (bool, struct S4)) to variable of type (int, interface Foo, (bool, interface Foo))

M pkg/semantic/testdata/static/interface_satisfied.snow.want => pkg/semantic/testdata/static/interface_satisfied.snow.want +25 -12
@@ 1,12 1,25 @@
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
testdata/interface_satisfied.snow:27:10: S4
testdata/interface_satisfied.snow:36:11: empty
testdata/interface_satisfied.snow:37:11: f
testdata/interface_satisfied.snow:38:11: s1
testdata/interface_satisfied.snow:39:11: integer
testdata/interface_satisfied.snow:41:7: S1
testdata/interface_satisfied.snow:42:7: S2
testdata/interface_satisfied.snow:43:7: S3
testdata/interface_satisfied.snow:45:13: S4
testdata/interface_satisfied.snow:46:13: S4
testdata/interface_satisfied.snow:49:7: vs4
testdata/interface_satisfied.snow:51:7: ls4
testdata/interface_satisfied.snow:53:7: S4
testdata/interface_satisfied.snow:57:13: vs4
testdata/interface_satisfied.snow:59:13: ls4
testdata/interface_satisfied.snow:62:14: vs4
testdata/interface_satisfied.snow:62:20: true
testdata/interface_satisfied.snow:62:26: vs4
testdata/interface_satisfied.snow:63:14: ls4
testdata/interface_satisfied.snow:63:20: true
testdata/interface_satisfied.snow:63:26: get_s4
testdata/interface_satisfied.snow:67:15: vs4
testdata/interface_satisfied.snow:67:21: ls4
testdata/interface_satisfied.snow:68:15: vs4
testdata/interface_satisfied.snow:68:21: get_s4

M pkg/semantic/testdata/types/interface_satisfied.snow.want => pkg/semantic/testdata/types/interface_satisfied.snow.want +70 -2
@@ 1,6 1,12 @@
file testdata/interface_satisfied.snow [0, 1, 4, 2]
file testdata/interface_satisfied.snow [0, 2, 4, 2]
  fn get_s4 [let: () -> struct S4]
    ident S4 [type: struct S4]
    block [1]
      return
        call [0] [value: struct S4]
          ident S4 [type: struct S4]
  fn main [let: () -> void]
    block [16]
    block [25]
      var: empty [var: interface Empty]
        ident Empty [type: interface Empty]
      var: f [var: interface Foo]


@@ 49,6 55,68 @@ file testdata/interface_satisfied.snow [0, 1, 4, 2]
        ident f [var: interface Foo]
        call [0] [value: struct S4]
          ident S4 [type: struct S4]
      var: tup [var: (int, interface Foo)]
        tuple type [2] [type: (int, interface Foo)]
          ident int [type: int]
          ident Foo [type: interface Foo]
      assign
        ident tup [var: (int, interface Foo)]
        tuple value [2] [value: (int, struct S4)]
          int [1] [const: int]
          ident vs4 [var: struct S4]
      assign
        ident tup [var: (int, interface Foo)]
        tuple value [2] [value: (int, struct S4)]
          int [1] [const: int]
          ident ls4 [let: struct S4]
      var: tup2 [var: (int, interface Foo, (bool, interface Foo))]
        tuple type [3] [type: (int, interface Foo, (bool, interface Foo))]
          ident int [type: int]
          ident Foo [type: interface Foo]
          tuple type [2] [type: (bool, interface Foo)]
            ident bool [type: bool]
            ident Foo [type: interface Foo]
      assign
        ident tup2 [var: (int, interface Foo, (bool, interface Foo))]
        tuple value [3] [value: (int, struct S4, (bool, struct S4))]
          int [1] [const: int]
          ident vs4 [var: struct S4]
          tuple value [2] [value: (bool, struct S4)]
            ident true [let: bool]
            ident vs4 [var: struct S4]
      assign
        ident tup2 [var: (int, interface Foo, (bool, interface Foo))]
        tuple value [3] [value: (int, struct S4, (bool, struct S4))]
          int [1] [const: int]
          ident ls4 [let: struct S4]
          tuple value [2] [value: (bool, struct S4)]
            ident true [let: bool]
            call [0] [value: struct S4]
              ident get_s4 [let: () -> struct S4]
      var: tup3 [var: (int, (interface Foo, interface Empty))]
        tuple type [2] [type: (int, (interface Foo, interface Empty))]
          ident int [type: int]
          tuple type [2] [type: (interface Foo, interface Empty)]
            ident Foo [type: interface Foo]
            paren [type: interface Empty]
              ident Empty [type: interface Empty]
      assign
        ident tup3 [var: (int, (interface Foo, interface Empty))]
        tuple value [2] [value: (int, (struct S4, struct S4))]
          int [3] [const: int]
          tuple value [2] [value: (struct S4, struct S4)]
            ident vs4 [var: struct S4]
            paren [let: struct S4]
              ident ls4 [let: struct S4]
      assign
        ident tup3 [var: (int, (interface Foo, interface Empty))]
        tuple value [2] [value: (int, (struct S4, struct S4))]
          int [3] [const: int]
          tuple value [2] [value: (struct S4, struct S4)]
            ident vs4 [var: struct S4]
            paren [value: struct S4]
              call [0] [value: struct S4]
                ident get_s4 [let: () -> struct S4]
  struct S1 [0, 2, 0, 0] [type: struct S1]
    fn foo [let: (int) -> int]
      let: x [let: int]

M pkg/semantic/type.go => pkg/semantic/type.go +17 -54
@@ 59,6 59,11 @@ type (
		// Inst is the list of types used in a generic instantiation, if the
		// struct declaration is generic.
		Inst []Type
		// Ctx is the type context associated with the value that has that type.
		// It only plays a role in one specific case: when the value associated
		// with that type is assigned to an interface type, to check if ref
		// functions can be used to satisfy that type (if Ctx is mutable).
		Ctx TypeContext

		// lookup cache of GenericType to instantiated type, nil until the first
		// call to typeOfSel.


@@ 395,7 400,7 @@ func (s *StructType) AssignableTo(T Type) bool {
	case *StructType:
		return s.IdenticalTo(T)
	case *InterfaceType:
		return T.mightBeSatisfiedBy(s)
		return T.isSatisfiedBy(s)
	}
	return false
}


@@ 501,7 506,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.mightBeSatisfiedBy(i)
		return i2.isSatisfiedBy(i)
	}
	return false
}


@@ 599,22 604,22 @@ func (i *InterfaceType) typeOfSel(T Type) Type {
	return T.ResolveGeneric(i.lookup)
}

// 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 {
func (i *InterfaceType) isSatisfiedBy(T Type) bool {
	var (
		tfns    []*Fn
		resolve func(Type) Type
		tfns     []*Fn
		resolve  func(Type) Type
		allowRef bool
	)

	switch T := T.(type) {
	case *StructType:
		tfns = T.Decl.Fns
		resolve = T.typeOfSel
		allowRef = T.Ctx == Mutable
	case *InterfaceType:
		tfns = T.Decl.Methods
		resolve = T.typeOfSel
		allowRef = false // cannot have ref methods on interface anyway
	default:
		return false
	}


@@ 628,6 633,10 @@ func (i *InterfaceType) mightBeSatisfiedBy(T Type) bool {
	for _, fn := range tfns {
		// if this fn is part of the required methods set, check if the types match
		if reqt := req[fn.Ident()]; reqt != nil {
			if fn.IsRef && !allowRef {
				// won't be able to satisfy this method
				return false
			}
			if !reqt.IdenticalTo(resolve(fn.Type())) {
				return false
			}


@@ 637,52 646,6 @@ func (i *InterfaceType) mightBeSatisfiedBy(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/typeassign_pass.go => pkg/semantic/typeassign_pass.go +6 -0
@@ 318,6 318,12 @@ func (t *typeassignVisitor) Visit(n Node) Visitor {
	if typed, ok := n.(Typed); ok {
		if !typed.Type().Valid() {
			t.addDeferred(n)
			return nil
		}

		if st := AsStructType(typed.Type()); st != nil {
			cloned := &StructType{Decl: st.Decl, Inst: st.Inst, Ctx: typed.TypeContext()}
			typed.setType(cloned)
		}
	}


M pkg/semantic/typecheck_pass.go => pkg/semantic/typecheck_pass.go +0 -21
@@ 781,32 781,11 @@ func asIdentDeclRef(expr Expr) Decl {
}

func (t *typecheckVisitor) createImplicitConv(expr Expr, toType Type) *ImplicitConv {
	var ms map[*Fn]*Fn
	// 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
	ic.pos = expr.Pos()
	ic.scope = expr.Scope()
	ic.Value = expr
	ic.typ = toType
	ic.ctx = Value
	ic.MethodMapping = ms
	return &ic
}