expr.go raw
1 package typecheck
2
3 import "moxie/syntax"
4
5 // checkExpr type-checks an expression and returns its type.
6 // It also records the type info in c.info if non-nil.
7 func (c *Checker) checkExpr(e syntax.Expr, scope *Scope) Type {
8 if e == nil {
9 return nil
10 }
11 tv := c.typeExpr(e, scope)
12 c.record(e, tv)
13 return tv.Type
14 }
15
16 // typeExpr computes the TypeAndValue for expression e.
17 func (c *Checker) typeExpr(e syntax.Expr, scope *Scope) TypeAndValue {
18 switch e := e.(type) {
19 case *syntax.Name:
20 return c.typeIdent(e, scope)
21 case *syntax.BasicLit:
22 return c.typeBasicLit(e)
23 case *syntax.Operation:
24 return c.typeOperation(e, scope)
25 case *syntax.CallExpr:
26 return c.typeCall(e, scope)
27 case *syntax.SelectorExpr:
28 return c.typeSelector(e, scope)
29 case *syntax.IndexExpr:
30 return c.typeIndex(e, scope)
31 case *syntax.SliceExpr:
32 return c.typeSlice(e, scope)
33 case *syntax.AssertExpr:
34 return c.typeAssert(e, scope)
35 case *syntax.TypeSwitchGuard:
36 return TypeAndValue{Type: c.checkExpr(e.X, scope), mode: modeValue}
37 case *syntax.CompositeLit:
38 return c.typeCompositeLit(e, scope)
39 case *syntax.FuncLit:
40 return c.typeFuncLit(e, scope)
41 case *syntax.KeyValueExpr:
42 // key:value — type is the value's type
43 c.checkExpr(e.Key, scope)
44 return c.typeExpr(e.Value, scope)
45 case *syntax.ParenExpr:
46 return c.typeExpr(e.X, scope)
47 case *syntax.ListExpr:
48 // multi-value list (tuple unpacking context)
49 var last TypeAndValue
50 for _, el := range e.ElemList {
51 last = c.typeExpr(el, scope)
52 }
53 return last
54 // type expressions used where a value is expected
55 case *syntax.SliceType, *syntax.ArrayType, *syntax.MapType,
56 *syntax.ChanType, *syntax.StructType, *syntax.InterfaceType,
57 *syntax.FuncType, *syntax.DotsType:
58 typ := c.resolveTypeExpr(e)
59 return TypeAndValue{Type: typ, mode: modeType}
60 }
61 return TypeAndValue{}
62 }
63
64 func (c *Checker) typeIdent(e *syntax.Name, scope *Scope) TypeAndValue {
65 if e.Value == "_" {
66 return TypeAndValue{mode: modeValue}
67 }
68 _, obj := c.lookup(e.Value, scope)
69 if obj == nil {
70 c.errorf(e.Pos(), "undefined: %s", e.Value)
71 return TypeAndValue{}
72 }
73 if c.info != nil {
74 c.info.Uses[e] = obj
75 }
76 switch obj := obj.(type) {
77 case *Var:
78 return TypeAndValue{Type: obj.typ, mode: modeVar}
79 case *Const:
80 return TypeAndValue{Type: obj.typ, Value: obj.val, mode: modeConst}
81 case *TypeName:
82 return TypeAndValue{Type: obj.typ, mode: modeType}
83 case *Func:
84 return TypeAndValue{Type: obj.typ, mode: modeValue}
85 case *Builtin:
86 return TypeAndValue{mode: modeBuiltin}
87 case *PkgName:
88 return TypeAndValue{mode: modePkg}
89 }
90 return TypeAndValue{}
91 }
92
93 func (c *Checker) typeBasicLit(e *syntax.BasicLit) TypeAndValue {
94 switch e.Kind {
95 case syntax.IntLit:
96 return TypeAndValue{Type: Typ[UntypedInt], mode: modeConst}
97 case syntax.FloatLit:
98 return TypeAndValue{Type: Typ[UntypedFloat], mode: modeConst}
99 case syntax.StringLit:
100 return TypeAndValue{Type: Typ[UntypedString], mode: modeConst}
101 case syntax.RuneLit:
102 return TypeAndValue{Type: Typ[UntypedRune], mode: modeConst}
103 }
104 return TypeAndValue{}
105 }
106
107 func (c *Checker) typeOperation(e *syntax.Operation, scope *Scope) TypeAndValue {
108 if e.Y == nil {
109 // unary
110 xt := c.checkExpr(e.X, scope)
111 if xt == nil {
112 return TypeAndValue{mode: modeValue}
113 }
114 switch e.Op {
115 case syntax.Recv: // <-ch
116 if ch, ok := xt.Underlying().(*Chan); ok {
117 return TypeAndValue{Type: ch.elem, mode: modeValue}
118 }
119 case syntax.And: // &x
120 return TypeAndValue{Type: NewPointer(xt), mode: modeValue}
121 case syntax.Mul: // *x (dereference)
122 if pt, ok := xt.Underlying().(*Pointer); ok {
123 return TypeAndValue{Type: pt.base, mode: modeVar}
124 }
125 default:
126 return TypeAndValue{Type: xt, mode: modeValue}
127 }
128 return TypeAndValue{mode: modeValue}
129 }
130 // binary
131 xt := c.checkExpr(e.X, scope)
132 yt := c.checkExpr(e.Y, scope)
133 switch e.Op {
134 case syntax.Eql, syntax.Neq, syntax.Lss, syntax.Leq, syntax.Gtr, syntax.Geq,
135 syntax.AndAnd, syntax.OrOr:
136 return TypeAndValue{Type: Typ[Bool], mode: modeValue}
137 case syntax.Or, syntax.Add:
138 // In Moxie, | on strings is concat. Use dominant side's type.
139 if xt != nil {
140 return TypeAndValue{Type: xt, mode: modeValue}
141 }
142 return TypeAndValue{Type: yt, mode: modeValue}
143 default:
144 if xt != nil {
145 return TypeAndValue{Type: xt, mode: modeValue}
146 }
147 return TypeAndValue{Type: yt, mode: modeValue}
148 }
149 }
150
151 func (c *Checker) typeCall(e *syntax.CallExpr, scope *Scope) TypeAndValue {
152 funTV := c.typeExpr(e.Fun, scope)
153 if c.info != nil {
154 c.info.Types[e.Fun] = funTV
155 }
156
157 for _, arg := range e.ArgList {
158 c.checkExpr(arg, scope)
159 }
160
161 if funTV.mode == modeBuiltin {
162 return c.typeBuiltinCall(e, scope)
163 }
164 if funTV.mode == modeType {
165 // conversion: T(x)
166 if len(e.ArgList) == 1 {
167 c.checkExpr(e.ArgList[0], scope)
168 }
169 return TypeAndValue{Type: funTV.Type, mode: modeValue}
170 }
171
172 if funTV.Type == nil {
173 return TypeAndValue{}
174 }
175 sig, ok := funTV.Type.Underlying().(*Signature)
176 if !ok {
177 return TypeAndValue{}
178 }
179 if sig.results == nil || sig.results.Len() == 0 {
180 return TypeAndValue{mode: modeVoid}
181 }
182 if sig.results.Len() == 1 {
183 return TypeAndValue{Type: sig.results.At(0).typ, mode: modeValue}
184 }
185 // multi-return: return a Tuple type
186 return TypeAndValue{Type: sig.results, mode: modeValue}
187 }
188
189 func (c *Checker) typeBuiltinCall(e *syntax.CallExpr, scope *Scope) TypeAndValue {
190 // Determine which builtin from the ident.
191 name, ok := e.Fun.(*syntax.Name)
192 if !ok {
193 return TypeAndValue{}
194 }
195 _, obj := c.lookup(name.Value, scope)
196 b, ok := obj.(*Builtin)
197 if !ok {
198 return TypeAndValue{}
199 }
200 switch b.id {
201 case BuiltinLen, BuiltinCap:
202 return TypeAndValue{Type: Typ[Int32], mode: modeValue}
203 case BuiltinAppend:
204 if len(e.ArgList) > 0 {
205 return TypeAndValue{Type: c.checkExpr(e.ArgList[0], scope), mode: modeValue}
206 }
207 case BuiltinMake:
208 if len(e.ArgList) > 0 {
209 return TypeAndValue{Type: c.resolveTypeExpr(e.ArgList[0]), mode: modeValue}
210 }
211 case BuiltinNew:
212 if len(e.ArgList) > 0 {
213 return TypeAndValue{Type: NewPointer(c.resolveTypeExpr(e.ArgList[0])), mode: modeValue}
214 }
215 case BuiltinPanic, BuiltinPrint, BuiltinPrintln:
216 return TypeAndValue{mode: modeVoid}
217 case BuiltinRecover:
218 return TypeAndValue{Type: universeError.typ, mode: modeValue}
219 case BuiltinClose, BuiltinDelete, BuiltinClear:
220 return TypeAndValue{mode: modeVoid}
221 case BuiltinCopy:
222 return TypeAndValue{Type: Typ[Int32], mode: modeValue}
223 case BuiltinSpawn:
224 return TypeAndValue{mode: modeVoid}
225 case BuiltinPush, BuiltinResize:
226 // push(s, v) and resize(s, n) mutate s in place, return void
227 return TypeAndValue{mode: modeVoid}
228 case BuiltinPop:
229 // pop(s) returns the element type of the slice
230 if len(e.ArgList) > 0 {
231 st := c.checkExpr(e.ArgList[0], scope)
232 if sl, ok := st.Underlying().(*Slice); ok {
233 return TypeAndValue{Type: sl.Elem(), mode: modeValue}
234 }
235 // string = []byte, pop returns byte
236 if b2, ok := st.Underlying().(*Basic); ok && (b2.Kind() == String || b2.Kind() == UntypedString) {
237 return TypeAndValue{Type: Typ[Uint8], mode: modeValue}
238 }
239 }
240 }
241 return TypeAndValue{}
242 }
243
244 func (c *Checker) typeSelector(e *syntax.SelectorExpr, scope *Scope) TypeAndValue {
245 xt := c.typeExpr(e.X, scope)
246 if c.info != nil {
247 c.info.Types[e.X] = xt
248 }
249 if xt.mode == modePkg {
250 // package.Name
251 if pkgName, ok := e.X.(*syntax.Name); ok {
252 _, pkgObj := c.lookup(pkgName.Value, scope)
253 if pn, ok := pkgObj.(*PkgName); ok && pn.imported != nil {
254 obj := pn.imported.scope.Lookup(e.Sel.Value)
255 if obj != nil {
256 if c.info != nil {
257 c.info.Uses[e.Sel] = obj
258 }
259 return TypeAndValue{Type: obj.Type(), mode: modeValue}
260 }
261 }
262 }
263 return TypeAndValue{}
264 }
265
266 // field or method lookup
267 typ := xt.Type
268 if typ == nil {
269 return TypeAndValue{}
270 }
271 sel := c.lookupFieldOrMethod(typ, e.Sel.Value)
272 if sel != nil {
273 if c.info != nil {
274 c.info.Selections[e] = sel
275 if sel.obj != nil {
276 c.info.Uses[e.Sel] = sel.obj
277 }
278 }
279 return TypeAndValue{Type: sel.Type(), mode: modeValue}
280 }
281 return TypeAndValue{}
282 }
283
284 func (c *Checker) typeIndex(e *syntax.IndexExpr, scope *Scope) TypeAndValue {
285 xt := c.checkExpr(e.X, scope)
286 c.checkExpr(e.Index, scope)
287 if xt == nil {
288 return TypeAndValue{}
289 }
290 switch t := xt.Underlying().(type) {
291 case *Array:
292 return TypeAndValue{Type: t.elem, mode: modeVar}
293 case *Slice:
294 return TypeAndValue{Type: t.elem, mode: modeVar}
295 case *Map:
296 return TypeAndValue{Type: t.elem, mode: modeValue}
297 }
298 return TypeAndValue{}
299 }
300
301 func (c *Checker) typeSlice(e *syntax.SliceExpr, scope *Scope) TypeAndValue {
302 xt := c.checkExpr(e.X, scope)
303 for _, idx := range e.Index {
304 if idx != nil {
305 c.checkExpr(idx, scope)
306 }
307 }
308 if xt == nil {
309 return TypeAndValue{}
310 }
311 switch t := xt.Underlying().(type) {
312 case *Array:
313 return TypeAndValue{Type: NewSlice(t.elem), mode: modeValue}
314 case *Slice:
315 return TypeAndValue{Type: xt, mode: modeValue}
316 }
317 // string[:] → string
318 if b, ok := xt.Underlying().(*Basic); ok && b.info&IsString != 0 {
319 return TypeAndValue{Type: xt, mode: modeValue}
320 }
321 return TypeAndValue{}
322 }
323
324 func (c *Checker) typeAssert(e *syntax.AssertExpr, scope *Scope) TypeAndValue {
325 c.checkExpr(e.X, scope)
326 assertedType := c.resolveTypeExpr(e.Type)
327 return TypeAndValue{Type: assertedType, mode: modeValue}
328 }
329
330 func (c *Checker) typeCompositeLit(e *syntax.CompositeLit, scope *Scope) TypeAndValue {
331 var typ Type
332 if e.Type != nil {
333 typ = c.resolveTypeExpr(e.Type)
334 }
335 // Determine whether keys in key:value pairs are field names (struct) or
336 // expressions (map/slice). Struct field names are not in scope.
337 // Also treat typeless composite literals (e.Type == nil) as structs —
338 // in Go they appear as slice/array element literals where type is inferred.
339 isStruct := e.Type == nil || (typ != nil && isStructType(typ))
340 // Fallback: if we have key:value elements but couldn't determine the type,
341 // assume struct (struct field names don't exist as scope variables).
342 if !isStruct && len(e.ElemList) > 0 {
343 if _, ok := e.ElemList[0].(*syntax.KeyValueExpr); ok {
344 isStruct = true
345 }
346 }
347 for _, el := range e.ElemList {
348 if kv, ok := el.(*syntax.KeyValueExpr); ok {
349 if isStruct {
350 // Struct field: only check value, skip key lookup.
351 c.checkExpr(kv.Value, scope)
352 } else {
353 // Map/unknown: check both key and value.
354 c.checkExpr(kv.Key, scope)
355 c.checkExpr(kv.Value, scope)
356 }
357 } else {
358 c.checkExpr(el, scope)
359 }
360 }
361 return TypeAndValue{Type: typ, mode: modeValue}
362 }
363
364 // isStructType returns true if t is (or is a Named wrapping) a struct.
365 // Named types with nil underlying (unresolved) are treated as structs
366 // because composite literals with key:value syntax on a Named type are
367 // always struct literals in Moxie code — map literals use an explicit
368 // map[K]V type, never a Named alias at this level.
369 func isStructType(t Type) bool {
370 if t == nil {
371 return false
372 }
373 switch t := t.(type) {
374 case *Struct:
375 return true
376 case *Named:
377 if t.underlying == nil {
378 return true // optimistic: Named with key:value syntax = struct
379 }
380 return isStructType(t.underlying)
381 }
382 return false
383 }
384
385 func (c *Checker) typeFuncLit(e *syntax.FuncLit, scope *Scope) TypeAndValue {
386 sig := c.resolveFuncType(e.Type, nil)
387 inner := c.openScope(e.Body, scope)
388 if sig != nil {
389 if sig.params != nil {
390 for i := 0; i < sig.params.Len(); i++ {
391 p := sig.params.At(i)
392 if p.name != "" {
393 inner.Insert(p)
394 }
395 }
396 }
397 if sig.results != nil {
398 for i := 0; i < sig.results.Len(); i++ {
399 r := sig.results.At(i)
400 if r.name != "" {
401 inner.Insert(r)
402 }
403 }
404 }
405 }
406 c.checkBlock(e.Body, inner)
407 return TypeAndValue{Type: sig, mode: modeValue}
408 }
409
410 // lookupFieldOrMethod finds a field or method named name on type t.
411 func (c *Checker) lookupFieldOrMethod(t Type, name string) *Selection {
412 if t == nil {
413 return nil
414 }
415 // dereference pointer
416 indirect := false
417 if pt, ok := safeUnderlying(t).(*Pointer); ok {
418 t = pt.base
419 indirect = true
420 }
421
422 switch t := safeUnderlying(t).(type) {
423 case *Struct:
424 for i, f := range t.fields {
425 if f.name == name {
426 return &Selection{
427 kind: FieldVal,
428 recv: t,
429 obj: f,
430 index: []int{i},
431 indir: indirect,
432 }
433 }
434 }
435 case *Interface:
436 for _, m := range t.allMethods {
437 if m.name == name {
438 fn := NewFunc(nil, name, m.sig)
439 return &Selection{kind: MethodVal, recv: t, obj: fn, indir: indirect}
440 }
441 }
442 case *Named:
443 for _, m := range t.methods {
444 if m.name == name {
445 return &Selection{kind: MethodVal, recv: t, obj: m, indir: indirect}
446 }
447 }
448 if t.underlying != nil {
449 return c.lookupFieldOrMethod(t.underlying, name)
450 }
451 }
452 return nil
453 }
454