Skip to content

Commit

Permalink
go/types, types2: always rename type parameters during inference
Browse files Browse the repository at this point in the history
Type inference uses a trick of "renaming" type parameters in the type
parameter list to avoid cycles during unification. This separates the
identity of type parameters from type arguments. When this trick was
introduced in CL 385494, we restricted its application to scenarios
where inference is truly self-recursive: the type parameter list being
inferred was the same as the type parameter list of the outer function
declaration. Unfortunately, the heuristic used to determine
self-recursiveness was flawed: type-checking function literals clobbers
the type-checker environment, losing information about the outer
signature.

We could fix this by introducing yet more state into the type-checker
(e.g. a 'declSig' field that would hold the signature of the active
function declaration), but it is simpler to just avoid this optimization
and always perform type parameter renaming. We can always optimize
later.

This CL removes the check for true self-recursion, always performing the
renaming.

Fixes golang#57155

Change-Id: I34c7617005c1f0ccfe2192da0e5ed104be6b92c9
Reviewed-on: https://go-review.googlesource.com/c/go/+/456236
Run-TryBot: Robert Findley <[email protected]>
Reviewed-by: Robert Griesemer <[email protected]>
TryBot-Result: Gopher Robot <[email protected]>
  • Loading branch information
findleyr committed Dec 8, 2022
1 parent 8247b9f commit e738a2f
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 52 deletions.
40 changes: 14 additions & 26 deletions src/cmd/compile/internal/types2/infer.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,34 +89,22 @@ func (check *Checker) infer(pos syntax.Pos, tparams []*TypeParam, targs []Type,
// f(p)
// }
//
// We can turn the first example into the second example by renaming type
// parameters in the original signature to give them a new identity. As an
// optimization, we do this only for self-recursive calls.

// We can detect if we are in a self-recursive call by comparing the
// identity of the first type parameter in the current function with the
// first type parameter in tparams. This works because type parameters are
// unique to their type parameter list.
selfRecursive := check.sig != nil && check.sig.tparams.Len() > 0 && tparams[0] == check.sig.tparams.At(0)

if selfRecursive {
// In self-recursive inference, rename the type parameters with new type
// parameters that are the same but for their pointer identity.
tparams2 := make([]*TypeParam, len(tparams))
for i, tparam := range tparams {
tname := NewTypeName(tparam.Obj().Pos(), tparam.Obj().Pkg(), tparam.Obj().Name(), nil)
tparams2[i] = NewTypeParam(tname, nil)
tparams2[i].index = tparam.index // == i
}

renameMap := makeRenameMap(tparams, tparams2)
for i, tparam := range tparams {
tparams2[i].bound = check.subst(pos, tparam.bound, renameMap, nil, check.context())
}
// We turn the first example into the second example by renaming type
// parameters in the original signature to give them a new identity.
tparams2 := make([]*TypeParam, len(tparams))
for i, tparam := range tparams {
tname := NewTypeName(tparam.Obj().Pos(), tparam.Obj().Pkg(), tparam.Obj().Name(), nil)
tparams2[i] = NewTypeParam(tname, nil)
tparams2[i].index = tparam.index // == i
}

tparams = tparams2
params = check.subst(pos, params, renameMap, nil, check.context()).(*Tuple)
renameMap := makeRenameMap(tparams, tparams2)
for i, tparam := range tparams {
tparams2[i].bound = check.subst(pos, tparam.bound, renameMap, nil, check.context())
}

tparams = tparams2
params = check.subst(pos, params, renameMap, nil, check.context()).(*Tuple)
}

// If we have more than 2 arguments, we may have arguments with named and unnamed types.
Expand Down
40 changes: 14 additions & 26 deletions src/go/types/infer.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,34 +89,22 @@ func (check *Checker) infer(posn positioner, tparams []*TypeParam, targs []Type,
// f(p)
// }
//
// We can turn the first example into the second example by renaming type
// parameters in the original signature to give them a new identity. As an
// optimization, we do this only for self-recursive calls.

// We can detect if we are in a self-recursive call by comparing the
// identity of the first type parameter in the current function with the
// first type parameter in tparams. This works because type parameters are
// unique to their type parameter list.
selfRecursive := check.sig != nil && check.sig.tparams.Len() > 0 && tparams[0] == check.sig.tparams.At(0)

if selfRecursive {
// In self-recursive inference, rename the type parameters with new type
// parameters that are the same but for their pointer identity.
tparams2 := make([]*TypeParam, len(tparams))
for i, tparam := range tparams {
tname := NewTypeName(tparam.Obj().Pos(), tparam.Obj().Pkg(), tparam.Obj().Name(), nil)
tparams2[i] = NewTypeParam(tname, nil)
tparams2[i].index = tparam.index // == i
}

renameMap := makeRenameMap(tparams, tparams2)
for i, tparam := range tparams {
tparams2[i].bound = check.subst(posn.Pos(), tparam.bound, renameMap, nil, check.context())
}
// We turn the first example into the second example by renaming type
// parameters in the original signature to give them a new identity.
tparams2 := make([]*TypeParam, len(tparams))
for i, tparam := range tparams {
tname := NewTypeName(tparam.Obj().Pos(), tparam.Obj().Pkg(), tparam.Obj().Name(), nil)
tparams2[i] = NewTypeParam(tname, nil)
tparams2[i].index = tparam.index // == i
}

tparams = tparams2
params = check.subst(posn.Pos(), params, renameMap, nil, check.context()).(*Tuple)
renameMap := makeRenameMap(tparams, tparams2)
for i, tparam := range tparams {
tparams2[i].bound = check.subst(posn.Pos(), tparam.bound, renameMap, nil, check.context())
}

tparams = tparams2
params = check.subst(posn.Pos(), params, renameMap, nil, check.context()).(*Tuple)
}

// If we have more than 2 arguments, we may have arguments with named and unnamed types.
Expand Down
14 changes: 14 additions & 0 deletions src/internal/types/testdata/fixedbugs/issue57155.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright 2022 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

func f[P *Q, Q any](p P, q Q) {
func() {
_ = f[P]
f(p, q)
f[P](p, q)
f[P, Q](p, q)
}()
}

0 comments on commit e738a2f

Please sign in to comment.