]> Cypherpunks repositories - gostls13.git/commitdiff
go/types, types2: consider methods when unifying type parameters and constraints
authorRobert Griesemer <gri@golang.org>
Tue, 28 Feb 2023 20:56:06 +0000 (12:56 -0800)
committerGopher Robot <gobot@golang.org>
Wed, 1 Mar 2023 21:23:52 +0000 (21:23 +0000)
An inferred type argument must implement its type parameter's constraint's
methods whether or not a core type exists. This allows us to infer type
parameters used in method signatures.

Fixes #51593.

Change-Id: I1fddb05a71d442641b4311d8e30a13ea9bdb4db5
Reviewed-on: https://go-review.googlesource.com/c/go/+/472298
TryBot-Result: Gopher Robot <gobot@golang.org>
Auto-Submit: Robert Griesemer <gri@google.com>
Run-TryBot: Robert Griesemer <gri@google.com>
Reviewed-by: Robert Griesemer <gri@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
src/cmd/compile/internal/types2/check_test.go
src/cmd/compile/internal/types2/infer.go
src/go/types/infer.go
src/internal/types/testdata/examples/inference.go
src/internal/types/testdata/fixedbugs/issue51593.go

index 98ace528e9cae24dd1764a255b03398fd1d923e5..26bb1aed9e6e8c5eaed271fbc6af2336a1dce87e 100644 (file)
@@ -326,7 +326,7 @@ func TestCheck(t *testing.T) {
 }
 func TestSpec(t *testing.T) { testDirFiles(t, "../../../../internal/types/testdata/spec", 0, false) }
 func TestExamples(t *testing.T) {
-       testDirFiles(t, "../../../../internal/types/testdata/examples", 60, false)
+       testDirFiles(t, "../../../../internal/types/testdata/examples", 125, false)
 } // TODO(gri) narrow column tolerance
 func TestFixedbugs(t *testing.T) {
        testDirFiles(t, "../../../../internal/types/testdata/fixedbugs", 100, false)
index 2328671f1029732d8746f9ed69aecb631b48e09c..49cf4601b87ac15e69232592b7741a6b31c142c0 100644 (file)
@@ -186,12 +186,12 @@ func (check *Checker) infer(pos syntax.Pos, tparams []*TypeParam, targs []Type,
                u.tracef("type parameters: %s", tparams)
        }
 
-       // Repeatedly apply constraint type inference as long as
-       // progress is being made.
+       // Unify type parameters with their constraints as long
+       // as progress is being made.
        //
        // This is an O(n^2) algorithm where n is the number of
        // type parameters: if there is progress, at least one
-       // type argument is inferred per iteration and we have
+       // type argument is inferred per iteration, and we have
        // a doubly nested loop.
        //
        // In practice this is not a problem because the number
@@ -205,6 +205,11 @@ func (check *Checker) infer(pos syntax.Pos, tparams []*TypeParam, targs []Type,
                nn := u.unknowns()
 
                for _, tpar := range tparams {
+                       tx := u.at(tpar)
+                       if traceInference && tx != nil {
+                               u.tracef("%s = %s", tpar, tx)
+                       }
+
                        // If there is a core term (i.e., a core type with tilde information)
                        // unify the type parameter with the core type.
                        if core, single := coreTerm(tpar); core != nil {
@@ -212,7 +217,6 @@ func (check *Checker) infer(pos syntax.Pos, tparams []*TypeParam, targs []Type,
                                        u.tracef("core(%s) = %s (single = %v)", tpar, core, single)
                                }
                                // A type parameter can be unified with its core type in two cases.
-                               tx := u.at(tpar)
                                switch {
                                case tx != nil:
                                        // The corresponding type argument tx is known. There are 2 cases:
@@ -239,6 +243,17 @@ func (check *Checker) infer(pos syntax.Pos, tparams []*TypeParam, targs []Type,
                                if traceInference {
                                        u.tracef("core(%s) = nil", tpar)
                                }
+                               if tx != nil {
+                                       // We don't have a core type, but the type argument tx is known.
+                                       // It must have (at least) all the methods of the type constraint,
+                                       // and the method signatures must unify; otherwise tx cannot satisfy
+                                       // the constraint.
+                                       constraint := tpar.iface()
+                                       if m, wrong := check.missingMethod(tx, constraint, true, u.unify); m != nil {
+                                               check.errorf(pos, CannotInferTypeArgs, "%s does not satisfy %s %s", tx, constraint, check.missingMethodCause(tx, constraint, m, wrong))
+                                               return nil
+                                       }
+                               }
                        }
                }
 
@@ -273,7 +288,7 @@ func (check *Checker) infer(pos syntax.Pos, tparams []*TypeParam, targs []Type,
                        j++
                }
        }
-       // untyped[:j] are the undices of parameters without a type yet
+       // untyped[:j] are the indices of parameters without a type yet
        for _, i := range untyped[:j] {
                tpar := params.At(i).typ.(*TypeParam)
                arg := args[i]
index 4143d2aabecde8f0f0aba102f6f786311e89accd..014036e2060932d4d86f1519a5442456ce524919 100644 (file)
@@ -188,12 +188,12 @@ func (check *Checker) infer(posn positioner, tparams []*TypeParam, targs []Type,
                u.tracef("type parameters: %s", tparams)
        }
 
-       // Repeatedly apply constraint type inference as long as
-       // progress is being made.
+       // Unify type parameters with their constraints as long
+       // as progress is being made.
        //
        // This is an O(n^2) algorithm where n is the number of
        // type parameters: if there is progress, at least one
-       // type argument is inferred per iteration and we have
+       // type argument is inferred per iteration, and we have
        // a doubly nested loop.
        //
        // In practice this is not a problem because the number
@@ -207,6 +207,11 @@ func (check *Checker) infer(posn positioner, tparams []*TypeParam, targs []Type,
                nn := u.unknowns()
 
                for _, tpar := range tparams {
+                       tx := u.at(tpar)
+                       if traceInference && tx != nil {
+                               u.tracef("%s = %s", tpar, tx)
+                       }
+
                        // If there is a core term (i.e., a core type with tilde information)
                        // unify the type parameter with the core type.
                        if core, single := coreTerm(tpar); core != nil {
@@ -214,7 +219,6 @@ func (check *Checker) infer(posn positioner, tparams []*TypeParam, targs []Type,
                                        u.tracef("core(%s) = %s (single = %v)", tpar, core, single)
                                }
                                // A type parameter can be unified with its core type in two cases.
-                               tx := u.at(tpar)
                                switch {
                                case tx != nil:
                                        // The corresponding type argument tx is known. There are 2 cases:
@@ -241,6 +245,17 @@ func (check *Checker) infer(posn positioner, tparams []*TypeParam, targs []Type,
                                if traceInference {
                                        u.tracef("core(%s) = nil", tpar)
                                }
+                               if tx != nil {
+                                       // We don't have a core type, but the type argument tx is known.
+                                       // It must have (at least) all the methods of the type constraint,
+                                       // and the method signatures must unify; otherwise tx cannot satisfy
+                                       // the constraint.
+                                       constraint := tpar.iface()
+                                       if m, wrong := check.missingMethod(tx, constraint, true, u.unify); m != nil {
+                                               check.errorf(posn, CannotInferTypeArgs, "%s does not satisfy %s %s", tx, constraint, check.missingMethodCause(tx, constraint, m, wrong))
+                                               return nil
+                                       }
+                               }
                        }
                }
 
@@ -275,7 +290,7 @@ func (check *Checker) infer(posn positioner, tparams []*TypeParam, targs []Type,
                        j++
                }
        }
-       // untyped[:j] are the undices of parameters without a type yet
+       // untyped[:j] are the indices of parameters without a type yet
        for _, i := range untyped[:j] {
                tpar := params.At(i).typ.(*TypeParam)
                arg := args[i]
index 34aa5fcad8b6a821f5e5f9b2e0a623b87508b148..2e88041df0f2e0fbe09786123831afd0c4468cab 100644 (file)
@@ -114,3 +114,38 @@ func _() {
        // List[Elem].
        related3 /* ERROR "cannot infer Slice" */ [int]()
 }
+
+func wantsMethods[P interface{ m1(Q); m2() R }, Q, R any](P) {}
+
+type hasMethods1 struct{}
+
+func (hasMethods1) m1(int)
+func (hasMethods1) m2() string
+
+type hasMethods2 struct{}
+
+func (*hasMethods2) m1(int)
+func (*hasMethods2) m2() string
+
+type hasMethods3 interface{
+       m1(float64)
+       m2() complex128
+}
+
+type hasMethods4 interface{
+       m1()
+}
+
+func _() {
+       // wantsMethod can be called with arguments that have the relevant methods
+       // and wantsMethod's type arguments are inferred from those types' method
+       // signatures.
+       wantsMethods(hasMethods1{})
+       wantsMethods(&hasMethods1{})
+       // TODO(gri) improve error message (the cause is ptr vs non-pointer receiver)
+       wantsMethods /* ERROR "hasMethods2 does not satisfy interface{m1(Q); m2() R} (wrong type for method m1)" */ (hasMethods2{})
+       wantsMethods(&hasMethods2{})
+       wantsMethods(hasMethods3(nil))
+       wantsMethods /* ERROR "any does not satisfy interface{m1(Q); m2() R} (missing method m1)" */ (any(nil))
+       wantsMethods /* ERROR "hasMethods4 does not satisfy interface{m1(Q); m2() R} (wrong type for method m1)" */ (hasMethods4(nil))
+}
index f7b3a499887e075d891a6656dfef2af7576f8c4e..62b0a5625ad9219590dd2ecd3d0e3d6bd6c465fb 100644 (file)
@@ -9,5 +9,5 @@ func f[P interface{ m(R) }, R any]() {}
 type T = interface { m(int) }
 
 func _() {
-       _ = f /* ERROR "cannot infer R" */ [T] // don't crash in type inference
+       _ = f[T]
 }