Skip to content

Commit 361eaff

Browse files
committed
Allow nongeneric string mapping types to exist
1 parent 4761ba6 commit 361eaff

11 files changed

+1567
-55
lines changed

src/compiler/checker.ts

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11943,7 +11943,7 @@ namespace ts {
1194311943
}
1194411944
if (t.flags & TypeFlags.StringMapping) {
1194511945
const constraint = getBaseConstraint((t as StringMappingType).type);
11946-
return constraint ? getStringMappingType((t as StringMappingType).symbol, constraint) : stringType;
11946+
return constraint && constraint !== (t as StringMappingType).type ? getStringMappingType((t as StringMappingType).symbol, constraint) : stringType;
1194711947
}
1194811948
if (t.flags & TypeFlags.IndexedAccess) {
1194911949
const baseObjectType = getBaseConstraint((t as IndexedAccessType).objectType);
@@ -15040,8 +15040,11 @@ namespace ts {
1504015040

1504115041
function getStringMappingType(symbol: Symbol, type: Type): Type {
1504215042
return type.flags & (TypeFlags.Union | TypeFlags.Never) ? mapType(type, t => getStringMappingType(symbol, t)) :
15043-
isGenericIndexType(type) ? getStringMappingTypeForGenericType(symbol, type) :
15043+
// Mapping<Mapping<T>> === Mapping<T>
15044+
type.flags & TypeFlags.StringMapping && symbol === type.symbol ? type :
15045+
isGenericIndexType(type) || isPatternLiteralPlaceholderType(type) ? getStringMappingTypeForGenericType(symbol, isPatternLiteralPlaceholderType(type) && !(type.flags & TypeFlags.StringMapping) ? getTemplateLiteralType(["", ""], [type]) : type) :
1504415046
type.flags & TypeFlags.StringLiteral ? getStringLiteralType(applyStringMapping(symbol, (type as StringLiteralType).value)) :
15047+
type.flags & TypeFlags.TemplateLiteral ? getTemplateLiteralType(...applyTemplateStringMapping(symbol, (type as TemplateLiteralType).texts, (type as TemplateLiteralType).types)) :
1504515048
type;
1504615049
}
1504715050

@@ -15055,6 +15058,16 @@ namespace ts {
1505515058
return str;
1505615059
}
1505715060

15061+
function applyTemplateStringMapping(symbol: Symbol, texts: readonly string[], types: readonly Type[]): [texts: readonly string[], types: readonly Type[]] {
15062+
switch (intrinsicTypeKinds.get(symbol.escapedName as string)) {
15063+
case IntrinsicTypeKind.Uppercase: return [texts.map(t => t.toUpperCase()), types.map(t => getStringMappingType(symbol, t))];
15064+
case IntrinsicTypeKind.Lowercase: return [texts.map(t => t.toLowerCase()), types.map(t => getStringMappingType(symbol, t))];
15065+
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];
15066+
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];
15067+
}
15068+
return [texts, types];
15069+
}
15070+
1505815071
function getStringMappingTypeForGenericType(symbol: Symbol, type: Type): Type {
1505915072
const id = `${getSymbolId(symbol)},${getTypeId(type)}`;
1506015073
let result = stringMappingTypes.get(id);
@@ -15310,8 +15323,8 @@ namespace ts {
1531015323
accessNode;
1531115324
}
1531215325

15313-
function isPatternLiteralPlaceholderType(type: Type) {
15314-
return !!(type.flags & (TypeFlags.Any | TypeFlags.String | TypeFlags.Number | TypeFlags.BigInt));
15326+
function isPatternLiteralPlaceholderType(type: Type): boolean {
15327+
return !!(type.flags & (TypeFlags.Any | TypeFlags.String | TypeFlags.Number | TypeFlags.BigInt)) || !!(type.flags & TypeFlags.StringMapping && isPatternLiteralPlaceholderType((type as StringMappingType).type));
1531515328
}
1531615329

1531715330
function isPatternLiteralType(type: Type) {
@@ -19168,6 +19181,13 @@ namespace ts {
1916819181
return Ternary.True;
1916919182
}
1917019183
}
19184+
else if (target.flags & TypeFlags.StringMapping) {
19185+
if (!(source.flags & TypeFlags.StringMapping)) {
19186+
if (isUnchangedByStringMapping(target.symbol, source)) {
19187+
return Ternary.True;
19188+
}
19189+
}
19190+
}
1917119191

1917219192
if (source.flags & TypeFlags.TypeVariable) {
1917319193
// IndexedAccess comparisons are handled above in the `target.flags & TypeFlage.IndexedAccess` branch
@@ -19208,7 +19228,10 @@ namespace ts {
1920819228
}
1920919229
}
1921019230
else if (source.flags & TypeFlags.StringMapping) {
19211-
if (target.flags & TypeFlags.StringMapping && (source as StringMappingType).symbol === (target as StringMappingType).symbol) {
19231+
if (target.flags & TypeFlags.StringMapping) {
19232+
if ((source as StringMappingType).symbol !== (target as StringMappingType).symbol) {
19233+
return Ternary.False;
19234+
}
1921219235
if (result = isRelatedTo((source as StringMappingType).type, (target as StringMappingType).type, RecursionFlags.Both, reportErrors)) {
1921319236
resetErrorInfo(saveErrorInfo);
1921419237
return result;
@@ -20129,7 +20152,7 @@ namespace ts {
2012920152
}
2013020153
}
2013120154

20132-
return isUnitType(type) || !!(type.flags & TypeFlags.TemplateLiteral);
20155+
return isUnitType(type) || !!(type.flags & TypeFlags.TemplateLiteral) || !!(type.flags & TypeFlags.StringMapping);
2013320156
}
2013420157

2013520158
function getExactOptionalUnassignableProperties(source: Type, target: Type) {
@@ -21625,6 +21648,10 @@ namespace ts {
2162521648
return success && result === SyntaxKind.BigIntLiteral && scanner.getTextPos() === (s.length + 1) && !(flags & TokenFlags.ContainsSeparator);
2162621649
}
2162721650

21651+
function isUnchangedByStringMapping(symbol: Symbol, value: Type) {
21652+
return value === getStringMappingType(symbol, value);
21653+
}
21654+
2162821655
function isValidTypeForTemplateLiteralPlaceholder(source: Type, target: Type): boolean {
2162921656
if (source === target || target.flags & (TypeFlags.Any | TypeFlags.String)) {
2163021657
return true;
@@ -21633,7 +21660,8 @@ namespace ts {
2163321660
const value = (source as StringLiteralType).value;
2163421661
return !!(target.flags & TypeFlags.Number && value !== "" && isFinite(+value) ||
2163521662
target.flags & TypeFlags.BigInt && value !== "" && isValidBigIntString(value) ||
21636-
target.flags & (TypeFlags.BooleanLiteral | TypeFlags.Nullable) && value === (target as IntrinsicType).intrinsicName);
21663+
target.flags & (TypeFlags.BooleanLiteral | TypeFlags.Nullable) && value === (target as IntrinsicType).intrinsicName ||
21664+
target.flags & TypeFlags.StringMapping && isUnchangedByStringMapping(target.symbol, getStringLiteralType(value)));
2163721665
}
2163821666
if (source.flags & TypeFlags.TemplateLiteral) {
2163921667
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

0 commit comments

Comments
 (0)