-
Notifications
You must be signed in to change notification settings - Fork 18k
go/types: cannot infer type arguments in self-recursive call (stack overflow in unification) #51158
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Likely a duplicate of #48656. |
Change https://go.dev/cl/385494 mentions this issue: |
Just took a look at this. It looks like a fundamental bug that the unification algorithm can't distinguish uninferred type parameters from type parameters occurring in explicit type arguments or function arguments, when they have the same pointer identity. We can fix in a number of ways. One approach is to create a synthetic copy of the type parameter list and substitute in explicit targs and function arguments. This ensures that we properly differentiate these occurrences from uninferred type parameters, and then we can invert the substitution in the resulting inferred type arguments. https://go.dev/cl/385494 is my quick-and-dirty prototype of this, and seems to work as expected. It also resolved some other tests that had unnecessary errors. |
Yes, you prototyped this a while back when one of these bugs showed up for the first time. I believe this is the right approach: when we call a function from itself, the type parameters that are supplied are getting mixed up with the type parameters for which we want to infer types. I tried to convince myself that this is correct and this is the argument I came up with: If we would call a different function with the same signature it would all work. For instance, the above example can be changed manually to: func f_[M map[K]int, K comparable](m M) {
}
func f[M map[K]int, K comparable](m M) {
f_(m)
} by calling a synthetic function And maybe we can even have an assertion that a type parameter should never infer itself as an argument. After all, this is (one of) the reasons we get endless recursion. In this part of case i >= 0:
// x is a type parameter, y is not
if tx := u.x.at(i); tx != nil {
return u.nifyEq(tx, y, p)
}
// otherwise, infer type from y
u.x.set(i, y)
return true if I will review your CL tomorrow. |
Right, in https://go.dev/cl/352510. I recalled that CL but thought that it was slightly different, because we figured out a different solution at the time. Looking at it now it is rather the same... down to my quick-and-dirty caveat 🤦. I suppose I am predictable. We need to differentiate type parameters occurring in the declaration list from type parameters that are passed in at the instantiation site. There are likely other ways to do this, but I'm skeptical about doing it inside the unifier. As you point out, in this case "if
On first principles it seems necessary to introduce a way to differentiate occurrences of type parameters from inside and outside the type declaration.
The way I think about it, type parameters are really two things: they are constraints on a parameter space of types, and they are placeholders for the types that are substituted for those parameters. The problem is that unification is conflating these two things, when it needs to keep them separate. Imagine an analogous example with subsets of a plane:
We're effectively trying to check whether We could alternatively do the substitution in the declaration type parameter list, which could be memoized, but I suspect it is more efficient to substitute in targs and args as in the common case they will not contain references to the type parameters being unified. I will clean up my CL. |
While going through and closing tabs, I noticed this and thought I'd close the loop: The primary advantage of substituting in args rather than params was that most of the time it would be a no-op. However, @griesemer had a great suggestion for how to cheaply detect self-recursive calls, and so we were able to substitute in parameters only in the case of self-recursion. This significantly simplified the complexity of the substitution code. |
This is a follow-up on #50755. The following code:
leads to an internal stack overflow during unification and then type checking fails with error:
Start of the inference process:
Marking for 1.18 in case the fix is easy. Ok to move to 1.19.
cc: @findleyr
The text was updated successfully, but these errors were encountered: