mirror of https://github.com/golang/go.git
go/types: support blank identifiers for unused receiver type parameters
Change-Id: I5d7d359d7453f7ed949a7cab7b85419f8f4ee2a9
This commit is contained in:
parent
08e2d11638
commit
f7bc9d382f
|
|
@ -10,6 +10,8 @@ TODO
|
|||
- improve error messages!
|
||||
- use []*TypeParam for tparams in subst? (unclear)
|
||||
- should we use nil instead of &emptyInterface for no type bounds (as an optimization)?
|
||||
- TBD: in prose, should we use "generic" or "parameterized" (try to be consistent)
|
||||
- don't apply receiver type identifier substitution in place (typexpr.go)
|
||||
|
||||
----------------------------------------------------------------------------------------------------
|
||||
KNOWN ISSUES
|
||||
|
|
@ -18,8 +20,18 @@ KNOWN ISSUES
|
|||
- cannot handle mutually recursive parameterized interfaces using themselves as type bounds
|
||||
- contract instantiation requires the type arguments to be type parameters from the type of function
|
||||
type parameter list or enclosing contract
|
||||
- leaving away unused receiver type parameters leads to an error; e.g.: "type S(type T) struct{}; func (S) _()"
|
||||
- using _ for an unused receiver type parameter leads to an error and crash; e.g.: "type T(type P) int; func (_ T(_)) m()"
|
||||
- A type parameter that is constrained by multiple contracts will not get the correct type bound.
|
||||
We may disallow this for now.
|
||||
|
||||
----------------------------------------------------------------------------------------------------
|
||||
OBSERVATIONS
|
||||
|
||||
- Because we permit parenthesized types anywhere for consistency, also in parameter lists (mea culpa),
|
||||
we have parsing ambiguities when using instantiated types in parameter lists w/o argument names.
|
||||
We could disallow the use of parentheses at the top level of type literals and then we might not have
|
||||
this problem. This is not a backward-compatible change but perhaps worthwhile investigating. Specifically,
|
||||
will this always work (look specifically at channel types where we need parentheses for disambiguation
|
||||
and possibly function types). File a proposal?
|
||||
|
||||
----------------------------------------------------------------------------------------------------
|
||||
OPEN QUESTIONS
|
||||
|
|
@ -97,3 +109,7 @@ DESIGN/IMPLEMENTATION
|
|||
|
||||
- 2/19/2020: Permit parentheses around embedded contracts for symmetry with embedding in structs
|
||||
and interfaces.
|
||||
|
||||
- 2/20/2020: Receiver type parameters must always be provided in the receiver parameter list of
|
||||
a method, even if they are not used by the method. Since the receiver acts like an implicit
|
||||
declaration of those type parameters, they may be blank, as with any other declaration.
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ var tests = [][]string{
|
|||
// Go 2 prototype examples
|
||||
{"examples/contracts.go2"},
|
||||
{"examples/functions.go2"},
|
||||
{"examples/methods.go2"},
|
||||
{"examples/types.go2"},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,111 @@
|
|||
// Copyright 2019 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.
|
||||
|
||||
// This file shows some examples of methods on type-parameterized types.
|
||||
|
||||
package p
|
||||
|
||||
// Parameterized types may have methods.
|
||||
type T1(type A) struct{ a A }
|
||||
|
||||
// When declaring a method for a parameterized type, the "instantiated"
|
||||
// receiver type acts as an implicit declaration of the type parameters
|
||||
// for the receiver type. In the example below, method m1 on type T1 has
|
||||
// the receiver type T1(A) which declares the type parameter A for use
|
||||
// with this method. That is, within the method m1, A stands for the
|
||||
// actual type argument provided to an instantiated T1.
|
||||
func (t T1(A)) m1() A { return t.a }
|
||||
|
||||
// For instance, if T1 is instantiated with the type int, the type
|
||||
// parameter A in m1 assumes that type (int) as well and we can write
|
||||
// code like this:
|
||||
var x T1(int)
|
||||
var _ int = x.m1()
|
||||
|
||||
// Because the type parameter provided to a parameterized receiver type
|
||||
// is declared through that receiver declaration, it must be an identifier.
|
||||
// It cannot possibly be some other type because the receiver type is not
|
||||
// instantiated with concrete types, it is standing for the parameterized
|
||||
// receiver type.
|
||||
func (t T1([ /* ERROR must be an identifier */ ]int)) m2() {}
|
||||
|
||||
// Note that using what looks like a predeclared identifier, say int,
|
||||
// as type parameter in this situation is deceptive and considered bad
|
||||
// style. In m3 below, int is the name of the local receiver type parameter
|
||||
// and it shadows the predeclared identifier int which then cannot be used
|
||||
// anymore as expected.
|
||||
func (t T1(int)) m3() { var _ int = 42 /* ERROR cannot convert 42 .* to int */ }
|
||||
|
||||
// The names of the type parameters used in a parameterized receiver
|
||||
// type don't have to match the type parameter names in the the declaration
|
||||
// of the type used for the receiver. In our example, even though T1 is
|
||||
// declared with type parameter named A, methods using that receiver type
|
||||
// are free to use their own name for that type parameter. That is, the
|
||||
// name of type parameters is always local to the declaration where they
|
||||
// are introduced. In our example we can write a method m2 and use the
|
||||
// name X instead of A for the type parameter w/o any difference.
|
||||
func (t T1(X)) m4() X { return t.a }
|
||||
|
||||
// If the receiver type is parameterized, type parameters must always be
|
||||
// provided: this simply follows from the general rule that a parameterized
|
||||
// type must be instantiated before it can be used. A method receiver
|
||||
// declaration using a parameterized receiver type is no exception. It is
|
||||
// simply that such receiver type expressions perform two tasks simultaneously:
|
||||
// they declare the (local) type parameters and then use them to instantiate
|
||||
// the receiver type. Forgetting to provide a type parameter leads to an error.
|
||||
func (t T1 /* ERROR generic type .* without instantiation */ ) m5() {}
|
||||
|
||||
// However, sometimes we don't need the type parameter, and thus it is
|
||||
// inconvenient to have to choose a name. Since the receiver type expression
|
||||
// serves as a declaration for its type parameters, we are free to choose the
|
||||
// blank identifier:
|
||||
func (t T1(_)) m6() {}
|
||||
|
||||
// Naturally, these rules apply to any number of type parameters on the receiver
|
||||
// type. Here are some more complex examples.
|
||||
type T2(type A, B, C) struct {
|
||||
a A
|
||||
b B
|
||||
c C
|
||||
}
|
||||
|
||||
// Naming of the type parameters is local and has no semantic impact:
|
||||
func (t T2(A, B, C)) m1() (A, B, C) { return t.a, t.b, t.c }
|
||||
func (t T2(C, B, A)) m2() (C, B, A) { return t.a, t.b, t.c }
|
||||
func (t T2(X, Y, Z)) m3() (X, Y, Z) { return t.a, t.b, t.c }
|
||||
|
||||
// Type parameters may be left blank if they are not needed:
|
||||
func (t T2(A, _, C)) m4() (A, C) { return t.a, t.c }
|
||||
func (t T2(_, _, X)) m5() X { return t.c }
|
||||
func (t T2(_, _, _)) m6() {}
|
||||
|
||||
// As usual, blank names may be used for any object which we don't care about
|
||||
// using later. For instance, we may write an unnamed method with a receiver
|
||||
// that cannot be accessed:
|
||||
func (_ T2(_, _, _)) _() int { return 42 }
|
||||
|
||||
// Because a receiver parameter list is simply a parameter list, we can
|
||||
// leave the receiver argument away for non-parameterized receiver types.
|
||||
type T0 struct{}
|
||||
func (T0) m() {}
|
||||
|
||||
// This doesn't work for parameterized receiver types because there is
|
||||
// a syntactic ambiguity: what looks like a parameterized reciver type
|
||||
// T1(A) is parsed as a receiver argument named T1 followed by a
|
||||
// parenthesized receiver type (A): (T1 (A)).
|
||||
func (T1(A /* ERROR undeclared name: A */ )) _() {}
|
||||
|
||||
// The work-around is to either use blank identifier for the receiver
|
||||
// argument or to parenthesize the receiver type:
|
||||
func (_ T1(A)) _(a A) {}
|
||||
func ((T1(A))) _(a A) {}
|
||||
|
||||
// Consequently, this problem appears in parameter lists in general:
|
||||
func _(T1(A /* ERROR undeclared name: A */ )) {}
|
||||
|
||||
// And the workaround is the same as for receivers:
|
||||
func _(_ T1(int)) {}
|
||||
func _((T1(int))) {}
|
||||
func _(type A)(_ T1(A)) {}
|
||||
func _(type A)((T1(A))) {}
|
||||
|
|
@ -4,14 +4,7 @@
|
|||
|
||||
package p
|
||||
|
||||
/*
|
||||
type T(type E) struct {
|
||||
f *T(E)
|
||||
}
|
||||
type T(type a, b, c) struct {}
|
||||
|
||||
var _ T(int)
|
||||
*/
|
||||
|
||||
func f(type T)(...T) T
|
||||
|
||||
var _ int = f() /* ERROR cannot infer T */
|
||||
//func (_ T(_, T(_, _, _), _)) m() {}
|
||||
func (_ T(_, int, _)) m() {}
|
||||
|
|
|
|||
|
|
@ -163,6 +163,51 @@ func (check *Checker) genericType(e ast.Expr, reportErr bool) Type {
|
|||
return typ
|
||||
}
|
||||
|
||||
// isubsts returns an x with identifiers substituted per the substitution map smap.
|
||||
// isubsts only handles the case of (valid) method receiver type expressions correctly.
|
||||
func isubst(x ast.Expr, smap map[*ast.Ident]*ast.Ident) ast.Expr {
|
||||
switch n := x.(type) {
|
||||
case *ast.Ident:
|
||||
if alt := smap[n]; alt != nil {
|
||||
return alt
|
||||
}
|
||||
case *ast.StarExpr:
|
||||
X := isubst(n.X, smap)
|
||||
if X != n.X {
|
||||
new := *n
|
||||
n.X = X
|
||||
return &new
|
||||
}
|
||||
case *ast.CallExpr:
|
||||
var args []ast.Expr
|
||||
for i, arg := range n.Args {
|
||||
Arg := isubst(arg, smap)
|
||||
if Arg != arg {
|
||||
if args == nil {
|
||||
args = make([]ast.Expr, len(n.Args))
|
||||
copy(args, n.Args)
|
||||
}
|
||||
args[i] = Arg
|
||||
}
|
||||
}
|
||||
if args != nil {
|
||||
new := *n
|
||||
new.Args = args
|
||||
return &new
|
||||
}
|
||||
case *ast.ParenExpr:
|
||||
X := isubst(n.X, smap)
|
||||
if X != n.X {
|
||||
return X // no need to recreate the parentheses
|
||||
}
|
||||
default:
|
||||
// Any more complex receiver type expression is invalid.
|
||||
// It's fine to ignore those here since we will report
|
||||
// an error later.
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// funcType type-checks a function or method type.
|
||||
func (check *Checker) funcType(sig *Signature, recvPar *ast.FieldList, ftyp *ast.FuncType) {
|
||||
check.openScope(ftyp, "function")
|
||||
|
|
@ -173,12 +218,32 @@ func (check *Checker) funcType(sig *Signature, recvPar *ast.FieldList, ftyp *ast
|
|||
|
||||
if recvPar != nil && len(recvPar.List) > 0 {
|
||||
// collect parameterized receiver type parameters, if any
|
||||
// - a receiver type parameter is like any other type parameter, except that it is passed implicitly (via the receiver)
|
||||
// - the receiver specification acts as local declaration for its type parameters (which may be blank _)
|
||||
// - if the receiver type is parameterized but we don't need the parameters, we permit leaving them away
|
||||
// (TODO(gri) this is not working because the code doesn't allow an uninstantiated parameterized recv type)
|
||||
// - a receiver type parameter is like any other type parameter, except that it is passed implicitly
|
||||
// - the receiver specification acts as local declaration for its type parameters, which may be blank
|
||||
_, rname, rparams := check.unpackRecv(recvPar.List[0].Type, true)
|
||||
if len(rparams) > 0 {
|
||||
// The receiver type parameters may not be used by the method, in which case they may be
|
||||
// blank identifiers. But blank identifiers don't get declared and regular type-checking
|
||||
// of the instantiated parameterized receiver type expression fails.
|
||||
// Identify blank type parameters and substitute each with a unique new identifier named
|
||||
// "!n" (where n is an increasing index) and which cannot conflict with any user-defined
|
||||
// name; do the substitution both in the rparams and the receiver parameter list.
|
||||
var smap map[*ast.Ident]*ast.Ident // substitution map from "_" to "!n" identifiers
|
||||
for i, p := range rparams {
|
||||
if p.Name == "_" {
|
||||
new := *p
|
||||
new.Name = fmt.Sprintf("%d_", i)
|
||||
rparams[i] = &new
|
||||
if smap == nil {
|
||||
smap = make(map[*ast.Ident]*ast.Ident)
|
||||
}
|
||||
smap[p] = &new
|
||||
}
|
||||
}
|
||||
if smap != nil {
|
||||
// TODO(gri) should not do this in place
|
||||
recvPar.List[0].Type = isubst(recvPar.List[0].Type, smap)
|
||||
}
|
||||
sig.rparams = check.declareTypeParams(nil, rparams)
|
||||
// determine receiver type to get its type parameters
|
||||
// and the respective type parameter bounds
|
||||
|
|
|
|||
Loading…
Reference in New Issue