Type of intersection of two object types A and B is not equal to A & B · Issue #25 · skunkteam/types · GitHub
Skip to content

Type of intersection of two object types A and B is not equal to A & B #25

Closed

Description

@wvanderdeijl

I am trying to build a generic type with @skunkteam/types that takes an existing (object) type and extends it with a number of fields (id in this example):

import { BaseObjectLikeTypeImpl, intersection, object, string, The, TypeImpl } from '@skunkteam/types';

export type ObjectType<ResultType> = TypeImpl<BaseObjectLikeTypeImpl<ResultType>>;

export type Person = The<typeof Person>;
export const Person = object('Person', { name: string });

export type RestDocument<T> = T & { id: string };
export function RestDocument<T>(type: ObjectType<T>): ObjectType<RestDocument<T>> {
    return intersection(`RestDocument<${type.name}>`, [object({ id: string }), type]);
}

Unfortunately this throws a typescript error:

error TS2322: Type 'TypeImpl<IntersectionType<[TypeImpl<InterfaceType<{ id: TypeImpl<BaseTypeImpl<string>>; }, TypeOfProperties<Writable<{ id: TypeImpl<BaseTypeImpl<string>>; }>>>>, TypeImpl<...>]>>' is not assignable to type 'TypeImpl<BaseObjectLikeTypeImpl<RestDocument<T>>>'.
  Type 'TypeImpl<IntersectionType<[TypeImpl<InterfaceType<{ id: TypeImpl<BaseTypeImpl<string>>; }, TypeOfProperties<Writable<{ id: TypeImpl<BaseTypeImpl<string>>; }>>>>, TypeImpl<...>]>>' is not assignable to type 'BaseObjectLikeTypeImpl<RestDocument<T>>'.
    The types returned by 'and(...)' are incompatible between these types.
      Type 'TypeImpl<IntersectionType<[TypeImpl<IntersectionType<[TypeImpl<InterfaceType<{ id: TypeImpl<BaseTypeImpl<string>>; }, TypeOfProperties<Writable<{ id: TypeImpl<BaseTypeImpl<string>>; }>>>>, TypeImpl<...>]>>, any]>>' is not assignable to type 'TypeImpl<IntersectionType<[BaseObjectLikeTypeImpl<RestDocument<T>>, any]>>'.
        Type 'TypeImpl<IntersectionType<[TypeImpl<IntersectionType<[TypeImpl<InterfaceType<{ id: TypeImpl<BaseTypeImpl<string>>; }, TypeOfProperties<Writable<{ id: TypeImpl<BaseTypeImpl<string>>; }>>>>, TypeImpl<...>]>>, any]>>' is not assignable to type 'IntersectionType<[BaseObjectLikeTypeImpl<RestDocument<T>>, any]>'.
          Types of property 'types' are incompatible.
            Type '[TypeImpl<IntersectionType<[TypeImpl<InterfaceType<{ id: TypeImpl<BaseTypeImpl<string>>; }, TypeOfProperties<Writable<{ id: TypeImpl<BaseTypeImpl<string>>; }>>>>, TypeImpl<...>]>>, any]' is not assignable to type '[BaseObjectLikeTypeImpl<RestDocument<T>>, any]'.
              Type 'TypeImpl<IntersectionType<[TypeImpl<InterfaceType<{ id: TypeImpl<BaseTypeImpl<string>>; }, TypeOfProperties<Writable<{ id: TypeImpl<BaseTypeImpl<string>>; }>>>>, TypeImpl<...>]>>' is not assignable to type 'BaseObjectLikeTypeImpl<RestDocument<T>>'.
                The types returned by 'and(...)' are incompatible between these types.
                  Type 'TypeImpl<IntersectionType<[TypeImpl<IntersectionType<[TypeImpl<InterfaceType<{ id: TypeImpl<BaseTypeImpl<string>>; }, TypeOfProperties<Writable<{ id: TypeImpl<BaseTypeImpl<string>>; }>>>>, TypeImpl<...>]>>, any]>>' is not assignable to type 'TypeImpl<IntersectionType<[BaseObjectLikeTypeImpl<RestDocument<T>>, any]>>'.
                    Type 'TypeImpl<IntersectionType<[TypeImpl<IntersectionType<[TypeImpl<InterfaceType<{ id: TypeImpl<BaseTypeImpl<string>>; }, TypeOfProperties<Writable<{ id: TypeImpl<BaseTypeImpl<string>>; }>>>>, TypeImpl<...>]>>, any]>>' is not assignable to type 'IntersectionType<[BaseObjectLikeTypeImpl<RestDocument<T>>, any]>'.
                      Types of property 'types' are incompatible.
                        Type '[TypeImpl<IntersectionType<[TypeImpl<InterfaceType<{ id: TypeImpl<BaseTypeImpl<string>>; }, TypeOfProperties<Writable<{ id: TypeImpl<BaseTypeImpl<string>>; }>>>>, TypeImpl<...>]>>, any]' is not assignable to type '[BaseObjectLikeTypeImpl<RestDocument<T>>, any]'.
                          Type 'TypeImpl<IntersectionType<[TypeImpl<InterfaceType<{ id: TypeImpl<BaseTypeImpl<string>>; }, TypeOfProperties<Writable<{ id: TypeImpl<BaseTypeImpl<string>>; }>>>>, TypeImpl<...>]>>' is not assignable to type 'BaseObjectLikeTypeImpl<RestDocument<T>>'.
                            The types returned by 'typeValidator(...)' are incompatible between these types.
                              Type 'Result<MergeIntersection<T & TypeOfProperties<Writable<{ id: TypeImpl<BaseTypeImpl<string>>; }>>>>' is not assignable to type 'Result<RestDocument<T>>'.
                                Type 'Success<MergeIntersection<T & TypeOfProperties<Writable<{ id: TypeImpl<BaseTypeImpl<string>>; }>>>>' is not assignable to type 'Result<RestDocument<T>>'.
                                  Type 'Success<MergeIntersection<T & TypeOfProperties<Writable<{ id: TypeImpl<BaseTypeImpl<string>>; }>>>>' is not assignable to type 'Success<RestDocument<T>>'.
                                    Type 'MergeIntersection<T & TypeOfProperties<Writable<{ id: TypeImpl<BaseTypeImpl<string>>; }>>>' is not assignable to type 'RestDocument<T>'.
                                      Type '(T & TypeOfProperties<Writable<{ id: TypeImpl<BaseTypeImpl<string>>; }>>) | { [P in keyof (T & TypeOfProperties<Writable<{ id: TypeImpl<BaseTypeImpl<string>>; }>>)]: MergeIntersection<...>; }' is not assignable to type 'RestDocument<T>'.
                                        Type '{ [P in keyof (T & TypeOfProperties<Writable<{ id: TypeImpl<BaseTypeImpl<string>>; }>>)]: MergeIntersection<(T & TypeOfProperties<Writable<{ id: TypeImpl<BaseTypeImpl<string>>; }>>)[P]>; }' is not assignable to type 'RestDocument<T>'.
                                          Type '{ [P in keyof (T & TypeOfProperties<Writable<{ id: TypeImpl<BaseTypeImpl<string>>; }>>)]: MergeIntersection<(T & TypeOfProperties<Writable<{ id: TypeImpl<BaseTypeImpl<string>>; }>>)[P]>; }' is not assignable to type 'T'.
                                            'T' could be instantiated with an arbitrary type which could be unrelated to '{ [P in keyof (T & TypeOfProperties<Writable<{ id: TypeImpl<BaseTypeImpl<string>>; }>>)]: MergeIntersection<(T & TypeOfProperties<Writable<{ id: TypeImpl<BaseTypeImpl<string>>; }>>)[P]>; }'.
                                              Type 'MergeIntersection<(T & TypeOfProperties<Writable<{ id: TypeImpl<BaseTypeImpl<string>>; }>>)[P]>' is not assignable to type 'T[P]'.
                                                Type '{ id: string; }' is not assignable to type 'RestDocument<T>'.
                                                  Type '{ id: string; }' is not assignable to type 'T'.
                                                    'T' could be instantiated with an arbitrary type which could be unrelated to '{ id: string; }'.
                                                      Type 'MergeIntersection<T & TypeOfProperties<Writable<{ id: TypeImpl<BaseTypeImpl<string>>; }>>>' is not assignable to type 'T'.
                                                        'T' could be instantiated with an arbitrary type which could be unrelated to 'MergeIntersection<T & TypeOfProperties<Writable<{ id: TypeImpl<BaseTypeImpl<string>>; }>>>'.
                                                          Type '(T & TypeOfProperties<Writable<{ id: TypeImpl<BaseTypeImpl<string>>; }>>) | { [P in keyof (T & TypeOfProperties<Writable<{ id: TypeImpl<BaseTypeImpl<string>>; }>>)]: MergeIntersection<...>; }' is not assignable to type 'T'.
                                                            'T' could be instantiated with an arbitrary type which could be unrelated to '(T & TypeOfProperties<Writable<{ id: TypeImpl<BaseTypeImpl<string>>; }>>) | { [P in keyof (T & TypeOfProperties<Writable<{ id: TypeImpl<BaseTypeImpl<string>>; }>>)]: MergeIntersection<...>; }'.
                                                              Type '{ [P in keyof (T & TypeOfProperties<Writable<{ id: TypeImpl<BaseTypeImpl<string>>; }>>)]: MergeIntersection<(T & TypeOfProperties<Writable<{ id: TypeImpl<BaseTypeImpl<string>>; }>>)[P]>; }' is not assignable to type 'T'.
                                                                'T' could be instantiated with an arbitrary type which could be unrelated to '{ [P in keyof (T & TypeOfProperties<Writable<{ id: TypeImpl<BaseTypeImpl<string>>; }>>)]: MergeIntersection<(T & TypeOfProperties<Writable<{ id: TypeImpl<BaseTypeImpl<string>>; }>>)[P]>; }'.
                                                                  Type 'MergeIntersection<(T & TypeOfProperties<Writable<{ id: TypeImpl<BaseTypeImpl<string>>; }>>)[P]>' is not assignable to type 'T[P]'.
                                                                    Type '(T & TypeOfProperties<Writable<{ id: TypeImpl<BaseTypeImpl<string>>; }>>)[P] | { [P in keyof (T & TypeOfProperties<Writable<{ id: TypeImpl<BaseTypeImpl<string>>; }>>)[P]]: MergeIntersection<...>; }' is not assignable to type 'T[P]'.
                                                                      Type '{ [P in keyof (T & TypeOfProperties<Writable<{ id: TypeImpl<BaseTypeImpl<string>>; }>>)[P]]: MergeIntersection<(T & TypeOfProperties<Writable<{ id: TypeImpl<BaseTypeImpl<string>>; }>>)[P][P]>; }' is not assignable to type 'T[P]'.
                                                                        Type 'MergeIntersection<(T & TypeOfProperties<Writable<{ id: TypeImpl<BaseTypeImpl<string>>; }>>)["id" | keyof T]>' is not assignable to type 'T[P]'.
                                                                          Type '(T & TypeOfProperties<Writable<{ id: TypeImpl<BaseTypeImpl<string>>; }>>)["id" | keyof T] | { [P in keyof (T & TypeOfProperties<Writable<{ id: TypeImpl<...>; }>>)["id" | keyof T]]: MergeIntersection<...>; }' is not assignable to type 'T[P]'.
                                                                            Type '(T["id"] & string) | (T & TypeOfProperties<Writable<{ id: TypeImpl<BaseTypeImpl<string>>; }>>)[keyof T]' is not assignable to type 'T[P]'.
                                                                              Type 'T["id"] & string' is not assignable to type 'T[P]'.
                                                                                Type 'MergeIntersection<T["id"] & string> | MergeIntersection<(T & TypeOfProperties<Writable<{ id: TypeImpl<BaseTypeImpl<string>>; }>>)[keyof T]>' is not assignable to type 'T[P]'.
                                                                                  Type 'MergeIntersection<T["id"] & string>' is not assignable to type 'T[P]'.
                                                                                    Type '(T["id"] & string) | { [P in keyof (Record<string | number | symbol, unknown> & T["id"] & string)]: MergeIntersection<(Record<string | number | symbol, unknown> & T["id"] & string)[P]>; }' is not assignable to type 'T[P]'.
                                                                                      Type 'T["id"] & string' is not assignable to type 'T[P]'.
                                                                                        Type 'string' is not assignable to type 'T[P]'.
                                                                                          Type '{ id: string; }' is not assignable to type 'T'.
                                                                                            'T' could be instantiated with an arbitrary type which could be unrelated to '{ id: string; }'.

