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 }
226 return TypeAndValue{}
227 }
228
229 func (c *Checker) typeSelector(e *syntax.SelectorExpr, scope *Scope) TypeAndValue {
230 xt := c.typeExpr(e.X, scope)
231 if c.info != nil {
232 c.info.Types[e.X] = xt
233 }
234 if xt.mode == modePkg {
235 // package.Name
236 if pkgName, ok := e.X.(*syntax.Name); ok {
237 _, pkgObj := c.lookup(pkgName.Value, scope)
238 if pn, ok := pkgObj.(*PkgName); ok && pn.imported != nil {
239 obj := pn.imported.scope.Lookup(e.Sel.Value)
240 if obj != nil {
241 if c.info != nil {
242 c.info.Uses[e.Sel] = obj
243 }
244 return TypeAndValue{Type: obj.Type(), mode: modeValue}
245 }
246 }
247 }
248 return TypeAndValue{}
249 }
250
251 // field or method lookup
252 typ := xt.Type
253 if typ == nil {
254 return TypeAndValue{}
255 }
256 sel := c.lookupFieldOrMethod(typ, e.Sel.Value)
257 if sel != nil {
258 if c.info != nil {
259 c.info.Selections[e] = sel
260 if sel.obj != nil {
261 c.info.Uses[e.Sel] = sel.obj
262 }
263 }
264 return TypeAndValue{Type: sel.Type(), mode: modeValue}
265 }
266 return TypeAndValue{}
267 }
268
269 func (c *Checker) typeIndex(e *syntax.IndexExpr, scope *Scope) TypeAndValue {
270 xt := c.checkExpr(e.X, scope)
271 c.checkExpr(e.Index, scope)
272 if xt == nil {
273 return TypeAndValue{}
274 }
275 switch t := xt.Underlying().(type) {
276 case *Array:
277 return TypeAndValue{Type: t.elem, mode: modeVar}
278 case *Slice:
279 return TypeAndValue{Type: t.elem, mode: modeVar}
280 case *Map:
281 return TypeAndValue{Type: t.elem, mode: modeValue}
282 }
283 return TypeAndValue{}
284 }
285
286 func (c *Checker) typeSlice(e *syntax.SliceExpr, scope *Scope) TypeAndValue {
287 xt := c.checkExpr(e.X, scope)
288 for _, idx := range e.Index {
289 if idx != nil {
290 c.checkExpr(idx, scope)
291 }
292 }
293 if xt == nil {
294 return TypeAndValue{}
295 }
296 switch t := xt.Underlying().(type) {
297 case *Array:
298 return TypeAndValue{Type: NewSlice(t.elem), mode: modeValue}
299 case *Slice:
300 return TypeAndValue{Type: xt, mode: modeValue}
301 }
302 // string[:] → string
303 if b, ok := xt.Underlying().(*Basic); ok && b.info&IsString != 0 {
304 return TypeAndValue{Type: xt, mode: modeValue}
305 }
306 return TypeAndValue{}
307 }
308
309 func (c *Checker) typeAssert(e *syntax.AssertExpr, scope *Scope) TypeAndValue {
310 c.checkExpr(e.X, scope)
311 assertedType := c.resolveTypeExpr(e.Type)
312 return TypeAndValue{Type: assertedType, mode: modeValue}
313 }
314
315 func (c *Checker) typeCompositeLit(e *syntax.CompositeLit, scope *Scope) TypeAndValue {
316 var typ Type
317 if e.Type != nil {
318 typ = c.resolveTypeExpr(e.Type)
319 }
320 // Determine whether keys in key:value pairs are field names (struct) or
321 // expressions (map/slice). Struct field names are not in scope.
322 // Also treat typeless composite literals (e.Type == nil) as structs —
323 // in Go they appear as slice/array element literals where type is inferred.
324 isStruct := e.Type == nil || (typ != nil && isStructType(typ))
325 // Fallback: if we have key:value elements but couldn't determine the type,
326 // assume struct (struct field names don't exist as scope variables).
327 if !isStruct && len(e.ElemList) > 0 {
328 if _, ok := e.ElemList[0].(*syntax.KeyValueExpr); ok {
329 isStruct = true
330 }
331 }
332 for _, el := range e.ElemList {
333 if kv, ok := el.(*syntax.KeyValueExpr); ok {
334 if isStruct {
335 // Struct field: only check value, skip key lookup.
336 c.checkExpr(kv.Value, scope)
337 } else {
338 // Map/unknown: check both key and value.
339 c.checkExpr(kv.Key, scope)
340 c.checkExpr(kv.Value, scope)
341 }
342 } else {
343 c.checkExpr(el, scope)
344 }
345 }
346 return TypeAndValue{Type: typ, mode: modeValue}
347 }
348
349 // isStructType returns true if t is (or is a Named wrapping) a struct.
350 // Named types with nil underlying (unresolved) are treated as structs
351 // because composite literals with key:value syntax on a Named type are
352 // always struct literals in Moxie code — map literals use an explicit
353 // map[K]V type, never a Named alias at this level.
354 func isStructType(t Type) bool {
355 if t == nil {
356 return false
357 }
358 switch t := t.(type) {
359 case *Struct:
360 return true
361 case *Named:
362 if t.underlying == nil {
363 return true // optimistic: Named with key:value syntax = struct
364 }
365 return isStructType(t.underlying)
366 }
367 return false
368 }
369
370 func (c *Checker) typeFuncLit(e *syntax.FuncLit, scope *Scope) TypeAndValue {
371 sig := c.resolveFuncType(e.Type, nil)
372 inner := c.openScope(e.Body, scope)
373 if sig != nil {
374 if sig.params != nil {
375 for i := 0; i < sig.params.Len(); i++ {
376 p := sig.params.At(i)
377 if p.name != "" {
378 inner.Insert(p)
379 }
380 }
381 }
382 if sig.results != nil {
383 for i := 0; i < sig.results.Len(); i++ {
384 r := sig.results.At(i)
385 if r.name != "" {
386 inner.Insert(r)
387 }
388 }
389 }
390 }
391 c.checkBlock(e.Body, inner)
392 return TypeAndValue{Type: sig, mode: modeValue}
393 }
394
395 // lookupFieldOrMethod finds a field or method named name on type t.
396 func (c *Checker) lookupFieldOrMethod(t Type, name string) *Selection {
397 if t == nil {
398 return nil
399 }
400 // dereference pointer
401 indirect := false
402 if pt, ok := safeUnderlying(t).(*Pointer); ok {
403 t = pt.base
404 indirect = true
405 }
406
407 switch t := safeUnderlying(t).(type) {
408 case *Struct:
409 for i, f := range t.fields {
410 if f.name == name {
411 return &Selection{
412 kind: FieldVal,
413 recv: t,
414 obj: f,
415 index: []int{i},
416 indir: indirect,
417 }
418 }
419 }
420 case *Interface:
421 for _, m := range t.allMethods {
422 if m.name == name {
423 fn := NewFunc(nil, name, m.sig)
424 return &Selection{kind: MethodVal, recv: t, obj: fn, indir: indirect}
425 }
426 }
427 case *Named:
428 for _, m := range t.methods {
429 if m.name == name {
430 return &Selection{kind: MethodVal, recv: t, obj: m, indir: indirect}
431 }
432 }
433 if t.underlying != nil {
434 return c.lookupFieldOrMethod(t.underlying, name)
435 }
436 }
437 return nil
438 }
439