-
Notifications
You must be signed in to change notification settings - Fork 17.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
cmd/compile: support field access for typeparam with structural const…
…raint In the compiler, we need to distinguish field and method access on a type param. For field access, we avoid the dictionary access (to create an interface bound) and just do the normal transformDot() (which will create the field access on the shape type). This field access works fine for non-pointer types, since the shape type preserves the underlying type of all types in the shape. But we generally merge all pointer types into a single shape, which means the field will not be accessible via the shape type. So, we need to change Shapify() so that a type which is a pointer type is mapped to its underlying type, rather than being merged with other pointers. Because we don't want to change the export format at this point in the release, we need to compute StructuralType() directly in types1, rather than relying on types2. That implementation is in types/type.go, along with the helper specificTypes(). I enabled the compiler-related tests in issue50417.go, added an extra test for unnamed pointer types, and added a bunch more tests for interesting cases involving StructuralType(). I added a test issue50417b.go similar to the original example, but also tests access to an embedded field. I also added a unit test in cmd/compile/internal/types/structuraltype_test.go that tests a bunch of unusual cases directly (some of which have no structural type). Updates #50417 Change-Id: I77c55cbad98a2b95efbd4a02a026c07dfbb46caa Reviewed-on: https://go-review.googlesource.com/c/go/+/376194 Reviewed-by: Keith Randall <khr@golang.org> Reviewed-by: Robert Griesemer <gri@golang.org> Trust: Dan Scales <danscales@google.com> Run-TryBot: Dan Scales <danscales@google.com> TryBot-Result: Gopher Robot <gobot@golang.org>
- Loading branch information
Showing
10 changed files
with
512 additions
and
46 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,187 @@ | ||
package types | ||
|
||
// Implementation of structural type computation for types. | ||
|
||
// TODO: we would like to depend only on the types2 computation of structural type, | ||
// but we can only do that the next time we change the export format and export | ||
// structural type info along with each constraint type, since the compiler imports | ||
// types directly into types1 format. | ||
|
||
// A term describes elementary type sets: | ||
// | ||
// term{false, T} set of type T | ||
// term{true, T} set of types with underlying type t | ||
// term{} empty set (we specifically check for typ == nil) | ||
type term struct { | ||
tilde bool | ||
typ *Type | ||
} | ||
|
||
// StructuralType returns the structural type of an interface, or nil if it has no | ||
// structural type. | ||
func (t *Type) StructuralType() *Type { | ||
sts, _ := specificTypes(t) | ||
var su *Type | ||
for _, st := range sts { | ||
u := st.typ.Underlying() | ||
if su != nil { | ||
u = match(su, u) | ||
if u == nil { | ||
return nil | ||
} | ||
} | ||
// su == nil || match(su, u) != nil | ||
su = u | ||
} | ||
return su | ||
} | ||
|
||
// If x and y are identical, match returns x. | ||
// If x and y are identical channels but for their direction | ||
// and one of them is unrestricted, match returns the channel | ||
// with the restricted direction. | ||
// In all other cases, match returns nil. | ||
// x and y are assumed to be underlying types, hence are not named types. | ||
func match(x, y *Type) *Type { | ||
if IdenticalStrict(x, y) { | ||
return x | ||
} | ||
|
||
if x.IsChan() && y.IsChan() && IdenticalStrict(x.Elem(), y.Elem()) { | ||
// We have channels that differ in direction only. | ||
// If there's an unrestricted channel, select the restricted one. | ||
// If both have the same direction, return x (either is fine). | ||
switch { | ||
case x.ChanDir().CanSend() && x.ChanDir().CanRecv(): | ||
return y | ||
case y.ChanDir().CanSend() && y.ChanDir().CanRecv(): | ||
return x | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// specificTypes returns the list of specific types of an interface type or nil if | ||
// there are none. It also returns a flag that indicates, for an empty term list | ||
// result, whether it represents the empty set, or the infinite set of all types (in | ||
// both cases, there are no specific types). | ||
func specificTypes(t *Type) (list []term, inf bool) { | ||
t.wantEtype(TINTER) | ||
|
||
// We have infinite term list before processing any type elements | ||
// (or if there are no type elements). | ||
inf = true | ||
for _, m := range t.Methods().Slice() { | ||
var r2 []term | ||
inf2 := false | ||
|
||
switch { | ||
case m.IsMethod(): | ||
inf2 = true | ||
|
||
case m.Type.IsUnion(): | ||
nt := m.Type.NumTerms() | ||
for i := 0; i < nt; i++ { | ||
t, tilde := m.Type.Term(i) | ||
if t.IsInterface() { | ||
r3, r3inf := specificTypes(t) | ||
if r3inf { | ||
// Union with an infinite set of types is | ||
// infinite, so skip remaining terms. | ||
r2 = nil | ||
inf2 = true | ||
break | ||
} | ||
// Add the elements of r3 to r2. | ||
for _, r3e := range r3 { | ||
r2 = insertType(r2, r3e) | ||
} | ||
} else { | ||
r2 = insertType(r2, term{tilde, t}) | ||
} | ||
} | ||
|
||
case m.Type.IsInterface(): | ||
r2, inf2 = specificTypes(m.Type) | ||
|
||
default: | ||
// m.Type is a single non-interface type, so r2 is just a | ||
// one-element list, inf2 is false. | ||
r2 = []term{term{false, m.Type}} | ||
} | ||
|
||
if inf2 { | ||
// If the current type element has infinite types, | ||
// its intersection with r is just r, so skip this type element. | ||
continue | ||
} | ||
|
||
if inf { | ||
// If r is infinite, then the intersection of r and r2 is just r2. | ||
list = r2 | ||
inf = false | ||
continue | ||
} | ||
|
||
// r and r2 are finite, so intersect r and r2. | ||
var r3 []term | ||
for _, re := range list { | ||
for _, r2e := range r2 { | ||
if tm := intersect(re, r2e); tm.typ != nil { | ||
r3 = append(r3, tm) | ||
} | ||
} | ||
} | ||
list = r3 | ||
} | ||
return | ||
} | ||
|
||
// insertType adds t to the returned list if it is not already in list. | ||
func insertType(list []term, tm term) []term { | ||
for i, elt := range list { | ||
if new := union(elt, tm); new.typ != nil { | ||
// Replace existing elt with the union of elt and new. | ||
list[i] = new | ||
return list | ||
} | ||
} | ||
return append(list, tm) | ||
} | ||
|
||
// If x and y are disjoint, return term with nil typ (which means the union should | ||
// include both types). If x and y are not disjoint, return the single type which is | ||
// the union of x and y. | ||
func union(x, y term) term { | ||
if disjoint(x, y) { | ||
return term{false, nil} | ||
} | ||
if x.tilde || !y.tilde { | ||
return x | ||
} | ||
return y | ||
} | ||
|
||
// intersect returns the intersection x ∩ y. | ||
func intersect(x, y term) term { | ||
if disjoint(x, y) { | ||
return term{false, nil} | ||
} | ||
if !x.tilde || y.tilde { | ||
return x | ||
} | ||
return y | ||
} | ||
|
||
// disjoint reports whether x ∩ y == ∅. | ||
func disjoint(x, y term) bool { | ||
ux := x.typ | ||
if y.tilde { | ||
ux = ux.Underlying() | ||
} | ||
uy := y.typ | ||
if x.tilde { | ||
uy = uy.Underlying() | ||
} | ||
return !IdenticalStrict(ux, uy) | ||
} |
Oops, something went wrong.