]> Cypherpunks repositories - gostls13.git/commitdiff
go/types: accept recv base type that is alias to a pointer type
authorRobert Griesemer <gri@golang.org>
Thu, 18 Oct 2018 22:19:15 +0000 (15:19 -0700)
committerRobert Griesemer <gri@golang.org>
Fri, 19 Oct 2018 18:55:14 +0000 (18:55 +0000)
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 <adonovan@google.com>
src/go/types/check_test.go
src/go/types/resolver.go
src/go/types/testdata/issue28251.src [new file with mode: 0644]

index e8ba1a037c45a2c7dfadf4ca64f1a90a7e876d91..45e1fcb60561f05249364ccae92646fc5059c45a 100644 (file)
@@ -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()
index c2726f4dd2870d3c6308cee8bfbd06bbe7cdac27..f6c3b601b24ee4d11c4ad3f327df40a9518aa220 100644 (file)
@@ -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 (file)
index 0000000..a456f5c
--- /dev/null
@@ -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 */ () }