-
Notifications
You must be signed in to change notification settings - Fork 13.4k
Fixed an issue with not being able to use mapped type over union constraint as rest param #49947
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
Changes from 5 commits
6554965
f94efbd
ce8483c
29d4bb9
940e1ad
88764fe
8e78414
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13861,7 +13861,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { | |
| const typeVariable = getHomomorphicTypeVariable(type); | ||
| if (typeVariable && !type.declaration.nameType) { | ||
| const constraint = getConstraintOfTypeParameter(typeVariable); | ||
| if (constraint && isArrayType(constraint)) { | ||
| if (constraint && everyType(constraint, isArrayOrTupleType)) { | ||
| return instantiateType(type, prependTypeMapping(typeVariable, constraint, type.mapper)); | ||
| } | ||
| } | ||
|
|
@@ -21655,7 +21655,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { | |
| return varianceResult; | ||
| } | ||
| } | ||
| else if (isReadonlyArrayType(target) ? isArrayOrTupleType(source) : isArrayType(target) && isTupleType(source) && !source.target.readonly) { | ||
| else if (isReadonlyArrayType(target) ? everyType(source, isArrayOrTupleType) : isArrayType(target) && everyType(source, t => isTupleType(t) && !t.target.readonly)) { | ||
| if (relation !== identityRelation) { | ||
| return isRelatedTo(getIndexTypeOfType(source, numberType) || anyType, getIndexTypeOfType(target, numberType) || anyType, RecursionFlags.Both, reportErrors); | ||
| } | ||
|
|
@@ -29205,7 +29205,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { | |
| getContextualType(node, contextFlags); | ||
| const instantiatedType = instantiateContextualType(contextualType, node, contextFlags); | ||
| if (instantiatedType && !(contextFlags && contextFlags & ContextFlags.NoConstraints && instantiatedType.flags & TypeFlags.TypeVariable)) { | ||
| const apparentType = mapType(instantiatedType, getApparentType, /*noReductions*/ true); | ||
| const apparentType = mapType( | ||
| instantiatedType, | ||
| // When obtaining apparent type of *contextual* type we don't want to get apparent type of mapped types. | ||
| // That would evaluate mapped types with array or tuple type constraints too eagerly | ||
| // and thus it would prevent `getTypeOfPropertyOfContextualType` from obtaining per-position contextual type for elements of array literal expressions. | ||
| // Apparent type of other mapped types is already the mapped type itself so we can just avoid calling `getApparentType` here for all mapped types. | ||
| t => getObjectFlags(t) & ObjectFlags.Mapped ? t : getApparentType(t), | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This might also need to handle intersections better, their mapped types parts should be left alone similarly. I don't have a test case to validate this at hand though so I refrained from making further changes to this. However, I think that I know how to write a sensible test case for this with the changes from this PR. So I would like to wait for one of them to land so I could tweak this part of the code and add the appropriate test case to the other PR.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And I pushed out this extra fix here: 56f12bc
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would you care to open a new PR for the further improvement? Since it comes with a test case proving it's utility, it looks pretty good - will just need to make sure the extended suites stay clean when that handling is in place.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, I thought that this would be linked better - this commit is already part of the #52062 :) it only made sense for me to add this change there as that PR was already concerned with intersected mapped types and thus I already had an idea what a test to write there for this patch
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ahh, OK. |
||
| /*noReductions*/ true | ||
| ); | ||
| return apparentType.flags & TypeFlags.Union && isObjectLiteralExpression(node) ? discriminateContextualTypeByObjectMembers(node, apparentType as UnionType) : | ||
| apparentType.flags & TypeFlags.Union && isJsxAttributes(node) ? discriminateContextualTypeByJSXAttributes(node, apparentType as UnionType) : | ||
| apparentType; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| tests/cases/compiler/mappedTypeUnionConstrainTupleTreatedAsArrayLike.ts(9,9): error TS2322: Type 'HomomorphicMappedType<T>' is not assignable to type 'any[]'. | ||
|
|
||
|
|
||
| ==== tests/cases/compiler/mappedTypeUnionConstrainTupleTreatedAsArrayLike.ts (1 errors) ==== | ||
| type HomomorphicMappedType<T> = { [P in keyof T]: T[P] extends string ? boolean : null } | ||
|
|
||
| function test1<T extends [number] | [string]>(args: T) { | ||
| const arr: any[] = [] as HomomorphicMappedType<T> | ||
| const arr2: readonly any[] = [] as HomomorphicMappedType<T> | ||
| } | ||
|
|
||
| function test2<T extends [number] | readonly [string]>(args: T) { | ||
| const arr: any[] = [] as HomomorphicMappedType<T> // error | ||
| ~~~ | ||
| !!! error TS2322: Type 'HomomorphicMappedType<T>' is not assignable to type 'any[]'. | ||
| const arr2: readonly any[] = [] as HomomorphicMappedType<T> | ||
| } | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| === tests/cases/compiler/mappedTypeUnionConstrainTupleTreatedAsArrayLike.ts === | ||
| type HomomorphicMappedType<T> = { [P in keyof T]: T[P] extends string ? boolean : null } | ||
| >HomomorphicMappedType : Symbol(HomomorphicMappedType, Decl(mappedTypeUnionConstrainTupleTreatedAsArrayLike.ts, 0, 0)) | ||
| >T : Symbol(T, Decl(mappedTypeUnionConstrainTupleTreatedAsArrayLike.ts, 0, 27)) | ||
| >P : Symbol(P, Decl(mappedTypeUnionConstrainTupleTreatedAsArrayLike.ts, 0, 35)) | ||
| >T : Symbol(T, Decl(mappedTypeUnionConstrainTupleTreatedAsArrayLike.ts, 0, 27)) | ||
| >T : Symbol(T, Decl(mappedTypeUnionConstrainTupleTreatedAsArrayLike.ts, 0, 27)) | ||
| >P : Symbol(P, Decl(mappedTypeUnionConstrainTupleTreatedAsArrayLike.ts, 0, 35)) | ||
|
|
||
| function test1<T extends [number] | [string]>(args: T) { | ||
| >test1 : Symbol(test1, Decl(mappedTypeUnionConstrainTupleTreatedAsArrayLike.ts, 0, 88)) | ||
| >T : Symbol(T, Decl(mappedTypeUnionConstrainTupleTreatedAsArrayLike.ts, 2, 15)) | ||
| >args : Symbol(args, Decl(mappedTypeUnionConstrainTupleTreatedAsArrayLike.ts, 2, 46)) | ||
| >T : Symbol(T, Decl(mappedTypeUnionConstrainTupleTreatedAsArrayLike.ts, 2, 15)) | ||
|
|
||
| const arr: any[] = [] as HomomorphicMappedType<T> | ||
| >arr : Symbol(arr, Decl(mappedTypeUnionConstrainTupleTreatedAsArrayLike.ts, 3, 7)) | ||
| >HomomorphicMappedType : Symbol(HomomorphicMappedType, Decl(mappedTypeUnionConstrainTupleTreatedAsArrayLike.ts, 0, 0)) | ||
| >T : Symbol(T, Decl(mappedTypeUnionConstrainTupleTreatedAsArrayLike.ts, 2, 15)) | ||
|
|
||
| const arr2: readonly any[] = [] as HomomorphicMappedType<T> | ||
| >arr2 : Symbol(arr2, Decl(mappedTypeUnionConstrainTupleTreatedAsArrayLike.ts, 4, 7)) | ||
| >HomomorphicMappedType : Symbol(HomomorphicMappedType, Decl(mappedTypeUnionConstrainTupleTreatedAsArrayLike.ts, 0, 0)) | ||
| >T : Symbol(T, Decl(mappedTypeUnionConstrainTupleTreatedAsArrayLike.ts, 2, 15)) | ||
| } | ||
|
|
||
| function test2<T extends [number] | readonly [string]>(args: T) { | ||
| >test2 : Symbol(test2, Decl(mappedTypeUnionConstrainTupleTreatedAsArrayLike.ts, 5, 1)) | ||
| >T : Symbol(T, Decl(mappedTypeUnionConstrainTupleTreatedAsArrayLike.ts, 7, 15)) | ||
| >args : Symbol(args, Decl(mappedTypeUnionConstrainTupleTreatedAsArrayLike.ts, 7, 55)) | ||
| >T : Symbol(T, Decl(mappedTypeUnionConstrainTupleTreatedAsArrayLike.ts, 7, 15)) | ||
|
|
||
| const arr: any[] = [] as HomomorphicMappedType<T> // error | ||
| >arr : Symbol(arr, Decl(mappedTypeUnionConstrainTupleTreatedAsArrayLike.ts, 8, 7)) | ||
| >HomomorphicMappedType : Symbol(HomomorphicMappedType, Decl(mappedTypeUnionConstrainTupleTreatedAsArrayLike.ts, 0, 0)) | ||
| >T : Symbol(T, Decl(mappedTypeUnionConstrainTupleTreatedAsArrayLike.ts, 7, 15)) | ||
|
|
||
| const arr2: readonly any[] = [] as HomomorphicMappedType<T> | ||
| >arr2 : Symbol(arr2, Decl(mappedTypeUnionConstrainTupleTreatedAsArrayLike.ts, 9, 7)) | ||
| >HomomorphicMappedType : Symbol(HomomorphicMappedType, Decl(mappedTypeUnionConstrainTupleTreatedAsArrayLike.ts, 0, 0)) | ||
| >T : Symbol(T, Decl(mappedTypeUnionConstrainTupleTreatedAsArrayLike.ts, 7, 15)) | ||
| } | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| === tests/cases/compiler/mappedTypeUnionConstrainTupleTreatedAsArrayLike.ts === | ||
| type HomomorphicMappedType<T> = { [P in keyof T]: T[P] extends string ? boolean : null } | ||
| >HomomorphicMappedType : HomomorphicMappedType<T> | ||
|
|
||
| function test1<T extends [number] | [string]>(args: T) { | ||
| >test1 : <T extends [number] | [string]>(args: T) => void | ||
| >args : T | ||
|
|
||
| const arr: any[] = [] as HomomorphicMappedType<T> | ||
| >arr : any[] | ||
| >[] as HomomorphicMappedType<T> : HomomorphicMappedType<T> | ||
| >[] : [] | ||
|
|
||
| const arr2: readonly any[] = [] as HomomorphicMappedType<T> | ||
| >arr2 : readonly any[] | ||
| >[] as HomomorphicMappedType<T> : HomomorphicMappedType<T> | ||
| >[] : [] | ||
| } | ||
|
|
||
| function test2<T extends [number] | readonly [string]>(args: T) { | ||
| >test2 : <T extends [number] | readonly [string]>(args: T) => void | ||
| >args : T | ||
|
|
||
| const arr: any[] = [] as HomomorphicMappedType<T> // error | ||
| >arr : any[] | ||
| >[] as HomomorphicMappedType<T> : HomomorphicMappedType<T> | ||
| >[] : [] | ||
|
|
||
| const arr2: readonly any[] = [] as HomomorphicMappedType<T> | ||
| >arr2 : readonly any[] | ||
| >[] as HomomorphicMappedType<T> : HomomorphicMappedType<T> | ||
| >[] : [] | ||
| } | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| === tests/cases/compiler/restParamUsingMappedTypeOverUnionConstraint.ts === | ||
| // repro 29919#issuecomment-470948453 | ||
|
|
||
| type HomomorphicMappedType<T> = { [P in keyof T]: T[P] extends string ? boolean : null } | ||
| >HomomorphicMappedType : Symbol(HomomorphicMappedType, Decl(restParamUsingMappedTypeOverUnionConstraint.ts, 0, 0)) | ||
| >T : Symbol(T, Decl(restParamUsingMappedTypeOverUnionConstraint.ts, 2, 27)) | ||
| >P : Symbol(P, Decl(restParamUsingMappedTypeOverUnionConstraint.ts, 2, 35)) | ||
| >T : Symbol(T, Decl(restParamUsingMappedTypeOverUnionConstraint.ts, 2, 27)) | ||
| >T : Symbol(T, Decl(restParamUsingMappedTypeOverUnionConstraint.ts, 2, 27)) | ||
| >P : Symbol(P, Decl(restParamUsingMappedTypeOverUnionConstraint.ts, 2, 35)) | ||
|
|
||
| declare function test<T extends [number] | [string]>( | ||
| >test : Symbol(test, Decl(restParamUsingMappedTypeOverUnionConstraint.ts, 2, 88)) | ||
| >T : Symbol(T, Decl(restParamUsingMappedTypeOverUnionConstraint.ts, 4, 22)) | ||
|
|
||
| args: T, | ||
| >args : Symbol(args, Decl(restParamUsingMappedTypeOverUnionConstraint.ts, 4, 53)) | ||
| >T : Symbol(T, Decl(restParamUsingMappedTypeOverUnionConstraint.ts, 4, 22)) | ||
|
|
||
| fn: (...args: HomomorphicMappedType<T>) => void | ||
| >fn : Symbol(fn, Decl(restParamUsingMappedTypeOverUnionConstraint.ts, 5, 10)) | ||
| >args : Symbol(args, Decl(restParamUsingMappedTypeOverUnionConstraint.ts, 6, 7)) | ||
| >HomomorphicMappedType : Symbol(HomomorphicMappedType, Decl(restParamUsingMappedTypeOverUnionConstraint.ts, 0, 0)) | ||
| >T : Symbol(T, Decl(restParamUsingMappedTypeOverUnionConstraint.ts, 4, 22)) | ||
|
|
||
| ): void | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| === tests/cases/compiler/restParamUsingMappedTypeOverUnionConstraint.ts === | ||
| // repro 29919#issuecomment-470948453 | ||
|
|
||
| type HomomorphicMappedType<T> = { [P in keyof T]: T[P] extends string ? boolean : null } | ||
| >HomomorphicMappedType : HomomorphicMappedType<T> | ||
|
|
||
| declare function test<T extends [number] | [string]>( | ||
| >test : <T extends [number] | [string]>(args: T, fn: (...args: HomomorphicMappedType<T>) => void) => void | ||
|
|
||
| args: T, | ||
| >args : T | ||
|
|
||
| fn: (...args: HomomorphicMappedType<T>) => void | ||
| >fn : (...args: HomomorphicMappedType<T>) => void | ||
| >args : HomomorphicMappedType<T> | ||
|
|
||
| ): void | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| // @noEmit: true | ||
| // @strict: true | ||
|
|
||
| type HomomorphicMappedType<T> = { [P in keyof T]: T[P] extends string ? boolean : null } | ||
|
|
||
| function test1<T extends [number] | [string]>(args: T) { | ||
| const arr: any[] = [] as HomomorphicMappedType<T> | ||
| const arr2: readonly any[] = [] as HomomorphicMappedType<T> | ||
| } | ||
|
|
||
| function test2<T extends [number] | readonly [string]>(args: T) { | ||
| const arr: any[] = [] as HomomorphicMappedType<T> // error | ||
| const arr2: readonly any[] = [] as HomomorphicMappedType<T> | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| // @noEmit: true | ||
| // @strict: true | ||
|
|
||
| // repro 29919#issuecomment-470948453 | ||
|
|
||
| type HomomorphicMappedType<T> = { [P in keyof T]: T[P] extends string ? boolean : null } | ||
|
|
||
| declare function test<T extends [number] | [string]>( | ||
| args: T, | ||
| fn: (...args: HomomorphicMappedType<T>) => void | ||
| ): void |
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.
In a way, this reverts the recent change from #52651 . That issue is now fixed by avoiding getting the apparent type of mapped types within
getApparentTypeOfContextualType- you can see the change in the commit hereI think this is a better fix for that issue anyway, I just didn't figure it out earlier. Getting the "resolved" apparent type for mapped types with tuple constraint (and array) constraints is important for relationship checking. The mapped type that resolves~ to a tuple is usually "normalized" here, that allows tuple/array-oriented branches to relate things.