Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Conflicting types in some constituents

I'm trying to have a method return a specific type depending on a string given as an argument. This is what I have:

interface Base {
    type: 'a' | 'b';
}

interface MyA extends Base {
    type: 'a';
    nameA: string;
}

interface MyB extends Base {
    type: 'b';
    nameB: string;
}

interface Mappings {
    a: MyA;
    b: MyB;
}

const testMethod = <K extends keyof Mappings>(
    type: K
): Mappings[K][] => {
    const values: Mappings[K][] = [];

    if(type === 'a') {
        values.push({
            type: 'a',
            nameA: 'My name a'
        });
    }
    else if(type === 'b') {
        values.push({
            type: 'b',
            nameA: 'My name b'
        });
    }

    return values;
}

So if I run testMethod('a') it would have a return type of MyA[] and when I use testMethod('b') it would return MyB[]. But no matter what I do I can't get it to work. The errors are always:

Argument of type 'MyA' is not assignable to parameter of type 'Mappings[K]'. Type 'MyA' is not assignable to type 'never'. The intersection 'MyA & MyB' was reduced to 'never' because property 'type' has conflicting types in some constituents.

I thought the array could be the problem but returning a variable of type Mappings[K] has the same issue. Here is the playground link. I've done a similar thing in the past like this one to modify a function's argument according to the string given as argument, but here I'm completely lost.

Any guidance or typescript resources I can read would be appreciated!

like image 398
Andres Avatar asked Jan 01 '26 07:01

Andres


1 Answers

Curiously, this worked for me as soon as I specified that Mappings extended Record<string, MyA | MyB>. I removed the common ancestor Base as it didn't seem to help.

interface MyA {
    type: 'a';
    nameA: string;
}

interface MyB {
    type: 'b';
    nameB: string;
}

interface Mappings extends Record<string, MyA | MyB> {
    a: MyA;
    b: MyB;
}

// same as above

const arrayOfMyA = testMethod('a');  // typed as MyA[]
const arrayOfMyB = testMethod('b');  // typed as MyB[]

I don't have an intuitive explanation, other than that an explicit union is the only way to convince TypeScript that 'a' | 'b' (keyof Mappings) exhaustively results in MyA | MyB...despite the fact that Mappings[keyof Mappings] correctly results in MyA | MyB both before and after the change! This might be a TypeScript bug, or at least an opportunity for an improvement.

type MappingResults = Mappings[keyof Mappings];
// typed as MyA | MyB, regardless of whether
// Mappings extends Record<string, MyA | MyB>!

Playground Link

like image 172
Jeff Bowman Avatar answered Jan 05 '26 23:01

Jeff Bowman



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!