1
type a = {
    a: number
    b: {d: string}[]
}
type b = {
    c : string
    b: {f: number}[]
}

type l = a & b

const ab: l = {a: 1, c: '', b: [{d: '', f:1}]}

const ac: l['b'] = [{ d: '', f: 1}]

Playground

After merging types a and b into type l why does const ab of type l work while const ac of type l['b'] does not work?

i tried to write this code on vscode and got this error on const ac :

Type '{ d: string; f: number; }[]' is not assignable to type '{ d: string; }[] & { f: number; }[]'.
  Types of property 'pop' are incompatible.
    Type '() => { d: string; f: number; }' is not assignable to type '(() => { d: string; }) & (() => { f: number; })'.
      Type '() => { d: string; f: number; }' is not assignable to type '() => { d: string; }'.
        Type '{ d: string; f: number; }' is not assignable to type '{ d: string; }'.
          Object literal may only specify known properties, and 'f' does not exist in type '{ d: string; }'.

Why does type l['b'] not contain the merged type {d: string, f: number}[] from type { d: string; }[] & { f: number; }[] while it does on type l?

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
marioxct
  • 13
  • 4
  • i understand, but somehow typescript will merge ``A[]`` and ``B[]`` into ``(A & B)[] `` if they are each passed to two properties with the same name into two different types like the example i wrote in my question: ``type a = { a: number b: {d: string}[] } type b = { c : string b: {f: number}[] } type l = a & b `` type ``l`` will actually accept both ``{d: string}[]`` and ``{f: number}[]`` as ``{d: string, f: number}[]`` under the same property ``b`` of ``l`` type. Sorry i could'nt indent the code in the comment – marioxct Feb 28 '23 at 16:51
  • Ah, okay, sorry! This seems to be a bug in TS reported at [ms/TS#42715](https://github.com/microsoft/TypeScript/issues/42715), which might not be fixable without breaking a lot of other things. Does that address the question now or am I still missing something? – jcalz Feb 28 '23 at 16:59
  • ok, seems a pretty complex bug, i thought there was some kind of simple explanation or a mistake in my code. i Will just use the combination that works and not the bugged one, thank you. – marioxct Mar 01 '23 at 10:19

1 Answers1

0

This is a known bug in TypeScript, reported at microsoft/TypeScript#42715. Apparently object properties whose type is an intersected array types receive excess property warnings erroneously. There was an attempt to fix it at microsoft/TypeScript#43707, but it apparently caused undesirable errors in real-world code bases, and so it was reverted according to this comment. It doesn't look like it will be fixed anytime soon.

Note that intersected arrays behave in other undesirable ways, as per microsoft/TypeScript#41874, so it's probably best to avoid such types where possible; you might be able to write your own utility type to perform a recursive intersection-like operation that behaves better with arrays. For example:

type IntersectNestedArrays<T, U> =
    (T extends readonly any[] ? U extends readonly any[] ?
        { [I in keyof T]: MapInt<T[I], U[Extract<I, keyof U>]> } :
        MapInt<T, U> : MapInt<T, U>)

type MapInt<T, U> = ({
    [K in keyof (T & U)]:
    K extends keyof T ? K extends keyof U ?
    IntersectNestedArrays<T[K], U[K]> : T[K] :
    K extends keyof U ? U[K] : never
}) extends infer O ? { [K in keyof O]: O[K] } : never;


type L = IntersectNestedArrays<A, B>;
/* type L = {
    a: number;
    b: {
        d: string;
        f: number;
    }[];
    c: string;
} */

It's probably out of scope for the question as asked to go into detail about how that type works, but the point is that the resulting type contains the {d: string, f: number}[] type you actually want, and then you completely circumvent the bug you ran into:

const ab: L =
    { a: 1, c: '', b: [{ d: '', f: 1 }] }; // okay

Playground link to code

jcalz
  • 264,269
  • 27
  • 359
  • 360