package main // checkExpr type-checks an expression and returns its type. // It also records the type info in c.info if non-nil. func (c *Checker) checkExpr(e Expr, scope *Scope) Type { if e == nil { return nil } tv := c.typeExpr(e, scope) c.record(e, tv) return tv.Type } // typeExpr computes the TCTypeAndValue for expression e. func (c *Checker) typeExpr(e Expr, scope *Scope) TCTypeAndValue { switch e := e.(type) { case *Name: return c.typeIdent(e, scope) case *BasicLit: return c.typeBasicLit(e) case *Operation: return c.typeOperation(e, scope) case *CallExpr: return c.typeCall(e, scope) case *SelectorExpr: return c.typeSelector(e, scope) case *IndexExpr: return c.typeIndex(e, scope) case *SliceExpr: return c.typeSlice(e, scope) case *AssertExpr: return c.typeAssert(e, scope) case *TypeSwitchGuard: return TCTypeAndValue{Type: c.checkExpr(e.X, scope), mode: modeValue} case *CompositeLit: return c.typeCompositeLit(e, scope) case *FuncLit: return c.typeFuncLit(e, scope) case *KeyValueExpr: // key:value - type is the value's type c.checkExpr(e.Key, scope) return c.typeExpr(e.Value, scope) case *ParenExpr: return c.typeExpr(e.X, scope) case *ListExpr: // multi-value list (tuple unpacking context) var last TCTypeAndValue for _, el := range e.ElemList { last = c.typeExpr(el, scope) } return last // type expressions used where a value is expected case *SliceType, *ArrayType, *MapType, *ChanType, *StructType, *InterfaceType, *FuncType, *DotsType: typ := c.resolveTypeExpr(e) return TCTypeAndValue{Type: typ, mode: modeType} } return TCTypeAndValue{} } func (c *Checker) typeIdent(e *Name, scope *Scope) TCTypeAndValue { if e.Value == "_" { return TCTypeAndValue{mode: modeValue} } _, obj := c.lookup(e.Value, scope) if obj == nil { c.errorf(e.Pos(), "undefined: %s", e.Value) return TCTypeAndValue{} } if c.info != nil { c.info.Uses[e] = obj } switch obj := obj.(type) { case *TCVar: return TCTypeAndValue{Type: obj.typ, mode: modeVar} case *TCConst: return TCTypeAndValue{Type: obj.typ, Value: obj.val, mode: modeConst} case *TypeName: return TCTypeAndValue{Type: obj.typ, mode: modeType} case *TCFunc: return TCTypeAndValue{Type: obj.typ, mode: modeValue} case *Builtin: return TCTypeAndValue{mode: modeBuiltin} case *PkgName: return TCTypeAndValue{mode: modePkg} } return TCTypeAndValue{} } func (c *Checker) typeBasicLit(e *BasicLit) TCTypeAndValue { switch e.Kind { case IntLit: return TCTypeAndValue{Type: Typ[UntypedInt], mode: modeConst} case FloatLit: return TCTypeAndValue{Type: Typ[UntypedFloat], mode: modeConst} case StringLit: return TCTypeAndValue{Type: Typ[UntypedString], mode: modeConst} case RuneLit: return TCTypeAndValue{Type: Typ[UntypedRune], mode: modeConst} } return TCTypeAndValue{} } func (c *Checker) typeOperation(e *Operation, scope *Scope) TCTypeAndValue { if e.Y == nil { // unary xt := c.checkExpr(e.X, scope) if xt == nil { return TCTypeAndValue{mode: modeValue} } switch e.Op { case Recv: // <-ch if ch, ok := xt.Underlying().(*TCChan); ok { return TCTypeAndValue{Type: ch.elem, mode: modeValue} } case And: // &x return TCTypeAndValue{Type: NewPointer(xt), mode: modeValue} case Mul: // *x (dereference) if pt, ok := xt.Underlying().(*Pointer); ok { return TCTypeAndValue{Type: pt.base, mode: modeVar} } default: return TCTypeAndValue{Type: xt, mode: modeValue} } return TCTypeAndValue{mode: modeValue} } // binary xt := c.checkExpr(e.X, scope) yt := c.checkExpr(e.Y, scope) switch e.Op { case Eql, Neq, Lss, Leq, Gtr, Geq, AndAnd, OrOr: return TCTypeAndValue{Type: Typ[Bool], mode: modeValue} case Or, Add: // In Moxie, | on strings is concat. Use dominant side's type. if xt != nil { return TCTypeAndValue{Type: xt, mode: modeValue} } return TCTypeAndValue{Type: yt, mode: modeValue} default: if xt != nil { return TCTypeAndValue{Type: xt, mode: modeValue} } return TCTypeAndValue{Type: yt, mode: modeValue} } } func (c *Checker) typeCall(e *CallExpr, scope *Scope) TCTypeAndValue { funTV := c.typeExpr(e.Fun, scope) if c.info != nil { c.info.Types[e.Fun] = funTV } for _, arg := range e.ArgList { c.checkExpr(arg, scope) } if funTV.mode == modeBuiltin { return c.typeBuiltinCall(e, scope) } if funTV.mode == modeType { // conversion: T(x) if len(e.ArgList) == 1 { c.checkExpr(e.ArgList[0], scope) } return TCTypeAndValue{Type: funTV.Type, mode: modeValue} } if funTV.Type == nil { return TCTypeAndValue{} } sig, ok := funTV.Type.Underlying().(*Signature) if !ok { return TCTypeAndValue{} } if sig.results == nil || sig.results.Len() == 0 { return TCTypeAndValue{mode: modeVoid} } if sig.results.Len() == 1 { return TCTypeAndValue{Type: sig.results.At(0).typ, mode: modeValue} } // multi-return: return a Tuple type return TCTypeAndValue{Type: sig.results, mode: modeValue} } func (c *Checker) typeBuiltinCall(e *CallExpr, scope *Scope) TCTypeAndValue { // Determine which builtin from the ident. name, ok := e.Fun.(*Name) if !ok { return TCTypeAndValue{} } _, obj := c.lookup(name.Value, scope) b, ok := obj.(*Builtin) if !ok { return TCTypeAndValue{} } switch b.id { case BuiltinLen, BuiltinCap: return TCTypeAndValue{Type: Typ[Int32], mode: modeValue} case BuiltinAppend: if len(e.ArgList) > 0 { return TCTypeAndValue{Type: c.checkExpr(e.ArgList[0], scope), mode: modeValue} } case BuiltinMake: if len(e.ArgList) > 0 { return TCTypeAndValue{Type: c.resolveTypeExpr(e.ArgList[0]), mode: modeValue} } case BuiltinNew: if len(e.ArgList) > 0 { return TCTypeAndValue{Type: NewPointer(c.resolveTypeExpr(e.ArgList[0])), mode: modeValue} } case BuiltinPanic, BuiltinPrint, BuiltinPrintln: return TCTypeAndValue{mode: modeVoid} case BuiltinRecover: return TCTypeAndValue{Type: universeError.typ, mode: modeValue} case BuiltinClose, BuiltinDelete, BuiltinClear: return TCTypeAndValue{mode: modeVoid} case BuiltinCopy: return TCTypeAndValue{Type: Typ[Int32], mode: modeValue} case BuiltinSpawn: return TCTypeAndValue{mode: modeVoid} } return TCTypeAndValue{} } func (c *Checker) typeSelector(e *SelectorExpr, scope *Scope) TCTypeAndValue { xt := c.typeExpr(e.X, scope) if c.info != nil { c.info.Types[e.X] = xt } if xt.mode == modePkg { // package.Name if pkgName, ok := e.X.(*Name); ok { _, pkgObj := c.lookup(pkgName.Value, scope) if pn, ok := pkgObj.(*PkgName); ok && pn.imported != nil { obj := pn.imported.scope.Lookup(e.Sel.Value) if obj != nil { if c.info != nil { c.info.Uses[e.Sel] = obj } return TCTypeAndValue{Type: obj.Type(), mode: modeValue} } } } return TCTypeAndValue{} } // field or method lookup typ := xt.Type if typ == nil { return TCTypeAndValue{} } sel := c.lookupFieldOrMethod(typ, e.Sel.Value) if sel != nil { if c.info != nil { c.info.Selections[e] = sel if sel.obj != nil { c.info.Uses[e.Sel] = sel.obj } } return TCTypeAndValue{Type: sel.Type(), mode: modeValue} } return TCTypeAndValue{} } func (c *Checker) typeIndex(e *IndexExpr, scope *Scope) TCTypeAndValue { xt := c.checkExpr(e.X, scope) c.checkExpr(e.Index, scope) if xt == nil { return TCTypeAndValue{} } switch t := xt.Underlying().(type) { case *Array: return TCTypeAndValue{Type: t.elem, mode: modeVar} case *Slice: return TCTypeAndValue{Type: t.elem, mode: modeVar} case *TCMap: return TCTypeAndValue{Type: t.elem, mode: modeValue} } return TCTypeAndValue{} } func (c *Checker) typeSlice(e *SliceExpr, scope *Scope) TCTypeAndValue { xt := c.checkExpr(e.X, scope) for _, idx := range e.Index { if idx != nil { c.checkExpr(idx, scope) } } if xt == nil { return TCTypeAndValue{} } switch t := xt.Underlying().(type) { case *Array: if b, ok := t.elem.(*Basic); ok && b.kind == Uint8 { return TCTypeAndValue{Type: Typ[TCString], mode: modeValue} } return TCTypeAndValue{Type: NewSlice(t.elem), mode: modeValue} case *Slice: return TCTypeAndValue{Type: xt, mode: modeValue} } // string[:] -> string if b, ok := xt.Underlying().(*Basic); ok && b.info&IsString != 0 { return TCTypeAndValue{Type: xt, mode: modeValue} } return TCTypeAndValue{} } func (c *Checker) typeAssert(e *AssertExpr, scope *Scope) TCTypeAndValue { c.checkExpr(e.X, scope) assertedType := c.resolveTypeExpr(e.Type) return TCTypeAndValue{Type: assertedType, mode: modeValue} } func (c *Checker) typeCompositeLit(e *CompositeLit, scope *Scope) TCTypeAndValue { var typ Type if e.Type != nil { typ = c.resolveTypeExpr(e.Type) } // Determine whether keys in key:value pairs are field names (struct) or // expressions (map/slice). Struct field names are not in scope. // Also treat typeless composite literals (e.Type == nil) as structs - // in Go they appear as slice/array element literals where type is inferred. isStruct := e.Type == nil || (typ != nil && isStructType(typ)) // Fallback: if we have key:value elements but couldn't determine the type, // assume struct (struct field names don't exist as scope variables). if !isStruct && len(e.ElemList) > 0 { if _, ok := e.ElemList[0].(*KeyValueExpr); ok { isStruct = true } } for _, el := range e.ElemList { if kv, ok := el.(*KeyValueExpr); ok { if isStruct { // Struct field: only check value, skip key lookup. c.checkExpr(kv.Value, scope) } else { // Map/unknown: check both key and value. c.checkExpr(kv.Key, scope) c.checkExpr(kv.Value, scope) } } else { c.checkExpr(el, scope) } } return TCTypeAndValue{Type: typ, mode: modeValue} } // isStructType returns true if t is (or is a Named wrapping) a struct. // Named types with nil underlying (unresolved) are treated as structs // because composite literals with key:value syntax on a Named type are // always struct literals in Moxie code - map literals use an explicit // map[K]V type, never a Named alias at this level. func isStructType(t Type) bool { if t == nil { return false } switch t := t.(type) { case *TCStruct: return true case *Named: if t.underlying == nil { return true // optimistic: Named with key:value syntax = struct } return isStructType(t.underlying) } return false } func (c *Checker) typeFuncLit(e *FuncLit, scope *Scope) TCTypeAndValue { sig := c.resolveFuncType(e.Type, nil) inner := c.openScope(e.Body, scope) if sig != nil { if sig.params != nil { for i := 0; i < sig.params.Len(); i++ { p := sig.params.At(i) if p.name != "" { inner.Insert(p) } } } if sig.results != nil { for i := 0; i < sig.results.Len(); i++ { r := sig.results.At(i) if r.name != "" { inner.Insert(r) } } } } c.checkBlock(e.Body, inner) return TCTypeAndValue{Type: sig, mode: modeValue} } // lookupFieldOrMethod finds a field or method named name on type t. func (c *Checker) lookupFieldOrMethod(t Type, name string) *Selection { if t == nil { return nil } // dereference pointer indirect := false if pt, ok := safeUnderlying(t).(*Pointer); ok { t = pt.base indirect = true } switch t := safeUnderlying(t).(type) { case *TCStruct: for i, f := range t.fields { if f.name == name { return &Selection{ kind: FieldVal, recv: t, obj: f, index: []int{i}, indir: indirect, } } } case *TCInterface: for _, m := range t.allMethods { if m.name == name { fn := NewTCFunc(nil, name, m.sig) return &Selection{kind: MethodVal, recv: t, obj: fn, indir: indirect} } } case *Named: for _, m := range t.methods { if m.name == name { return &Selection{kind: MethodVal, recv: t, obj: m, indir: indirect} } } if t.underlying != nil { return c.lookupFieldOrMethod(t.underlying, name) } } return nil }