From 2e5116bd999be18691d860e47cb87f1446cf70fe Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Fri, 16 Dec 2016 15:10:07 -0800 Subject: [PATCH 01/26] [dev.typealias] go/ast, go/parser, go/printer, go/types: initial type alias support Parsing and printing support for type aliases complete. go/types recognizes them an issues an "unimplemented" error for now. For #18130. Change-Id: I9f2f7b1971b527276b698d9347bcd094ef0012ee Reviewed-on: https://go-review.googlesource.com/34986 Run-TryBot: Robert Griesemer Reviewed-by: Matthew Dempsky TryBot-Result: Gobot Gobot --- src/go/ast/ast.go | 1 + src/go/parser/parser.go | 5 ++++- src/go/parser/short_test.go | 2 ++ src/go/printer/nodes.go | 3 +++ src/go/printer/testdata/declarations.golden | 15 +++++++++++++++ src/go/printer/testdata/declarations.input | 15 +++++++++++++++ src/go/types/decl.go | 3 +++ src/go/types/resolver.go | 3 +++ src/go/types/testdata/decls0.src | 8 ++++++++ 9 files changed, 54 insertions(+), 1 deletion(-) diff --git a/src/go/ast/ast.go b/src/go/ast/ast.go index a197b5a5bf..2ecc48b741 100644 --- a/src/go/ast/ast.go +++ b/src/go/ast/ast.go @@ -848,6 +848,7 @@ type ( TypeSpec struct { Doc *CommentGroup // associated documentation; or nil Name *Ident // type name + Assign token.Pos // position of '=', if any Type Expr // *Ident, *ParenExpr, *SelectorExpr, *StarExpr, or any of the *XxxTypes Comment *CommentGroup // line comments; or nil } diff --git a/src/go/parser/parser.go b/src/go/parser/parser.go index d3ef7db31e..40c4a3e58d 100644 --- a/src/go/parser/parser.go +++ b/src/go/parser/parser.go @@ -2327,7 +2327,10 @@ func (p *parser) parseTypeSpec(doc *ast.CommentGroup, _ token.Token, _ int) ast. // (Global identifiers are resolved in a separate phase after parsing.) spec := &ast.TypeSpec{Doc: doc, Name: ident} p.declare(spec, nil, p.topScope, ast.Typ, ident) - + if p.tok == token.ASSIGN { + spec.Assign = p.pos + p.next() + } spec.Type = p.parseType() p.expectSemi() // call before accessing p.linecomment spec.Comment = p.lineComment diff --git a/src/go/parser/short_test.go b/src/go/parser/short_test.go index cdd343ea3c..6f8ef6b0f7 100644 --- a/src/go/parser/short_test.go +++ b/src/go/parser/short_test.go @@ -46,6 +46,8 @@ var valids = []string{ `package p; const (x = 0; y; z)`, // issue 9639 `package p; var _ = map[P]int{P{}:0, {}:1}`, `package p; var _ = map[*P]int{&P{}:0, {}:1}`, + `package p; type T = int`, + `package p; type (T = p.T; _ = struct{}; x = *T)`, } func TestValid(t *testing.T) { diff --git a/src/go/printer/nodes.go b/src/go/printer/nodes.go index 11f26d45ea..5a408cd571 100644 --- a/src/go/printer/nodes.go +++ b/src/go/printer/nodes.go @@ -1445,6 +1445,9 @@ func (p *printer) spec(spec ast.Spec, n int, doIndent bool) { } else { p.print(vtab) } + if s.Assign.IsValid() { + p.print(token.ASSIGN, blank) + } p.expr(s.Type) p.setComment(s.Comment) diff --git a/src/go/printer/testdata/declarations.golden b/src/go/printer/testdata/declarations.golden index 82f5e0f914..d4ea545658 100644 --- a/src/go/printer/testdata/declarations.golden +++ b/src/go/printer/testdata/declarations.golden @@ -985,3 +985,18 @@ func _(struct { x int y int }) // no extra comma between } and ) + +// alias declarations + +type c0 struct{} +type c1 = C +type c2 = struct{ x int } +type c3 = p.C +type ( + s struct{} + a = A + b = A + c = foo + d = interface{} + ddd = p.Foo +) diff --git a/src/go/printer/testdata/declarations.input b/src/go/printer/testdata/declarations.input index a0a3783b84..50386eb8d5 100644 --- a/src/go/printer/testdata/declarations.input +++ b/src/go/printer/testdata/declarations.input @@ -999,3 +999,18 @@ func _(struct { x int y int }) // no extra comma between } and ) + +// alias declarations + +type c0 struct{} +type c1 = C +type c2 = struct{ x int} +type c3 = p.C +type ( + s struct{} + a = A + b = A + c = foo + d = interface{} + ddd = p.Foo +) \ No newline at end of file diff --git a/src/go/types/decl.go b/src/go/types/decl.go index dced7a6d6d..2472aa3434 100644 --- a/src/go/types/decl.go +++ b/src/go/types/decl.go @@ -534,6 +534,9 @@ func (check *Checker) declStmt(decl ast.Decl) { } case *ast.TypeSpec: + if s.Assign.IsValid() { + check.errorf(s.Assign, "type alias declarations not yet implemented") + } obj := NewTypeName(s.Name.Pos(), pkg, s.Name.Name, nil) // spec: "The scope of a type identifier declared inside a function // begins at the identifier in the TypeSpec and ends at the end of diff --git a/src/go/types/resolver.go b/src/go/types/resolver.go index 046e147456..d37f93de45 100644 --- a/src/go/types/resolver.go +++ b/src/go/types/resolver.go @@ -346,6 +346,9 @@ func (check *Checker) collectObjects() { } case *ast.TypeSpec: + if s.Assign.IsValid() { + check.errorf(s.Assign, "type alias declarations not yet implemented") + } obj := NewTypeName(s.Name.Pos(), pkg, s.Name.Name, nil) check.declarePkgObj(s.Name, obj, &declInfo{file: fileScope, typ: s.Type}) diff --git a/src/go/types/testdata/decls0.src b/src/go/types/testdata/decls0.src index d4df386b13..3ed1b976e5 100644 --- a/src/go/types/testdata/decls0.src +++ b/src/go/types/testdata/decls0.src @@ -208,3 +208,11 @@ func (BlankT) _() {} func (BlankT) _(int) {} func (BlankT) _() int { return 0 } func (BlankT) _(int) int { return 0} + +// type alias declarations +// TODO(gri) complete this +type ( + __ = /* ERROR not yet implemented */ int + a0 = /* ERROR not yet implemented */ int + a1 = /* ERROR not yet implemented */ struct{} +) From e0a05c274aa5a3917c5e53f72537e38bb05c10d6 Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Fri, 16 Dec 2016 15:56:05 -0800 Subject: [PATCH 02/26] [dev.typealias] cmd/gofmt: added test cases for alias type declarations For #18130. Change-Id: I95e84130df40db5241e0cc25c36873c3281199ff Reviewed-on: https://go-review.googlesource.com/34987 Reviewed-by: Matthew Dempsky --- src/cmd/gofmt/testdata/typealias.golden | 24 ++++++++++++++++++++++++ src/cmd/gofmt/testdata/typealias.input | 24 ++++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 src/cmd/gofmt/testdata/typealias.golden create mode 100644 src/cmd/gofmt/testdata/typealias.input diff --git a/src/cmd/gofmt/testdata/typealias.golden b/src/cmd/gofmt/testdata/typealias.golden new file mode 100644 index 0000000000..bbbbf32121 --- /dev/null +++ b/src/cmd/gofmt/testdata/typealias.golden @@ -0,0 +1,24 @@ +package q + +import "p" + +type _ = int +type a = struct{ x int } +type b = p.B + +type ( + _ = chan<- int + aa = interface{} + bb = p.BB +) + +// TODO(gri) We may want to put the '=' into a separate column if +// we have mixed (regular and alias) type declarations in a group. +type ( + _ chan<- int + _ = chan<- int + aa0 interface{} + aaa = interface{} + bb0 p.BB + bbb = p.BB +) diff --git a/src/cmd/gofmt/testdata/typealias.input b/src/cmd/gofmt/testdata/typealias.input new file mode 100644 index 0000000000..6e49328e34 --- /dev/null +++ b/src/cmd/gofmt/testdata/typealias.input @@ -0,0 +1,24 @@ +package q + +import "p" + +type _ = int +type a = struct{ x int } +type b = p.B + +type ( + _ = chan<- int + aa = interface{} + bb = p.BB +) + +// TODO(gri) We may want to put the '=' into a separate column if +// we have mixed (regular and alias) type declarations in a group. +type ( + _ chan<- int + _ = chan<- int + aa0 interface{} + aaa = interface{} + bb0 p.BB + bbb = p.BB +) From 3e119404372fd0d47de1458802b68522f593bf36 Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Fri, 16 Dec 2016 16:28:30 -0800 Subject: [PATCH 03/26] [dev.typealias] cmd/compile: recognize type aliases but complain for now (not yet supported) Added test file. For #18130. Change-Id: Ifcfd7cd1acf9dd6a2f4f3d85979d232bb6b8c6b1 Reviewed-on: https://go-review.googlesource.com/34988 Run-TryBot: Robert Griesemer Reviewed-by: Matthew Dempsky TryBot-Result: Gobot Gobot --- src/cmd/compile/internal/gc/noder.go | 4 ++ src/cmd/compile/internal/syntax/nodes.go | 1 + src/cmd/compile/internal/syntax/parser.go | 3 +- src/cmd/compile/internal/syntax/printer.go | 6 +- .../compile/internal/syntax/printer_test.go | 17 ++++++ test/alias2.go | 58 +++++++++++++++++++ 6 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 test/alias2.go diff --git a/src/cmd/compile/internal/gc/noder.go b/src/cmd/compile/internal/gc/noder.go index a501cb67b6..3f6fe20b6b 100644 --- a/src/cmd/compile/internal/gc/noder.go +++ b/src/cmd/compile/internal/gc/noder.go @@ -185,6 +185,10 @@ func (p *noder) constDecl(decl *syntax.ConstDecl) []*Node { } func (p *noder) typeDecl(decl *syntax.TypeDecl) *Node { + if decl.Alias { + yyerror("type alias declarations unimplemented") + } + name := typedcl0(p.name(decl.Name)) name.Name.Param.Pragma = Pragma(decl.Pragma) diff --git a/src/cmd/compile/internal/syntax/nodes.go b/src/cmd/compile/internal/syntax/nodes.go index fadba84bce..34524e5c09 100644 --- a/src/cmd/compile/internal/syntax/nodes.go +++ b/src/cmd/compile/internal/syntax/nodes.go @@ -74,6 +74,7 @@ type ( // Name Type TypeDecl struct { Name *Name + Alias bool Type Expr Group *Group // nil means not part of a group Pragma Pragma diff --git a/src/cmd/compile/internal/syntax/parser.go b/src/cmd/compile/internal/syntax/parser.go index 121dfb75e5..1185507238 100644 --- a/src/cmd/compile/internal/syntax/parser.go +++ b/src/cmd/compile/internal/syntax/parser.go @@ -325,7 +325,7 @@ func (p *parser) constDecl(group *Group) Decl { return d } -// TypeSpec = identifier Type . +// TypeSpec = identifier [ "=" ] Type . func (p *parser) typeDecl(group *Group) Decl { if trace { defer p.trace("typeDecl")() @@ -335,6 +335,7 @@ func (p *parser) typeDecl(group *Group) Decl { d.init(p) d.Name = p.name() + d.Alias = p.got(_Assign) d.Type = p.tryType() if d.Type == nil { p.syntax_error("in type declaration") diff --git a/src/cmd/compile/internal/syntax/printer.go b/src/cmd/compile/internal/syntax/printer.go index 0cacf1e5d4..43876a25c2 100644 --- a/src/cmd/compile/internal/syntax/printer.go +++ b/src/cmd/compile/internal/syntax/printer.go @@ -619,7 +619,11 @@ func (p *printer) printRawNode(n Node) { if n.Group == nil { p.print(_Type, blank) } - p.print(n.Name, blank, n.Type) + p.print(n.Name, blank) + if n.Alias { + p.print(_Assign, blank) + } + p.print(n.Type) case *VarDecl: if n.Group == nil { diff --git a/src/cmd/compile/internal/syntax/printer_test.go b/src/cmd/compile/internal/syntax/printer_test.go index 5c0fc776a1..a9969e610a 100644 --- a/src/cmd/compile/internal/syntax/printer_test.go +++ b/src/cmd/compile/internal/syntax/printer_test.go @@ -22,3 +22,20 @@ func TestPrint(t *testing.T) { Fprint(os.Stdout, ast, true) fmt.Println() } + +func TestPrintString(t *testing.T) { + for _, want := range []string{ + "package p", + "package p; type _ = int; type T1 = struct{}; type ( _ = *struct{}; T2 = float32 )", + // TODO(gri) expand + } { + ast, err := ParseBytes([]byte(want), nil, nil, 0) + if err != nil { + t.Error(err) + continue + } + if got := String(ast); got != want { + t.Errorf("%q: got %q", want, got) + } + } +} diff --git a/test/alias2.go b/test/alias2.go new file mode 100644 index 0000000000..25df7c287d --- /dev/null +++ b/test/alias2.go @@ -0,0 +1,58 @@ +// errorcheck + +// Copyright 2016 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. + +// Test basic restrictions on type aliases. + +// The compiler doesn't implement type aliases yet, +// so for now we get the same error (unimplemented) +// everywhere, OR-ed into the ERROR checks. +// TODO(gri) remove the need for "unimplemented" + +package p + +import ( + "reflect" + . "reflect" +) + +// Valid type alias declarations. + +type _ = int // ERROR "unimplemented" +type _ = struct{} // ERROR "unimplemented" +type _ = reflect.Value // ERROR "unimplemented" +type _ = Value // ERROR "unimplemented" + +type ( + a1 = int // ERROR "unimplemented" + a2 = struct{} // ERROR "unimplemented" + a3 = reflect.Value // ERROR "unimplemented" + a4 = Value // ERROR "unimplemented" +) + +func _() { + type _ = int // ERROR "unimplemented" + type _ = struct{} // ERROR "unimplemented" + type _ = reflect.Value // ERROR "unimplemented" + type _ = Value // ERROR "unimplemented" + + type ( + a1 = int // ERROR "unimplemented" + a2 = struct{} // ERROR "unimplemented" + a3 = reflect.Value // ERROR "unimplemented" + a4 = Value // ERROR "unimplemented" + ) +} + +// Invalid type alias declarations. + +type _ = reflect.ValueOf // ERROR "reflect.ValueOf is not a type|unimplemented" + +type b1 = struct{} // ERROR "unimplemented" +func (b1) m() {} // disabled ERROR "invalid receiver type" + +// TODO(gri) expand +// It appears that type-checking exits after some more severe errors, so we may +// need more test files. From a917097b5e5fd42bb4e6f4884a58544330d34984 Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Mon, 9 Jan 2017 19:41:19 -0500 Subject: [PATCH 04/26] [dev.typealias] go/build: add go1.9 build tag It's earlier than usual but this will help us put the type alias-aware code into x/tools without breaking clients on go1.6, go1.7, or (eventually) go1.8. Change-Id: I43e7ea804922de07d153c7e356cf95e2a11fc592 Reviewed-on: https://go-review.googlesource.com/35050 Run-TryBot: Russ Cox Reviewed-by: Robert Griesemer Reviewed-by: Brad Fitzpatrick --- src/go/build/build.go | 3 ++- src/go/build/doc.go | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/go/build/build.go b/src/go/build/build.go index da12d50bb1..31456ea343 100644 --- a/src/go/build/build.go +++ b/src/go/build/build.go @@ -290,7 +290,8 @@ func defaultContext() Context { // in all releases >= Go 1.x. Code that requires Go 1.x or later should // say "+build go1.x", and code that should only be built before Go 1.x // (perhaps it is the stub to use in that case) should say "+build !go1.x". - c.ReleaseTags = []string{"go1.1", "go1.2", "go1.3", "go1.4", "go1.5", "go1.6", "go1.7", "go1.8"} + // NOTE: If you add to this list, also update the doc comment in doc.go. + c.ReleaseTags = []string{"go1.1", "go1.2", "go1.3", "go1.4", "go1.5", "go1.6", "go1.7", "go1.8", "go1.9"} env := os.Getenv("CGO_ENABLED") if env == "" { diff --git a/src/go/build/doc.go b/src/go/build/doc.go index 979d0477df..422e1a5ffd 100644 --- a/src/go/build/doc.go +++ b/src/go/build/doc.go @@ -105,6 +105,7 @@ // - "go1.6", from Go version 1.6 onward // - "go1.7", from Go version 1.7 onward // - "go1.8", from Go version 1.8 onward +// - "go1.9", from Go version 1.9 onward // - any additional words listed in ctxt.BuildTags // // If a file's name, after stripping the extension and a possible _test suffix, From 80d8b69e95a4514f6567d3b314aa3434ec924363 Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Mon, 19 Dec 2016 17:55:47 -0800 Subject: [PATCH 05/26] [dev.typealias] go/types: implement type aliases Now a TypeName is just that: a name for a type (not just Named and Basic types as before). If it happens to be an alias, its type won't be a Named or Basic type, or it won't have the same name. We can determine this externally. It may be useful to provide a helper predicate to make that test easily accessible, but we can get to that if there's an actual need. The field/method lookup code has become more general an simpler, which is a good sign. The changes in methodset.go are symmetric to the changes in lookup.go. Known issue: Cycles created via alias types are not properly detected at the moment. For #18130. Change-Id: I90a3206be13116f89c221b5ab4d0f577eec6c78a Reviewed-on: https://go-review.googlesource.com/35091 Run-TryBot: Robert Griesemer TryBot-Result: Gobot Gobot Reviewed-by: Alan Donovan --- src/go/types/call.go | 3 - src/go/types/check_test.go | 2 +- src/go/types/decl.go | 193 ++++++++----------------------- src/go/types/lookup.go | 81 +++++++------ src/go/types/methodset.go | 50 ++++---- src/go/types/object.go | 48 ++------ src/go/types/resolver.go | 15 +-- src/go/types/testdata/decls0.src | 8 -- src/go/types/testdata/decls4.src | 149 ++++++++++++++++++++++++ src/go/types/typestring.go | 1 + src/go/types/typexpr.go | 60 ++++------ 11 files changed, 310 insertions(+), 300 deletions(-) create mode 100644 src/go/types/testdata/decls4.src diff --git a/src/go/types/call.go b/src/go/types/call.go index 8e5c5371f2..194b1fea10 100644 --- a/src/go/types/call.go +++ b/src/go/types/call.go @@ -275,8 +275,6 @@ func (check *Checker) selector(x *operand, e *ast.SelectorExpr) { // so we don't need a "package" mode for operands: package names // can only appear in qualified identifiers which are mapped to // selector expressions. - // (see also decl.go: checker.aliasDecl) - // TODO(gri) factor this code out and share with checker.aliasDecl if ident, ok := e.X.(*ast.Ident); ok { _, obj := check.scope.LookupParent(ident.Name, check.pos) if pname, _ := obj.(*PkgName); pname != nil { @@ -296,7 +294,6 @@ func (check *Checker) selector(x *operand, e *ast.SelectorExpr) { // ok to continue } check.recordUse(e.Sel, exp) - exp = original(exp) // avoid further errors if the imported object is an alias that's broken if exp == nil { diff --git a/src/go/types/check_test.go b/src/go/types/check_test.go index f844575269..24b3365717 100644 --- a/src/go/types/check_test.go +++ b/src/go/types/check_test.go @@ -68,11 +68,11 @@ var tests = [][]string{ {"testdata/decls1.src"}, {"testdata/decls2a.src", "testdata/decls2b.src"}, {"testdata/decls3.src"}, + {"testdata/decls4.src"}, {"testdata/const0.src"}, {"testdata/const1.src"}, {"testdata/constdecl.src"}, {"testdata/vardecl.src"}, - //{"testdata/aliasdecl.src"}, {"testdata/expr0.src"}, {"testdata/expr1.src"}, {"testdata/expr2.src"}, diff --git a/src/go/types/decl.go b/src/go/types/decl.go index 2472aa3434..7428f8f995 100644 --- a/src/go/types/decl.go +++ b/src/go/types/decl.go @@ -81,14 +81,10 @@ func (check *Checker) objDecl(obj Object, def *Named, path []*TypeName) { check.varDecl(obj, d.lhs, d.typ, d.init) case *TypeName: // invalid recursive types are detected via path - check.typeDecl(obj, d.typ, def, path) + check.typeDecl(obj, d.typ, def, path, d.alias) case *Func: // functions may be recursive - no need to track dependencies check.funcDecl(obj, d) - // Alias-related code. Keep for now. - // case *Alias: - // // aliases cannot be recursive - no need to track dependencies - // check.aliasDecl(obj, d) default: unreachable() } @@ -219,33 +215,42 @@ func (n *Named) setUnderlying(typ Type) { } } -func (check *Checker) typeDecl(obj *TypeName, typ ast.Expr, def *Named, path []*TypeName) { +func (check *Checker) typeDecl(obj *TypeName, typ ast.Expr, def *Named, path []*TypeName, alias bool) { assert(obj.typ == nil) // type declarations cannot use iota assert(check.iota == nil) - named := &Named{obj: obj} - def.setUnderlying(named) - obj.typ = named // make sure recursive type declarations terminate + if alias { - // determine underlying type of named - check.typExpr(typ, named, append(path, obj)) + obj.typ = Typ[Invalid] + obj.typ = check.typExpr(typ, nil, append(path, obj)) - // The underlying type of named may be itself a named type that is - // incomplete: - // - // type ( - // A B - // B *C - // C A - // ) - // - // The type of C is the (named) type of A which is incomplete, - // and which has as its underlying type the named type B. - // Determine the (final, unnamed) underlying type by resolving - // any forward chain (they always end in an unnamed type). - named.underlying = underlying(named.underlying) + } else { + + named := &Named{obj: obj} + def.setUnderlying(named) + obj.typ = named // make sure recursive type declarations terminate + + // determine underlying type of named + check.typExpr(typ, named, append(path, obj)) + + // The underlying type of named may be itself a named type that is + // incomplete: + // + // type ( + // A B + // B *C + // C A + // ) + // + // The type of C is the (named) type of A which is incomplete, + // and which has as its underlying type the named type B. + // Determine the (final, unnamed) underlying type by resolving + // any forward chain (they always end in an unnamed type). + named.underlying = underlying(named.underlying) + + } // check and add associated methods // TODO(gri) It's easy to create pathological cases where the @@ -268,21 +273,23 @@ func (check *Checker) addMethodDecls(obj *TypeName) { // spec: "If the base type is a struct type, the non-blank method // and field names must be distinct." - base := obj.typ.(*Named) - if t, _ := base.underlying.(*Struct); t != nil { - for _, fld := range t.fields { - if fld.name != "_" { - assert(mset.insert(fld) == nil) + base, _ := obj.typ.(*Named) // nil if receiver base type is type alias + if base != nil { + if t, _ := base.underlying.(*Struct); t != nil { + for _, fld := range t.fields { + if fld.name != "_" { + assert(mset.insert(fld) == nil) + } } } - } - // Checker.Files may be called multiple times; additional package files - // may add methods to already type-checked types. Add pre-existing methods - // so that we can detect redeclarations. - for _, m := range base.methods { - assert(m.name != "_") - assert(mset.insert(m) == nil) + // Checker.Files may be called multiple times; additional package files + // may add methods to already type-checked types. Add pre-existing methods + // so that we can detect redeclarations. + for _, m := range base.methods { + assert(m.name != "_") + assert(mset.insert(m) == nil) + } } // type-check methods @@ -295,7 +302,7 @@ func (check *Checker) addMethodDecls(obj *TypeName) { case *Var: check.errorf(m.pos, "field and method with the same name %s", m.name) case *Func: - check.errorf(m.pos, "method %s already declared for %s", m.name, base) + check.errorf(m.pos, "method %s already declared for %s", m.name, obj) default: unreachable() } @@ -303,9 +310,12 @@ func (check *Checker) addMethodDecls(obj *TypeName) { continue } } + + // type-check check.objDecl(m, nil, nil) + // methods with blank _ names cannot be found - don't keep them - if m.name != "_" { + if base != nil && m.name != "_" { base.methods = append(base.methods, m) } } @@ -333,106 +343,6 @@ func (check *Checker) funcDecl(obj *Func, decl *declInfo) { } } -// original returns the original Object if obj is an Alias; -// otherwise it returns obj. The result is never an Alias, -// but it may be nil. -func original(obj Object) Object { - // an alias stands for the original object; use that one instead - if alias, _ := obj.(*disabledAlias); alias != nil { - obj = alias.orig - // aliases always refer to non-alias originals - if _, ok := obj.(*disabledAlias); ok { - panic("original is an alias") - } - } - return obj -} - -func (check *Checker) aliasDecl(obj *disabledAlias, decl *declInfo) { - assert(obj.typ == nil) - - // alias declarations cannot use iota - assert(check.iota == nil) - - // assume alias is invalid to start with - obj.typ = Typ[Invalid] - - // rhs must be package-qualified identifer pkg.sel (see also call.go: checker.selector) - // TODO(gri) factor this code out and share with checker.selector - rhs := decl.init - var pkg *Package - var sel *ast.Ident - if sexpr, ok := rhs.(*ast.SelectorExpr); ok { - if ident, ok := sexpr.X.(*ast.Ident); ok { - _, obj := check.scope.LookupParent(ident.Name, check.pos) - if pname, _ := obj.(*PkgName); pname != nil { - assert(pname.pkg == check.pkg) - check.recordUse(ident, pname) - pname.used = true - pkg = pname.imported - sel = sexpr.Sel - } - } - } - if pkg == nil { - check.errorf(rhs.Pos(), "invalid alias: %v is not a package-qualified identifier", rhs) - return - } - - // qualified identifier must denote an exported object - orig := pkg.scope.Lookup(sel.Name) - if orig == nil || !orig.Exported() { - if !pkg.fake { - check.errorf(rhs.Pos(), "%s is not exported by package %s", sel.Name, pkg.name) - } - return - } - check.recordUse(sel, orig) - orig = original(orig) - - // avoid further errors if the imported object is an alias that's broken - if orig == nil { - return - } - - // An alias declaration must not refer to package unsafe. - if orig.Pkg() == Unsafe { - check.errorf(rhs.Pos(), "invalid alias: %s refers to package unsafe (%v)", obj.Name(), orig) - return - } - - // The original must be of the same kind as the alias declaration. - var why string - switch obj.kind { - case token.CONST: - if _, ok := orig.(*Const); !ok { - why = "constant" - } - case token.TYPE: - if _, ok := orig.(*TypeName); !ok { - why = "type" - } - case token.VAR: - if _, ok := orig.(*Var); !ok { - why = "variable" - } - case token.FUNC: - if _, ok := orig.(*Func); !ok { - why = "function" - } - default: - unreachable() - } - if why != "" { - check.errorf(rhs.Pos(), "invalid alias: %v is not a %s", orig, why) - return - } - - // alias is valid - obj.typ = orig.Type() - obj.orig = orig -} - func (check *Checker) declStmt(decl ast.Decl) { pkg := check.pkg @@ -534,16 +444,13 @@ func (check *Checker) declStmt(decl ast.Decl) { } case *ast.TypeSpec: - if s.Assign.IsValid() { - check.errorf(s.Assign, "type alias declarations not yet implemented") - } obj := NewTypeName(s.Name.Pos(), pkg, s.Name.Name, nil) // spec: "The scope of a type identifier declared inside a function // begins at the identifier in the TypeSpec and ends at the end of // the innermost containing block." scopePos := s.Name.Pos() check.declare(check.scope, s.Name, obj, scopePos) - check.typeDecl(obj, s.Type, nil, nil) + check.typeDecl(obj, s.Type, nil, nil, s.Assign.IsValid()) default: check.invalidAST(s.Pos(), "const, type, or var declaration expected") diff --git a/src/go/types/lookup.go b/src/go/types/lookup.go index 3caca5519b..ee8202d9e4 100644 --- a/src/go/types/lookup.go +++ b/src/go/types/lookup.go @@ -67,24 +67,22 @@ func lookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (o } typ, isPtr := deref(T) - named, _ := typ.(*Named) // *typ where typ is an interface has no methods. - if isPtr { - utyp := typ - if named != nil { - utyp = named.underlying - } - if _, ok := utyp.(*Interface); ok { - return - } + if isPtr && IsInterface(typ) { + return } // Start with typ as single entry at shallowest depth. - // If typ is not a named type, insert a nil type instead. - current := []embeddedType{{named, nil, isPtr, false}} + current := []embeddedType{{typ, nil, isPtr, false}} - // named types that we have seen already, allocated lazily + // Named types that we have seen already, allocated lazily. + // Used to avoid endless searches in case of recursive types. + // Since only Named types can be used for recursive types, we + // only need to track those. + // (If we ever allow type aliases to construct recursive types, + // we must use type identity rather than pointer equality for + // the map key comparison, as we do in consolidateMultiples.) var seen map[*Named]bool // search current depth @@ -93,11 +91,12 @@ func lookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (o // look for (pkg, name) in all types at current depth for _, e := range current { - // The very first time only, e.typ may be nil. - // In this case, we don't have a named type and - // we simply continue with the underlying type. - if e.typ != nil { - if seen[e.typ] { + typ := e.typ + + // If we have a named type, we may have associated methods. + // Look for those first. + if named, _ := typ.(*Named); named != nil { + if seen[named] { // We have seen this type before, at a more shallow depth // (note that multiples of this type at the current depth // were consolidated before). The type at that depth shadows @@ -108,10 +107,10 @@ func lookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (o if seen == nil { seen = make(map[*Named]bool) } - seen[e.typ] = true + seen[named] = true // look for a matching attached method - if i, m := lookupMethod(e.typ.methods, pkg, name); m != nil { + if i, m := lookupMethod(named.methods, pkg, name); m != nil { // potential match assert(m.typ != nil) index = concat(e.index, i) @@ -124,7 +123,7 @@ func lookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (o } // continue with underlying type - typ = e.typ.underlying + typ = named.underlying } switch t := typ.(type) { @@ -147,16 +146,15 @@ func lookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (o // we have a name collision on the same depth; in either // case we don't need to look further). // Embedded fields are always of the form T or *T where - // T is a named type. If e.typ appeared multiple times at + // T is a type name. If e.typ appeared multiple times at // this depth, f.typ appears multiple times at the next // depth. if obj == nil && f.anonymous { - // Ignore embedded basic types - only user-defined - // named types can have methods or struct fields. typ, isPtr := deref(f.typ) - if t, _ := typ.(*Named); t != nil { - next = append(next, embeddedType{t, concat(e.index, i), e.indirect || isPtr, e.multiples}) - } + // TODO(gri) optimization: ignore types that can't + // have fields or methods (only Named, Struct, and + // Interface types need to be considered). + next = append(next, embeddedType{typ, concat(e.index, i), e.indirect || isPtr, e.multiples}) } } @@ -193,12 +191,12 @@ func lookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (o return nil, nil, false // not found } -// embeddedType represents an embedded named type +// embeddedType represents an embedded type type embeddedType struct { - typ *Named // nil means use the outer typ variable instead - index []int // embedded field indices, starting with index at depth 0 - indirect bool // if set, there was a pointer indirection on the path to this field - multiples bool // if set, typ appears multiple times at this depth + typ Type + index []int // embedded field indices, starting with index at depth 0 + indirect bool // if set, there was a pointer indirection on the path to this field + multiples bool // if set, typ appears multiple times at this depth } // consolidateMultiples collects multiple list entries with the same type @@ -209,10 +207,10 @@ func consolidateMultiples(list []embeddedType) []embeddedType { return list // at most one entry - nothing to do } - n := 0 // number of entries w/ unique type - prev := make(map[*Named]int) // index at which type was previously seen + n := 0 // number of entries w/ unique type + prev := make(map[Type]int) // index at which type was previously seen for _, e := range list { - if i, found := prev[e.typ]; found { + if i, found := lookupType(prev, e.typ); found { list[i].multiples = true // ignore this entry } else { @@ -224,6 +222,21 @@ func consolidateMultiples(list []embeddedType) []embeddedType { return list[:n] } +func lookupType(m map[Type]int, typ Type) (int, bool) { + // fast path: maybe the types are equal + if i, found := m[typ]; found { + return i, true + } + + for t, i := range m { + if Identical(t, typ) { + return i, true + } + } + + return 0, false +} + // MissingMethod returns (nil, false) if V implements T, otherwise it // returns a missing method required by T and whether it is missing or // just has the wrong type. diff --git a/src/go/types/methodset.go b/src/go/types/methodset.go index b27f2dac34..4f791d9d51 100644 --- a/src/go/types/methodset.go +++ b/src/go/types/methodset.go @@ -72,24 +72,22 @@ func NewMethodSet(T Type) *MethodSet { var base methodSet typ, isPtr := deref(T) - named, _ := typ.(*Named) // *typ where typ is an interface has no methods. - if isPtr { - utyp := typ - if named != nil { - utyp = named.underlying - } - if _, ok := utyp.(*Interface); ok { - return &emptyMethodSet - } + if isPtr && IsInterface(typ) { + return &emptyMethodSet } // Start with typ as single entry at shallowest depth. - // If typ is not a named type, insert a nil type instead. - current := []embeddedType{{named, nil, isPtr, false}} + current := []embeddedType{{typ, nil, isPtr, false}} - // named types that we have seen already, allocated lazily + // Named types that we have seen already, allocated lazily. + // Used to avoid endless searches in case of recursive types. + // Since only Named types can be used for recursive types, we + // only need to track those. + // (If we ever allow type aliases to construct recursive types, + // we must use type identity rather than pointer equality for + // the map key comparison, as we do in consolidateMultiples.) var seen map[*Named]bool // collect methods at current depth @@ -101,11 +99,12 @@ func NewMethodSet(T Type) *MethodSet { var mset methodSet for _, e := range current { - // The very first time only, e.typ may be nil. - // In this case, we don't have a named type and - // we simply continue with the underlying type. - if e.typ != nil { - if seen[e.typ] { + typ := e.typ + + // If we have a named type, we may have associated methods. + // Look for those first. + if named, _ := typ.(*Named); named != nil { + if seen[named] { // We have seen this type before, at a more shallow depth // (note that multiples of this type at the current depth // were consolidated before). The type at that depth shadows @@ -116,12 +115,12 @@ func NewMethodSet(T Type) *MethodSet { if seen == nil { seen = make(map[*Named]bool) } - seen[e.typ] = true + seen[named] = true - mset = mset.add(e.typ.methods, e.index, e.indirect, e.multiples) + mset = mset.add(named.methods, e.index, e.indirect, e.multiples) // continue with underlying type - typ = e.typ.underlying + typ = named.underlying } switch t := typ.(type) { @@ -130,16 +129,15 @@ func NewMethodSet(T Type) *MethodSet { fset = fset.add(f, e.multiples) // Embedded fields are always of the form T or *T where - // T is a named type. If typ appeared multiple times at + // T is a type name. If typ appeared multiple times at // this depth, f.Type appears multiple times at the next // depth. if f.anonymous { - // Ignore embedded basic types - only user-defined - // named types can have methods or struct fields. typ, isPtr := deref(f.typ) - if t, _ := typ.(*Named); t != nil { - next = append(next, embeddedType{t, concat(e.index, i), e.indirect || isPtr, e.multiples}) - } + // TODO(gri) optimization: ignore types that can't + // have fields or methods (only Named, Struct, and + // Interface types need to be considered). + next = append(next, embeddedType{typ, concat(e.index, i), e.indirect || isPtr, e.multiples}) } } diff --git a/src/go/types/object.go b/src/go/types/object.go index 6c0c5c4a24..57b82c5983 100644 --- a/src/go/types/object.go +++ b/src/go/types/object.go @@ -154,7 +154,7 @@ func NewConst(pos token.Pos, pkg *Package, name string, typ Type, val constant.V func (obj *Const) Val() constant.Value { return obj.val } func (*Const) isDependency() {} // a constant may be a dependency of an initialization expression -// A TypeName represents a declared type. +// A TypeName represents a name for a (named or alias) type. type TypeName struct { object } @@ -215,28 +215,6 @@ func (obj *Func) FullName() string { func (obj *Func) Scope() *Scope { return obj.typ.(*Signature).scope } func (*Func) isDependency() {} // a function may be a dependency of an initialization expression -// An Alias represents a declared alias. -type disabledAlias struct { - object - orig Object // aliased constant, type, variable, or function; never an alias - kind token.Token // token.CONST, token.TYPE, token.VAR, or token.FUNC (only needed during resolve phase) -} - -func disabledNewAlias(pos token.Pos, pkg *Package, name string, orig Object) *disabledAlias { - var typ Type = Typ[Invalid] - if orig != nil { - typ = orig.Type() - } - // No need to set a valid Alias.kind - that field is only used during identifier - // resolution (1st type-checker pass). We could store the field outside but it's - // easier to keep it here. - return &disabledAlias{object{nil, pos, pkg, name, typ, 0, token.NoPos}, orig, token.ILLEGAL} -} - -// Orig returns the aliased object, or nil if there was an error. -// The returned object is never an Alias. -func (obj *disabledAlias) disabledOrig() Object { return obj.orig } - // A Label represents a declared label. type Label struct { object @@ -295,10 +273,6 @@ func writeObject(buf *bytes.Buffer, obj Object, qf Qualifier) { } return - // Alias-related code. Keep for now. - // case *Alias: - // buf.WriteString("alias") - case *Label: buf.WriteString("label") typ = nil @@ -322,6 +296,9 @@ func writeObject(buf *bytes.Buffer, obj Object, qf Qualifier) { writePackage(buf, obj.Pkg(), qf) } buf.WriteString(obj.Name()) + + // TODO(gri) indicate type alias if we have one + if typ != nil { buf.WriteByte(' ') WriteType(buf, typ, qf) @@ -353,15 +330,14 @@ func ObjectString(obj Object, qf Qualifier) string { return buf.String() } -func (obj *PkgName) String() string { return ObjectString(obj, nil) } -func (obj *Const) String() string { return ObjectString(obj, nil) } -func (obj *TypeName) String() string { return ObjectString(obj, nil) } -func (obj *Var) String() string { return ObjectString(obj, nil) } -func (obj *Func) String() string { return ObjectString(obj, nil) } -func (obj *disabledAlias) String() string { return ObjectString(obj, nil) } -func (obj *Label) String() string { return ObjectString(obj, nil) } -func (obj *Builtin) String() string { return ObjectString(obj, nil) } -func (obj *Nil) String() string { return ObjectString(obj, nil) } +func (obj *PkgName) String() string { return ObjectString(obj, nil) } +func (obj *Const) String() string { return ObjectString(obj, nil) } +func (obj *TypeName) String() string { return ObjectString(obj, nil) } +func (obj *Var) String() string { return ObjectString(obj, nil) } +func (obj *Func) String() string { return ObjectString(obj, nil) } +func (obj *Label) String() string { return ObjectString(obj, nil) } +func (obj *Builtin) String() string { return ObjectString(obj, nil) } +func (obj *Nil) String() string { return ObjectString(obj, nil) } func writeFuncName(buf *bytes.Buffer, f *Func, qf Qualifier) { if f.typ != nil { diff --git a/src/go/types/resolver.go b/src/go/types/resolver.go index d37f93de45..939f70a9ca 100644 --- a/src/go/types/resolver.go +++ b/src/go/types/resolver.go @@ -14,13 +14,14 @@ import ( "unicode" ) -// A declInfo describes a package-level const, type, var, func, or alias declaration. +// A declInfo describes a package-level const, type, var, or func declaration. type declInfo struct { file *Scope // scope of file containing this declaration lhs []*Var // lhs of n:1 variable declarations, or nil typ ast.Expr // type, or nil init ast.Expr // init/orig expression, or nil fdecl *ast.FuncDecl // func declaration, or nil + alias bool // type alias declaration // The deps field tracks initialization expression dependencies. // As a special (overloaded) case, it also tracks dependencies of @@ -274,13 +275,6 @@ func (check *Checker) collectObjects() { check.declare(fileScope, nil, obj, token.NoPos) } - // Alias-related code. Keep for now. - // case *ast.AliasSpec: - // obj := NewAlias(s.Name.Pos(), pkg, s.Name.Name, nil) - // obj.typ = nil // unresolved - // obj.kind = d.Tok - // check.declarePkgObj(s.Name, obj, &declInfo{file: fileScope, init: s.Orig}) - case *ast.ValueSpec: switch d.Tok { case token.CONST: @@ -346,11 +340,8 @@ func (check *Checker) collectObjects() { } case *ast.TypeSpec: - if s.Assign.IsValid() { - check.errorf(s.Assign, "type alias declarations not yet implemented") - } obj := NewTypeName(s.Name.Pos(), pkg, s.Name.Name, nil) - check.declarePkgObj(s.Name, obj, &declInfo{file: fileScope, typ: s.Type}) + check.declarePkgObj(s.Name, obj, &declInfo{file: fileScope, typ: s.Type, alias: s.Assign.IsValid()}) default: check.invalidAST(s.Pos(), "unknown ast.Spec node %T", s) diff --git a/src/go/types/testdata/decls0.src b/src/go/types/testdata/decls0.src index 3ed1b976e5..d4df386b13 100644 --- a/src/go/types/testdata/decls0.src +++ b/src/go/types/testdata/decls0.src @@ -208,11 +208,3 @@ func (BlankT) _() {} func (BlankT) _(int) {} func (BlankT) _() int { return 0 } func (BlankT) _(int) int { return 0} - -// type alias declarations -// TODO(gri) complete this -type ( - __ = /* ERROR not yet implemented */ int - a0 = /* ERROR not yet implemented */ int - a1 = /* ERROR not yet implemented */ struct{} -) diff --git a/src/go/types/testdata/decls4.src b/src/go/types/testdata/decls4.src new file mode 100644 index 0000000000..6030edb7cb --- /dev/null +++ b/src/go/types/testdata/decls4.src @@ -0,0 +1,149 @@ +// Copyright 2016 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. + +// type aliases + +package decls4 + +type ( + T0 [10]int + T1 []byte + T2 struct { + x int + } + T3 interface{ + m() T2 + } + T4 func(int, T0) chan T2 +) + +type ( + Ai = int + A0 = T0 + A1 = T1 + A2 = T2 + A3 = T3 + A4 = T4 + + A10 = [10]int + A11 = []byte + A12 = struct { + x int + } + A13 = interface{ + m() A2 + } + A14 = func(int, A0) chan A2 +) + +// check assignment compatibility due to equality of types +var ( + xi_ int + ai Ai = xi_ + + x0 T0 + a0 A0 = x0 + + x1 T1 + a1 A1 = x1 + + x2 T2 + a2 A2 = x2 + + x3 T3 + a3 A3 = x3 + + x4 T4 + a4 A4 = x4 +) + +// alias receiver types +func (Ai /* ERROR "invalid receiver" */) m1() {} +func (T0) m1() {} +func (A0) m1 /* ERROR already declared */ () {} +func (A0) m2 () {} +func (A10 /* ERROR invalid receiver */ ) m1() {} + +// x0 has methods m1, m2 declared via receiver type names T0 and A0 +var _ interface{ m1(); m2() } = x0 + +// cycles +type ( + C2 /* ERROR illegal cycle */ = C2 + C3 /* ERROR illegal cycle */ = C4 + C4 = C3 + C5 struct { + f *C6 + } + C6 = C5 + C7 /* ERROR illegal cycle */ struct { + f C8 + } + C8 = C7 +) + +// embedded fields +var ( + s0 struct { T0 } + s1 struct { A0 } = s0 /* ERROR cannot use */ // embedded field names are different +) + +// embedding and lookup of fields and methods +func _(s struct{A0}) { s.A0 = x0 } + +type eX struct{xf int} + +func (eX) xm() + +type eY = struct{eX} // field/method set of eY includes xf, xm + +type eZ = *struct{eX} // field/method set of eZ includes xf, xm + +type eA struct { + eX // eX contributes xf, xm to eA +} + +type eA2 struct { + *eX // *eX contributes xf, xm to eA +} + +type eB struct { + eY // eY contributes xf, xm to eB +} + +type eB2 struct { + *eY // *eY contributes xf, xm to eB +} + +type eC struct { + eZ // eZ contributes xf, xm to eC +} + +var ( + _ = eA{}.xf + _ = eA{}.xm + _ = eA2{}.xf + _ = eA2{}.xm + _ = eB{}.xf + _ = eB{}.xm + _ = eB2{}.xf + _ = eB2{}.xm + _ = eC{}.xf + _ = eC{}.xm +) + +// ambiguous selectors due to embedding via type aliases +type eD struct { + eY + eZ +} + +var ( + _ = eD /* ERROR ambiguous selector */ {}.xf + _ = eD /* ERROR ambiguous selector */ {}.xm +) + +var ( + _ interface{ xm() } = eD /* ERROR missing method xm */ {} +) \ No newline at end of file diff --git a/src/go/types/typestring.go b/src/go/types/typestring.go index 47378e744c..0f8a7adc24 100644 --- a/src/go/types/typestring.go +++ b/src/go/types/typestring.go @@ -56,6 +56,7 @@ func RelativeTo(pkg *Package) Qualifier { // This flag is exported in the x/tools/go/types package. We don't // need it at the moment in the std repo and so we don't export it // anymore. We should eventually try to remove it altogether. +// TODO(gri) remove this var gcCompatibilityMode bool // TypeString returns the string representation of typ. diff --git a/src/go/types/typexpr.go b/src/go/types/typexpr.go index ecc0a7da02..1e906fc4d8 100644 --- a/src/go/types/typexpr.go +++ b/src/go/types/typexpr.go @@ -45,17 +45,6 @@ func (check *Checker) ident(x *operand, e *ast.Ident, def *Named, path []*TypeNa delete(check.unusedDotImports[scope], pkg) } - // Alias-related code. Keep for now. - // An alias stands for the original object; use that one instead. - // TODO(gri) We should be able to factor out the Typ[Invalid] test. - // if alias, _ := obj.(*Alias); alias != nil { - // obj = original(obj) - // if obj == nil || typ == Typ[Invalid] { - // return - // } - // assert(typ == obj.Type()) - // } - switch obj := obj.(type) { case *PkgName: check.errorf(e.Pos(), "use of package %s not in selector", obj.name) @@ -661,47 +650,41 @@ func (check *Checker) structType(styp *Struct, e *ast.StructType, path []*TypeNa } } else { // anonymous field - name := anonymousFieldIdent(f.Type) + // spec: "An embedded type must be specified as a type name T or as a pointer + // to a non-interface type name *T, and T itself may not be a pointer type." pos := f.Type.Pos() + name := anonymousFieldIdent(f.Type) + if name == nil { + check.invalidAST(pos, "anonymous field type %s has no name", f.Type) + continue + } t, isPtr := deref(typ) - switch t := t.(type) { + // Because we have a name, typ must be of the form T or *T, where T is the name + // of a (named or alias) type, and t (= deref(typ)) must be the type of T. + switch t := t.Underlying().(type) { case *Basic: if t == Typ[Invalid] { // error was reported before continue } + // unsafe.Pointer is treated like a regular pointer if t.kind == UnsafePointer { check.errorf(pos, "anonymous field type cannot be unsafe.Pointer") continue } - add(f, name, true, pos) - case *Named: - // spec: "An embedded type must be specified as a type name - // T or as a pointer to a non-interface type name *T, and T - // itself may not be a pointer type." - switch u := t.underlying.(type) { - case *Basic: - // unsafe.Pointer is treated like a regular pointer - if u.kind == UnsafePointer { - check.errorf(pos, "anonymous field type cannot be unsafe.Pointer") - continue - } - case *Pointer: - check.errorf(pos, "anonymous field type cannot be a pointer") + case *Pointer: + check.errorf(pos, "anonymous field type cannot be a pointer") + continue + + case *Interface: + if isPtr { + check.errorf(pos, "anonymous field type cannot be a pointer to an interface") continue - case *Interface: - if isPtr { - check.errorf(pos, "anonymous field type cannot be a pointer to an interface") - continue - } } - add(f, name, true, pos) - - default: - check.invalidAST(pos, "anonymous field type %s must be named", typ) } + add(f, name, true, pos) } } @@ -714,7 +697,10 @@ func anonymousFieldIdent(e ast.Expr) *ast.Ident { case *ast.Ident: return e case *ast.StarExpr: - return anonymousFieldIdent(e.X) + // *T is valid, but **T is not + if _, ok := e.X.(*ast.StarExpr); !ok { + return anonymousFieldIdent(e.X) + } case *ast.SelectorExpr: return e.Sel } From c80748e3894b5623681fc5f1059ffdbd2cff6b7c Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Thu, 22 Dec 2016 09:48:43 -0800 Subject: [PATCH 06/26] [dev.typealias] go/types: remove some more vestiges of prior alias implementation For #18130. Change-Id: Ibec8efd158d32746978242910dc71e5ed23e9d91 Reviewed-on: https://go-review.googlesource.com/35092 Run-TryBot: Robert Griesemer TryBot-Result: Gobot Gobot Reviewed-by: Alan Donovan --- src/go/types/api_test.go | 152 --------------------------------------- src/go/types/call.go | 5 -- 2 files changed, 157 deletions(-) diff --git a/src/go/types/api_test.go b/src/go/types/api_test.go index 1208eb8b3a..92c6d75e70 100644 --- a/src/go/types/api_test.go +++ b/src/go/types/api_test.go @@ -1295,155 +1295,3 @@ func f(x int) { y := x; print(y) } } } } - -// Alias-related code. Keep for now. -/* -func TestAliases(t *testing.T) { - testenv.MustHaveGoBuild(t) - - const src = ` -package b - -import ( - "./testdata/alias" - a "./testdata/alias" - "math" -) - -const ( - c1 = alias.Pi1 - c2 => a.Pi1 - c3 => a.Pi2 - c4 => math.Pi -) - -var ( - v1 => alias.Default - v2 => a.Default - v3 = f1 -) - -type ( - t1 => alias.Context - t2 => a.Context -) - -func f1 => alias.Sin -func f2 => a.Sin - -func _() { - assert(c1 == alias.Pi1 && c2 == a.Pi1 && c3 == a.Pi2 && c4 == math.Pi) - assert(c2 == c2 && c2 == c3 && c3 == c4) - v1 = v2 // must be assignable - var _ *t1 = new(t2) // must be assignable - var _ t2 = alias.Default - f1(1) // must be callable - f2(1) - _ = alias.Sin(1) - _ = a.Sin(1) -} -` - - if out := compile(t, "testdata", "alias.go"); out != "" { - defer os.Remove(out) - } - - DefPredeclaredTestFuncs() // declare assert built-in for testing - mustTypecheck(t, "Aliases", src, nil) -} - -func compile(t *testing.T, dirname, filename string) string { - cmd := exec.Command(testenv.GoToolPath(t), "tool", "compile", filename) - cmd.Dir = dirname - out, err := cmd.CombinedOutput() - if err != nil { - t.Logf("%s", out) - t.Fatalf("go tool compile %s failed: %s", filename, err) - } - // filename should end with ".go" - return filepath.Join(dirname, filename[:len(filename)-2]+"o") -} - -func TestAliasDefUses(t *testing.T) { - testenv.MustHaveGoBuild(t) - - const src = ` -package p - -import( - "go/build" - "go/types" -) - -// Defs -const Invalid => types.Invalid -type Struct => types.Struct -var Default => build.Default -func Implements => types.Implements - -// Uses -const _ = Invalid -var _ types.Struct = Struct{} // types must be identical -var _ build.Context = Default -var _ = Implements(nil, nil) -` - - info := Info{ - Defs: make(map[*ast.Ident]Object), - Uses: make(map[*ast.Ident]Object), - } - mustTypecheck(t, "TestAliasDefUses", src, &info) - - // verify Defs - defs := map[string]string{ - "Invalid": "types.Invalid", - "Struct": "types.Struct", - "Default": "build.Default", - "Implements": "types.Implements", - } - - for ident, obj := range info.Defs { - if alias, ok := obj.(*Alias); ok { - if want := defs[ident.Name]; want != "" { - orig := alias.Orig() - if got := orig.Pkg().Name() + "." + orig.Name(); got != want { - t.Errorf("%v: got %v, want %v", ident, got, want) - } - delete(defs, ident.Name) // mark as found - } else { - t.Errorf("unexpected alias def of %v", ident) - } - } - } - - if len(defs) != 0 { - t.Errorf("missing aliases: %v", defs) - } - - // verify Uses - uses := map[string]string{ - "Invalid": "types.Invalid", - "Struct": "types.Struct", - "Default": "build.Default", - "Implements": "types.Implements", - } - - for ident, obj := range info.Uses { - if alias, ok := obj.(*Alias); ok { - if want := uses[ident.Name]; want != "" { - orig := alias.Orig() - if got := orig.Pkg().Name() + "." + orig.Name(); got != want { - t.Errorf("%v: got %v, want %v", ident, got, want) - } - delete(uses, ident.Name) // mark as found - } else { - t.Errorf("unexpected alias use of %v", ident) - } - } - } - - if len(uses) != 0 { - t.Errorf("missing aliases: %v", defs) - } -} -*/ diff --git a/src/go/types/call.go b/src/go/types/call.go index 194b1fea10..7f5823c829 100644 --- a/src/go/types/call.go +++ b/src/go/types/call.go @@ -295,11 +295,6 @@ func (check *Checker) selector(x *operand, e *ast.SelectorExpr) { } check.recordUse(e.Sel, exp) - // avoid further errors if the imported object is an alias that's broken - if exp == nil { - goto Error - } - // Simplified version of the code for *ast.Idents: // - imported objects are always fully initialized switch exp := exp.(type) { From aa1f0681bc34da2088fec08773eacebc3aee7391 Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Thu, 22 Dec 2016 11:27:48 -0800 Subject: [PATCH 07/26] [dev.typealias] go/types: improved Object printing - added internal isAlias predicated and test - use it for improved Object printing - when printing a basic type object, don't repeat type name (i.e., print "type int" rather than "type int int") - added another test to testdata/decls4.src For #18130. Change-Id: Ice9517c0065a2cc465c6d12f87cd27c01ef801e6 Reviewed-on: https://go-review.googlesource.com/35093 Run-TryBot: Robert Griesemer TryBot-Result: Gobot Gobot Reviewed-by: Alan Donovan --- src/go/types/example_test.go | 4 +-- src/go/types/object.go | 52 ++++++++++++++++++++++++++++---- src/go/types/object_test.go | 36 ++++++++++++++++++++++ src/go/types/predicates.go | 2 +- src/go/types/testdata/decls4.src | 1 + 5 files changed, 86 insertions(+), 9 deletions(-) create mode 100644 src/go/types/object_test.go diff --git a/src/go/types/example_test.go b/src/go/types/example_test.go index 8882e5063a..2a2fb3fc59 100644 --- a/src/go/types/example_test.go +++ b/src/go/types/example_test.go @@ -239,10 +239,10 @@ func fib(x int) int { // type S string: // defined at fib.go:4:6 // used at 6:23 - // type int int: + // type int: // defined at - // used at 8:12, 8:17 - // type string string: + // type string: // defined at - // used at 4:8 // var b S: diff --git a/src/go/types/object.go b/src/go/types/object.go index 57b82c5983..9a1740825f 100644 --- a/src/go/types/object.go +++ b/src/go/types/object.go @@ -163,6 +163,30 @@ func NewTypeName(pos token.Pos, pkg *Package, name string, typ Type) *TypeName { return &TypeName{object{nil, pos, pkg, name, typ, 0, token.NoPos}} } +func (obj *TypeName) isAlias() bool { + switch t := obj.typ.(type) { + case nil: + return false + case *Basic: + // It would seem that we should be able to look for different names here; + // but the names of universeByte/Rune are "byte" and "rune", respectively. + // We do this so that we get better error messages. However, general alias + // types don't have that name information and thus behave differently when + // reporting errors (we won't see the alias name, only the original name). + // Maybe we should remove the special handling for the predeclared types + // as well to be consistent (at the cost of slightly less clear error + // messages when byte/rune are involved). + // This also plays out in the implementation of the Identical(Type, Type) + // predicate. + // TODO(gri) consider possible clean up + return t == universeByte || t == universeRune + case *Named: + return obj != t.obj + default: + return true + } +} + // A Variable represents a declared variable (including function parameters and results, and struct fields). type Var struct { object @@ -242,7 +266,9 @@ type Nil struct { } func writeObject(buf *bytes.Buffer, obj Object, qf Qualifier) { + var tname *TypeName typ := obj.Type() + switch obj := obj.(type) { case *PkgName: fmt.Fprintf(buf, "package %s", obj.Name()) @@ -255,8 +281,8 @@ func writeObject(buf *bytes.Buffer, obj Object, qf Qualifier) { buf.WriteString("const") case *TypeName: + tname = obj buf.WriteString("type") - typ = typ.Underlying() case *Var: if obj.isField { @@ -297,12 +323,26 @@ func writeObject(buf *bytes.Buffer, obj Object, qf Qualifier) { } buf.WriteString(obj.Name()) - // TODO(gri) indicate type alias if we have one - - if typ != nil { - buf.WriteByte(' ') - WriteType(buf, typ, qf) + if typ == nil { + return } + + if tname != nil { + // We have a type object: Don't print anything more for + // basic types since there's no more information (names + // are the same; see also comment in TypeName.isAlias). + if _, ok := typ.(*Basic); ok { + return + } + if tname.isAlias() { + buf.WriteString(" =") + } else { + typ = typ.Underlying() + } + } + + buf.WriteByte(' ') + WriteType(buf, typ, qf) } func writePackage(buf *bytes.Buffer, pkg *Package, qf Qualifier) { diff --git a/src/go/types/object_test.go b/src/go/types/object_test.go new file mode 100644 index 0000000000..c9fc7b7258 --- /dev/null +++ b/src/go/types/object_test.go @@ -0,0 +1,36 @@ +// Copyright 2016 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. + +package types + +import "testing" + +func TestIsAlias(t *testing.T) { + check := func(obj *TypeName, want bool) { + if got := obj.isAlias(); got != want { + t.Errorf("%v: got isAlias = %v; want %v", obj, got, want) + } + } + + // predeclared types + for _, name := range Universe.Names() { + if obj, _ := Universe.Lookup(name).(*TypeName); obj != nil { + check(obj, name == "byte" || name == "rune") + } + } + + // various other types + t0 := NewTypeName(0, nil, "t0", nil) + check(t0, false) // no type yet + + t1 := NewTypeName(0, nil, "t1", nil) + n1 := NewNamed(t1, new(Struct), nil) + check(t1, false) // type name refers to named type and vice versa + + t2 := NewTypeName(0, nil, "t2", new(Interface)) + check(t2, true) // type name refers to unnamed type + + t3 := NewTypeName(0, nil, "t3", n1) + check(t3, true) // type name refers to named type with different type name (true alias) +} diff --git a/src/go/types/predicates.go b/src/go/types/predicates.go index 21fd81e3c2..707fb9619d 100644 --- a/src/go/types/predicates.go +++ b/src/go/types/predicates.go @@ -139,7 +139,7 @@ func identical(x, y Type, cmpTags bool, p *ifacePair) bool { case *Basic: // Basic types are singletons except for the rune and byte // aliases, thus we cannot solely rely on the x == y check - // above. + // above. See also comment in TypeName.isAlias. if y, ok := y.(*Basic); ok { return x.kind == y.kind } diff --git a/src/go/types/testdata/decls4.src b/src/go/types/testdata/decls4.src index 6030edb7cb..5e5e2e940b 100644 --- a/src/go/types/testdata/decls4.src +++ b/src/go/types/testdata/decls4.src @@ -63,6 +63,7 @@ func (Ai /* ERROR "invalid receiver" */) m1() {} func (T0) m1() {} func (A0) m1 /* ERROR already declared */ () {} func (A0) m2 () {} +func (A3 /* ERROR invalid receiver */ ) m1 () {} func (A10 /* ERROR invalid receiver */ ) m1() {} // x0 has methods m1, m2 declared via receiver type names T0 and A0 From 5ceec42dc0db9342bc4f37503844b46cf2689c65 Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Thu, 22 Dec 2016 14:42:05 -0800 Subject: [PATCH 08/26] [dev.typealias] go/types: export TypeName.IsAlias so clients can use it For #18130. Change-Id: I634eaaeaa11e92fc31219d70419fdb4a7aa6e0b4 Reviewed-on: https://go-review.googlesource.com/35099 Run-TryBot: Robert Griesemer Reviewed-by: Alan Donovan TryBot-Result: Gobot Gobot --- src/go/types/object.go | 6 +++--- src/go/types/object_test.go | 4 ++-- src/go/types/predicates.go | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/go/types/object.go b/src/go/types/object.go index 9a1740825f..f4f628f876 100644 --- a/src/go/types/object.go +++ b/src/go/types/object.go @@ -163,7 +163,7 @@ func NewTypeName(pos token.Pos, pkg *Package, name string, typ Type) *TypeName { return &TypeName{object{nil, pos, pkg, name, typ, 0, token.NoPos}} } -func (obj *TypeName) isAlias() bool { +func (obj *TypeName) IsAlias() bool { switch t := obj.typ.(type) { case nil: return false @@ -330,11 +330,11 @@ func writeObject(buf *bytes.Buffer, obj Object, qf Qualifier) { if tname != nil { // We have a type object: Don't print anything more for // basic types since there's no more information (names - // are the same; see also comment in TypeName.isAlias). + // are the same; see also comment in TypeName.IsAlias). if _, ok := typ.(*Basic); ok { return } - if tname.isAlias() { + if tname.IsAlias() { buf.WriteString(" =") } else { typ = typ.Underlying() diff --git a/src/go/types/object_test.go b/src/go/types/object_test.go index c9fc7b7258..70656ae022 100644 --- a/src/go/types/object_test.go +++ b/src/go/types/object_test.go @@ -8,8 +8,8 @@ import "testing" func TestIsAlias(t *testing.T) { check := func(obj *TypeName, want bool) { - if got := obj.isAlias(); got != want { - t.Errorf("%v: got isAlias = %v; want %v", obj, got, want) + if got := obj.IsAlias(); got != want { + t.Errorf("%v: got IsAlias = %v; want %v", obj, got, want) } } diff --git a/src/go/types/predicates.go b/src/go/types/predicates.go index 707fb9619d..c3b87dd9cd 100644 --- a/src/go/types/predicates.go +++ b/src/go/types/predicates.go @@ -139,7 +139,7 @@ func identical(x, y Type, cmpTags bool, p *ifacePair) bool { case *Basic: // Basic types are singletons except for the rune and byte // aliases, thus we cannot solely rely on the x == y check - // above. See also comment in TypeName.isAlias. + // above. See also comment in TypeName.IsAlias. if y, ok := y.(*Basic); ok { return x.kind == y.kind } From 49de5f035169526675b9d5897753d257bf2c7965 Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Thu, 22 Dec 2016 13:04:04 -0800 Subject: [PATCH 09/26] [dev.typealias] cmd/compile, go/importer: define export format and implement importing of type aliases This defines the (tentative) export/import format for type aliases. The compiler doesn't support type aliases yet, so while the code is present it is guarded with a flag. The export format for embedded (anonymous) fields now has three modes (mode 3 is new): 1) The original type name and the anonymous field name are the same, and the name is exported: we don't need the field name and write "" instead 2) The original type name and the anonymous field name are the same, and the name is not exported: we don't need the field name and write "?" instead, indicating that there is package info 3) The original type name and the anonymous field name are different: we do need the field name and write "@" followed by the field name (and possible package info) For #18130. Change-Id: I790dad826757233fa71396a210f966c6256b75d3 Reviewed-on: https://go-review.googlesource.com/35100 Reviewed-by: Alan Donovan --- src/cmd/compile/internal/gc/bexport.go | 52 +++++++++++++++------ src/cmd/compile/internal/gc/bimport.go | 61 +++++++++++++++++-------- src/go/internal/gcimporter/bimport.go | 62 +++++++++++++------------- 3 files changed, 114 insertions(+), 61 deletions(-) diff --git a/src/cmd/compile/internal/gc/bexport.go b/src/cmd/compile/internal/gc/bexport.go index ffc5419708..b7529163b9 100644 --- a/src/cmd/compile/internal/gc/bexport.go +++ b/src/cmd/compile/internal/gc/bexport.go @@ -140,11 +140,12 @@ const debugFormat = false // default: false const forceObjFileStability = true // Current export format version. Increase with each format change. -// 3: added aliasTag and export of aliases -// 2: removed unused bool in ODCL export +// 4: type name objects support type aliases, uses aliasTag +// 3: Go1.8 encoding (same as version 2, aliasTag defined but never used) +// 2: removed unused bool in ODCL export (compiler only) // 1: header format change (more regular), export package for _ struct fields // 0: Go1.7 encoding -const exportVersion = 3 +const exportVersion = 4 // exportInlined enables the export of inlined function bodies and related // dependencies. The compiler should work w/o any loss of functionality with @@ -509,7 +510,14 @@ func (p *exporter) obj(sym *Sym) { Fatalf("exporter: export of incomplete type %v", sym) } - p.tag(typeTag) + const alias = false // TODO(gri) fix this + if alias { + p.tag(aliasTag) + p.pos(n) + p.qualifiedName(sym) + } else { + p.tag(typeTag) + } p.typ(t) case ONAME: @@ -868,19 +876,29 @@ func (p *exporter) methodList(t *Type) { func (p *exporter) method(m *Field) { p.pos(m.Nname) - p.fieldName(m) + p.methodName(m.Sym) p.paramList(m.Type.Params(), false) p.paramList(m.Type.Results(), false) } -// fieldName is like qualifiedName but it doesn't record the package for exported names. func (p *exporter) fieldName(t *Field) { name := t.Sym.Name if t.Embedded != 0 { - name = "" // anonymous field - if bname := basetypeName(t.Type); bname != "" && !exportname(bname) { - // anonymous field with unexported base type name - name = "?" // unexported name to force export of package + // anonymous field - we distinguish between 3 cases: + // 1) field name matches base type name and name is exported + // 2) field name matches base type name and name is not exported + // 3) field name doesn't match base type name (type name is alias) + bname := basetypeName(t.Type) + if name == bname { + if exportname(name) { + name = "" // 1) we don't need to know the name + } else { + name = "?" // 2) use unexported name to force package export + } + } else { + // 3) indicate alias and export name as is + // (this requires an extra "@" but this is a rare case) + p.string("@") } } p.string(name) @@ -889,6 +907,14 @@ func (p *exporter) fieldName(t *Field) { } } +// methodName is like qualifiedName but it doesn't record the package for exported names. +func (p *exporter) methodName(sym *Sym) { + p.string(sym.Name) + if !exportname(sym.Name) { + p.pkg(sym.Pkg) + } +} + func basetypeName(t *Type) string { s := t.Sym if s == nil && t.IsPtr() { @@ -1797,7 +1823,7 @@ const ( nilTag unknownTag // not used by gc (only appears in packages with errors) - // Aliases + // Type aliases aliasTag ) @@ -1835,7 +1861,7 @@ var tagString = [...]string{ -nilTag: "nil", -unknownTag: "unknown", - // Aliases + // Type aliases -aliasTag: "alias", } @@ -1889,7 +1915,7 @@ func predeclared() []*Type { Types[TCOMPLEX128], Types[TSTRING], - // aliases + // basic type aliases bytetype, runetype, diff --git a/src/cmd/compile/internal/gc/bimport.go b/src/cmd/compile/internal/gc/bimport.go index 1d668412a1..853c4bd2a4 100644 --- a/src/cmd/compile/internal/gc/bimport.go +++ b/src/cmd/compile/internal/gc/bimport.go @@ -86,10 +86,10 @@ func Import(in *bufio.Reader) { // read version specific flags - extend as necessary switch p.version { - // case 4: + // case 5: // ... // fallthrough - case 3, 2, 1: + case 4, 3, 2, 1: p.debugFormat = p.rawStringln(p.rawByte()) == "debug" p.trackAllTypes = p.bool() p.posInfoFormat = p.bool() @@ -315,6 +315,12 @@ func (p *importer) obj(tag int) { val := p.value(typ) importconst(sym, idealType(typ), nodlit(val)) + case aliasTag: + // TODO(gri) hook up type alias + p.pos() + p.qualifiedName() + p.typ() + case typeTag: p.typ() @@ -354,17 +360,6 @@ func (p *importer) obj(tag int) { } } - case aliasTag: - p.pos() - alias := importpkg.Lookup(p.string()) - orig := p.qualifiedName() - - // Although the protocol allows the alias to precede the original, - // this never happens in files produced by gc. - alias.Flags |= SymAlias - alias.Def = orig.Def - importsym(alias, orig.Def.Op) - default: formatErrorf("unexpected object (tag = %d)", tag) } @@ -594,6 +589,8 @@ func (p *importer) field() *Field { } sym = sym.Pkg.Lookup(s.Name) f.Embedded = 1 + } else if sym.Flags&SymAlias != 0 { + f.Embedded = 1 } f.Sym = sym @@ -616,7 +613,7 @@ func (p *importer) methodList() (methods []*Field) { func (p *importer) method() *Field { p.pos() - sym := p.fieldName() + sym := p.methodName() params := p.paramList() result := p.paramList() @@ -630,15 +627,43 @@ func (p *importer) method() *Field { func (p *importer) fieldName() *Sym { name := p.string() if p.version == 0 && name == "_" { - // version 0 didn't export a package for _ fields + // version 0 didn't export a package for _ field names // but used the builtin package instead return builtinpkg.Lookup(name) } pkg := localpkg - if name != "" && !exportname(name) { - if name == "?" { - name = "" + var flag SymFlags + switch name { + case "": + // field name is exported - nothing to do + case "?": + // field name is not exported - need package + name = "" + pkg = p.pkg() + case "@": + // field name doesn't match type name (alias) + name = p.string() + flag = SymAlias + fallthrough + default: + if !exportname(name) { + pkg = p.pkg() } + } + sym := pkg.Lookup(name) + sym.Flags |= flag + return sym +} + +func (p *importer) methodName() *Sym { + name := p.string() + if p.version == 0 && name == "_" { + // version 0 didn't export a package for _ method names + // but used the builtin package instead + return builtinpkg.Lookup(name) + } + pkg := localpkg + if !exportname(name) { pkg = p.pkg() } return pkg.Lookup(name) diff --git a/src/go/internal/gcimporter/bimport.go b/src/go/internal/gcimporter/bimport.go index a8f349052a..55019df39d 100644 --- a/src/go/internal/gcimporter/bimport.go +++ b/src/go/internal/gcimporter/bimport.go @@ -98,10 +98,10 @@ func BImportData(fset *token.FileSet, imports map[string]*types.Package, data [] // read version specific flags - extend as necessary switch p.version { - // case 4: + // case 5: // ... // fallthrough - case 3, 2, 1: + case 4, 3, 2, 1: p.debugFormat = p.rawStringln(p.rawByte()) == "debug" p.trackAllTypes = p.int() != 0 p.posInfoFormat = p.int() != 0 @@ -208,7 +208,6 @@ func (p *importer) pkg() *types.Package { } // objTag returns the tag value for each object kind. -// obj must not be a *types.Alias. func objTag(obj types.Object) int { switch obj.(type) { case *types.Const: @@ -219,7 +218,6 @@ func objTag(obj types.Object) int { return varTag case *types.Func: return funcTag - // Aliases are not exported multiple times, thus we should not see them here. default: errorf("unexpected object: %v (%T)", obj, obj) // panics panic("unreachable") @@ -237,14 +235,14 @@ func (p *importer) declare(obj types.Object) { pkg := obj.Pkg() if alt := pkg.Scope().Insert(obj); alt != nil { // This can only trigger if we import a (non-type) object a second time. - // Excluding aliases, this cannot happen because 1) we only import a package + // Excluding type aliases, this cannot happen because 1) we only import a package // once; and b) we ignore compiler-specific export data which may contain // functions whose inlined function bodies refer to other functions that // were already imported. - // However, aliases require reexporting the original object, so we need + // However, type aliases require reexporting the original type, so we need // to allow it (see also the comment in cmd/compile/internal/gc/bimport.go, // method importer.obj, switch case importing functions). - // Note that the original itself cannot be an alias. + // TODO(gri) review/update this comment once the gc compiler handles type aliases. if !sameObj(obj, alt) { errorf("inconsistent import:\n\t%v\npreviously imported as:\n\t%v\n", obj, alt) } @@ -260,6 +258,13 @@ func (p *importer) obj(tag int) { val := p.value() p.declare(types.NewConst(pos, pkg, name, typ, val)) + case aliasTag: + // TODO(gri) verify type alias hookup is correct + pos := p.pos() + pkg, name := p.qualifiedName() + typ := p.typ(nil) + p.declare(types.NewTypeName(pos, pkg, name, typ)) + case typeTag: p.typ(nil) @@ -277,19 +282,6 @@ func (p *importer) obj(tag int) { sig := types.NewSignature(nil, params, result, isddd) p.declare(types.NewFunc(pos, pkg, name, sig)) - case aliasTag: - pos := p.pos() - name := p.string() - var orig types.Object - if pkg, name := p.qualifiedName(); pkg != nil { - orig = pkg.Scope().Lookup(name) - } - // Alias-related code. Keep for now. - _ = pos - _ = name - _ = orig - // p.declare(types.NewAlias(pos, p.pkgList[0], name, orig)) - default: errorf("unexpected object tag %d", tag) } @@ -556,17 +548,17 @@ func (p *importer) fieldList(parent *types.Package) (fields []*types.Var, tags [ fields = make([]*types.Var, n) tags = make([]string, n) for i := range fields { - fields[i] = p.field(parent) - tags[i] = p.string() + fields[i], tags[i] = p.field(parent) } } return } -func (p *importer) field(parent *types.Package) *types.Var { +func (p *importer) field(parent *types.Package) (*types.Var, string) { pos := p.pos() pkg, name := p.fieldName(parent) typ := p.typ(parent) + tag := p.string() anonymous := false if name == "" { @@ -583,7 +575,7 @@ func (p *importer) field(parent *types.Package) *types.Var { anonymous = true } - return types.NewField(pos, pkg, name, typ, anonymous) + return types.NewField(pos, pkg, name, typ, anonymous), tag } func (p *importer) methodList(parent *types.Package) (methods []*types.Func) { @@ -616,11 +608,21 @@ func (p *importer) fieldName(parent *types.Package) (*types.Package, string) { // version 0 didn't export a package for _ fields return pkg, name } - if name != "" && !exported(name) { - if name == "?" { - name = "" - } + switch name { + case "": + // field name is exported - nothing to do + case "?": + // field name is not exported - need package + name = "" pkg = p.pkg() + case "@": + // field name doesn't match type name (alias) + name = p.string() + fallthrough + default: + if !exported(name) { + pkg = p.pkg() + } } return pkg, name } @@ -893,7 +895,7 @@ const ( nilTag // only used by gc (appears in exported inlined function bodies) unknownTag // not used by gc (only appears in packages with errors) - // Aliases + // Type aliases aliasTag ) @@ -917,7 +919,7 @@ var predeclared = []types.Type{ types.Typ[types.Complex128], types.Typ[types.String], - // aliases + // basic type aliases types.Universe.Lookup("byte").Type(), types.Universe.Lookup("rune").Type(), From f011e0c6c378427f32bbf09f24ba211f7bd96b9c Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Tue, 27 Dec 2016 16:53:33 -0800 Subject: [PATCH 10/26] [dev.typealias] cmd/compile, go/types, go/importer: various alias related fixes cmd/compile: - remove crud from prior alias implementation - better comments in places go/types: - fix TypeName.IsAlias predicate - more tests go/importer (go/internal/gcimporter15): - handle "@" format for anonymous fields using aliases (currently tested indirectly via x/tools/gcimporter15 tests) For #18130. Change-Id: I23a6d4e3a4c2a5c1ae589513da73fde7cad5f386 Reviewed-on: https://go-review.googlesource.com/35101 Reviewed-by: Alan Donovan --- src/cmd/compile/internal/gc/bexport.go | 37 +++++--------------------- src/cmd/compile/internal/gc/bimport.go | 9 ++++--- src/go/internal/gcimporter/bimport.go | 30 +++++++++++---------- src/go/types/object.go | 20 ++++++-------- src/go/types/object_test.go | 29 ++++++++++++-------- 5 files changed, 53 insertions(+), 72 deletions(-) diff --git a/src/cmd/compile/internal/gc/bexport.go b/src/cmd/compile/internal/gc/bexport.go index b7529163b9..4125e83b3a 100644 --- a/src/cmd/compile/internal/gc/bexport.go +++ b/src/cmd/compile/internal/gc/bexport.go @@ -447,30 +447,6 @@ func unidealType(typ *Type, val Val) *Type { } func (p *exporter) obj(sym *Sym) { - if sym.Flags&SymAlias != 0 { - p.tag(aliasTag) - p.pos(nil) // TODO(gri) fix position information - // Aliases can only be exported from the package that - // declares them (aliases to aliases are resolved to the - // original object, and so are uses of aliases in inlined - // exported function bodies). Thus, we only need the alias - // name without package qualification. - if sym.Pkg != localpkg { - Fatalf("exporter: export of non-local alias: %v", sym) - } - p.string(sym.Name) - orig := sym.Def.Sym - if orig.Flags&SymAlias != 0 { - Fatalf("exporter: original object %v marked as alias", sym) - } - p.qualifiedName(orig) - return - } - - if sym != sym.Def.Sym { - Fatalf("exporter: exported object %v is not original %v", sym, sym.Def.Sym) - } - // Exported objects may be from different packages because they // may be re-exported via an exported alias or as dependencies in // exported inlined function bodies. Thus, exported object names @@ -885,15 +861,15 @@ func (p *exporter) fieldName(t *Field) { name := t.Sym.Name if t.Embedded != 0 { // anonymous field - we distinguish between 3 cases: - // 1) field name matches base type name and name is exported - // 2) field name matches base type name and name is not exported - // 3) field name doesn't match base type name (type name is alias) + // 1) field name matches base type name and is exported + // 2) field name matches base type name and is not exported + // 3) field name doesn't match base type name (alias name) bname := basetypeName(t.Type) if name == bname { if exportname(name) { - name = "" // 1) we don't need to know the name + name = "" // 1) we don't need to know the field name or package } else { - name = "?" // 2) use unexported name to force package export + name = "?" // 2) use unexported name "?" to force package export } } else { // 3) indicate alias and export name as is @@ -920,11 +896,10 @@ func basetypeName(t *Type) string { if s == nil && t.IsPtr() { s = t.Elem().Sym // deref } - // s should exist, but be conservative if s != nil { return s.Name } - return "" + return "" // unnamed type } func (p *exporter) paramList(params *Type, numbered bool) { diff --git a/src/cmd/compile/internal/gc/bimport.go b/src/cmd/compile/internal/gc/bimport.go index 853c4bd2a4..6b34770e08 100644 --- a/src/cmd/compile/internal/gc/bimport.go +++ b/src/cmd/compile/internal/gc/bimport.go @@ -582,7 +582,7 @@ func (p *importer) field() *Field { f := newField() if sym.Name == "" { - // anonymous field - typ must be T or *T and T must be a type name + // anonymous field: typ must be T or *T and T must be a type name s := typ.Sym if s == nil && typ.IsPtr() { s = typ.Elem().Sym // deref @@ -590,6 +590,7 @@ func (p *importer) field() *Field { sym = sym.Pkg.Lookup(s.Name) f.Embedded = 1 } else if sym.Flags&SymAlias != 0 { + // anonymous field: we have an explicit name because it's an alias f.Embedded = 1 } @@ -635,13 +636,13 @@ func (p *importer) fieldName() *Sym { var flag SymFlags switch name { case "": - // field name is exported - nothing to do + // 1) field name matches base type name and is exported: nothing to do case "?": - // field name is not exported - need package + // 2) field name matches base type name and is not exported: need package name = "" pkg = p.pkg() case "@": - // field name doesn't match type name (alias) + // 3) field name doesn't match base type name (alias name): need name and possibly package name = p.string() flag = SymAlias fallthrough diff --git a/src/go/internal/gcimporter/bimport.go b/src/go/internal/gcimporter/bimport.go index 55019df39d..5badd337d9 100644 --- a/src/go/internal/gcimporter/bimport.go +++ b/src/go/internal/gcimporter/bimport.go @@ -341,9 +341,7 @@ var ( func (p *importer) qualifiedName() (pkg *types.Package, name string) { name = p.string() - if name != "" { - pkg = p.pkg() - } + pkg = p.pkg() return } @@ -556,7 +554,7 @@ func (p *importer) fieldList(parent *types.Package) (fields []*types.Var, tags [ func (p *importer) field(parent *types.Package) (*types.Var, string) { pos := p.pos() - pkg, name := p.fieldName(parent) + pkg, name, alias := p.fieldName(parent) typ := p.typ(parent) tag := p.string() @@ -570,9 +568,12 @@ func (p *importer) field(parent *types.Package) (*types.Var, string) { case *types.Named: name = typ.Obj().Name() default: - errorf("anonymous field expected") + errorf("named base type expected") } anonymous = true + } else if alias { + // anonymous field: we have an explicit name because it's an alias + anonymous = true } return types.NewField(pos, pkg, name, typ, anonymous), tag @@ -590,41 +591,42 @@ func (p *importer) methodList(parent *types.Package) (methods []*types.Func) { func (p *importer) method(parent *types.Package) *types.Func { pos := p.pos() - pkg, name := p.fieldName(parent) + pkg, name, _ := p.fieldName(parent) params, isddd := p.paramList() result, _ := p.paramList() sig := types.NewSignature(nil, params, result, isddd) return types.NewFunc(pos, pkg, name, sig) } -func (p *importer) fieldName(parent *types.Package) (*types.Package, string) { - name := p.string() - pkg := parent +func (p *importer) fieldName(parent *types.Package) (pkg *types.Package, name string, alias bool) { + name = p.string() + pkg = parent if pkg == nil { // use the imported package instead pkg = p.pkgList[0] } if p.version == 0 && name == "_" { // version 0 didn't export a package for _ fields - return pkg, name + return } switch name { case "": - // field name is exported - nothing to do + // 1) field name matches base type name and is exported: nothing to do case "?": - // field name is not exported - need package + // 2) field name matches base type name and is not exported: need package name = "" pkg = p.pkg() case "@": - // field name doesn't match type name (alias) + // 3) field name doesn't match type name (alias) name = p.string() + alias = true fallthrough default: if !exported(name) { pkg = p.pkg() } } - return pkg, name + return } func (p *importer) paramList() (*types.Tuple, bool) { diff --git a/src/go/types/object.go b/src/go/types/object.go index f4f628f876..1668ba396b 100644 --- a/src/go/types/object.go +++ b/src/go/types/object.go @@ -163,23 +163,19 @@ func NewTypeName(pos token.Pos, pkg *Package, name string, typ Type) *TypeName { return &TypeName{object{nil, pos, pkg, name, typ, 0, token.NoPos}} } +// IsAlias reports whether obj is an alias name for a type. func (obj *TypeName) IsAlias() bool { switch t := obj.typ.(type) { case nil: return false case *Basic: - // It would seem that we should be able to look for different names here; - // but the names of universeByte/Rune are "byte" and "rune", respectively. - // We do this so that we get better error messages. However, general alias - // types don't have that name information and thus behave differently when - // reporting errors (we won't see the alias name, only the original name). - // Maybe we should remove the special handling for the predeclared types - // as well to be consistent (at the cost of slightly less clear error - // messages when byte/rune are involved). - // This also plays out in the implementation of the Identical(Type, Type) - // predicate. - // TODO(gri) consider possible clean up - return t == universeByte || t == universeRune + // Any user-defined type name for a basic type is an alias for a + // basic type (because basic types are pre-declared in the Universe + // scope, outside any package scope), and so is any type name with + // a different name than the name of the basic type it refers to. + // Additionaly, we need to look for "byte" and "rune" because they + // are aliases but have the same names (for better error messages). + return obj.pkg != nil || t.name != obj.name || t == universeByte || t == universeRune case *Named: return obj != t.obj default: diff --git a/src/go/types/object_test.go b/src/go/types/object_test.go index 70656ae022..16d7d5c723 100644 --- a/src/go/types/object_test.go +++ b/src/go/types/object_test.go @@ -21,16 +21,23 @@ func TestIsAlias(t *testing.T) { } // various other types - t0 := NewTypeName(0, nil, "t0", nil) - check(t0, false) // no type yet - - t1 := NewTypeName(0, nil, "t1", nil) + pkg := NewPackage("p", "p") + t1 := NewTypeName(0, pkg, "t1", nil) n1 := NewNamed(t1, new(Struct), nil) - check(t1, false) // type name refers to named type and vice versa - - t2 := NewTypeName(0, nil, "t2", new(Interface)) - check(t2, true) // type name refers to unnamed type - - t3 := NewTypeName(0, nil, "t3", n1) - check(t3, true) // type name refers to named type with different type name (true alias) + for _, test := range []struct { + name *TypeName + alias bool + }{ + {NewTypeName(0, nil, "t0", nil), false}, // no type yet + {NewTypeName(0, pkg, "t0", nil), false}, // no type yet + {t1, false}, // type name refers to named type and vice versa + {NewTypeName(0, nil, "t2", new(Interface)), true}, // type name refers to unnamed type + {NewTypeName(0, pkg, "t3", n1), true}, // type name refers to named type with different type name + {NewTypeName(0, nil, "t4", Typ[Int32]), true}, // type name refers to basic type with different name + {NewTypeName(0, nil, "int32", Typ[Int32]), false}, // type name refers to basic type with same name + {NewTypeName(0, pkg, "int32", Typ[Int32]), true}, // type name is declared in user-defined package (outside Universe) + {NewTypeName(0, nil, "rune", Typ[Rune]), true}, // type name refers to basic type rune which is an alias already + } { + check(test.name, test.alias) + } } From ac8421f9a58c2c4df9072d1702783baa62eb99f3 Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Thu, 29 Dec 2016 15:49:01 -0800 Subject: [PATCH 11/26] [dev.typealias] cmd/compile: various minor cleanups Also: Don't allow type pragmas with type alias declarations. For #18130. Change-Id: Ie54ea5fefcd677ad87ced03466bbfd783771e974 Reviewed-on: https://go-review.googlesource.com/35102 Reviewed-by: Matthew Dempsky --- src/cmd/compile/internal/gc/main.go | 20 +++++++++++------- src/cmd/compile/internal/gc/noder.go | 31 ++++++++++++++-------------- 2 files changed, 28 insertions(+), 23 deletions(-) diff --git a/src/cmd/compile/internal/gc/main.go b/src/cmd/compile/internal/gc/main.go index 75f58a731c..b0b31dd30d 100644 --- a/src/cmd/compile/internal/gc/main.go +++ b/src/cmd/compile/internal/gc/main.go @@ -344,8 +344,9 @@ func Main() { // Don't use range--typecheck can add closures to xtop. timings.Start("fe", "typecheck", "top1") for i := 0; i < len(xtop); i++ { - if xtop[i].Op != ODCL && xtop[i].Op != OAS && xtop[i].Op != OAS2 { - xtop[i] = typecheck(xtop[i], Etop) + n := xtop[i] + if op := n.Op; op != ODCL && op != OAS && op != OAS2 { + xtop[i] = typecheck(n, Etop) } } @@ -355,8 +356,9 @@ func Main() { // Don't use range--typecheck can add closures to xtop. timings.Start("fe", "typecheck", "top2") for i := 0; i < len(xtop); i++ { - if xtop[i].Op == ODCL || xtop[i].Op == OAS || xtop[i].Op == OAS2 { - xtop[i] = typecheck(xtop[i], Etop) + n := xtop[i] + if op := n.Op; op == ODCL || op == OAS || op == OAS2 { + xtop[i] = typecheck(n, Etop) } } resumecheckwidth() @@ -366,8 +368,9 @@ func Main() { timings.Start("fe", "typecheck", "func") var fcount int64 for i := 0; i < len(xtop); i++ { - if xtop[i].Op == ODCLFUNC || xtop[i].Op == OCLOSURE { - Curfn = xtop[i] + n := xtop[i] + if op := n.Op; op == ODCLFUNC || op == OCLOSURE { + Curfn = n decldepth = 1 saveerrors() typecheckslice(Curfn.Nbody.Slice(), Etop) @@ -459,8 +462,9 @@ func Main() { timings.Start("be", "compilefuncs") fcount = 0 for i := 0; i < len(xtop); i++ { - if xtop[i].Op == ODCLFUNC { - funccompile(xtop[i]) + n := xtop[i] + if n.Op == ODCLFUNC { + funccompile(n) fcount++ } } diff --git a/src/cmd/compile/internal/gc/noder.go b/src/cmd/compile/internal/gc/noder.go index 3f6fe20b6b..8d830ad62d 100644 --- a/src/cmd/compile/internal/gc/noder.go +++ b/src/cmd/compile/internal/gc/noder.go @@ -153,11 +153,7 @@ func (p *noder) importDecl(imp *syntax.ImportDecl) { func (p *noder) varDecl(decl *syntax.VarDecl) []*Node { names := p.declNames(decl.NameList) - - var typ *Node - if decl.Type != nil { - typ = p.typeExpr(decl.Type) - } + typ := p.typeExprOrNil(decl.Type) var exprs []*Node if decl.Values != nil { @@ -170,11 +166,7 @@ func (p *noder) varDecl(decl *syntax.VarDecl) []*Node { func (p *noder) constDecl(decl *syntax.ConstDecl) []*Node { names := p.declNames(decl.NameList) - - var typ *Node - if decl.Type != nil { - typ = p.typeExpr(decl.Type) - } + typ := p.typeExprOrNil(decl.Type) var exprs []*Node if decl.Values != nil { @@ -190,12 +182,14 @@ func (p *noder) typeDecl(decl *syntax.TypeDecl) *Node { } name := typedcl0(p.name(decl.Name)) - name.Name.Param.Pragma = Pragma(decl.Pragma) - - var typ *Node - if decl.Type != nil { - typ = p.typeExpr(decl.Type) + pragma := Pragma(decl.Pragma) + if pragma != 0 && decl.Alias { + yyerror("cannot specify directive with type alias") + pragma = 0 } + name.Name.Param.Pragma = pragma + + typ := p.typeExprOrNil(decl.Type) return typedcl1(name, typ, true) } @@ -470,6 +464,13 @@ func (p *noder) typeExpr(typ syntax.Expr) *Node { return p.expr(typ) } +func (p *noder) typeExprOrNil(typ syntax.Expr) *Node { + if typ != nil { + return p.expr(typ) + } + return nil +} + func (p *noder) chanDir(dir syntax.ChanDir) ChanDir { switch dir { case 0: From b2386dffa1f646f06c230f9b317cb3640fef11d4 Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Wed, 11 Jan 2017 11:24:35 -0800 Subject: [PATCH 12/26] [dev.typealias] cmd/compile: type-check type alias declarations Known issues: - needs many more tests - duplicate method declarations via type alias names are not detected - type alias cycle error messages need to be improved - need to review setup of byte/rune type aliases For #18130. Change-Id: Icc2fefad6214e5e56539a9dcb3fe537bf58029f8 Reviewed-on: https://go-review.googlesource.com/35121 Run-TryBot: Robert Griesemer TryBot-Result: Gobot Gobot Reviewed-by: Matthew Dempsky --- src/cmd/compile/internal/gc/bexport.go | 7 +- src/cmd/compile/internal/gc/bimport.go | 24 +++--- src/cmd/compile/internal/gc/dcl.go | 18 ++++- src/cmd/compile/internal/gc/export.go | 25 ++++++- src/cmd/compile/internal/gc/go.go | 9 ++- src/cmd/compile/internal/gc/main.go | 2 +- src/cmd/compile/internal/gc/noder.go | 13 +--- src/cmd/compile/internal/gc/syntax.go | 15 ++-- src/cmd/compile/internal/gc/typecheck.go | 22 +++++- test/alias2.go | 93 +++++++++++++++++------- 10 files changed, 153 insertions(+), 75 deletions(-) diff --git a/src/cmd/compile/internal/gc/bexport.go b/src/cmd/compile/internal/gc/bexport.go index 4125e83b3a..5f14e0152b 100644 --- a/src/cmd/compile/internal/gc/bexport.go +++ b/src/cmd/compile/internal/gc/bexport.go @@ -352,8 +352,8 @@ func export(out *bufio.Writer, trace bool) int { p.tracef("\n") } - if sym.Flags&SymAlias != 0 { - Fatalf("exporter: unexpected alias %v in inlined function body", sym) + if sym.isAlias() { + Fatalf("exporter: unexpected type alias %v in inlined function body", sym) } p.obj(sym) @@ -486,8 +486,7 @@ func (p *exporter) obj(sym *Sym) { Fatalf("exporter: export of incomplete type %v", sym) } - const alias = false // TODO(gri) fix this - if alias { + if sym.isAlias() { p.tag(aliasTag) p.pos(n) p.qualifiedName(sym) diff --git a/src/cmd/compile/internal/gc/bimport.go b/src/cmd/compile/internal/gc/bimport.go index 6b34770e08..3c1f7100c3 100644 --- a/src/cmd/compile/internal/gc/bimport.go +++ b/src/cmd/compile/internal/gc/bimport.go @@ -316,10 +316,10 @@ func (p *importer) obj(tag int) { importconst(sym, idealType(typ), nodlit(val)) case aliasTag: - // TODO(gri) hook up type alias p.pos() - p.qualifiedName() - p.typ() + sym := p.qualifiedName() + typ := p.typ() + importalias(sym, typ) case typeTag: p.typ() @@ -576,7 +576,7 @@ func (p *importer) fieldList() (fields []*Field) { func (p *importer) field() *Field { p.pos() - sym := p.fieldName() + sym, alias := p.fieldName() typ := p.typ() note := p.string() @@ -589,8 +589,8 @@ func (p *importer) field() *Field { } sym = sym.Pkg.Lookup(s.Name) f.Embedded = 1 - } else if sym.Flags&SymAlias != 0 { - // anonymous field: we have an explicit name because it's an alias + } else if alias { + // anonymous field: we have an explicit name because it's a type alias f.Embedded = 1 } @@ -625,15 +625,15 @@ func (p *importer) method() *Field { return f } -func (p *importer) fieldName() *Sym { +func (p *importer) fieldName() (*Sym, bool) { name := p.string() if p.version == 0 && name == "_" { // version 0 didn't export a package for _ field names // but used the builtin package instead - return builtinpkg.Lookup(name) + return builtinpkg.Lookup(name), false } pkg := localpkg - var flag SymFlags + alias := false switch name { case "": // 1) field name matches base type name and is exported: nothing to do @@ -644,16 +644,14 @@ func (p *importer) fieldName() *Sym { case "@": // 3) field name doesn't match base type name (alias name): need name and possibly package name = p.string() - flag = SymAlias + alias = true fallthrough default: if !exportname(name) { pkg = p.pkg() } } - sym := pkg.Lookup(name) - sym.Flags |= flag - return sym + return pkg.Lookup(name), alias } func (p *importer) methodName() *Sym { diff --git a/src/cmd/compile/internal/gc/dcl.go b/src/cmd/compile/internal/gc/dcl.go index 3cdd71df0d..5a1c5e12a0 100644 --- a/src/cmd/compile/internal/gc/dcl.go +++ b/src/cmd/compile/internal/gc/dcl.go @@ -695,10 +695,20 @@ func typedcl0(s *Sym) *Node { // node n, which was returned by typedcl0 // is being declared to have uncompiled type t. -// return the ODCLTYPE node to use. -func typedcl1(n *Node, t *Node, local bool) *Node { - n.Name.Param.Ntype = t - n.Local = local +// returns the ODCLTYPE node to use. +func typedcl1(n *Node, t *Node, pragma Pragma, alias bool) *Node { + if pragma != 0 && alias { + yyerror("cannot specify directive with type alias") + pragma = 0 + } + + n.Local = true + + p := n.Name.Param + p.Ntype = t + p.Pragma = pragma + p.Alias = alias + return nod(ODCLTYPE, n, nil) } diff --git a/src/cmd/compile/internal/gc/export.go b/src/cmd/compile/internal/gc/export.go index b4c15e40b1..5556984dcb 100644 --- a/src/cmd/compile/internal/gc/export.go +++ b/src/cmd/compile/internal/gc/export.go @@ -45,8 +45,8 @@ func exportsym(n *Node) { fmt.Printf("export symbol %v\n", n.Sym) } - // Ensure original object is on exportlist before aliases. - if n.Sym.Flags&SymAlias != 0 { + // Ensure original types are on exportlist before type aliases. + if n.Sym.isAlias() { exportlist = append(exportlist, n.Sym.Def) } @@ -348,6 +348,27 @@ func importvar(s *Sym, t *Type) { } } +// importalias declares symbol s as an imported type alias with type t. +func importalias(s *Sym, t *Type) { + importsym(s, OTYPE) + if s.Def != nil && s.Def.Op == OTYPE { + if eqtype(t, s.Def.Type) { + return + } + yyerror("inconsistent definition for type alias %v during import\n\t%v (in %q)\n\t%v (in %q)", s, s.Def.Type, s.Importdef.Path, t, importpkg.Path) + } + + n := newname(s) + n.Op = OTYPE + s.Importdef = importpkg + n.Type = t + declare(n, PEXTERN) + + if Debug['E'] != 0 { + fmt.Printf("import type %v = %L\n", s, t) + } +} + func dumpasmhdr() { b, err := bio.Create(asmhdr) if err != nil { diff --git a/src/cmd/compile/internal/gc/go.go b/src/cmd/compile/internal/gc/go.go index ff33e9c1c4..070fb5f54b 100644 --- a/src/cmd/compile/internal/gc/go.go +++ b/src/cmd/compile/internal/gc/go.go @@ -63,9 +63,12 @@ const ( SymSiggen SymAsm SymAlgGen - SymAlias // alias, original is Sym.Def.Sym ) +func (sym *Sym) isAlias() bool { + return sym.Def != nil && sym.Def.Sym != sym +} + // The Class of a variable/function describes the "storage class" // of a variable or function. During parsing, storage classes are // called declaration contexts. @@ -87,7 +90,7 @@ const ( // of the compilers arrays. // // typedef struct -// { // must not move anything +// { // must not move anything // uchar array[8]; // pointer to data // uchar nel[4]; // number of elements // uchar cap[4]; // allocated number of elements @@ -104,7 +107,7 @@ var sizeof_Array int // runtime sizeof(Array) // of the compilers strings. // // typedef struct -// { // must not move anything +// { // must not move anything // uchar array[8]; // pointer to data // uchar nel[4]; // number of elements // } String; diff --git a/src/cmd/compile/internal/gc/main.go b/src/cmd/compile/internal/gc/main.go index b0b31dd30d..a861a3556b 100644 --- a/src/cmd/compile/internal/gc/main.go +++ b/src/cmd/compile/internal/gc/main.go @@ -927,7 +927,7 @@ func mkpackage(pkgname string) { continue } - if s.Def.Sym != s && s.Flags&SymAlias == 0 { + if s.isAlias() { // throw away top-level name left over // from previous import . "x" if s.Def.Name != nil && s.Def.Name.Pack != nil && !s.Def.Name.Pack.Used && nsyntaxerrors == 0 { diff --git a/src/cmd/compile/internal/gc/noder.go b/src/cmd/compile/internal/gc/noder.go index 8d830ad62d..699015488a 100644 --- a/src/cmd/compile/internal/gc/noder.go +++ b/src/cmd/compile/internal/gc/noder.go @@ -177,21 +177,12 @@ func (p *noder) constDecl(decl *syntax.ConstDecl) []*Node { } func (p *noder) typeDecl(decl *syntax.TypeDecl) *Node { - if decl.Alias { - yyerror("type alias declarations unimplemented") - } - name := typedcl0(p.name(decl.Name)) - pragma := Pragma(decl.Pragma) - if pragma != 0 && decl.Alias { - yyerror("cannot specify directive with type alias") - pragma = 0 - } - name.Name.Param.Pragma = pragma + // decl.Type may be nil but in that case we got a syntax error during parsing typ := p.typeExprOrNil(decl.Type) - return typedcl1(name, typ, true) + return typedcl1(name, typ, Pragma(decl.Pragma), decl.Alias) } func (p *noder) declNames(names []*syntax.Name) []*Node { diff --git a/src/cmd/compile/internal/gc/syntax.go b/src/cmd/compile/internal/gc/syntax.go index 8848bb5955..7a52dc612f 100644 --- a/src/cmd/compile/internal/gc/syntax.go +++ b/src/cmd/compile/internal/gc/syntax.go @@ -27,7 +27,7 @@ type Node struct { // func Func *Func - // ONAME + // ONAME, OTYPE, OPACK, OLABEL, some OLITERAL Name *Name Sym *Sym // various @@ -59,8 +59,8 @@ type Node struct { Noescape bool // func arguments do not escape; TODO(rsc): move Noescape to Func struct (see CL 7360) Walkdef uint8 // tracks state during typecheckdef; 2 == loop detected Typecheck uint8 // tracks state during typechecking; 2 == loop detected - Local bool - IsStatic bool // whether this Node will be converted to purely static data + Local bool // type created in this file (see also Type.Local); TODO(gri): move this into flags + IsStatic bool // whether this Node will be converted to purely static data Initorder uint8 Used bool // for variable/label declared and not used error Isddd bool // is the argument variadic @@ -180,14 +180,14 @@ func (n *Node) SetIota(x int64) { n.Xoffset = x } -// Name holds Node fields used only by named nodes (ONAME, OPACK, OLABEL, some OLITERAL). +// Name holds Node fields used only by named nodes (ONAME, OTYPE, OPACK, OLABEL, some OLITERAL). type Name struct { Pack *Node // real package for import . names Pkg *Pkg // pkg for OPACK nodes Heapaddr *Node // temp holding heap address of param (could move to Param?) Defn *Node // initializing assignment Curfn *Node // function for local variables - Param *Param // additional fields for ONAME + Param *Param // additional fields for ONAME, OTYPE Decldepth int32 // declaration loop depth, increased for every loop or label Vargen int32 // unique name for ONAME within a function. Function outputs are numbered starting at one. Funcdepth int32 @@ -280,10 +280,11 @@ type Param struct { Innermost *Node Outer *Node - // OTYPE pragmas + // OTYPE // // TODO: Should Func pragmas also be stored on the Name? Pragma Pragma + Alias bool // node is alias for Ntype } // Func holds Node fields used only with function-like nodes. @@ -382,7 +383,7 @@ const ( ODCLFUNC // func f() or func (r) f() ODCLFIELD // struct field, interface field, or func/method argument/return value. ODCLCONST // const pi = 3.14 - ODCLTYPE // type Int int + ODCLTYPE // type Int int or type Int = int ODELETE // delete(Left, Right) ODOT // Left.Sym (Left is of struct type) diff --git a/src/cmd/compile/internal/gc/typecheck.go b/src/cmd/compile/internal/gc/typecheck.go index 5ec1c9e2f2..46c71d69c4 100644 --- a/src/cmd/compile/internal/gc/typecheck.go +++ b/src/cmd/compile/internal/gc/typecheck.go @@ -3578,8 +3578,6 @@ func typecheckdeftype(n *Node) { // copy new type and clear fields // that don't come along. - // anything zeroed here must be zeroed in - // typedcl2 too. copytype(n, t) ret: @@ -3758,12 +3756,29 @@ func typecheckdef(n *Node) *Node { n.Name.Defn = typecheck(n.Name.Defn, Etop) // fills in n->type case OTYPE: + if p := n.Name.Param; p.Alias { + // Type alias declaration: Simply use the rhs type - no need + // to create a new type. + // If we have a syntax error, p.Ntype may be nil. + if p.Ntype != nil { + p.Ntype = typecheck(p.Ntype, Etype) + n.Type = p.Ntype.Type + if n.Type == nil { + n.Diag = true + goto ret + } + n.Sym.Def = p.Ntype + } + break + } + + // regular type declaration if Curfn != nil { defercheckwidth() } n.Walkdef = 1 n.Type = typ(TFORW) - n.Type.Sym = n.Sym + n.Type.Sym = n.Sym // TODO(gri) this also happens in typecheckdeftype(n) - where should it happen? nerrors0 := nerrors typecheckdeftype(n) if n.Type.Etype == TFORW && nerrors > nerrors0 { @@ -3771,7 +3786,6 @@ func typecheckdef(n *Node) *Node { // but it was reported. Silence future errors. n.Type.Broke = true } - if Curfn != nil { resumecheckwidth() } diff --git a/test/alias2.go b/test/alias2.go index 25df7c287d..fb0a97feb2 100644 --- a/test/alias2.go +++ b/test/alias2.go @@ -6,11 +6,6 @@ // Test basic restrictions on type aliases. -// The compiler doesn't implement type aliases yet, -// so for now we get the same error (unimplemented) -// everywhere, OR-ed into the ERROR checks. -// TODO(gri) remove the need for "unimplemented" - package p import ( @@ -18,41 +13,87 @@ import ( . "reflect" ) +type T0 struct{} + // Valid type alias declarations. -type _ = int // ERROR "unimplemented" -type _ = struct{} // ERROR "unimplemented" -type _ = reflect.Value // ERROR "unimplemented" -type _ = Value // ERROR "unimplemented" +type _ = T0 +type _ = int +type _ = struct{} +type _ = reflect.Value +type _ = Value type ( - a1 = int // ERROR "unimplemented" - a2 = struct{} // ERROR "unimplemented" - a3 = reflect.Value // ERROR "unimplemented" - a4 = Value // ERROR "unimplemented" + A0 = T0 + A1 = int + A2 = struct{} + A3 = reflect.Value + A4 = Value + A5 = Value + + N0 A0 ) +// Methods can be declared on the original named type and the alias. +func (T0) m1() {} +func (A0) m1() {} // TODO(gri) this should be an error +func (A0) m2() {} + +// Type aliases and the original type name can be used interchangeably. +var _ A0 = T0{} +var _ T0 = A0{} + +// But aliases and original types cannot be used with new types based on them. +var _ N0 = T0{} // ERROR "cannot use T0 literal \(type T0\) as type N0 in assignment" +var _ N0 = A0{} // ERROR "cannot use T0 literal \(type T0\) as type N0 in assignment" + +var _ A5 = Value{} + +var _ interface { + m1() + m2() +} = T0{} + +var _ interface { + m1() + m2() +} = A0{} + func _() { - type _ = int // ERROR "unimplemented" - type _ = struct{} // ERROR "unimplemented" - type _ = reflect.Value // ERROR "unimplemented" - type _ = Value // ERROR "unimplemented" + type _ = T0 + type _ = int + type _ = struct{} + type _ = reflect.Value + type _ = Value type ( - a1 = int // ERROR "unimplemented" - a2 = struct{} // ERROR "unimplemented" - a3 = reflect.Value // ERROR "unimplemented" - a4 = Value // ERROR "unimplemented" + A0 = T0 + A1 = int + A2 = struct{} + A3 = reflect.Value + A4 = Value + A5 Value + + N0 A0 ) + + var _ A0 = T0{} + var _ T0 = A0{} + + var _ N0 = T0{} // ERROR "cannot use T0 literal \(type T0\) as type N0 in assignment" + var _ N0 = A0{} // ERROR "cannot use T0 literal \(type T0\) as type N0 in assignment" + + var _ A5 = Value{} // ERROR "cannot use reflect\.Value literal \(type reflect.Value\) as type A5 in assignment" } // Invalid type alias declarations. -type _ = reflect.ValueOf // ERROR "reflect.ValueOf is not a type|unimplemented" +type _ = reflect.ValueOf // ERROR "reflect.ValueOf is not a type" -type b1 = struct{} // ERROR "unimplemented" -func (b1) m() {} // disabled ERROR "invalid receiver type" +func (A1) m() {} // ERROR "cannot define new methods on non-local type int" + +type B1 = struct{} + +func (B1) m() {} // ERROR "invalid receiver type" // TODO(gri) expand -// It appears that type-checking exits after some more severe errors, so we may -// need more test files. From 5c160b28baceb263fbd95ea0c95f5083e191c114 Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Thu, 12 Jan 2017 14:35:20 -0800 Subject: [PATCH 13/26] [dev.typealias] cmd/compile: improved error message for cyles involving type aliases Known issue: #18640 (requires a bit more work, I believe). For #18130. Change-Id: I53dc26012070e0c79f63b7c76266732190a83d47 Reviewed-on: https://go-review.googlesource.com/35129 Reviewed-by: Matthew Dempsky --- src/cmd/compile/internal/gc/fmt.go | 4 +++- src/cmd/compile/internal/gc/typecheck.go | 27 +++++++++++++++--------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/cmd/compile/internal/gc/fmt.go b/src/cmd/compile/internal/gc/fmt.go index fffce440bc..16a4e342fc 100644 --- a/src/cmd/compile/internal/gc/fmt.go +++ b/src/cmd/compile/internal/gc/fmt.go @@ -1077,6 +1077,7 @@ var opprec = []int{ OSEND: 3, OANDAND: 2, OOROR: 1, + // Statements handled by stmtfmt OAS: -1, OAS2: -1, @@ -1104,7 +1105,8 @@ var opprec = []int{ OSWITCH: -1, OXCASE: -1, OXFALL: -1, - OEND: 0, + + OEND: 0, } func (n *Node) exprfmt(s fmt.State, prec int) { diff --git a/src/cmd/compile/internal/gc/typecheck.go b/src/cmd/compile/internal/gc/typecheck.go index 46c71d69c4..f18bcfad78 100644 --- a/src/cmd/compile/internal/gc/typecheck.go +++ b/src/cmd/compile/internal/gc/typecheck.go @@ -96,16 +96,16 @@ func typekind(t *Type) string { return fmt.Sprintf("etype=%d", et) } -// sprint_depchain prints a dependency chain of nodes into fmt. +// sprint_depchain prints a dependency chain of nodes into trace. // It is used by typecheck in the case of OLITERAL nodes // to print constant definition loops. -func sprint_depchain(fmt_ *string, stack []*Node, cur *Node, first *Node) { +func sprint_depchain(trace *string, stack []*Node, cur *Node, first *Node) { for i := len(stack) - 1; i >= 0; i-- { if n := stack[i]; n.Op == cur.Op { if n != first { - sprint_depchain(fmt_, stack[:i], n, first) + sprint_depchain(trace, stack[:i], n, first) } - *fmt_ += fmt.Sprintf("\n\t%v: %v uses %v", n.Line(), n, cur) + *trace += fmt.Sprintf("\n\t%v: %v uses %v", n.Line(), n, cur) return } } @@ -152,7 +152,6 @@ func typecheck(n *Node, top int) *Node { if n.Typecheck == 2 { // Typechecking loop. Trying printing a meaningful message, // otherwise a stack trace of typechecking. - var fmt_ string switch n.Op { // We can already diagnose variables used as types. case ONAME: @@ -160,22 +159,30 @@ func typecheck(n *Node, top int) *Node { yyerror("%v is not a type", n) } + case OTYPE: + if top&Etype == Etype { + var trace string + sprint_depchain(&trace, typecheck_tcstack, n, n) + yyerrorl(n.Lineno, "invalid recursive type alias %v%s", n, trace) + } + case OLITERAL: if top&(Erv|Etype) == Etype { yyerror("%v is not a type", n) break } - sprint_depchain(&fmt_, typecheck_tcstack, n, n) - yyerrorl(n.Lineno, "constant definition loop%s", fmt_) + var trace string + sprint_depchain(&trace, typecheck_tcstack, n, n) + yyerrorl(n.Lineno, "constant definition loop%s", trace) } if nsavederrors+nerrors == 0 { - fmt_ = "" + var trace string for i := len(typecheck_tcstack) - 1; i >= 0; i-- { x := typecheck_tcstack[i] - fmt_ += fmt.Sprintf("\n\t%v %v", x.Line(), x) + trace += fmt.Sprintf("\n\t%v %v", x.Line(), x) } - yyerror("typechecking loop involving %v%s", n, fmt_) + yyerror("typechecking loop involving %v%s", n, trace) } lineno = lno From cc2dcce3d748b5585a1da5a9aa2e7ab8b8be00cd Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Thu, 12 Jan 2017 15:21:21 -0800 Subject: [PATCH 14/26] [dev.typealias] cmd/compile: a few better comments related to alias types For #18130. Change-Id: I50bded3af0db673fc92b20c41a86b9cae614acd9 Reviewed-on: https://go-review.googlesource.com/35191 Reviewed-by: Matthew Dempsky --- src/cmd/compile/internal/gc/syntax.go | 2 +- src/cmd/compile/internal/gc/universe.go | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/cmd/compile/internal/gc/syntax.go b/src/cmd/compile/internal/gc/syntax.go index 7a52dc612f..1955a0e42f 100644 --- a/src/cmd/compile/internal/gc/syntax.go +++ b/src/cmd/compile/internal/gc/syntax.go @@ -284,7 +284,7 @@ type Param struct { // // TODO: Should Func pragmas also be stored on the Name? Pragma Pragma - Alias bool // node is alias for Ntype + Alias bool // node is alias for Ntype (only used when type-checking ODCLTYPE) } // Func holds Node fields used only with function-like nodes. diff --git a/src/cmd/compile/internal/gc/universe.go b/src/cmd/compile/internal/gc/universe.go index 30c9c3783a..d23aebeafb 100644 --- a/src/cmd/compile/internal/gc/universe.go +++ b/src/cmd/compile/internal/gc/universe.go @@ -398,6 +398,14 @@ func lexinit1() { // errortype.Orig = makeErrorInterface() s.Def = typenod(errortype) + // We create separate byte and rune types for better error messages + // rather than just creating type alias *Sym's for the uint8 and + // int32 types. Hence, (bytetype|runtype).Sym.isAlias() is false. + // TODO(gri) Should we get rid of this special case (at the cost + // of less informative error messages involving bytes and runes)? + // (Alternatively, we could introduce an OTALIAS node representing + // type aliases, albeit at the cost of having to deal with it everywhere). + // byte alias s = Pkglookup("byte", builtinpkg) bytetype = typ(TUINT8) From d7cabd40dd92e5364969273229373d515bee14e8 Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Tue, 17 Jan 2017 11:40:04 -0800 Subject: [PATCH 15/26] [dev.typealias] go/types: clarified doc string Also: removed internal TODO and added better comment Fixes #18644. Change-Id: I3e3763d3afdad6937173cdd32fc661618fb60820 Reviewed-on: https://go-review.googlesource.com/35245 Run-TryBot: Robert Griesemer TryBot-Result: Gobot Gobot Reviewed-by: Josh Bleecher Snyder --- src/go/types/object.go | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/go/types/object.go b/src/go/types/object.go index 1668ba396b..3c44348696 100644 --- a/src/go/types/object.go +++ b/src/go/types/object.go @@ -25,7 +25,7 @@ type Object interface { Name() string // package local object name Type() Type // object type Exported() bool // reports whether the name starts with a capital letter - Id() string // object id (see Id below) + Id() string // object name if exported, qualified name if not exported (see func Id) // String returns a human-readable string of the object. String() string @@ -64,15 +64,10 @@ func Id(pkg *Package, name string) string { // inside a package and outside a package - which breaks some // tests) path := "_" - // TODO(gri): shouldn't !ast.IsExported(name) => pkg != nil be an precondition? - // if pkg == nil { - // panic("nil package in lookup of unexported name") - // } - if pkg != nil { + // pkg is nil for objects in Universe scope and possibly types + // introduced via Eval (see also comment in object.sameId) + if pkg != nil && pkg.path != "" { path = pkg.path - if path == "" { - path = "_" - } } return path + "." + name } From 5802cfd900c238baeb835bff62bad61c4f4c9852 Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Fri, 13 Jan 2017 17:23:01 -0800 Subject: [PATCH 16/26] [dev.typealias] cmd/compile: export/import test cases for type aliases Plus a few minor changes. For #18130. Change-Id: Ica6503fe9c888cc05c15b46178423f620c087491 Reviewed-on: https://go-review.googlesource.com/35233 Reviewed-by: Alan Donovan --- src/cmd/compile/internal/gc/dcl.go | 9 +++---- test/alias2.go | 11 +++++--- test/alias3.dir/a.go | 42 ++++++++++++++++++++++++++++++ test/alias3.dir/b.go | 26 ++++++++++++++++++ test/alias3.dir/c.go | 25 ++++++++++++++++++ test/alias3.go | 7 +++++ 6 files changed, 112 insertions(+), 8 deletions(-) create mode 100644 test/alias3.dir/a.go create mode 100644 test/alias3.dir/b.go create mode 100644 test/alias3.dir/c.go create mode 100644 test/alias3.go diff --git a/src/cmd/compile/internal/gc/dcl.go b/src/cmd/compile/internal/gc/dcl.go index 5a1c5e12a0..f934a650bd 100644 --- a/src/cmd/compile/internal/gc/dcl.go +++ b/src/cmd/compile/internal/gc/dcl.go @@ -1163,14 +1163,14 @@ bad: return nil } -func methodname(n *Node, t *Node) *Node { +func methodname(n, recv *Node) *Node { star := false - if t.Op == OIND { + if recv.Op == OIND { star = true - t = t.Left + recv = recv.Left } - return methodname0(n.Sym, star, t.Sym) + return methodname0(n.Sym, star, recv.Sym) } func methodname0(s *Sym, star bool, tsym *Sym) *Node { @@ -1198,7 +1198,6 @@ func methodname0(s *Sym, star bool, tsym *Sym) *Node { // - msym is the method symbol // - t is function type (with receiver) func addmethod(msym *Sym, t *Type, local, nointerface bool) { - // get field sym if msym == nil { Fatalf("no method symbol") } diff --git a/test/alias2.go b/test/alias2.go index fb0a97feb2..f404d0dd3b 100644 --- a/test/alias2.go +++ b/test/alias2.go @@ -35,9 +35,11 @@ type ( ) // Methods can be declared on the original named type and the alias. -func (T0) m1() {} -func (A0) m1() {} // TODO(gri) this should be an error -func (A0) m2() {} +func (T0) m1() {} +func (*T0) m1() {} // ERROR "method redeclared: T0\.m1" +func (A0) m1() {} // TODO(gri) this should be an error +func (A0) m1() {} // ERROR "A0\.m1 redeclared in this block" +func (A0) m2() {} // Type aliases and the original type name can be used interchangeably. var _ A0 = T0{} @@ -91,6 +93,9 @@ func _() { type _ = reflect.ValueOf // ERROR "reflect.ValueOf is not a type" func (A1) m() {} // ERROR "cannot define new methods on non-local type int" +func (A2) m() {} // ERROR "invalid receiver type struct {}" +func (A3) m() {} // ERROR "cannot define new methods on non-local type reflect.Value" +func (A4) m() {} // ERROR "cannot define new methods on non-local type reflect.Value" type B1 = struct{} diff --git a/test/alias3.dir/a.go b/test/alias3.dir/a.go new file mode 100644 index 0000000000..09b3408d16 --- /dev/null +++ b/test/alias3.dir/a.go @@ -0,0 +1,42 @@ +// Copyright 2017 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. + +package a + +import "go/build" + +type ( + Float64 = float64 + Rune = rune +) + +type ( + Int int + IntAlias = Int + IntAlias2 = IntAlias + S struct { + Int + IntAlias + IntAlias2 + } +) + +type ( + Context = build.Context +) + +type ( + I1 interface { + M1(IntAlias2) Float64 + M2() Context + } + + I2 = interface { + M1(Int) float64 + M2() build.Context + } +) + +var i1 I1 +var i2 I2 = i1 diff --git a/test/alias3.dir/b.go b/test/alias3.dir/b.go new file mode 100644 index 0000000000..8a86cc0643 --- /dev/null +++ b/test/alias3.dir/b.go @@ -0,0 +1,26 @@ +// Copyright 2017 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. + +package b + +import ( + "./a" + . "go/build" +) + +func F(x float64) a.Float64 { + return x +} + +type MyContext = Context // = build.Context + +var C a.Context = Default + +type S struct{} + +func (S) M1(x a.IntAlias) float64 { return a.Float64(x) } +func (S) M2() Context { return Default } + +var _ a.I1 = S{} +var _ a.I2 = S{} diff --git a/test/alias3.dir/c.go b/test/alias3.dir/c.go new file mode 100644 index 0000000000..161d5934c2 --- /dev/null +++ b/test/alias3.dir/c.go @@ -0,0 +1,25 @@ +// Copyright 2017 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. + +package main + +import ( + "./a" + "./b" +) + +func main() { + var _ float64 = b.F(0) + var _ a.Rune = int32(0) + + // embedded types can have different names but the same types + var s a.S + s.Int = 1 + s.IntAlias = s.Int + s.IntAlias2 = s.Int + + // aliases denote identical types across packages + var c a.Context = b.C + var _ b.MyContext = c +} diff --git a/test/alias3.go b/test/alias3.go new file mode 100644 index 0000000000..c3732c311b --- /dev/null +++ b/test/alias3.go @@ -0,0 +1,7 @@ +// rundir + +// Copyright 2017 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. + +package ignored From a7c884efc14368750e30067367b6eab57ed06c0e Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Tue, 17 Jan 2017 15:06:04 -0800 Subject: [PATCH 17/26] [dev.typealias] go/internal/gccgoimporter: support for type aliases For #18130. Change-Id: Iac182a6c5bc62633eb02191d9da6166d3b254c4c Reviewed-on: https://go-review.googlesource.com/35268 Run-TryBot: Robert Griesemer TryBot-Result: Gobot Gobot Reviewed-by: Alan Donovan --- .../internal/gccgoimporter/importer_test.go | 1 + src/go/internal/gccgoimporter/parser.go | 52 ++++++++++++------- .../internal/gccgoimporter/testdata/alias.gox | 4 ++ 3 files changed, 38 insertions(+), 19 deletions(-) create mode 100644 src/go/internal/gccgoimporter/testdata/alias.gox diff --git a/src/go/internal/gccgoimporter/importer_test.go b/src/go/internal/gccgoimporter/importer_test.go index 2b454701be..4fca828bf6 100644 --- a/src/go/internal/gccgoimporter/importer_test.go +++ b/src/go/internal/gccgoimporter/importer_test.go @@ -101,6 +101,7 @@ var importerTests = [...]importerTest{ {pkgpath: "unicode", name: "IsUpper", want: "func IsUpper(r rune) bool"}, {pkgpath: "unicode", name: "MaxRune", want: "const MaxRune untyped rune", wantval: "1114111"}, {pkgpath: "imports", wantinits: []string{"imports..import", "fmt..import", "math..import"}}, + {pkgpath: "alias", name: "IntAlias2", want: "type IntAlias2 = Int"}, } func TestGoxImporter(t *testing.T) { diff --git a/src/go/internal/gccgoimporter/parser.go b/src/go/internal/gccgoimporter/parser.go index 3b97c96d43..0d788653e3 100644 --- a/src/go/internal/gccgoimporter/parser.go +++ b/src/go/internal/gccgoimporter/parser.go @@ -370,27 +370,41 @@ func (p *parser) parseConst(pkg *types.Package) *types.Const { return types.NewConst(token.NoPos, pkg, name, typ, val) } -// TypeName = ExportedName . -func (p *parser) parseTypeName() *types.TypeName { - pkg, name := p.parseExportedName() - scope := pkg.Scope() - if obj := scope.Lookup(name); obj != nil { - return obj.(*types.TypeName) - } - obj := types.NewTypeName(token.NoPos, pkg, name, nil) - // a named type may be referred to before the underlying type - // is known - set it up - types.NewNamed(obj, nil, nil) - scope.Insert(obj) - return obj -} - -// NamedType = TypeName Type { Method } . +// NamedType = TypeName [ "=" ] Type { Method } . +// TypeName = ExportedName . // Method = "func" "(" Param ")" Name ParamList ResultList ";" . func (p *parser) parseNamedType(n int) types.Type { - obj := p.parseTypeName() + pkg, name := p.parseExportedName() + scope := pkg.Scope() + + if p.tok == '=' { + // type alias + p.next() + typ := p.parseType(pkg) + if obj := scope.Lookup(name); obj != nil { + typ = obj.Type() // use previously imported type + if typ == nil { + p.errorf("%v (type alias) used in cycle", obj) + } + } else { + obj = types.NewTypeName(token.NoPos, pkg, name, typ) + scope.Insert(obj) + } + p.typeMap[n] = typ + return typ + } + + // named type + obj := scope.Lookup(name) + if obj == nil { + // a named type may be referred to before the underlying type + // is known - set it up + tname := types.NewTypeName(token.NoPos, pkg, name, nil) + types.NewNamed(tname, nil, nil) + scope.Insert(tname) + obj = tname + } - pkg := obj.Pkg() typ := obj.Type() p.typeMap[n] = typ @@ -409,8 +423,8 @@ func (p *parser) parseNamedType(n int) types.Type { nt.SetUnderlying(underlying.Underlying()) } + // collect associated methods for p.tok == scanner.Ident { - // collect associated methods p.expectKeyword("func") p.expect('(') receiver, _ := p.parseParam(pkg) diff --git a/src/go/internal/gccgoimporter/testdata/alias.gox b/src/go/internal/gccgoimporter/testdata/alias.gox new file mode 100644 index 0000000000..ced7d84c4f --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/alias.gox @@ -0,0 +1,4 @@ +v1; +package alias; +pkgpath alias; +type >>>) < type 114>; M2 () ; }>>; From 5d92916770ef57aeb2ae2cb556285d5e093c3aa0 Mon Sep 17 00:00:00 2001 From: Matthew Dempsky Date: Mon, 23 Jan 2017 13:40:25 -0800 Subject: [PATCH 18/26] [dev.typealias] cmd/compile: change Func.Shortname to *Sym A Func's Shortname is just an identifier. No need for an entire ONAME Node. Change-Id: Ie4d397e8d694c907fdf924ce57bd96bdb4aaabca Reviewed-on: https://go-review.googlesource.com/35574 Run-TryBot: Matthew Dempsky TryBot-Result: Gobot Gobot Reviewed-by: Robert Griesemer --- src/cmd/compile/internal/gc/dcl.go | 8 ++++---- src/cmd/compile/internal/gc/noder.go | 2 +- src/cmd/compile/internal/gc/obj.go | 2 +- src/cmd/compile/internal/gc/syntax.go | 2 +- src/cmd/compile/internal/gc/typecheck.go | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/cmd/compile/internal/gc/dcl.go b/src/cmd/compile/internal/gc/dcl.go index f934a650bd..a5c50f06dc 100644 --- a/src/cmd/compile/internal/gc/dcl.go +++ b/src/cmd/compile/internal/gc/dcl.go @@ -1163,14 +1163,14 @@ bad: return nil } -func methodname(n, recv *Node) *Node { +func methodname(s *Sym, recv *Node) *Node { star := false if recv.Op == OIND { star = true recv = recv.Left } - return methodname0(n.Sym, star, recv.Sym) + return methodname0(s, star, recv.Sym) } func methodname0(s *Sym, star bool, tsym *Sym) *Node { @@ -1318,7 +1318,7 @@ func funcsym(s *Sym) *Sym { s1 := Pkglookup(s.Name+"·f", s.Pkg) if !Ctxt.Flag_dynlink && s1.Def == nil { s1.Def = newfuncname(s1) - s1.Def.Func.Shortname = newname(s) + s1.Def.Func.Shortname = s funcsyms = append(funcsyms, s1.Def) } s.Fsym = s1 @@ -1336,7 +1336,7 @@ func makefuncsym(s *Sym) { } s1 := funcsym(s) s1.Def = newfuncname(s1) - s1.Def.Func.Shortname = newname(s) + s1.Def.Func.Shortname = s funcsyms = append(funcsyms, s1.Def) } diff --git a/src/cmd/compile/internal/gc/noder.go b/src/cmd/compile/internal/gc/noder.go index 699015488a..0c5957f987 100644 --- a/src/cmd/compile/internal/gc/noder.go +++ b/src/cmd/compile/internal/gc/noder.go @@ -252,7 +252,7 @@ func (p *noder) funcHeader(fun *syntax.FuncDecl) *Node { } else { // Receiver MethodName Signature - f.Func.Shortname = newfuncname(name) + f.Func.Shortname = name f.Func.Nname = methodname(f.Func.Shortname, t.Left.Right) } diff --git a/src/cmd/compile/internal/gc/obj.go b/src/cmd/compile/internal/gc/obj.go index 08ed5604da..6d5f2aa208 100644 --- a/src/cmd/compile/internal/gc/obj.go +++ b/src/cmd/compile/internal/gc/obj.go @@ -213,7 +213,7 @@ func dumpglobls() { } for _, n := range funcsyms { - dsymptr(n.Sym, 0, n.Sym.Def.Func.Shortname.Sym, 0) + dsymptr(n.Sym, 0, n.Sym.Def.Func.Shortname, 0) ggloblsym(n.Sym, int32(Widthptr), obj.DUPOK|obj.RODATA) } diff --git a/src/cmd/compile/internal/gc/syntax.go b/src/cmd/compile/internal/gc/syntax.go index 1955a0e42f..0bd877e26a 100644 --- a/src/cmd/compile/internal/gc/syntax.go +++ b/src/cmd/compile/internal/gc/syntax.go @@ -289,7 +289,7 @@ type Param struct { // Func holds Node fields used only with function-like nodes. type Func struct { - Shortname *Node + Shortname *Sym Enter Nodes // for example, allocate and initialize memory for escaping parameters Exit Nodes Cvars Nodes // closure params diff --git a/src/cmd/compile/internal/gc/typecheck.go b/src/cmd/compile/internal/gc/typecheck.go index f18bcfad78..d751610763 100644 --- a/src/cmd/compile/internal/gc/typecheck.go +++ b/src/cmd/compile/internal/gc/typecheck.go @@ -3436,7 +3436,7 @@ func typecheckfunc(n *Node) { t.SetNname(n.Func.Nname) rcvr := t.Recv() if rcvr != nil && n.Func.Shortname != nil { - addmethod(n.Func.Shortname.Sym, t, true, n.Func.Pragma&Nointerface != 0) + addmethod(n.Func.Shortname, t, true, n.Func.Pragma&Nointerface != 0) } } From 9259f3073afe0830ab1484bfee46bfa1f322e7e7 Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Tue, 24 Jan 2017 12:43:52 -0800 Subject: [PATCH 19/26] [dev.typealias] test: match gccgo error messages on alias2.go For #18130. Change-Id: I9561ee2b8a9f7b11f0851f281a899f78b9e9703e Reviewed-on: https://go-review.googlesource.com/35640 Reviewed-by: Robert Griesemer --- test/alias2.go | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/test/alias2.go b/test/alias2.go index f404d0dd3b..58eedf0c8c 100644 --- a/test/alias2.go +++ b/test/alias2.go @@ -35,10 +35,10 @@ type ( ) // Methods can be declared on the original named type and the alias. -func (T0) m1() {} -func (*T0) m1() {} // ERROR "method redeclared: T0\.m1" -func (A0) m1() {} // TODO(gri) this should be an error -func (A0) m1() {} // ERROR "A0\.m1 redeclared in this block" +func (T0) m1() {} // GCCGO_ERROR "previous" +func (*T0) m1() {} // ERROR "method redeclared: T0\.m1|redefinition of .m1." +func (A0) m1() {} // TODO(gri) this should be an error // GCCGO_ERROR "redefinition of .m1." +func (A0) m1() {} // ERROR "A0\.m1 redeclared in this block|redefinition of .m1." func (A0) m2() {} // Type aliases and the original type name can be used interchangeably. @@ -46,8 +46,8 @@ var _ A0 = T0{} var _ T0 = A0{} // But aliases and original types cannot be used with new types based on them. -var _ N0 = T0{} // ERROR "cannot use T0 literal \(type T0\) as type N0 in assignment" -var _ N0 = A0{} // ERROR "cannot use T0 literal \(type T0\) as type N0 in assignment" +var _ N0 = T0{} // ERROR "cannot use T0 literal \(type T0\) as type N0 in assignment|incompatible type" +var _ N0 = A0{} // ERROR "cannot use T0 literal \(type T0\) as type N0 in assignment|incompatible type" var _ A5 = Value{} @@ -82,20 +82,20 @@ func _() { var _ A0 = T0{} var _ T0 = A0{} - var _ N0 = T0{} // ERROR "cannot use T0 literal \(type T0\) as type N0 in assignment" - var _ N0 = A0{} // ERROR "cannot use T0 literal \(type T0\) as type N0 in assignment" + var _ N0 = T0{} // ERROR "cannot use T0 literal \(type T0\) as type N0 in assignment|incompatible type" + var _ N0 = A0{} // ERROR "cannot use T0 literal \(type T0\) as type N0 in assignment|incompatible type" - var _ A5 = Value{} // ERROR "cannot use reflect\.Value literal \(type reflect.Value\) as type A5 in assignment" + var _ A5 = Value{} // ERROR "cannot use reflect\.Value literal \(type reflect.Value\) as type A5 in assignment|incompatible type" } // Invalid type alias declarations. -type _ = reflect.ValueOf // ERROR "reflect.ValueOf is not a type" +type _ = reflect.ValueOf // ERROR "reflect.ValueOf is not a type|expected type" -func (A1) m() {} // ERROR "cannot define new methods on non-local type int" -func (A2) m() {} // ERROR "invalid receiver type struct {}" -func (A3) m() {} // ERROR "cannot define new methods on non-local type reflect.Value" -func (A4) m() {} // ERROR "cannot define new methods on non-local type reflect.Value" +func (A1) m() {} // ERROR "cannot define new methods on non-local type int|may not define methods on non-local type" +func (A2) m() {} // ERROR "invalid receiver type" +func (A3) m() {} // ERROR "cannot define new methods on non-local type reflect.Value|may not define methods on non-local type" +func (A4) m() {} // ERROR "cannot define new methods on non-local type reflect.Value|may not define methods on non-local type" type B1 = struct{} From de2e5459aecb531a67dad274b789ffeb61dca020 Mon Sep 17 00:00:00 2001 From: Matthew Dempsky Date: Mon, 23 Jan 2017 14:24:24 -0800 Subject: [PATCH 20/26] [dev.typealias] cmd/compile: declare methods after resolving receiver type For #18130. Fixes #18655. Change-Id: I58e2f076b9d8273f128cc033bba9edcd06c81567 Reviewed-on: https://go-review.googlesource.com/35575 Run-TryBot: Matthew Dempsky TryBot-Result: Gobot Gobot Reviewed-by: Robert Griesemer --- src/cmd/compile/internal/gc/bimport.go | 9 +-------- src/cmd/compile/internal/gc/dcl.go | 25 ++++++++++++------------ src/cmd/compile/internal/gc/export.go | 2 +- src/cmd/compile/internal/gc/noder.go | 12 ++++++------ src/cmd/compile/internal/gc/typecheck.go | 7 +++++++ test/alias2.go | 8 ++++---- test/fixedbugs/issue18655.go | 22 +++++++++++++++++++++ 7 files changed, 53 insertions(+), 32 deletions(-) create mode 100644 test/fixedbugs/issue18655.go diff --git a/src/cmd/compile/internal/gc/bimport.go b/src/cmd/compile/internal/gc/bimport.go index 3c1f7100c3..1ee9e76737 100644 --- a/src/cmd/compile/internal/gc/bimport.go +++ b/src/cmd/compile/internal/gc/bimport.go @@ -466,14 +466,7 @@ func (p *importer) typ() *Type { result := p.paramList() nointerface := p.bool() - base := recv[0].Type - star := false - if base.IsPtr() { - base = base.Elem() - star = true - } - - n := methodname0(sym, star, base.Sym) + n := newfuncname(methodname(sym, recv[0].Type)) n.Type = functypefield(recv[0], params, result) checkwidth(n.Type) addmethod(sym, n.Type, false, nointerface) diff --git a/src/cmd/compile/internal/gc/dcl.go b/src/cmd/compile/internal/gc/dcl.go index a5c50f06dc..856a7faced 100644 --- a/src/cmd/compile/internal/gc/dcl.go +++ b/src/cmd/compile/internal/gc/dcl.go @@ -519,10 +519,6 @@ func funchdr(n *Node) { Fatalf("funchdr: dclcontext = %d", dclcontext) } - if Ctxt.Flag_dynlink && importpkg == nil && n.Func.Nname != nil { - makefuncsym(n.Func.Nname.Sym) - } - dclcontext = PAUTO funcstart(n) @@ -1163,19 +1159,19 @@ bad: return nil } -func methodname(s *Sym, recv *Node) *Node { +// methodname is a misnomer because this now returns a Sym, rather +// than an ONAME. +// TODO(mdempsky): Reconcile with methodsym. +func methodname(s *Sym, recv *Type) *Sym { star := false - if recv.Op == OIND { + if recv.IsPtr() { star = true - recv = recv.Left + recv = recv.Elem() } - return methodname0(s, star, recv.Sym) -} - -func methodname0(s *Sym, star bool, tsym *Sym) *Node { + tsym := recv.Sym if tsym == nil || isblanksym(s) { - return newfuncname(s) + return s } var p string @@ -1191,7 +1187,7 @@ func methodname0(s *Sym, star bool, tsym *Sym) *Node { s = Pkglookup(p, tsym.Pkg) } - return newfuncname(s) + return s } // Add a method, declared as a function. @@ -1335,6 +1331,9 @@ func makefuncsym(s *Sym) { return } s1 := funcsym(s) + if s1.Def != nil { + return + } s1.Def = newfuncname(s1) s1.Def.Func.Shortname = s funcsyms = append(funcsyms, s1.Def) diff --git a/src/cmd/compile/internal/gc/export.go b/src/cmd/compile/internal/gc/export.go index 5556984dcb..58b2bf8121 100644 --- a/src/cmd/compile/internal/gc/export.go +++ b/src/cmd/compile/internal/gc/export.go @@ -83,7 +83,7 @@ func autoexport(n *Node, ctxt Class) { if (ctxt != PEXTERN && ctxt != PFUNC) || dclcontext != PEXTERN { return } - if n.Name.Param != nil && n.Name.Param.Ntype != nil && n.Name.Param.Ntype.Op == OTFUNC && n.Name.Param.Ntype.Left != nil { // method + if n.Type != nil && n.Type.IsKind(TFUNC) && n.Type.Recv() != nil { // method return } diff --git a/src/cmd/compile/internal/gc/noder.go b/src/cmd/compile/internal/gc/noder.go index 0c5957f987..1d69151cc4 100644 --- a/src/cmd/compile/internal/gc/noder.go +++ b/src/cmd/compile/internal/gc/noder.go @@ -247,19 +247,19 @@ func (p *noder) funcHeader(fun *syntax.FuncDecl) *Node { yyerror("func main must have no arguments and no return values") } } - - f.Func.Nname = newfuncname(name) } else { - // Receiver MethodName Signature - f.Func.Shortname = name - f.Func.Nname = methodname(f.Func.Shortname, t.Left.Right) + name = nblank.Sym // filled in by typecheckfunc } + f.Func.Nname = newfuncname(name) f.Func.Nname.Name.Defn = f f.Func.Nname.Name.Param.Ntype = t // TODO: check if nname already has an ntype - declare(f.Func.Nname, PFUNC) + if fun.Recv == nil { + declare(f.Func.Nname, PFUNC) + } + funchdr(f) return f } diff --git a/src/cmd/compile/internal/gc/typecheck.go b/src/cmd/compile/internal/gc/typecheck.go index d751610763..1379bb56d4 100644 --- a/src/cmd/compile/internal/gc/typecheck.go +++ b/src/cmd/compile/internal/gc/typecheck.go @@ -3436,8 +3436,15 @@ func typecheckfunc(n *Node) { t.SetNname(n.Func.Nname) rcvr := t.Recv() if rcvr != nil && n.Func.Shortname != nil { + n.Func.Nname.Sym = methodname(n.Func.Shortname, rcvr.Type) + declare(n.Func.Nname, PFUNC) + addmethod(n.Func.Shortname, t, true, n.Func.Pragma&Nointerface != 0) } + + if Ctxt.Flag_dynlink && importpkg == nil && n.Func.Nname != nil { + makefuncsym(n.Func.Nname.Sym) + } } // The result of stringtoarraylit MUST be assigned back to n, e.g. diff --git a/test/alias2.go b/test/alias2.go index 58eedf0c8c..32c3654995 100644 --- a/test/alias2.go +++ b/test/alias2.go @@ -37,8 +37,8 @@ type ( // Methods can be declared on the original named type and the alias. func (T0) m1() {} // GCCGO_ERROR "previous" func (*T0) m1() {} // ERROR "method redeclared: T0\.m1|redefinition of .m1." -func (A0) m1() {} // TODO(gri) this should be an error // GCCGO_ERROR "redefinition of .m1." -func (A0) m1() {} // ERROR "A0\.m1 redeclared in this block|redefinition of .m1." +func (A0) m1() {} // ERROR "T0\.m1 redeclared in this block|redefinition of .m1." +func (A0) m1() {} // ERROR "T0\.m1 redeclared in this block|redefinition of .m1." func (A0) m2() {} // Type aliases and the original type name can be used interchangeably. @@ -95,10 +95,10 @@ type _ = reflect.ValueOf // ERROR "reflect.ValueOf is not a type|expected type" func (A1) m() {} // ERROR "cannot define new methods on non-local type int|may not define methods on non-local type" func (A2) m() {} // ERROR "invalid receiver type" func (A3) m() {} // ERROR "cannot define new methods on non-local type reflect.Value|may not define methods on non-local type" -func (A4) m() {} // ERROR "cannot define new methods on non-local type reflect.Value|may not define methods on non-local type" +func (A4) m() {} // ERROR "reflect.Value.m redeclared in this block" "cannot define new methods on non-local type reflect.Value|may not define methods on non-local type" type B1 = struct{} -func (B1) m() {} // ERROR "invalid receiver type" +func (B1) m() {} // ERROR "m redeclared in this block" "invalid receiver type" // TODO(gri) expand diff --git a/test/fixedbugs/issue18655.go b/test/fixedbugs/issue18655.go new file mode 100644 index 0000000000..abc2600280 --- /dev/null +++ b/test/fixedbugs/issue18655.go @@ -0,0 +1,22 @@ +// errorcheck + +// Copyright 2017 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. + +package p + +type T struct{} +type A = T +type B = T + +func (T) m() {} +func (T) m() {} // ERROR "redeclared" +func (A) m() {} // ERROR "redeclared" +func (A) m() {} // ERROR "redeclared" +func (B) m() {} // ERROR "redeclared" +func (B) m() {} // ERROR "redeclared" + +func (*T) m() {} // ERROR "redeclared" +func (*A) m() {} // ERROR "redeclared" +func (*B) m() {} // ERROR "redeclared" From 9657e0b0777f3af3b48908cc39e5ab6d06022422 Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Tue, 24 Jan 2017 14:53:31 -0500 Subject: [PATCH 21/26] [dev.typealias] cmd/doc: update for type alias For #18130. Change-Id: I06b05a2b45a2aa6764053fc51e05883063572dad Reviewed-on: https://go-review.googlesource.com/35670 Run-TryBot: Russ Cox Reviewed-by: Robert Griesemer --- src/cmd/doc/doc_test.go | 14 ++++++++++++++ src/cmd/doc/pkg.go | 6 +++++- src/cmd/doc/testdata/pkg.go | 4 ++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/cmd/doc/doc_test.go b/src/cmd/doc/doc_test.go index 1c054fd566..1244476ab2 100644 --- a/src/cmd/doc/doc_test.go +++ b/src/cmd/doc/doc_test.go @@ -71,6 +71,7 @@ var tests = []test{ `const MultiLineConst = ...`, // Multi line constant. `var MultiLineVar = map\[struct{ ... }\]struct{ ... }{ ... }`, // Multi line variable. `func MultiLineFunc\(x interface{ ... }\) \(r struct{ ... }\)`, // Multi line function. + `type T1 = T2`, // Type alias }, []string{ `const internalConstant = 2`, // No internal constants. @@ -89,6 +90,7 @@ var tests = []test{ `unexportedTypedConstant`, // No unexported typed constant. `Field`, // No fields. `Method`, // No methods. + `type T1 T2`, // Type alias does not display as type declaration. }, }, // Package dump -u @@ -265,6 +267,18 @@ var tests = []test{ `error`, // No embedded error. }, }, + // Type T1 dump (alias). + { + "type T1", + []string{p+".T1"}, + []string{ + `type T1 = T2`, + }, + []string{ + `type T1 T2`, + `type ExportedType`, + }, + }, // Type -u with unexported fields. { "type with unexported fields and -u", diff --git a/src/cmd/doc/pkg.go b/src/cmd/doc/pkg.go index daa6ed358c..32d08f21fd 100644 --- a/src/cmd/doc/pkg.go +++ b/src/cmd/doc/pkg.go @@ -258,7 +258,11 @@ func (pkg *Package) oneLineNodeDepth(node ast.Node, depth int) string { return fmt.Sprintf("func %s%s%s", recv, name, fnc) case *ast.TypeSpec: - return fmt.Sprintf("type %s %s", n.Name.Name, pkg.oneLineNodeDepth(n.Type, depth)) + sep := " " + if n.Assign.IsValid() { + sep = " = " + } + return fmt.Sprintf("type %s%s%s", n.Name.Name, sep, pkg.oneLineNodeDepth(n.Type, depth)) case *ast.FuncType: var params []string diff --git a/src/cmd/doc/testdata/pkg.go b/src/cmd/doc/testdata/pkg.go index 924daa171b..0ebea67d58 100644 --- a/src/cmd/doc/testdata/pkg.go +++ b/src/cmd/doc/testdata/pkg.go @@ -172,3 +172,7 @@ const ( ) const ConstGroup4 ExportedType = ExportedType{} + +type T2 int + +type T1 = T2 From 43c70943861ce39b44e5bd577a8c3c2ef18538db Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Wed, 25 Jan 2017 09:50:36 -0500 Subject: [PATCH 22/26] [dev.typealias] reflect: fix StructOf use of StructField to match StructField docs The runtime internal structField interprets name=="" as meaning anonymous, but the exported reflect.StructField has always set Name, even for anonymous fields, and also set Anonymous=true. The initial implementation of StructOf confused the internal and public meanings of the StructField, expecting the runtime representation of anonymous fields instead of the exported reflect API representation. It also did not document this fact, so that users had no way to know how to create an anonymous field. This CL changes StructOf to use the previously documented interpretation of reflect.StructField instead of an undocumented one. The implementation of StructOf also, in some cases, allowed creating structs with unexported fields (if you knew how to ask) but set the PkgPath incorrectly on those fields. Rather than try to fix that, this CL changes StructOf to reject attempts to create unexported fields. (I think that may be the right design choice, not just a temporary limitation. In any event, it's not the topic for today's work.) For #17766. Fixes #18780. Change-Id: I585a4e324dc5a90551f49d21ae04d2de9ea04b6c Reviewed-on: https://go-review.googlesource.com/35731 Run-TryBot: Russ Cox Reviewed-by: Robert Griesemer --- src/reflect/all_test.go | 136 +++++++++++++++++++++------------------- src/reflect/type.go | 33 +++++----- 2 files changed, 91 insertions(+), 78 deletions(-) diff --git a/src/reflect/all_test.go b/src/reflect/all_test.go index 033a18171d..e057b0cfcc 100644 --- a/src/reflect/all_test.go +++ b/src/reflect/all_test.go @@ -3689,7 +3689,7 @@ func checkSameType(t *testing.T, x, y interface{}) { func TestArrayOf(t *testing.T) { // check construction and use of type not in binary - for _, table := range []struct { + tests := []struct { n int value func(i int) interface{} comparable bool @@ -3767,7 +3767,9 @@ func TestArrayOf(t *testing.T) { comparable: true, want: "[{0 0} {1 1} {2 2} {3 3} {4 4} {5 5} {6 6} {7 7} {8 8} {9 9}]", }, - } { + } + + for _, table := range tests { at := ArrayOf(table.n, TypeOf(table.value(0))) v := New(at).Elem() vok := New(at).Elem() @@ -4133,50 +4135,58 @@ func TestStructOfExportRules(t *testing.T) { f() } - for i, test := range []struct { + tests := []struct { field StructField mustPanic bool exported bool }{ { - field: StructField{Name: "", Type: TypeOf(S1{})}, - mustPanic: false, - exported: true, + field: StructField{Name: "S1", Anonymous: true, Type: TypeOf(S1{})}, + exported: true, }, { - field: StructField{Name: "", Type: TypeOf((*S1)(nil))}, - mustPanic: false, - exported: true, + field: StructField{Name: "S1", Anonymous: true, Type: TypeOf((*S1)(nil))}, + exported: true, }, { - field: StructField{Name: "", Type: TypeOf(s2{})}, - mustPanic: false, - exported: false, - }, - { - field: StructField{Name: "", Type: TypeOf((*s2)(nil))}, - mustPanic: false, - exported: false, - }, - { - field: StructField{Name: "", Type: TypeOf(S1{}), PkgPath: "other/pkg"}, + field: StructField{Name: "s2", Anonymous: true, Type: TypeOf(s2{})}, mustPanic: true, - exported: true, }, { - field: StructField{Name: "", Type: TypeOf((*S1)(nil)), PkgPath: "other/pkg"}, + field: StructField{Name: "s2", Anonymous: true, Type: TypeOf((*s2)(nil))}, mustPanic: true, - exported: true, }, { - field: StructField{Name: "", Type: TypeOf(s2{}), PkgPath: "other/pkg"}, + field: StructField{Name: "Name", Type: nil, PkgPath: ""}, mustPanic: true, - exported: false, }, { - field: StructField{Name: "", Type: TypeOf((*s2)(nil)), PkgPath: "other/pkg"}, + field: StructField{Name: "", Type: TypeOf(S1{}), PkgPath: ""}, + mustPanic: true, + }, + { + field: StructField{Name: "S1", Anonymous: true, Type: TypeOf(S1{}), PkgPath: "other/pkg"}, + mustPanic: true, + }, + { + field: StructField{Name: "S1", Anonymous: true, Type: TypeOf((*S1)(nil)), PkgPath: "other/pkg"}, + mustPanic: true, + }, + { + field: StructField{Name: "s2", Anonymous: true, Type: TypeOf(s2{}), PkgPath: "other/pkg"}, + mustPanic: true, + }, + { + field: StructField{Name: "s2", Anonymous: true, Type: TypeOf((*s2)(nil)), PkgPath: "other/pkg"}, + mustPanic: true, + }, + { + field: StructField{Name: "s2", Type: TypeOf(int(0)), PkgPath: "other/pkg"}, + mustPanic: true, + }, + { + field: StructField{Name: "s2", Type: TypeOf(int(0)), PkgPath: "other/pkg"}, mustPanic: true, - exported: false, }, { field: StructField{Name: "S", Type: TypeOf(S1{})}, @@ -4184,81 +4194,68 @@ func TestStructOfExportRules(t *testing.T) { exported: true, }, { - field: StructField{Name: "S", Type: TypeOf((*S1)(nil))}, - mustPanic: false, - exported: true, + field: StructField{Name: "S", Type: TypeOf((*S1)(nil))}, + exported: true, }, { - field: StructField{Name: "S", Type: TypeOf(s2{})}, - mustPanic: false, - exported: true, + field: StructField{Name: "S", Type: TypeOf(s2{})}, + exported: true, }, { - field: StructField{Name: "S", Type: TypeOf((*s2)(nil))}, - mustPanic: false, - exported: true, + field: StructField{Name: "S", Type: TypeOf((*s2)(nil))}, + exported: true, }, { field: StructField{Name: "s", Type: TypeOf(S1{})}, mustPanic: true, - exported: false, }, { field: StructField{Name: "s", Type: TypeOf((*S1)(nil))}, mustPanic: true, - exported: false, }, { field: StructField{Name: "s", Type: TypeOf(s2{})}, mustPanic: true, - exported: false, }, { field: StructField{Name: "s", Type: TypeOf((*s2)(nil))}, mustPanic: true, - exported: false, }, { field: StructField{Name: "s", Type: TypeOf(S1{}), PkgPath: "other/pkg"}, mustPanic: true, // TODO(sbinet): creating a name with a package path - exported: false, }, { field: StructField{Name: "s", Type: TypeOf((*S1)(nil)), PkgPath: "other/pkg"}, mustPanic: true, // TODO(sbinet): creating a name with a package path - exported: false, }, { field: StructField{Name: "s", Type: TypeOf(s2{}), PkgPath: "other/pkg"}, mustPanic: true, // TODO(sbinet): creating a name with a package path - exported: false, }, { field: StructField{Name: "s", Type: TypeOf((*s2)(nil)), PkgPath: "other/pkg"}, mustPanic: true, // TODO(sbinet): creating a name with a package path - exported: false, }, { field: StructField{Name: "", Type: TypeOf(ΦType{})}, - mustPanic: false, - exported: true, + mustPanic: true, }, { field: StructField{Name: "", Type: TypeOf(φType{})}, - mustPanic: false, - exported: false, + mustPanic: true, }, { - field: StructField{Name: "Φ", Type: TypeOf(0)}, - mustPanic: false, - exported: true, + field: StructField{Name: "Φ", Type: TypeOf(0)}, + exported: true, }, { - field: StructField{Name: "φ", Type: TypeOf(0)}, - mustPanic: false, - exported: false, + field: StructField{Name: "φ", Type: TypeOf(0)}, + exported: false, }, - } { + } + + for i, test := range tests { testPanic(i, test.mustPanic, func() { typ := StructOf([]StructField{test.field}) if typ == nil { @@ -4346,7 +4343,7 @@ func TestStructOfGenericAlg(t *testing.T) { {Name: "S1", Type: st1}, }) - for _, table := range []struct { + tests := []struct { rt Type idx []int }{ @@ -4427,7 +4424,9 @@ func TestStructOfGenericAlg(t *testing.T) { ), idx: []int{2}, }, - } { + } + + for _, table := range tests { v1 := New(table.rt).Elem() v2 := New(table.rt).Elem() @@ -4529,18 +4528,21 @@ func TestStructOfWithInterface(t *testing.T) { type Iface interface { Get() int } - for i, table := range []struct { + tests := []struct { + name string typ Type val Value impl bool }{ { + name: "StructI", typ: TypeOf(StructI(want)), val: ValueOf(StructI(want)), impl: true, }, { - typ: PtrTo(TypeOf(StructI(want))), + name: "StructI", + typ: PtrTo(TypeOf(StructI(want))), val: ValueOf(func() interface{} { v := StructI(want) return &v @@ -4548,7 +4550,8 @@ func TestStructOfWithInterface(t *testing.T) { impl: true, }, { - typ: PtrTo(TypeOf(StructIPtr(want))), + name: "StructIPtr", + typ: PtrTo(TypeOf(StructIPtr(want))), val: ValueOf(func() interface{} { v := StructIPtr(want) return &v @@ -4556,6 +4559,7 @@ func TestStructOfWithInterface(t *testing.T) { impl: true, }, { + name: "StructIPtr", typ: TypeOf(StructIPtr(want)), val: ValueOf(StructIPtr(want)), impl: false, @@ -4565,13 +4569,16 @@ func TestStructOfWithInterface(t *testing.T) { // val: ValueOf(StructI(want)), // impl: true, // }, - } { + } + + for i, table := range tests { rt := StructOf( []StructField{ { - Name: "", - PkgPath: "", - Type: table.typ, + Name: table.name, + Anonymous: true, + PkgPath: "", + Type: table.typ, }, }, ) @@ -5951,6 +5958,7 @@ func TestSwapper(t *testing.T) { want: []pairPtr{{5, 6, &c}, {3, 4, &b}, {1, 2, &a}}, }, } + for i, tt := range tests { inStr := fmt.Sprint(tt.in) Swapper(tt.in)(tt.i, tt.j) diff --git a/src/reflect/type.go b/src/reflect/type.go index 9d6e7a6846..e0be36d970 100644 --- a/src/reflect/type.go +++ b/src/reflect/type.go @@ -2403,6 +2403,9 @@ func StructOf(fields []StructField) Type { lastzero := uintptr(0) repr = append(repr, "struct {"...) for i, field := range fields { + if field.Name == "" { + panic("reflect.StructOf: field " + strconv.Itoa(i) + " has no name") + } if field.Type == nil { panic("reflect.StructOf: field " + strconv.Itoa(i) + " has no type") } @@ -2794,23 +2797,25 @@ func StructOf(fields []StructField) Type { } func runtimeStructField(field StructField) structField { - exported := field.PkgPath == "" - if field.Name == "" { - t := field.Type.(*rtype) - if t.Kind() == Ptr { - t = t.Elem().(*rtype) - } - exported = t.nameOff(t.str).isExported() - } else if exported { - b0 := field.Name[0] - if ('a' <= b0 && b0 <= 'z') || b0 == '_' { - panic("reflect.StructOf: field \"" + field.Name + "\" is unexported but has no PkgPath") - } + if field.PkgPath != "" { + panic("reflect.StructOf: StructOf does not allow unexported fields") } - _ = resolveReflectType(field.Type.common()) + // Best-effort check for misuse. + // Since PkgPath is empty, not much harm done if Unicode lowercase slips through. + c := field.Name[0] + if 'a' <= c && c <= 'z' || c == '_' { + panic("reflect.StructOf: field \"" + field.Name + "\" is unexported but missing PkgPath") + } + + name := field.Name + if field.Anonymous { + name = "" + } + + resolveReflectType(field.Type.common()) // install in runtime return structField{ - name: newName(field.Name, string(field.Tag), field.PkgPath, exported), + name: newName(name, string(field.Tag), "", true), typ: field.Type.common(), offset: 0, } From 9bbb07ddec63e5e747f1cd9dbf82b7504b29dd09 Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Wed, 25 Jan 2017 10:19:33 -0500 Subject: [PATCH 23/26] [dev.typealias] cmd/compile, reflect: fix struct field names for embedded byte, rune Will also fix type aliases. Fixes #17766. For #18130. Change-Id: I9e1584d47128782152e06abd0a30ef423d5c30d2 Reviewed-on: https://go-review.googlesource.com/35732 Run-TryBot: Russ Cox Reviewed-by: Robert Griesemer --- src/cmd/compile/internal/gc/align.go | 8 ++- src/cmd/compile/internal/gc/reflect.go | 11 +++- src/cmd/link/internal/ld/decodesym.go | 2 +- src/reflect/all_test.go | 19 +++++- src/reflect/type.go | 89 +++++++++++--------------- src/reflect/value.go | 4 +- src/runtime/cgocall.go | 2 +- src/runtime/type.go | 12 ++-- 8 files changed, 85 insertions(+), 62 deletions(-) diff --git a/src/cmd/compile/internal/gc/align.go b/src/cmd/compile/internal/gc/align.go index eee801fb8e..1dd04e349d 100644 --- a/src/cmd/compile/internal/gc/align.go +++ b/src/cmd/compile/internal/gc/align.go @@ -74,7 +74,13 @@ func widstruct(errtype *Type, t *Type, o int64, flag int) int64 { lastzero = o } o += w - if o >= Thearch.MAXWIDTH { + maxwidth := Thearch.MAXWIDTH + // On 32-bit systems, reflect tables impose an additional constraint + // that each field start offset must fit in 31 bits. + if maxwidth < 1<<32 { + maxwidth = 1<<31 - 1 + } + if o >= maxwidth { yyerror("type %L too large", errtype) o = 8 // small but nonzero } diff --git a/src/cmd/compile/internal/gc/reflect.go b/src/cmd/compile/internal/gc/reflect.go index 61ac67c0bc..7cd02749a5 100644 --- a/src/cmd/compile/internal/gc/reflect.go +++ b/src/cmd/compile/internal/gc/reflect.go @@ -511,7 +511,7 @@ func isExportedField(ft *Field) (bool, *Pkg) { // dnameField dumps a reflect.name for a struct field. func dnameField(s *Sym, ot int, spkg *Pkg, ft *Field) int { var name string - if ft.Sym != nil && ft.Embedded == 0 { + if ft.Sym != nil { name = ft.Sym.Name } isExported, fpkg := isExportedField(ft) @@ -1345,7 +1345,14 @@ ok: // ../../../../runtime/type.go:/structField ot = dnameField(s, ot, pkg, f) ot = dsymptr(s, ot, dtypesym(f.Type), 0) - ot = duintptr(s, ot, uint64(f.Offset)) + offsetAnon := uint64(f.Offset) << 1 + if offsetAnon>>1 != uint64(f.Offset) { + Fatalf("%v: bad field offset for %s", t, f.Sym.Name) + } + if f.Embedded != 0 { + offsetAnon |= 1 + } + ot = duintptr(s, ot, offsetAnon) } } diff --git a/src/cmd/link/internal/ld/decodesym.go b/src/cmd/link/internal/ld/decodesym.go index d111b005d9..d898c40c1c 100644 --- a/src/cmd/link/internal/ld/decodesym.go +++ b/src/cmd/link/internal/ld/decodesym.go @@ -255,7 +255,7 @@ func decodetypeStructFieldType(s *Symbol, i int) *Symbol { func decodetypeStructFieldOffs(arch *sys.Arch, s *Symbol, i int) int64 { off := decodetypeStructFieldArrayOff(s, i) - return int64(decodeInuxi(arch, s.P[off+2*SysArch.PtrSize:], SysArch.IntSize)) + return int64(decodeInuxi(arch, s.P[off+2*SysArch.PtrSize:], SysArch.IntSize) >> 1) } // InterfaceType.methods.length diff --git a/src/reflect/all_test.go b/src/reflect/all_test.go index e057b0cfcc..ed3ad33835 100644 --- a/src/reflect/all_test.go +++ b/src/reflect/all_test.go @@ -4265,7 +4265,7 @@ func TestStructOfExportRules(t *testing.T) { field := typ.Field(0) n := field.Name if n == "" { - n = field.Type.Name() + panic("field.Name must not be empty") } exported := isExported(n) if exported != test.exported { @@ -5984,3 +5984,20 @@ func TestUnaddressableField(t *testing.T) { lv.Set(rv) }) } + +type Talias1 struct { + byte + uint8 + int + int32 + rune +} + +func TestAliasNames(t *testing.T) { + t1 := Talias1{byte: 1, uint8: 2, int: 3, int32: 4, rune: 5} + out := fmt.Sprintf("%#v", t1) + want := "reflect_test.Talias1{byte:0x1, uint8:0x2, int:3, int32:4, rune:5}" + if out != want { + t.Errorf("Talias1 print:\nhave: %s\nwant: %s", out, want) + } +} diff --git a/src/reflect/type.go b/src/reflect/type.go index e0be36d970..fbfda3a363 100644 --- a/src/reflect/type.go +++ b/src/reflect/type.go @@ -417,9 +417,17 @@ type sliceType struct { // Struct field type structField struct { - name name // name is empty for embedded fields - typ *rtype // type of field - offset uintptr // byte offset of field within struct + name name // name is always non-empty + typ *rtype // type of field + offsetAnon uintptr // byte offset of field<<1 | isAnonymous +} + +func (f *structField) offset() uintptr { + return f.offsetAnon >> 1 +} + +func (f *structField) anon() bool { + return f.offsetAnon&1 != 0 } // structType represents a struct type. @@ -1215,16 +1223,8 @@ func (t *structType) Field(i int) (f StructField) { } p := &t.fields[i] f.Type = toType(p.typ) - if name := p.name.name(); name != "" { - f.Name = name - } else { - t := f.Type - if t.Kind() == Ptr { - t = t.Elem() - } - f.Name = t.Name() - f.Anonymous = true - } + f.Name = p.name.name() + f.Anonymous = p.anon() if !p.name.isExported() { f.PkgPath = p.name.pkgPath() if f.PkgPath == "" { @@ -1234,7 +1234,7 @@ func (t *structType) Field(i int) (f StructField) { if tag := p.name.tag(); tag != "" { f.Tag = StructTag(tag) } - f.Offset = p.offset + f.Offset = p.offset() // NOTE(rsc): This is the only allocation in the interface // presented by a reflect.Type. It would be nice to avoid, @@ -1321,19 +1321,15 @@ func (t *structType) FieldByNameFunc(match func(string) bool) (result StructFiel visited[t] = true for i := range t.fields { f := &t.fields[i] - // Find name and type for field f. - var fname string + // Find name and (for anonymous field) type for field f. + fname := f.name.name() var ntyp *rtype - if name := f.name.name(); name != "" { - fname = name - } else { + if f.anon() { // Anonymous field of type T or *T. - // Name taken from type. ntyp = f.typ if ntyp.Kind() == Ptr { ntyp = ntyp.Elem().common() } - fname = ntyp.Name() } // Does it match? @@ -1390,14 +1386,12 @@ func (t *structType) FieldByName(name string) (f StructField, present bool) { if name != "" { for i := range t.fields { tf := &t.fields[i] - tfname := tf.name.name() - if tfname == "" { - hasAnon = true - continue - } - if tfname == name { + if tf.name.name() == name { return t.Field(i), true } + if tf.anon() { + hasAnon = true + } } } if !hasAnon { @@ -1694,7 +1688,7 @@ func haveIdenticalUnderlyingType(T, V *rtype, cmpTags bool) bool { if cmpTags && tf.name.tag() != vf.name.tag() { return false } - if tf.offset != vf.offset { + if tf.offsetAnon != vf.offsetAnon { return false } if !tf.name.isExported() { @@ -2418,13 +2412,11 @@ func StructOf(fields []StructField) Type { hasPtr = true } - name := "" // Update string and hash - if f.name.nameLen() > 0 { - hash = fnv1(hash, []byte(f.name.name())...) - repr = append(repr, (" " + f.name.name())...) - name = f.name.name() - } else { + name := f.name.name() + hash = fnv1(hash, []byte(name)...) + repr = append(repr, (" " + name)...) + if f.anon() { // Embedded field if f.typ.Kind() == Ptr { // Embedded ** and *interface{} are illegal @@ -2432,11 +2424,7 @@ func StructOf(fields []StructField) Type { if k := elem.Kind(); k == Ptr || k == Interface { panic("reflect.StructOf: illegal anonymous field type " + ft.String()) } - name = elem.String() - } else { - name = ft.String() } - // TODO(sbinet) check for syntactically impossible type names? switch f.typ.Kind() { case Interface: @@ -2568,11 +2556,12 @@ func StructOf(fields []StructField) Type { comparable = comparable && (ft.alg.equal != nil) hashable = hashable && (ft.alg.hash != nil) - f.offset = align(size, uintptr(ft.align)) + offset := align(size, uintptr(ft.align)) if ft.align > typalign { typalign = ft.align } - size = f.offset + ft.size + size = offset + ft.size + f.offsetAnon |= offset << 1 if ft.size == 0 { lastzero = size @@ -2764,7 +2753,7 @@ func StructOf(fields []StructField) Type { typ.alg.hash = func(p unsafe.Pointer, seed uintptr) uintptr { o := seed for _, ft := range typ.fields { - pi := unsafe.Pointer(uintptr(p) + ft.offset) + pi := unsafe.Pointer(uintptr(p) + ft.offset()) o = ft.typ.alg.hash(pi, o) } return o @@ -2774,8 +2763,8 @@ func StructOf(fields []StructField) Type { if comparable { typ.alg.equal = func(p, q unsafe.Pointer) bool { for _, ft := range typ.fields { - pi := unsafe.Pointer(uintptr(p) + ft.offset) - qi := unsafe.Pointer(uintptr(q) + ft.offset) + pi := unsafe.Pointer(uintptr(p) + ft.offset()) + qi := unsafe.Pointer(uintptr(q) + ft.offset()) if !ft.typ.alg.equal(pi, qi) { return false } @@ -2808,16 +2797,16 @@ func runtimeStructField(field StructField) structField { panic("reflect.StructOf: field \"" + field.Name + "\" is unexported but missing PkgPath") } - name := field.Name + offsetAnon := uintptr(0) if field.Anonymous { - name = "" + offsetAnon |= 1 } resolveReflectType(field.Type.common()) // install in runtime return structField{ - name: newName(name, string(field.Tag), "", true), - typ: field.Type.common(), - offset: 0, + name: newName(field.Name, string(field.Tag), "", true), + typ: field.Type.common(), + offsetAnon: offsetAnon, } } @@ -2840,7 +2829,7 @@ func typeptrdata(t *rtype) uintptr { } } f := st.fields[field] - return f.offset + f.typ.ptrdata + return f.offset() + f.typ.ptrdata default: panic("reflect.typeptrdata: unexpected type, " + t.String()) @@ -3214,7 +3203,7 @@ func addTypeBits(bv *bitVector, offset uintptr, t *rtype) { tt := (*structType)(unsafe.Pointer(t)) for i := range tt.fields { f := &tt.fields[i] - addTypeBits(bv, offset+f.offset, f.typ) + addTypeBits(bv, offset+f.offset(), f.typ) } } } diff --git a/src/reflect/value.go b/src/reflect/value.go index 042414ffe7..a1bfb6d489 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -755,7 +755,7 @@ func (v Value) Field(i int) Value { fl := v.flag&(flagStickyRO|flagIndir|flagAddr) | flag(typ.Kind()) // Using an unexported field forces flagRO. if !field.name.isExported() { - if field.name.name() == "" { + if field.anon() { fl |= flagEmbedRO } else { fl |= flagStickyRO @@ -766,7 +766,7 @@ func (v Value) Field(i int) Value { // In the former case, we want v.ptr + offset. // In the latter case, we must have field.offset = 0, // so v.ptr + field.offset is still okay. - ptr := unsafe.Pointer(uintptr(v.ptr) + field.offset) + ptr := unsafe.Pointer(uintptr(v.ptr) + field.offset()) return Value{typ, ptr, fl} } diff --git a/src/runtime/cgocall.go b/src/runtime/cgocall.go index 69e29ef976..879e786231 100644 --- a/src/runtime/cgocall.go +++ b/src/runtime/cgocall.go @@ -531,7 +531,7 @@ func cgoCheckArg(t *_type, p unsafe.Pointer, indir, top bool, msg string) { return } for _, f := range st.fields { - cgoCheckArg(f.typ, add(p, f.offset), true, top, msg) + cgoCheckArg(f.typ, add(p, f.offset()), true, top, msg) } case kindPtr, kindUnsafePointer: if indir { diff --git a/src/runtime/type.go b/src/runtime/type.go index 3ecc54c72c..10442eff69 100644 --- a/src/runtime/type.go +++ b/src/runtime/type.go @@ -390,9 +390,13 @@ type ptrtype struct { } type structfield struct { - name name - typ *_type - offset uintptr + name name + typ *_type + offsetAnon uintptr +} + +func (f *structfield) offset() uintptr { + return f.offsetAnon >> 1 } type structtype struct { @@ -650,7 +654,7 @@ func typesEqual(t, v *_type) bool { if tf.name.tag() != vf.name.tag() { return false } - if tf.offset != vf.offset { + if tf.offsetAnon != vf.offsetAnon { return false } } From 49b7af8a308c24b1e3f6e83ded9e97513316c8d5 Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Tue, 24 Jan 2017 14:59:22 -0500 Subject: [PATCH 24/26] [dev.typealias] reflect: add test for type aliases For #18130. Change-Id: Idd77cb391178c185227cfd779c70fec16351f825 Reviewed-on: https://go-review.googlesource.com/35733 Run-TryBot: Russ Cox Reviewed-by: Robert Griesemer Reviewed-by: Ian Lance Taylor --- src/reflect/all_test.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/reflect/all_test.go b/src/reflect/all_test.go index ed3ad33835..1fed972eea 100644 --- a/src/reflect/all_test.go +++ b/src/reflect/all_test.go @@ -5985,6 +5985,11 @@ func TestUnaddressableField(t *testing.T) { }) } + +type Tint int + +type Tint2 = Tint + type Talias1 struct { byte uint8 @@ -5993,6 +5998,11 @@ type Talias1 struct { rune } +type Talias2 struct { + Tint + Tint2 +} + func TestAliasNames(t *testing.T) { t1 := Talias1{byte: 1, uint8: 2, int: 3, int32: 4, rune: 5} out := fmt.Sprintf("%#v", t1) @@ -6000,4 +6010,12 @@ func TestAliasNames(t *testing.T) { if out != want { t.Errorf("Talias1 print:\nhave: %s\nwant: %s", out, want) } + + t2 := Talias2{Tint: 1, Tint2: 2} + out = fmt.Sprintf("%#v", t2) + want = "reflect_test.Talias2{Tint:1, Tint2:2}" + if out != want { + t.Errorf("Talias2 print:\nhave: %s\nwant: %s", out, want) + } } + From 9ecc3ee2523f2db87b5b2d79efdd04abda93fb6e Mon Sep 17 00:00:00 2001 From: Matthew Dempsky Date: Thu, 26 Jan 2017 09:00:56 -0800 Subject: [PATCH 25/26] [dev.typealias] cmd/compile: avoid false positive cycles from type aliases For #18130. Fixes #18640. Change-Id: I26cf1d1b78cca6ef207cc4333f30a9011ef347c9 Reviewed-on: https://go-review.googlesource.com/35831 Run-TryBot: Matthew Dempsky TryBot-Result: Gobot Gobot Reviewed-by: Robert Griesemer --- src/cmd/compile/internal/gc/main.go | 6 ++++-- test/fixedbugs/issue18640.go | 26 ++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 test/fixedbugs/issue18640.go diff --git a/src/cmd/compile/internal/gc/main.go b/src/cmd/compile/internal/gc/main.go index a861a3556b..11f0547d5e 100644 --- a/src/cmd/compile/internal/gc/main.go +++ b/src/cmd/compile/internal/gc/main.go @@ -339,13 +339,15 @@ func Main() { // Phase 1: const, type, and names and types of funcs. // This will gather all the information about types // and methods but doesn't depend on any of it. + // We also defer type alias declarations until phase 2 + // to avoid cycles like #18640. defercheckwidth() // Don't use range--typecheck can add closures to xtop. timings.Start("fe", "typecheck", "top1") for i := 0; i < len(xtop); i++ { n := xtop[i] - if op := n.Op; op != ODCL && op != OAS && op != OAS2 { + if op := n.Op; op != ODCL && op != OAS && op != OAS2 && (op != ODCLTYPE || !n.Left.Name.Param.Alias) { xtop[i] = typecheck(n, Etop) } } @@ -357,7 +359,7 @@ func Main() { timings.Start("fe", "typecheck", "top2") for i := 0; i < len(xtop); i++ { n := xtop[i] - if op := n.Op; op == ODCL || op == OAS || op == OAS2 { + if op := n.Op; op == ODCL || op == OAS || op == OAS2 || op == ODCLTYPE && n.Left.Name.Param.Alias { xtop[i] = typecheck(n, Etop) } } diff --git a/test/fixedbugs/issue18640.go b/test/fixedbugs/issue18640.go new file mode 100644 index 0000000000..c4f948b706 --- /dev/null +++ b/test/fixedbugs/issue18640.go @@ -0,0 +1,26 @@ +// compile + +// Copyright 2017 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. + +package p + +type ( + a = b + b struct { + *a + } + + c struct { + *d + } + d = c + + e = f + f = g + g = []h + h i + i = j + j = e +) From f8b4123613a2cb0c453726033a03a1968205ccae Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Tue, 10 Jan 2017 16:19:14 -0800 Subject: [PATCH 26/26] [dev.typealias] spec: use term 'embedded field' rather than 'anonymous field' First steps towards defining type aliases in the spec. This is a nomenclature clarification, not a language change. The spec used all three terms 'embedded type', 'anonymous field', and 'embedded field'. Users where using the terms inconsistently. The notion of an 'anonymous' field was always misleading since they always had a de-facto name. With type aliases that name becomes even more important because we may have different names for the same type. Use the term 'embedded field' consistently and remove competing terminology. For #18130. Change-Id: I2083bbc85788cab0b2e2cb1ff58b2f979491f001 Reviewed-on: https://go-review.googlesource.com/35108 Reviewed-by: Alan Donovan Reviewed-by: Russ Cox Reviewed-by: Rob Pike --- doc/go_spec.html | 50 ++++++++++++++++++++++++------------------------ 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/doc/go_spec.html b/doc/go_spec.html index 5872eefb03..c71126d25d 100644 --- a/doc/go_spec.html +++ b/doc/go_spec.html @@ -1,6 +1,6 @@ @@ -738,7 +738,7 @@ The method set of any other type T consists of all The method set of the corresponding pointer type *T is the set of all methods declared with receiver *T or T (that is, it also contains the method set of T). -Further rules apply to structs containing anonymous fields, as described +Further rules apply to structs containing embedded fields, as described in the section on struct types. Any other type has an empty method set. In a method set, each method must have a @@ -947,16 +947,16 @@ Moreover, the inner slices must be initialized individually.

