599 lines
12 KiB
Go
599 lines
12 KiB
Go
// Copyright 2022 The Gc 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 gc // modernc.org/gc/v3
|
|
|
|
import (
|
|
"fmt"
|
|
"go/constant"
|
|
"go/token"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
type ctx struct {
|
|
ast *AST
|
|
cfg *Config
|
|
errs errList
|
|
iota int64
|
|
pkg *Package
|
|
|
|
int32 Type // Set by newCtx
|
|
untypedFloat Type // Set by newCtx
|
|
untypedInt Type // Set by newCtx
|
|
untypedString Type // Set by newCtx
|
|
}
|
|
|
|
func newCtx(cfg *Config) (r *ctx) {
|
|
r = &ctx{
|
|
cfg: cfg,
|
|
iota: -1, // -> Invalid
|
|
}
|
|
r.int32 = r.newPredeclaredType(znode, Int32)
|
|
r.untypedFloat = r.newPredeclaredType(znode, UntypedFloat)
|
|
r.untypedInt = r.newPredeclaredType(znode, UntypedInt)
|
|
r.untypedString = r.newPredeclaredType(znode, UntypedString)
|
|
return r
|
|
}
|
|
|
|
func (c *ctx) err(n Node, msg string, args ...interface{}) {
|
|
var pos token.Position
|
|
if n != nil {
|
|
pos = n.Position()
|
|
}
|
|
s := fmt.Sprintf(msg, args...)
|
|
if trcTODOs && strings.HasPrefix(s, "TODO") {
|
|
fmt.Fprintf(os.Stderr, "%v: %s (%v)\n", pos, s, origin(2))
|
|
os.Stderr.Sync()
|
|
}
|
|
switch {
|
|
case extendedErrors:
|
|
c.errs.err(pos, "%s (%v: %v: %v)", s, origin(4), origin(3), origin(2))
|
|
default:
|
|
c.errs.err(pos, s)
|
|
}
|
|
}
|
|
|
|
func (c *ctx) isBuiltin() bool { return c.pkg.Scope.kind == UniverseScope }
|
|
func (c *ctx) isUnsafe() bool { return c.pkg.isUnsafe }
|
|
|
|
func (c *ctx) lookup(sc *Scope, id Token) (pkg *Package, in *Scope, r named) {
|
|
sc0 := sc
|
|
pkg = c.pkg
|
|
for {
|
|
switch in, nm := sc.lookup(id); x := nm.n.(type) {
|
|
case *TypeDefNode:
|
|
if sc.kind == UniverseScope {
|
|
if sc0.kind != UniverseScope && token.IsExported(id.Src()) {
|
|
// trc("%v: %q %v %v", id.Position(), id.Src(), sc0.kind, sc.kind)
|
|
return nil, nil, r
|
|
}
|
|
}
|
|
|
|
return x.pkg, in, nm
|
|
default:
|
|
panic(todo("%v: %q %T", id.Position(), id.Src(), x))
|
|
}
|
|
}
|
|
}
|
|
|
|
func (n *Package) check(c *ctx) (err error) {
|
|
if n == nil {
|
|
return nil
|
|
}
|
|
|
|
c.pkg = n
|
|
// trc("PKG %q", n.ImportPath)
|
|
// defer func() { trc("PKG %q -> err: %v", n.ImportPath, err) }()
|
|
for _, v := range n.GoFiles {
|
|
path := filepath.Join(n.FSPath, v.Name())
|
|
n.AST[path].check(c)
|
|
}
|
|
return c.errs.Err()
|
|
}
|
|
|
|
func (n *AST) check(c *ctx) {
|
|
if n == nil {
|
|
return
|
|
}
|
|
|
|
c.ast = n
|
|
n.SourceFile.check(c)
|
|
}
|
|
|
|
func (n *SourceFileNode) check(c *ctx) {
|
|
if n == nil {
|
|
return
|
|
}
|
|
|
|
n.PackageClause.check(c)
|
|
for l := n.ImportDeclList; l != nil; l = l.List {
|
|
l.ImportDecl.check(c)
|
|
}
|
|
for l := n.TopLevelDeclList; l != nil; l = l.List {
|
|
switch x := l.TopLevelDecl.(type) {
|
|
case *TypeDeclNode:
|
|
x.check(c)
|
|
case *ConstDeclNode:
|
|
x.check(c)
|
|
case *VarDeclNode:
|
|
x.check(c)
|
|
case *FunctionDeclNode:
|
|
x.check(c)
|
|
case *MethodDeclNode:
|
|
x.check(c)
|
|
default:
|
|
panic(todo("%v: %T %s", x.Position(), x, x.Source(false)))
|
|
}
|
|
}
|
|
}
|
|
|
|
func (n *MethodDeclNode) check(c *ctx) {
|
|
if n == nil {
|
|
return
|
|
}
|
|
|
|
n.Receiver.check(c)
|
|
n.Signature.check(c)
|
|
}
|
|
|
|
func (n *FunctionDeclNode) check(c *ctx) {
|
|
if n == nil {
|
|
return
|
|
}
|
|
|
|
if c.isBuiltin() {
|
|
switch nm := n.FunctionName.IDENT.Src(); nm {
|
|
case
|
|
"append",
|
|
"cap",
|
|
"close",
|
|
"complex",
|
|
"copy",
|
|
"delete",
|
|
"imag",
|
|
"len",
|
|
"make",
|
|
"new",
|
|
"panic",
|
|
"print",
|
|
"println",
|
|
"real",
|
|
"recover",
|
|
|
|
// Go 1.21
|
|
"max",
|
|
"min",
|
|
"clear":
|
|
|
|
n.Signature.t = c.newPredeclaredType(n, Function)
|
|
default:
|
|
panic(todo("%v: %q %s", n.Position(), nm, n.Source(false)))
|
|
}
|
|
return
|
|
}
|
|
|
|
n.Signature.check(c)
|
|
if n.TypeParameters != nil {
|
|
panic(todo("%v: %T %s", n.Position(), n, n.Source(false)))
|
|
}
|
|
}
|
|
|
|
func (n *SignatureNode) check(c *ctx) Type {
|
|
if n == nil {
|
|
return Invalid
|
|
}
|
|
|
|
if !n.enter(c, n) {
|
|
return n.Type()
|
|
}
|
|
|
|
in := n.Parameters.check(c)
|
|
out := n.Result.check(c)
|
|
return n.setType(newTupleType(n.Parameters, []Type{in, out}))
|
|
}
|
|
|
|
func (n *ResultNode) check(c *ctx) Type {
|
|
if n == nil {
|
|
return Invalid
|
|
}
|
|
|
|
switch {
|
|
case n.Parameters != nil:
|
|
return n.Parameters.check(c)
|
|
case n.TypeNode != nil:
|
|
return n.TypeNode.check(c)
|
|
default:
|
|
panic(todo("%v: %T %s", n.Position(), n, n.Source(false)))
|
|
}
|
|
}
|
|
|
|
func (n *ParametersNode) check(c *ctx) Type {
|
|
if n == nil {
|
|
return Invalid
|
|
}
|
|
|
|
r := newTupleType(n, nil)
|
|
for l := n.ParameterDeclList; l != nil; l = l.List {
|
|
r.Types = append(r.Types, l.ParameterDecl.check(c)...)
|
|
}
|
|
return r
|
|
}
|
|
|
|
func (n *ParameterDeclNode) check(c *ctx) (r []Type) {
|
|
if n == nil {
|
|
return nil
|
|
}
|
|
|
|
t := n.TypeNode.check(c)
|
|
for l := n.IdentifierList; l != nil; l = l.List {
|
|
r = append(r, t)
|
|
}
|
|
return r
|
|
}
|
|
|
|
func (n *VarDeclNode) check(c *ctx) {
|
|
if n == nil {
|
|
return
|
|
}
|
|
|
|
switch x := n.VarSpec.(type) {
|
|
case *VarSpecNode:
|
|
x.check(c)
|
|
default:
|
|
panic(todo("%v: %T %s", n.Position(), x, n.Source(false)))
|
|
}
|
|
}
|
|
|
|
func (n *VarSpecNode) check(c *ctx) {
|
|
if n == nil {
|
|
return
|
|
}
|
|
|
|
if c.isBuiltin() {
|
|
switch nm := n.IDENT.Src(); nm {
|
|
case "nil":
|
|
n.TypeNode = c.newPredeclaredType(n, UntypedNil)
|
|
default:
|
|
panic(todo("%v: %q", n.IDENT.Position(), nm))
|
|
}
|
|
return
|
|
}
|
|
|
|
if n.TypeNode != nil {
|
|
c.err(n, "TODO %v", n.TypeNode.Source(false))
|
|
}
|
|
var e []Expression
|
|
for l := n.ExpressionList; l != nil; l = l.List {
|
|
e = append(e, l.Expression.checkExpr(c))
|
|
}
|
|
switch len(e) {
|
|
default:
|
|
panic(todo("", len(e)))
|
|
c.err(n, "TODO %v", len(e))
|
|
}
|
|
}
|
|
|
|
func (n *ConstDeclNode) check(c *ctx) {
|
|
if n == nil {
|
|
return
|
|
}
|
|
|
|
switch x := n.ConstSpec.(type) {
|
|
case *ConstSpecListNode:
|
|
var prev Node
|
|
for l := x; l != nil; l = l.List {
|
|
switch y := l.ConstSpec.(type) {
|
|
case *ConstSpecNode:
|
|
y.check(c, prev)
|
|
if y.Expression != nil || y.TypeNode != nil {
|
|
prev = y
|
|
}
|
|
default:
|
|
panic(todo("%v: %T %s", n.Position(), y, n.Source(false)))
|
|
}
|
|
}
|
|
case *ConstSpecNode:
|
|
x.check(c, nil)
|
|
default:
|
|
panic(todo("%v: %T %s", n.Position(), x, n.Source(false)))
|
|
}
|
|
|
|
}
|
|
|
|
func (n *ConstSpecNode) check(c *ctx, prev Node) {
|
|
if n == nil {
|
|
return
|
|
}
|
|
|
|
if !n.enter(c, n) {
|
|
if n.guard == guardChecking {
|
|
panic(todo("")) // report recursive
|
|
}
|
|
return
|
|
}
|
|
|
|
defer func() { n.guard = guardChecked }()
|
|
|
|
if c.isBuiltin() {
|
|
switch n.IDENT.Src() {
|
|
case "true":
|
|
switch x := n.Expression.(type) {
|
|
case *BinaryExpressionNode:
|
|
x.setValue(trueVal)
|
|
x.setType(c.newPredeclaredType(x, UntypedBool))
|
|
default:
|
|
panic(todo("%v: %T %s", n.Position(), x, n.Source(false)))
|
|
}
|
|
case "false":
|
|
switch x := n.Expression.(type) {
|
|
case *BinaryExpressionNode:
|
|
x.setValue(falseVal)
|
|
x.setType(c.newPredeclaredType(x, UntypedBool))
|
|
default:
|
|
panic(todo("%v: %T %s", n.Position(), x, n.Source(false)))
|
|
}
|
|
case "iota":
|
|
switch x := n.Expression.(type) {
|
|
case *BasicLitNode:
|
|
// ok
|
|
default:
|
|
panic(todo("%v: %T %s", n.Position(), x, n.Source(false)))
|
|
}
|
|
default:
|
|
panic(todo("", n.Position(), n.Source(false)))
|
|
}
|
|
return
|
|
}
|
|
|
|
save := c.iota
|
|
c.iota = n.iota
|
|
|
|
defer func() { c.iota = save }()
|
|
|
|
switch {
|
|
case n.Expression != nil:
|
|
n.Expression = n.Expression.checkExpr(c)
|
|
if n.TypeNode == nil {
|
|
n.TypeNode = n.Expression.Type()
|
|
return
|
|
}
|
|
|
|
t := n.TypeNode.check(c)
|
|
trc("", t)
|
|
panic(todo("%v: %T %s", n.Position(), n, n.Source(false)))
|
|
default:
|
|
// var e Expression
|
|
// var pe *Expression
|
|
// switch {
|
|
// case n.Expression != nil:
|
|
// e = n.Expression
|
|
// pe = &n.Expression
|
|
// default:
|
|
// switch x := prev.(type) {
|
|
// case *ConstSpecNode:
|
|
// e = x.Expression.clone()
|
|
// pe = &e
|
|
// default:
|
|
// panic(todo("%v: %T %s", n.Position(), x, n.Source(false)))
|
|
// }
|
|
// }
|
|
// ev, et := e.checkExpr(c, pe)
|
|
// e = *pe
|
|
// if ev.Kind() == constant.Unknown {
|
|
// c.err(e, "%s is not a constant", e.Source(false))
|
|
// n.t = Invalid
|
|
// n.setValue(unknown)
|
|
// return Invalid
|
|
// }
|
|
// switch {
|
|
// case n.t == nil:
|
|
// n.t = et
|
|
// default:
|
|
|
|
// c.err(n.Expression, "cannot assign %v (type %v) to type %v", ev, et, n.Type())
|
|
// return Invalid
|
|
// } else {
|
|
// n.setValue(convertValue(c, e, ev, n.Type()))
|
|
// }
|
|
// }
|
|
// return n.Type()
|
|
panic(todo("%v: %T %s", n.Position(), n, n.Source(false)))
|
|
}
|
|
|
|
}
|
|
|
|
func (n *TypeDeclNode) check(c *ctx) {
|
|
if n == nil {
|
|
return
|
|
}
|
|
|
|
for l := n.TypeSpecList; l != nil; l = l.List {
|
|
switch x := l.TypeSpec.(type) {
|
|
case *TypeDefNode:
|
|
switch {
|
|
case c.isBuiltin():
|
|
x.pkg = c.pkg
|
|
switch nm := x.IDENT.Src(); nm {
|
|
case "bool":
|
|
x.TypeNode = c.newPredeclaredType(x, Bool)
|
|
case "int":
|
|
x.TypeNode = c.newPredeclaredType(x, Int)
|
|
c.cfg.int = x.TypeNode
|
|
case "int8":
|
|
x.TypeNode = c.newPredeclaredType(x, Int8)
|
|
case "int16":
|
|
x.TypeNode = c.newPredeclaredType(x, Int16)
|
|
case "int32":
|
|
x.TypeNode = c.newPredeclaredType(x, Int32)
|
|
case "int64":
|
|
x.TypeNode = c.newPredeclaredType(x, Int64)
|
|
case "uint":
|
|
x.TypeNode = c.newPredeclaredType(x, Uint)
|
|
c.cfg.uint = x.TypeNode
|
|
case "uint8":
|
|
x.TypeNode = c.newPredeclaredType(x, Uint8)
|
|
case "uint16":
|
|
x.TypeNode = c.newPredeclaredType(x, Uint16)
|
|
case "uint32":
|
|
x.TypeNode = c.newPredeclaredType(x, Uint32)
|
|
case "uint64":
|
|
x.TypeNode = c.newPredeclaredType(x, Uint64)
|
|
case "uintptr":
|
|
x.TypeNode = c.newPredeclaredType(x, Uintptr)
|
|
case "string":
|
|
x.TypeNode = c.newPredeclaredType(x, String)
|
|
case "float32":
|
|
x.TypeNode = c.newPredeclaredType(x, Float32)
|
|
case "float64":
|
|
x.TypeNode = c.newPredeclaredType(x, Float64)
|
|
case "complex64":
|
|
x.TypeNode = c.newPredeclaredType(x, Complex64)
|
|
case "complex128":
|
|
x.TypeNode = c.newPredeclaredType(x, Complex128)
|
|
case "comparable":
|
|
x.TypeNode = c.newPredeclaredType(x, Interface)
|
|
case "error":
|
|
x.check(c)
|
|
default:
|
|
if token.IsExported(nm) {
|
|
delete(c.pkg.Scope.nodes, nm)
|
|
return
|
|
}
|
|
|
|
panic(todo("%v: %T %s", x.Position(), x, x.Source(false)))
|
|
}
|
|
case c.isUnsafe():
|
|
switch nm := x.IDENT.Src(); nm {
|
|
case "ArbitraryType", "IntegerType", "Pointer":
|
|
x.TypeNode.check(c)
|
|
default:
|
|
panic(todo("%v: %T %s", x.Position(), x, x.Source(false)))
|
|
}
|
|
default:
|
|
switch {
|
|
case x.TypeParameters != nil:
|
|
panic(todo("%v: %T %s", x.Position(), x, x.Source(false)))
|
|
default:
|
|
x.check(c)
|
|
}
|
|
}
|
|
case *AliasDeclNode:
|
|
x.check(c)
|
|
default:
|
|
panic(todo("%v: %T %s", x.Position(), x, x.Source(false)))
|
|
}
|
|
}
|
|
}
|
|
|
|
func (n *AliasDeclNode) check(c *ctx) {
|
|
if n == nil {
|
|
return
|
|
}
|
|
|
|
n.TypeNode.check(c)
|
|
}
|
|
|
|
func (n *ImportDeclNode) check(c *ctx) {
|
|
if n == nil {
|
|
return
|
|
}
|
|
|
|
type result struct {
|
|
spec *ImportSpecNode
|
|
pkg *Package
|
|
err error
|
|
}
|
|
var a []*result
|
|
var wg sync.WaitGroup
|
|
for l := n.ImportSpecList; l != nil; l = l.List {
|
|
r := &result{}
|
|
a = append(a, r)
|
|
wg.Add(1)
|
|
go func(isln *ImportSpecListNode, r *result) {
|
|
|
|
defer wg.Done()
|
|
|
|
r.spec = isln.ImportSpec
|
|
r.pkg, r.err = r.spec.check(c)
|
|
r.spec.pkg = r.pkg
|
|
}(l, r)
|
|
}
|
|
wg.Wait()
|
|
fileScope := c.ast.FileScope
|
|
pkgScope := c.pkg.Scope
|
|
for _, v := range a {
|
|
switch x := v.err.(type) {
|
|
case nil:
|
|
// ok
|
|
default:
|
|
panic(todo("%v: %T: %s", v.spec.Position(), x, x))
|
|
}
|
|
if c.pkg.ImportPath == "builtin" && v.spec.ImportPath.Src() == `"cmp"` {
|
|
continue
|
|
}
|
|
|
|
switch ex := fileScope.declare(v.pkg.Name, v.spec, 0, nil, true); {
|
|
case ex.declTok.IsValid():
|
|
c.err(n, "%s redeclared, previous declaration at %v:", v.pkg.Name.Src(), ex.declTok.Position())
|
|
continue
|
|
}
|
|
|
|
switch ex := pkgScope.declare(v.pkg.Name, v.spec, 0, nil, true); {
|
|
case ex.declTok.IsValid():
|
|
c.err(n, "%s redeclared, previous declaration at %v:", v.pkg.Name.Src(), ex.declTok.Position())
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
func (n *ImportSpecNode) check(c *ctx) (*Package, error) {
|
|
if n == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
switch {
|
|
case n.PERIOD.IsValid():
|
|
panic(todo("", n.Position(), n.Source(false)))
|
|
case n.PackageName.IsValid():
|
|
//TODO version
|
|
check := c.pkg.typeCheck
|
|
switch check {
|
|
case TypeCheckAll:
|
|
// nop
|
|
default:
|
|
panic(todo("", check))
|
|
}
|
|
return c.cfg.newPackage(c.pkg.FSPath, constant.StringVal(n.ImportPath.Value()), "", nil, false, check, c.pkg.guard)
|
|
default:
|
|
//TODO version
|
|
check := c.pkg.typeCheck
|
|
switch check {
|
|
case TypeCheckAll:
|
|
// nop
|
|
default:
|
|
if c.pkg.ImportPath == "builtin" && n.ImportPath.Src() == `"cmp"` {
|
|
return nil, nil
|
|
}
|
|
}
|
|
return c.cfg.newPackage(c.pkg.FSPath, constant.StringVal(n.ImportPath.Value()), "", nil, false, check, c.pkg.guard)
|
|
}
|
|
}
|
|
|
|
func (n *PackageClauseNode) check(c *ctx) {
|
|
if n == nil {
|
|
return
|
|
}
|
|
|
|
nm := n.PackageName.Src()
|
|
if ex := c.pkg.Name; ex.IsValid() && ex.Src() != nm {
|
|
c.err(n.PackageName, "found different packages %q and %q", ex.Src(), nm)
|
|
return
|
|
}
|
|
|
|
c.pkg.Name = n.PackageName
|
|
}
|