Activity

  1. pavadeli commented on Jun 25, 2021

    @pavadeli
    Member

    The following should work:

    export type Person = The<typeof Person>;
    export const Person = object('Person', { name: string });
    
    // This is the trick here:    vvvvvvvv
    export type RestDocument<T> = Writable<T> & { id: string };
    export function RestDocument<T>(type: ObjectType<T>): ObjectType<RestDocument<T>> {
        return intersection(`RestDocument<${type.name}>`, [object({ id: string }), type]);
    }

    Objects that are passed into object to configure the validator are converted to TypeScript types on the fly (during compile time). During this conversion we ensure that any readonly annotation of the properties is removed (using the Writable type that is exported from the @skunkteam/types lib). If we wouldn't do that, a readonly property in the config-object would result in a readonly property in the resulting type and that would be unreasonable I think.

    You have to forgive TypeScript for the error-message inception, we are stretching the limits here. Please try the suggested solution above and let me know.

  2. pavadeli commented on Jun 28, 2021

    @pavadeli
    Member

    Just came out of a call with @wvanderdeijl and we figured that this needs to be documented in the README. Maybe in the future we can think of a way to remove the mandatory use of Writable.

  3. github-actions commented on Jun 29, 2021

    @github-actions

    🎉 This issue has been resolved in version 3.1.1 🎉

    The release is available on:

    Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions