-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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
Make any[]
the effective rest type when any
is spread
#50034
Conversation
This PR doesn't have any linked issues. Please open an issue that references this PR. From there we can discuss and prioritise. |
>x : string | ||
>args : T | ||
|
||
type U17<T extends any[]> = InstanceType<abstract new (x: string, ...args: T) => T[]>; // T[] | ||
>U17 : T[] | ||
>U17 : U17<T> |
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.
Those 3 changes here are basically caused by the same issue.
Because target
/source
is now any[]
and not any
when we start comparing the signatures within the conditional type we don't pass the isTypeAssignable
check here. Because of that the conditional type can't be "simplified" here to the trueType
and thus the displayed type changed.
This check reaches the structuredTypeRelatedToWorker
with types like:
source // any[]
target // [x: string, ...args: T]
I've expected this to be satisfied somewhere around this check but actually the target
is not an array - it's a tuple and there is no logic supporting tuple targets here. The question is if this is deliberate or just an accidental omission?
I've played around with this more though and I've noticed 2 additional problems here though:
EDIT:// I know now that it's not how it's supposed to work, tuples might come with required elements and stuff andany[]
is not assignable to[string, ...any[]]
. I understand why the error is reported for this case but in the presence ofany
in the type system it feels weird. Intuitively I've expectedany[]
to be assignable to all arrays and tuples, similarly to howanyFunctionType
is always assignable, regardless of the parameters count etc, see the check hereany[]
doesn't guarantee that- even if the previous point would get "fixed" then the base constraint for
T
here is unknown and notany[]
. I don't understand why as I've expected the latter.
@@ -490,6 +490,6 @@ const result = invoker('test', true)({ test: (a: boolean) => 123 }) | |||
>123 : 123 | |||
|
|||
type Foo2<A extends any[]> = ReturnType<(...args: A) => string>; | |||
>Foo2 : string | |||
>Foo2 : Foo2<A> |
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 change is caused by pretty similar stuff to the one described here.
The only~ difference here is that we get:
source // any[]
target // A
and there is not a lot of logic related to handling type params in the structuredTypeRelatedToWorker
. So this target
stays as A
, its constraint is not really requested - but, again, the constraint for some reason is unknown
, instead of any[]
. Even though T
's declaration points to T extends any[]
.
# Conflicts: # src/compiler/checker.ts
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'd like to check with the team first if I'm on the right path and if this change makes any sense.
Maybe? On its' own, I wouldn't really want to take this change. With some actual motivating test cases and alongside whatever other changes are needed to fix those? More likely. On the other hand, the original issue is pretty easy to work around. Toss in an extra Extract<whatever, any[]>
and bam, constrained to an array type and now valid to spread. So if there are any adverse effects on existing code, it's pretty unlikely we'd take the change. Plus, why only any
- either never
would conceivably behave the same depending on if we're talking about the read or write constraint of the signature. For complexity's sake, it may just be better to expect the Extract<T, any[]>
workaround.
But, to go back to the original issue, I honestly think a better fix would be in the constraint calculation for homomorphic mapped types, rather than this. Specifically, right now they don't have a constraint (they're just themselves), and since Parameters<GenericThing>
is still... generic... when the mapped type is instantiated with it, you still get a mapped type out, rather than an array or tuple type. We could add a constraint to the mapped type, such that when the modifiers type of the mapped type is constrained to an array or tuple type, you get a (readonly, depending on constraint) array of the substituted template type as the constraint - basically removing the mapped-type-y-ness from the constraint calculation. Not sure if that'd be a perfect solution, but I think I'd be happier with that direction than a syntactic approach.
This sounds nice (and kinda what I'm trying to achieve here) but the That's what this PR was meant to solve - it brought array-ness to this |
The TypeScript team hasn't accepted the linked issue #29919. If you can get it accepted, this PR will have a better chance of being reviewed. |
It's worth mentioning that a syntactic approach is already used by // When an 'infer T' declaration is immediately contained in a rest parameter declaration, a rest type
// or a named rest tuple element, we infer an 'unknown[]' constraint. But the problem here is that the It also turns out that in the meantime I have fixed the other part of the underlying issue (see #56727 ). So this PR now fixes #29919 . Of course, I understand that you might still want a different fix for this but I fail to see how to properly propagate this arrayness without relying on the syntax or without introducing some internal |
@typescript-bot test top100 |
Heya @ahejlsberg, I've started to run the faster perf test suite on this PR at 1c1506a. 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 1c1506a. 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 1c1506a. 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 1c1506a. 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. Branch only errors:Package: lodash
Package: es-abstract
|
@ahejlsberg Here are the results of running the top-repos suite comparing Something interesting changed - please have a look. Details
|
All of the above failures are lodash. Code like: flowRight<A extends any[]>(f1: (...args: A) => Parameters<T>["0"]): Function<(...args: A) => ReturnType<T>>; |
I’ll investigate this as soon as I can 😉 declare module "lodash" {
interface Function<T extends (...args: any) => any> {
iteratee(): Function<T>;
}
interface Function<T> {
flowRight<A extends any[], R1>(
f2: (a: R1) => Parameters<T>["0"],
f1: (...args: A) => R1,
): void;
}
} |
Hmm, I don't think so. There are instantiations of |
This one doesn't error though: type A<T extends any[]> = T[0] Should they both be errors then? 😉 If they should error then I'd say that the lodash failure above is correct and shouldn't get fixed. |
Well, that's nice and inconsistent. It appears |
So I got this working with this patch: `getLiteralTypeFromProperties` patchdiff --git a/src/compiler/checker.ts b/src/compiler/checker.ts
index dd24f9be31..29abbfa96a 100644
--- a/src/compiler/checker.ts
+++ b/src/compiler/checker.ts
@@ -17856,7 +17856,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const propertyTypes = map(getPropertiesOfType(type), prop => getLiteralTypeFromProperty(prop, include));
const indexKeyTypes = map(getIndexInfosOfType(type), info =>
info !== enumNumberIndexInfo && isKeyTypeIncluded(info.keyType, include) ?
- info.keyType === stringType && include & TypeFlags.Number ? stringOrNumberType : info.keyType : neverType);
+ info.keyType === stringType && include & TypeFlags.Number ? stringOrNumberType : info.keyType === numberType ? getUnionType([numberType, numericStringType]) : info.keyType : neverType);
return getUnionType(concatenate(propertyTypes, indexKeyTypes), UnionReduction.Literal, /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined, origin);
} That broke some things: Failed testsdiff --git a/tests/baselines/reference/conditionalTypes2.errors.txt b/tests/baselines/reference/conditionalTypes2.errors.txt
index de78fe2d37..d5c6945d1e 100644
--- a/tests/baselines/reference/conditionalTypes2.errors.txt
+++ b/tests/baselines/reference/conditionalTypes2.errors.txt
@@ -13,10 +13,10 @@ conditionalTypes2.ts(24,5): error TS2322: Type 'Invariant<B>' is not assignable
Type 'keyof B' is not assignable to type 'keyof A'.
Type 'string | number | symbol' is not assignable to type 'keyof A'.
Type 'string' is not assignable to type 'keyof A'.
- Type 'string' is not assignable to type 'number | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "length" | "substr" | "valueOf"'.
- Type 'keyof B' is not assignable to type 'number | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "length" | "substr" | "valueOf"'.
- Type 'string | number | symbol' is not assignable to type 'number | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "length" | "substr" | "valueOf"'.
- Type 'string' is not assignable to type 'number | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "length" | "substr" | "valueOf"'.
+ Type 'string' is not assignable to type 'number | `${number}` | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "length" | "substr" | "valueOf"'.
+ Type 'keyof B' is not assignable to type 'number | `${number}` | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "length" | "substr" | "valueOf"'.
+ Type 'string | number | symbol' is not assignable to type 'number | `${number}` | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "length" | "substr" | "valueOf"'.
+ Type 'string' is not assignable to type 'number | `${number}` | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "length" | "substr" | "valueOf"'.
conditionalTypes2.ts(25,5): error TS2322: Type 'Invariant<A>' is not assignable to type 'Invariant<B>'.
Types of property 'foo' are incompatible.
Type 'A extends string ? keyof A : A' is not assignable to type 'B extends string ? keyof B : B'.
@@ -79,10 +79,10 @@ conditionalTypes2.ts(75,12): error TS2345: Argument of type 'Extract2<T, Foo, Ba
!!! error TS2322: Type 'keyof B' is not assignable to type 'keyof A'.
!!! error TS2322: Type 'string | number | symbol' is not assignable to type 'keyof A'.
!!! error TS2322: Type 'string' is not assignable to type 'keyof A'.
-!!! error TS2322: Type 'string' is not assignable to type 'number | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "length" | "substr" | "valueOf"'.
-!!! error TS2322: Type 'keyof B' is not assignable to type 'number | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "length" | "substr" | "valueOf"'.
-!!! error TS2322: Type 'string | number | symbol' is not assignable to type 'number | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "length" | "substr" | "valueOf"'.
-!!! error TS2322: Type 'string' is not assignable to type 'number | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "length" | "substr" | "valueOf"'.
+!!! error TS2322: Type 'string' is not assignable to type 'number | `${number}` | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "length" | "substr" | "valueOf"'.
+!!! error TS2322: Type 'keyof B' is not assignable to type 'number | `${number}` | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "length" | "substr" | "valueOf"'.
+!!! error TS2322: Type 'string | number | symbol' is not assignable to type 'number | `${number}` | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "length" | "substr" | "valueOf"'.
+!!! error TS2322: Type 'string' is not assignable to type 'number | `${number}` | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "length" | "substr" | "valueOf"'.
b = a; // Error
~
!!! error TS2322: Type 'Invariant<A>' is not assignable to type 'Invariant<B>'.
diff --git a/tests/baselines/reference/deferredLookupTypeResolution2.types b/tests/baselines/reference/deferredLookupTypeResolution2.types
index b67a3bc92c..e638c63a2c 100644
--- a/tests/baselines/reference/deferredLookupTypeResolution2.types
+++ b/tests/baselines/reference/deferredLookupTypeResolution2.types
@@ -17,7 +17,7 @@ type B = ObjectHasKey<[string, number], '1'>; // "true"
>B : "true"
type C = ObjectHasKey<[string, number], '2'>; // "false"
->C : "false"
+>C : "true"
type D = A<[string]>; // "true"
>D : "true"
diff --git a/tests/baselines/reference/keyofAndIndexedAccess.types b/tests/baselines/reference/keyofAndIndexedAccess.types
index d1aca03181..345381f895 100644
--- a/tests/baselines/reference/keyofAndIndexedAccess.types
+++ b/tests/baselines/reference/keyofAndIndexedAccess.types
@@ -60,7 +60,7 @@ type K00 = keyof any; // string
>K00 : string | number | symbol
type K01 = keyof string; // "toString" | "charAt" | ...
->K01 : number | "length" | "toString" | "concat" | "slice" | "indexOf" | "lastIndexOf" | "charAt" | "charCodeAt" | "localeCompare" | "match" | "replace" | "search" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "substr" | "valueOf"
+>K01 : number | `${number}` | "length" | "toString" | "concat" | "slice" | "indexOf" | "lastIndexOf" | "charAt" | "charCodeAt" | "localeCompare" | "match" | "replace" | "search" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "substr" | "valueOf"
type K02 = keyof number; // "toString" | "toFixed" | "toExponential" | ...
>K02 : "toString" | "toLocaleString" | "toFixed" | "toExponential" | "toPrecision" | "valueOf"
@@ -111,7 +111,7 @@ type K18 = keyof (Shape & Item); // "name" | "width" | "height" | "visible" | "
>K18 : "name" | "width" | "height" | "visible" | "price"
type K19 = keyof NumericallyIndexed<Shape> // never
->K19 : number
+>K19 : number | `${number}`
type KeyOf<T> = keyof T;
>KeyOf : keyof T
diff --git a/tests/baselines/reference/keyofAndIndexedAccessErrors.types b/tests/baselines/reference/keyofAndIndexedAccessErrors.types
index 204e358211..2ba22589e9 100644
--- a/tests/baselines/reference/keyofAndIndexedAccessErrors.types
+++ b/tests/baselines/reference/keyofAndIndexedAccessErrors.types
@@ -28,19 +28,19 @@ type T01 = keyof Object;
>T01 : keyof Object
type T02 = keyof keyof Object;
->T02 : number | "length" | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "substr" | "valueOf"
+>T02 : number | `${number}` | "length" | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "substr" | "valueOf"
type T03 = keyof keyof keyof Object;
>T03 : "toString" | "valueOf"
type T04 = keyof keyof keyof keyof Object;
->T04 : number | "length" | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "substr" | "valueOf"
+>T04 : number | `${number}` | "length" | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "substr" | "valueOf"
type T05 = keyof keyof keyof keyof keyof Object;
>T05 : "toString" | "valueOf"
type T06 = keyof keyof keyof keyof keyof keyof Object;
->T06 : number | "length" | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "substr" | "valueOf"
+>T06 : number | `${number}` | "length" | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "substr" | "valueOf"
type T10 = Shape["name"];
>T10 : string
diff --git a/tests/baselines/reference/mappedTypeAsClauses.types b/tests/baselines/reference/mappedTypeAsClauses.types
index 168e8bd6ea..0f1c89f7f8 100644
--- a/tests/baselines/reference/mappedTypeAsClauses.types
+++ b/tests/baselines/reference/mappedTypeAsClauses.types
@@ -35,10 +35,10 @@ type TP1 = TypeFromDefs<{ name: 'a', type: string } | { name: 'b', type: number
// No array or tuple type mapping when 'as N' clause present
type TA1 = Getters<string[]>;
->TA1 : { getConcat: () => { (...items: ConcatArray<string>[]): string[]; (...items: (string | ConcatArray<string>)[]): string[]; }; getIndexOf: () => (searchElement: string, fromIndex?: number | undefined) => number; getLastIndexOf: () => (searchElement: string, fromIndex?: number | undefined) => number; getSlice: () => (start?: number | undefined, end?: number | undefined) => string[]; getLength: () => number; getToLocaleString: () => () => string; getToString: () => () => string; getPop: () => () => string | undefined; getPush: () => (...items: string[]) => number; getJoin: () => (separator?: string | undefined) => string; getReverse: () => () => string[]; getShift: () => () => string | undefined; getSort: () => (compareFn?: ((a: string, b: string) => number) | undefined) => string[]; getSplice: () => { (start: number, deleteCount?: number | undefined): string[]; (start: number, deleteCount: number, ...items: string[]): string[]; }; getUnshift: () => (...items: string[]) => number; getEvery: () => { <S extends string>(predicate: (value: string, index: number, array: string[]) => value is S, thisArg?: any): this is S[]; (predicate: (value: string, index: number, array: string[]) => unknown, thisArg?: any): boolean; }; getSome: () => (predicate: (value: string, index: number, array: string[]) => unknown, thisArg?: any) => boolean; getForEach: () => (callbackfn: (value: string, index: number, array: string[]) => void, thisArg?: any) => void; getMap: () => <U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[]; getFilter: () => { <S_1 extends string>(predicate: (value: string, index: number, array: string[]) => value is S_1, thisArg?: any): S_1[]; (predicate: (value: string, index: number, array: string[]) => unknown, thisArg?: any): string[]; }; getReduce: () => { (callbackfn: (previousValue: string, currentValue: string, currentIndex: number, array: string[]) => string): string; (callbackfn: (previousValue: string, currentValue: string, currentIndex: number, array: string[]) => string, initialValue: string): string; <U_1>(callbackfn: (previousValue: U_1, currentValue: string, currentIndex: number, array: string[]) => U_1, initialValue: U_1): U_1; }; getReduceRight: () => { (callbackfn: (previousValue: string, currentValue: string, currentIndex: number, array: string[]) => string): string; (callbackfn: (previousValue: string, currentValue: string, currentIndex: number, array: string[]) => string, initialValue: string): string; <U_2>(callbackfn: (previousValue: U_2, currentValue: string, currentIndex: number, array: string[]) => U_2, initialValue: U_2): U_2; }; }
+>TA1 : { [x: `get${Capitalize<`${number}`>}`]: () => string; getConcat: () => { (...items: ConcatArray<string>[]): string[]; (...items: (string | ConcatArray<string>)[]): string[]; }; getIndexOf: () => (searchElement: string, fromIndex?: number | undefined) => number; getLastIndexOf: () => (searchElement: string, fromIndex?: number | undefined) => number; getSlice: () => (start?: number | undefined, end?: number | undefined) => string[]; getLength: () => number; getToLocaleString: () => () => string; getToString: () => () => string; getPop: () => () => string | undefined; getPush: () => (...items: string[]) => number; getJoin: () => (separator?: string | undefined) => string; getReverse: () => () => string[]; getShift: () => () => string | undefined; getSort: () => (compareFn?: ((a: string, b: string) => number) | undefined) => string[]; getSplice: () => { (start: number, deleteCount?: number | undefined): string[]; (start: number, deleteCount: number, ...items: string[]): string[]; }; getUnshift: () => (...items: string[]) => number; getEvery: () => { <S extends string>(predicate: (value: string, index: number, array: string[]) => value is S, thisArg?: any): this is S[]; (predicate: (value: string, index: number, array: string[]) => unknown, thisArg?: any): boolean; }; getSome: () => (predicate: (value: string, index: number, array: string[]) => unknown, thisArg?: any) => boolean; getForEach: () => (callbackfn: (value: string, index: number, array: string[]) => void, thisArg?: any) => void; getMap: () => <U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[]; getFilter: () => { <S_1 extends string>(predicate: (value: string, index: number, array: string[]) => value is S_1, thisArg?: any): S_1[]; (predicate: (value: string, index: number, array: string[]) => unknown, thisArg?: any): string[]; }; getReduce: () => { (callbackfn: (previousValue: string, currentValue: string, currentIndex: number, array: string[]) => string): string; (callbackfn: (previousValue: string, currentValue: string, currentIndex: number, array: string[]) => string, initialValue: string): string; <U_1>(callbackfn: (previousValue: U_1, currentValue: string, currentIndex: number, array: string[]) => U_1, initialValue: U_1): U_1; }; getReduceRight: () => { (callbackfn: (previousValue: string, currentValue: string, currentIndex: number, array: string[]) => string): string; (callbackfn: (previousValue: string, currentValue: string, currentIndex: number, array: string[]) => string, initialValue: string): string; <U_2>(callbackfn: (previousValue: U_2, currentValue: string, currentIndex: number, array: string[]) => U_2, initialValue: U_2): U_2; }; }
type TA2 = Getters<[number, boolean]>;
->TA2 : { getConcat: () => { (...items: ConcatArray<number | boolean>[]): (number | boolean)[]; (...items: (number | boolean | ConcatArray<number | boolean>)[]): (number | boolean)[]; }; getIndexOf: () => (searchElement: number | boolean, fromIndex?: number | undefined) => number; getLastIndexOf: () => (searchElement: number | boolean, fromIndex?: number | undefined) => number; getSlice: () => (start?: number | undefined, end?: number | undefined) => (number | boolean)[]; getLength: () => 2; getToLocaleString: () => () => string; getToString: () => () => string; getPop: () => () => number | boolean | undefined; getPush: () => (...items: (number | boolean)[]) => number; getJoin: () => (separator?: string | undefined) => string; getReverse: () => () => (number | boolean)[]; getShift: () => () => number | boolean | undefined; getSort: () => (compareFn?: ((a: number | boolean, b: number | boolean) => number) | undefined) => [number, boolean]; getSplice: () => { (start: number, deleteCount?: number | undefined): (number | boolean)[]; (start: number, deleteCount: number, ...items: (number | boolean)[]): (number | boolean)[]; }; getUnshift: () => (...items: (number | boolean)[]) => number; getEvery: () => { <S extends number | boolean>(predicate: (value: number | boolean, index: number, array: (number | boolean)[]) => value is S, thisArg?: any): this is S[]; (predicate: (value: number | boolean, index: number, array: (number | boolean)[]) => unknown, thisArg?: any): boolean; }; getSome: () => (predicate: (value: number | boolean, index: number, array: (number | boolean)[]) => unknown, thisArg?: any) => boolean; getForEach: () => (callbackfn: (value: number | boolean, index: number, array: (number | boolean)[]) => void, thisArg?: any) => void; getMap: () => <U>(callbackfn: (value: number | boolean, index: number, array: (number | boolean)[]) => U, thisArg?: any) => U[]; getFilter: () => { <S_1 extends number | boolean>(predicate: (value: number | boolean, index: number, array: (number | boolean)[]) => value is S_1, thisArg?: any): S_1[]; (predicate: (value: number | boolean, index: number, array: (number | boolean)[]) => unknown, thisArg?: any): (number | boolean)[]; }; getReduce: () => { (callbackfn: (previousValue: number | boolean, currentValue: number | boolean, currentIndex: number, array: (number | boolean)[]) => number | boolean): number | boolean; (callbackfn: (previousValue: number | boolean, currentValue: number | boolean, currentIndex: number, array: (number | boolean)[]) => number | boolean, initialValue: number | boolean): number | boolean; <U_1>(callbackfn: (previousValue: U_1, currentValue: number | boolean, currentIndex: number, array: (number | boolean)[]) => U_1, initialValue: U_1): U_1; }; getReduceRight: () => { (callbackfn: (previousValue: number | boolean, currentValue: number | boolean, currentIndex: number, array: (number | boolean)[]) => number | boolean): number | boolean; (callbackfn: (previousValue: number | boolean, currentValue: number | boolean, currentIndex: number, array: (number | boolean)[]) => number | boolean, initialValue: number | boolean): number | boolean; <U_2>(callbackfn: (previousValue: U_2, currentValue: number | boolean, currentIndex: number, array: (number | boolean)[]) => U_2, initialValue: U_2): U_2; }; get0: () => number; get1: () => boolean; }
+>TA2 : { [x: `get${Capitalize<`${number}`>}`]: () => number | boolean; getConcat: () => { (...items: ConcatArray<number | boolean>[]): (number | boolean)[]; (...items: (number | boolean | ConcatArray<number | boolean>)[]): (number | boolean)[]; }; getIndexOf: () => (searchElement: number | boolean, fromIndex?: number | undefined) => number; getLastIndexOf: () => (searchElement: number | boolean, fromIndex?: number | undefined) => number; getSlice: () => (start?: number | undefined, end?: number | undefined) => (number | boolean)[]; getLength: () => 2; getToLocaleString: () => () => string; getToString: () => () => string; getPop: () => () => number | boolean | undefined; getPush: () => (...items: (number | boolean)[]) => number; getJoin: () => (separator?: string | undefined) => string; getReverse: () => () => (number | boolean)[]; getShift: () => () => number | boolean | undefined; getSort: () => (compareFn?: ((a: number | boolean, b: number | boolean) => number) | undefined) => [number, boolean]; getSplice: () => { (start: number, deleteCount?: number | undefined): (number | boolean)[]; (start: number, deleteCount: number, ...items: (number | boolean)[]): (number | boolean)[]; }; getUnshift: () => (...items: (number | boolean)[]) => number; getEvery: () => { <S extends number | boolean>(predicate: (value: number | boolean, index: number, array: (number | boolean)[]) => value is S, thisArg?: any): this is S[]; (predicate: (value: number | boolean, index: number, array: (number | boolean)[]) => unknown, thisArg?: any): boolean; }; getSome: () => (predicate: (value: number | boolean, index: number, array: (number | boolean)[]) => unknown, thisArg?: any) => boolean; getForEach: () => (callbackfn: (value: number | boolean, index: number, array: (number | boolean)[]) => void, thisArg?: any) => void; getMap: () => <U>(callbackfn: (value: number | boolean, index: number, array: (number | boolean)[]) => U, thisArg?: any) => U[]; getFilter: () => { <S_1 extends number | boolean>(predicate: (value: number | boolean, index: number, array: (number | boolean)[]) => value is S_1, thisArg?: any): S_1[]; (predicate: (value: number | boolean, index: number, array: (number | boolean)[]) => unknown, thisArg?: any): (number | boolean)[]; }; getReduce: () => { (callbackfn: (previousValue: number | boolean, currentValue: number | boolean, currentIndex: number, array: (number | boolean)[]) => number | boolean): number | boolean; (callbackfn: (previousValue: number | boolean, currentValue: number | boolean, currentIndex: number, array: (number | boolean)[]) => number | boolean, initialValue: number | boolean): number | boolean; <U_1>(callbackfn: (previousValue: U_1, currentValue: number | boolean, currentIndex: number, array: (number | boolean)[]) => U_1, initialValue: U_1): U_1; }; getReduceRight: () => { (callbackfn: (previousValue: number | boolean, currentValue: number | boolean, currentIndex: number, array: (number | boolean)[]) => number | boolean): number | boolean; (callbackfn: (previousValue: number | boolean, currentValue: number | boolean, currentIndex: number, array: (number | boolean)[]) => number | boolean, initialValue: number | boolean): number | boolean; <U_2>(callbackfn: (previousValue: U_2, currentValue: number | boolean, currentIndex: number, array: (number | boolean)[]) => U_2, initialValue: U_2): U_2; }; }
// Filtering using 'as N' clause
diff --git a/tests/baselines/reference/templateLiteralTypes4.errors.txt b/tests/baselines/reference/templateLiteralTypes4.errors.txt
index 42bf2664af..ce37b8df59 100644
--- a/tests/baselines/reference/templateLiteralTypes4.errors.txt
+++ b/tests/baselines/reference/templateLiteralTypes4.errors.txt
@@ -1,8 +1,7 @@
-templateLiteralTypes4.ts(285,12): error TS2345: Argument of type '2' is not assignable to parameter of type '0 | 1'.
-templateLiteralTypes4.ts(289,12): error TS2345: Argument of type '2' is not assignable to parameter of type '0 | 1'.
+templateLiteralTypes4.ts(289,15): error TS2345: Argument of type 'number' is not assignable to parameter of type 'never'.
-==== templateLiteralTypes4.ts (2 errors) ====
+==== templateLiteralTypes4.ts (1 errors) ====
// infer from number
type TNumber0 = "100" extends `${infer N extends number}` ? N : never; // 100
type TNumber1 = "-100" extends `${infer N extends number}` ? N : never; // -100
@@ -288,14 +287,12 @@ templateLiteralTypes4.ts(289,12): error TS2345: Argument of type '2' is not assi
p.getIndex(0); // ok, 0 is a valid index
p.getIndex(1); // ok, 1 is a valid index
p.getIndex(2); // error, 2 is not a valid index
- ~
-!!! error TS2345: Argument of type '2' is not assignable to parameter of type '0 | 1'.
p.setIndex(0, 0); // ok, 0 is a valid index
p.setIndex(1, 0); // ok, 1 is a valid index
p.setIndex(2, 3); // error, 2 is not a valid index
- ~
-!!! error TS2345: Argument of type '2' is not assignable to parameter of type '0 | 1'.
+ ~
+!!! error TS2345: Argument of type 'number' is not assignable to parameter of type 'never'.
// function inference
declare function f1<T extends string | number>(s: `**${T}**`): T;
diff --git a/tests/baselines/reference/templateLiteralTypes4.types b/tests/baselines/reference/templateLiteralTypes4.types
index e139cd975a..d561764a0e 100644
--- a/tests/baselines/reference/templateLiteralTypes4.types
+++ b/tests/baselines/reference/templateLiteralTypes4.types
@@ -515,46 +515,46 @@ declare const p: Point;
p.getIndex(0); // ok, 0 is a valid index
>p.getIndex(0) : number
->p.getIndex : <I extends 0 | 1>(index: I) => FieldType<Extract<[{ name: "x"; type: "f64"; }, { name: "y"; type: "f64"; }][I], FieldDefinition>["type"]>
+>p.getIndex : <I extends number>(index: I) => FieldType<Extract<[{ name: "x"; type: "f64"; }, { name: "y"; type: "f64"; }][I], FieldDefinition>["type"]>
>p : Point
->getIndex : <I extends 0 | 1>(index: I) => FieldType<Extract<[{ name: "x"; type: "f64"; }, { name: "y"; type: "f64"; }][I], FieldDefinition>["type"]>
+>getIndex : <I extends number>(index: I) => FieldType<Extract<[{ name: "x"; type: "f64"; }, { name: "y"; type: "f64"; }][I], FieldDefinition>["type"]>
>0 : 0
p.getIndex(1); // ok, 1 is a valid index
>p.getIndex(1) : number
->p.getIndex : <I extends 0 | 1>(index: I) => FieldType<Extract<[{ name: "x"; type: "f64"; }, { name: "y"; type: "f64"; }][I], FieldDefinition>["type"]>
+>p.getIndex : <I extends number>(index: I) => FieldType<Extract<[{ name: "x"; type: "f64"; }, { name: "y"; type: "f64"; }][I], FieldDefinition>["type"]>
>p : Point
->getIndex : <I extends 0 | 1>(index: I) => FieldType<Extract<[{ name: "x"; type: "f64"; }, { name: "y"; type: "f64"; }][I], FieldDefinition>["type"]>
+>getIndex : <I extends number>(index: I) => FieldType<Extract<[{ name: "x"; type: "f64"; }, { name: "y"; type: "f64"; }][I], FieldDefinition>["type"]>
>1 : 1
p.getIndex(2); // error, 2 is not a valid index
->p.getIndex(2) : number
->p.getIndex : <I extends 0 | 1>(index: I) => FieldType<Extract<[{ name: "x"; type: "f64"; }, { name: "y"; type: "f64"; }][I], FieldDefinition>["type"]>
+>p.getIndex(2) : never
+>p.getIndex : <I extends number>(index: I) => FieldType<Extract<[{ name: "x"; type: "f64"; }, { name: "y"; type: "f64"; }][I], FieldDefinition>["type"]>
>p : Point
->getIndex : <I extends 0 | 1>(index: I) => FieldType<Extract<[{ name: "x"; type: "f64"; }, { name: "y"; type: "f64"; }][I], FieldDefinition>["type"]>
+>getIndex : <I extends number>(index: I) => FieldType<Extract<[{ name: "x"; type: "f64"; }, { name: "y"; type: "f64"; }][I], FieldDefinition>["type"]>
>2 : 2
p.setIndex(0, 0); // ok, 0 is a valid index
>p.setIndex(0, 0) : void
->p.setIndex : <I extends 0 | 1>(index: I, value: FieldType<Extract<[{ name: "x"; type: "f64"; }, { name: "y"; type: "f64"; }][I], FieldDefinition>["type"]>) => void
+>p.setIndex : <I extends number>(index: I, value: FieldType<Extract<[{ name: "x"; type: "f64"; }, { name: "y"; type: "f64"; }][I], FieldDefinition>["type"]>) => void
>p : Point
->setIndex : <I extends 0 | 1>(index: I, value: FieldType<Extract<[{ name: "x"; type: "f64"; }, { name: "y"; type: "f64"; }][I], FieldDefinition>["type"]>) => void
+>setIndex : <I extends number>(index: I, value: FieldType<Extract<[{ name: "x"; type: "f64"; }, { name: "y"; type: "f64"; }][I], FieldDefinition>["type"]>) => void
>0 : 0
>0 : 0
p.setIndex(1, 0); // ok, 1 is a valid index
>p.setIndex(1, 0) : void
->p.setIndex : <I extends 0 | 1>(index: I, value: FieldType<Extract<[{ name: "x"; type: "f64"; }, { name: "y"; type: "f64"; }][I], FieldDefinition>["type"]>) => void
+>p.setIndex : <I extends number>(index: I, value: FieldType<Extract<[{ name: "x"; type: "f64"; }, { name: "y"; type: "f64"; }][I], FieldDefinition>["type"]>) => void
>p : Point
->setIndex : <I extends 0 | 1>(index: I, value: FieldType<Extract<[{ name: "x"; type: "f64"; }, { name: "y"; type: "f64"; }][I], FieldDefinition>["type"]>) => void
+>setIndex : <I extends number>(index: I, value: FieldType<Extract<[{ name: "x"; type: "f64"; }, { name: "y"; type: "f64"; }][I], FieldDefinition>["type"]>) => void
>1 : 1
>0 : 0
p.setIndex(2, 3); // error, 2 is not a valid index
>p.setIndex(2, 3) : void
->p.setIndex : <I extends 0 | 1>(index: I, value: FieldType<Extract<[{ name: "x"; type: "f64"; }, { name: "y"; type: "f64"; }][I], FieldDefinition>["type"]>) => void
+>p.setIndex : <I extends number>(index: I, value: FieldType<Extract<[{ name: "x"; type: "f64"; }, { name: "y"; type: "f64"; }][I], FieldDefinition>["type"]>) => void
>p : Point
->setIndex : <I extends 0 | 1>(index: I, value: FieldType<Extract<[{ name: "x"; type: "f64"; }, { name: "y"; type: "f64"; }][I], FieldDefinition>["type"]>) => void
+>setIndex : <I extends number>(index: I, value: FieldType<Extract<[{ name: "x"; type: "f64"; }, { name: "y"; type: "f64"; }][I], FieldDefinition>["type"]>) => void
>2 : 2
>3 : 3
diff --git a/tests/baselines/reference/variadicTuples1.errors.txt b/tests/baselines/reference/variadicTuples1.errors.txt
index a272e38931..707cbe7077 100644
--- a/tests/baselines/reference/variadicTuples1.errors.txt
+++ b/tests/baselines/reference/variadicTuples1.errors.txt
@@ -33,8 +33,6 @@ variadicTuples1.ts(190,5): error TS2322: Type 'T' is not assignable to type '[..
variadicTuples1.ts(191,5): error TS2322: Type '[...T]' is not assignable to type '[...U]'.
Type 'T' is not assignable to type 'U'.
'T' is assignable to the constraint of type 'U', but 'U' could be instantiated with a different subtype of constraint 'readonly string[]'.
-variadicTuples1.ts(203,5): error TS2322: Type 'string' is not assignable to type 'keyof [1, 2, ...T]'.
- Type '"2"' is not assignable to type '"0" | "1" | keyof T[]'.
variadicTuples1.ts(213,5): error TS2322: Type '[...T, ...T]' is not assignable to type '[unknown, unknown]'.
Type '[] | [unknown] | [unknown, unknown]' is not assignable to type '[unknown, unknown]'.
Type '[]' is not assignable to type '[unknown, unknown]'.
@@ -48,7 +46,7 @@ variadicTuples1.ts(411,7): error TS2322: Type '[boolean, false]' is not assignab
Type 'boolean' is not assignable to type 'number'.
-==== variadicTuples1.ts (22 errors) ====
+==== variadicTuples1.ts (21 errors) ====
// Variadics in tuple types
type TV0<T extends unknown[]> = [string, ...T];
@@ -305,9 +303,6 @@ variadicTuples1.ts(411,7): error TS2322: Type '[boolean, false]' is not assignab
k3 = '0';
k3 = '1';
k3 = '2'; // Error
- ~~
-!!! error TS2322: Type 'string' is not assignable to type 'keyof [1, 2, ...T]'.
-!!! error TS2322: Type '"2"' is not assignable to type '"0" | "1" | keyof T[]'.
}
// Constraints of variadic tuple types
All of those failures are related to the fact that Like showcased above - array types are always indexable with concrete numbers (despite the fact that some instantiations might not have those elements): type Test1<A extends unknown[]> = A[0] This actually isn't even an array problem, it's just an index signature thing: type Test2<O extends { [k: string]: number }> = O["foo"] // ok
type Res2 = Test2<{ bar: 10 }> // unknown We can read the comment in // A 'string' index signature applies to types assignable to 'string' or 'number', and a 'number' index
// signature applies to types assignable to 'number', `${number}` and numeric string literal types. To me, this means that But... that's not exactly how it works today and we can find some further examples of how things are not completely consistent: type Arr = number[];
type Access1 = Arr[0]; // ok
type Access2 = Arr["0"]; // ok
type Check1 = 100 extends keyof Arr ? 1 : 0; // 1
type Check2 = "100" extends keyof Arr ? 1 : 0; // 0 Should That being said, scoped `getLiteralTypeFromProperties` patchdiff --git a/src/compiler/checker.ts b/src/compiler/checker.ts
index dd24f9be31..92714df2e7 100644
--- a/src/compiler/checker.ts
+++ b/src/compiler/checker.ts
@@ -17851,12 +17851,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return !!(keyType.flags & include || keyType.flags & TypeFlags.Intersection && some((keyType as IntersectionType).types, t => isKeyTypeIncluded(t, include)));
}
- function getLiteralTypeFromProperties(type: Type, include: TypeFlags, includeOrigin: boolean) {
+ function getLiteralTypeFromProperties(type: Type, include: TypeFlags, includeOrigin: boolean, includeNumericStringIndex = false) {
const origin = includeOrigin && (getObjectFlags(type) & (ObjectFlags.ClassOrInterface | ObjectFlags.Reference) || type.aliasSymbol) ? createOriginIndexType(type) : undefined;
const propertyTypes = map(getPropertiesOfType(type), prop => getLiteralTypeFromProperty(prop, include));
const indexKeyTypes = map(getIndexInfosOfType(type), info =>
info !== enumNumberIndexInfo && isKeyTypeIncluded(info.keyType, include) ?
- info.keyType === stringType && include & TypeFlags.Number ? stringOrNumberType : info.keyType : neverType);
+ info.keyType === stringType && include & TypeFlags.Number ? stringOrNumberType : info.keyType === numberType && includeNumericStringIndex ? getUnionType([numberType, numericStringType]) : info.keyType : neverType);
return getUnionType(concatenate(propertyTypes, indexKeyTypes), UnionReduction.Literal, /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined, origin);
}
@@ -17878,7 +17878,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
type === wildcardType ? wildcardType :
type.flags & TypeFlags.Unknown ? neverType :
type.flags & (TypeFlags.Any | TypeFlags.Never) ? keyofConstraintType :
- getLiteralTypeFromProperties(type, (indexFlags & IndexFlags.NoIndexSignatures ? TypeFlags.StringLiteral : TypeFlags.StringLike) | (indexFlags & IndexFlags.StringsOnly ? 0 : TypeFlags.NumberLike | TypeFlags.ESSymbolLike), indexFlags === defaultIndexFlags);
+ getLiteralTypeFromProperties(type, (indexFlags & IndexFlags.NoIndexSignatures ? TypeFlags.StringLiteral : TypeFlags.StringLike) | (indexFlags & IndexFlags.StringsOnly ? 0 : TypeFlags.NumberLike | TypeFlags.ESSymbolLike), indexFlags === defaultIndexFlags, !!(indexFlags & IndexFlags.IncludeNumericStringIndex));
}
function getExtractStringType(type: Type) {
@@ -22474,7 +22474,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// false positives. For example, given 'T extends { [K in keyof T]: string }',
// 'keyof T' has itself as its constraint and produces a Ternary.Maybe when
// related to other types.
- if (isRelatedTo(source, getIndexType(constraint, (target as IndexType).indexFlags | IndexFlags.NoReducibleCheck), RecursionFlags.Target, reportErrors) === Ternary.True) {
+ if (isRelatedTo(source, getIndexType(constraint, (target as IndexType).indexFlags | IndexFlags.IncludeNumericStringIndex | IndexFlags.NoReducibleCheck), RecursionFlags.Target, reportErrors) === Ternary.True) {
return Ternary.True;
}
}
diff --git a/src/compiler/types.ts b/src/compiler/types.ts
index 48d8e2d01b..584076aac9 100644
--- a/src/compiler/types.ts
+++ b/src/compiler/types.ts
@@ -6670,6 +6670,7 @@ export const enum IndexFlags {
StringsOnly = 1 << 0,
NoIndexSignatures = 1 << 1,
NoReducibleCheck = 1 << 2,
+ IncludeNumericStringIndex = 1 << 3,
}
// keyof T types (TypeFlags.Index) That isn't as breaking - at least not when it comes to the tests contained in this repo. We only get one different error message: changed error message baselinediff --git a/tests/baselines/reference/conditionalTypes2.errors.txt b/tests/baselines/reference/conditionalTypes2.errors.txt
index de78fe2d37..d5c6945d1e 100644
--- a/tests/baselines/reference/conditionalTypes2.errors.txt
+++ b/tests/baselines/reference/conditionalTypes2.errors.txt
@@ -13,10 +13,10 @@ conditionalTypes2.ts(24,5): error TS2322: Type 'Invariant<B>' is not assignable
Type 'keyof B' is not assignable to type 'keyof A'.
Type 'string | number | symbol' is not assignable to type 'keyof A'.
Type 'string' is not assignable to type 'keyof A'.
- Type 'string' is not assignable to type 'number | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "length" | "substr" | "valueOf"'.
- Type 'keyof B' is not assignable to type 'number | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "length" | "substr" | "valueOf"'.
- Type 'string | number | symbol' is not assignable to type 'number | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "length" | "substr" | "valueOf"'.
- Type 'string' is not assignable to type 'number | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "length" | "substr" | "valueOf"'.
+ Type 'string' is not assignable to type 'number | `${number}` | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "length" | "substr" | "valueOf"'.
+ Type 'keyof B' is not assignable to type 'number | `${number}` | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "length" | "substr" | "valueOf"'.
+ Type 'string | number | symbol' is not assignable to type 'number | `${number}` | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "length" | "substr" | "valueOf"'.
+ Type 'string' is not assignable to type 'number | `${number}` | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "length" | "substr" | "valueOf"'.
conditionalTypes2.ts(25,5): error TS2322: Type 'Invariant<A>' is not assignable to type 'Invariant<B>'.
Types of property 'foo' are incompatible.
Type 'A extends string ? keyof A : A' is not assignable to type 'B extends string ? keyof B : B'.
@@ -79,10 +79,10 @@ conditionalTypes2.ts(75,12): error TS2345: Argument of type 'Extract2<T, Foo, Ba
!!! error TS2322: Type 'keyof B' is not assignable to type 'keyof A'.
!!! error TS2322: Type 'string | number | symbol' is not assignable to type 'keyof A'.
!!! error TS2322: Type 'string' is not assignable to type 'keyof A'.
-!!! error TS2322: Type 'string' is not assignable to type 'number | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "length" | "substr" | "valueOf"'.
-!!! error TS2322: Type 'keyof B' is not assignable to type 'number | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "length" | "substr" | "valueOf"'.
-!!! error TS2322: Type 'string | number | symbol' is not assignable to type 'number | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "length" | "substr" | "valueOf"'.
-!!! error TS2322: Type 'string' is not assignable to type 'number | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "length" | "substr" | "valueOf"'.
+!!! error TS2322: Type 'string' is not assignable to type 'number | `${number}` | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "length" | "substr" | "valueOf"'.
+!!! error TS2322: Type 'keyof B' is not assignable to type 'number | `${number}` | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "length" | "substr" | "valueOf"'.
+!!! error TS2322: Type 'string | number | symbol' is not assignable to type 'number | `${number}` | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "length" | "substr" | "valueOf"'.
+!!! error TS2322: Type 'string' is not assignable to type 'number | `${number}` | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "length" | "substr" | "valueOf"'.
b = a; // Error
~
!!! error TS2322: Type 'Invariant<A>' is not assignable to type 'Invariant<B>'. Let me know what you think - which direction is worth exploring here (I don't expect the patch posted above to be final). And where the fix for this should go. It doesn't quite feel like part of this PR here - I'd probably extend #56878 with this since it feels highly related to this problem/inconsistency. |
I'm closing this as this change became part of #57122 |
I think this might be a prerequisite to fixing the issue #29919fixes #29919Why do I think this might be required?
getResolvedApparentTypeOfMappedType
forMappedType<Parameters<FnType>>
is not recognized as an array typeParameters<FnType>
has to produce an array typeconstraint
here isany
and thus it doesn't passisArrayOrTupleType
check, the mapped type stays non-instantiated and later on can't be used as a type for the rest parameterParameters
to the mapped type in the easiest way possible - by changing the final constraint of...T
toany[]
whenT extends any
.There are still some changes to be made to the code for this to be part of the fix. I'm still struggling to tweak the logic inThis has been done in #56727getResolvedApparentTypeOfMappedType
to instantiate the mapped type using this newly adjusted constraint but that's, sort of, a problem for another day. I'd like to check with the team first if I'm on the right path and if this change makes any sense.