From 68a6c0ea7abf0812d758823fe7fa0efe9b3e1255 Mon Sep 17 00:00:00 2001
From: Anders Hejlsberg <andersh@microsoft.com>
Date: Mon, 25 Apr 2022 06:01:09 -0700
Subject: [PATCH 1/3] Improve support for numeric string types

---
 src/compiler/checker.ts | 45 +++++++++++++++++++++++++++++------------
 1 file changed, 32 insertions(+), 13 deletions(-)

diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts
index 2d3060682ffb5..a7297903cfbe5 100644
--- a/src/compiler/checker.ts
+++ b/src/compiler/checker.ts
@@ -833,6 +833,7 @@ namespace ts {
         const keyofConstraintType = keyofStringsOnly ? stringType : stringNumberSymbolType;
         const numberOrBigIntType = getUnionType([numberType, bigintType]);
         const templateConstraintType = getUnionType([stringType, numberType, booleanType, bigintType, nullType, undefinedType]) as UnionType;
+        const numericStringType = getTemplateLiteralType(["", ""], [numberType]);  // The `${number}` type
 
         const restrictiveMapper: TypeMapper = makeFunctionTypeMapper(t => t.flags & TypeFlags.TypeParameter ? getRestrictiveTypeParameter(t as TypeParameter) : t);
         const permissiveMapper: TypeMapper = makeFunctionTypeMapper(t => t.flags & TypeFlags.TypeParameter ? wildcardType : t);
@@ -12273,7 +12274,7 @@ namespace ts {
             const typeVariable = getHomomorphicTypeVariable(type);
             if (typeVariable && !type.declaration.nameType) {
                 const constraint = getConstraintOfTypeParameter(typeVariable);
-                if (constraint && (isArrayType(constraint) || isTupleType(constraint))) {
+                if (constraint && isArrayOrTupleType(constraint)) {
                     return instantiateType(type, prependTypeMapping(typeVariable, constraint, type.mapper));
                 }
             }
@@ -12654,10 +12655,10 @@ namespace ts {
 
         function isApplicableIndexType(source: Type, target: Type): boolean {
             // A 'string' index signature applies to types assignable to 'string' or 'number', and a 'number' index
-            // signature applies to types assignable to 'number' and numeric string literal types.
+            // signature applies to types assignable to 'number', `${number}` and numeric string literal types.
             return isTypeAssignableTo(source, target) ||
                 target === stringType && isTypeAssignableTo(source, numberType) ||
-                target === numberType && !!(source.flags & TypeFlags.StringLiteral) && isNumericLiteralName((source as StringLiteralType).value);
+                target === numberType && (source === numericStringType || !!(source.flags & TypeFlags.StringLiteral) && isNumericLiteralName((source as StringLiteralType).value));
         }
 
         function getIndexInfosOfStructuredType(type: Type): readonly IndexInfo[] {
@@ -13778,6 +13779,20 @@ namespace ts {
                         constraints = append(constraints, constraint);
                     }
                 }
+                // Given a homomorphic mapped type { [K in keyof T]: XXX }, where T is constrained to an array or tuple type, in the
+                // template type XXX, K has an added constraint of number | `${number}`.
+                else if (type.flags & TypeFlags.TypeParameter && parent.kind === SyntaxKind.MappedType && node === (parent as MappedTypeNode).type) {
+                    const mappedType = getTypeFromTypeNode(parent as TypeNode) as MappedType;
+                    if (getTypeParameterFromMappedType(mappedType) === getActualTypeVariable(type)) {
+                        const typeParameter = getHomomorphicTypeVariable(mappedType);
+                        if (typeParameter) {
+                            const constraint = getConstraintOfTypeParameter(typeParameter);
+                            if (constraint && everyType(constraint, isArrayOrTupleType)) {
+                                constraints = append(constraints, getUnionType([numberType, numericStringType]));
+                            }
+                        }
+                    }
+                }
                 node = parent;
             }
             return constraints ? getSubstitutionType(type, getIntersectionType(append(constraints, type))) : type;
@@ -14817,7 +14832,7 @@ namespace ts {
                 i--;
                 const t = types[i];
                 const remove =
-                    t.flags & TypeFlags.String && includes & TypeFlags.StringLiteral ||
+                    t.flags & TypeFlags.String && includes & (TypeFlags.StringLiteral | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) ||
                     t.flags & TypeFlags.Number && includes & TypeFlags.NumberLiteral ||
                     t.flags & TypeFlags.BigInt && includes & TypeFlags.BigIntLiteral ||
                     t.flags & TypeFlags.ESSymbol && includes & TypeFlags.UniqueESSymbol;
@@ -14978,7 +14993,7 @@ namespace ts {
             if (!strictNullChecks && includes & TypeFlags.Nullable) {
                 return includes & TypeFlags.Undefined ? undefinedType : nullType;
             }
-            if (includes & TypeFlags.String && includes & TypeFlags.StringLiteral ||
+            if (includes & TypeFlags.String && includes & (TypeFlags.StringLiteral | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) ||
                 includes & TypeFlags.Number && includes & TypeFlags.NumberLiteral ||
                 includes & TypeFlags.BigInt && includes & TypeFlags.BigIntLiteral ||
                 includes & TypeFlags.ESSymbol && includes & TypeFlags.UniqueESSymbol) {
@@ -16996,7 +17011,7 @@ namespace ts {
                             if (!type.declaration.nameType) {
                                 let constraint;
                                 if (isArrayType(t) || t.flags & TypeFlags.Any && findResolutionCycleStartIndex(typeVariable, TypeSystemPropertyName.ImmediateBaseConstraint) < 0 &&
-                                    (constraint = getConstraintOfTypeParameter(typeVariable)) && everyType(constraint, or(isArrayType, isTupleType))) {
+                                    (constraint = getConstraintOfTypeParameter(typeVariable)) && everyType(constraint, isArrayOrTupleType)) {
                                     return instantiateMappedArrayType(t, type, prependTypeMapping(typeVariable, t, mapper));
                                 }
                                 if (isGenericTupleType(t)) {
@@ -18565,7 +18580,7 @@ namespace ts {
                         }
                         return false;
                     }
-                    return isTupleType(target) || isArrayType(target);
+                    return isArrayOrTupleType(target);
                 }
                 if (isReadonlyArrayType(source) && isMutableArrayOrTuple(target)) {
                     if (reportErrors) {
@@ -18700,7 +18715,7 @@ namespace ts {
                     // recursive intersections that are structurally similar but not exactly identical. See #37854.
                     if (result && !inPropertyCheck && (
                         target.flags & TypeFlags.Intersection && (isPerformingExcessPropertyChecks || isPerformingCommonPropertyChecks) ||
-                        isNonGenericObjectType(target) && !isArrayType(target) && !isTupleType(target) && source.flags & TypeFlags.Intersection && getApparentType(source).flags & TypeFlags.StructuredType && !some((source as IntersectionType).types, t => !!(getObjectFlags(t) & ObjectFlags.NonInferrableType)))) {
+                        isNonGenericObjectType(target) && !isArrayOrTupleType(target) && source.flags & TypeFlags.Intersection && getApparentType(source).flags & TypeFlags.StructuredType && !some((source as IntersectionType).types, t => !!(getObjectFlags(t) & ObjectFlags.NonInferrableType)))) {
                         inPropertyCheck = true;
                         result &= recursiveTypeRelatedTo(source, target, reportErrors, IntersectionState.PropertyCheck, recursionFlags);
                         inPropertyCheck = false;
@@ -19708,7 +19723,7 @@ namespace ts {
                             return varianceResult;
                         }
                     }
-                    else if (isReadonlyArrayType(target) ? isArrayType(source) || isTupleType(source) : isArrayType(target) && isTupleType(source) && !source.target.readonly) {
+                    else if (isReadonlyArrayType(target) ? isArrayOrTupleType(source) : isArrayType(target) && isTupleType(source) && !source.target.readonly) {
                         if (relation !== identityRelation) {
                             return isRelatedTo(getIndexTypeOfType(source, numberType) || anyType, getIndexTypeOfType(target, numberType) || anyType, RecursionFlags.Both, reportErrors);
                         }
@@ -20093,7 +20108,7 @@ namespace ts {
                 }
                 let result = Ternary.True;
                 if (isTupleType(target)) {
-                    if (isArrayType(source) || isTupleType(source)) {
+                    if (isArrayOrTupleType(source)) {
                         if (!target.target.readonly && (isReadonlyArrayType(source) || isTupleType(source) && source.target.readonly)) {
                             return Ternary.False;
                         }
@@ -21098,6 +21113,10 @@ namespace ts {
             return !!(getObjectFlags(type) & ObjectFlags.Reference) && (type as TypeReference).target === globalReadonlyArrayType;
         }
 
+        function isArrayOrTupleType(type: Type): type is TypeReference {
+            return isArrayType(type) || isTupleType(type);
+        }
+
         function isMutableArrayOrTuple(type: Type): boolean {
             return isArrayType(type) && !isReadonlyArrayType(type) || isTupleType(type) && !type.target.readonly;
         }
@@ -21598,7 +21617,7 @@ namespace ts {
                 else if (type.flags & TypeFlags.Intersection) {
                     result = getIntersectionType(sameMap((type as IntersectionType).types, getWidenedType));
                 }
-                else if (isArrayType(type) || isTupleType(type)) {
+                else if (isArrayOrTupleType(type)) {
                     result = createTypeReference(type.target, sameMap(getTypeArguments(type), getWidenedType));
                 }
                 if (result && context === undefined) {
@@ -21635,7 +21654,7 @@ namespace ts {
                         }
                     }
                 }
-                if (isArrayType(type) || isTupleType(type)) {
+                if (isArrayOrTupleType(type)) {
                     for (const t of getTypeArguments(type)) {
                         if (reportWideningErrorsInType(t)) {
                             errorReported = true;
@@ -22714,7 +22733,7 @@ namespace ts {
                 }
                 // Infer from the members of source and target only if the two types are possibly related
                 if (!typesDefinitelyUnrelated(source, target)) {
-                    if (isArrayType(source) || isTupleType(source)) {
+                    if (isArrayOrTupleType(source)) {
                         if (isTupleType(target)) {
                             const sourceArity = getTypeReferenceArity(source);
                             const targetArity = getTypeReferenceArity(target);

From 71a2b47442a30b0b2b469509c1537bcf2b50452f Mon Sep 17 00:00:00 2001
From: Anders Hejlsberg <andersh@microsoft.com>
Date: Mon, 25 Apr 2022 06:01:51 -0700
Subject: [PATCH 2/3] Update test

---
 .../templateLiteralTypes1.errors.txt          |  3 +-
 .../reference/templateLiteralTypes1.js        |  1 +
 .../reference/templateLiteralTypes1.symbols   | 95 ++++++++++---------
 .../reference/templateLiteralTypes1.types     |  1 +
 .../types/literal/templateLiteralTypes1.ts    |  1 +
 5 files changed, 54 insertions(+), 47 deletions(-)

diff --git a/tests/baselines/reference/templateLiteralTypes1.errors.txt b/tests/baselines/reference/templateLiteralTypes1.errors.txt
index e586a1678eb65..384125f3380bc 100644
--- a/tests/baselines/reference/templateLiteralTypes1.errors.txt
+++ b/tests/baselines/reference/templateLiteralTypes1.errors.txt
@@ -6,7 +6,7 @@ tests/cases/conformance/types/literal/templateLiteralTypes1.ts(165,15): error TS
 tests/cases/conformance/types/literal/templateLiteralTypes1.ts(197,16): error TS2590: Expression produces a union type that is too complex to represent.
 tests/cases/conformance/types/literal/templateLiteralTypes1.ts(201,16): error TS2590: Expression produces a union type that is too complex to represent.
 tests/cases/conformance/types/literal/templateLiteralTypes1.ts(205,16): error TS2590: Expression produces a union type that is too complex to represent.
-tests/cases/conformance/types/literal/templateLiteralTypes1.ts(251,7): error TS2590: Expression produces a union type that is too complex to represent.
+tests/cases/conformance/types/literal/templateLiteralTypes1.ts(252,7): error TS2590: Expression produces a union type that is too complex to represent.
 
 
 ==== tests/cases/conformance/types/literal/templateLiteralTypes1.ts (7 errors) ====
@@ -242,6 +242,7 @@ tests/cases/conformance/types/literal/templateLiteralTypes1.ts(251,7): error TS2
     // Repro from #40970
     
     type PathKeys<T> =
+        unknown extends T ? never :
         T extends readonly any[] ? Extract<keyof T, `${number}`> | SubKeys<T, Extract<keyof T, `${number}`>> :
         T extends object ? Extract<keyof T, string> | SubKeys<T, Extract<keyof T, string>> :
         never;
diff --git a/tests/baselines/reference/templateLiteralTypes1.js b/tests/baselines/reference/templateLiteralTypes1.js
index d212909fd7d2b..afa410f6dc9de 100644
--- a/tests/baselines/reference/templateLiteralTypes1.js
+++ b/tests/baselines/reference/templateLiteralTypes1.js
@@ -217,6 +217,7 @@ type BB = AA<-2, -2>;
 // Repro from #40970
 
 type PathKeys<T> =
+    unknown extends T ? never :
     T extends readonly any[] ? Extract<keyof T, `${number}`> | SubKeys<T, Extract<keyof T, `${number}`>> :
     T extends object ? Extract<keyof T, string> | SubKeys<T, Extract<keyof T, string>> :
     never;
diff --git a/tests/baselines/reference/templateLiteralTypes1.symbols b/tests/baselines/reference/templateLiteralTypes1.symbols
index 8f539fe347e15..fda962de761e1 100644
--- a/tests/baselines/reference/templateLiteralTypes1.symbols
+++ b/tests/baselines/reference/templateLiteralTypes1.symbols
@@ -878,11 +878,14 @@ type PathKeys<T> =
 >PathKeys : Symbol(PathKeys, Decl(templateLiteralTypes1.ts, 213, 21))
 >T : Symbol(T, Decl(templateLiteralTypes1.ts, 217, 14))
 
+    unknown extends T ? never :
+>T : Symbol(T, Decl(templateLiteralTypes1.ts, 217, 14))
+
     T extends readonly any[] ? Extract<keyof T, `${number}`> | SubKeys<T, Extract<keyof T, `${number}`>> :
 >T : Symbol(T, Decl(templateLiteralTypes1.ts, 217, 14))
 >Extract : Symbol(Extract, Decl(lib.es5.d.ts, --, --))
 >T : Symbol(T, Decl(templateLiteralTypes1.ts, 217, 14))
->SubKeys : Symbol(SubKeys, Decl(templateLiteralTypes1.ts, 220, 10))
+>SubKeys : Symbol(SubKeys, Decl(templateLiteralTypes1.ts, 221, 10))
 >T : Symbol(T, Decl(templateLiteralTypes1.ts, 217, 14))
 >Extract : Symbol(Extract, Decl(lib.es5.d.ts, --, --))
 >T : Symbol(T, Decl(templateLiteralTypes1.ts, 217, 14))
@@ -891,7 +894,7 @@ type PathKeys<T> =
 >T : Symbol(T, Decl(templateLiteralTypes1.ts, 217, 14))
 >Extract : Symbol(Extract, Decl(lib.es5.d.ts, --, --))
 >T : Symbol(T, Decl(templateLiteralTypes1.ts, 217, 14))
->SubKeys : Symbol(SubKeys, Decl(templateLiteralTypes1.ts, 220, 10))
+>SubKeys : Symbol(SubKeys, Decl(templateLiteralTypes1.ts, 221, 10))
 >T : Symbol(T, Decl(templateLiteralTypes1.ts, 217, 14))
 >Extract : Symbol(Extract, Decl(lib.es5.d.ts, --, --))
 >T : Symbol(T, Decl(templateLiteralTypes1.ts, 217, 14))
@@ -899,63 +902,63 @@ type PathKeys<T> =
     never;
 
 type SubKeys<T, K extends string> = K extends keyof T ? `${K}.${PathKeys<T[K]>}` : never;
->SubKeys : Symbol(SubKeys, Decl(templateLiteralTypes1.ts, 220, 10))
->T : Symbol(T, Decl(templateLiteralTypes1.ts, 222, 13))
->K : Symbol(K, Decl(templateLiteralTypes1.ts, 222, 15))
->K : Symbol(K, Decl(templateLiteralTypes1.ts, 222, 15))
->T : Symbol(T, Decl(templateLiteralTypes1.ts, 222, 13))
->K : Symbol(K, Decl(templateLiteralTypes1.ts, 222, 15))
+>SubKeys : Symbol(SubKeys, Decl(templateLiteralTypes1.ts, 221, 10))
+>T : Symbol(T, Decl(templateLiteralTypes1.ts, 223, 13))
+>K : Symbol(K, Decl(templateLiteralTypes1.ts, 223, 15))
+>K : Symbol(K, Decl(templateLiteralTypes1.ts, 223, 15))
+>T : Symbol(T, Decl(templateLiteralTypes1.ts, 223, 13))
+>K : Symbol(K, Decl(templateLiteralTypes1.ts, 223, 15))
 >PathKeys : Symbol(PathKeys, Decl(templateLiteralTypes1.ts, 213, 21))
->T : Symbol(T, Decl(templateLiteralTypes1.ts, 222, 13))
->K : Symbol(K, Decl(templateLiteralTypes1.ts, 222, 15))
+>T : Symbol(T, Decl(templateLiteralTypes1.ts, 223, 13))
+>K : Symbol(K, Decl(templateLiteralTypes1.ts, 223, 15))
 
 declare function getProp2<T, P extends PathKeys<T>>(obj: T, path: P): PropType<T, P>;
->getProp2 : Symbol(getProp2, Decl(templateLiteralTypes1.ts, 222, 89))
->T : Symbol(T, Decl(templateLiteralTypes1.ts, 224, 26))
->P : Symbol(P, Decl(templateLiteralTypes1.ts, 224, 28))
+>getProp2 : Symbol(getProp2, Decl(templateLiteralTypes1.ts, 223, 89))
+>T : Symbol(T, Decl(templateLiteralTypes1.ts, 225, 26))
+>P : Symbol(P, Decl(templateLiteralTypes1.ts, 225, 28))
 >PathKeys : Symbol(PathKeys, Decl(templateLiteralTypes1.ts, 213, 21))
->T : Symbol(T, Decl(templateLiteralTypes1.ts, 224, 26))
->obj : Symbol(obj, Decl(templateLiteralTypes1.ts, 224, 52))
->T : Symbol(T, Decl(templateLiteralTypes1.ts, 224, 26))
->path : Symbol(path, Decl(templateLiteralTypes1.ts, 224, 59))
->P : Symbol(P, Decl(templateLiteralTypes1.ts, 224, 28))
+>T : Symbol(T, Decl(templateLiteralTypes1.ts, 225, 26))
+>obj : Symbol(obj, Decl(templateLiteralTypes1.ts, 225, 52))
+>T : Symbol(T, Decl(templateLiteralTypes1.ts, 225, 26))
+>path : Symbol(path, Decl(templateLiteralTypes1.ts, 225, 59))
+>P : Symbol(P, Decl(templateLiteralTypes1.ts, 225, 28))
 >PropType : Symbol(PropType, Decl(templateLiteralTypes1.ts, 138, 69))
->T : Symbol(T, Decl(templateLiteralTypes1.ts, 224, 26))
->P : Symbol(P, Decl(templateLiteralTypes1.ts, 224, 28))
+>T : Symbol(T, Decl(templateLiteralTypes1.ts, 225, 26))
+>P : Symbol(P, Decl(templateLiteralTypes1.ts, 225, 28))
 
 const obj2 = {
->obj2 : Symbol(obj2, Decl(templateLiteralTypes1.ts, 226, 5))
+>obj2 : Symbol(obj2, Decl(templateLiteralTypes1.ts, 227, 5))
 
     name: 'John',
->name : Symbol(name, Decl(templateLiteralTypes1.ts, 226, 14))
+>name : Symbol(name, Decl(templateLiteralTypes1.ts, 227, 14))
 
     age: 42,
->age : Symbol(age, Decl(templateLiteralTypes1.ts, 227, 17))
+>age : Symbol(age, Decl(templateLiteralTypes1.ts, 228, 17))
 
     cars: [
->cars : Symbol(cars, Decl(templateLiteralTypes1.ts, 228, 12))
+>cars : Symbol(cars, Decl(templateLiteralTypes1.ts, 229, 12))
 
         { make: 'Ford', age: 10 },
->make : Symbol(make, Decl(templateLiteralTypes1.ts, 230, 9))
->age : Symbol(age, Decl(templateLiteralTypes1.ts, 230, 23))
+>make : Symbol(make, Decl(templateLiteralTypes1.ts, 231, 9))
+>age : Symbol(age, Decl(templateLiteralTypes1.ts, 231, 23))
 
         { make: 'Trabant', age: 35 }
->make : Symbol(make, Decl(templateLiteralTypes1.ts, 231, 9))
->age : Symbol(age, Decl(templateLiteralTypes1.ts, 231, 26))
+>make : Symbol(make, Decl(templateLiteralTypes1.ts, 232, 9))
+>age : Symbol(age, Decl(templateLiteralTypes1.ts, 232, 26))
 
     ]
 } as const;
 >const : Symbol(const)
 
 let make = getProp2(obj2, 'cars.1.make');  // 'Trabant'
->make : Symbol(make, Decl(templateLiteralTypes1.ts, 235, 3))
->getProp2 : Symbol(getProp2, Decl(templateLiteralTypes1.ts, 222, 89))
->obj2 : Symbol(obj2, Decl(templateLiteralTypes1.ts, 226, 5))
+>make : Symbol(make, Decl(templateLiteralTypes1.ts, 236, 3))
+>getProp2 : Symbol(getProp2, Decl(templateLiteralTypes1.ts, 223, 89))
+>obj2 : Symbol(obj2, Decl(templateLiteralTypes1.ts, 227, 5))
 
 // Repro from #46480
 
 export type Spacing =
->Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 235, 41))
+>Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 236, 41))
 
     | `0`
     | `${number}px`
@@ -963,28 +966,28 @@ export type Spacing =
     | `s${1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20}`;
 
 const spacing: Spacing = "s12"
->spacing : Symbol(spacing, Decl(templateLiteralTypes1.ts, 245, 5))
->Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 235, 41))
+>spacing : Symbol(spacing, Decl(templateLiteralTypes1.ts, 246, 5))
+>Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 236, 41))
 
 export type SpacingShorthand =
->SpacingShorthand : Symbol(SpacingShorthand, Decl(templateLiteralTypes1.ts, 245, 30))
+>SpacingShorthand : Symbol(SpacingShorthand, Decl(templateLiteralTypes1.ts, 246, 30))
 
     | `${Spacing} ${Spacing}`
->Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 235, 41))
->Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 235, 41))
+>Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 236, 41))
+>Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 236, 41))
 
     | `${Spacing} ${Spacing} ${Spacing}`
->Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 235, 41))
->Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 235, 41))
->Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 235, 41))
+>Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 236, 41))
+>Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 236, 41))
+>Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 236, 41))
 
     | `${Spacing} ${Spacing} ${Spacing} ${Spacing}`;
->Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 235, 41))
->Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 235, 41))
->Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 235, 41))
->Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 235, 41))
+>Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 236, 41))
+>Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 236, 41))
+>Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 236, 41))
+>Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 236, 41))
 
 const test1: SpacingShorthand = "0 0 0";
->test1 : Symbol(test1, Decl(templateLiteralTypes1.ts, 252, 5))
->SpacingShorthand : Symbol(SpacingShorthand, Decl(templateLiteralTypes1.ts, 245, 30))
+>test1 : Symbol(test1, Decl(templateLiteralTypes1.ts, 253, 5))
+>SpacingShorthand : Symbol(SpacingShorthand, Decl(templateLiteralTypes1.ts, 246, 30))
 
diff --git a/tests/baselines/reference/templateLiteralTypes1.types b/tests/baselines/reference/templateLiteralTypes1.types
index fed8939ea7605..42036b95f496d 100644
--- a/tests/baselines/reference/templateLiteralTypes1.types
+++ b/tests/baselines/reference/templateLiteralTypes1.types
@@ -541,6 +541,7 @@ type BB = AA<-2, -2>;
 type PathKeys<T> =
 >PathKeys : PathKeys<T>
 
+    unknown extends T ? never :
     T extends readonly any[] ? Extract<keyof T, `${number}`> | SubKeys<T, Extract<keyof T, `${number}`>> :
     T extends object ? Extract<keyof T, string> | SubKeys<T, Extract<keyof T, string>> :
     never;
