Skip to content

"Lookup" Type Broken When Using Generics #62770

@awfulstew

Description

@awfulstew

🔎 Search Terms

"nested lookup with generic", "type lookup with generic"

🕗 Version & Regression Information

  • For doesNotWork1, this changed between versions 3.9.7 and 4.0.5.
  • For other "not working" examples, this is the behavior in every version I tried, and I reviewed the FAQ for entries about generics within lookup tables.

⏯ Playground Link

https://www.typescriptlang.org/play/?ts=5.9.3#code/JYOwLgpgTgZghgYwgAgOrDACwMIHsC2+uIyA3gFDJXIzAQA2AJgIwBcyIArvgEbQDclarQaMATOwDOYKKADmggL7lyoSLEQp0WAMoAHCAmBx6yCAA9IIRpLQYcBIiQrVkkg0ZPsuvAeWWq4NDwSMj6hsb0ADK4uADWnHpkQlTuEV52uh6RSipqwZrIeITEMfGJZpYQ1rbhntGxCUku1AiOxOzaDiUgueRgAJ4GyGVNADwAcnD4KBZWNshxEAO4MEXtIKOJAHzIALzJrgDaANLIoIvLq+s9W3pHUzMAuk8A-OzFTncP0xBPp08lIJyAB6EHICYAeQAKgBRdhYYC2JHIHhQeLVMwAN0xAHd7MgAAZLFZrT6lRqJQnIQAoBJcBkS0vVqZg4LYwLhUSg2oRqpBGMhcJwwDRcFBKtM9PQUFg4CLGJyQLgRbixXFkBy3BBZlB0VBbFAIGBOFAQOQYJwQAgwMBiG4CBBHhAxmc5tUFiTrnVIndtgAKACU7CxuGAApaVDaIGkyFVUDikjYI0pejGAHImZE07sDhHXDQ6Ewk8wADQpfMiJgSZBpnlONNl-PUTMZMQABgAzI3qIpga4ozG4wnq3d03XiNn9ocmwXREmAKzdpuV8TsDMOxEgOQN8uuMHINq6wxgegM2g1DWskWI2wrtyYIVMDjKrmX6AQXfN7IZDtiJfIXsVD3cELA8MA5VtEgUVlEVCUtRgIHPCBGGpSQH04J8+GQEx6FwXFkOQRhOBlTk9HRAwoEGDUhggWw0Lw+RLzlc4RXojCBT4ct9yhOEEUwFEACpcTZATYzVRiLixDsADoAE5pIAdnLAd5VwWiJmVVA1STUcThzacm33P0yNwCjBgDNxv3od4bi+FMASOAAiFt6Ecp5kAAH2QeDENAAi6UkTlmPwUBgHwbhLxRNjMJQQ1AvoHENU5ODrF8kBkNQ0BQkJdKcXFLyfKQlDkDi3AEtopKiUKvyUOkurP1SKz2Gq9LGH-VwVyTFrkPa4RC1XbzUqK-9AK4kDzDAiC7Wgq9ItsVV2NfOAeGlSrD0Na1T3OBDwGAGAz365ESHHEgYDFObLIiWgEC1EVrhJSQxohGF4QuhUKqVFU1XOEg4BABk8skSDsKxOBgHoZbVouKU4AGOR0Xg5TiBjd7JA0sAtPjEcUxdfS81cFz2GYNs2wAFhGoDqH3UDjymqD2Vmm8xMWrDIZIg8xQ2k8GTDPk9oO0Qjo5npYwJHCJXwKVaKenjXqZ1Hny++Mfuw-7kEB4G4FB8G2ZVmG4YR6wkejVT1M0tUO3YXS8Ya2ciyJxdbZXas0z+5VMGgNNy2UZQgA

💻 Code

interface WithCommon {
    field1: number;
    field2: string;
}

interface WithSpecial extends WithCommon {
    special: number;
}

interface SpecialLookup {
    special: WithSpecial;
}

interface CommonLookup extends SpecialLookup {
    common: WithCommon;
}

type Lookup<Name extends keyof CommonLookup> = {
    [K in keyof CommonLookup[Name]]?: CommonLookup[Name][K];
};

// NOTE: this is broken even with `keyof CommonLookup` — key `special` has to be removed to see current error return since with `CommonLookup` we do detect that `special` is not a shared key of all members
function someName<K extends keyof SpecialLookup>(): void {
    const works1: Lookup<'special'> = {
        field1: 1,
        field2: 'common',
        special: 203,
    };

    const works2: Lookup<'common'> = {
        field1: 5,
        field2: 'something',
        // correctly finds that this field should not be there
        special: 32,
    };

    // expectation is that `undefined` should be allowed due to property types showing that it should be
    // NOTE: this *was* working in v3.9.7
    const doesNotWork1: Lookup<K> = {
        // (property) special?: CommonLookup[K]["special"] | undefined — so at minimum this should be resolve to `undefined` since `never | undefined` resolves to `undefined`...
        special: undefined,
        field1: undefined,
        field2: undefined,
    };

    // expectation is that this would be able to correctly identify fields in common for this specific set of keys
    // NOTE: this does not work in any version available in playground
    const doesNotWork2: Lookup<K> = {
        special: 1004,
    }

    // expectation is that this would be able to correctly identify fields in common with all examples
    // NOTE: this does not work in any version available in playground
    const doesNotWork3: Lookup<K> = {
        field1: 15,
        field2: 'another'
    }
}

🙁 Actual behavior

When using a generic as the input to a "lookup" type, we should be able to correctly type the common fields of the "looked-up" interfaces.

What is interesting here is that we appear to get correct behavior from "code completion" (i.e. special is correctly typed as a property of Lookup<K> with type CommonLookup[K]["special"] | undefined), but actually setting the presented type value doesn't work.

🙂 Expected behavior

We should get correct typing for the generic Lookup<K> type:

  • doesNotWork1 should allow undefined as a value since field type evaluates to CommonLookup[K][Field] | undefined.
  • doesNotWork2 should allow number as value for field special since all interfaces within keyof SpecialLookup have special: number on the interface.
  • doesNotWork3 should allow values for field1 and field2 for the same reason as doesNotWork2.

Additional information about the issue

As I called out in my NOTE within the code example, this is also broken in a "simpler" example where the generic is K extends keyof CommonLookup. I presented the above code example because it is the most similar to my use case and I wasn't sure if fixing the "simpler" case would also fix the one where we are choosing a smaller key set.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Domain: Mapped TypesThe issue relates to mapped typesHelp WantedYou can do thisPossible ImprovementThe current behavior isn't wrong, but it's possible to see that it might be better in some cases

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions