mirror of
https://github.com/golang/go
synced 2024-11-17 23:34:52 -07:00
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/32108 Reviewed-by: Alan Donovan <adonovan@google.com>
This commit is contained in:
parent
f4c7a12c2c
commit
53c004f94e
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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 (*Const) isDependency() {} // a constant may be a dependency of an initialization expression
|
||||
|
||||
// A TypeName represents a declared type.
|
||||
@ -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) 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
|
||||
@ -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 (*Func) isDependency() {} // a function may be a dependency of an initialization expression
|
||||
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 (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
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
)
|
||||
|
107
src/go/types/testdata/aliasdecl.src
vendored
107
src/go/types/testdata/aliasdecl.src
vendored
@ -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() {}
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user