Skip to content

Commit 969c3ba

Browse files
griesemergopherbot
authored andcommitted
go/types, types2: use new type inference algorithm exclusively
The primary change is that type inference now always reports an error if a unification step fails (rather than ignoring that case, see infer2.go). This brings the implementation closely to the description in #58650; but the implementation is more direct by always maintaining a simple (type parameter => type) mapping. To make this work, there are two small but subtle changes in the unifier: 1) When deciding whether to proceed with the underlying type of a defined type, we also use the underlying type if the other type is a basic type (switch from !hasName(x) to isTypeLit(x) in unifier.go). This makes the case in issue #53650 work out. See the comment in the code for a detailed explanation of this change. 2) When we unify against an unbound type parameter, we always proceed with its core type (if any). Again, see the comment in the code for a detailed explanation of this change. The remaining changes are comment and test adjustments. Because the new logic now results in failing type inference where it succeeded before or vice versa, and then instatiation or parameter passing failed, a handful of error messages changed. As desired, we still have the same number of errors for the same programs. Also, because type inference now produces different results, we cannot easily compare against infer1 anymore (also infer1 won't work correctly anymore due to the changes in the unifier). This comparison (together with infer1) is now disabled. Because some errors and their positions have changed, we need a slightly larger error position tolerance for types2 (which produces less accurate error positions than go/types). Hence the change in types2/check_test.go. Finally, because type inference is now slightly more relaxed, issue #51139 doesn't produce a type unification failure anymore for a (previously correctly) inferred type argument. Fixes #51139. Change-Id: Id796eea42f1b706a248843ad855d9d429d077bd1 Reviewed-on: https://go-review.googlesource.com/c/go/+/470916 Reviewed-by: Robert Griesemer <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Robert Findley <[email protected]> Run-TryBot: Robert Griesemer <[email protected]> Auto-Submit: Robert Griesemer <[email protected]>
1 parent 37a2004 commit 969c3ba

File tree

11 files changed

+192
-98
lines changed

11 files changed

+192
-98
lines changed

