Skip to content

Commit

Permalink
go/gcimporter15: update import/export to handle type aliases
Browse files Browse the repository at this point in the history
- bimport.go is a 1:1 copy of the respective version in the std
  library: $GOROOT/src/go/internal/gcimporter/bimport.go .

- bexport.go is mimicking the respective code in the cmd/compile.

- isAlias18/19.go are needed because types.TypeName.IsAlias does
  not exist before Go 1.9.

Tested against Go 1.6, 1.7, 1.8, 1.9 (dev.typealias branch).

For golang/go#18130.

Change-Id: Ic46c5850923fab2a35d4dc33850f2b0667c30398
Reviewed-on: https://go-review.googlesource.com/35104
Run-TryBot: Robert Griesemer <gri@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Alan Donovan <adonovan@google.com>
  • Loading branch information
griesemer committed Jan 11, 2017
1 parent 5d76f8c commit de55728
Show file tree
Hide file tree
Showing 6 changed files with 209 additions and 156 deletions.
84 changes: 44 additions & 40 deletions go/gcimporter15/bexport.go
Expand Up @@ -39,11 +39,12 @@ const debugFormat = false // default: false
const trace = false // default: false

// Current export format version. Increase with each format change.
// 3: added aliasTag and export of aliases
// 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

// trackAllTypes enables cycle tracking for all types, not just named
// types. The existing compiler invariants assume that unnamed types
Expand All @@ -65,9 +66,6 @@ type exporter struct {
pkgIndex map[*types.Package]int
typIndex map[types.Type]int

// track objects that we've reexported already
reexported map[types.Object]bool

// position encoding
posInfoFormat bool
prevFile string
Expand All @@ -86,7 +84,6 @@ func BExportData(fset *token.FileSet, pkg *types.Package) []byte {
strIndex: map[string]int{"": 0}, // empty string is mapped to 0
pkgIndex: make(map[*types.Package]int),
typIndex: make(map[types.Type]int),
reexported: make(map[types.Object]bool),
posInfoFormat: true, // TODO(gri) might become a flag, eventually
}

Expand Down Expand Up @@ -188,7 +185,13 @@ func (p *exporter) obj(obj types.Object) {
p.value(obj.Val())

case *types.TypeName:
p.tag(typeTag)
if isAlias(obj) {
p.tag(aliasTag)
p.pos(obj)
p.qualifiedName(obj)
} else {
p.tag(typeTag)
}
p.typ(obj.Type())

case *types.Var:
Expand All @@ -205,21 +208,6 @@ func (p *exporter) obj(obj types.Object) {
p.paramList(sig.Params(), sig.Variadic())
p.paramList(sig.Results(), false)

// Alias-related code. Keep for now.
// case *types_Alias:
// // make sure the original is exported before the alias
// // (if the alias declaration was invalid, orig will be nil)
// orig := original(obj)
// if orig != nil && !p.reexported[orig] {
// p.obj(orig)
// p.reexported[orig] = true
// }

// p.tag(aliasTag)
// p.pos(obj)
// p.string(obj.Name())
// p.qualifiedName(orig)

default:
log.Fatalf("gcimporter: unexpected object %v (%T)", obj, obj)
}
Expand Down Expand Up @@ -279,10 +267,6 @@ func commonPrefixLen(a, b string) int {
}

func (p *exporter) qualifiedName(obj types.Object) {
if obj == nil {
p.string("")
return
}
p.string(obj.Name())
p.pkg(obj.Pkg(), false)
}
Expand Down Expand Up @@ -482,28 +466,45 @@ func (p *exporter) method(m *types.Func) {
p.paramList(sig.Results(), false)
}

// fieldName is like qualifiedName but it doesn't record the package for exported names.
func (p *exporter) fieldName(f *types.Var) {
name := f.Name()

// anonymous field with unexported base type name: use "?" as field name
// (bname != "" per spec, but we are conservative in case of errors)
if f.Anonymous() {
base := f.Type()
if ptr, ok := base.(*types.Pointer); ok {
base = ptr.Elem()
}
if named, ok := base.(*types.Named); ok && !named.Obj().Exported() {
// 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 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(f.Type())
if name == bname {
if ast.IsExported(name) {
name = "" // 1) we don't need to know the field name or package
} 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)
if !f.Exported() {
if name != "" && !ast.IsExported(name) {
p.pkg(f.Pkg(), false)
}
}

func basetypeName(typ types.Type) string {
switch typ := deref(typ).(type) {
case *types.Basic:
return typ.Name()
case *types.Named:
return typ.Obj().Name()
default:
return "" // unnamed type
}
}

func (p *exporter) paramList(params *types.Tuple, variadic bool) {
// use negative length to indicate unnamed parameters
// (look at the first parameter only since either all
Expand Down Expand Up @@ -797,10 +798,10 @@ func (p *exporter) tracef(format string, args ...interface{}) {
// Debugging support.
// (tagString is only used when tracing is enabled)
var tagString = [...]string{
// Packages:
// Packages
-packageTag: "package",

// Types:
// Types
-namedTag: "named type",
-arrayTag: "array",
-sliceTag: "slice",
Expand All @@ -812,7 +813,7 @@ var tagString = [...]string{
-mapTag: "map",
-chanTag: "chan",

// Values:
// Values
-falseTag: "false",
-trueTag: "true",
-int64Tag: "int64",
Expand All @@ -821,4 +822,7 @@ var tagString = [...]string{
-complexTag: "complex",
-stringTag: "string",
-unknownTag: "unknown",

// Type aliases
-aliasTag: "alias",
}
60 changes: 0 additions & 60 deletions go/gcimporter15/bexport18_test.go

This file was deleted.

93 changes: 93 additions & 0 deletions go/gcimporter15/bexport19_test.go
@@ -0,0 +1,93 @@
// 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.

// +build go1.9

package gcimporter_test

import (
"go/ast"
"go/parser"
"go/token"
"go/types"
"testing"

gcimporter "golang.org/x/tools/go/gcimporter15"
)

const src = `
package p
type (
T0 = int32
T1 = struct{}
T2 = struct{ T1 }
Invalid = foo // foo is undeclared
)
`

func checkPkg(t *testing.T, pkg *types.Package, label string) {
T1 := types.NewStruct(nil, nil)
T2 := types.NewStruct([]*types.Var{types.NewField(0, pkg, "T1", T1, true)}, nil)

for _, test := range []struct {
name string
typ types.Type
}{
{"T0", types.Typ[types.Int32]},
{"T1", T1},
{"T2", T2},
{"Invalid", types.Typ[types.Invalid]},
} {
obj := pkg.Scope().Lookup(test.name)
if obj == nil {
t.Errorf("%s: %s not found", label, test.name)
continue
}
tname, _ := obj.(*types.TypeName)
if tname == nil {
t.Errorf("%s: %v not a type name", label, obj)
continue
}
if !tname.IsAlias() {
t.Errorf("%s: %v: not marked as alias", label, tname)
continue
}
if got := tname.Type(); !types.Identical(got, test.typ) {
t.Errorf("%s: %v: got %v; want %v", label, tname, got, test.typ)
}
}
}

func TestTypeAliases(t *testing.T) {
// parse and typecheck
fset1 := token.NewFileSet()
f, err := parser.ParseFile(fset1, "p.go", src, 0)
if err != nil {
t.Fatal(err)
}
var conf types.Config
pkg1, err := conf.Check("p", fset1, []*ast.File{f}, nil)
if err == nil {
// foo in undeclared in src; we should see an error
t.Fatal("invalid source type-checked without error")
}
if pkg1 == nil {
// despite incorrect src we should see a (partially) type-checked package
t.Fatal("nil package returned")
}
checkPkg(t, pkg1, "export")

// export
exportdata := gcimporter.BExportData(fset1, pkg1)

// import
imports := make(map[string]*types.Package)
fset2 := token.NewFileSet()
_, pkg2, err := gcimporter.BImportData(fset2, imports, exportdata, pkg1.Path())
if err != nil {
t.Fatalf("BImportData(%s): %v", pkg1.Path(), err)
}
checkPkg(t, pkg2, "import")
}

0 comments on commit de55728

Please sign in to comment.