1
0
mirror of https://github.com/golang/go synced 2024-11-17 02:54:45 -07:00

go/types, types2: do not run CTI before FTI

Until now, CTI (constraint type inference) was run before
FTI (function type inference). This lead to situations
where CTI infered a type that is missing necessary methods
even though a function argument of correct type was given.
This can happen when constraint type inference produces a
inferred type that is the structural type of multiple types,
which then is an underlying type, possibly without methods.

This CL removes the initial CTI step; it is only applied
after FTI with type arguments is run, and again after FTI
with untyped arguments is run.

Various comments are adjusted to reflect the new reality.

Fixes #50426.

Change-Id: I700ae6e762d7aa00d742943a2880f1a1db33c2b8
Reviewed-on: https://go-review.googlesource.com/c/go/+/377594
Trust: Robert Griesemer <gri@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
This commit is contained in:
Robert Griesemer 2022-01-10 17:29:21 -08:00
parent 3df4472201
commit 3d3f5d912b
4 changed files with 112 additions and 44 deletions

View File

@ -19,15 +19,17 @@ const useConstraintTypeInference = true
// function arguments args, if any. There must be at least one type parameter, no more type arguments
// than type parameters, and params and args must match in number (incl. zero).
// If successful, infer returns the complete list of type arguments, one for each type parameter.
// Otherwise the result is nil and appropriate errors will be reported unless report is set to false.
// Otherwise the result is nil and appropriate errors will be reported.
//
// Inference proceeds in 3 steps:
// Inference proceeds as follows:
//
// 1) Start with given type arguments.
// 2) Infer type arguments from typed function arguments.
// 3) Infer type arguments from untyped function arguments.
// Starting with given type arguments
// 1) apply FTI (function type inference) with typed arguments,
// 2) apply CTI (constraint type inference),
// 3) apply FTI with untyped function arguments,
// 4) apply CTI.
//
// Constraint type inference is used after each step to expand the set of type arguments.
// The process stops as soon as all type arguments are known or an error occurs.
func (check *Checker) infer(pos syntax.Pos, tparams []*TypeParam, targs []Type, params *Tuple, args []*operand) (result []Type) {
if debug {
defer func() {
@ -46,7 +48,6 @@ func (check *Checker) infer(pos syntax.Pos, tparams []*TypeParam, targs []Type,
// Function parameters and arguments must match in number.
assert(params.Len() == len(args))
// --- 0 ---
// If we already have all type arguments, we're done.
if len(targs) == n {
return targs
@ -54,25 +55,13 @@ func (check *Checker) infer(pos syntax.Pos, tparams []*TypeParam, targs []Type,
// len(targs) < n
// --- 1 ---
// Explicitly provided type arguments take precedence over any inferred types;
// and types inferred via constraint type inference take precedence over types
// inferred from function arguments.
// If we have type arguments, see how far we get with constraint type inference.
if len(targs) > 0 && useConstraintTypeInference {
var index int
targs, index = check.inferB(pos, tparams, targs)
if targs == nil || index < 0 {
return targs
}
}
// Continue with the type arguments we have now. Avoid matching generic
// Continue with the type arguments we have. Avoid matching generic
// parameters that already have type arguments against function arguments:
// It may fail because matching uses type identity while parameter passing
// uses assignment rules. Instantiate the parameter list with the type
// arguments we have, and continue with that parameter list.
// First, make sure we have a "full" list of type arguments, so of which
// First, make sure we have a "full" list of type arguments, some of which
// may be nil (unknown).
if len(targs) < n {
targs2 := make([]Type, n)
@ -90,7 +79,6 @@ func (check *Checker) infer(pos syntax.Pos, tparams []*TypeParam, targs []Type,
params = check.subst(nopos, params, smap, nil).(*Tuple)
}
// --- 2 ---
// Unify parameter and argument types for generic parameters with typed arguments
// and collect the indices of generic parameters with untyped arguments.
// Terminology: generic parameter = function parameter with a type-parameterized type
@ -167,6 +155,7 @@ func (check *Checker) infer(pos syntax.Pos, tparams []*TypeParam, targs []Type,
return targs
}
// --- 2 ---
// See how far we get with constraint type inference.
// Note that even if we don't have any type arguments, constraint type inference
// may produce results for constraints that explicitly specify a type.
@ -207,6 +196,7 @@ func (check *Checker) infer(pos syntax.Pos, tparams []*TypeParam, targs []Type,
return targs
}
// --- 4 ---
// Again, follow up with constraint type inference.
if useConstraintTypeInference {
targs, index = check.inferB(pos, tparams, targs)

View File

@ -0,0 +1,44 @@
// 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
type A1 [2]uint64
type A2 [2]uint64
func (a A1) m() A1 { return a }
func (a A2) m() A2 { return a }
func f[B any, T interface {
A1 | A2
m() T
}](v T) {
}
func _() {
var v A2
// Use function type inference to infer type A2 for T.
// Don't use constraint type inference before function
// type inference for typed arguments, otherwise it would
// infer type [2]uint64 for T which doesn't have method m
// (was the bug).
f[int](v)
}
// Keep using constraint type inference before function type
// inference for untyped arguments so we infer type float64
// for E below, and not int (which would not work).
func g[S ~[]E, E any](S, E) {}
func _() {
var s []float64
g[[]float64](s, 0)
}
// Keep using constraint type inference after function
// type inference for untyped arguments so we infer
// missing type arguments for which we only have the
// untyped arguments as starting point.
func h[E any, R []E](v E) R { return R{v} }
func _() []int { return h(0) }

View File

@ -18,15 +18,17 @@ import (
// function arguments args, if any. There must be at least one type parameter, no more type arguments
// than type parameters, and params and args must match in number (incl. zero).
// If successful, infer returns the complete list of type arguments, one for each type parameter.
// Otherwise the result is nil and appropriate errors will be reported unless report is set to false.
// Otherwise the result is nil and appropriate errors will be reported.
//
// Inference proceeds in 3 steps:
// Inference proceeds as follows:
//
// 1) Start with given type arguments.
// 2) Infer type arguments from typed function arguments.
// 3) Infer type arguments from untyped function arguments.
// Starting with given type arguments
// 1) apply FTI (function type inference) with typed arguments,
// 2) apply CTI (constraint type inference),
// 3) apply FTI with untyped function arguments,
// 4) apply CTI.
//
// Constraint type inference is used after each step to expand the set of type arguments.
// The process stops as soon as all type arguments are known or an error occurs.
func (check *Checker) infer(posn positioner, tparams []*TypeParam, targs []Type, params *Tuple, args []*operand) (result []Type) {
if debug {
defer func() {
@ -45,7 +47,6 @@ func (check *Checker) infer(posn positioner, tparams []*TypeParam, targs []Type,
// Function parameters and arguments must match in number.
assert(params.Len() == len(args))
// --- 0 ---
// If we already have all type arguments, we're done.
if len(targs) == n {
return targs
@ -53,25 +54,13 @@ func (check *Checker) infer(posn positioner, tparams []*TypeParam, targs []Type,
// len(targs) < n
// --- 1 ---
// Explicitly provided type arguments take precedence over any inferred types;
// and types inferred via constraint type inference take precedence over types
// inferred from function arguments.
// If we have type arguments, see how far we get with constraint type inference.
if len(targs) > 0 {
var index int
targs, index = check.inferB(posn, tparams, targs)
if targs == nil || index < 0 {
return targs
}
}
// Continue with the type arguments we have now. Avoid matching generic
// Continue with the type arguments we have. Avoid matching generic
// parameters that already have type arguments against function arguments:
// It may fail because matching uses type identity while parameter passing
// uses assignment rules. Instantiate the parameter list with the type
// arguments we have, and continue with that parameter list.
// First, make sure we have a "full" list of type arguments, so of which
// First, make sure we have a "full" list of type arguments, some of which
// may be nil (unknown).
if len(targs) < n {
targs2 := make([]Type, n)
@ -89,7 +78,6 @@ func (check *Checker) infer(posn positioner, tparams []*TypeParam, targs []Type,
params = check.subst(token.NoPos, params, smap, nil).(*Tuple)
}
// --- 2 ---
// Unify parameter and argument types for generic parameters with typed arguments
// and collect the indices of generic parameters with untyped arguments.
// Terminology: generic parameter = function parameter with a type-parameterized type
@ -171,6 +159,7 @@ func (check *Checker) infer(posn positioner, tparams []*TypeParam, targs []Type,
return targs
}
// --- 2 ---
// See how far we get with constraint type inference.
// Note that even if we don't have any type arguments, constraint type inference
// may produce results for constraints that explicitly specify a type.
@ -209,6 +198,7 @@ func (check *Checker) infer(posn positioner, tparams []*TypeParam, targs []Type,
return targs
}
// --- 4 ---
// Again, follow up with constraint type inference.
targs, index = check.inferB(posn, tparams, targs)
if targs == nil || index < 0 {

View File

@ -0,0 +1,44 @@
// 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
type A1 [2]uint64
type A2 [2]uint64
func (a A1) m() A1 { return a }
func (a A2) m() A2 { return a }
func f[B any, T interface {
A1 | A2
m() T
}](v T) {
}
func _() {
var v A2
// Use function type inference to infer type A2 for T.
// Don't use constraint type inference before function
// type inference for typed arguments, otherwise it would
// infer type [2]uint64 for T which doesn't have method m
// (was the bug).
f[int](v)
}
// Keep using constraint type inference before function type
// inference for untyped arguments so we infer type float64
// for E below, and not int (which would not work).
func g[S ~[]E, E any](S, E) {}
func _() {
var s []float64
g[[]float64](s, 0)
}
// Keep using constraint type inference after function
// type inference for untyped arguments so we infer
// missing type arguments for which we only have the
// untyped arguments as starting point.
func h[E any, R []E](v E) R { return R{v} }
func _() []int { return h(0) }