-
Notifications
You must be signed in to change notification settings - Fork 12.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add optionality to mapped type indexed access substitutions #57549
Conversation
@typescript-bot test top200 |
Heya @ahejlsberg, I've started to run the faster perf test suite on this PR at ab3184b. You can monitor the build here. Update: The results are in! |
Heya @ahejlsberg, I've started to run the diff-based top-repos suite on this PR at ab3184b. You can monitor the build here. Update: The results are in! |
Heya @ahejlsberg, I've started to run the parallelized Definitely Typed test suite on this PR at ab3184b. You can monitor the build here. Update: The results are in! |
Heya @ahejlsberg, I've started to run the diff-based user code test suite on this PR at ab3184b. You can monitor the build here. Update: The results are in! |
@ahejlsberg Here are the results of running the user test suite comparing There were infrastructure failures potentially unrelated to your change:
Otherwise... Something interesting changed - please have a look. Details
|
@ahejlsberg Here they are:
tscComparison Report - baseline..pr
System info unknown
Hosts
Scenarios
Developer Information: |
Hey @ahejlsberg, the results of running the DT tests are ready. |
getObjectFlags(type) & ObjectFlags.Mapped ? getMappedTypeOptionality(type as MappedType) : | ||
0; | ||
} | ||
|
||
function getCombinedMappedTypeOptionality(type: MappedType): number { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I'm not mistaken this currently falls short here:
type Obj = {
a: 1,
b: 2
};
type Identity<T> = { [K in keyof T]: T[K] };
const mapped: {
[K in keyof Identity<Partial<Obj>>]: Obj[K]
} = {}
const resolveMapped = <K extends keyof typeof mapped>(key: K) => mapped[key].toString(); // should error but it doesnt
Some extra handling of homomorphic mapped types might have to be added here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, looks like we need to recurse in getCombinedMappedTypeOptionality
.
@ahejlsberg Here are the results of running the top-repos suite comparing Everything looks good! |
@@ -18517,7 +18521,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { | |||
function substituteIndexedMappedType(objectType: MappedType, index: Type) { | |||
const mapper = createTypeMapper([getTypeParameterFromMappedType(objectType)], [index]); | |||
const templateMapper = combineTypeMappers(objectType.mapper, mapper); | |||
return instantiateType(getTemplateTypeFromMappedType(objectType.target as MappedType || objectType), templateMapper); | |||
const instantiatedTemplateType = instantiateType(getTemplateTypeFromMappedType(objectType.target as MappedType || objectType), templateMapper); | |||
return addOptionality(instantiatedTemplateType, /*isProperty*/ true, getCombinedMappedTypeOptionality(objectType) > 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Taking known properties into consideration would fix this inconsistency that this PR creates:
type Obj = {
a: string;
b: number;
};
type Obj2 = {
b: number;
c: boolean;
};
declare const mapped: {
[K in keyof (Partial<Obj> & Required<Obj2>)]: number;
};
// displays the same way as `resolved` below!
mapped;
// ^? const mapped: { a?: number | undefined; b: number; c: number; }
const accessMapped = <K extends keyof Obj2>(key: K) => mapped[key].toString();
declare const resolved: { a?: number | undefined; b: number; c: number };
const accessResolved = <K extends keyof Obj2>(key: K) => resolved[key].toString();
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, when mapping over intersections constructed from multiple mapped types the PR makes the most conservative assumption about optionality. I think that's an acceptable compromise and I'm concerned with the potential cost of examining every member possibly selected by the key.
@@ -14040,10 +14040,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { | |||
return modifiers & MappedTypeModifiers.ExcludeOptional ? -1 : modifiers & MappedTypeModifiers.IncludeOptional ? 1 : 0; | |||
} | |||
|
|||
function getModifiersTypeOptionality(type: Type): number { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand what this is computing. What does a result of 2
mean?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The result is always either -1, 0, or 1. -1 means optionality is stripped (i.e. -?
), 0 means optionality is unchanged, 1 means optionality is added (i.e. +?
). When a homomorphic mapped type doesn't modify optionality, we (recursively) consult the optionality of the type you're mapping over to see if it strips or adds optionality.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be an enum? Then it'd be easier to tell what the numbers mean.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It could be an enum, but number
feels more appropriate since we use the greater and less than operators to relate the combined values.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would a comment about what each number represents work?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Behavior looks good
Fixes #57487.