]> Cypherpunks.ru repositories - gostls13.git/commitdiff
go/types, types2: don't infer type argument for unused parameter in interfaces
authorRobert Griesemer <gri@golang.org>
Thu, 25 May 2023 16:54:11 +0000 (09:54 -0700)
committerGopher Robot <gobot@golang.org>
Thu, 25 May 2023 21:37:01 +0000 (21:37 +0000)
Two interface types that are assignable don't have to be identical;
specifically, if they are defined types, they can be different
defined types. If those defined types specify type parameters which
are never used, do not infer a type argument based on the instantiation
of a matching defined type.

Adjusted three existing tests where we inferred type arguments incorrectly.

Fixes #60377.

Change-Id: I91fb207235424b3cbc42b5fd93eee619e7541cb7
Reviewed-on: https://go-review.googlesource.com/c/go/+/498315
Auto-Submit: Robert Griesemer <gri@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
Reviewed-by: Robert Griesemer <gri@google.com>
Run-TryBot: Robert Griesemer <gri@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>

src/cmd/compile/internal/types2/unify.go
src/go/types/unify.go
src/internal/types/testdata/fixedbugs/issue49541.go
src/internal/types/testdata/fixedbugs/issue60377.go [new file with mode: 0644]
test/fixedbugs/issue53309.go
test/typeparam/issue53762.go

index 7b7d7dc9e9e88f339f8183a3c4b90fff0be1ae2b..fa41ae0798f2006171786a59b7c118ecdf10d92b 100644 (file)
@@ -613,45 +613,23 @@ func (u *unifier) nify(x, y Type, p *ifacePair) (result bool) {
                }
 
        case *Named:
-               // Two named types unify if their type names originate
-               // in the same type declaration. If they are instantiated,
-               // their type argument lists must unify.
+               // Two named non-interface types unify if their type names originate
+               // in the same type declaration. If they are instantiated, their type
+               // argument lists must unify.
+               // If one or both named types are interfaces, the types unify if the
+               // respective methods unify (per the rules for interface unification).
                if y, ok := y.(*Named); ok {
-                       sameOrig := indenticalOrigin(x, y)
                        if enableInterfaceInference {
-                               xu := x.under()
-                               yu := y.under()
-                               xi, _ := xu.(*Interface)
-                               yi, _ := yu.(*Interface)
-                               // If one or both defined types are interfaces, use interface unification,
-                               // unless they originated in the same type declaration.
-                               if xi != nil && yi != nil {
-                                       // If both interfaces originate in the same declaration,
-                                       // their methods unify if the type parameters unify.
-                                       // Unify the type parameters rather than the methods in
-                                       // case the type parameters are not used in the methods
-                                       // (and to preserve existing behavior in this case).
-                                       if sameOrig {
-                                               xargs := x.TypeArgs().list()
-                                               yargs := y.TypeArgs().list()
-                                               assert(len(xargs) == len(yargs))
-                                               for i, xarg := range xargs {
-                                                       if !u.nify(xarg, yargs[i], p) {
-                                                               return false
-                                                       }
-                                               }
-                                               return true
-                                       }
-                                       return u.nify(xu, yu, p)
-                               }
-                               // We don't have two interfaces. If we have one, make sure it's in xi.
-                               if yi != nil {
-                                       xi = yi
-                                       y = x
-                               }
-                               // If xi is an interface, use interface unification.
-                               if xi != nil {
+                               xi, _ := x.under().(*Interface)
+                               yi, _ := y.under().(*Interface)
+                               // If one or both of x and y are interfaces, use interface unification.
+                               switch {
+                               case xi != nil && yi != nil:
+                                       return u.nify(xi, yi, p)
+                               case xi != nil:
                                        return u.nify(xi, y, p)
+                               case yi != nil:
+                                       return u.nify(x, yi, p)
                                }
                                // In all other cases, the type arguments and origins must match.
                        }
@@ -669,7 +647,7 @@ func (u *unifier) nify(x, y Type, p *ifacePair) (result bool) {
                                        return false
                                }
                        }
-                       return sameOrig
+                       return indenticalOrigin(x, y)
                }
 
        case *TypeParam:
index d5757defd6d281df9cac3f4af964856d3ab6b7a0..0c00329747a30126a95cb207c86bc799ae126a29 100644 (file)
@@ -615,45 +615,23 @@ func (u *unifier) nify(x, y Type, p *ifacePair) (result bool) {
                }
 
        case *Named:
-               // Two named types unify if their type names originate
-               // in the same type declaration. If they are instantiated,
-               // their type argument lists must unify.
+               // Two named non-interface types unify if their type names originate
+               // in the same type declaration. If they are instantiated, their type
+               // argument lists must unify.
+               // If one or both named types are interfaces, the types unify if the
+               // respective methods unify (per the rules for interface unification).
                if y, ok := y.(*Named); ok {
-                       sameOrig := indenticalOrigin(x, y)
                        if enableInterfaceInference {
-                               xu := x.under()
-                               yu := y.under()
-                               xi, _ := xu.(*Interface)
-                               yi, _ := yu.(*Interface)
-                               // If one or both defined types are interfaces, use interface unification,
-                               // unless they originated in the same type declaration.
-                               if xi != nil && yi != nil {
-                                       // If both interfaces originate in the same declaration,
-                                       // their methods unify if the type parameters unify.
-                                       // Unify the type parameters rather than the methods in
-                                       // case the type parameters are not used in the methods
-                                       // (and to preserve existing behavior in this case).
-                                       if sameOrig {
-                                               xargs := x.TypeArgs().list()
-                                               yargs := y.TypeArgs().list()
-                                               assert(len(xargs) == len(yargs))
-                                               for i, xarg := range xargs {
-                                                       if !u.nify(xarg, yargs[i], p) {
-                                                               return false
-                                                       }
-                                               }
-                                               return true
-                                       }
-                                       return u.nify(xu, yu, p)
-                               }
-                               // We don't have two interfaces. If we have one, make sure it's in xi.
-                               if yi != nil {
-                                       xi = yi
-                                       y = x
-                               }
-                               // If xi is an interface, use interface unification.
-                               if xi != nil {
+                               xi, _ := x.under().(*Interface)
+                               yi, _ := y.under().(*Interface)
+                               // If one or both of x and y are interfaces, use interface unification.
+                               switch {
+                               case xi != nil && yi != nil:
+                                       return u.nify(xi, yi, p)
+                               case xi != nil:
                                        return u.nify(xi, y, p)
+                               case yi != nil:
+                                       return u.nify(x, yi, p)
                                }
                                // In all other cases, the type arguments and origins must match.
                        }
@@ -671,7 +649,7 @@ func (u *unifier) nify(x, y Type, p *ifacePair) (result bool) {
                                        return false
                                }
                        }
-                       return sameOrig
+                       return indenticalOrigin(x, y)
                }
 
        case *TypeParam:
