microsoft / microsoft/TypeScript
Using `undefined` as a discriminator within a mapped type yields erroneous types when primitive intersections are involved
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 111k
- Forks
- 14.3k
- Avg merge
- 2d 4h
- Merged PRs (30d)
- 132
Description
TypeScript Version: 2.2.0
// A *self-contained* demonstration of the problem follows...
type DeepReadonly<T> = {
readonly [K in keyof T]: DeepReadonly<T[K]>;
}
interface FieldBrand {
" do not use ": void;
}
type FieldId = number & FieldBrand;
interface DefOne {
field: string | FieldId;
kind: string;
}
interface DefTwo {
field?: undefined; // Allow discriminant checks on 'field'
value: string;
}
type Def = DefOne | DefTwo;
interface State {
a?: Def;
b?: Def;
}
type ROState = DeepReadonly<State>;
function lookupName(f: FieldId): string {
return "";
}
function remapFieldsToNames(channels: ROState): ROState {
const newState: State = {};
for (const k of Object.keys(channels)) {
const key = k as keyof ROState;
const ch = channels[key];
let replacement: ROState[typeof key] | undefined = undefined;
if (ch) {
if (ch.field) {
const f = ch.field;
if (typeof f === "number") {
f; // Should be FieldId or number, not never!
replacement = { ...ch, field: lookupName(f) };
}
else if (typeof f === "string") {
f; // correct
}
}
}
newState[k] = replacement || channels[k];
}
return newState;
}
Expected behavior:
There are no type errors in the above, and f after the typeof f === "number" check is either a number or FieldId.
Actual behavior:
replacement = { ...ch, field: lookupName(f) }; has a type error and f is of type never.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by running the self-contained TypeScript reproduction from the issue and inspect narrowing for the mapped DeepReadonly type, undefined discriminator, and primitive intersection. The fix is done when the reproduction reports no type errors and the number branch narrows f to number or FieldId rather than never.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 45/100