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