index d309abf058d3e6c3083fa227015463f0b9ff3f6e..da3731195be037b5722509c9ce13cf9dc0a19efb 100644 (file)
@@ -22,7 +22,7 @@ func _[A any](s S /* ERROR "got 1 arguments but 2 type parameters" */ [A]) {
 // another test case from the issue
 
 func _() {
-       X(Interface[*F /* ERROR "got 1 arguments but 2 type parameters" */ [string]](Impl{}))
+       X /* ERROR "cannot infer Q" */ (Interface[*F /* ERROR "got 1 arguments but 2 type parameters" */ [string]](Impl{}))
 }
 
 func X[Q Qer](fs Interface[Q]) {
diff --git a/src/internal/types/testdata/fixedbugs/issue60377.go b/src/internal/types/testdata/fixedbugs/issue60377.go
new file mode 100644 (file)
index 0000000..be37c51
--- /dev/null
@@ -0,0 +1,94 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package p
+
+// The type parameter P is not used in interface T1.
+// T1 is a defined parameterized interface type which
+// can be assigned to any other interface with the same
+// methods. We cannot infer a type argument in this case
+// because any type would do.
+
+type T1[P any] interface{ m() }
+
+func g[P any](T1[P]) {}
+
+func _() {
+       var x T1[int]
+       g /* ERROR "cannot infer P" */ (x)
+       g[int](x)    // int is ok for P
+       g[string](x) // string is also ok for P!
+}
+
+// This is analogous to the above example,
+// but uses two interface types of the same structure.
+
+type T2[P any] interface{ m() }
+
+func _() {
+       var x T2[int]
+       g /* ERROR "cannot infer P" */ (x)
+       g[int](x)    // int is ok for P
+       g[string](x) // string is also ok for P!
+}
+
+// Analogous to the T2 example but using an unparameterized interface T3.
+
+type T3 interface{ m() }
+
+func _() {
+       var x T3
+       g /* ERROR "cannot infer P" */ (x)
+       g[int](x)    // int is ok for P
+       g[string](x) // string is also ok for P!
+}
+
+// The type parameter P is not used in struct S.
+// S is a defined parameterized (non-interface) type which can only
+// be assigned to another type S with the same type argument.
+// Therefore we can infer a type argument in this case.
+
+type S[P any] struct{}
+
+func g4[P any](S[P]) {}
+
+func _() {
+       var x S[int]
+       g4(x)      // we can infer int for P
+       g4[int](x) // int is the correct type argument
+       g4[string](x /* ERROR "cannot use x (variable of type S[int]) as S[string] value in argument to g4[string]" */)
+}
+
+// This is similar to the first example but here T1 is a component
+// of a func type. In this case we should be able to infer a type
+// argument for P because component types must be identical even
+// in the case of interfaces.
+// This is a short-coming of type inference at the moment, but it
+// is better to not be able to infer a type here (we can always
+// supply one), than to infer the wrong type in other cases (see
+// below). Finally, if we decide to accept go.dev/issues/8082,
+// the behavior here is correct.
+
+func g5[P any](func(T1[P])) {}
+
+func _() {
+       var f func(T1[int])
+       g5 /* ERROR "cannot infer P" */ (f)
+       g5[int](f)
+       g5[string](f /* ERROR "cannot use f (variable of type func(T1[int])) as func(T1[string]) value in argument to g5[string]" */)
+}
+
+// This example would fail if we were to infer the type argument int for P
+// exactly because any type argument would be ok for the first argument.
+// Choosing the wrong type would cause the second argument to not match.
+
+type T[P any] interface{}
+
+func g6[P any](T[P], P) {}
+
+func _() {
+       var x T[int]
+       g6(x, 1.2)
+       g6(x, "")
+}
index 2b752fe16156ae885df501792a7ccdea02c06400..4ad88d4753a27a7d3772fd1566ec7f839d070462 100644 (file)
@@ -38,5 +38,5 @@ func use[T any](v Value[T]) {
 
 func main() {
        tr := &taskResult{&taskDefinition{}}
-       use(Value[string](tr))
+       use[string](Value[string](tr))
 }
index 4d959888544daf7696f1062e63bc64fdc9b53ab0..e6d7f0f9f3a9f0698202aa634f21a5b32e84809e 100644 (file)
@@ -14,5 +14,5 @@ func use[T any](v Value[T]) {
 }
 
 func main() {
-       use(Value[int](1))
+       use[int](Value[int](1))
 }