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