A struct is a sequence of named elements, called fields, each of which has a name and a type. Field names may be specified explicitly (IdentifierList) or -implicitly (AnonymousField). +implicitly (EmbeddedField). Within a struct, non-blank field names must be unique.

-StructType     = "struct" "{" { FieldDecl ";" } "}" .
-FieldDecl      = (IdentifierList Type | AnonymousField) [ Tag ] .
-AnonymousField = [ "*" ] TypeName .
-Tag            = string_lit .
+StructType    = "struct" "{" { FieldDecl ";" } "}" .
+FieldDecl     = (IdentifierList Type | EmbeddedField) [ Tag ] .
+EmbeddedField = [ "*" ] TypeName .
+Tag           = string_lit .
 
@@ -974,16 +974,15 @@ struct {
 

-A field declared with a type but no explicit field name is an anonymous field, -also called an embedded field or an embedding of the type in the struct. -An embedded type must be specified as +A field declared with a type but no explicit field name is called an embedded field. +An embedded field must be specified as a type name T or as a pointer to a non-interface type name *T, and T itself may not be a pointer type. The unqualified type name acts as the field name.

-// A struct with four anonymous fields of type T1, *T2, P.T3 and *P.T4
+// A struct with four embedded fields of types T1, *T2, P.T3 and *P.T4
 struct {
 	T1        // field name is T1
 	*T2       // field name is T2
@@ -1000,15 +999,15 @@ in a struct type:
 
 
 struct {
-	T     // conflicts with anonymous field *T and *P.T
-	*T    // conflicts with anonymous field T and *P.T
-	*P.T  // conflicts with anonymous field T and *T
+	T     // conflicts with embedded field *T and *P.T
+	*T    // conflicts with embedded field T and *P.T
+	*P.T  // conflicts with embedded field T and *T
 }
 

A field or method f of an -anonymous field in a struct x is called promoted if +embedded field in a struct x is called promoted if x.f is a legal selector that denotes that field or method f.

@@ -1025,7 +1024,7 @@ promoted methods are included in the method set of the struct as follows:

  • - If S contains an anonymous field T, + If S contains an embedded field T, the method sets of S and *S both include promoted methods with receiver T. The method set of *S also @@ -1033,7 +1032,7 @@ promoted methods are included in the method set of the struct as follows:
  • - If S contains an anonymous field *T, + If S contains an embedded field *T, the method sets of S and *S both include promoted methods with receiver T or *T. @@ -1434,8 +1433,8 @@ literal structure and corresponding components have identical types. In detail:
  • Two struct types are identical if they have the same sequence of fields, and if corresponding fields have the same names, and identical types, and identical tags. - Two anonymous fields are considered to have the same name. Lower-case field - names from different packages are always different.
  • + Non-exported field names from different + packages are always different.
  • Two pointer types are identical if they have identical base types.
  • @@ -1445,8 +1444,9 @@ literal structure and corresponding components have identical types. In detail: Parameter and result names are not required to match.
  • Two interface types are identical if they have the same set of methods - with the same names and identical function types. Lower-case method names from - different packages are always different. The order of the methods is irrelevant.
  • + with the same names and identical function types. + Non-exported method names from different + packages are always different. The order of the methods is irrelevant.
  • Two map types are identical if they have identical key and value types.
  • @@ -1891,7 +1891,7 @@ type NewMutex Mutex type PtrMutex *Mutex // The method set of *PrintableMutex contains the methods -// Lock and Unlock bound to its anonymous field Mutex. +// Lock and Unlock bound to its embedded field Mutex. type PrintableMutex struct { Mutex } @@ -2492,13 +2492,13 @@ If x is a package name, see the section on A selector f may denote a field or method f of a type T, or it may refer to a field or method f of a nested -anonymous field of T. -The number of anonymous fields traversed +embedded field of T. +The number of embedded fields traversed to reach f is called its depth in T. The depth of a field or method f declared in T is zero. The depth of a field or method f declared in -an anonymous field A in T is the +an embedded field A in T is the depth of f in A plus one.