From: Robert Griesemer Date: Thu, 18 Oct 2018 22:19:15 +0000 (-0700) Subject: go/types: accept recv base type that is alias to a pointer type X-Git-Tag: go1.12beta1~712 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=f5636523998594af75ec87d3c2a2070dc1cb65f4;p=gostls13.git go/types: accept recv base type that is alias to a pointer type Per the spec clarification https://golang.org/cl/142757 (issue #27995). Fixes #28251. Updates #27995. Change-Id: Idc142829955f9306a8698c5ed1c24baa8ee2b109 Reviewed-on: https://go-review.googlesource.com/c/143179 Reviewed-by: Alan Donovan --- diff --git a/src/go/types/check_test.go b/src/go/types/check_test.go index e8ba1a037c..45e1fcb605 100644 --- a/src/go/types/check_test.go +++ b/src/go/types/check_test.go @@ -94,6 +94,7 @@ var tests = [][]string{ {"testdata/issue26390.src"}, // stand-alone test to ensure case is triggered {"testdata/issue23203a.src"}, {"testdata/issue23203b.src"}, + {"testdata/issue28251.src"}, } var fset = token.NewFileSet() diff --git a/src/go/types/resolver.go b/src/go/types/resolver.go index c2726f4dd2..f6c3b601b2 100644 --- a/src/go/types/resolver.go +++ b/src/go/types/resolver.go @@ -460,65 +460,74 @@ func (check *Checker) collectObjects() { for _, f := range methods { fdecl := check.objMap[f].fdecl if list := fdecl.Recv.List; len(list) > 0 { - // f is a method - // receiver may be of the form T or *T, possibly with parentheses - typ := unparen(list[0].Type) - if ptr, _ := typ.(*ast.StarExpr); ptr != nil { - typ = unparen(ptr.X) - // TODO(gri): This may not be sufficient. See issue #27995. - f.hasPtrRecv = true - } - if base, _ := typ.(*ast.Ident); base != nil { - // base is a potential base type name; determine - // "underlying" defined type and associate f with it - if tname := check.resolveBaseTypeName(base); tname != nil { - check.methods[tname] = append(check.methods[tname], f) - } + // f is a method. + // Determine the receiver base type and associate f with it. + ptr, base := check.resolveBaseTypeName(list[0].Type) + if base != nil { + f.hasPtrRecv = ptr + check.methods[base] = append(check.methods[base], f) } } } } -// resolveBaseTypeName returns the non-alias receiver base type name, -// explicitly declared in the package scope, for the given receiver -// type name; or nil. -func (check *Checker) resolveBaseTypeName(name *ast.Ident) *TypeName { +// resolveBaseTypeName returns the non-alias base type name for typ, and whether +// there was a pointer indirection to get to it. The base type name must be declared +// in package scope, and there can be at most one pointer indirection. If no such type +// name exists, the returned base is nil. +func (check *Checker) resolveBaseTypeName(typ ast.Expr) (ptr bool, base *TypeName) { + // Algorithm: Starting from a type expression, which may be a name, + // we follow that type through alias declarations until we reach a + // non-alias type name. If we encounter anything but pointer types or + // parentheses we're done. If we encounter more than one pointer type + // we're done. var path []*TypeName for { + typ = unparen(typ) + + // check if we have a pointer type + if pexpr, _ := typ.(*ast.StarExpr); pexpr != nil { + // if we've already seen a pointer, we're done + if ptr { + return false, nil + } + ptr = true + typ = unparen(pexpr.X) // continue with pointer base type + } + + // typ must be the name + name, _ := typ.(*ast.Ident) + if name == nil { + return false, nil + } + // name must denote an object found in the current package scope // (note that dot-imported objects are not in the package scope!) obj := check.pkg.scope.Lookup(name.Name) if obj == nil { - return nil + return false, nil } + // the object must be a type name... tname, _ := obj.(*TypeName) if tname == nil { - return nil + return false, nil } // ... which we have not seen before if check.cycle(tname, path, false) { - return nil + return false, nil } // we're done if tdecl defined tname as a new type // (rather than an alias) tdecl := check.objMap[tname] // must exist for objects in package scope if !tdecl.alias { - return tname - } - - // Otherwise, if tdecl defined an alias for a (possibly parenthesized) - // type which is not an (unqualified) named type, we're done because - // receiver base types must be named types declared in this package. - typ := unparen(tdecl.typ) // a type may be parenthesized - name, _ = typ.(*ast.Ident) - if name == nil { - return nil + return ptr, tname } - // continue resolving name + // otherwise, continue resolving + typ = tdecl.typ path = append(path, tname) } } diff --git a/src/go/types/testdata/issue28251.src b/src/go/types/testdata/issue28251.src new file mode 100644 index 0000000000..a456f5c27e --- /dev/null +++ b/src/go/types/testdata/issue28251.src @@ -0,0 +1,65 @@ +// Copyright 2018 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 contains test cases for various forms of +// method receiver declarations, per the spec clarification +// https://golang.org/cl/142757. + +package issue28251 + +// test case from issue28251 +type T struct{} + +type T0 = *T + +func (T0) m() {} + +func _() { (&T{}).m() } + +// various alternative forms +type ( + T1 = (((T))) +) + +func ((*(T1))) m1() {} +func _() { (T{}).m2() } +func _() { (&T{}).m2() } + +type ( + T2 = (((T3))) + T3 = T +) + +func (T2) m2() {} +func _() { (T{}).m2() } +func _() { (&T{}).m2() } + +type ( + T4 = ((*(T5))) + T5 = T +) + +func (T4) m4() {} +func _() { (T{}).m4 /* ERROR m4 is not in method set of T */ () } +func _() { (&T{}).m4() } + +type ( + T6 = (((T7))) + T7 = (*(T8)) + T8 = T +) + +func (T6) m6() {} +func _() { (T{}).m6 /* ERROR m6 is not in method set of T */ () } +func _() { (&T{}).m6() } + +type ( + T9 = *T10 + T10 = *T11 + T11 = T +) + +func (T9 /* ERROR invalid receiver \*\*T */ ) m9() {} +func _() { (T{}).m9 /* ERROR has no field or method m9 */ () } +func _() { (&T{}).m9 /* ERROR has no field or method m9 */ () }