package types import ( "git.smesh.lol/moxie/pkg/syntax" "git.smesh.lol/moxie/pkg/token" ) // Importer resolves import paths to Packages. type Importer interface { Import(path string) (*TCPackage, error) } // Config controls type checker behavior. type Config struct { Importer Importer Error func(err error) // if nil, first error is returned and checking stops } // Checker is the Moxie type checker. // Create one with NewChecker, then call Files to type-check a package. type Checker struct { conf *Config pkg *TCPackage info *Info files []*syntax.File iota int64 // current iota value errors []error localScope *Scope // innermost scope during function body checking; nil at pkg level fileScope *Scope // scope of the file currently being checked; holds its imports fileScopes map[*syntax.File]*Scope } // NewChecker creates a checker for pkg. info receives the type information. func NewChecker(conf *Config, pkg *TCPackage, info *Info) (c *Checker) { return &Checker{conf: conf, pkg: pkg, info: info} } // Check type-checks the given files as package pkg. // It returns an error if type checking fails. func Check(path string, files []*syntax.File, importer Importer) (pkg *TCPackage, info *Info, err error) { pkg = NewTCPackage(path, packageName(files)) info = newInfo() conf := &Config{Importer: importer} c := NewChecker(conf, pkg, info) if err = c.Files(files); err != nil { return pkg, info, err } return pkg, info, nil } // packageName returns the package name declared in the first file, or "main". func packageName(files []*syntax.File) (s string) { for _, f := range files { if f.PkgName != nil { return f.PkgName.Value } } return "main" } // Files type-checks the given files as the checker's package. func (c *Checker) Files(files []*syntax.File) (err error) { c.files = files // Imports are file-scoped: each file gets its own scope (parent = // pkg.scope) holding the PkgNames it imports. Two files may bind the // same local name to different packages. c.fileScopes = map[*syntax.File]*Scope{} for _, f := range files { c.fileScopes[f] = NewScope(c.pkg.Scope) if c.info != nil && c.info.Scopes != nil { c.info.Scopes[f] = c.fileScopes[f] } } for _, f := range files { c.fileScope = c.fileScopes[f] c.collectDecls(f) } // Count methods per receiver type name, then pre-allocate // Methods slices with exact capacity. Two-pass: count before cut. c.preallocMethods(files) for _, f := range files { c.fileScope = c.fileScopes[f] for _, decl := range f.DeclList { if d, ok := decl.(*syntax.TypeDecl); ok { c.checkTypeDecl(d) } } } for _, f := range files { c.fileScope = c.fileScopes[f] for _, decl := range f.DeclList { switch decl.(type) { case *syntax.TypeDecl: default: c.checkDecl(decl) } } } for _, f := range files { c.fileScope = c.fileScopes[f] for _, decl := range f.DeclList { if fd, ok := decl.(*syntax.FuncDecl); ok { c.checkFuncBody(fd) } } } c.fileScope = nil if len(c.errors) > 0 { return c.errors[0] } return nil } // error records a type error at pos. func (c *Checker) errorf(pos token.Pos, msg string) { err := &TypeError{Pos: pos, Msg: msg} if c.conf.Error != nil { c.conf.Error(err) } push(c.errors, err) } // TypeError is a type-checking error with position info. type TypeError struct { Pos token.Pos Msg string } func (e *TypeError) Error() (s string) { return e.Pos.String() | ": " | e.Msg } func (e *TypeError) String() (s string) { return e.Error() } // recvTypeName extracts the receiver type name from a FuncDecl's // receiver field without resolving types. Returns "" if not extractable. func recvTypeName(d *syntax.FuncDecl) (name string) { if d.Recv == nil { return "" } t := d.Recv.Type // Unwrap pointer: *T -> T if op, ok := t.(*syntax.Operation); ok && op.Y == nil { t = op.X } // Unwrap index: T[X] -> T (generic receiver) if idx, ok := t.(*syntax.IndexExpr); ok { t = idx.X } if n, ok := t.(*syntax.Name); ok { return n.Value } return "" } // preallocMethods counts methods per receiver type name across all files, // then pre-allocates the Methods slice on each type with exact capacity. // Two-pass: count before cut. push() fills what was already measured. func (c *Checker) preallocMethods(files []*syntax.File) { // Pass 1: count methods per receiver type name. counts := map[string]int32{} for _, f := range files { for _, decl := range f.DeclList { if fd, ok := decl.(*syntax.FuncDecl); ok && fd.Recv != nil { name := recvTypeName(fd) if name != "" { counts[name] = counts[name] + 1 } } } } // Pass 2: pre-allocate Methods slices on declared types. for name, count := range counts { obj := c.pkg.Scope.Lookup(name) if obj == nil { continue } tn, ok := obj.(*TypeName) if !ok { continue } if tn.Typ == nil { continue } switch t := tn.Typ.(type) { case *Named: if t.Methods == nil { t.Methods = []*TCFunc{:0:count} } case *Basic: if t.Methods == nil { t.Methods = []*TCFunc{:0:count} } } } } // record stores type info for an expression. func (c *Checker) record(e syntax.Expr, tv TCTypeAndValue) { if c.info != nil { c.info.Types[e] = tv } var styp syntax.Type if tv.Type != nil { if st, ok := tv.Type.(syntax.Type); ok { styp = st } } stv := syntax.TypeAndValue{Type: styp} stv.Value = tv.Value e.SetTypeInfo(stv) } // openScope creates a child scope of s and records it for node n. func (c *Checker) openScope(n syntax.Node, parent *Scope) (s *Scope) { s = NewScope(parent) if c.info != nil { c.info.Scopes[n] = s } return s } // lookup finds a named object starting from s, then universe. func (c *Checker) lookup(name string, s *Scope) (sc *Scope, obj Object) { sc, obj = s.LookupParent(name) if obj != nil { return sc, obj } obj = Universe.Lookup(name) if obj != nil { return Universe, obj } return nil, nil } // lookupType finds a type name in localScope (if set), then the current // file scope (imports), then package scope, then universe. // Used by resolveTypeName to find locally-defined types (e.g. inside function bodies). func (c *Checker) lookupType(name string) (sc *Scope, obj Object) { if c.localScope != nil { sc, obj = c.localScope.LookupParent(name) if obj != nil { return sc, obj } } if c.fileScope != nil { sc, obj = c.fileScope.LookupParent(name) if obj != nil { return sc, obj } } return c.lookup(name, c.pkg.Scope) }