// Package typecheck implements a Moxie-native type checker that consumes // syntax.File AST nodes directly. It replaces go/types in the compiler // pipeline once B3 (backend rewrite) is complete. // // Type system differences from Go: // - string and []byte are the same type (Moxie text unification) // - int32 = int32, uint32 = uint32 on all targets // - no complex64/complex128 // - no goroutines (go keyword is a compile error) // - spawn is a builtin with Codec constraints (not a type-level concept) package types import ( "bytes" "git.smesh.lol/moxie/pkg/token" ) // Type is already defined in nodes.mx (syntax.Type interface). // ---------------------------------------------------------------------------- // BasicKind type BasicKind int32 const ( Invalid BasicKind = iota // boolean Bool // integer Int8 Int16 Int32 Int64 Uint8 Uint16 Uint32 Uint64 // float Float32 Float64 // text - string and []byte are the same underlying kind in Moxie TCString // unsafe UnsafePointer // untyped constants UntypedBool UntypedInt UntypedRune UntypedFloat UntypedString UntypedNil ) // BasicInfo is a bitmask of type properties. type BasicInfo uint32 const ( IsBoolean BasicInfo = 1 << iota IsInteger // includes unsigned IsUnsigned // only for unsigned integer kinds IsFloat IsString IsUntyped IsOrdered // ordered by < (integers, floats, strings) IsNumeric // integers + floats ) // Basic is an elementary type (bool, int32, string, etc.). type Basic struct { Kind BasicKind Info BasicInfo Name string Methods []*TCFunc } func (t *Basic) Underlying() (tv Type) { return t } func (t *Basic) String() (s string) { return t.Name } func (t *Basic) NumMethods() (n int32) { return len(t.Methods) } func (t *Basic) Method(i int32) (tv *TCFunc) { return t.Methods[i] } func (t *Basic) AddMethod(m *TCFunc) { for i := int32(0); i < len(t.Methods); i++ { if t.Methods[i].Name == m.Name { t.Methods[i] = m return } } if len(t.Methods) >= cap(t.Methods) { old := t.Methods t.Methods = []*TCFunc{:len(old):len(old) + 4} copy(t.Methods, old) } push(t.Methods, m) } func (t *Named) AddMethod(m *TCFunc) { for i := int32(0); i < len(t.Methods); i++ { if t.Methods[i].Name == m.Name { t.Methods[i] = m return } } n := len(t.Methods) if n >= cap(t.Methods) { old := t.Methods t.Methods = []*TCFunc{:n:n + 4} copy(t.Methods, old) } t.Methods = t.Methods[:n+1] t.Methods[n] = m } func UntypedToTyped(b *Basic) (t Type) { switch b.Kind { case UntypedBool: return Typ[Bool] case UntypedInt: return Typ[Int64] case UntypedRune: if Universe != nil { obj := Universe.Lookup("rune") if obj != nil { if tn, ok := obj.(*TypeName); ok { return tn.Typ } } } return Typ[Int32] case UntypedFloat: return Typ[Float64] case UntypedString: return Typ[TCString] } return nil } // ---------------------------------------------------------------------------- // Array type Array struct { Len int64 Elem Type } func NewArray(elem Type, n int64) (a *Array) { a = (&Array{}); a.Len = n; a.Elem = elem; return a } func (t *Array) Underlying() (tv Type) { return t } func (t *Array) String() (s string) { return "[" | token.Itoa64(t.Len) | "]" | t.Elem.String() } // ---------------------------------------------------------------------------- // Slice type Slice struct{ Elem Type } func NewSlice(elem Type) (s *Slice) { s = (&Slice{}); s.Elem = elem; return s } func (t *Slice) Underlying() (tv Type) { return t } func (t *Slice) String() (s string) { return "[]" | t.Elem.String() } // ---------------------------------------------------------------------------- // Pointer type Pointer struct{ Base Type } func NewPointer(base Type) (p *Pointer) { p = (&Pointer{}); p.Base = base; return p } func (t *Pointer) Elem() (tv Type) { return t.Base } func (t *Pointer) Underlying() (tv Type) { return t } func (t *Pointer) String() (s string) { return "*" | t.Base.String() } // ---------------------------------------------------------------------------- // Map type TCMap struct{ Key, Elem Type } func NewTCMap(key, elem Type) (t *TCMap) { t = (&TCMap{}); t.Key = key; t.Elem = elem; return t } func (t *TCMap) Underlying() (tv Type) { return t } func (t *TCMap) String() (s string) { return "map[" | t.Key.String() | "]" | t.Elem.String() } // ---------------------------------------------------------------------------- // Chan type TCChanDir int32 const ( TCSendRecv TCChanDir = iota TCSendOnly TCRecvOnly ) type TCChan struct { Dir TCChanDir Elem Type } func NewTCChan(dir TCChanDir, elem Type) (t *TCChan) { t = (&TCChan{}); t.Dir = dir; t.Elem = elem; return t } func (t *TCChan) Underlying() (tv Type) { return t } func (t *TCChan) String() (s string) { switch t.Dir { case TCSendOnly: return "chan<- " | t.Elem.String() case TCRecvOnly: return "<-chan " | t.Elem.String() } return "chan " | t.Elem.String() } // ---------------------------------------------------------------------------- // Var (a field or variable, used in Struct/Signature/Tuple) type TCVar struct { Pkg *TCPackage Name string Typ Type Anonymous bool // embedded field (no explicit name) Used bool Pos interface{} // Pos - stored as interface to avoid import cycle } func NewTCVar(pkg *TCPackage, name string, typ Type) (t *TCVar) { t = (&TCVar{}); t.Pkg = pkg; t.Name = name; t.Typ = typ; return t } func NewTCField(pkg *TCPackage, name string, typ Type, anonymous bool) (t *TCVar) { t = (&TCVar{}); t.Pkg = pkg; t.Name = name; t.Typ = typ; t.Anonymous = anonymous; return t } func (v *TCVar) Exported() (ok bool) { return len(v.Name) > 0 && v.Name[0] >= 'A' && v.Name[0] <= 'Z' } func (v *TCVar) objectTag() {} func (v *TCVar) String() (s string) { return v.Name } // ---------------------------------------------------------------------------- // Struct type TCStruct struct { Fields []*TCVar Tags []string } func NewTCStruct(fields []*TCVar, tags []string) (t *TCStruct) { t = (&TCStruct{}); t.Fields = fields; t.Tags = tags; return t } func (t *TCStruct) NumFields() (n int32) { if t == nil { return 0 } return len(t.Fields) } func (t *TCStruct) Field(i int32) (tv *TCVar) { if t == nil { return nil } return t.Fields[i] } func (t *TCStruct) Tag(i int32) (s string) { if i < len(t.Tags) { return t.Tags[i] } return "" } func (t *TCStruct) Underlying() (tv Type) { return t } func (t *TCStruct) String() (s string) { var sb bytes.Buffer sb.WriteString("struct{") for i, f := range t.Fields { if i > 0 { sb.WriteString("; ") } if !f.Anonymous { sb.WriteString(f.Name) sb.WriteByte(' ') } sb.WriteString(f.Typ.String()) if tag := t.Tag(i); tag != "" { sb.WriteString(" \"") sb.WriteString(tag) sb.WriteByte('"') } } sb.WriteByte('}') return sb.String() } // ---------------------------------------------------------------------------- // Tuple (multi-return, parameter list) type Tuple struct{ Vars []*TCVar } func NewTuple(vars ...*TCVar) (t *Tuple) { t = (&Tuple{}); t.Vars = vars; return t } func (t *Tuple) Len() (n int32) { return len(t.Vars) } func (t *Tuple) At(i int32) (tv *TCVar) { return t.Vars[i] } func (t *Tuple) Underlying() (tv Type) { return t } func (t *Tuple) String() (s string) { if t == nil || len(t.Vars) == 0 { return "()" } var sb bytes.Buffer sb.WriteByte('(') for i, v := range t.Vars { if i > 0 { sb.WriteString(", ") } sb.WriteString(v.Typ.String()) } sb.WriteByte(')') return sb.String() } // ---------------------------------------------------------------------------- // Signature (function type) type Signature struct { Recv *TCVar // nil for plain functions Params *Tuple Results *Tuple Variadic bool } func NewSignature(recv *TCVar, params, results *Tuple, variadic bool) (s *Signature) { s = (&Signature{}); s.Recv = recv; s.Params = params; s.Results = results; s.Variadic = variadic; return s } func (t *Signature) Underlying() (tv Type) { return t } func (t *Signature) String() (s string) { var sb bytes.Buffer sb.WriteString("func") if t.Params != nil { writeParams(&sb, t.Params, t.Variadic) } else { sb.WriteString("()") } if t.Results != nil && t.Results.Len() > 0 { sb.WriteByte(' ') if t.Results.Len() == 1 && t.Results.At(0).Name == "" { sb.WriteString(t.Results.At(0).Typ.String()) } else { writeParams(&sb, t.Results, false) } } return sb.String() } func writeParams(sb *bytes.Buffer, t *Tuple, variadic bool) { sb.WriteByte('(') for i, v := range t.Vars { if i > 0 { sb.WriteString(", ") } if v.Name != "" { sb.WriteString(v.Name) sb.WriteByte(' ') } if variadic && i == len(t.Vars)-1 { if sl, ok := v.Typ.(*Slice); ok { sb.WriteString("...") sb.WriteString(sl.Elem.String()) continue } } sb.WriteString(v.Typ.String()) } sb.WriteByte(')') } // ---------------------------------------------------------------------------- // Interface // IfaceMethod holds a method signature for an interface. type IfaceMethod struct { Name string Sig *Signature } // IfaceMethod constructor used by the bridge package. func NewTCIfaceMethod(name string, sig *Signature) (i *IfaceMethod) { i = (&IfaceMethod{}); i.Name = name; i.Sig = sig; return i } type TCInterface struct { XMethods []*IfaceMethod Embeds []Type Completed bool AllMethods []*IfaceMethod } func NewTCInterface(methods []*IfaceMethod, embeds []Type) (t *TCInterface) { t = (&TCInterface{}); t.XMethods = methods; t.Embeds = embeds; return t } func (t *TCInterface) NumMethods() (n int32) { return len(t.AllMethods) } func (t *TCInterface) Method(i int32) (i2 *IfaceMethod) { return t.AllMethods[i] } func (t *TCInterface) NumExplicitMethods() (n int32) { return len(t.XMethods) } func (t *TCInterface) ExplicitMethod(i int32) (i2 *IfaceMethod) { return t.XMethods[i] } func (t *TCInterface) NumEmbeddeds() (n int32) { return len(t.Embeds) } func (t *TCInterface) EmbeddedType(i int32) (tv Type) { return t.Embeds[i] } func (t *TCInterface) IsEmpty() (ok bool) { return len(t.AllMethods) == 0 } func (t *TCInterface) Underlying() (tv Type) { return t } func (t *TCInterface) String() (s string) { if t.IsEmpty() { return "interface{}" } var sb bytes.Buffer sb.WriteString("interface{") for i, m := range t.AllMethods { if i > 0 { sb.WriteString("; ") } sb.WriteString(m.Name) sig := m.Sig if sig.Params != nil { writeParams(&sb, sig.Params, sig.Variadic) } else { sb.WriteString("()") } if sig.Results != nil && sig.Results.Len() > 0 { sb.WriteByte(' ') if sig.Results.Len() == 1 { sb.WriteString(sig.Results.At(0).Typ.String()) } else { writeParams(&sb, sig.Results, false) } } } sb.WriteByte('}') return sb.String() } // Complete fills AllMethods from XMethods + Embeds (called once after all types are resolved). func (t *TCInterface) Complete() { if t.Completed { return } seen := map[string]bool{} t.AllMethods = t.AllMethods | t.XMethods for _, m := range t.XMethods { seen[m.Name] = true } for _, embed := range t.Embeds { if embed == nil { continue } u := SafeUnderlying(embed) if u == nil { continue } if iface, ok := u.(*TCInterface); ok { iface.Complete() for _, m := range iface.AllMethods { if !seen[m.Name] { push(t.AllMethods, m) seen[m.Name] = true } } } } t.Completed = true } // ---------------------------------------------------------------------------- // TypeParam (generics) type TypeParam struct { Id int32 Obj *TypeName Constraint Type } func NewTypeParam(obj *TypeName, constraint Type) (t *TypeParam) { t = (&TypeParam{}); t.Obj = obj; t.Constraint = constraint; return t } func (t *TypeParam) Underlying() (tv Type) { return t } func (t *TypeParam) String() (s string) { if t.Obj != nil { return t.Obj.Name } return "T" | token.Itoa(t.Id) } // ---------------------------------------------------------------------------- // Named (named types: type Foo struct{...}) type Named struct { Obj *TypeName // the type name declaration Under Type // the underlying type Methods []*TCFunc // methods with this type as receiver TParams []*TypeParam TArgs []Type // set when instantiated } func NewNamed(obj *TypeName, underlying Type) (n *Named) { n := (&Named{}) n.Obj = obj; n.Under = underlying if obj != nil { obj.Typ = n } return n } func (t *Named) NumMethods() (n int32) { return len(t.Methods) } func (t *Named) Method(i int32) (tv *TCFunc) { return t.Methods[i] } func (t *Named) Underlying() (tv Type) { if t == nil { return nil } cur := Type(t) for depth := 0; depth < 20; depth++ { n, ok := cur.(*Named) if !ok || n == nil { return cur } if n.Under == nil || n.Under == n { return n } cur = n.Under } return cur } func (t *Named) String() (s string) { if t == nil { return "" } if t.Obj != nil { if t.Obj.Pkg != nil { return t.Obj.Pkg.Path | "." | t.Obj.Name } return t.Obj.Name } return "" } func SafeUnderlying(t Type) (tv Type) { if t == nil { return nil } if n, ok := t.(*Named); ok { if n == nil { return nil } u := n.Underlying() if u != nil && u != Type(n) { return u } if n.Obj != nil && n.Obj.Pkg != nil && ImportRegistry != nil { pkg := ImportRegistry[n.Obj.Pkg.Path] if pkg != nil && pkg.Scope != nil { obj := pkg.Scope.Lookup(n.Obj.Name) if obj != nil { ot := ObjectType(obj) if ot != nil && ot != Type(n) { return SafeUnderlying(ot) } } } } return n } return t.Underlying() }