decl.go raw

   1  package typecheck
   2  
   3  import "moxie/syntax"
   4  
   5  // collectDecls walks a file's top-level declarations and inserts all
   6  // package-level names into pkg.scope. No type resolution yet — just
   7  // name registration so forward references work during pass 2.
   8  // Const groups are tracked so iota can be assigned correctly in pass 2.
   9  func (c *Checker) collectDecls(f *syntax.File) {
  10  	// Track const groups: group pointer → ordered list of ConstDecls.
  11  	constGroups := map[*syntax.Group][]*syntax.ConstDecl{}
  12  	var constGroupOrder []*syntax.Group // preserve declaration order
  13  
  14  	for _, d := range f.DeclList {
  15  		switch d := d.(type) {
  16  		case *syntax.ImportDecl:
  17  			c.collectImport(d)
  18  		case *syntax.ConstDecl:
  19  			for _, name := range d.NameList {
  20  				obj := NewConst(c.pkg, name.Value, nil, nil)
  21  				c.declare(c.pkg.scope, name, obj)
  22  			}
  23  			// Group nil means a standalone const (iota=0).
  24  			g := d.Group
  25  			if g == nil {
  26  				// Use a unique sentinel group for standalone consts.
  27  				g = &syntax.Group{}
  28  			}
  29  			if _, seen := constGroups[g]; !seen {
  30  				constGroupOrder = append(constGroupOrder, g)
  31  			}
  32  			constGroups[g] = append(constGroups[g], d)
  33  		case *syntax.TypeDecl:
  34  			obj := NewTypeName(c.pkg, d.Name.Value, nil)
  35  			c.declare(c.pkg.scope, d.Name, obj)
  36  		case *syntax.VarDecl:
  37  			for _, name := range d.NameList {
  38  				obj := NewVar(c.pkg, name.Value, nil)
  39  				c.declare(c.pkg.scope, name, obj)
  40  			}
  41  		case *syntax.FuncDecl:
  42  			if d.Recv == nil {
  43  				// init functions: multiple allowed per package, never called by name.
  44  				if d.Name.Value == "init" {
  45  					continue
  46  				}
  47  				obj := NewFunc(c.pkg, d.Name.Value, nil)
  48  				c.declare(c.pkg.scope, d.Name, obj)
  49  			}
  50  		}
  51  	}
  52  
  53  	// Resolve const groups now that all names are registered.
  54  	for _, g := range constGroupOrder {
  55  		c.checkConstGroup(constGroups[g])
  56  	}
  57  }
  58  
  59  // collectImport resolves an import declaration and adds the package name
  60  // to the file scope. (File scope is per-file; we approximate it with
  61  // pkg.scope for now — proper file scopes come in the full implementation.)
  62  func (c *Checker) collectImport(d *syntax.ImportDecl) {
  63  	if d.Path == nil {
  64  		return
  65  	}
  66  	path := d.Path.Value
  67  	if len(path) >= 2 {
  68  		path = path[1 : len(path)-1] // strip quotes
  69  	}
  70  	if c.conf.Importer == nil {
  71  		return
  72  	}
  73  	imported, err := c.conf.Importer.Import(path)
  74  	if err != nil {
  75  		c.errorf(d.Pos(), "cannot import %q: %v", path, err)
  76  		return
  77  	}
  78  	localName := imported.name
  79  	if d.LocalPkgName != nil {
  80  		localName = d.LocalPkgName.Value
  81  	}
  82  	if localName == "_" {
  83  		return
  84  	}
  85  	pkgObj := NewPkgName(c.pkg, localName, imported)
  86  	// File-level imports go into pkg.scope for now (proper file scopes in B3).
  87  	// Silently skip if the same name is already declared — multi-file packages
  88  	// often repeat the same import alias in each file (e.g. errorspkg "errors").
  89  	if existing := c.pkg.scope.Lookup(localName); existing != nil {
  90  		if ep, ok := existing.(*PkgName); ok && ep.imported == imported {
  91  			return // same import, already registered
  92  		}
  93  	}
  94  	if d.LocalPkgName != nil {
  95  		c.declare(c.pkg.scope, d.LocalPkgName, pkgObj)
  96  	} else {
  97  		c.pkg.scope.Insert(pkgObj)
  98  	}
  99  }
 100  
 101  // checkDecl resolves the type of a top-level declaration.
 102  // Const decls are handled in collectDecls via checkConstGroup.
 103  func (c *Checker) checkDecl(d syntax.Decl) {
 104  	switch d := d.(type) {
 105  	case *syntax.TypeDecl:
 106  		c.checkTypeDecl(d)
 107  	case *syntax.VarDecl:
 108  		c.checkVarDecl(d)
 109  	case *syntax.FuncDecl:
 110  		c.checkFuncDecl(d)
 111  	}
 112  }
 113  
 114  func (c *Checker) checkTypeDecl(d *syntax.TypeDecl) {
 115  	obj, ok := c.pkg.scope.Lookup(d.Name.Value).(*TypeName)
 116  	if !ok {
 117  		return
 118  	}
 119  	// Generic type declaration: register type params in a temporary scope so
 120  	// the underlying type can reference them (e.g. type Seq[V any] func(...V...)).
 121  	if len(d.TParamList) > 0 {
 122  		tmpScope := NewScope(c.pkg.scope)
 123  		for _, tp := range d.TParamList {
 124  			if tp.Name != nil && tp.Name.Value != "" {
 125  				tpObj := NewTypeName(c.pkg, tp.Name.Value,
 126  					NewTypeParam(NewTypeName(c.pkg, tp.Name.Value, nil), nil))
 127  				tmpScope.Insert(tpObj)
 128  			}
 129  		}
 130  		saved := c.localScope
 131  		c.localScope = tmpScope
 132  		defer func() { c.localScope = saved }()
 133  	}
 134  	underlying := c.resolveTypeExpr(d.Type)
 135  	named := NewNamed(obj, underlying)
 136  	obj.typ = named
 137  }
 138  
 139  func (c *Checker) checkVarDecl(d *syntax.VarDecl) {
 140  	var typ Type
 141  	if d.Type != nil {
 142  		typ = c.resolveTypeExpr(d.Type)
 143  	}
 144  	for _, name := range d.NameList {
 145  		if obj, ok := c.pkg.scope.Lookup(name.Value).(*Var); ok {
 146  			obj.typ = typ
 147  		}
 148  	}
 149  }
 150  
 151  func (c *Checker) checkFuncDecl(d *syntax.FuncDecl) {
 152  	// Build a temporary scope containing all type parameters visible in this
 153  	// function declaration: (1) explicit type params on the function itself,
 154  	// (2) type args from a generic receiver (func (h Handle[T]) ...).
 155  	typeParams := collectFuncTypeParams(d)
 156  	if len(typeParams) > 0 {
 157  		tmpScope := NewScope(c.pkg.scope)
 158  		for _, name := range typeParams {
 159  			obj := NewTypeName(c.pkg, name, NewTypeParam(NewTypeName(c.pkg, name, nil), nil))
 160  			tmpScope.Insert(obj)
 161  		}
 162  		saved := c.localScope
 163  		c.localScope = tmpScope
 164  		defer func() { c.localScope = saved }()
 165  	}
 166  	sig := c.resolveFuncType(d.Type, d.Recv)
 167  	fn := NewFunc(c.pkg, d.Name.Value, sig)
 168  	if d.Recv == nil {
 169  		// plain function — update the already-registered object
 170  		if obj, ok := c.pkg.scope.Lookup(d.Name.Value).(*Func); ok {
 171  			obj.typ = sig
 172  		}
 173  		return
 174  	}
 175  	// method — find the named receiver type and attach the method
 176  	recvTyp := c.resolveTypeExpr(d.Recv.Type)
 177  	if recvTyp == nil {
 178  		return
 179  	}
 180  	// unwrap pointer receiver: func (p *T) F() → receiver type is T
 181  	if pt, ok := recvTyp.(*Pointer); ok {
 182  		fn.hasPtrRecv = true
 183  		recvTyp = pt.base
 184  	}
 185  	if named, ok := recvTyp.(*Named); ok {
 186  		named.AddMethod(fn)
 187  	}
 188  	if c.info != nil && d.Name != nil {
 189  		c.info.Defs[d.Name] = fn
 190  	}
 191  }
 192  
 193  // checkFuncBody type-checks the body of a function declaration.
 194  func (c *Checker) checkFuncBody(fd *syntax.FuncDecl) {
 195  	if fd.Body == nil {
 196  		return
 197  	}
 198  	scope := c.openScope(fd.Body, c.pkg.scope)
 199  	// Set localScope so resolveTypeExpr can find locally-defined types.
 200  	saved := c.localScope
 201  	c.localScope = scope
 202  	defer func() { c.localScope = saved }()
 203  	// Register all type parameters (function-level + receiver type args) in
 204  	// the body scope so the signature and body can reference them.
 205  	for _, name := range collectFuncTypeParams(fd) {
 206  		obj := NewTypeName(c.pkg, name, NewTypeParam(NewTypeName(c.pkg, name, nil), nil))
 207  		scope.Insert(obj)
 208  	}
 209  	sig := c.resolveFuncType(fd.Type, fd.Recv)
 210  	if sig != nil {
 211  		// Receiver variable (for methods).
 212  		if sig.recv != nil && sig.recv.name != "" {
 213  			scope.Insert(sig.recv)
 214  		}
 215  		// Parameters.
 216  		if sig.params != nil {
 217  			for i := 0; i < sig.params.Len(); i++ {
 218  				p := sig.params.At(i)
 219  				if p.name != "" {
 220  					scope.Insert(p)
 221  				}
 222  			}
 223  		}
 224  		// Named results.
 225  		if sig.results != nil {
 226  			for i := 0; i < sig.results.Len(); i++ {
 227  				r := sig.results.At(i)
 228  				if r.name != "" {
 229  					scope.Insert(r)
 230  				}
 231  			}
 232  		}
 233  	}
 234  	c.checkBlock(fd.Body, scope)
 235  }
 236  
 237  // collectFuncTypeParams returns all type parameter names visible in a function
 238  // declaration: explicit type params on the function (TParamList) plus type
 239  // arguments on a generic receiver (func (h Handle[T]) F() needs T in scope).
 240  func collectFuncTypeParams(d *syntax.FuncDecl) []string {
 241  	seen := map[string]bool{}
 242  	var names []string
 243  	add := func(name string) {
 244  		if name != "" && !seen[name] {
 245  			seen[name] = true
 246  			names = append(names, name)
 247  		}
 248  	}
 249  	for _, tp := range d.TParamList {
 250  		if tp.Name != nil {
 251  			add(tp.Name.Value)
 252  		}
 253  	}
 254  	// Extract type args from a generic receiver: func (h Foo[T, U]) ...
 255  	if d.Recv != nil {
 256  		extractTypeArgs(d.Recv.Type, add)
 257  	}
 258  	return names
 259  }
 260  
 261  // extractTypeArgs recursively extracts bare Name nodes from a generic receiver
 262  // type expression like Handle[T] or Ptr[K, V].
 263  func extractTypeArgs(e syntax.Expr, add func(string)) {
 264  	if e == nil {
 265  		return
 266  	}
 267  	switch e := e.(type) {
 268  	case *syntax.IndexExpr:
 269  		// Handle[T] or Handle[T, U] (ListExpr inside)
 270  		extractTypeArgs(e.Index, add)
 271  	case *syntax.Name:
 272  		add(e.Value)
 273  	case *syntax.ListExpr:
 274  		for _, el := range e.ElemList {
 275  			extractTypeArgs(el, add)
 276  		}
 277  	case *syntax.Operation:
 278  		if e.Y == nil {
 279  			extractTypeArgs(e.X, add)
 280  		}
 281  	}
 282  }
 283  
 284  // declare inserts obj into s, recording the definition in info.
 285  func (c *Checker) declare(s *Scope, id *syntax.Name, obj Object) {
 286  	if id != nil && id.Value != "_" {
 287  		if alt := s.Insert(obj); alt != nil {
 288  			c.errorf(id.Pos(), "%s redeclared in this block", id.Value)
 289  			return
 290  		}
 291  	}
 292  	if c.info != nil && id != nil {
 293  		c.info.Defs[id] = obj
 294  	}
 295  }
 296