package types // Identical reports whether T and U are identical types under Moxie rules: // - string = []byte (Moxie text unification) // - int32 = int32, uint32 = uint32 (enforced via universe scope, but checked here too) func Identical(T, U Type) (ok bool) { if T == U { return true } if T == nil || U == nil { return false } switch t := T.(type) { case *Basic: var u *Basic u, ok = U.(*Basic) if !ok { if t.Info&IsString != 0 { sl, slOk := U.(*Slice) if slOk { eb, ebOk := sl.Elem.(*Basic) if ebOk && eb.Kind == Uint8 { return true } } } return false } tk := normaliseKind(t.Kind) uk := normaliseKind(u.Kind) if tk == uk { return true } if t.Info&IsString != 0 && u.Info&IsString != 0 { return true } return false case *Pointer: var u *Pointer u, ok = U.(*Pointer) return ok && Identical(t.Base, u.Base) case *Array: var u *Array u, ok = U.(*Array) return ok && t.Len == u.Len && Identical(t.Elem, u.Elem) case *Slice: var u *Slice u, ok = U.(*Slice) if !ok { // []byte = string in Moxie (including untyped string) b, bOk := U.(*Basic) if bOk && b.Info&IsString != 0 { eb, ebOk := t.Elem.(*Basic) if ebOk && eb.Kind == Uint8 { return true } } return false } return Identical(t.Elem, u.Elem) case *TCMap: var u *TCMap u, ok = U.(*TCMap) return ok && Identical(t.Key, u.Key) && Identical(t.Elem, u.Elem) case *TCChan: var u *TCChan u, ok = U.(*TCChan) return ok && t.Dir == u.Dir && Identical(t.Elem, u.Elem) case *TCStruct: var u *TCStruct u, ok = U.(*TCStruct) if !ok || t.NumFields() != u.NumFields() { return false } for i := range t.Fields { tf := t.Fields[i] uf := u.Fields[i] if tf.Name != uf.Name || tf.Anonymous != uf.Anonymous { return false } if !Identical(tf.Typ, uf.Typ) { return false } } return true case *TCInterface: var u *TCInterface u, ok = U.(*TCInterface) if !ok { return false } if t.NumMethods() != u.NumMethods() { return false } for i, tm := range t.AllMethods { um := u.AllMethods[i] if tm.Name != um.Name || !Identical(tm.Sig, um.Sig) { return false } } return true case *Signature: var u *Signature u, ok = U.(*Signature) if !ok { return false } if t.Variadic != u.Variadic { return false } if !identicalTuples(t.Params, u.Params) || !identicalTuples(t.Results, u.Results) { return false } return true case *Named: var u *Named u, ok = U.(*Named) return ok && t.Obj == u.Obj case *TypeParam: var u *TypeParam u, ok = U.(*TypeParam) return ok && t.Id == u.Id case *Tuple: var u *Tuple u, ok = U.(*Tuple) return ok && identicalTuples(t, u) } return false } func identicalTuples(a, b *Tuple) (ok bool) { la, lb := tupleLen(a), tupleLen(b) if la != lb { return false } for i := 0; i < la; i++ { if !Identical(a.Vars[i].Typ, b.Vars[i].Typ) { return false } } return true } func tupleLen(t *Tuple) (n int32) { if t == nil { return 0 } return t.Len() } // normaliseKind maps int32/uint32 to their 32-bit equivalents for identity checks. func normaliseKind(k BasicKind) (b BasicKind) { return k // int32=int32 is handled by the universe scope; no extra mapping needed } // Assignable reports whether a value of type V is assignable to type T // under Moxie's type rules (spec Assignability, plus Moxie extensions). func Assignable(V, T Type) (ok bool) { if V == nil || T == nil { return V == T } // Identical types are always assignable. if Identical(V, T) { return true } // Moxie extension: string = []byte anywhere. if isTextType(V) && isTextType(T) { return true } // Untyped constants are assignable to any numeric/bool/string type they fit in. bv, bvOk := V.(*Basic) if bvOk && bv.Info&IsUntyped != 0 { return untypedAssignable(bv, T) } // nil is assignable to pointer, slice, map, chan, func, interface. if V == Typ[UntypedNil] { return isNilable(T) } // V's underlying type identical to T's underlying type, and at least one is unnamed. vu, tu := SafeUnderlying(V), SafeUnderlying(T) if Identical(vu, tu) { _, vNamed := V.(*Named) _, tNamed := T.(*Named) if !vNamed || !tNamed { return true } } // T is an interface and V implements it. iface, ifOk := SafeUnderlying(T).(*TCInterface) if ifOk { return Implements(V, iface) } // Directional channel: bidirectional chan is assignable to directed chan. tc, tcOk := T.(*TCChan) if tcOk { vc, vcOk := V.(*TCChan) if vcOk { if Identical(tc.Elem, vc.Elem) { return vc.Dir == TCSendRecv || vc.Dir == tc.Dir } } } return false } func isNilable(T Type) (ok bool) { switch SafeUnderlying(T).(type) { case *Pointer, *Slice, *TCMap, *TCChan, *Signature, *TCInterface: return true } return false } func untypedAssignable(V *Basic, T Type) (ok bool) { var b *Basic b, ok = SafeUnderlying(T).(*Basic) if !ok { return false } switch V.Kind { case UntypedBool: return b.Info&IsBoolean != 0 case UntypedInt, UntypedRune: return b.Info&IsInteger != 0 || b.Info&IsFloat != 0 case UntypedFloat: return b.Info&IsFloat != 0 case UntypedString: return b.Info&IsString != 0 } return false } // isTextType returns true if T is string or []byte under Moxie's unified model. func isTextType(T Type) (ok bool) { b, bOk := SafeUnderlying(T).(*Basic) if bOk && b.Info&IsString != 0 { return true } sl, slOk := SafeUnderlying(T).(*Slice) if slOk { eb, ebOk := SafeUnderlying(sl.Elem).(*Basic) if ebOk && eb.Kind == Uint8 { return true } } return false } // Implements reports whether type T implements interface I. func Implements(T Type, I *TCInterface) (ok bool) { if I.IsEmpty() { return true } // Collect T's method set. ms := methodSet(T) for _, im := range I.AllMethods { var m *TCFunc m, ok = ms[im.Name] if !ok { return false } if !Identical(m.Signature(), im.Sig) { return false } } return true } // methodSet returns the named method set of T as a name->Func map. // Includes methods from *T if T is a named type (pointer receiver methods). func methodSet(T Type) (m map[string]*TCFunc) { ms := map[string]*TCFunc{} collectMethods(T, ms, false) return ms } func collectMethods(T Type, ms map[string]*TCFunc, ptrOk bool) { switch t := T.(type) { case *Named: for _, m := range t.Methods { if _, dup := ms[m.Name]; !dup { ms[m.Name] = m } } // Also collect pointer receiver methods if T is addressable. if ptrOk { collectMethods(NewPointer(T), ms, false) } collectMethods(t.Under, ms, ptrOk) case *Pointer: if named, ok := t.Base.(*Named); ok { for _, m := range named.Methods { if _, dup := ms[m.Name]; !dup { ms[m.Name] = m } } } case *TCInterface: for _, m := range t.AllMethods { if _, dup := ms[m.Name]; !dup { ms[m.Name] = NewTCFunc(nil, m.Name, m.Sig) } } } }