Commit 53c004f9 authored by Robert Griesemer's avatar Robert Griesemer

go/types: local type-checking of alias declarations

Does not handle imports of packages with exported aliases yet.

For #17592.

Change-Id: Iee63fb9d521014995003a417271fbe0384ae04ef
Reviewed-on: https://go-review.googlesource.com/32108Reviewed-by: default avatarAlan Donovan <adonovan@google.com>
parent f4c7a12c
...@@ -275,21 +275,24 @@ func (check *Checker) selector(x *operand, e *ast.SelectorExpr) { ...@@ -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 // so we don't need a "package" mode for operands: package names
// can only appear in qualified identifiers which are mapped to // can only appear in qualified identifiers which are mapped to
// selector expressions. // 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 { if ident, ok := e.X.(*ast.Ident); ok {
_, obj := check.scope.LookupParent(ident.Name, check.pos) _, obj := check.scope.LookupParent(ident.Name, check.pos)
if pkg, _ := obj.(*PkgName); pkg != nil { if pname, _ := obj.(*PkgName); pname != nil {
assert(pkg.pkg == check.pkg) assert(pname.pkg == check.pkg)
check.recordUse(ident, pkg) check.recordUse(ident, pname)
pkg.used = true pname.used = true
exp := pkg.imported.scope.Lookup(sel) pkg := pname.imported
exp := pkg.scope.Lookup(sel)
if exp == nil { if exp == nil {
if !pkg.imported.fake { if !pkg.fake {
check.errorf(e.Pos(), "%s not declared by package %s", sel, ident) check.errorf(e.Pos(), "%s not declared by package %s", sel, pkg.name)
} }
goto Error goto Error
} }
if !exp.Exported() { 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 // ok to continue
} }
check.recordUse(e.Sel, exp) check.recordUse(e.Sel, exp)
......
...@@ -85,6 +85,9 @@ func (check *Checker) objDecl(obj Object, def *Named, path []*TypeName) { ...@@ -85,6 +85,9 @@ func (check *Checker) objDecl(obj Object, def *Named, path []*TypeName) {
case *Func: case *Func:
// functions may be recursive - no need to track dependencies // functions may be recursive - no need to track dependencies
check.funcDecl(obj, d) check.funcDecl(obj, d)
case *Alias:
// aliases cannot be recursive - no need to track dependencies
check.aliasDecl(obj, d)
default: default:
unreachable() unreachable()
} }
...@@ -329,6 +332,85 @@ func (check *Checker) funcDecl(obj *Func, decl *declInfo) { ...@@ -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) { func (check *Checker) declStmt(decl ast.Decl) {
pkg := check.pkg pkg := check.pkg
......
...@@ -152,7 +152,6 @@ func NewConst(pos token.Pos, pkg *Package, name string, typ Type, val constant.V ...@@ -152,7 +152,6 @@ func NewConst(pos token.Pos, pkg *Package, name string, typ Type, val constant.V
} }
func (obj *Const) Val() constant.Value { return obj.val } 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. // A TypeName represents a declared type.
...@@ -186,9 +185,7 @@ func NewField(pos token.Pos, pkg *Package, name string, typ Type, anonymous bool ...@@ -186,9 +185,7 @@ func NewField(pos token.Pos, pkg *Package, name string, typ Type, anonymous bool
} }
func (obj *Var) Anonymous() bool { return obj.anonymous } func (obj *Var) Anonymous() bool { return obj.anonymous }
func (obj *Var) IsField() bool { return obj.isField } func (obj *Var) IsField() bool { return obj.isField }
func (*Var) isDependency() {} // a variable may be a dependency of an initialization expression func (*Var) isDependency() {} // a variable may be a dependency of an initialization expression
// A Func represents a declared function, concrete method, or abstract // A Func represents a declared function, concrete method, or abstract
...@@ -215,11 +212,22 @@ func (obj *Func) FullName() string { ...@@ -215,11 +212,22 @@ func (obj *Func) FullName() string {
return buf.String() return buf.String()
} }
func (obj *Func) Scope() *Scope { func (obj *Func) Scope() *Scope { return obj.typ.(*Signature).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. // A Label represents a declared label.
type Label struct { type Label struct {
...@@ -279,6 +287,9 @@ func writeObject(buf *bytes.Buffer, obj Object, qf Qualifier) { ...@@ -279,6 +287,9 @@ func writeObject(buf *bytes.Buffer, obj Object, qf Qualifier) {
} }
return return
case *Alias:
buf.WriteString("alias")
case *Label: case *Label:
buf.WriteString("label") buf.WriteString("label")
typ = nil typ = nil
......
...@@ -14,12 +14,12 @@ import ( ...@@ -14,12 +14,12 @@ import (
"unicode" "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 { type declInfo struct {
file *Scope // scope of file containing this declaration file *Scope // scope of file containing this declaration
lhs []*Var // lhs of n:1 variable declarations, or nil lhs []*Var // lhs of n:1 variable declarations, or nil
typ ast.Expr // type, 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 fdecl *ast.FuncDecl // func declaration, or nil
// The deps field tracks initialization expression dependencies. // The deps field tracks initialization expression dependencies.
...@@ -275,7 +275,8 @@ func (check *Checker) collectObjects() { ...@@ -275,7 +275,8 @@ func (check *Checker) collectObjects() {
} }
case *ast.AliasSpec: 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: case *ast.ValueSpec:
switch d.Tok { switch d.Tok {
......
...@@ -138,7 +138,6 @@ func TestStdTest(t *testing.T) { ...@@ -138,7 +138,6 @@ func TestStdTest(t *testing.T) {
} }
testTestDir(t, filepath.Join(runtime.GOROOT(), "test"), testTestDir(t, filepath.Join(runtime.GOROOT(), "test"),
"alias2.go", // excluded until we can handle alias declarations
"cmplxdivide.go", // also needs file cmplxdivide1.go - ignore "cmplxdivide.go", // also needs file cmplxdivide1.go - ignore
"sigchld.go", // don't work on Windows; testTestDir should consult build tags "sigchld.go", // don't work on Windows; testTestDir should consult build tags
) )
......
...@@ -4,7 +4,108 @@ ...@@ -4,7 +4,108 @@
package aliasdecl 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 // helper
const c /* ERROR "cannot handle alias declarations yet" */ => math.Pi 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() {}
...@@ -45,6 +45,19 @@ func (check *Checker) ident(x *operand, e *ast.Ident, def *Named, path []*TypeNa ...@@ -45,6 +45,19 @@ func (check *Checker) ident(x *operand, e *ast.Ident, def *Named, path []*TypeNa
delete(check.unusedDotImports[scope], pkg) 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) { switch obj := obj.(type) {
case *PkgName: case *PkgName:
check.errorf(e.Pos(), "use of package %s not in selector", obj.name) check.errorf(e.Pos(), "use of package %s not in selector", obj.name)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment