Skip to content

Fix inference from enum object type to generic mapped type#30944

Merged
ahejlsberg merged 6 commits into
masterfrom
fixInferenceToMappedType
Apr 17, 2019
Merged

Fix inference from enum object type to generic mapped type#30944
ahejlsberg merged 6 commits into
masterfrom
fixInferenceToMappedType

Conversation

@ahejlsberg
Copy link
Copy Markdown
Member

This PR fixes a break introduced by #29253. We now exclude index signatures when inferring from an enum object type to a generic mapped type.

Fixes #30218.

Comment thread src/compiler/checker.ts Outdated
...map(getPropertiesOfType(source), getTypeOfSymbol)
]);
inferFromTypes(getUnionType(valueTypes), getTemplateTypeFromMappedType(target));
const indexType = source.symbol && source.symbol.flags & SymbolFlags.Enum && getEnumKind(source.symbol) === EnumKind.Literal ?
Copy link
Copy Markdown
Member

@weswigham weswigham Apr 16, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than all this stuff, we shouldn't we just filter the enumNumberIndexInfo singleton from the result of getIndexTypeOfType(source, IndexKind.Number) so intersection-based inferences still work out?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that works.

Comment thread src/compiler/checker.ts Outdated
]);
inferFromTypes(getUnionType(valueTypes), getTemplateTypeFromMappedType(target));
const indexInfo = getIndexInfoOfType(source, IndexKind.String) || getNonEnumNumberIndexInfo(source);
const sourcePropsType = indexInfo && indexInfo.type || getUnionType(map(getPropertiesOfType(source), getTypeOfSymbol));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't including the index signature type in the union with the named property types, whereas it was before. Doesn't seem intentional and is potentially an undesirably break, since now properties will be ignored for inference when an index signature exists.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It matches what we do for keyof T, e.g. if T has a numeric index signature, we include number in the index type (and thus forget about any numerically named properties).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, but that's not how we operate with homorphic constraints in play, and that erasure of information is typically undesirable during inference. Take when we infer to interface F<T extends string> { kind: T; [idx: string]: string} - never inferring to the kind field's value seems like a mistake to me; even though the index signature "makes the property redundant" in the key space, the information is still there and still quite useful.

IMO, rather than producing a union we should probably be inferring from each value (indexes and properties alike) we find to the target template, rather than producing a union to infer with (and adjusting the inference priority for these inferences to allow a union result to handle the intersection-with-incompatable-index case).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But here we specifically know we're inferring to a non-homomorphic mapped type. And, we're in the final part where we're inferring to the template type, not specific properties.

Copy link
Copy Markdown
Member

@weswigham weswigham Apr 17, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, but the source is just some object type. If I'm inferring from {x: "x", [idx: string]: string} to {[X in K]: Foo<X, T>}, while technically {[X in string | "x"]: Foo<X, T>} producing {[idx: string]: Foo<string, T>} is... ok, {x: Foo<"x", T>; [idx: string]: Foo<string, T>;} would preserve more data for contextual typing and completions, which is always desirable. And to top that off, if we infer from {x: 42} & {[idx: number]: string} to {[X in K]: Foo<X, T>}, we'd want to be effectively producing {[X in "x" | number]: Foo<X, T>} which is {x: Foo<"x", T>; [idx: number]: Foo<number, T>} and inferring to both is just straight up required.

Especially if you consider Foo<X, T> could be defined along the lines of X extends "x" ? T : never, so the presence of that explicit "x" prop could change its meaning (and thus our final inference result) quite a bit!

@ahejlsberg ahejlsberg merged commit a40b08d into master Apr 17, 2019
@ahejlsberg ahejlsberg deleted the fixInferenceToMappedType branch April 17, 2019 22:10
@microsoft microsoft locked as resolved and limited conversation to collaborators Oct 21, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Inference of the Enum object as a Record fails in TS3.3.3333

2 participants