-
Notifications
You must be signed in to change notification settings - Fork 12.8k
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # 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 |
@DanielRosenwasser Do you agree with the changes I suggest above? |
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