package types import ( "git.smesh.lol/moxie/pkg/syntax" "git.smesh.lol/moxie/pkg/token" ) // collectDecls walks a file's top-level declarations and inserts all // package-level names into pkg.scope. No type resolution yet - just // name registration so forward references work during pass 2. // Const groups are tracked so iota can be assigned correctly in pass 2. type constGroupEntry struct { group *syntax.Group decls []*syntax.ConstDecl } func (c *Checker) collectDecls(f *syntax.File) { var constEntries []constGroupEntry for _, d := range f.DeclList { switch dd := d.(type) { case *syntax.ImportDecl: c.collectImport(dd) case *syntax.ConstDecl: for _, name := range dd.NameList { obj := NewTCConst(c.pkg, name.Value, nil, nil) c.declare(c.pkg.Scope, name, obj) } g := dd.Group if g == nil { g = &syntax.Group{} } found := false for i := 0; i < len(constEntries); i++ { if constEntries[i].group == g { push(constEntries[i].decls, dd) found = true break } } if !found { push(constEntries, constGroupEntry{group: g, decls: []*syntax.ConstDecl{dd}}) } case *syntax.TypeDecl: obj := NewTypeName(c.pkg, dd.Name.Value, nil) c.declare(c.pkg.Scope, dd.Name, obj) case *syntax.VarDecl: for _, name := range dd.NameList { obj := NewTCVar(c.pkg, name.Value, nil) c.declare(c.pkg.Scope, name, obj) } case *syntax.FuncDecl: if dd.Recv == nil { if dd.Name.Value == "init" { continue } obj := NewTCFunc(c.pkg, dd.Name.Value, nil) c.declare(c.pkg.Scope, dd.Name, obj) } } } for _, e := range constEntries { c.checkConstGroup(e.decls) } } // collectImport resolves an import declaration and adds the package name // to the current file's scope. A best-effort copy also lands in pkg.scope // (first-wins) for legacy lookup paths without file context. func (c *Checker) collectImport(d *syntax.ImportDecl) { if d.Path == nil { return } path := d.Path.Value if len(path) >= 2 { path = path[1 : len(path)-1] // strip quotes } if c.conf.Importer == nil { return } imported, err := c.conf.Importer.Import(path) if err != nil { c.errorf(d.Pos(), "cannot import \"" | path | "\": " | err.Error()) return } // Track all direct imports regardless of local-name collisions. found := false for _, ip := range c.pkg.Imports { if ip == imported { found = true break } } if !found { push(c.pkg.Imports, imported) } localName := imported.Name if d.LocalPkgName != nil { localName = d.LocalPkgName.Value } if localName == "_" { return } if localName == "." { if imported.Scope != nil { for _, n := range imported.Scope.Names() { obj := imported.Scope.Lookup(n) if obj != nil && ObjectExported(obj) { if c.fileScope != nil { c.fileScope.Insert(obj) } c.pkg.Scope.Insert(obj) } } } return } pkgObj := NewPkgName(c.pkg, localName, imported) if c.fileScope != nil { if existing := c.fileScope.Lookup(localName); existing != nil { if ep, ok := existing.(*PkgName); ok && ep.Imported == imported { return // same import repeated in this file } c.errorf(d.Pos(), localName | " redeclared in this file") return } c.fileScope.Insert(pkgObj) } // Legacy fallback: first-wins, silent on collision (the file scope // disambiguates; paths without file context keep working for the // common no-collision case). c.pkg.Scope.Insert(pkgObj) } // checkDecl resolves the type of a top-level declaration. // Const decls are handled in collectDecls via checkConstGroup. func (c *Checker) checkDecl(d syntax.Decl) { switch dd := d.(type) { case *syntax.TypeDecl: c.checkTypeDecl(dd) case *syntax.VarDecl: c.checkVarDecl(dd) case *syntax.FuncDecl: c.checkFuncDecl(dd) } } func (c *Checker) checkTypeDecl(d *syntax.TypeDecl) { obj, ok := c.pkg.Scope.Lookup(d.Name.Value).(*TypeName) if !ok { return } // Generic type declaration: register type params in a temporary scope so // the underlying type can reference them (e.g. type Seq[V any] func(...V...)). if len(d.TParamList) > 0 { tpParent := c.pkg.Scope if c.fileScope != nil { tpParent = c.fileScope } tmpScope := NewScope(tpParent) for _, tp := range d.TParamList { if tp.Name != nil && tp.Name.Value != "" { tpObj := NewTypeName(c.pkg, tp.Name.Value, NewTypeParam(NewTypeName(c.pkg, tp.Name.Value, nil), nil)) tmpScope.Insert(tpObj) } } saved := c.localScope c.localScope = tmpScope defer func() { c.localScope = saved }() } named := NewNamed(obj, nil) // Mark generic types: their methods are never emitted (erasure + // monomorphization), so dispatch and mxh generation must skip them. for _, tp := range d.TParamList { tpName := "" if tp.Name != nil { tpName = tp.Name.Value } push(named.TParams, NewTypeParam(NewTypeName(c.pkg, tpName, nil), nil)) } underlying := c.resolveTypeExpr(d.Type) named.Under = underlying } func (c *Checker) checkVarDecl(d *syntax.VarDecl) { var typ Type if d.Type != nil { typ = c.resolveTypeExpr(d.Type) } for _, name := range d.NameList { if obj, ok := c.pkg.Scope.Lookup(name.Value).(*TCVar); ok { obj.Typ = typ } } } func (c *Checker) checkFuncDecl(d *syntax.FuncDecl) { // Build a temporary scope containing all type parameters visible in this // function declaration: (1) explicit type params on the function itself, // (2) type args from a generic receiver (func (h Handle[T]) ...). typeParams := collectFuncTypeParams(d) if len(typeParams) > 0 { tpParent := c.pkg.Scope if c.fileScope != nil { tpParent = c.fileScope } tmpScope := NewScope(tpParent) for _, name := range typeParams { obj := NewTypeName(c.pkg, name, NewTypeParam(NewTypeName(c.pkg, name, nil), nil)) tmpScope.Insert(obj) } saved := c.localScope c.localScope = tmpScope defer func() { c.localScope = saved }() } sig := c.resolveFuncType(d.Type, d.Recv) fn := NewTCFunc(c.pkg, d.Name.Value, sig) if d.Recv == nil { // plain function - update the already-registered object if obj, ok := c.pkg.Scope.Lookup(d.Name.Value).(*TCFunc); ok { obj.Typ = sig } return } // method - find the named receiver type and attach the method recvTyp := c.resolveTypeExpr(d.Recv.Type) if recvTyp == nil { return } // unwrap pointer receiver: func (p *T) F() -> receiver type is T if pt, ok := recvTyp.(*Pointer); ok { fn.PtrRecv = true recvTyp = pt.Base } switch rt := recvTyp.(type) { case *Named: rt.AddMethod(fn) case *Basic: rt.AddMethod(fn) } if c.info != nil && d.Name != nil { c.info.Defs[d.Name] = fn } } // checkFuncBody type-checks the body of a function declaration. func (c *Checker) checkFuncBody(fd *syntax.FuncDecl) { if fd.Body == nil { return } bodyParent := c.pkg.Scope if c.fileScope != nil { bodyParent = c.fileScope } scope := c.openScope(fd.Body, bodyParent) // Set localScope so resolveTypeExpr can find locally-defined types. saved := c.localScope c.localScope = scope defer func() { c.localScope = saved }() // Register all type parameters (function-level + receiver type args) in // the body scope so the signature and body can reference them. for _, name := range collectFuncTypeParams(fd) { obj := NewTypeName(c.pkg, name, NewTypeParam(NewTypeName(c.pkg, name, nil), nil)) scope.Insert(obj) } sig := c.resolveFuncType(fd.Type, fd.Recv) if sig != nil { // Receiver variable (for methods). if sig.Recv != nil && sig.Recv.Name != "" { c.insertWithShadowCheck(scope, sig.Recv, fd.Recv.Name) } // Parameters. if sig.Params != nil { for i := 0; i < sig.Params.Len(); i++ { p := sig.Params.At(i) if p.Name != "" && p.Name != "_" { var nameNode *syntax.Name if i < len(fd.Type.ParamList) && fd.Type.ParamList[i].Name != nil { nameNode = fd.Type.ParamList[i].Name } c.insertWithShadowCheck(scope, p, nameNode) } } } // Named results. if sig.Results != nil { for i := 0; i < sig.Results.Len(); i++ { r := sig.Results.At(i) if r.Name != "" && r.Name != "_" { var nameNode *syntax.Name if i < len(fd.Type.ResultList) && fd.Type.ResultList[i].Name != nil { nameNode = fd.Type.ResultList[i].Name } c.insertWithShadowCheck(scope, r, nameNode) } } } } c.checkBlock(fd.Body, scope) } // insertWithShadowCheck inserts obj into scope and checks for shadowing of // outer scope names, imported package names, and predeclared identifiers. // Used for function parameters and named results which bypass declare(). func (c *Checker) insertWithShadowCheck(s *Scope, obj Object, nameNode *syntax.Name) { name := ObjectName(obj) if alt := s.Insert(obj); alt != nil { if nameNode != nil { c.errorf(nameNode.Pos(), name | " redeclared in this block") } return } var pos token.Pos if nameNode != nil { pos = nameNode.Pos() } if s.parent != nil { if _, outer := s.parent.LookupParent(name); outer != nil { c.errorf(pos, name | " shadows declaration in outer scope") } } if Universe.Lookup(name) != nil { c.errorf(pos, name | " shadows predeclared identifier") } } // collectFuncTypeParams returns all type parameter names visible in a function // declaration: explicit type params on the function (TParamList) plus type // arguments on a generic receiver (func (h Handle[T]) F() needs T in scope). func collectFuncTypeParams(d *syntax.FuncDecl) (ss []string) { seen := map[string]bool{} var names []string add := func(name string) { if name != "" && !seen[name] { seen[name] = true push(names, name) } } for _, tp := range d.TParamList { if tp.Name != nil { add(tp.Name.Value) } } // Extract type args from a generic receiver: func (h Foo[T, U]) ... if d.Recv != nil { extractTypeArgs(d.Recv.Type, add) } return names } // extractTypeArgs recursively extracts bare Name nodes from a generic receiver // type expression like Handle[T] or Ptr[K, V]. func extractTypeArgs(e syntax.Expr, add func(string)) { if e == nil { return } switch ev := e.(type) { case *syntax.IndexExpr: // Handle[T] or Handle[T, U] (ListExpr inside) extractTypeArgs(ev.Index, add) case *syntax.Name: add(ev.Value) case *syntax.ListExpr: for _, el := range ev.ElemList { extractTypeArgs(el, add) } case *syntax.Operation: if ev.Y == nil { extractTypeArgs(ev.X, add) } } } // declare inserts obj into s, recording the definition in info. // Moxie prohibits all variable shadowing: a name declared in an inner scope // must not collide with any name in an outer scope, the file scope (imported // package names), or the universe scope (predeclared identifiers). func (c *Checker) declare(s *Scope, id *syntax.Name, obj Object) { if id != nil && id.Value != "_" { if alt := s.Insert(obj); alt != nil { c.errorf(id.Pos(), id.Value | " redeclared in this block") return } // Reject shadowing of any name from an outer scope (includes // file-scope imports, package-level names, and universe builtins). if s.parent != nil { if _, outer := s.parent.LookupParent(id.Value); outer != nil { c.errorf(id.Pos(), id.Value | " shadows declaration in outer scope") } } // Explicit universe check for package-level declarations whose // parent IS universe (LookupParent above wouldn't reach it from // package scope since universe is package scope's parent only // if file scopes are interposed). if Universe.Lookup(id.Value) != nil { c.errorf(id.Pos(), id.Value | " shadows predeclared identifier") } } if c.info != nil && id != nil { c.info.Defs[id] = obj } }