diff --git a/tests/cases/conformance/types/literal/templateLiteralTypes1.ts b/tests/cases/conformance/types/literal/templateLiteralTypes1.ts
index daa3fbaeb78e7..0b11e9f5d613a 100644
--- a/tests/cases/conformance/types/literal/templateLiteralTypes1.ts
+++ b/tests/cases/conformance/types/literal/templateLiteralTypes1.ts
@@ -219,6 +219,7 @@ type BB = AA<-2, -2>;
 // Repro from #40970
 
 type PathKeys<T> =
+    unknown extends T ? never :
     T extends readonly any[] ? Extract<keyof T, `${number}`> | SubKeys<T, Extract<keyof T, `${number}`>> :
     T extends object ? Extract<keyof T, string> | SubKeys<T, Extract<keyof T, string>> :
     never;

From 2a7fc1e81c62ff9f61af2b56262bce50542562eb Mon Sep 17 00:00:00 2001
From: Anders Hejlsberg <andersh@microsoft.com>
Date: Mon, 25 Apr 2022 06:15:51 -0700
Subject: [PATCH 3/3] Add tests

---
 .../reference/numericStringLiteralTypes.js    |  80 ++++++++++++
 .../numericStringLiteralTypes.symbols         | 122 ++++++++++++++++++
 .../reference/numericStringLiteralTypes.types | 102 +++++++++++++++
 .../literal/numericStringLiteralTypes.ts      |  40 ++++++
 4 files changed, 344 insertions(+)
 create mode 100644 tests/baselines/reference/numericStringLiteralTypes.js
 create mode 100644 tests/baselines/reference/numericStringLiteralTypes.symbols
 create mode 100644 tests/baselines/reference/numericStringLiteralTypes.types
 create mode 100644 tests/cases/conformance/types/literal/numericStringLiteralTypes.ts

