-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Check for array types when instantiating mapped type constraints with any
#46218
Conversation
… like in `Promise.all`.
@typescript-bot pack this |
Heya @DanielRosenwasser, I've started to run the parallelized community code test suite on this PR at 99d9b56. You can monitor the build here. |
Heya @DanielRosenwasser, I've started to run the tarball bundle task on this PR at 99d9b56. You can monitor the build here. |
Heya @DanielRosenwasser, I've started to run the extended test suite on this PR at 99d9b56. You can monitor the build here. |
Hey @DanielRosenwasser, I've packed this into an installable tgz. You can install it for testing by referencing it in your
and then running There is also a playground for this build and an npm module you can use via |
The user suite test run you requested has finished and failed. I've opened a PR with the baseline diff from master. |
arr = indirectArrayish; | ||
~~~ | ||
!!! error TS2322: Type 'Objectish<any>' is not assignable to type 'any[]'. |
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.
My intent was that IndirectArrayish<U>
should be a mapped object type that has a type variable constraint of unknown[]
; it seems like that might not be the case, so I actually might need some help 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.
I think you'd need a new assignability rule for mapped types matching that pattern to accomplish that.
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 think I see what you're saying - that you need to have something recursive that checks if you're arrayish or a generic mapped type that would produce an array result.
function mappedTypeConstraintProducesArrayResult(type: Type) {
if (isArrayType(type) || isTupleType(type)) return true;
if (type.flags & TypeFlags.Union) return everyType(type, mappedTypeConstraintProducesArrayResult);
const typeVariable = type.flags & TypeFlags.MappedType && getHomomorphicTypeVariable(type as MappedType);
if (typeVariable && !type.declaration.nameType) {
const constraint = getConstraintOfTypeParameter(typeVariable);
return !!(constraint && mappedTypeConstraintProducesArrayResult(constraint));
}
return false;
}
But IndirectArrayish<any>
and Objectish<any>
aren't generic mapped types, are they?
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.
Uhh, I was thinking of something like relating a generic IndirectArrayish<U>
to a U[]
. In this case, I don't think we can distinguish between a Objectish<any>
and an IndirectArrayish<any>
since they're exactly equivalent - the "is this constrained to arrayish types only" check you have is purely syntactic (it only cares if the mapped type was both declared homomorphic and that variable is array-constrained) - it can't pick up information from wrapping declarations or anything.
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 guess the intent here was that when we declared IndirectArrayish<U extends unknown[]>
, we'd end up with a fresh generic mapped type whose type variable (U
) has the array constraint. Is that not what's happening?
@@ -16515,7 +16515,8 @@ namespace ts { | |||
return mapTypeWithAlias(getReducedType(mappedTypeVariable), t => { | |||
if (t.flags & (TypeFlags.AnyOrUnknown | TypeFlags.InstantiableNonPrimitive | TypeFlags.Object | TypeFlags.Intersection) && t !== wildcardType && !isErrorType(t)) { | |||
if (!type.declaration.nameType) { | |||
if (isArrayType(t)) { | |||
let constraint; | |||
if (isArrayType(t) || (t.flags & TypeFlags.Any) && (constraint = getConstraintOfTypeParameter(typeVariable)) && everyType(constraint, or(isArrayType, isTupleType))) { |
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 (isArrayType(t) || (t.flags & TypeFlags.Any) && (constraint = getConstraintOfTypeParameter(typeVariable)) && everyType(constraint, or(isArrayType, isTupleType))) { | |
if (isArrayType(t) || (t.flags & TypeFlags.Any) && (constraint = getConstraintOfTypeParameter(typeVariable)) && everyType(constraint, isArrayOrTupleLikeType)) { | |
? Or is including the x-like types (length properties and 0 properties) going to mess up the behavior in a case we care about? I'm thinking passing any
to a T extends {length: number}
may also justify an array mapping rather than a string
index signature.
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 thought about the tuple-like type thing, and that's one concern - I'm already not 100% convinced that that would always be desirable. The bigger concern is the implementation of isArrayLikeType
:
return isArrayType(type) || !(type.flags & TypeFlags.Nullable) && isTypeAssignableTo(type, anyReadonlyArrayType);
which I think would consider any
an array-like type.
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.
Raw any
in a constraint position is eagerly replaced with unknown
nowadays (so it behaves as a proper top type constraint and not as an odd anyish thing) so it shouldn't be an issue I don't think.
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 think that might be fine, but for now there's some precedent in getResolvedApparentTypeOfMappedType
for the current behavior.
I don't think it's quite the same, but is this at all related to (or will it have any impact on) #27995? |
I don't think so - this only affects situations when you have a mapped type over |
The basic change looks good to me and I don't think it's necessary (or even feasible) to check for "arrayishness" in the indirect case. However, the new behavior isn't right for type parameters constrained to tuple types since |
Sorry I missed your comment - I agree, and I've updated the PR to only act specially on array types. I've also updated the comment in the test case to explain some of the limitations/concerns we talked about. |
@ahejlsberg just hit a problem - the whole motivation of the original logic was that the type parameter of |
I'm inclined to go back to the original proposal; alternatively, I can change the current implementation to check if the union type contains only array or tuple types, and contains at least one array type. |
What is “the original proposal”?
This sounds weirdly specific, but not terrible. |
The original proposal is that we just check if all types are array-like or tuple-like - basically the behavior prior to this commit f01ae16 |
This reverts commit f01ae16.
Latest commits (46283e2) brings that back. |
@typescript-bot pack this I know @ahejlsberg had concerns about the correctness of this for tuple types in #46218 (comment). |
Heya @andrewbranch, I've started to run the tarball bundle task on this PR at b3492a9. You can monitor the build here. |
Hey @andrewbranch, I've packed this into an installable tgz. You can install it for testing by referencing it in your
and then running There is also a playground for this build and an npm module you can use via |
@DanielRosenwasser Am I confused, or did you first implement the change I suggested, and then revert again? |
@DanielRosenwasser Ah, I see your reason here. In that case I'm fine with the your original behavior, since our old behavior for tuples wasn't right either. |
Thanks for the reviews! |
This breaks ramda types on DT. I'll file a bug. |
… `any` (microsoft#46218) * Added/updated tests. * Accepted baselines. * Update test. * Update instantiateMappedType to work specially when 'any' replaced an array. * Accepted baselines. * Ensure check works when constraint is a union of arrayish types, just like in `Promise.all`. * Accepted baselines. * Update test for indirect instantiation of a mapped type. * Accepted baselines. * Update test comment. * Accepted baselines. * Added tuple test case. * Accepted baselines. * Don't add special behavior for tuples. * Accepted baselines. * Revert "Don't add special behavior for tuples." This reverts commit f01ae16. * Accepted baselines.
This introduces a change to mapped types so that homomorphic-like mapped types play better with
any
.Today, when instantiating a mapped types on
keyof T
, an instantiation ofT
with an array-like type behaves differently than with other object types. This allows us to reuse the same mapped types syntax with different kinds of types, but forces us to make a decision when we've hit a type likeany
. When instantiating withany
, we don't consider it to be array-like in any way, and we produce{ [x: string]: any }
.This is fine in the above example, but is undesirable when constraints would otherwise only allow array-like types.
The constraints imply that the above mapped type should always produce an array; however,
any
throws a wrench in the equation. This change introduces a new rule to account for this strange behavior.Instantiating a mapped type
{ [K in keyof T]: ... }
now producesany[]
if the instantiation ofT
isany
, and ifT
is constrained by only array and tuple types.This provides better behavior for
Promise.all
when passing in an argument of typeany
instead of an array.Fixes #46169