]> Cypherpunks repositories - gostls13.git/commitdiff
go/types: local type-checking of alias declarations
authorRobert Griesemer <gri@golang.org>
Fri, 28 Oct 2016 00:40:33 +0000 (17:40 -0700)
committerRobert Griesemer <gri@golang.org>
Mon, 31 Oct 2016 16:39:55 +0000 (16:39 +0000)
Does not handle imports of packages with exported aliases yet.

For #17592.

Change-Id: Iee63fb9d521014995003a417271fbe0384ae04ef
Reviewed-on: https://go-review.googlesource.com/32108
Reviewed-by: Alan Donovan <adonovan@google.com>
src/go/types/call.go
src/go/types/decl.go
src/go/types/object.go
src/go/types/resolver.go
src/go/types/stdlib_test.go
src/go/types/testdata/aliasdecl.src
src/go/types/typexpr.go

index 45f3e9a6056ebdd5f894168e31c2960c5e40b168..37595985a5e872d555455211c176ac61e7d3736a 100644 (file)
@@ -275,21 +275,24 @@ 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 pkg, _ := obj.(*PkgName); pkg != nil {
-                       assert(pkg.pkg == check.pkg)
-                       check.recordUse(ident, pkg)
-                       pkg.used = true
-                       exp := pkg.imported.scope.Lookup(sel)
+               if pname, _ := obj.(*PkgName); pname != nil {
+                       assert(pname.pkg == check.pkg)
+                       check.recordUse(ident, pname)
+                       pname.used = true
+                       pkg := pname.imported
+                       exp := pkg.scope.Lookup(sel)
                        if exp == nil {
-                               if !pkg.imported.fake {
-                                       check.errorf(e.Pos(), "%s not declared by package %s", sel, ident)
+                               if !pkg.fake {
+                                       check.errorf(e.Pos(), "%s not declared by package %s", sel, pkg.name)
                                }
                                goto Error
                        }
                        if !exp.Exported() {
-                               check.errorf(e.Pos(), "%s not exported by package %s", sel, ident)
+                               check.errorf(e.Pos(), "%s not exported by package %s", sel, pkg.name)
                                // ok to continue
                        }
                        check.recordUse(e.Sel, exp)
index 1ecfb35f60c3ee2357ae928adff25cca1e346190..89c56534d296035a8b5a234fa8e772ba9bf716b7 100644 (file)
@@ -85,6 +85,9 @@ func (check *Checker) objDecl(obj Object, def *Named, path []*TypeName) {
        case *Func:
                // functions may be recursive - no need to track dependencies
                check.funcDecl(obj, d)
+       case *Alias:
+               // aliases cannot be recursive - no need to track dependencies
+               check.aliasDecl(obj, d)
        default:
                unreachable()
        }
@@ -329,6 +332,85 @@ func (check *Checker) funcDecl(obj *Func, decl *declInfo) {
        }
 }
 
+func (check *Checker) aliasDecl(obj *Alias, 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)
+
+       // 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
 
index b83be4336a7c1f6c00c5228d3d5f975369064e5a..42f030df046a87e55b3a78ab81082c7f7b09cda8 100644 (file)
@@ -152,8 +152,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
+func (*Const) isDependency()           {} // a constant may be a dependency of an initialization expression
 
 // A TypeName represents a declared type.
 type TypeName struct {
@@ -186,10 +185,8 @@ func NewField(pos token.Pos, pkg *Package, name string, typ Type, anonymous bool
 }
 
 func (obj *Var) Anonymous() bool { return obj.anonymous }
-
-func (obj *Var) IsField() bool { return obj.isField }
-
-func (*Var) isDependency() {} // a variable may be a dependency of an initialization expression
+func (obj *Var) IsField() bool   { return obj.isField }
+func (*Var) isDependency()       {} // a variable may be a dependency of an initialization expression
 
 // A Func represents a declared function, concrete method, or abstract
 // (interface) method. Its Type() is always a *Signature.
@@ -215,11 +212,22 @@ func (obj *Func) FullName() string {
        return buf.String()
 }
 
-func (obj *Func) Scope() *Scope {
-       return obj.typ.(*Signature).scope
+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 Alias struct {
+       object
+       kind token.Token // token.CONST, token.TYPE, token.VAR, or token.FUNC
+       orig Object      // aliased constant, type, variable, or function
+}
+
+func NewAlias(pos token.Pos, pkg *Package, name string, kind token.Token, orig Object) *Alias {
+       return &Alias{object{pos: pos, pkg: pkg, name: name}, kind, orig}
 }
 
-func (*Func) isDependency() {} // a function may be a dependency of an initialization expression
+func (obj *Alias) Kind() token.Token { return obj.kind }
+func (obj *Alias) Orig() Object      { return obj.orig }
 
 // A Label represents a declared label.
 type Label struct {
@@ -279,6 +287,9 @@ func writeObject(buf *bytes.Buffer, obj Object, qf Qualifier) {
                }
                return
 
+       case *Alias:
+               buf.WriteString("alias")
+
        case *Label:
                buf.WriteString("label")
                typ = nil
index 15722dec8d0bedbc368800c9e093215caf99e045..b6a85fc02a32bf7fd74b93f9f307101570e08d98 100644 (file)
@@ -14,12 +14,12 @@ import (
        "unicode"
 )
 
-// A declInfo describes a package-level const, type, var, or func declaration.
+// A declInfo describes a package-level const, type, var, func, or alias 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 expression, or nil
+       init  ast.Expr      // init/orig expression, or nil
        fdecl *ast.FuncDecl // func declaration, or nil
 
        // The deps field tracks initialization expression dependencies.
@@ -275,7 +275,8 @@ func (check *Checker) collectObjects() {
                                                }
 
                                        case *ast.AliasSpec:
-                                               check.errorf(s.Name.Pos(), "cannot handle alias declarations yet")
+                                               obj := NewAlias(s.Name.Pos(), pkg, s.Name.Name, d.Tok, nil)
+                                               check.declarePkgObj(s.Name, obj, &declInfo{file: fileScope, init: s.Orig})
 
                                        case *ast.ValueSpec:
                                                switch d.Tok {
index 4192a3608ebf126b668dd1a5540c7e63ab1a131c..1c6d7b5299309738d7a9e523ca4278a08151006e 100644 (file)
@@ -138,7 +138,6 @@ func TestStdTest(t *testing.T) {
        }
 
        testTestDir(t, filepath.Join(runtime.GOROOT(), "test"),
-               "alias2.go",      // excluded until we can handle alias declarations
                "cmplxdivide.go", // also needs file cmplxdivide1.go - ignore
                "sigchld.go",     // don't work on Windows; testTestDir should consult build tags
        )
index d1153516f2930d1a210d2a3c0f85b51d1846f490..074732df0cae28289c0faa0bff7eb523e3b284dc 100644 (file)
@@ -4,7 +4,108 @@
 
 package aliasdecl
 
-import "math"
+import (
+       "flag"
+       "fmt" // use at most once (to test "imported but not used" error)
+       "go/build"
+       . "go/build"
+       "io"
+       "math"
+       "unsafe"
+)
 
-const _ = math.Pi
-const c /* ERROR "cannot handle alias declarations yet" */ => math.Pi
+// helper
+var before struct {
+       f int
+}
+
+// aliases must refer to package-qualified identifiers
+type _ => _ /* ERROR "_ is not a package-qualified identifier" */
+type t1 => _ /* ERROR "_ is not a package-qualified identifier" */
+
+const _ => iota /* ERROR "iota is not a package-qualified identifier" */
+type _ => int   /* ERROR "int is not a package-qualified identifier" */
+
+const c => iota /* ERROR "iota is not a package-qualified identifier" */
+type t2 => int   /* ERROR "int is not a package-qualified identifier" */
+
+// dot-imported identifiers are not qualified identifiers
+// TODO(gri) fix error printing - should not print a qualified identifier...
+var _ => Default /* ERROR "Default is not a package-qualified identifier" */
+
+// qualified identifiers must start with a package
+var _ => before /* ERROR "before.f is not a package-qualified identifier" */ .f
+func _ => before /* ERROR "before.f is not a package-qualified identifier" */ .f
+var _ => after /* ERROR "after.m is not a package-qualified identifier" */ .m
+func _ => after /* ERROR "after.m is not a package-qualified identifier" */ .m
+
+var v1 => before /* ERROR "before.f is not a package-qualified identifier" */ .f
+func f1 => before /* ERROR "before.f is not a package-qualified identifier" */ .f
+var v2 => after /* ERROR "after.m is not a package-qualified identifier" */ .m
+func f2 => after /* ERROR "after.m is not a package-qualified identifier" */ .m
+
+// TODO(gri) fix error printing - should print correct qualified identifier...
+var _ => Default /* ERROR "Default.ARCH is not a package-qualified identifier" */ .ARCH
+var _ Context // use dot-imported package go/build
+
+// aliases may not refer to package unsafe
+type ptr => unsafe /* ERROR "refers to package unsafe" */ .Pointer
+func size => unsafe /* ERROR "refers to package unsafe" */ .Sizeof
+
+// aliases must refer to entities of the same kind
+const _ => math.Pi
+const pi => math.Pi
+const pi1 => math /* ERROR "math.Sin.* is not a constant" */ .Sin
+
+type _ => io.Writer
+type writer => io.Writer
+type writer1 => math /* ERROR "math.Sin.* is not a type" */ .Sin
+
+var _ => build.Default
+var def => build.Default
+var def1 => build /* ERROR "build.Import.* is not a variable" */ .Import
+
+func _ => math.Sin
+func sin => math.Sin
+func sin1 => math /* ERROR "math.Pi.* is not a function" */ .Pi
+
+// using an incorrectly declared alias should not lead to more errors
+const _ = pi1
+type _ writer1
+var _ def1 = 0
+var _ = sin1
+
+// aliases may not be called init
+func init /* ERROR "cannot declare init" */ => flag.Parse
+func _ => flag.Parse // use package flag
+
+// alias reference to a package marks package as used
+func _ => fmt.Println
+
+// re-exported aliases
+const Pi => math.Pi
+
+type Writer => io.Writer
+
+var Def => build.Default
+
+func Sin => math.Sin
+
+// const aliases may appear in "iota" context
+// (this verifies a type-checker internal assertion)
+const (
+       _ = iota
+       pi2 => math.Pi
+)
+
+// type aliases denote identical types
+type myPackage => build.Package
+
+var pkg myPackage
+var _ build.Package = pkg   // valid assignment
+var _ *build.Package = &pkg // valid assignment
+
+// helper
+type after struct{}
+
+func (after) m() {}
index 931b9247124f363498f48faf016a2c974a156b0e..012d3a7034ca52122ae705dba7e0d3f94d7f4c6f 100644 (file)
@@ -45,6 +45,19 @@ func (check *Checker) ident(x *operand, e *ast.Ident, def *Named, path []*TypeNa
                delete(check.unusedDotImports[scope], pkg)
        }
 
+       // An alias stands for the original object; use that one instead.
+       if alias, _ := obj.(*Alias); alias != nil {
+               if typ == Typ[Invalid] {
+                       return
+               }
+               obj = alias.orig
+               // Aliases always refer to non-alias originals.
+               if _, ok := obj.(*Alias); ok {
+                       panic("original is an alias")
+               }
+               assert(typ == obj.Type())
+       }
+
        switch obj := obj.(type) {
        case *PkgName:
                check.errorf(e.Pos(), "use of package %s not in selector", obj.name)