package types import ( "slices" "strconv" "strings" "github.com/marzeq/qk/shared" ) type Type interface { Equals(Type) bool CanCoerceTo(Type) bool CanCastTo(Type) bool String() string } // DetachForSerialization returns an equivalent type graph without runtime-only // recursive AliasRef target pointers. Stable module/name references are enough // to reconnect aliases when a module interface is loaded. func DetachForSerialization(value Type) Type { if value == nil { return nil } switch value := value.(type) { case *AliasRef: return &AliasRef{Module: value.Module, Name: value.Name} case TypeParameter: value.Constraint = DetachForSerialization(value.Constraint) return value case DefinedType: value.Underlying = DetachForSerialization(value.Underlying) value.TypeArguments = detachTypes(value.TypeArguments) return value case PointerType: value.Base = DetachForSerialization(value.Base) return value case SliceType: value.Base = DetachForSerialization(value.Base) return value case ArrayType: value.Base = DetachForSerialization(value.Base) return value case SequenceType: value.Base = DetachForSerialization(value.Base) return value case StructType: value.Fields = detachFields(value.Fields) if value.TaggedUnion != nil { info := *value.TaggedUnion info.Tag = DetachForSerialization(info.Tag) info.Variants = append([]TaggedUnionVariant(nil), info.Variants...) for index := range info.Variants { info.Variants[index].Fields = detachFields(info.Variants[index].Fields) } value.TaggedUnion = &info } return value case UnionType: value.Fields = detachFields(value.Fields) return value case TraitType: value.Methods = append([]TraitMethod(nil), value.Methods...) for index := range value.Methods { value.Methods[index].Parameters = detachTypes(value.Methods[index].Parameters) value.Methods[index].ReturnType = DetachForSerialization(value.Methods[index].ReturnType) value.Methods[index].GenericParameters = append([]TypeParameter(nil), value.Methods[index].GenericParameters...) for parameter := range value.Methods[index].GenericParameters { constraint := value.Methods[index].GenericParameters[parameter].Constraint value.Methods[index].GenericParameters[parameter].Constraint = DetachForSerialization(constraint) } } return value case TraitPointerType: value.Trait = DetachForSerialization(value.Trait).(TraitType) return value case FunctionType: value.Parameters = detachTypes(value.Parameters) value.ReturnType = DetachForSerialization(value.ReturnType) value.VariadicElement = DetachForSerialization(value.VariadicElement) return value case MultipleReturnType: value.Types = detachTypes(value.Types) return value default: return value } } // SubstituteParameters replaces declaration-scoped generic parameters in a // detached type graph. Bindings are keyed by TypeParameter.Key(). func SubstituteParameters(value Type, bindings map[string]Type) Type { if value == nil { return nil } if parameter, ok := value.(TypeParameter); ok { if replacement := bindings[parameter.Key()]; replacement != nil { return replacement } parameter.Constraint = SubstituteParameters(parameter.Constraint, bindings) return parameter } detached := DetachForSerialization(value) switch value := detached.(type) { case DefinedType: value.Underlying = SubstituteParameters(value.Underlying, bindings) value.TypeArguments = substituteTypes(value.TypeArguments, bindings) return value case PointerType: value.Base = SubstituteParameters(value.Base, bindings) return value case SliceType: value.Base = SubstituteParameters(value.Base, bindings) return value case ArrayType: value.Base = SubstituteParameters(value.Base, bindings) return value case SequenceType: value.Base = SubstituteParameters(value.Base, bindings) return value case StructType: value.Fields = substituteFields(value.Fields, bindings) if value.TaggedUnion != nil { value.TaggedUnion.Tag = SubstituteParameters(value.TaggedUnion.Tag, bindings) for index := range value.TaggedUnion.Variants { value.TaggedUnion.Variants[index].Fields = substituteFields(value.TaggedUnion.Variants[index].Fields, bindings) } } return value case UnionType: value.Fields = substituteFields(value.Fields, bindings) return value case TraitType: for index := range value.Methods { value.Methods[index].Parameters = substituteTypes(value.Methods[index].Parameters, bindings) value.Methods[index].ReturnType = SubstituteParameters(value.Methods[index].ReturnType, bindings) } return value case TraitPointerType: value.Trait = SubstituteParameters(value.Trait, bindings).(TraitType) return value case FunctionType: value.Parameters = substituteTypes(value.Parameters, bindings) value.ReturnType = SubstituteParameters(value.ReturnType, bindings) value.VariadicElement = SubstituteParameters(value.VariadicElement, bindings) return value case MultipleReturnType: value.Types = substituteTypes(value.Types, bindings) return value default: return value } } func substituteTypes(values []Type, bindings map[string]Type) []Type { result := make([]Type, len(values)) for index, value := range values { result[index] = SubstituteParameters(value, bindings) } return result } func substituteFields(values []shared.Pair[string, Type], bindings map[string]Type) []shared.Pair[string, Type] { result := append([]shared.Pair[string, Type](nil), values...) for index := range result { result[index].R = SubstituteParameters(result[index].R, bindings) } return result } func detachTypes(values []Type) []Type { result := make([]Type, len(values)) for index, value := range values { result[index] = DetachForSerialization(value) } return result } func detachFields(values []shared.Pair[string, Type]) []shared.Pair[string, Type] { result := append([]shared.Pair[string, Type](nil), values...) for index := range result { result[index].R = DetachForSerialization(result[index].R) } return result } // SelfType is the implementing type of the immediately enclosing trait. It // remains symbolic until a structural-conformance check selects a concrete // candidate. type SelfType struct{} func (SelfType) Equals(other Type) bool { _, ok := other.(SelfType); return ok } func (SelfType) CanCoerceTo(other Type) bool { return SelfType{}.Equals(other) } func (SelfType) CanCastTo(other Type) bool { return SelfType{}.Equals(other) } func (SelfType) String() string { return "Self" } // SubstituteSelf replaces a trait's symbolic Self type throughout one method // parameter or result type. Nested trait definitions introduce their own Self // and are therefore substitution boundaries. func SubstituteSelf(t Type, replacement Type) Type { if t == nil { return nil } switch t := t.(type) { case SelfType: return replacement case DefinedType: t.Underlying = SubstituteSelf(t.Underlying, replacement) t.TypeArguments = append([]Type(nil), t.TypeArguments...) for i := range t.TypeArguments { t.TypeArguments[i] = SubstituteSelf(t.TypeArguments[i], replacement) } return t case *AliasRef: return t case PointerType: t.Base = SubstituteSelf(t.Base, replacement) return t case SliceType: t.Base = SubstituteSelf(t.Base, replacement) return t case ArrayType: t.Base = SubstituteSelf(t.Base, replacement) return t case StructType: t.Fields = append([]shared.Pair[string, Type](nil), t.Fields...) for i := range t.Fields { t.Fields[i].R = SubstituteSelf(t.Fields[i].R, replacement) } if t.TaggedUnion != nil { info := *t.TaggedUnion info.Tag = SubstituteSelf(info.Tag, replacement) info.Variants = append([]TaggedUnionVariant(nil), info.Variants...) for i := range info.Variants { info.Variants[i].Fields = append([]shared.Pair[string, Type](nil), info.Variants[i].Fields...) for j := range info.Variants[i].Fields { info.Variants[i].Fields[j].R = SubstituteSelf(info.Variants[i].Fields[j].R, replacement) } } t.TaggedUnion = &info } return t case UnionType: t.Fields = append([]shared.Pair[string, Type](nil), t.Fields...) for i := range t.Fields { t.Fields[i].R = SubstituteSelf(t.Fields[i].R, replacement) } return t case FunctionType: t.Parameters = append([]Type(nil), t.Parameters...) for i := range t.Parameters { t.Parameters[i] = SubstituteSelf(t.Parameters[i], replacement) } t.ReturnType = SubstituteSelf(t.ReturnType, replacement) t.VariadicElement = SubstituteSelf(t.VariadicElement, replacement) return t case MultipleReturnType: t.Types = append([]Type(nil), t.Types...) for i := range t.Types { t.Types[i] = SubstituteSelf(t.Types[i], replacement) } return t default: return t } } // HasSelfType reports whether a type depends on its enclosing trait's concrete // implementer. Referenced and nested traits own their own Self placeholders. func HasSelfType(t Type) bool { switch t := t.(type) { case SelfType: return true case DefinedType: return slices.ContainsFunc(t.TypeArguments, HasSelfType) case PointerType: return HasSelfType(t.Base) case SliceType: return HasSelfType(t.Base) case ArrayType: return HasSelfType(t.Base) case StructType: for _, field := range t.Fields { if HasSelfType(field.R) { return true } } case UnionType: for _, field := range t.Fields { if HasSelfType(field.R) { return true } } case FunctionType: return slices.ContainsFunc(t.Parameters, HasSelfType) || HasSelfType(t.ReturnType) case MultipleReturnType: return slices.ContainsFunc(t.Types, HasSelfType) } return false } // TypeParameter is a declaration-scoped placeholder used only while describing // a generic binding. Owner makes equally named parameters from different // declarations distinct. type TypeParameter struct { Owner string Name string Index int Constraint Type } func (p TypeParameter) Equals(other Type) bool { o, ok := other.(TypeParameter) return ok && p.Owner == o.Owner && p.Index == o.Index } func (p TypeParameter) CanCoerceTo(other Type) bool { return p.Equals(other) } func (p TypeParameter) CanCastTo(other Type) bool { return p.Equals(other) } func (p TypeParameter) String() string { return p.Name } func (p TypeParameter) Key() string { return p.Owner + "#" + strconv.Itoa(p.Index) } func HasTypeParameter(t Type) bool { switch t := t.(type) { case TypeParameter: return true case DefinedType: return slices.ContainsFunc(t.TypeArguments, HasTypeParameter) case PointerType: return HasTypeParameter(t.Base) case SliceType: return HasTypeParameter(t.Base) case ArrayType: return HasTypeParameter(t.Base) case StructType: for _, field := range t.Fields { if HasTypeParameter(field.R) { return true } } case UnionType: for _, field := range t.Fields { if HasTypeParameter(field.R) { return true } } case FunctionType: return slices.ContainsFunc(t.Parameters, HasTypeParameter) || HasTypeParameter(t.ReturnType) case MultipleReturnType: return slices.ContainsFunc(t.Types, HasTypeParameter) } return false } // NoInitializerType is a contextual marker used only while validating `---`. type NoInitializerType struct{} func (NoInitializerType) Equals(other Type) bool { _, ok := other.(NoInitializerType); return ok } func (NoInitializerType) CanCoerceTo(Type) bool { return false } func (NoInitializerType) CanCastTo(Type) bool { return false } func (NoInitializerType) String() string { return "" } // DefinedType is a nominal user-defined type. Its underlying type determines // representation and explicit cast compatibility, but never implicit coercion. type DefinedType struct { Module string Name string Underlying Type GenericName string TypeArguments []Type } // StrType returns the nominal builtin string type. Its representation is a // dynamic byte slice, but it is intentionally distinct from []u8. func StrType() DefinedType { return DefinedType{ Name: "str", Underlying: SliceType{Base: PrimitiveU8}, } } // AliasRef represents a recursive reference to a named type while that type is // being resolved. It is only constructed for cycles behind pointer indirection. type AliasRef struct { Module string Name string Target *Type } func (r *AliasRef) Equals(other Type) bool { if o, ok := other.(*AliasRef); ok { if r.Module == o.Module && r.Name == o.Name { return true } } if d, ok := other.(DefinedType); ok { if r.Module == d.Module && r.Name == d.Name { return true } } // Recursive resolution can leave a provisional reference to a transparent // alias in an already-built aggregate. Once the alias target is available, // compare through it. Nominal recursive types remain distinct because their // target is the nominal DefinedType rather than its underlying structure. if r.Target != nil && *r.Target != nil { target := *r.Target if reference, same := target.(*AliasRef); !same || reference != r { return target.Equals(other) } } return false } func (r *AliasRef) CanCoerceTo(other Type) bool { return r.Equals(other) } func (r *AliasRef) CanCastTo(other Type) bool { return castCompatible(r, other) } func (r *AliasRef) String() string { if r.Module == "" { return r.Name } return r.Module + "." + r.Name } func (d DefinedType) Equals(other Type) bool { switch o := other.(type) { case DefinedType: return d.Module == o.Module && d.Name == o.Name case *AliasRef: return o.Equals(d) default: return false } } func (d DefinedType) CanCoerceTo(other Type) bool { return d.Equals(other) } func (d DefinedType) CanCastTo(other Type) bool { return castCompatible(d.Underlying, Underlying(other)) } func (d DefinedType) String() string { if d.Module == "" { return d.Name } return d.Module + "." + d.Name } func Underlying(t Type) Type { for { switch d := t.(type) { case DefinedType: t = d.Underlying case *AliasRef: if d.Target == nil || *d.Target == nil { return d } t = *d.Target default: return t } } } // Substitute replaces declaration-scoped type parameters throughout a type. func Substitute(t Type, arguments map[string]Type) Type { if t == nil { return nil } switch t := t.(type) { case TypeParameter: if replacement, ok := arguments[t.Key()]; ok { return replacement } return t case DefinedType: t.Underlying = Substitute(t.Underlying, arguments) t.TypeArguments = append([]Type(nil), t.TypeArguments...) for i := range t.TypeArguments { t.TypeArguments[i] = Substitute(t.TypeArguments[i], arguments) } return t case *AliasRef: return t case PointerType: t.Base = Substitute(t.Base, arguments) return t case SliceType: t.Base = Substitute(t.Base, arguments) return t case ArrayType: t.Base = Substitute(t.Base, arguments) return t case StructType: t.Fields = append([]shared.Pair[string, Type](nil), t.Fields...) for i := range t.Fields { t.Fields[i].R = Substitute(t.Fields[i].R, arguments) } if t.TaggedUnion != nil { info := *t.TaggedUnion info.Tag = Substitute(info.Tag, arguments) info.Variants = append([]TaggedUnionVariant(nil), info.Variants...) for i := range info.Variants { info.Variants[i].Fields = append([]shared.Pair[string, Type](nil), info.Variants[i].Fields...) for j := range info.Variants[i].Fields { info.Variants[i].Fields[j].R = Substitute(info.Variants[i].Fields[j].R, arguments) } } t.TaggedUnion = &info } return t case UnionType: t.Fields = append([]shared.Pair[string, Type](nil), t.Fields...) for i := range t.Fields { t.Fields[i].R = Substitute(t.Fields[i].R, arguments) } return t case FunctionType: t.Parameters = append([]Type(nil), t.Parameters...) for i := range t.Parameters { t.Parameters[i] = Substitute(t.Parameters[i], arguments) } t.ReturnType = Substitute(t.ReturnType, arguments) t.VariadicElement = Substitute(t.VariadicElement, arguments) return t case MultipleReturnType: t.Types = append([]Type(nil), t.Types...) for i := range t.Types { t.Types[i] = Substitute(t.Types[i], arguments) } return t case TraitPointerType: substituted := Substitute(t.Trait, arguments) if trait, ok := substituted.(TraitType); ok { t.Trait = trait } return t case TraitType: t.Methods = append([]TraitMethod(nil), t.Methods...) for i := range t.Methods { t.Methods[i].GenericParameters = append([]TypeParameter(nil), t.Methods[i].GenericParameters...) t.Methods[i].Parameters = append([]Type(nil), t.Methods[i].Parameters...) for j := range t.Methods[i].GenericParameters { t.Methods[i].GenericParameters[j].Constraint = Substitute( t.Methods[i].GenericParameters[j].Constraint, arguments, ) } for j := range t.Methods[i].Parameters { t.Methods[i].Parameters[j] = Substitute(t.Methods[i].Parameters[j], arguments) } t.Methods[i].ReturnType = Substitute(t.Methods[i].ReturnType, arguments) } return t default: return t } } // Identity returns a deterministic, declaration-sensitive encoding suitable // for specialization caches and symbol mangling. func Identity(t Type) string { if t == nil { return "" } switch t := t.(type) { case SelfType: return "trait-self" case TypeParameter: return "param(" + t.Key() + ")" case DefinedType: if t.GenericName != "" { arguments := make([]string, len(t.TypeArguments)) for i, argument := range t.TypeArguments { arguments[i] = Identity(argument) } return "defined(" + t.Module + ":" + t.GenericName + "<" + strings.Join(arguments, ",") + ">)" } return "defined(" + t.Module + ":" + t.Name + ")" case *AliasRef: return "alias(" + t.Module + ":" + t.Name + ")" case PointerType: mutable := "" if t.Mutable { mutable = "mut:" } return "ptr(" + mutable + Identity(t.Base) + ")" case SliceType: mutable := "" if t.Mutable { mutable = "mut:" } return "slice(" + mutable + Identity(t.Base) + ")" case ArrayType: return "array(" + strconv.Itoa(t.Length) + ":" + Identity(t.Base) + ")" case FunctionType: parameters := make([]string, len(t.Parameters)) for i, parameter := range t.Parameters { parameters[i] = Identity(parameter) } return "fn(" + strings.Join(parameters, ",") + ")->" + Identity(t.ReturnType) case MultipleReturnType: items := make([]string, len(t.Types)) for i, item := range t.Types { items[i] = Identity(item) } return "multi(" + strings.Join(items, ",") + ")" case PrimitiveType: return "primitive(" + string(t) + ")" case StructType: fields := make([]string, len(t.Fields)) for i, field := range t.Fields { fields[i] = field.L + ":" + Identity(field.R) } packed := "" if t.Packed { packed = "packed:" } return "struct(" + packed + strings.Join(fields, ",") + ")" case UnionType: return "union(" + t.Module + ":" + t.Name + ")" case EnumType: if t.IdentityName != "" { return "enum(" + t.IdentityName + ")" } return "enum(" + t.Module + ":" + t.Name + ")" case FlagsType: return "flags(" + t.String() + ")" case TraitType: return "trait(" + t.String() + ")" case TraitPointerType: mutable := "" if t.Mutable { mutable = "mut:" } return "dyn(" + mutable + Identity(t.Trait) + ")" default: return t.String() } } func castCompatible(from, to Type) bool { from = Underlying(from) to = Underlying(to) if upgradesMutableAccess(from, to) { return false } return from.Equals(to) || from.CanCastTo(to) || to.CanCastTo(from) } func upgradesMutableAccess(from, to Type) bool { switch source := from.(type) { case PointerType: switch target := to.(type) { case PointerType: return target.Mutable && !source.Mutable case SliceType: return target.Mutable && !source.Mutable } case SliceType: switch target := to.(type) { case PointerType: return target.Mutable && !source.Mutable case SliceType: return target.Mutable && !source.Mutable } } return false } func CanExplicitCast(from, to Type) bool { if from.CanCastTo(to) { return true } _, fromDefined := from.(DefinedType) _, toDefined := to.(DefinedType) return (fromDefined || toDefined) && castCompatible(from, to) } type PrimitiveType string const ( PrimitiveI8 PrimitiveType = "i8" PrimitiveI16 PrimitiveType = "i16" PrimitiveI32 PrimitiveType = "i32" PrimitiveI64 PrimitiveType = "i64" PrimitiveU8 PrimitiveType = "u8" PrimitiveU16 PrimitiveType = "u16" PrimitiveU32 PrimitiveType = "u32" PrimitiveU64 PrimitiveType = "u64" PrimitiveF32 PrimitiveType = "f32" PrimitiveF64 PrimitiveType = "f64" PrimitiveIsz PrimitiveType = "isz" PrimitiveUsz PrimitiveType = "usz" PrimitiveVoid PrimitiveType = "void" PrimitiveBool PrimitiveType = "bool" ) func IsSigned(t Type) bool { t = Underlying(t) return t == PrimitiveI8 || t == PrimitiveI16 || t == PrimitiveI32 || t == PrimitiveI64 || t == PrimitiveIsz } func IsUnsigned(t Type) bool { t = Underlying(t) return t == PrimitiveU8 || t == PrimitiveU16 || t == PrimitiveU32 || t == PrimitiveU64 || t == PrimitiveUsz } func IsInteger(t Type) bool { return IsSigned(t) || IsUnsigned(t) || t.Equals(UntypedInt{}) } func IsFloat(t Type) bool { return t == PrimitiveF32 || t == PrimitiveF64 || t.Equals(UntypedFloat{}) } func IsNumeric(t Type) bool { return IsInteger(t) || IsFloat(t) } func IntegerRank(p PrimitiveType) int { switch p { case PrimitiveI8, PrimitiveU8: return 1 case PrimitiveI16, PrimitiveU16: return 2 case PrimitiveI32, PrimitiveU32: return 3 case PrimitiveI64, PrimitiveU64: return 4 case PrimitiveIsz, PrimitiveUsz: return 5 default: return 0 } } func FloatRank(p PrimitiveType) int { switch p { case PrimitiveF32: return 1 case PrimitiveF64: return 2 default: return 0 } } func (p PrimitiveType) Equals(other Type) bool { if otherPrimitive, ok := other.(PrimitiveType); ok { return p == otherPrimitive } return false } func (p PrimitiveType) CanCoerceTo(other Type) bool { if p.Equals(other) { return true } otherPrimitive, ok := other.(PrimitiveType) if !ok { return false } if IsInteger(p) && IsInteger(otherPrimitive) { if p == PrimitiveIsz && IsSigned(otherPrimitive) || otherPrimitive == PrimitiveUsz { return true } if IsSigned(p) && IsSigned(otherPrimitive) { return IntegerRank(p) <= IntegerRank(otherPrimitive) } if IsUnsigned(p) && IsUnsigned(otherPrimitive) { return IntegerRank(p) <= IntegerRank(otherPrimitive) } return false } if IsInteger(p) && IsFloat(otherPrimitive) { return true } if IsFloat(p) && IsFloat(otherPrimitive) { return FloatRank(p) <= FloatRank(otherPrimitive) } return false } func (p PrimitiveType) CanCastTo(other Type) bool { if p.Equals(other) { return true } if IsInteger(p) { if _, ok := other.(PointerType); ok { return true } } otherPrimitive, ok := other.(PrimitiveType) if !ok { return false } if p.Equals(PrimitiveBool) && IsInteger(otherPrimitive) || IsInteger(p) && otherPrimitive.Equals(PrimitiveBool) { return true } if IsNumeric(p) && IsNumeric(otherPrimitive) { return true } return false } func (p PrimitiveType) String() string { return string(p) } type StructType struct { Fields []shared.Pair[string, Type] Packed bool TaggedUnion *TaggedUnionInfo } type TaggedUnionInfo struct { Tag Type Variants []TaggedUnionVariant Auto bool } type TaggedUnionVariant struct { Name string TagValue string Fields []shared.Pair[string, Type] } func TaggedUnion(t Type) (*TaggedUnionInfo, bool) { st, ok := Underlying(t).(StructType) if !ok || st.TaggedUnion == nil { return nil, false } return st.TaggedUnion, true } // TaggedUnionRepr returns the ordinary source-visible struct and union type // that has the same layout as an explicitly tagged union. Auto tags remain // compiler-private and intentionally have no raw representation view. func TaggedUnionRepr(t Type) (StructType, bool) { storage, ok := Underlying(t).(StructType) if !ok || storage.TaggedUnion == nil || storage.TaggedUnion.Auto { return StructType{}, false } payload := make([]shared.Pair[string, Type], 0, len(storage.TaggedUnion.Variants)) for _, variant := range storage.TaggedUnion.Variants { if len(variant.Fields) == 0 { continue } fields := make([]shared.Pair[string, Type], len(variant.Fields)) for i, field := range variant.Fields { name := field.L if _, positional := strconv.Atoi(name); positional == nil { name = "_" + name } fields[i] = shared.Pair[string, Type]{L: name, R: field.R} } payload = append(payload, shared.Pair[string, Type]{ L: variant.Name, R: StructType{Fields: fields}, }) } return StructType{ Packed: storage.Packed, Fields: []shared.Pair[string, Type]{ {L: "tag", R: storage.TaggedUnion.Tag}, {L: "payload", R: UnionType{Fields: payload}}, }, }, true } func (i *TaggedUnionInfo) Variant(name string) (TaggedUnionVariant, int, bool) { if i == nil { return TaggedUnionVariant{}, -1, false } for index, variant := range i.Variants { if variant.Name == name { return variant, index, true } } return TaggedUnionVariant{}, -1, false } type TraitMethod struct { Name string GenericParameters []TypeParameter Receiver TraitReceiverKind Parameters []Type ReturnType Type HasDefault bool } type TraitReceiverKind uint8 const ( TraitReceiverValue TraitReceiverKind = iota TraitReceiverPointer TraitReceiverMutablePointer ) // TraitType is an unsized, nominal set of method requirements. Values exist // only through TraitPointerType descriptors. type TraitType struct { Module string Name string Methods []TraitMethod Any bool } func (t TraitType) DynamicIncompatibility() (method string, reason string, incompatible bool) { for _, method := range t.Methods { if len(method.GenericParameters) != 0 { return method.Name, "is generic", true } if HasSelfType(method.ReturnType) || slices.ContainsFunc(method.Parameters, HasSelfType) { return method.Name, "uses Self outside its receiver", true } } return "", "", false } func (t TraitType) Equals(other Type) bool { o, ok := Underlying(other).(TraitType) return ok && t.Module == o.Module && t.Name == o.Name && t.Any == o.Any } func (t TraitType) CanCoerceTo(other Type) bool { return t.Equals(other) } func (t TraitType) CanCastTo(other Type) bool { return t.Equals(other) } func (t TraitType) String() string { if t.Any { return "Any" } if t.Module == "" { return t.Name } return t.Module + "." + t.Name } // StaticTraitView describes the method surface of a concrete value that was // reinterpreted as a trait without erasing its representation. type StaticTraitView struct { Trait TraitType Access TraitReceiverKind } func (v StaticTraitView) String() string { switch v.Access { case TraitReceiverPointer: return "*" + v.Trait.String() case TraitReceiverMutablePointer: return "*mut " + v.Trait.String() default: return v.Trait.String() } } type TraitPointerType struct { Trait TraitType Mutable bool } func (p TraitPointerType) Equals(other Type) bool { o, ok := other.(TraitPointerType) return ok && p.Mutable == o.Mutable && p.Trait.Equals(o.Trait) } func (p TraitPointerType) CanCoerceTo(other Type) bool { o, ok := other.(TraitPointerType) return ok && (p.Mutable || !o.Mutable) && p.Trait.Equals(o.Trait) } func (p TraitPointerType) CanCastTo(other Type) bool { return p.CanCoerceTo(other) } func (p TraitPointerType) String() string { if p.Mutable { return "*mut dyn " + p.Trait.String() } return "*dyn " + p.Trait.String() } // OpaqueType is an incomplete type with no known value representation. It is // used as the underlying type of a nominal DefinedType and may only be used // behind pointer indirection. type OpaqueType struct{} func (OpaqueType) Equals(other Type) bool { _, ok := other.(OpaqueType); return ok } func (o OpaqueType) CanCoerceTo(other Type) bool { return o.Equals(other) } func (o OpaqueType) CanCastTo(other Type) bool { return o.Equals(other) } func (OpaqueType) String() string { return "opaque" } func IsOpaque(t Type) bool { _, ok := Underlying(t).(OpaqueType) return ok } // IsComplete reports whether a type has a known by-value representation. // Pointer representation never depends on the completeness of its base type. func IsComplete(t Type) bool { return isComplete(t, make(map[string]bool), make(map[*AliasRef]bool)) } func isComplete(t Type, defined map[string]bool, aliases map[*AliasRef]bool) bool { switch t := t.(type) { case DefinedType: key := Identity(t) if defined[key] { return false } defined[key] = true complete := isComplete(t.Underlying, defined, aliases) delete(defined, key) return complete case *AliasRef: if aliases[t] || t.Target == nil || *t.Target == nil { return false } aliases[t] = true complete := isComplete(*t.Target, defined, aliases) delete(aliases, t) return complete case OpaqueType: return false case TraitType: return false case SelfType: return false case StructType: for _, field := range t.Fields { if !isComplete(field.R, defined, aliases) { return false } } case UnionType: for _, field := range t.Fields { if !isComplete(field.R, defined, aliases) { return false } } case SliceType: return isComplete(t.Base, defined, aliases) case ArrayType: return isComplete(t.Base, defined, aliases) case MultipleReturnType: for _, item := range t.Types { if !isComplete(item, defined, aliases) { return false } } } return true } type EnumType struct { Module string Name string IdentityName string Variants []string Values []string } type FlagsType struct { Underlying PrimitiveType Variants []string Values []string } func (f FlagsType) Equals(other Type) bool { o, ok := other.(FlagsType) return ok && f.Underlying == o.Underlying && slices.Equal(f.Variants, o.Variants) && slices.Equal(f.Values, o.Values) } func (f FlagsType) CanCoerceTo(other Type) bool { return f.Equals(other) } func (f FlagsType) CanCastTo(other Type) bool { return other.Equals(f.Underlying) || f.Equals(other) } func (f FlagsType) String() string { return "flags(" + f.Underlying.String() + ")" } func (f FlagsType) VariantValue(name string) (string, bool) { for i, variant := range f.Variants { if variant == name { return f.Values[i], true } } return "", false } type UnionType struct { Module string Name string Fields []shared.Pair[string, Type] } func (u UnionType) Equals(other Type) bool { o, ok := other.(UnionType) return ok && u.Module == o.Module && u.Name == o.Name } func (u UnionType) CanCoerceTo(other Type) bool { return u.Equals(other) } func (u UnionType) CanCastTo(other Type) bool { return u.Equals(other) } func (u UnionType) String() string { if u.Name != "" { if u.Module == "" { return u.Name } return u.Module + "." + u.Name } var result strings.Builder result.WriteString("union { ") writeFields(&result, u.Fields) result.WriteString(" }") return result.String() } func (e EnumType) Equals(other Type) bool { o, ok := other.(EnumType) if !ok { return false } if e.IdentityName != "" || o.IdentityName != "" { return e.IdentityName != "" && e.IdentityName == o.IdentityName } return e.Module == o.Module && e.Name == o.Name } func (e EnumType) CanCoerceTo(other Type) bool { return e.Equals(other) } func (e EnumType) CanCastTo(other Type) bool { return e.Equals(other) } func (e EnumType) String() string { if e.Name != "" { if e.Module == "" { return e.Name } return e.Module + "." + e.Name } return "enum { " + strings.Join(e.Variants, ", ") + " }" } func (e EnumType) VariantValue(name string) (string, bool) { for i, variant := range e.Variants { if variant == name { return e.Values[i], true } } return "", false } func (s StructType) Equals(other Type) bool { otherStruct, ok := other.(StructType) if !ok { return false } if s.Packed != otherStruct.Packed { return false } if len(s.Fields) != len(otherStruct.Fields) { return false } for i, field := range s.Fields { otherField := otherStruct.Fields[i] if field.L != otherField.L || !field.R.Equals(otherField.R) { return false } } return true } func (s StructType) CanCoerceTo(other Type) bool { return s.Equals(other) } func (s StructType) CanCastTo(other Type) bool { return s.CanCoerceTo(other) } func (s StructType) String() string { var result strings.Builder result.WriteString("struct ") if s.Packed { result.WriteString("@packed ") } result.WriteString("{ ") writeFields(&result, s.Fields) result.WriteString(" }") return result.String() } func writeFields(result *strings.Builder, fields []shared.Pair[string, Type]) { for i, field := range fields { if field.L != "" { result.WriteString(field.L) result.WriteString(": ") } result.WriteString(field.R.String()) if i < len(fields)-1 { result.WriteString(", ") } } } type PointerType struct { Base Type Mutable bool } func (p PointerType) Equals(other Type) bool { if otherPointer, ok := other.(PointerType); ok { return p.Base.Equals(otherPointer.Base) && p.Mutable == otherPointer.Mutable } return false } func (p PointerType) CanCoerceTo(other Type) bool { if p.Equals(other) { return true } otherPointer, ok := other.(PointerType) if !ok { return false } if !p.Mutable && otherPointer.Mutable { return false } if p.Base.Equals(PrimitiveVoid) || otherPointer.Base.Equals(PrimitiveVoid) { return true } return p.Base.CanCoerceTo(otherPointer.Base) } func (p PointerType) CanCastTo(other Type) bool { if p.Equals(other) { return true } switch t := other.(type) { case PointerType: if !p.Mutable && t.Mutable { return false } return true case PrimitiveType: return IsInteger(t) } return false } func (p PointerType) String() string { sb := strings.Builder{} sb.WriteString("*") if p.Mutable { sb.WriteString("mut ") } sb.WriteString(p.Base.String()) return sb.String() } func IsPointer(t Type) bool { t = Underlying(t) _, ok := t.(PointerType) return ok } type SliceType struct { Base Type Mutable bool } func (a SliceType) Equals(other Type) bool { otherSlice, ok := other.(SliceType) if !ok { return false } if a.Mutable != otherSlice.Mutable { return false } return a.Base.Equals(otherSlice.Base) } func (a SliceType) CanCoerceTo(other Type) bool { if a.Equals(other) { return true } otherSlice, ok := other.(SliceType) if !ok { return false } if otherSlice.Mutable && !a.Mutable { return false } return a.Base.Equals(otherSlice.Base) } func (a SliceType) CanCastTo(other Type) bool { if a.Equals(other) { return true } if otherSlice, ok := other.(SliceType); ok { if otherSlice.Mutable && !a.Mutable { return false } return a.Base.Equals(otherSlice.Base) } if otherArray, ok := other.(ArrayType); ok { return a.Base.Equals(otherArray.Base) } return false } func (a SliceType) String() string { mutable := "" if a.Mutable { mutable = "mut " } return "[]" + mutable + a.Base.String() } type ArrayType struct { Base Type Length int } func (a ArrayType) Equals(other Type) bool { o, ok := other.(ArrayType) return ok && a.Length == o.Length && a.Base.Equals(o.Base) } func (a ArrayType) CanCoerceTo(other Type) bool { if a.Equals(other) { return true } s, ok := other.(SliceType) return ok && !s.Mutable && a.Base.Equals(s.Base) } func (a ArrayType) CanCastTo(other Type) bool { if a.Equals(other) { return true } s, ok := other.(SliceType) return ok && a.Base.Equals(s.Base) } func (a ArrayType) String() string { return "[" + strconv.Itoa(a.Length) + "]" + a.Base.String() } // SequenceType is the provisional type of an array/slice literal before an // expected type selects its storage representation. It must not reach IR. type SequenceType struct { Base Type Length int } func (s SequenceType) Equals(other Type) bool { o, ok := other.(SequenceType) return ok && s.Length == o.Length && s.Base.Equals(o.Base) } func (s SequenceType) CanCoerceTo(Type) bool { return false } func (s SequenceType) CanCastTo(Type) bool { return false } func (s SequenceType) String() string { return "" } type FunctionType struct { Parameters []Type ReturnType Type TypedVariadic bool VariadicElement Type } // MultipleReturnType is an ABI result bundle, not a source-level tuple. type MultipleReturnType struct{ Types []Type } func (m MultipleReturnType) Equals(other Type) bool { o, ok := other.(MultipleReturnType) if !ok || len(m.Types) != len(o.Types) { return false } for i := range m.Types { if !m.Types[i].Equals(o.Types[i]) { return false } } return true } func (m MultipleReturnType) CanCoerceTo(other Type) bool { o, ok := other.(MultipleReturnType) if !ok || len(m.Types) != len(o.Types) { return false } for i := range m.Types { if !m.Types[i].CanCoerceTo(o.Types[i]) { return false } } return true } func (m MultipleReturnType) CanCastTo(other Type) bool { return m.Equals(other) } func (m MultipleReturnType) String() string { parts := make([]string, len(m.Types)) for i, t := range m.Types { parts[i] = t.String() } return "(" + strings.Join(parts, ", ") + ")" } func (f FunctionType) Equals(other Type) bool { otherFunction, ok := other.(FunctionType) if !ok { return false } if len(f.Parameters) != len(otherFunction.Parameters) { return false } if f.TypedVariadic != otherFunction.TypedVariadic { return false } for i, param := range f.Parameters { if !param.Equals(otherFunction.Parameters[i]) { return false } } return f.ReturnType.Equals(otherFunction.ReturnType) } func (f FunctionType) CanCoerceTo(other Type) bool { otherFunction, ok := other.(FunctionType) if !ok { return false } if len(f.Parameters) != len(otherFunction.Parameters) { return false } if f.TypedVariadic != otherFunction.TypedVariadic { return false } for i := range f.Parameters { if !otherFunction.Parameters[i].CanCoerceTo(f.Parameters[i]) { return false } } return f.ReturnType.CanCoerceTo(otherFunction.ReturnType) } func (f FunctionType) CanCastTo(other Type) bool { return f.Equals(other) } func (f FunctionType) String() string { var result strings.Builder result.WriteString("(") for i, param := range f.Parameters { if f.TypedVariadic && i == len(f.Parameters)-1 { result.WriteString("...") result.WriteString(f.VariadicElement.String()) } else { result.WriteString(param.String()) } if i < len(f.Parameters)-1 { result.WriteString(", ") } } result.WriteString("): ") result.WriteString(f.ReturnType.String()) return result.String() } type ErrorType struct{} func (e ErrorType) Equals(other Type) bool { _, ok := other.(ErrorType) return ok } func (e ErrorType) CanCoerceTo(other Type) bool { return true } func (e ErrorType) CanCastTo(other Type) bool { return true } func (e ErrorType) String() string { return "" } type UntypedInt struct{} func (u UntypedInt) Equals(other Type) bool { _, ok := other.(UntypedInt) return ok } func (u UntypedInt) CanCoerceTo(other Type) bool { switch t := other.(type) { case PrimitiveType: return IsInteger(t) || IsFloat(t) case UntypedInt: return true case UntypedFloat: return true default: return false } } func (u UntypedInt) CanCastTo(other Type) bool { switch t := other.(type) { case PrimitiveType: return IsNumeric(t) || t.Equals(PrimitiveBool) default: return false } } func (u UntypedInt) String() string { return "" } type UntypedFloat struct{} func (u UntypedFloat) Equals(other Type) bool { _, ok := other.(UntypedFloat) return ok } func (u UntypedFloat) CanCoerceTo(other Type) bool { switch t := other.(type) { case PrimitiveType: return IsFloat(t) case UntypedFloat: return true default: return false } } func (u UntypedFloat) CanCastTo(other Type) bool { switch t := other.(type) { case PrimitiveType: return IsFloat(t) || IsInteger(t) default: return false } } func (u UntypedFloat) String() string { return "" } type UnresolvedEnum struct{} func (u UnresolvedEnum) Equals(other Type) bool { _, ok := other.(UnresolvedEnum) return ok } func (u UnresolvedEnum) CanCoerceTo(other Type) bool { return false } func (u UnresolvedEnum) CanCastTo(other Type) bool { return false } func (u UnresolvedEnum) String() string { return "" } func IsUntyped(t Type) bool { switch t.(type) { case UntypedInt, UntypedFloat, UnresolvedEnum: return true } return false } func HasUntyped(t Type) bool { if IsUntyped(t) { return true } switch t := t.(type) { case SliceType: return HasUntyped(t.Base) case ArrayType: return HasUntyped(t.Base) case SequenceType: return HasUntyped(t.Base) case PointerType: return HasUntyped(t.Base) case TraitPointerType: return false case StructType: for _, field := range t.Fields { if HasUntyped(field.R) { return true } } case FunctionType: if slices.ContainsFunc(t.Parameters, HasUntyped) { return true } return HasUntyped(t.ReturnType) case MultipleReturnType: return slices.ContainsFunc(t.Types, HasUntyped) } return false } // HasError reports whether semantic failure has poisoned any part of a type. // Composite types containing ErrorType are erroneous as a whole and must not // participate in follow-on compatibility diagnostics. func HasError(t Type) bool { return hasError(t, make(map[string]bool), make(map[*AliasRef]bool)) } func hasError(t Type, defined map[string]bool, aliases map[*AliasRef]bool) bool { if t == nil { return false } switch t := t.(type) { case ErrorType: return true case DefinedType: key := Identity(t) if defined[key] { return false } defined[key] = true for _, argument := range t.TypeArguments { if hasError(argument, defined, aliases) { return true } } return hasError(t.Underlying, defined, aliases) case *AliasRef: if aliases[t] || t.Target == nil || *t.Target == nil { return false } aliases[t] = true return hasError(*t.Target, defined, aliases) case SliceType: return hasError(t.Base, defined, aliases) case ArrayType: return hasError(t.Base, defined, aliases) case SequenceType: return hasError(t.Base, defined, aliases) case PointerType: return hasError(t.Base, defined, aliases) case TraitPointerType: return hasError(t.Trait, defined, aliases) case TypeParameter: key := Identity(t) if defined[key] { return false } defined[key] = true return hasError(t.Constraint, defined, aliases) case StructType: for _, field := range t.Fields { if hasError(field.R, defined, aliases) { return true } } if t.TaggedUnion != nil { if hasError(t.TaggedUnion.Tag, defined, aliases) { return true } for _, variant := range t.TaggedUnion.Variants { for _, field := range variant.Fields { if hasError(field.R, defined, aliases) { return true } } } } case UnionType: for _, field := range t.Fields { if hasError(field.R, defined, aliases) { return true } } case FunctionType: for _, parameter := range t.Parameters { if hasError(parameter, defined, aliases) { return true } } return hasError(t.ReturnType, defined, aliases) case MultipleReturnType: for _, item := range t.Types { if hasError(item, defined, aliases) { return true } } case TraitType: key := Identity(t) if defined[key] { return false } defined[key] = true for _, method := range t.Methods { for _, parameter := range method.GenericParameters { if hasError(parameter.Constraint, defined, aliases) { return true } } for _, parameter := range method.Parameters { if hasError(parameter, defined, aliases) { return true } } if hasError(method.ReturnType, defined, aliases) { return true } } } return false } func HasTraitPointer(t Type) bool { switch t := Underlying(t).(type) { case TraitPointerType: return true case StructType: for _, field := range t.Fields { if HasTraitPointer(field.R) { return true } } case UnionType: for _, field := range t.Fields { if HasTraitPointer(field.R) { return true } } case SliceType: return HasTraitPointer(t.Base) case ArrayType: return HasTraitPointer(t.Base) } return false } // HasTaggedUnion reports whether a nominal tagged union occurs anywhere in a // type, including behind pointer or slice indirection. func HasTaggedUnion(t Type) bool { return hasTaggedUnion(t, make(map[string]bool), make(map[*AliasRef]bool)) } func hasTaggedUnion(t Type, defined map[string]bool, aliases map[*AliasRef]bool) bool { switch t := t.(type) { case DefinedType: key := Identity(t) if defined[key] { return false } defined[key] = true return hasTaggedUnion(t.Underlying, defined, aliases) case *AliasRef: if aliases[t] || t.Target == nil || *t.Target == nil { return false } aliases[t] = true return hasTaggedUnion(*t.Target, defined, aliases) case PointerType: return hasTaggedUnion(t.Base, defined, aliases) case SliceType: return hasTaggedUnion(t.Base, defined, aliases) case ArrayType: return hasTaggedUnion(t.Base, defined, aliases) case StructType: if t.TaggedUnion != nil { return true } for _, field := range t.Fields { if hasTaggedUnion(field.R, defined, aliases) { return true } } case UnionType: for _, field := range t.Fields { if hasTaggedUnion(field.R, defined, aliases) { return true } } case FunctionType: for _, parameter := range t.Parameters { if hasTaggedUnion(parameter, defined, aliases) { return true } } return hasTaggedUnion(t.ReturnType, defined, aliases) case MultipleReturnType: for _, item := range t.Types { if hasTaggedUnion(item, defined, aliases) { return true } } } return false } func PromoteNumeric(a, b Type) Type { if a.Equals(b) { return a } _, aUntypedFloat := a.(UntypedFloat) _, bUntypedFloat := b.(UntypedFloat) if aUntypedFloat || bUntypedFloat { other := b if bUntypedFloat { other = a } if _, ok := other.(UntypedInt); ok { return UntypedFloat{} } if primitive, ok := other.(PrimitiveType); ok { if IsFloat(primitive) { return primitive } if IsInteger(primitive) { return PrimitiveF64 } } return ErrorType{} } if _, ok := a.(UntypedInt); ok { return b } if _, ok := b.(UntypedInt); ok { return a } pa, aok := a.(PrimitiveType) pb, bok := b.(PrimitiveType) if !aok || !bok { return ErrorType{} } if IsFloat(pa) || IsFloat(pb) { if pa == PrimitiveF64 || pb == PrimitiveF64 { return PrimitiveF64 } return PrimitiveF32 } return PromoteIntegers(pa, pb) } // CommonType finds the type shared by values in aggregate literals. func CommonType(a, b Type) Type { if a.Equals(b) { return a } return PromoteNumeric(a, b) } func PromoteIntegers(a, b PrimitiveType) Type { if IsSigned(a) && IsSigned(b) { return widerSigned(a, b) } if IsUnsigned(a) && IsUnsigned(b) { return widerUnsigned(a, b) } return ErrorType{} } func widerSigned(a, b PrimitiveType) PrimitiveType { if !IsSigned(a) || !IsSigned(b) { panic("widerSigned called with non-signed types") } if IntegerRank(a) >= IntegerRank(b) { return a } return b } func widerUnsigned(a, b PrimitiveType) PrimitiveType { if !IsUnsigned(a) || !IsUnsigned(b) { panic("widerUnsigned called with non-unsigned types") } if IntegerRank(a) >= IntegerRank(b) { return a } return b }