go/parser, go/types: allow parenthesized embedded interfaces

This enables the distinction between a method M(int) and an
embedded instantiated (parameterized) interface (M(int)).

Change-Id: I150d1b1cd53a2b14ddc0ad6336d84078fcb41ad6
This commit is contained in:
Robert Griesemer 2020-01-07 15:04:23 -08:00
parent 9c75653a79
commit 2c0e53f518
5 changed files with 87 additions and 20 deletions

View File

@ -1129,18 +1129,25 @@ func (p *parser) parseMethodSpec(scope *ast.Scope) *ast.Field {
doc := p.leadComment
var idents []*ast.Ident
var typ ast.Expr
x := p.parseTypeName(nil)
if ident, isIdent := x.(*ast.Ident); isIdent && p.tok == token.LPAREN {
// method
idents = []*ast.Ident{ident}
scope := ast.NewScope(nil) // method scope
tparams, params := p.parseParameters(scope, methodTypeParamsOk|variadicOk, "method")
results := p.parseResult(scope, true)
typ = &ast.FuncType{Func: token.NoPos, TParams: tparams, Params: params, Results: results}
} else {
// embedded interface
typ = x
p.resolve(typ)
switch p.tok {
case token.IDENT:
x := p.parseTypeName(nil)
if ident, isIdent := x.(*ast.Ident); isIdent && p.tok == token.LPAREN {
// method
idents = []*ast.Ident{ident}
scope := ast.NewScope(nil) // method scope
tparams, params := p.parseParameters(scope, methodTypeParamsOk|variadicOk, "method")
results := p.parseResult(scope, true)
typ = &ast.FuncType{Func: token.NoPos, TParams: tparams, Params: params, Results: results}
} else {
// embedded interface
typ = x
p.resolve(typ)
}
case token.LPAREN:
// embedded, possibly parameterized interface
// (using the enclosing parentheses to distinguish it from a method declaration)
typ = p.parseType(true)
}
p.expectSemi() // call before accessing p.linecomment
@ -1163,7 +1170,7 @@ func (p *parser) parseInterfaceType() *ast.InterfaceType {
L:
for {
switch p.tok {
case token.IDENT:
case token.IDENT, token.LPAREN:
mlist = append(mlist, p.parseMethodSpec(scope))
case token.TYPE:
p.next()

View File

@ -98,6 +98,11 @@ var valids = []string{
// interfaces with (contract) type lists
`package p; type _ interface{type int}`,
`package p; type _ interface{type int, float32; type bool; m(); type string;}`,
// interfaces with parenthesized embedded and possibly parameterized interfaces
`package p; type I1 interface{}; type I2 interface{ (I1) }`,
`package p; type I1(type T) interface{}; type I2 interface{ (I1(int)) }`,
`package p; type I1(type T) interface{}; type I2(type T) interface{ (I1(T)) }`,
}
func TestValid(t *testing.T) {

View File

@ -1,7 +1,9 @@
This file works as a sort of notebook/implementation log. It replaces my notebook based approach
so we have a better track record. I only switched to this file recently, hence it is incomplete.
----------------------------------------------------------------------------------------------------
TODO
- type assertions on/against parameterized types
- no need for error messages where a _ type parameter cannot be inferred (_ cannot be used)
- use Underlying() to return a type parameter's bound? investigate!
@ -11,16 +13,20 @@ TODO
- improve error messages!
- use []*TypeParam for tparams in subst? (unclear)
----------------------------------------------------------------------------------------------------
OPEN ISSUES
- instantiating a parameterized function type w/o value or result parameters may have unexpected side-effects
(we don't make a copy of the signature in some cases) - investigate
- using a contract and enumerating type arguments currently leads to an error (e.g. func f(type T C(T)) (x T) ... )
- leaving away unused receiver type parameters leads to an error; e.g.: "type S(type T) struct{}; func (S) _()"
- allow parenthesized embedded interfaces so we can embed parameterized interfaces and they won't be confused
for methods
- confirm that it's ok to use inference in missingMethod to compare parameterized methods
- now that we allow parenthesized embedded interfaces, should we allow parenthesized embedded fields?
(probably yes, for symmetry and consistency).
----------------------------------------------------------------------------------------------------
DESIGN/IMPLEMENTATION
- 11/19/2019: For type parameters with interface bounds to work, the scope of all type parameters in
a type parameter list starts at the "type" keyword. This makes all type parameters visible for all
type parameter bounds (interfaces that may be parameterized with the type parameters).
@ -67,6 +73,12 @@ DESIGN/IMPLEMENTATION
name cannot be a type parameter name declared later (or by the receiver type specification). This
seems reasonable and should help avoid confusion which is possible otherwise.
- 1/6/2020: Embedding a parameterized interface in an interface is not possible because it looks
like a method declaration. We probably need to find a away around this; maybe parenthesize the
embedded interface (which is currently not permitted, syntactically).
- 1/7/2020: We distinguish embedded instantiated (parameterized) interfaces from methods by enclosing
the embedded interfaces in parentheses (the design draft recommends this change). Since this opens
the possibility for any parenthesized type (that is an interface), we can also allow (parenthesized)
interface literals as it is simpler to permit those than forbid them.
- 1/7/2020: The current implementation permits empty type parameter lists as in: "func f(type)(x int)"
but we cannot call such a function as "f()(1)"; the empty type argument list causes problems.
We should either disallow empty type parameter lists or document this well.

View File

@ -18,7 +18,6 @@ var _ List(byte) = []byte{}
// A generic binary tree might be declared as follows.
type Tree(type E) struct {
// TODO(gri) should be able to use Tree w/o repeating type args
left, right *Tree(E)
payload E
}
@ -107,3 +106,43 @@ var _ T // ERROR cannot use generic type T
// In type context, parameterized (generic) types cannot use instantiation syntax.
// See also NOTES entry from 12/4/2019.
var _ (T /* ERROR cannot use generic type T */ )( /* ERROR expected ';' */ int)
// All types may be parameterized, including interfaces.
type I1(type T) interface{
m1(T)
}
// To differentiate an instantiated interface such as I1(int) from a method
// declaration when embedding it, we must use parentheses around it:
type I2 interface {
I1(int) // method!
(I1(string)) // embedded I1
}
func _() {
var x I2
x.I1(0)
x.m1("foo")
}
// Generally, we now allow any embedded interfaces to be parenthesized.
// Because that removes the restriction that embedded interfaces must
// be named, we can even "inline" interface literals (though that's of
// little practial use - it's simply more complicated to disallow them).
// Here are some more examples:
type I0 interface {
m0()
}
type I3 interface {
I0
(I1(bool))
((interface{ m(string) }))
}
func _() {
var x I3
x.m0()
x.m1(true)
x.m("foo")
}

View File

@ -14,8 +14,12 @@ func (S) m(type B)(B)
var _ I = S{}
type E(type T) interface {
m(type B)(B)
}
type II interface{
m(type C)(C)
(E(int))
mm() int
}