src/cmd/compile/internal/types2/check_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@ func TestCheck(t *testing.T) {
326326
}
327327
func TestSpec(t *testing.T) { testDirFiles(t, "../../../../internal/types/testdata/spec", 0, false) }
328328
func TestExamples(t *testing.T) {
329-
testDirFiles(t, "../../../../internal/types/testdata/examples", 50, false)
329+
testDirFiles(t, "../../../../internal/types/testdata/examples", 60, false)
330330
} // TODO(gri) narrow column tolerance
331331
func TestFixedbugs(t *testing.T) {
332332
testDirFiles(t, "../../../../internal/types/testdata/fixedbugs", 100, false)

src/cmd/compile/internal/types2/infer2.go

+12-26
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313

1414
// If compareWithInfer1, infer2 results must match infer1 results.
1515
// Disable before releasing Go 1.21.
16-
const compareWithInfer1 = true
16+
const compareWithInfer1 = false
1717

1818
// infer attempts to infer the complete set of type arguments for generic function instantiation/call
1919
// based on the given type parameters tparams, type arguments targs, function parameters params, and
@@ -76,6 +76,10 @@ func (check *Checker) infer2(pos syntax.Pos, tparams []*TypeParam, targs []Type,
7676
// Rename type parameters to avoid conflicts in recursive instantiation scenarios.
7777
tparams, params = check.renameTParams(pos, tparams, params)
7878

79+
if traceInference {
80+
check.dump("after rename: %s%s ➞ %s\n", tparams, params, targs)
81+
}
82+
7983
// Make sure we have a "full" list of type arguments, some of which may
8084
// be nil (unknown). Make a copy so as to not clobber the incoming slice.
8185
if len(targs) < n {
@@ -222,39 +226,21 @@ func (check *Checker) infer2(pos syntax.Pos, tparams []*TypeParam, targs []Type,
222226
// In this case, if the core type has a tilde, the type argument's underlying
223227
// type must match the core type, otherwise the type argument and the core type
224228
// must match.
225-
// If tx is an external type parameter, don't consider its underlying type
226-
// (which is an interface). Core type unification will attempt to unify against
227-
// core.typ.
228-
// Note also that even with inexact unification we cannot leave away the under
229-
// call here because it's possible that both tx and core.typ are named types,
230-
// with under(tx) being a (named) basic type matching core.typ. Such cases do
231-
// not match with inexact unification.
229+
// If tx is an (external) type parameter, don't consider its underlying type
230+
// (which is an interface). The unifier will use the type parameter's core
231+
// type automatically.
232232
if core.tilde && !isTypeParam(tx) {
233233
tx = under(tx)
234234
}
235-
// Unification may fail because it operates with limited information (core type),
236-
// even if a given type argument satisfies the corresponding type constraint.
237-
// For instance, given [P T1|T2, ...] where the type argument for P is (named
238-
// type) T1, and T1 and T2 have the same built-in (named) type T0 as underlying
239-
// type, the core type will be the named type T0, which doesn't match T1.
240-
// Yet the instantiation of P with T1 is clearly valid (see go.dev/issue/53650).
241-
// Reporting an error if unification fails would be incorrect in this case.
242-
// On the other hand, it is safe to ignore failing unification during constraint
243-
// type inference because if the failure is true, an error will be reported when
244-
// checking instantiation.
245-
// TODO(gri) we should be able to report an error here and fix the issue in
246-
// unification
247-
u.unify(tx, core.typ)
248-
235+
if !u.unify(tx, core.typ) {
236+
check.errorf(pos, CannotInferTypeArgs, "%s does not match %s", tpar, core.typ)
237+
return nil
238+
}
249239
case single && !core.tilde:
250240
// The corresponding type argument tx is unknown and there's a single
251241
// specific type and no tilde.
252242
// In this case the type argument must be that single type; set it.
253243
u.set(tpar, core.typ)
254-
255-
default:
256-
// Unification is not possible and no progress was made.
257-
continue
258244
}
259245
} else {
260246
if traceInference {

src/cmd/compile/internal/types2/unify.go

+68-20
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,31 @@
33
// license that can be found in the LICENSE file.
44

55
// This file implements type unification.
6+
//
7+
// Type unification attempts to make two types x and y structurally
8+
// identical by determining the types for a given list of (bound)
9+
// type parameters which may occur within x and y. If x and y are
10+
// are structurally different (say []T vs chan T), or conflicting
11+
// types are determined for type parameters, unification fails.
12+
// If unification succeeds, as a side-effect, the types of the
13+
// bound type parameters may be determined.
14+
//
15+
// Unification typically requires multiple calls u.unify(x, y) to
16+
// a given unifier u, with various combinations of types x and y.
17+
// In each call, additional type parameter types may be determined
18+
// as a side effect. If a call fails (returns false), unification
19+
// fails.
20+
//
21+
// In the unification context, structural identity ignores the
22+
// difference between a defined type and its underlying type.
23+
// It also ignores the difference between an (external, unbound)
24+
// type parameter and its core type.
25+
// If two types are not structurally identical, they cannot be Go
26+
// identical types. On the other hand, if they are structurally
27+
// identical, they may be Go identical or at least assignable, or
28+
// they may be in the type set of a constraint.
29+
// Whether they indeed are identical or assignable is determined
30+
// upon instantiation and function argument passing.
631

732
package types2
833

@@ -239,7 +264,7 @@ func (u *unifier) nify(x, y Type, p *ifacePair) (result bool) {
239264

240265
// Unification is symmetric, so we can swap the operands.
241266
// Ensure that if we have at least one
242-
// - defined type, make sure sure one is in y
267+
// - defined type, make sure one is in y
243268
// - type parameter recorded with u, make sure one is in x
244269
if _, ok := x.(*Named); ok || u.asTypeParam(y) != nil {
245270
if traceInference {
@@ -248,13 +273,24 @@ func (u *unifier) nify(x, y Type, p *ifacePair) (result bool) {
248273
x, y = y, x
249274
}
250275

251-
// If exact unification is known to fail because we attempt to
252-
// match a defined type against an unnamed type literal, consider
253-
// the underlying type of the defined type.
276+
// Unification will fail if we match a defined type against a type literal.
277+
// Per the (spec) assignment rules, assignments of values to variables with
278+
// the same type structure are permitted as long as at least one of them
279+
// is not a defined type. To accomodate for that possibility, we continue
280+
// unification with the underlying type of a defined type if the other type
281+
// is a type literal.
282+
// We also continue if the other type is a basic type because basic types
283+
// are valid underlying types and may appear as core types of type constraints.
284+
// If we exclude them, inferred defined types for type parameters may not
285+
// match against the core types of their constraints (even though they might
286+
// correctly match against some of the types in the constraint's type set).
287+
// Finally, if unification (incorrectly) succeeds by matching the underlying
288+
// type of a defined type against a basic type (because we include basic types
289+
// as type literals here), and if that leads to an incorrectly inferred type,
290+
// we will fail at function instantiation or argument assignment time.
291+
//
254292
// If we have at least one defined type, there is one in y.
255-
// (We use !hasName to exclude any type with a name, including
256-
// basic types and type parameters; the rest are unamed types.)
257-
if ny, _ := y.(*Named); ny != nil && !hasName(x) {
293+
if ny, _ := y.(*Named); ny != nil && isTypeLit(x) {
258294
if traceInference {
259295
u.tracef("%s ≡ under %s", x, ny)
260296
}
@@ -266,6 +302,10 @@ func (u *unifier) nify(x, y Type, p *ifacePair) (result bool) {
266302

267303
// Cases where at least one of x or y is a type parameter recorded with u.
268304
// If we have at least one type parameter, there is one in x.
305+
// If we have exactly one type parameter, because it is in x,
306+
// isTypeLit(x) is false and y was not changed above. In other
307+
// words, if y was a defined type, it is still a defined type
308+
// (relevant for the logic below).
269309
switch px, py := u.asTypeParam(x), u.asTypeParam(y); {
270310
case px != nil && py != nil:
271311
// both x and y are type parameters
@@ -296,8 +336,19 @@ func (u *unifier) nify(x, y Type, p *ifacePair) (result bool) {
296336
return true
297337
}
298338

299-
// If we get here and x or y is a type parameter, they are type parameters
300-
// from outside our declaration list. Try to unify their core types, if any
339+
// If we get here and x or y is a type parameter, they are unbound
340+
// (not recorded with the unifier).
341+
// By definition, a valid type argument must be in the type set of
342+
// the respective type constraint. Therefore, the type argument's
343+
// underlying type must be in the set of underlying types of that
344+
// constraint. If there is a single such underlying type, it's the
345+
// constraint's core type. It must match the type argument's under-
346+
// lying type, irrespective of whether the actual type argument,
347+
// which may be a defined type, is actually in the type set (that
348+
// will be determined at instantiation time).
349+
// Thus, if we have the core type of an unbound type parameter,
350+
// we know the structure of the possible types satisfying such
351+
// parameters. Use that core type for further unification
301352
// (see go.dev/issue/50755 for a test case).
302353
if enableCoreTypeUnification {
303354
// swap x and y as needed
@@ -308,18 +359,15 @@ func (u *unifier) nify(x, y Type, p *ifacePair) (result bool) {
308359
}
309360
x, y = y, x
310361
}
311-
if isTypeParam(x) && !hasName(y) {
362+
if isTypeParam(x) {
312363
// When considering the type parameter for unification
313-
// we look at the adjusted core term (adjusted core type
314-
// with tilde information).
315-
// If the adjusted core type is a named type N; the
316-
// corresponding core type is under(N).
317-
// Since y doesn't have a name, unification will end up
318-
// comparing under(N) to y, so we can just use the core
319-
// type instead. And we can ignore the tilde because we
320-
// already look at the underlying types on both sides
321-
// and we have known types on both sides.
322-
// Optimization.
364+
// we look at the core type.
365+
// Because the core type is always an underlying type,
366+
// unification will take care of matching against a
367+
// defined or literal type automatically.
368+
// If y is also an unbound type parameter, we will end
369+
// up here again with x and y swapped, so we don't
370+
// need to take care of that case separately.
323371
if cx := coreType(x); cx != nil {
324372
if traceInference {
325373
u.tracef("core %s ≡ %s", x, y)

src/go/types/infer2.go

+12-26
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)