-
Notifications
You must be signed in to change notification settings - Fork 13.1k
Avoid rewriting homomorphic mapped types with homomorphic instantiations #53215
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
Avoid rewriting homomorphic mapped types with homomorphic instantiations #53215
Conversation
src/compiler/checker.ts
Outdated
| return isMappedTypeWithKeyofConstraintDeclaration(type) | ||
| && !(getModifiersTypeFromMappedType(type).flags & TypeFlags.TypeParameter); | ||
| && !(getModifiersTypeFromMappedType(type).flags & TypeFlags.TypeParameter) | ||
| && !!type.aliasSymbol; |
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.
This shouldn't have anything to do with the alias symbol as by the time we're trying to print with this function, the alias symbol has already been discarded as an option for printing the mapped type. Instead, what we care about is if the type will be picked up as a homomorphic type variable (or not). Type parameters always are, so were a conservative initial choice - it's generally safe to wrap anything else which is still recognized as a homomorphic type variable up and present it as a type parameter, too - all you get is unnecessarily verbose printback sometimes. Instead of checking the alias symbol here (which seems pretty unrelated), you want to check, rather than if the modifiers type is still a type parameter, if an index on the modifiers type still produces a homomorphic type variable. In this case, an index of a mapped type just returns the inner mapped type's constraint type, which can then be unwrapped, so it is recognized as homomorphic. So, instead of the simple, conservative !(getModifiersTypeFromMappedType(type).flags & TypeFlags.TypeParameter) check, you want something to more closely mirror getHomomorphicTypeVariable's conditions like
let index: Type;
return isMappedTypeWithKeyofConstraintDeclaration(type)
&& (index = getIndexType(getModifiersTypeFromMappedType(type)), !(index.flags & TypeFlags.Index && (index as IndexType).type.flags & TypeFlags.TypeParameter)))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 applied the suggested change but it reverted the change to the emit of this thing (TS playground):
export declare type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
export declare type PartialProperties<T, K extends keyof T> = Partial<Pick<T, K>> & Omit<T, K>;
export function doSomething_Actual<T extends {
prop: string;
}>(a: T) {
const x: { [P in keyof PartialProperties<T, "prop">]: PartialProperties<T, "prop">[P]; } = null as any;
return x;
}Does this one have to be rewritten? 🤔
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.
Probably not, since, in that case, I don't think PartialProperties is homomorphic, so the wrapping mapped type shouldn't be either. Right now we're using isMappedTypeWithKeyofConstraintDeclaration as a proxy for was-this-declared-as-homomorphic - we probably need to swap that out for something a bit more accurate, too. Potentially the same check we're doing on type but on type.target (and negated)?
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.
Indeed, it's not actually homomorphic -
doSomething_Actual<{prop: string, propb: string} | {prop: string, propa: string}>(null as any);ends up with type
{
prop?: string | undefined;
}so we just need to tighten up that side of the check, too.
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.
This particular mapped type doesn't have a .target property - I might have misunderstood your recommendation for the proposed changes but for now, I've chosen to just avoid rewriting in case of !type.target. I think this is correct (it yields the expected result for this case and the rest of the test suite) but maybe I'm missing some further details.
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.
Actually, that's perfect - if the mapped type doesn't have a .target, then leaving it as-is is always correct - it's still it's declared form, uninstantiated.
weswigham
left a comment
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, let's replace the isMappedTypeWithKeyofConstraintDeclaration check with something that more accurately checks if the declared type was homomorphic (eg, by doing the same check we do to see if type is homomorphic, but on type.target) - that should be the last piece needed here, I think.
src/compiler/checker.ts
Outdated
| function isHomomorphicMappedTypeWithNonHomomorphicInstantiation(type: MappedType) { | ||
| return isMappedTypeWithKeyofConstraintDeclaration(type) | ||
| && !(getModifiersTypeFromMappedType(type).flags & TypeFlags.TypeParameter); | ||
| if (!type.target || !isMappedTypeWithKeyofConstraintDeclaration(type)) { | ||
| return false; | ||
| } | ||
| const index = getIndexType(getModifiersTypeFromMappedType(type)); | ||
| return !(index.flags & TypeFlags.Index && (index as IndexType).type.flags & TypeFlags.TypeParameter); |
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.
So I was thinking something like
function isMappedTypeHomomorphic(type: MappedType) {
return !!getHomomorphicTypeVariable(type);
}
function isHomomorphicMappedTypeWithNonHomomorphicInstantiation(type: MappedType) {
return !type.target ? false : isMappedTypeHomomorphic(type.target) && !isMappedTypeHomomorphic(type);
}Looks easier to reason about a bit, too, by replacing the complex check I proposed before with a direct getHomomorphicTypeVariable call on the type and its' target.
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.
nice, this is definitely is easier to reason about 👍 pushed out the suggested change
…e-rewriting-in-declarations
weswigham
left a comment
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.
let's just double check the tests...
|
@typescript-bot test this |
|
Heya @weswigham, I've started to run the diff-based top-repos suite on this PR at 4b6afba. You can monitor the build here. Update: The results are in! |
|
Heya @weswigham, I've started to run the extended test suite on this PR at 4b6afba. You can monitor the build here. |
|
Heya @weswigham, I've started to run the parallelized Definitely Typed test suite on this PR at 4b6afba. You can monitor the build here. Update: The results are in! |
|
Heya @weswigham, I've started to run the perf test suite on this PR at 4b6afba. You can monitor the build here. Update: The results are in! |
|
@weswigham Here they are:
CompilerComparison Report - main..53215
System
Hosts
Scenarios
TSServerComparison Report - main..53215
System
Hosts
Scenarios
StartupComparison Report - main..53215
System
Hosts
Scenarios
Developer Information: |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@weswigham Here are the results of running the top-repos suite comparing Everything looks good! |
|
Hey @weswigham, it looks like the DT test run failed. Please check the log for more details. |
fixes #53109
I'm not 100% sure of this change but I can't find why a non-aliased mapped type would have to be rewritten. The PR that introduced this rewriting also mentions explicitly that it's about "inlined mapped types" (PR here) so I think that the ones that are already inline can be just left as-is.