tc_expr.mx raw

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