microsoft / microsoft/TypeScript

"Lookup" Type Broken When Using Generics

Open
#62,770 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Domain: Mapped Types Help Wanted Possible Improvement
Dominant language
Go
Stars
111k
Forks
14.3k
Avg merge
2d 4h
Merged PRs (30d)
132

Description

### 🔎 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

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

interface WithSpecial extends WithCommon {
special: number;
}

interface SpecialLookup {
special: WithSpecial;
}

interface CommonLookup extends SpecialLookup {
common: WithCommon;
}

type Lookup = {
[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(): 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 = {
// (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 = {
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 = {
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` 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` 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.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the linked TypeScript Playground and reproduce the doesNotWork1–3 examples across the noted compiler versions. Trace the generic lookup type-checking behavior, then verify that the expected undefined and common-field assignments type-check without allowing invalid fields.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
compilers
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.