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