Skip to content

Commit 5c4caaf

Browse files
authored
Allow nongeneric string mapping types to exist (#47050)
* Allow nongeneric string mapping types to exist * Accept baseline * Recusive membership testing function * Fix lint * Add @DanielRosenwasser's comment
1 parent 86d5040 commit 5c4caaf

17 files changed

+1731
-57
lines changed

src/compiler/checker.ts

Lines changed: 57 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12182,7 +12182,7 @@ namespace ts {
1218212182
}
1218312183
if (t.flags & TypeFlags.StringMapping) {
1218412184
const constraint = getBaseConstraint((t as StringMappingType).type);
12185-
return constraint ? getStringMappingType((t as StringMappingType).symbol, constraint) : stringType;
12185+
return constraint && constraint !== (t as StringMappingType).type ? getStringMappingType((t as StringMappingType).symbol, constraint) : stringType;
1218612186
}
1218712187
if (t.flags & TypeFlags.IndexedAccess) {
1218812188
if (isMappedTypeGenericIndexedAccess(t)) {
@@ -15381,8 +15381,11 @@ namespace ts {
1538115381

1538215382
function getStringMappingType(symbol: Symbol, type: Type): Type {
1538315383
return type.flags & (TypeFlags.Union | TypeFlags.Never) ? mapType(type, t => getStringMappingType(symbol, t)) :
15384-
isGenericIndexType(type) ? getStringMappingTypeForGenericType(symbol, type) :
15384+
// Mapping<Mapping<T>> === Mapping<T>
15385+
type.flags & TypeFlags.StringMapping && symbol === type.symbol ? type :
15386+
isGenericIndexType(type) || isPatternLiteralPlaceholderType(type) ? getStringMappingTypeForGenericType(symbol, isPatternLiteralPlaceholderType(type) && !(type.flags & TypeFlags.StringMapping) ? getTemplateLiteralType(["", ""], [type]) : type) :
1538515387
type.flags & TypeFlags.StringLiteral ? getStringLiteralType(applyStringMapping(symbol, (type as StringLiteralType).value)) :
15388+
type.flags & TypeFlags.TemplateLiteral ? getTemplateLiteralType(...applyTemplateStringMapping(symbol, (type as TemplateLiteralType).texts, (type as TemplateLiteralType).types)) :
1538615389
type;
1538715390
}
1538815391

@@ -15396,6 +15399,16 @@ namespace ts {
1539615399
return str;
1539715400
}
1539815401

15402+
function applyTemplateStringMapping(symbol: Symbol, texts: readonly string[], types: readonly Type[]): [texts: readonly string[], types: readonly Type[]] {
15403+
switch (intrinsicTypeKinds.get(symbol.escapedName as string)) {
15404+
case IntrinsicTypeKind.Uppercase: return [texts.map(t => t.toUpperCase()), types.map(t => getStringMappingType(symbol, t))];
15405+
case IntrinsicTypeKind.Lowercase: return [texts.map(t => t.toLowerCase()), types.map(t => getStringMappingType(symbol, t))];
15406+
case IntrinsicTypeKind.Capitalize: return [texts[0] === "" ? texts : [texts[0].charAt(0).toUpperCase() + texts[0].slice(1), ...texts.slice(1)], texts[0] === "" ? [getStringMappingType(symbol, types[0]), ...types.slice(1)] : types];
15407+
case IntrinsicTypeKind.Uncapitalize: return [texts[0] === "" ? texts : [texts[0].charAt(0).toLowerCase() + texts[0].slice(1), ...texts.slice(1)], texts[0] === "" ? [getStringMappingType(symbol, types[0]), ...types.slice(1)] : types];
15408+
}
15409+
return [texts, types];
15410+
}
15411+
1539915412
function getStringMappingTypeForGenericType(symbol: Symbol, type: Type): Type {
1540015413
const id = `${getSymbolId(symbol)},${getTypeId(type)}`;
1540115414
let result = stringMappingTypes.get(id);
@@ -15651,8 +15664,8 @@ namespace ts {
1565115664
accessNode;
1565215665
}
1565315666

15654-
function isPatternLiteralPlaceholderType(type: Type) {
15655-
return !!(type.flags & (TypeFlags.Any | TypeFlags.String | TypeFlags.Number | TypeFlags.BigInt));
15667+
function isPatternLiteralPlaceholderType(type: Type): boolean {
15668+
return !!(type.flags & (TypeFlags.Any | TypeFlags.String | TypeFlags.Number | TypeFlags.BigInt)) || !!(type.flags & TypeFlags.StringMapping && isPatternLiteralPlaceholderType((type as StringMappingType).type));
1565615669
}
1565715670

1565815671
function isPatternLiteralType(type: Type) {
@@ -19613,6 +19626,13 @@ namespace ts {
1961319626
return Ternary.True;
1961419627
}
1961519628
}
19629+
else if (target.flags & TypeFlags.StringMapping) {
19630+
if (!(source.flags & TypeFlags.StringMapping)) {
19631+
if (isMemberOfStringMapping(source, target)) {
19632+
return Ternary.True;
19633+
}
19634+
}
19635+
}
1961619636

1961719637
if (sourceFlags & TypeFlags.TypeVariable) {
1961819638
// IndexedAccess comparisons are handled above in the `targetFlags & TypeFlage.IndexedAccess` branch
@@ -19657,7 +19677,10 @@ namespace ts {
1965719677
}
1965819678
}
1965919679
else if (sourceFlags & TypeFlags.StringMapping) {
19660-
if (targetFlags & TypeFlags.StringMapping && (source as StringMappingType).symbol === (target as StringMappingType).symbol) {
19680+
if (targetFlags & TypeFlags.StringMapping) {
19681+
if ((source as StringMappingType).symbol !== (target as StringMappingType).symbol) {
19682+
return Ternary.False;
19683+
}
1966119684
if (result = isRelatedTo((source as StringMappingType).type, (target as StringMappingType).type, RecursionFlags.Both, reportErrors)) {
1966219685
resetErrorInfo(saveErrorInfo);
1966319686
return result;
@@ -20611,7 +20634,7 @@ namespace ts {
2061120634
}
2061220635
}
2061320636

20614-
return isUnitType(type) || !!(type.flags & TypeFlags.TemplateLiteral);
20637+
return isUnitType(type) || !!(type.flags & TypeFlags.TemplateLiteral) || !!(type.flags & TypeFlags.StringMapping);
2061520638
}
2061620639

2061720640
function getExactOptionalUnassignableProperties(source: Type, target: Type) {
@@ -22171,6 +22194,32 @@ namespace ts {
2217122194
&& (!roundTripOnly || s === pseudoBigIntToString({ negative, base10Value: parsePseudoBigInt(scanner.getTokenValue()) }));
2217222195
}
2217322196

22197+
function isMemberOfStringMapping(source: Type, target: Type): boolean {
22198+
if (target.flags & (TypeFlags.String | TypeFlags.AnyOrUnknown)) {
22199+
return true;
22200+
}
22201+
if (target.flags & TypeFlags.TemplateLiteral) {
22202+
return isTypeAssignableTo(source, target);
22203+
}
22204+
if (target.flags & TypeFlags.StringMapping) {
22205+
// We need to see whether applying the same mappings of the target
22206+
// onto the source would produce an identical type *and* that
22207+
// it's compatible with the inner-most non-string-mapped type.
22208+
//
22209+
// The intuition here is that if same mappings don't affect the source at all,
22210+
// and the source is compatible with the unmapped target, then they must
22211+
// still reside in the same domain.
22212+
const mappingStack = [];
22213+
while (target.flags & TypeFlags.StringMapping) {
22214+
mappingStack.unshift(target.symbol);
22215+
target = (target as StringMappingType).type;
22216+
}
22217+
const mappedSource = reduceLeft(mappingStack, (memo, value) => getStringMappingType(value, memo), source);
22218+
return mappedSource === source && isMemberOfStringMapping(source, target);
22219+
}
22220+
return false;
22221+
}
22222+
2217422223
function isValidTypeForTemplateLiteralPlaceholder(source: Type, target: Type): boolean {
2217522224
if (source === target || target.flags & (TypeFlags.Any | TypeFlags.String)) {
2217622225
return true;
@@ -22179,7 +22228,8 @@ namespace ts {
2217922228
const value = (source as StringLiteralType).value;
2218022229
return !!(target.flags & TypeFlags.Number && isValidNumberString(value, /*roundTripOnly*/ false) ||
2218122230
target.flags & TypeFlags.BigInt && isValidBigIntString(value, /*roundTripOnly*/ false) ||
22182-
target.flags & (TypeFlags.BooleanLiteral | TypeFlags.Nullable) && value === (target as IntrinsicType).intrinsicName);
22231+
target.flags & (TypeFlags.BooleanLiteral | TypeFlags.Nullable) && value === (target as IntrinsicType).intrinsicName ||
22232+
target.flags & TypeFlags.StringMapping && isMemberOfStringMapping(getStringLiteralType(value), target));
2218322233
}
2218422234
if (source.flags & TypeFlags.TemplateLiteral) {
2218522235
const texts = (source as TemplateLiteralType).texts;

tests/baselines/reference/intrinsicTypes.errors.txt

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,35 +13,35 @@ tests/cases/conformance/types/typeAliases/intrinsicTypes.ts(43,5): error TS2322:
1313
==== tests/cases/conformance/types/typeAliases/intrinsicTypes.ts (8 errors) ====
1414
type TU1 = Uppercase<'hello'>; // "HELLO"
1515
type TU2 = Uppercase<'foo' | 'bar'>; // "FOO" | "BAR"
16-
type TU3 = Uppercase<string>; // string
17-
type TU4 = Uppercase<any>; // any
16+
type TU3 = Uppercase<string>; // Uppercase<string>
17+
type TU4 = Uppercase<any>; // Uppercase<`${any}`>
1818
type TU5 = Uppercase<never>; // never
1919
type TU6 = Uppercase<42>; // Error
2020
~~
2121
!!! error TS2344: Type 'number' does not satisfy the constraint 'string'.
2222

2323
type TL1 = Lowercase<'HELLO'>; // "hello"
2424
type TL2 = Lowercase<'FOO' | 'BAR'>; // "foo" | "bar"
25-
type TL3 = Lowercase<string>; // string
26-
type TL4 = Lowercase<any>; // any
25+
type TL3 = Lowercase<string>; // Lowercase<string>
26+
type TL4 = Lowercase<any>; // Lowercase<`${any}`>
2727
type TL5 = Lowercase<never>; // never
2828
type TL6 = Lowercase<42>; // Error
2929
~~
3030
!!! error TS2344: Type 'number' does not satisfy the constraint 'string'.
3131

3232
type TC1 = Capitalize<'hello'>; // "Hello"
3333
type TC2 = Capitalize<'foo' | 'bar'>; // "Foo" | "Bar"
34-
type TC3 = Capitalize<string>; // string
35-
type TC4 = Capitalize<any>; // any
34+
type TC3 = Capitalize<string>; // Capitalize<string>
35+
type TC4 = Capitalize<any>; // Capitalize<`${any}`>
3636
type TC5 = Capitalize<never>; // never
3737
type TC6 = Capitalize<42>; // Error
3838
~~
3939
!!! error TS2344: Type 'number' does not satisfy the constraint 'string'.
4040

4141
type TN1 = Uncapitalize<'Hello'>; // "hello"
4242
type TN2 = Uncapitalize<'Foo' | 'Bar'>; // "foo" | "bar"
43-
type TN3 = Uncapitalize<string>; // string
44-
type TN4 = Uncapitalize<any>; // any
43+
type TN3 = Uncapitalize<string>; // Uncapitalize<string>
44+
type TN4 = Uncapitalize<any>; // Uncapitalize<`${any}`>
4545
type TN5 = Uncapitalize<never>; // never
4646
type TN6 = Uncapitalize<42>; // Error
4747
~~

tests/baselines/reference/intrinsicTypes.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,29 @@
11
//// [intrinsicTypes.ts]
22
type TU1 = Uppercase<'hello'>; // "HELLO"
33
type TU2 = Uppercase<'foo' | 'bar'>; // "FOO" | "BAR"
4-
type TU3 = Uppercase<string>; // string
5-
type TU4 = Uppercase<any>; // any
4+
type TU3 = Uppercase<string>; // Uppercase<string>
5+
type TU4 = Uppercase<any>; // Uppercase<`${any}`>
66
type TU5 = Uppercase<never>; // never
77
type TU6 = Uppercase<42>; // Error
88

99
type TL1 = Lowercase<'HELLO'>; // "hello"
1010
type TL2 = Lowercase<'FOO' | 'BAR'>; // "foo" | "bar"
11-
type TL3 = Lowercase<string>; // string
12-
type TL4 = Lowercase<any>; // any
11+
type TL3 = Lowercase<string>; // Lowercase<string>
12+
type TL4 = Lowercase<any>; // Lowercase<`${any}`>
1313
type TL5 = Lowercase<never>; // never
1414
type TL6 = Lowercase<42>; // Error
1515

1616
type TC1 = Capitalize<'hello'>; // "Hello"
1717
type TC2 = Capitalize<'foo' | 'bar'>; // "Foo" | "Bar"
18-
type TC3 = Capitalize<string>; // string
19-
type TC4 = Capitalize<any>; // any
18+
type TC3 = Capitalize<string>; // Capitalize<string>
19+
type TC4 = Capitalize<any>; // Capitalize<`${any}`>
2020
type TC5 = Capitalize<never>; // never
2121
type TC6 = Capitalize<42>; // Error
2222

2323
type TN1 = Uncapitalize<'Hello'>; // "hello"
2424
type TN2 = Uncapitalize<'Foo' | 'Bar'>; // "foo" | "bar"
25-
type TN3 = Uncapitalize<string>; // string
26-
type TN4 = Uncapitalize<any>; // any
25+
type TN3 = Uncapitalize<string>; // Uncapitalize<string>
26+
type TN4 = Uncapitalize<any>; // Uncapitalize<`${any}`>
2727
type TN5 = Uncapitalize<never>; // never
2828
type TN6 = Uncapitalize<42>; // Error
2929

tests/baselines/reference/intrinsicTypes.symbols

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ type TU2 = Uppercase<'foo' | 'bar'>; // "FOO" | "BAR"
77
>TU2 : Symbol(TU2, Decl(intrinsicTypes.ts, 0, 30))
88
>Uppercase : Symbol(Uppercase, Decl(lib.es5.d.ts, --, --))
99

10-
type TU3 = Uppercase<string>; // string
10+
type TU3 = Uppercase<string>; // Uppercase<string>
1111
>TU3 : Symbol(TU3, Decl(intrinsicTypes.ts, 1, 36))
1212
>Uppercase : Symbol(Uppercase, Decl(lib.es5.d.ts, --, --))
1313

14-
type TU4 = Uppercase<any>; // any
14+
type TU4 = Uppercase<any>; // Uppercase<`${any}`>
1515
>TU4 : Symbol(TU4, Decl(intrinsicTypes.ts, 2, 29))
1616
>Uppercase : Symbol(Uppercase, Decl(lib.es5.d.ts, --, --))
1717

@@ -31,11 +31,11 @@ type TL2 = Lowercase<'FOO' | 'BAR'>; // "foo" | "bar"
3131
>TL2 : Symbol(TL2, Decl(intrinsicTypes.ts, 7, 30))
3232
>Lowercase : Symbol(Lowercase, Decl(lib.es5.d.ts, --, --))
3333

34-
type TL3 = Lowercase<string>; // string
34+
type TL3 = Lowercase<string>; // Lowercase<string>
3535
>TL3 : Symbol(TL3, Decl(intrinsicTypes.ts, 8, 36))
3636
>Lowercase : Symbol(Lowercase, Decl(lib.es5.d.ts, --, --))
3737

38-
type TL4 = Lowercase<any>; // any
38+
type TL4 = Lowercase<any>; // Lowercase<`${any}`>
3939
>TL4 : Symbol(TL4, Decl(intrinsicTypes.ts, 9, 29))
4040
>Lowercase : Symbol(Lowercase, Decl(lib.es5.d.ts, --, --))
4141

@@ -55,11 +55,11 @@ type TC2 = Capitalize<'foo' | 'bar'>; // "Foo" | "Bar"
5555
>TC2 : Symbol(TC2, Decl(intrinsicTypes.ts, 14, 31))
5656
>Capitalize : Symbol(Capitalize, Decl(lib.es5.d.ts, --, --))
5757

58-
type TC3 = Capitalize<string>; // string
58+
type TC3 = Capitalize<string>; // Capitalize<string>
5959
>TC3 : Symbol(TC3, Decl(intrinsicTypes.ts, 15, 37))
6060
>Capitalize : Symbol(Capitalize, Decl(lib.es5.d.ts, --, --))
6161

62-
type TC4 = Capitalize<any>; // any
62+
type TC4 = Capitalize<any>; // Capitalize<`${any}`>
6363
>TC4 : Symbol(TC4, Decl(intrinsicTypes.ts, 16, 30))
6464
>Capitalize : Symbol(Capitalize, Decl(lib.es5.d.ts, --, --))
6565

@@ -79,11 +79,11 @@ type TN2 = Uncapitalize<'Foo' | 'Bar'>; // "foo" | "bar"
7979
>TN2 : Symbol(TN2, Decl(intrinsicTypes.ts, 21, 33))
8080
>Uncapitalize : Symbol(Uncapitalize, Decl(lib.es5.d.ts, --, --))
8181

82-
type TN3 = Uncapitalize<string>; // string
82+
type TN3 = Uncapitalize<string>; // Uncapitalize<string>
8383
>TN3 : Symbol(TN3, Decl(intrinsicTypes.ts, 22, 39))
8484
>Uncapitalize : Symbol(Uncapitalize, Decl(lib.es5.d.ts, --, --))
8585

86-
type TN4 = Uncapitalize<any>; // any
86+
type TN4 = Uncapitalize<any>; // Uncapitalize<`${any}`>
8787
>TN4 : Symbol(TN4, Decl(intrinsicTypes.ts, 23, 32))
8888
>Uncapitalize : Symbol(Uncapitalize, Decl(lib.es5.d.ts, --, --))
8989

tests/baselines/reference/intrinsicTypes.types

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ type TU1 = Uppercase<'hello'>; // "HELLO"
55
type TU2 = Uppercase<'foo' | 'bar'>; // "FOO" | "BAR"
66
>TU2 : "FOO" | "BAR"
77

8-
type TU3 = Uppercase<string>; // string
9-
>TU3 : string
8+
type TU3 = Uppercase<string>; // Uppercase<string>
9+
>TU3 : Uppercase<string>
1010

11-
type TU4 = Uppercase<any>; // any
12-
>TU4 : any
11+
type TU4 = Uppercase<any>; // Uppercase<`${any}`>
12+
>TU4 : Uppercase<`${any}`>
1313

1414
type TU5 = Uppercase<never>; // never
1515
>TU5 : never
@@ -23,11 +23,11 @@ type TL1 = Lowercase<'HELLO'>; // "hello"
2323
type TL2 = Lowercase<'FOO' | 'BAR'>; // "foo" | "bar"
2424
>TL2 : "foo" | "bar"
2525

26-
type TL3 = Lowercase<string>; // string
27-
>TL3 : string
26+
type TL3 = Lowercase<string>; // Lowercase<string>
27+
>TL3 : Lowercase<string>
2828

29-
type TL4 = Lowercase<any>; // any
30-
>TL4 : any
29+
type TL4 = Lowercase<any>; // Lowercase<`${any}`>
30+
>TL4 : Lowercase<`${any}`>
3131

3232
type TL5 = Lowercase<never>; // never
3333
>TL5 : never
@@ -41,11 +41,11 @@ type TC1 = Capitalize<'hello'>; // "Hello"
4141
type TC2 = Capitalize<'foo' | 'bar'>; // "Foo" | "Bar"
4242
>TC2 : "Foo" | "Bar"
4343

44-
type TC3 = Capitalize<string>; // string
45-
>TC3 : string
44+
type TC3 = Capitalize<string>; // Capitalize<string>
45+
>TC3 : Capitalize<string>
4646

47-
type TC4 = Capitalize<any>; // any
48-
>TC4 : any
47+
type TC4 = Capitalize<any>; // Capitalize<`${any}`>
48+
>TC4 : Capitalize<`${any}`>
4949

5050
type TC5 = Capitalize<never>; // never
5151
>TC5 : never
@@ -59,11 +59,11 @@ type TN1 = Uncapitalize<'Hello'>; // "hello"
5959
type TN2 = Uncapitalize<'Foo' | 'Bar'>; // "foo" | "bar"
6060
>TN2 : "foo" | "bar"
6161

62-
type TN3 = Uncapitalize<string>; // string
63-
>TN3 : string
62+
type TN3 = Uncapitalize<string>; // Uncapitalize<string>
63+
>TN3 : Uncapitalize<string>
6464

65-
type TN4 = Uncapitalize<any>; // any
66-
>TN4 : any
65+
type TN4 = Uncapitalize<any>; // Uncapitalize<`${any}`>
66+
>TN4 : Uncapitalize<`${any}`>
6767

6868
type TN5 = Uncapitalize<never>; // never
6969
>TN5 : never

tests/baselines/reference/mappedTypeConstraints2.errors.txt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
tests/cases/conformance/types/mapped/mappedTypeConstraints2.ts(10,11): error TS2322: Type 'Mapped2<K>[`get${K}`]' is not assignable to type '{ a: K; }'.
22
Type 'Mapped2<K>[`get${string}`]' is not assignable to type '{ a: K; }'.
33
tests/cases/conformance/types/mapped/mappedTypeConstraints2.ts(16,11): error TS2322: Type 'Mapped3<K>[Uppercase<K>]' is not assignable to type '{ a: K; }'.
4-
Type 'Mapped3<K>[string]' is not assignable to type '{ a: K; }'.
4+
Type 'Mapped3<K>[Uppercase<string>]' is not assignable to type '{ a: K; }'.
5+
Type 'Mapped3<K>[string]' is not assignable to type '{ a: K; }'.
56
tests/cases/conformance/types/mapped/mappedTypeConstraints2.ts(25,57): error TS2322: Type 'Foo<T>[`get${T}`]' is not assignable to type 'T'.
67
'T' could be instantiated with an arbitrary type which could be unrelated to 'Foo<T>[`get${T}`]'.
78

@@ -28,7 +29,8 @@ tests/cases/conformance/types/mapped/mappedTypeConstraints2.ts(25,57): error TS2
2829
const x: { a: K } = obj[key]; // Error
2930
~
3031
!!! error TS2322: Type 'Mapped3<K>[Uppercase<K>]' is not assignable to type '{ a: K; }'.
31-
!!! error TS2322: Type 'Mapped3<K>[string]' is not assignable to type '{ a: K; }'.
32+
!!! error TS2322: Type 'Mapped3<K>[Uppercase<string>]' is not assignable to type '{ a: K; }'.
33+
!!! error TS2322: Type 'Mapped3<K>[string]' is not assignable to type '{ a: K; }'.
3234
}
3335

3436
// Repro from #47794
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
tests/cases/conformance/types/literal/stringLiteralsAssignedToStringMappings.ts(7,1): error TS2322: Type 'string' is not assignable to type 'Uppercase<Lowercase<string>>'.
2+
tests/cases/conformance/types/literal/stringLiteralsAssignedToStringMappings.ts(15,1): error TS2322: Type 'string' is not assignable to type 'Uppercase<`${Lowercase<`${number}`>}`>'.
3+
tests/cases/conformance/types/literal/stringLiteralsAssignedToStringMappings.ts(16,1): error TS2322: Type 'string' is not assignable to type 'Uppercase<`${Lowercase<`${number}`>}`>'.
4+
5+
6+
==== tests/cases/conformance/types/literal/stringLiteralsAssignedToStringMappings.ts (3 errors) ====
7+
declare var x: Uppercase<Lowercase<string>>;
8+
9+
// good
10+
x = "A";
11+
12+
// bad
13+
x = "a";
14+
~
15+
!!! error TS2322: Type 'string' is not assignable to type 'Uppercase<Lowercase<string>>'.
16+
17+
declare var y: Uppercase<Lowercase<`${number}`>>;
18+
19+
// good
20+
y = "1";
21+
22+
// bad
23+
y = "a";
24+
~
25+
!!! error TS2322: Type 'string' is not assignable to type 'Uppercase<`${Lowercase<`${number}`>}`>'.
26+
y = "A";
27+
~
28+
!!! error TS2322: Type 'string' is not assignable to type 'Uppercase<`${Lowercase<`${number}`>}`>'.

0 commit comments

Comments
 (0)