diff --git a/tests/baselines/reference/numericStringLiteralTypes.js b/tests/baselines/reference/numericStringLiteralTypes.js
new file mode 100644
index 0000000000000..975ca1d31799c
--- /dev/null
+++ b/tests/baselines/reference/numericStringLiteralTypes.js
@@ -0,0 +1,80 @@
+//// [numericStringLiteralTypes.ts]
+type T0 = string & `${string}`;  // string
+type T1 = string & `${number}`;  // `${number}
+type T2 = string & `${bigint}`;  // `${bigint}
+type T3<T extends string> = string & `${T}`;  // `${T}
+type T4<T extends string> = string & `${Capitalize<`${T}`>}`;  // `${Capitalize<T>}`
+
+function f1(a: boolean[], x: `${number}`) {
+    let s = a[x];  // boolean
+}
+
+function f2(a: boolean[], x: number | `${number}`) {
+    let s = a[x];  // boolean
+}
+
+type T10 = boolean[][`${number}`];  // boolean
+type T11 = boolean[][number | `${number}`];  // boolean
+
+type T20<T extends number | `${number}`> = T;
+type T21<T extends unknown[]> = { [K in keyof T]: T20<K> };
+
+type Container<T> = {
+    value: T
+}
+
+type UnwrapContainers<T extends Container<unknown>[]> = { [K in keyof T]: T[K]['value'] };
+
+declare function createContainer<T extends unknown>(value: T): Container<T>;
+
+declare function f<T extends Container<unknown>[]>(containers: [...T], callback: (...values: UnwrapContainers<T>) => void): void;
+
+const container1 = createContainer('hi')
+const container2 = createContainer(2)
+
+f([container1, container2], (value1, value2) => {
+    value1;  // string
+    value2;  // number
+});
+
+
+//// [numericStringLiteralTypes.js]
+"use strict";
+function f1(a, x) {
+    var s = a[x]; // boolean
+}
+function f2(a, x) {
+    var s = a[x]; // boolean
+}
+var container1 = createContainer('hi');
+var container2 = createContainer(2);
+f([container1, container2], function (value1, value2) {
+    value1; // string
+    value2; // number
+});
+
+
+//// [numericStringLiteralTypes.d.ts]
+declare type T0 = string & `${string}`;
+declare type T1 = string & `${number}`;
+declare type T2 = string & `${bigint}`;
+declare type T3<T extends string> = string & `${T}`;
+declare type T4<T extends string> = string & `${Capitalize<`${T}`>}`;
+declare function f1(a: boolean[], x: `${number}`): void;
+declare function f2(a: boolean[], x: number | `${number}`): void;
+declare type T10 = boolean[][`${number}`];
+declare type T11 = boolean[][number | `${number}`];
+declare type T20<T extends number | `${number}`> = T;
+declare type T21<T extends unknown[]> = {
+    [K in keyof T]: T20<K>;
+};
+declare type Container<T> = {
+    value: T;
+};
+declare type UnwrapContainers<T extends Container<unknown>[]> = {
+    [K in keyof T]: T[K]['value'];
+};
+declare function createContainer<T extends unknown>(value: T): Container<T>;
+declare function f<T extends Container<unknown>[]>(containers: [...T], callback: (...values: UnwrapContainers<T>) => void): void;
+declare const container1: Container<string>;
+declare const container2: Container<number>;
diff --git a/tests/baselines/reference/numericStringLiteralTypes.symbols b/tests/baselines/reference/numericStringLiteralTypes.symbols
new file mode 100644
index 0000000000000..33d2a1633a6d5
--- /dev/null
+++ b/tests/baselines/reference/numericStringLiteralTypes.symbols
@@ -0,0 +1,122 @@
+=== tests/cases/conformance/types/literal/numericStringLiteralTypes.ts ===
+type T0 = string & `${string}`;  // string
+>T0 : Symbol(T0, Decl(numericStringLiteralTypes.ts, 0, 0))
+
+type T1 = string & `${number}`;  // `${number}
+>T1 : Symbol(T1, Decl(numericStringLiteralTypes.ts, 0, 31))
+
+type T2 = string & `${bigint}`;  // `${bigint}
+>T2 : Symbol(T2, Decl(numericStringLiteralTypes.ts, 1, 31))
+
+type T3<T extends string> = string & `${T}`;  // `${T}
+>T3 : Symbol(T3, Decl(numericStringLiteralTypes.ts, 2, 31))
+>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 3, 8))
+>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 3, 8))
+
+type T4<T extends string> = string & `${Capitalize<`${T}`>}`;  // `${Capitalize<T>}`
+>T4 : Symbol(T4, Decl(numericStringLiteralTypes.ts, 3, 44))
+>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 4, 8))
+>Capitalize : Symbol(Capitalize, Decl(lib.es5.d.ts, --, --))
+>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 4, 8))
+
+function f1(a: boolean[], x: `${number}`) {
+>f1 : Symbol(f1, Decl(numericStringLiteralTypes.ts, 4, 61))
+>a : Symbol(a, Decl(numericStringLiteralTypes.ts, 6, 12))
+>x : Symbol(x, Decl(numericStringLiteralTypes.ts, 6, 25))
+
+    let s = a[x];  // boolean
+>s : Symbol(s, Decl(numericStringLiteralTypes.ts, 7, 7))
+>a : Symbol(a, Decl(numericStringLiteralTypes.ts, 6, 12))
+>x : Symbol(x, Decl(numericStringLiteralTypes.ts, 6, 25))
+}
+
+function f2(a: boolean[], x: number | `${number}`) {
+>f2 : Symbol(f2, Decl(numericStringLiteralTypes.ts, 8, 1))
+>a : Symbol(a, Decl(numericStringLiteralTypes.ts, 10, 12))
+>x : Symbol(x, Decl(numericStringLiteralTypes.ts, 10, 25))
+
+    let s = a[x];  // boolean
+>s : Symbol(s, Decl(numericStringLiteralTypes.ts, 11, 7))
+>a : Symbol(a, Decl(numericStringLiteralTypes.ts, 10, 12))
+>x : Symbol(x, Decl(numericStringLiteralTypes.ts, 10, 25))
+}
+
+type T10 = boolean[][`${number}`];  // boolean
+>T10 : Symbol(T10, Decl(numericStringLiteralTypes.ts, 12, 1))
+
+type T11 = boolean[][number | `${number}`];  // boolean
+>T11 : Symbol(T11, Decl(numericStringLiteralTypes.ts, 14, 34))
+
+type T20<T extends number | `${number}`> = T;
+>T20 : Symbol(T20, Decl(numericStringLiteralTypes.ts, 15, 43))
+>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 17, 9))
+>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 17, 9))
+
+type T21<T extends unknown[]> = { [K in keyof T]: T20<K> };
+>T21 : Symbol(T21, Decl(numericStringLiteralTypes.ts, 17, 45))
+>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 18, 9))
+>K : Symbol(K, Decl(numericStringLiteralTypes.ts, 18, 35))
+>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 18, 9))
+>T20 : Symbol(T20, Decl(numericStringLiteralTypes.ts, 15, 43))
+>K : Symbol(K, Decl(numericStringLiteralTypes.ts, 18, 35))
+
+type Container<T> = {
+>Container : Symbol(Container, Decl(numericStringLiteralTypes.ts, 18, 59))
+>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 20, 15))
+
+    value: T
+>value : Symbol(value, Decl(numericStringLiteralTypes.ts, 20, 21))
+>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 20, 15))
+}
+
+type UnwrapContainers<T extends Container<unknown>[]> = { [K in keyof T]: T[K]['value'] };
+>UnwrapContainers : Symbol(UnwrapContainers, Decl(numericStringLiteralTypes.ts, 22, 1))
+>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 24, 22))
+>Container : Symbol(Container, Decl(numericStringLiteralTypes.ts, 18, 59))
+>K : Symbol(K, Decl(numericStringLiteralTypes.ts, 24, 59))
+>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 24, 22))
+>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 24, 22))
+>K : Symbol(K, Decl(numericStringLiteralTypes.ts, 24, 59))
+
+declare function createContainer<T extends unknown>(value: T): Container<T>;
+>createContainer : Symbol(createContainer, Decl(numericStringLiteralTypes.ts, 24, 90))
+>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 26, 33))
+>value : Symbol(value, Decl(numericStringLiteralTypes.ts, 26, 52))
+>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 26, 33))
+>Container : Symbol(Container, Decl(numericStringLiteralTypes.ts, 18, 59))
+>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 26, 33))
+
+declare function f<T extends Container<unknown>[]>(containers: [...T], callback: (...values: UnwrapContainers<T>) => void): void;
+>f : Symbol(f, Decl(numericStringLiteralTypes.ts, 26, 76))
+>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 28, 19))
+>Container : Symbol(Container, Decl(numericStringLiteralTypes.ts, 18, 59))
+>containers : Symbol(containers, Decl(numericStringLiteralTypes.ts, 28, 51))
+>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 28, 19))
+>callback : Symbol(callback, Decl(numericStringLiteralTypes.ts, 28, 70))
+>values : Symbol(values, Decl(numericStringLiteralTypes.ts, 28, 82))
+>UnwrapContainers : Symbol(UnwrapContainers, Decl(numericStringLiteralTypes.ts, 22, 1))
+>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 28, 19))
+
+const container1 = createContainer('hi')
+>container1 : Symbol(container1, Decl(numericStringLiteralTypes.ts, 30, 5))
+>createContainer : Symbol(createContainer, Decl(numericStringLiteralTypes.ts, 24, 90))
+
+const container2 = createContainer(2)
+>container2 : Symbol(container2, Decl(numericStringLiteralTypes.ts, 31, 5))
+>createContainer : Symbol(createContainer, Decl(numericStringLiteralTypes.ts, 24, 90))
+
+f([container1, container2], (value1, value2) => {
+>f : Symbol(f, Decl(numericStringLiteralTypes.ts, 26, 76))
+>container1 : Symbol(container1, Decl(numericStringLiteralTypes.ts, 30, 5))
+>container2 : Symbol(container2, Decl(numericStringLiteralTypes.ts, 31, 5))
+>value1 : Symbol(value1, Decl(numericStringLiteralTypes.ts, 33, 29))
+>value2 : Symbol(value2, Decl(numericStringLiteralTypes.ts, 33, 36))
+
+    value1;  // string
+>value1 : Symbol(value1, Decl(numericStringLiteralTypes.ts, 33, 29))
+
+    value2;  // number
+>value2 : Symbol(value2, Decl(numericStringLiteralTypes.ts, 33, 36))
+
+});
+
diff --git a/tests/baselines/reference/numericStringLiteralTypes.types b/tests/baselines/reference/numericStringLiteralTypes.types
new file mode 100644
index 0000000000000..245a3a674ba8e
--- /dev/null
+++ b/tests/baselines/reference/numericStringLiteralTypes.types
@@ -0,0 +1,102 @@
+=== tests/cases/conformance/types/literal/numericStringLiteralTypes.ts ===
+type T0 = string & `${string}`;  // string
+>T0 : string
+
+type T1 = string & `${number}`;  // `${number}
+>T1 : `${number}`
+
+type T2 = string & `${bigint}`;  // `${bigint}
+>T2 : `${bigint}`
+
+type T3<T extends string> = string & `${T}`;  // `${T}
+>T3 : `${T}`
+
+type T4<T extends string> = string & `${Capitalize<`${T}`>}`;  // `${Capitalize<T>}`
+>T4 : `${Capitalize<`${T}`>}`
+
+function f1(a: boolean[], x: `${number}`) {
+>f1 : (a: boolean[], x: `${number}`) => void
+>a : boolean[]
+>x : `${number}`
+
+    let s = a[x];  // boolean
+>s : boolean
+>a[x] : boolean
+>a : boolean[]
+>x : `${number}`
+}
+
+function f2(a: boolean[], x: number | `${number}`) {
+>f2 : (a: boolean[], x: number | `${number}`) => void
+>a : boolean[]
+>x : number | `${number}`
+
+    let s = a[x];  // boolean
+>s : boolean
+>a[x] : boolean
+>a : boolean[]
+>x : number | `${number}`
+}
+
+type T10 = boolean[][`${number}`];  // boolean
+>T10 : boolean
+
+type T11 = boolean[][number | `${number}`];  // boolean
+>T11 : boolean
+
+type T20<T extends number | `${number}`> = T;
+>T20 : T
+
+type T21<T extends unknown[]> = { [K in keyof T]: T20<K> };
+>T21 : T21<T>
+
+type Container<T> = {
+>Container : Container<T>
+
+    value: T
+>value : T
+}
+
+type UnwrapContainers<T extends Container<unknown>[]> = { [K in keyof T]: T[K]['value'] };
+>UnwrapContainers : UnwrapContainers<T>
+
+declare function createContainer<T extends unknown>(value: T): Container<T>;
+>createContainer : <T extends unknown>(value: T) => Container<T>
+>value : T
+
+declare function f<T extends Container<unknown>[]>(containers: [...T], callback: (...values: UnwrapContainers<T>) => void): void;
+>f : <T extends Container<unknown>[]>(containers: [...T], callback: (...values: UnwrapContainers<T>) => void) => void
+>containers : [...T]
+>callback : (...values: UnwrapContainers<T>) => void
+>values : UnwrapContainers<T>
+
+const container1 = createContainer('hi')
+>container1 : Container<string>
+>createContainer('hi') : Container<string>
+>createContainer : <T extends unknown>(value: T) => Container<T>
+>'hi' : "hi"
+
+const container2 = createContainer(2)
+>container2 : Container<number>
+>createContainer(2) : Container<number>
+>createContainer : <T extends unknown>(value: T) => Container<T>
+>2 : 2
+
+f([container1, container2], (value1, value2) => {
+>f([container1, container2], (value1, value2) => {    value1;  // string    value2;  // number}) : void
+>f : <T extends Container<unknown>[]>(containers: [...T], callback: (...values: UnwrapContainers<T>) => void) => void
+>[container1, container2] : [Container<string>, Container<number>]
+>container1 : Container<string>
+>container2 : Container<number>
+>(value1, value2) => {    value1;  // string    value2;  // number} : (value1: string, value2: number) => void
+>value1 : string
+>value2 : number
+
+    value1;  // string
+>value1 : string
+
+    value2;  // number
+>value2 : number
+
+});
+
diff --git a/tests/cases/conformance/types/literal/numericStringLiteralTypes.ts b/tests/cases/conformance/types/literal/numericStringLiteralTypes.ts
new file mode 100644
index 0000000000000..cb12bde1d45b7
--- /dev/null
+++ b/tests/cases/conformance/types/literal/numericStringLiteralTypes.ts
@@ -0,0 +1,40 @@
+// @strict: true
+// @declaration: true
+
+type T0 = string & `${string}`;  // string
+type T1 = string & `${number}`;  // `${number}
+type T2 = string & `${bigint}`;  // `${bigint}
+type T3<T extends string> = string & `${T}`;  // `${T}
+type T4<T extends string> = string & `${Capitalize<`${T}`>}`;  // `${Capitalize<T>}`
+
+function f1(a: boolean[], x: `${number}`) {
+    let s = a[x];  // boolean
+}
+
+function f2(a: boolean[], x: number | `${number}`) {
+    let s = a[x];  // boolean
+}
+
+type T10 = boolean[][`${number}`];  // boolean
+type T11 = boolean[][number | `${number}`];  // boolean
+
+type T20<T extends number | `${number}`> = T;
+type T21<T extends unknown[]> = { [K in keyof T]: T20<K> };
+
+type Container<T> = {
+    value: T
+}
+
+type UnwrapContainers<T extends Container<unknown>[]> = { [K in keyof T]: T[K]['value'] };
+
+declare function createContainer<T extends unknown>(value: T): Container<T>;
+
+declare function f<T extends Container<unknown>[]>(containers: [...T], callback: (...values: UnwrapContainers<T>) => void): void;
+
+const container1 = createContainer('hi')
+const container2 = createContainer(2)
+
+f([container1, container2], (value1, value2) => {
+    value1;  // string
+    value2;  // number
+});