tc_decl.mx raw

   1  package types
   2  
   3  import (
   4  	"git.smesh.lol/moxie/pkg/syntax"
   5  	"git.smesh.lol/moxie/pkg/token"
   6  )
   7  
   8  // collectDecls walks a file's top-level declarations and inserts all
   9  // package-level names into pkg.scope. No type resolution yet - just
  10  // name registration so forward references work during pass 2.
  11  // Const groups are tracked so iota can be assigned correctly in pass 2.
  12  type constGroupEntry struct {
  13  	group *syntax.Group
  14  	decls []*syntax.ConstDecl
  15  }
  16  
  17  func (c *Checker) collectDecls(f *syntax.File) {
  18  	var constEntries []constGroupEntry
  19  
  20  	for _, d := range f.DeclList {
  21  		switch dd := d.(type) {
  22  		case *syntax.ImportDecl:
  23  			c.collectImport(dd)
  24  		case *syntax.ConstDecl:
  25  			for _, name := range dd.NameList {
  26  				obj := NewTCConst(c.pkg, name.Value, nil, nil)
  27  				c.declare(c.pkg.Scope, name, obj)
  28  			}
  29  			g := dd.Group
  30  			if g == nil {
  31  				g = &syntax.Group{}
  32  			}
  33  			found := false
  34  			for i := 0; i < len(constEntries); i++ {
  35  				if constEntries[i].group == g {
  36  					push(constEntries[i].decls, dd)
  37  					found = true
  38  					break
  39  				}
  40  			}
  41  			if !found {
  42  				push(constEntries, constGroupEntry{group: g, decls: []*syntax.ConstDecl{dd}})
  43  			}
  44  		case *syntax.TypeDecl:
  45  			obj := NewTypeName(c.pkg, dd.Name.Value, nil)
  46  			c.declare(c.pkg.Scope, dd.Name, obj)
  47  		case *syntax.VarDecl:
  48  			for _, name := range dd.NameList {
  49  				obj := NewTCVar(c.pkg, name.Value, nil)
  50  				c.declare(c.pkg.Scope, name, obj)
  51  			}
  52  		case *syntax.FuncDecl:
  53  			if dd.Recv == nil {
  54  				if dd.Name.Value == "init" {
  55  					continue
  56  				}
  57  				obj := NewTCFunc(c.pkg, dd.Name.Value, nil)
  58  				c.declare(c.pkg.Scope, dd.Name, obj)
  59  			}
  60  		}
  61  	}
  62  
  63  	for _, e := range constEntries {
  64  		c.checkConstGroup(e.decls)
  65  	}
  66  }
  67  
  68  // collectImport resolves an import declaration and adds the package name
  69  // to the current file's scope. A best-effort copy also lands in pkg.scope
  70  // (first-wins) for legacy lookup paths without file context.
  71  func (c *Checker) collectImport(d *syntax.ImportDecl) {
  72  	if d.Path == nil {
  73  		return
  74  	}
  75  	path := d.Path.Value
  76  	if len(path) >= 2 {
  77  		path = path[1 : len(path)-1] // strip quotes
  78  	}
  79  	if c.conf.Importer == nil {
  80  		return
  81  	}
  82  	imported, err := c.conf.Importer.Import(path)
  83  	if err != nil {
  84  		c.errorf(d.Pos(), "cannot import \"" | path | "\": " | err.Error())
  85  		return
  86  	}
  87  	// Track all direct imports regardless of local-name collisions.
  88  	found := false
  89  	for _, ip := range c.pkg.Imports {
  90  		if ip == imported {
  91  			found = true
  92  			break
  93  		}
  94  	}
  95  	if !found {
  96  		push(c.pkg.Imports, imported)
  97  	}
  98  	localName := imported.Name
  99  	if d.LocalPkgName != nil {
 100  		localName = d.LocalPkgName.Value
 101  	}
 102  	if localName == "_" {
 103  		return
 104  	}
 105  	if localName == "." {
 106  		if imported.Scope != nil {
 107  			for _, n := range imported.Scope.Names() {
 108  				obj := imported.Scope.Lookup(n)
 109  				if obj != nil && ObjectExported(obj) {
 110  					if c.fileScope != nil {
 111  						c.fileScope.Insert(obj)
 112  					}
 113  					c.pkg.Scope.Insert(obj)
 114  				}
 115  			}
 116  		}
 117  		return
 118  	}
 119  	pkgObj := NewPkgName(c.pkg, localName, imported)
 120  	if c.fileScope != nil {
 121  		if existing := c.fileScope.Lookup(localName); existing != nil {
 122  			if ep, ok := existing.(*PkgName); ok && ep.Imported == imported {
 123  				return // same import repeated in this file
 124  			}
 125  			c.errorf(d.Pos(), localName | " redeclared in this file")
 126  			return
 127  		}
 128  		c.fileScope.Insert(pkgObj)
 129  	}
 130  	// Legacy fallback: first-wins, silent on collision (the file scope
 131  	// disambiguates; paths without file context keep working for the
 132  	// common no-collision case).
 133  	c.pkg.Scope.Insert(pkgObj)
 134  }
 135  
 136  // checkDecl resolves the type of a top-level declaration.
 137  // Const decls are handled in collectDecls via checkConstGroup.
 138  func (c *Checker) checkDecl(d syntax.Decl) {
 139  	switch dd := d.(type) {
 140  	case *syntax.TypeDecl:
 141  		c.checkTypeDecl(dd)
 142  	case *syntax.VarDecl:
 143  		c.checkVarDecl(dd)
 144  	case *syntax.FuncDecl:
 145  		c.checkFuncDecl(dd)
 146  	}
 147  }
 148  
 149  func (c *Checker) checkTypeDecl(d *syntax.TypeDecl) {
 150  	obj, ok := c.pkg.Scope.Lookup(d.Name.Value).(*TypeName)
 151  	if !ok {
 152  		return
 153  	}
 154  	// Generic type declaration: register type params in a temporary scope so
 155  	// the underlying type can reference them (e.g. type Seq[V any] func(...V...)).
 156  	if len(d.TParamList) > 0 {
 157  		tpParent := c.pkg.Scope
 158  		if c.fileScope != nil {
 159  			tpParent = c.fileScope
 160  		}
 161  		tmpScope := NewScope(tpParent)
 162  		for _, tp := range d.TParamList {
 163  			if tp.Name != nil && tp.Name.Value != "" {
 164  				tpObj := NewTypeName(c.pkg, tp.Name.Value,
 165  					NewTypeParam(NewTypeName(c.pkg, tp.Name.Value, nil), nil))
 166  				tmpScope.Insert(tpObj)
 167  			}
 168  		}
 169  		saved := c.localScope
 170  		c.localScope = tmpScope
 171  		defer func() { c.localScope = saved }()
 172  	}
 173  	named := NewNamed(obj, nil)
 174  	// Mark generic types: their methods are never emitted (erasure +
 175  	// monomorphization), so dispatch and mxh generation must skip them.
 176  	for _, tp := range d.TParamList {
 177  		tpName := ""
 178  		if tp.Name != nil {
 179  			tpName = tp.Name.Value
 180  		}
 181  		push(named.TParams, NewTypeParam(NewTypeName(c.pkg, tpName, nil), nil))
 182  	}
 183  	underlying := c.resolveTypeExpr(d.Type)
 184  	named.Under = underlying
 185  }
 186  
 187  func (c *Checker) checkVarDecl(d *syntax.VarDecl) {
 188  	var typ Type
 189  	if d.Type != nil {
 190  		typ = c.resolveTypeExpr(d.Type)
 191  	}
 192  	for _, name := range d.NameList {
 193  		if obj, ok := c.pkg.Scope.Lookup(name.Value).(*TCVar); ok {
 194  			obj.Typ = typ
 195  		}
 196  	}
 197  }
 198  
 199  func (c *Checker) checkFuncDecl(d *syntax.FuncDecl) {
 200  	// Build a temporary scope containing all type parameters visible in this
 201  	// function declaration: (1) explicit type params on the function itself,
 202  	// (2) type args from a generic receiver (func (h Handle[T]) ...).
 203  	typeParams := collectFuncTypeParams(d)
 204  	if len(typeParams) > 0 {
 205  		tpParent := c.pkg.Scope
 206  		if c.fileScope != nil {
 207  			tpParent = c.fileScope
 208  		}
 209  		tmpScope := NewScope(tpParent)
 210  		for _, name := range typeParams {
 211  			obj := NewTypeName(c.pkg, name, NewTypeParam(NewTypeName(c.pkg, name, nil), nil))
 212  			tmpScope.Insert(obj)
 213  		}
 214  		saved := c.localScope
 215  		c.localScope = tmpScope
 216  		defer func() { c.localScope = saved }()
 217  	}
 218  	sig := c.resolveFuncType(d.Type, d.Recv)
 219  	fn := NewTCFunc(c.pkg, d.Name.Value, sig)
 220  	if d.Recv == nil {
 221  		// plain function - update the already-registered object
 222  		if obj, ok := c.pkg.Scope.Lookup(d.Name.Value).(*TCFunc); ok {
 223  			obj.Typ = sig
 224  		}
 225  		return
 226  	}
 227  	// method - find the named receiver type and attach the method
 228  	recvTyp := c.resolveTypeExpr(d.Recv.Type)
 229  	if recvTyp == nil {
 230  		return
 231  	}
 232  	// unwrap pointer receiver: func (p *T) F() -> receiver type is T
 233  	if pt, ok := recvTyp.(*Pointer); ok {
 234  		fn.PtrRecv = true
 235  		recvTyp = pt.Base
 236  	}
 237  	switch rt := recvTyp.(type) {
 238  	case *Named:
 239  		rt.AddMethod(fn)
 240  	case *Basic:
 241  		rt.AddMethod(fn)
 242  	}
 243  	if c.info != nil && d.Name != nil {
 244  		c.info.Defs[d.Name] = fn
 245  	}
 246  }
 247  
 248  // checkFuncBody type-checks the body of a function declaration.
 249  func (c *Checker) checkFuncBody(fd *syntax.FuncDecl) {
 250  	if fd.Body == nil {
 251  		return
 252  	}
 253  	bodyParent := c.pkg.Scope
 254  	if c.fileScope != nil {
 255  		bodyParent = c.fileScope
 256  	}
 257  	scope := c.openScope(fd.Body, bodyParent)
 258  	// Set localScope so resolveTypeExpr can find locally-defined types.
 259  	saved := c.localScope
 260  	c.localScope = scope
 261  	defer func() { c.localScope = saved }()
 262  	// Register all type parameters (function-level + receiver type args) in
 263  	// the body scope so the signature and body can reference them.
 264  	for _, name := range collectFuncTypeParams(fd) {
 265  		obj := NewTypeName(c.pkg, name, NewTypeParam(NewTypeName(c.pkg, name, nil), nil))
 266  		scope.Insert(obj)
 267  	}
 268  	sig := c.resolveFuncType(fd.Type, fd.Recv)
 269  	if sig != nil {
 270  		// Receiver variable (for methods).
 271  		if sig.Recv != nil && sig.Recv.Name != "" {
 272  			c.insertWithShadowCheck(scope, sig.Recv, fd.Recv.Name)
 273  		}
 274  		// Parameters.
 275  		if sig.Params != nil {
 276  			for i := 0; i < sig.Params.Len(); i++ {
 277  				p := sig.Params.At(i)
 278  				if p.Name != "" && p.Name != "_" {
 279  					var nameNode *syntax.Name
 280  					if i < len(fd.Type.ParamList) && fd.Type.ParamList[i].Name != nil {
 281  						nameNode = fd.Type.ParamList[i].Name
 282  					}
 283  					c.insertWithShadowCheck(scope, p, nameNode)
 284  				}
 285  			}
 286  		}
 287  		// Named results.
 288  		if sig.Results != nil {
 289  			for i := 0; i < sig.Results.Len(); i++ {
 290  				r := sig.Results.At(i)
 291  				if r.Name != "" && r.Name != "_" {
 292  					var nameNode *syntax.Name
 293  					if i < len(fd.Type.ResultList) && fd.Type.ResultList[i].Name != nil {
 294  						nameNode = fd.Type.ResultList[i].Name
 295  					}
 296  					c.insertWithShadowCheck(scope, r, nameNode)
 297  				}
 298  			}
 299  		}
 300  	}
 301  	c.checkBlock(fd.Body, scope)
 302  }
 303  
 304  // insertWithShadowCheck inserts obj into scope and checks for shadowing of
 305  // outer scope names, imported package names, and predeclared identifiers.
 306  // Used for function parameters and named results which bypass declare().
 307  func (c *Checker) insertWithShadowCheck(s *Scope, obj Object, nameNode *syntax.Name) {
 308  	name := ObjectName(obj)
 309  	if alt := s.Insert(obj); alt != nil {
 310  		if nameNode != nil {
 311  			c.errorf(nameNode.Pos(), name | " redeclared in this block")
 312  		}
 313  		return
 314  	}
 315  	var pos token.Pos
 316  	if nameNode != nil {
 317  		pos = nameNode.Pos()
 318  	}
 319  	if s.parent != nil {
 320  		if _, outer := s.parent.LookupParent(name); outer != nil {
 321  			c.errorf(pos, name | " shadows declaration in outer scope")
 322  		}
 323  	}
 324  	if Universe.Lookup(name) != nil {
 325  		c.errorf(pos, name | " shadows predeclared identifier")
 326  	}
 327  }
 328  
 329  // collectFuncTypeParams returns all type parameter names visible in a function
 330  // declaration: explicit type params on the function (TParamList) plus type
 331  // arguments on a generic receiver (func (h Handle[T]) F() needs T in scope).
 332  func collectFuncTypeParams(d *syntax.FuncDecl) (ss []string) {
 333  	seen := map[string]bool{}
 334  	var names []string
 335  	add := func(name string) {
 336  		if name != "" && !seen[name] {
 337  			seen[name] = true
 338  			push(names, name)
 339  		}
 340  	}
 341  	for _, tp := range d.TParamList {
 342  		if tp.Name != nil {
 343  			add(tp.Name.Value)
 344  		}
 345  	}
 346  	// Extract type args from a generic receiver: func (h Foo[T, U]) ...
 347  	if d.Recv != nil {
 348  		extractTypeArgs(d.Recv.Type, add)
 349  	}
 350  	return names
 351  }
 352  
 353  // extractTypeArgs recursively extracts bare Name nodes from a generic receiver
 354  // type expression like Handle[T] or Ptr[K, V].
 355  func extractTypeArgs(e syntax.Expr, add func(string)) {
 356  	if e == nil {
 357  		return
 358  	}
 359  	switch ev := e.(type) {
 360  	case *syntax.IndexExpr:
 361  		// Handle[T] or Handle[T, U] (ListExpr inside)
 362  		extractTypeArgs(ev.Index, add)
 363  	case *syntax.Name:
 364  		add(ev.Value)
 365  	case *syntax.ListExpr:
 366  		for _, el := range ev.ElemList {
 367  			extractTypeArgs(el, add)
 368  		}
 369  	case *syntax.Operation:
 370  		if ev.Y == nil {
 371  			extractTypeArgs(ev.X, add)
 372  		}
 373  	}
 374  }
 375  
 376  // declare inserts obj into s, recording the definition in info.
 377  // Moxie prohibits all variable shadowing: a name declared in an inner scope
 378  // must not collide with any name in an outer scope, the file scope (imported
 379  // package names), or the universe scope (predeclared identifiers).
 380  func (c *Checker) declare(s *Scope, id *syntax.Name, obj Object) {
 381  	if id != nil && id.Value != "_" {
 382  		if alt := s.Insert(obj); alt != nil {
 383  			c.errorf(id.Pos(), id.Value | " redeclared in this block")
 384  			return
 385  		}
 386  		// Reject shadowing of any name from an outer scope (includes
 387  		// file-scope imports, package-level names, and universe builtins).
 388  		if s.parent != nil {
 389  			if _, outer := s.parent.LookupParent(id.Value); outer != nil {
 390  				c.errorf(id.Pos(), id.Value | " shadows declaration in outer scope")
 391  			}
 392  		}
 393  		// Explicit universe check for package-level declarations whose
 394  		// parent IS universe (LookupParent above wouldn't reach it from
 395  		// package scope since universe is package scope's parent only
 396  		// if file scopes are interposed).
 397  		if Universe.Lookup(id.Value) != nil {
 398  			c.errorf(id.Pos(), id.Value | " shadows predeclared identifier")
 399  		}
 400  	}
 401  	if c.info != nil && id != nil {
 402  		c.info.Defs[id] = obj
 403  	}
 404  }
 405