Skip to content

Commit 787bb9d

Browse files
authored
Improve support for numeric string types (microsoft#48837)
* Improve support for numeric string types * Update test * Add tests
1 parent 7920783 commit 787bb9d

10 files changed

+430
-60
lines changed

src/compiler/checker.ts

+32-13
Original file line numberDiff line numberDiff line change
@@ -833,6 +833,7 @@ namespace ts {
833833
const keyofConstraintType = keyofStringsOnly ? stringType : stringNumberSymbolType;
834834
const numberOrBigIntType = getUnionType([numberType, bigintType]);
835835
const templateConstraintType = getUnionType([stringType, numberType, booleanType, bigintType, nullType, undefinedType]) as UnionType;
836+
const numericStringType = getTemplateLiteralType(["", ""], [numberType]); // The `${number}` type
836837

837838
const restrictiveMapper: TypeMapper = makeFunctionTypeMapper(t => t.flags & TypeFlags.TypeParameter ? getRestrictiveTypeParameter(t as TypeParameter) : t);
838839
const permissiveMapper: TypeMapper = makeFunctionTypeMapper(t => t.flags & TypeFlags.TypeParameter ? wildcardType : t);
@@ -12273,7 +12274,7 @@ namespace ts {
1227312274
const typeVariable = getHomomorphicTypeVariable(type);
1227412275
if (typeVariable && !type.declaration.nameType) {
1227512276
const constraint = getConstraintOfTypeParameter(typeVariable);
12276-
if (constraint && (isArrayType(constraint) || isTupleType(constraint))) {
12277+
if (constraint && isArrayOrTupleType(constraint)) {
1227712278
return instantiateType(type, prependTypeMapping(typeVariable, constraint, type.mapper));
1227812279
}
1227912280
}
@@ -12654,10 +12655,10 @@ namespace ts {
1265412655

1265512656
function isApplicableIndexType(source: Type, target: Type): boolean {
1265612657
// A 'string' index signature applies to types assignable to 'string' or 'number', and a 'number' index
12657-
// signature applies to types assignable to 'number' and numeric string literal types.
12658+
// signature applies to types assignable to 'number', `${number}` and numeric string literal types.
1265812659
return isTypeAssignableTo(source, target) ||
1265912660
target === stringType && isTypeAssignableTo(source, numberType) ||
12660-
target === numberType && !!(source.flags & TypeFlags.StringLiteral) && isNumericLiteralName((source as StringLiteralType).value);
12661+
target === numberType && (source === numericStringType || !!(source.flags & TypeFlags.StringLiteral) && isNumericLiteralName((source as StringLiteralType).value));
1266112662
}
1266212663

1266312664
function getIndexInfosOfStructuredType(type: Type): readonly IndexInfo[] {
@@ -13778,6 +13779,20 @@ namespace ts {
1377813779
constraints = append(constraints, constraint);
1377913780
}
1378013781
}
13782+
// Given a homomorphic mapped type { [K in keyof T]: XXX }, where T is constrained to an array or tuple type, in the
13783+
// template type XXX, K has an added constraint of number | `${number}`.
13784+
else if (type.flags & TypeFlags.TypeParameter && parent.kind === SyntaxKind.MappedType && node === (parent as MappedTypeNode).type) {
13785+
const mappedType = getTypeFromTypeNode(parent as TypeNode) as MappedType;
13786+
if (getTypeParameterFromMappedType(mappedType) === getActualTypeVariable(type)) {
13787+
const typeParameter = getHomomorphicTypeVariable(mappedType);
13788+
if (typeParameter) {
13789+
const constraint = getConstraintOfTypeParameter(typeParameter);
13790+
if (constraint && everyType(constraint, isArrayOrTupleType)) {
13791+
constraints = append(constraints, getUnionType([numberType, numericStringType]));
13792+
}
13793+
}
13794+
}
13795+
}
1378113796
node = parent;
1378213797
}
1378313798
return constraints ? getSubstitutionType(type, getIntersectionType(append(constraints, type))) : type;
@@ -14817,7 +14832,7 @@ namespace ts {
1481714832
i--;
1481814833
const t = types[i];
1481914834
const remove =
14820-
t.flags & TypeFlags.String && includes & TypeFlags.StringLiteral ||
14835+
t.flags & TypeFlags.String && includes & (TypeFlags.StringLiteral | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) ||
1482114836
t.flags & TypeFlags.Number && includes & TypeFlags.NumberLiteral ||
1482214837
t.flags & TypeFlags.BigInt && includes & TypeFlags.BigIntLiteral ||
1482314838
t.flags & TypeFlags.ESSymbol && includes & TypeFlags.UniqueESSymbol;
@@ -14978,7 +14993,7 @@ namespace ts {
1497814993
if (!strictNullChecks && includes & TypeFlags.Nullable) {
1497914994
return includes & TypeFlags.Undefined ? undefinedType : nullType;
1498014995
}
14981-
if (includes & TypeFlags.String && includes & TypeFlags.StringLiteral ||
14996+
if (includes & TypeFlags.String && includes & (TypeFlags.StringLiteral | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) ||
1498214997
includes & TypeFlags.Number && includes & TypeFlags.NumberLiteral ||
1498314998
includes & TypeFlags.BigInt && includes & TypeFlags.BigIntLiteral ||
1498414999
includes & TypeFlags.ESSymbol && includes & TypeFlags.UniqueESSymbol) {
@@ -16996,7 +17011,7 @@ namespace ts {
1699617011
if (!type.declaration.nameType) {
1699717012
let constraint;
1699817013
if (isArrayType(t) || t.flags & TypeFlags.Any && findResolutionCycleStartIndex(typeVariable, TypeSystemPropertyName.ImmediateBaseConstraint) < 0 &&
16999-
(constraint = getConstraintOfTypeParameter(typeVariable)) && everyType(constraint, or(isArrayType, isTupleType))) {
17014+
(constraint = getConstraintOfTypeParameter(typeVariable)) && everyType(constraint, isArrayOrTupleType)) {
1700017015
return instantiateMappedArrayType(t, type, prependTypeMapping(typeVariable, t, mapper));
1700117016
}
1700217017
if (isGenericTupleType(t)) {
@@ -18565,7 +18580,7 @@ namespace ts {
1856518580
}
1856618581
return false;
1856718582
}
18568-
return isTupleType(target) || isArrayType(target);
18583+
return isArrayOrTupleType(target);
1856918584
}
1857018585
if (isReadonlyArrayType(source) && isMutableArrayOrTuple(target)) {
1857118586
if (reportErrors) {
@@ -18700,7 +18715,7 @@ namespace ts {
1870018715
// recursive intersections that are structurally similar but not exactly identical. See #37854.
1870118716
if (result && !inPropertyCheck && (
1870218717
target.flags & TypeFlags.Intersection && (isPerformingExcessPropertyChecks || isPerformingCommonPropertyChecks) ||
18703-
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)))) {
18718+
isNonGenericObjectType(target) && !isArrayOrTupleType(target) && source.flags & TypeFlags.Intersection && getApparentType(source).flags & TypeFlags.StructuredType && !some((source as IntersectionType).types, t => !!(getObjectFlags(t) & ObjectFlags.NonInferrableType)))) {
1870418719
inPropertyCheck = true;
1870518720
result &= recursiveTypeRelatedTo(source, target, reportErrors, IntersectionState.PropertyCheck, recursionFlags);
1870618721
inPropertyCheck = false;
@@ -19708,7 +19723,7 @@ namespace ts {
1970819723
return varianceResult;
1970919724
}
1971019725
}
19711-
else if (isReadonlyArrayType(target) ? isArrayType(source) || isTupleType(source) : isArrayType(target) && isTupleType(source) && !source.target.readonly) {
19726+
else if (isReadonlyArrayType(target) ? isArrayOrTupleType(source) : isArrayType(target) && isTupleType(source) && !source.target.readonly) {
1971219727
if (relation !== identityRelation) {
1971319728
return isRelatedTo(getIndexTypeOfType(source, numberType) || anyType, getIndexTypeOfType(target, numberType) || anyType, RecursionFlags.Both, reportErrors);
1971419729
}
@@ -20093,7 +20108,7 @@ namespace ts {
2009320108
}
2009420109
let result = Ternary.True;
2009520110
if (isTupleType(target)) {
20096-
if (isArrayType(source) || isTupleType(source)) {
20111+
if (isArrayOrTupleType(source)) {
2009720112
if (!target.target.readonly && (isReadonlyArrayType(source) || isTupleType(source) && source.target.readonly)) {
2009820113
return Ternary.False;
2009920114
}
@@ -21098,6 +21113,10 @@ namespace ts {
2109821113
return !!(getObjectFlags(type) & ObjectFlags.Reference) && (type as TypeReference).target === globalReadonlyArrayType;
2109921114
}
2110021115

21116+
function isArrayOrTupleType(type: Type): type is TypeReference {
21117+
return isArrayType(type) || isTupleType(type);
21118+
}
21119+
2110121120
function isMutableArrayOrTuple(type: Type): boolean {
2110221121
return isArrayType(type) && !isReadonlyArrayType(type) || isTupleType(type) && !type.target.readonly;
2110321122
}
@@ -21598,7 +21617,7 @@ namespace ts {
2159821617
else if (type.flags & TypeFlags.Intersection) {
2159921618
result = getIntersectionType(sameMap((type as IntersectionType).types, getWidenedType));
2160021619
}
21601-
else if (isArrayType(type) || isTupleType(type)) {
21620+
else if (isArrayOrTupleType(type)) {
2160221621
result = createTypeReference(type.target, sameMap(getTypeArguments(type), getWidenedType));
2160321622
}
2160421623
if (result && context === undefined) {
@@ -21635,7 +21654,7 @@ namespace ts {
2163521654
}
2163621655
}
2163721656
}
21638-
if (isArrayType(type) || isTupleType(type)) {
21657+
if (isArrayOrTupleType(type)) {
2163921658
for (const t of getTypeArguments(type)) {
2164021659
if (reportWideningErrorsInType(t)) {
2164121660
errorReported = true;
@@ -22714,7 +22733,7 @@ namespace ts {
2271422733
}
2271522734
// Infer from the members of source and target only if the two types are possibly related
2271622735
if (!typesDefinitelyUnrelated(source, target)) {
22717-
if (isArrayType(source) || isTupleType(source)) {
22736+
if (isArrayOrTupleType(source)) {
2271822737
if (isTupleType(target)) {
2271922738
const sourceArity = getTypeReferenceArity(source);
2272022739
const targetArity = getTypeReferenceArity(target);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
//// [numericStringLiteralTypes.ts]
2+
type T0 = string & `${string}`; // string
3+
type T1 = string & `${number}`; // `${number}
4+
type T2 = string & `${bigint}`; // `${bigint}
5+
type T3<T extends string> = string & `${T}`; // `${T}
6+
type T4<T extends string> = string & `${Capitalize<`${T}`>}`; // `${Capitalize<T>}`
7+
8+
function f1(a: boolean[], x: `${number}`) {
9+
let s = a[x]; // boolean
10+
}
11+
12+
function f2(a: boolean[], x: number | `${number}`) {
13+
let s = a[x]; // boolean
14+
}
15+
16+
type T10 = boolean[][`${number}`]; // boolean
17+
type T11 = boolean[][number | `${number}`]; // boolean
18+
19+
type T20<T extends number | `${number}`> = T;
20+
type T21<T extends unknown[]> = { [K in keyof T]: T20<K> };
21+
22+
type Container<T> = {
23+
value: T
24+
}
25+
26+
type UnwrapContainers<T extends Container<unknown>[]> = { [K in keyof T]: T[K]['value'] };
27+
28+
declare function createContainer<T extends unknown>(value: T): Container<T>;
29+
30+
declare function f<T extends Container<unknown>[]>(containers: [...T], callback: (...values: UnwrapContainers<T>) => void): void;
31+
32+
const container1 = createContainer('hi')
33+
const container2 = createContainer(2)
34+
35+
f([container1, container2], (value1, value2) => {
36+
value1; // string
37+
value2; // number
38+
});
39+
40+
41+
//// [numericStringLiteralTypes.js]
42+
"use strict";
43+
function f1(a, x) {
44+
var s = a[x]; // boolean
45+
}
46+
function f2(a, x) {
47+
var s = a[x]; // boolean
48+
}
49+
var container1 = createContainer('hi');
50+
var container2 = createContainer(2);
51+
f([container1, container2], function (value1, value2) {
52+
value1; // string
53+
value2; // number
54+
});
55+
56+
57+
//// [numericStringLiteralTypes.d.ts]
58+
declare type T0 = string & `${string}`;
59+
declare type T1 = string & `${number}`;
60+
declare type T2 = string & `${bigint}`;
61+
declare type T3<T extends string> = string & `${T}`;
62+
declare type T4<T extends string> = string & `${Capitalize<`${T}`>}`;
63+
declare function f1(a: boolean[], x: `${number}`): void;
64+
declare function f2(a: boolean[], x: number | `${number}`): void;
65+
declare type T10 = boolean[][`${number}`];
66+
declare type T11 = boolean[][number | `${number}`];
67+
declare type T20<T extends number | `${number}`> = T;
68+
declare type T21<T extends unknown[]> = {
69+
[K in keyof T]: T20<K>;
70+
};
71+
declare type Container<T> = {
72+
value: T;
73+
};
74+
declare type UnwrapContainers<T extends Container<unknown>[]> = {
75+
[K in keyof T]: T[K]['value'];
76+
};
77+
declare function createContainer<T extends unknown>(value: T): Container<T>;
78+
declare function f<T extends Container<unknown>[]>(containers: [...T], callback: (...values: UnwrapContainers<T>) => void): void;
79+
declare const container1: Container<string>;
80+
declare const container2: Container<number>;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
=== tests/cases/conformance/types/literal/numericStringLiteralTypes.ts ===
2+
type T0 = string & `${string}`; // string
3+
>T0 : Symbol(T0, Decl(numericStringLiteralTypes.ts, 0, 0))
4+
5+
type T1 = string & `${number}`; // `${number}
6+
>T1 : Symbol(T1, Decl(numericStringLiteralTypes.ts, 0, 31))
7+
8+
type T2 = string & `${bigint}`; // `${bigint}
9+
>T2 : Symbol(T2, Decl(numericStringLiteralTypes.ts, 1, 31))
10+
11+
type T3<T extends string> = string & `${T}`; // `${T}
12+
>T3 : Symbol(T3, Decl(numericStringLiteralTypes.ts, 2, 31))
13+
>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 3, 8))
14+
>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 3, 8))
15+
16+
type T4<T extends string> = string & `${Capitalize<`${T}`>}`; // `${Capitalize<T>}`
17+
>T4 : Symbol(T4, Decl(numericStringLiteralTypes.ts, 3, 44))
18+
>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 4, 8))
19+
>Capitalize : Symbol(Capitalize, Decl(lib.es5.d.ts, --, --))
20+
>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 4, 8))
21+
22+
function f1(a: boolean[], x: `${number}`) {
23+
>f1 : Symbol(f1, Decl(numericStringLiteralTypes.ts, 4, 61))
24+
>a : Symbol(a, Decl(numericStringLiteralTypes.ts, 6, 12))
25+
>x : Symbol(x, Decl(numericStringLiteralTypes.ts, 6, 25))
26+
27+
let s = a[x]; // boolean
28+
>s : Symbol(s, Decl(numericStringLiteralTypes.ts, 7, 7))
29+
>a : Symbol(a, Decl(numericStringLiteralTypes.ts, 6, 12))
30+
>x : Symbol(x, Decl(numericStringLiteralTypes.ts, 6, 25))
31+
}
32+
33+
function f2(a: boolean[], x: number | `${number}`) {
34+
>f2 : Symbol(f2, Decl(numericStringLiteralTypes.ts, 8, 1))
35+
>a : Symbol(a, Decl(numericStringLiteralTypes.ts, 10, 12))
36+
>x : Symbol(x, Decl(numericStringLiteralTypes.ts, 10, 25))
37+
38+
let s = a[x]; // boolean
39+
>s : Symbol(s, Decl(numericStringLiteralTypes.ts, 11, 7))
40+
>a : Symbol(a, Decl(numericStringLiteralTypes.ts, 10, 12))
41+
>x : Symbol(x, Decl(numericStringLiteralTypes.ts, 10, 25))
42+
}
43+
44+
type T10 = boolean[][`${number}`]; // boolean
45+
>T10 : Symbol(T10, Decl(numericStringLiteralTypes.ts, 12, 1))
46+
47+
type T11 = boolean[][number | `${number}`]; // boolean
48+
>T11 : Symbol(T11, Decl(numericStringLiteralTypes.ts, 14, 34))
49+
50+
type T20<T extends number | `${number}`> = T;
51+
>T20 : Symbol(T20, Decl(numericStringLiteralTypes.ts, 15, 43))
52+
>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 17, 9))
53+
>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 17, 9))
54+
55+
type T21<T extends unknown[]> = { [K in keyof T]: T20<K> };
56+
>T21 : Symbol(T21, Decl(numericStringLiteralTypes.ts, 17, 45))
57+
>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 18, 9))
58+
>K : Symbol(K, Decl(numericStringLiteralTypes.ts, 18, 35))
59+
>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 18, 9))
60+
>T20 : Symbol(T20, Decl(numericStringLiteralTypes.ts, 15, 43))
61+
>K : Symbol(K, Decl(numericStringLiteralTypes.ts, 18, 35))
62+
63+
type Container<T> = {
64+
>Container : Symbol(Container, Decl(numericStringLiteralTypes.ts, 18, 59))
65+
>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 20, 15))
66+
67+
value: T
68+
>value : Symbol(value, Decl(numericStringLiteralTypes.ts, 20, 21))
69+
>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 20, 15))
70+
}
71+
72+
type UnwrapContainers<T extends Container<unknown>[]> = { [K in keyof T]: T[K]['value'] };
73+
>UnwrapContainers : Symbol(UnwrapContainers, Decl(numericStringLiteralTypes.ts, 22, 1))
74+
>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 24, 22))
75+
>Container : Symbol(Container, Decl(numericStringLiteralTypes.ts, 18, 59))
76+
>K : Symbol(K, Decl(numericStringLiteralTypes.ts, 24, 59))
77+
>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 24, 22))
78+
>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 24, 22))
79+
>K : Symbol(K, Decl(numericStringLiteralTypes.ts, 24, 59))
80+
81+
declare function createContainer<T extends unknown>(value: T): Container<T>;
82+
>createContainer : Symbol(createContainer, Decl(numericStringLiteralTypes.ts, 24, 90))
83+
>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 26, 33))
84+
>value : Symbol(value, Decl(numericStringLiteralTypes.ts, 26, 52))
85+
>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 26, 33))
86+
>Container : Symbol(Container, Decl(numericStringLiteralTypes.ts, 18, 59))
87+
>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 26, 33))
88+
89+
declare function f<T extends Container<unknown>[]>(containers: [...T], callback: (...values: UnwrapContainers<T>) => void): void;
90+
>f : Symbol(f, Decl(numericStringLiteralTypes.ts, 26, 76))
91+
>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 28, 19))
92+
>Container : Symbol(Container, Decl(numericStringLiteralTypes.ts, 18, 59))
93+
>containers : Symbol(containers, Decl(numericStringLiteralTypes.ts, 28, 51))
94+
>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 28, 19))
95+
>callback : Symbol(callback, Decl(numericStringLiteralTypes.ts, 28, 70))
96+
>values : Symbol(values, Decl(numericStringLiteralTypes.ts, 28, 82))
97+
>UnwrapContainers : Symbol(UnwrapContainers, Decl(numericStringLiteralTypes.ts, 22, 1))
98+
>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 28, 19))
99+
100+
const container1 = createContainer('hi')
101+
>container1 : Symbol(container1, Decl(numericStringLiteralTypes.ts, 30, 5))
102+
>createContainer : Symbol(createContainer, Decl(numericStringLiteralTypes.ts, 24, 90))
103+
104+
const container2 = createContainer(2)
105+
>container2 : Symbol(container2, Decl(numericStringLiteralTypes.ts, 31, 5))
106+
>createContainer : Symbol(createContainer, Decl(numericStringLiteralTypes.ts, 24, 90))
107+
108+
f([container1, container2], (value1, value2) => {
109+
>f : Symbol(f, Decl(numericStringLiteralTypes.ts, 26, 76))
110+
>container1 : Symbol(container1, Decl(numericStringLiteralTypes.ts, 30, 5))
111+
>container2 : Symbol(container2, Decl(numericStringLiteralTypes.ts, 31, 5))
112+
>value1 : Symbol(value1, Decl(numericStringLiteralTypes.ts, 33, 29))
113+
>value2 : Symbol(value2, Decl(numericStringLiteralTypes.ts, 33, 36))
114+
115+
value1; // string
116+
>value1 : Symbol(value1, Decl(numericStringLiteralTypes.ts, 33, 29))
117+
118+
value2; // number
119+
>value2 : Symbol(value2, Decl(numericStringLiteralTypes.ts, 33, 36))
120+
121+
});
122+

0 commit comments

Comments
 (0)