go/types, types2: disable field accesses through type parameters

This is a feature that is not understood well enough and may have
subtle repercussions impacting future changes. Disable for Go 1.18.

The actual change is trivial: disable a branch through a flag.
The remaining changes are adjustments to tests.

Fixes #51576.

Change-Id: Ib77b038b846711a808315a8889b3904e72367bce
Reviewed-on: https://go-review.googlesource.com/c/go/+/391135
Trust: Robert Griesemer <gri@golang.org>
Run-TryBot: Robert Griesemer <gri@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
This commit is contained in:
Robert Griesemer 2022-03-09 10:01:24 -08:00
parent 7026eeb8cf
commit b8248fab89
13 changed files with 177 additions and 74 deletions

View file

@ -70,7 +70,8 @@ func LookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (o
// see if there is a matching field (but not a method, those need to be declared // see if there is a matching field (but not a method, those need to be declared
// explicitly in the constraint). If the constraint is a named pointer type (see // explicitly in the constraint). If the constraint is a named pointer type (see
// above), we are ok here because only fields are accepted as results. // above), we are ok here because only fields are accepted as results.
if obj == nil && isTypeParam(T) { const enableTParamFieldLookup = false // see issue #51576
if enableTParamFieldLookup && obj == nil && isTypeParam(T) {
if t := coreType(T); t != nil { if t := coreType(T); t != nil {
obj, index, indirect = lookupFieldOrMethod(t, addressable, pkg, name, false) obj, index, indirect = lookupFieldOrMethod(t, addressable, pkg, name, false)
if _, ok := obj.(*Var); !ok { if _, ok := obj.(*Var); !ok {

View file

@ -2,6 +2,10 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// Field accesses through type parameters are disabled
// until we have a more thorough understanding of the
// implications on the spec. See issue #51576.
package p package p
type Sf struct { type Sf struct {
@ -9,13 +13,13 @@ type Sf struct {
} }
func f0[P Sf](p P) { func f0[P Sf](p P) {
_ = p.f _ = p.f // ERROR p\.f undefined
p.f = 0 p.f /* ERROR p\.f undefined */ = 0
} }
func f0t[P ~struct{f int}](p P) { func f0t[P ~struct{f int}](p P) {
_ = p.f _ = p.f // ERROR p\.f undefined
p.f = 0 p.f /* ERROR p\.f undefined */ = 0
} }
var _ = f0[Sf] var _ = f0[Sf]
@ -25,8 +29,8 @@ var _ = f0[Sm /* ERROR does not implement */ ]
var _ = f0t[Sm /* ERROR does not implement */ ] var _ = f0t[Sm /* ERROR does not implement */ ]
func f1[P interface{ Sf; m() }](p P) { func f1[P interface{ Sf; m() }](p P) {
_ = p.f _ = p.f // ERROR p\.f undefined
p.f = 0 p.f /* ERROR p\.f undefined */ = 0
p.m() p.m()
} }
@ -44,8 +48,8 @@ type Sfm struct {
func (Sfm) m() {} func (Sfm) m() {}
func f2[P interface{ Sfm; m() }](p P) { func f2[P interface{ Sfm; m() }](p P) {
_ = p.f _ = p.f // ERROR p\.f undefined
p.f = 0 p.f /* ERROR p\.f undefined */ = 0
p.m() p.m()
} }
@ -56,8 +60,8 @@ var _ = f2[Sfm]
type PSfm *Sfm type PSfm *Sfm
func f3[P interface{ PSfm }](p P) { func f3[P interface{ PSfm }](p P) {
_ = p.f _ = p.f // ERROR p\.f undefined
p.f = 0 p.f /* ERROR p\.f undefined */ = 0
p.m /* ERROR type P has no field or method m */ () p.m /* ERROR type P has no field or method m */ ()
} }

View file

@ -2,6 +2,10 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// Field accesses through type parameters are disabled
// until we have a more thorough understanding of the
// implications on the spec. See issue #51576.
package p package p
// The first example from the issue. // The first example from the issue.
@ -18,9 +22,12 @@ type numericAbs[T Numeric] interface {
// AbsDifference computes the absolute value of the difference of // AbsDifference computes the absolute value of the difference of
// a and b, where the absolute value is determined by the Abs method. // a and b, where the absolute value is determined by the Abs method.
func absDifference[T numericAbs[T /* ERROR T does not implement Numeric */]](a, b T) T { func absDifference[T numericAbs[T /* ERROR T does not implement Numeric */]](a, b T) T {
// TODO: the error below should probably be positioned on the '-'. // Field accesses are not permitted for now. Keep an error so
d := a /* ERROR "invalid operation: operator - not defined" */ .Value - b.Value // we can find and fix this code once the situation changes.
return d.Abs() return a.Value // ERROR a\.Value undefined
// TODO: The error below should probably be positioned on the '-'.
// d := a /* ERROR "invalid operation: operator - not defined" */ .Value - b.Value
// return d.Abs()
} }
// The second example from the issue. // The second example from the issue.

View file

@ -70,7 +70,8 @@ func LookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (o
// see if there is a matching field (but not a method, those need to be declared // see if there is a matching field (but not a method, those need to be declared
// explicitly in the constraint). If the constraint is a named pointer type (see // explicitly in the constraint). If the constraint is a named pointer type (see
// above), we are ok here because only fields are accepted as results. // above), we are ok here because only fields are accepted as results.
if obj == nil && isTypeParam(T) { const enableTParamFieldLookup = false // see issue #51576
if enableTParamFieldLookup && obj == nil && isTypeParam(T) {
if t := coreType(T); t != nil { if t := coreType(T); t != nil {
obj, index, indirect = lookupFieldOrMethod(t, addressable, pkg, name, false) obj, index, indirect = lookupFieldOrMethod(t, addressable, pkg, name, false)
if _, ok := obj.(*Var); !ok { if _, ok := obj.(*Var); !ok {

View file

@ -2,6 +2,10 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// Field accesses through type parameters are disabled
// until we have a more thorough understanding of the
// implications on the spec. See issue #51576.
package p package p
type Sf struct { type Sf struct {
@ -9,13 +13,13 @@ type Sf struct {
} }
func f0[P Sf](p P) { func f0[P Sf](p P) {
_ = p.f _ = p.f // ERROR p\.f undefined
p.f = 0 p.f /* ERROR p\.f undefined */ = 0
} }
func f0t[P ~struct{f int}](p P) { func f0t[P ~struct{f int}](p P) {
_ = p.f _ = p.f // ERROR p\.f undefined
p.f = 0 p.f /* ERROR p\.f undefined */ = 0
} }
var _ = f0[Sf] var _ = f0[Sf]
@ -25,8 +29,8 @@ var _ = f0[Sm /* ERROR does not implement */ ]
var _ = f0t[Sm /* ERROR does not implement */ ] var _ = f0t[Sm /* ERROR does not implement */ ]
func f1[P interface{ Sf; m() }](p P) { func f1[P interface{ Sf; m() }](p P) {
_ = p.f _ = p.f // ERROR p\.f undefined
p.f = 0 p.f /* ERROR p\.f undefined */ = 0
p.m() p.m()
} }
@ -44,8 +48,8 @@ type Sfm struct {
func (Sfm) m() {} func (Sfm) m() {}
func f2[P interface{ Sfm; m() }](p P) { func f2[P interface{ Sfm; m() }](p P) {
_ = p.f _ = p.f // ERROR p\.f undefined
p.f = 0 p.f /* ERROR p\.f undefined */ = 0
p.m() p.m()
} }
@ -56,8 +60,8 @@ var _ = f2[Sfm]
type PSfm *Sfm type PSfm *Sfm
func f3[P interface{ PSfm }](p P) { func f3[P interface{ PSfm }](p P) {
_ = p.f _ = p.f // ERROR p\.f undefined
p.f = 0 p.f /* ERROR p\.f undefined */ = 0
p.m /* ERROR type P has no field or method m */ () p.m /* ERROR type P has no field or method m */ ()
} }

View file

@ -2,6 +2,10 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// Field accesses through type parameters are disabled
// until we have a more thorough understanding of the
// implications on the spec. See issue #51576.
package p package p
// The first example from the issue. // The first example from the issue.
@ -18,9 +22,12 @@ type numericAbs[T Numeric] interface {
// AbsDifference computes the absolute value of the difference of // AbsDifference computes the absolute value of the difference of
// a and b, where the absolute value is determined by the Abs method. // a and b, where the absolute value is determined by the Abs method.
func absDifference[T numericAbs[T /* ERROR T does not implement Numeric */]](a, b T) T { func absDifference[T numericAbs[T /* ERROR T does not implement Numeric */]](a, b T) T {
// TODO: the error below should probably be positioned on the '-'. // Field accesses are not permitted for now. Keep an error so
d := a /* ERROR "invalid operation: operator - not defined" */ .Value - b.Value // we can find and fix this code once the situation changes.
return d.Abs() return a.Value // ERROR a\.Value undefined
// TODO: The error below should probably be positioned on the '-'.
// d := a /* ERROR "invalid operation: operator - not defined" */ .Value - b.Value
// return d.Abs()
} }
// The second example from the issue. // The second example from the issue.

View file

@ -23,15 +23,16 @@ type Numeric interface {
// numericAbs matches a struct containing a numeric type that has an Abs method. // numericAbs matches a struct containing a numeric type that has an Abs method.
type numericAbs[T Numeric] interface { type numericAbs[T Numeric] interface {
~struct{ Value T } ~struct{ Value_ T }
Abs() T Abs() T
Value() T
} }
// absDifference computes the absolute value of the difference of // absDifference computes the absolute value of the difference of
// a and b, where the absolute value is determined by the Abs method. // a and b, where the absolute value is determined by the Abs method.
func absDifference[T Numeric, U numericAbs[T]](a, b U) T { func absDifference[T Numeric, U numericAbs[T]](a, b U) T {
d := a.Value - b.Value d := a.Value() - b.Value()
dt := U{Value: d} dt := U{Value_: d}
return dt.Abs() return dt.Abs()
} }
@ -50,20 +51,29 @@ type Complex interface {
// orderedAbs is a helper type that defines an Abs method for // orderedAbs is a helper type that defines an Abs method for
// a struct containing an ordered numeric type. // a struct containing an ordered numeric type.
type orderedAbs[T orderedNumeric] struct { type orderedAbs[T orderedNumeric] struct {
Value T Value_ T
} }
func (a orderedAbs[T]) Abs() T { func (a orderedAbs[T]) Abs() T {
if a.Value < 0 { if a.Value_ < 0 {
return -a.Value return -a.Value_
} }
return a.Value return a.Value_
}
// Field accesses through type parameters are disabled
// until we have a more thorough understanding of the
// implications on the spec. See issue #51576.
// Use accessor method instead.
func (a orderedAbs[T]) Value() T {
return a.Value_
} }
// complexAbs is a helper type that defines an Abs method for // complexAbs is a helper type that defines an Abs method for
// a struct containing a complex type. // a struct containing a complex type.
type complexAbs[T Complex] struct { type complexAbs[T Complex] struct {
Value T Value_ T
} }
func realimag(x any) (re, im float64) { func realimag(x any) (re, im float64) {
@ -82,13 +92,17 @@ func realimag(x any) (re, im float64) {
func (a complexAbs[T]) Abs() T { func (a complexAbs[T]) Abs() T {
// TODO use direct conversion instead of realimag once #50937 is fixed // TODO use direct conversion instead of realimag once #50937 is fixed
r, i := realimag(a.Value) r, i := realimag(a.Value_)
// r := float64(real(a.Value)) // r := float64(real(a.Value))
// i := float64(imag(a.Value)) // i := float64(imag(a.Value))
d := math.Sqrt(r*r + i*i) d := math.Sqrt(r*r + i*i)
return T(complex(d, 0)) return T(complex(d, 0))
} }
func (a complexAbs[T]) Value() T {
return a.Value_
}
// OrderedAbsDifference returns the absolute value of the difference // OrderedAbsDifference returns the absolute value of the difference
// between a and b, where a and b are of an ordered type. // between a and b, where a and b are of an ordered type.
func OrderedAbsDifference[T orderedNumeric](a, b T) T { func OrderedAbsDifference[T orderedNumeric](a, b T) T {

View file

@ -17,15 +17,16 @@ type Numeric interface {
// numericAbs matches a struct containing a numeric type that has an Abs method. // numericAbs matches a struct containing a numeric type that has an Abs method.
type numericAbs[T Numeric] interface { type numericAbs[T Numeric] interface {
~struct{ Value T } ~struct{ Value_ T }
Abs() T Abs() T
Value() T
} }
// absDifference computes the absolute value of the difference of // absDifference computes the absolute value of the difference of
// a and b, where the absolute value is determined by the Abs method. // a and b, where the absolute value is determined by the Abs method.
func absDifference[T Numeric, U numericAbs[T]](a, b U) T { func absDifference[T Numeric, U numericAbs[T]](a, b U) T {
d := a.Value - b.Value d := a.Value() - b.Value()
dt := U{Value: d} dt := U{Value_: d}
return dt.Abs() return dt.Abs()
} }
@ -44,20 +45,29 @@ type Complex interface {
// orderedAbs is a helper type that defines an Abs method for // orderedAbs is a helper type that defines an Abs method for
// a struct containing an ordered numeric type. // a struct containing an ordered numeric type.
type orderedAbs[T orderedNumeric] struct { type orderedAbs[T orderedNumeric] struct {
Value T Value_ T
} }
func (a orderedAbs[T]) Abs() T { func (a orderedAbs[T]) Abs() T {
if a.Value < 0 { if a.Value_ < 0 {
return -a.Value return -a.Value_
} }
return a.Value return a.Value_
}
// Field accesses through type parameters are disabled
// until we have a more thorough understanding of the
// implications on the spec. See issue #51576.
// Use accessor method instead.
func (a orderedAbs[T]) Value() T {
return a.Value_
} }
// complexAbs is a helper type that defines an Abs method for // complexAbs is a helper type that defines an Abs method for
// a struct containing a complex type. // a struct containing a complex type.
type complexAbs[T Complex] struct { type complexAbs[T Complex] struct {
Value T Value_ T
} }
func realimag(x any) (re, im float64) { func realimag(x any) (re, im float64) {
@ -76,13 +86,17 @@ func realimag(x any) (re, im float64) {
func (a complexAbs[T]) Abs() T { func (a complexAbs[T]) Abs() T {
// TODO use direct conversion instead of realimag once #50937 is fixed // TODO use direct conversion instead of realimag once #50937 is fixed
r, i := realimag(a.Value) r, i := realimag(a.Value_)
// r := float64(real(a.Value)) // r := float64(real(a.Value))
// i := float64(imag(a.Value)) // i := float64(imag(a.Value))
d := math.Sqrt(r*r + i*i) d := math.Sqrt(r*r + i*i)
return T(complex(d, 0)) return T(complex(d, 0))
} }
func (a complexAbs[T]) Value() T {
return a.Value_
}
// OrderedAbsDifference returns the absolute value of the difference // OrderedAbsDifference returns the absolute value of the difference
// between a and b, where a and b are of an ordered type. // between a and b, where a and b are of an ordered type.
func OrderedAbsDifference[T orderedNumeric](a, b T) T { func OrderedAbsDifference[T orderedNumeric](a, b T) T {

View file

@ -8,6 +8,11 @@ package main
func main() {} func main() {}
// Field accesses through type parameters are disabled
// until we have a more thorough understanding of the
// implications on the spec. See issue #51576.
/*
type Sf struct { type Sf struct {
f int f int
} }
@ -138,3 +143,4 @@ func f8[P Int4](p P) {
} }
var _ = f8[*Sf] var _ = f8[*Sf]
*/

View file

@ -6,6 +6,13 @@
package main package main
func main() {}
// Field accesses through type parameters are disabled
// until we have a more thorough understanding of the
// implications on the spec. See issue #51576.
/*
import "fmt" import "fmt"
type MyStruct struct { type MyStruct struct {
@ -48,3 +55,4 @@ func main() {
panic(fmt.Sprintf("got %d, want %d", got, want)) panic(fmt.Sprintf("got %d, want %d", got, want))
} }
} }
*/

View file

@ -29,34 +29,47 @@ func Sum[T Numeric](args ...T) T {
// Ledger is an identifiable, financial record. // Ledger is an identifiable, financial record.
type Ledger[T ~string, K Numeric] struct { type Ledger[T ~string, K Numeric] struct {
// ID identifies the ledger. // ID identifies the ledger.
ID T ID_ T
// Amounts is a list of monies associated with this ledger. // Amounts is a list of monies associated with this ledger.
Amounts []K Amounts_ []K
// SumFn is a function that can be used to sum the amounts // SumFn is a function that can be used to sum the amounts
// in this ledger. // in this ledger.
SumFn func(...K) K SumFn_ func(...K) K
} }
// Field accesses through type parameters are disabled
// until we have a more thorough understanding of the
// implications on the spec. See issue #51576.
// Use accessor methods instead.
func (l Ledger[T, _]) ID() T { return l.ID_ }
func (l Ledger[_, K]) Amounts() []K { return l.Amounts_ }
func (l Ledger[_, K]) SumFn() func(...K) K { return l.SumFn_ }
func PrintLedger[ func PrintLedger[
T ~string, T ~string,
K Numeric, K Numeric,
L ~struct { L interface {
ID T ~struct {
Amounts []K ID_ T
SumFn func(...K) K Amounts_ []K
SumFn_ func(...K) K
}
ID() T
Amounts() []K
SumFn() func(...K) K
}, },
](l L) { ](l L) {
fmt.Printf("%s has a sum of %v\n", l.ID, l.SumFn(l.Amounts...)) fmt.Printf("%s has a sum of %v\n", l.ID(), l.SumFn()(l.Amounts()...))
} }
func main() { func main() {
PrintLedger(Ledger[string, int]{ PrintLedger(Ledger[string, int]{
ID: "fake", ID_: "fake",
Amounts: []int{1, 2, 3}, Amounts_: []int{1, 2, 3},
SumFn: Sum[int], SumFn_: Sum[int],
}) })
} }

View file

@ -18,24 +18,34 @@ func Print[T ~string](s T) {
fmt.Println(s) fmt.Println(s)
} }
func PrintWithPrinter[T ~string, S ~struct { func PrintWithPrinter[T ~string, S interface {
ID T ~struct {
PrintFn func(T) ID T
PrintFn_ func(T)
}
PrintFn() func(T)
}](message T, obj S) { }](message T, obj S) {
obj.PrintFn(message) obj.PrintFn()(message)
} }
type PrintShop[T ~string] struct { type PrintShop[T ~string] struct {
ID T ID T
PrintFn func(T) PrintFn_ func(T)
} }
// Field accesses through type parameters are disabled
// until we have a more thorough understanding of the
// implications on the spec. See issue #51576.
// Use accessor method instead.
func (s PrintShop[T]) PrintFn() func(T) { return s.PrintFn_ }
func main() { func main() {
PrintWithPrinter( PrintWithPrinter(
"Hello, world.", "Hello, world.",
PrintShop[string]{ PrintShop[string]{
ID: "fake", ID: "fake",
PrintFn: Print[string], PrintFn_: Print[string],
}, },
) )
} }

View file

@ -18,19 +18,33 @@ func Print[T ~string](s T) {
fmt.Println(s) fmt.Println(s)
} }
func PrintWithPrinter[T ~string, S struct { func PrintWithPrinter[T ~string, S interface {
ID T ~struct {
PrintFn func(T) ID T
PrintFn_ func(T)
}
PrintFn() func(T)
}](message T, obj S) { }](message T, obj S) {
obj.PrintFn(message) obj.PrintFn()(message)
} }
func main() { func main() {
PrintWithPrinter( PrintWithPrinter(
"Hello, world.", "Hello, world.",
struct { StructWithPrinter{ID: "fake", PrintFn_: Print[string]},
ID string
PrintFn func(string)
}{ID: "fake", PrintFn: Print[string]},
) )
} }
type StructWithPrinter struct {
ID string
PrintFn_ func(string)
}
// Field accesses through type parameters are disabled
// until we have a more thorough understanding of the
// implications on the spec. See issue #51576.
// Use accessor method instead.
func (s StructWithPrinter) PrintFn() func(string) {
return s.PrintFn_
}