Skip to content

Commit 28cd1fb

Browse files
authored
Allow intersections to be used as valid types for template literal placeholders (#54188)
1 parent cd391b0 commit 28cd1fb

File tree

7 files changed

+123
-8
lines changed

7 files changed

+123
-8
lines changed

src/compiler/checker.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16457,7 +16457,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1645716457
}
1645816458

1645916459
function removeStringLiteralsMatchedByTemplateLiterals(types: Type[]) {
16460-
const templates = filter(types, t => !!(t.flags & TypeFlags.TemplateLiteral) && isPatternLiteralType(t)) as TemplateLiteralType[];
16460+
const templates = filter(types, t =>
16461+
!!(t.flags & TypeFlags.TemplateLiteral) &&
16462+
isPatternLiteralType(t) &&
16463+
(t as TemplateLiteralType).types.every(t => !(t.flags & TypeFlags.Intersection) || !areIntersectedTypesAvoidingPrimitiveReduction((t as IntersectionType).types))
16464+
) as TemplateLiteralType[];
1646116465
if (templates.length) {
1646216466
let i = types.length;
1646316467
while (i > 0) {
@@ -16966,16 +16970,20 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1696616970
return reduceLeft(types, (n, t) => n + getConstituentCount(t), 0);
1696716971
}
1696816972

16969-
function areIntersectedTypesAvoidingPrimitiveReduction(t1: Type, t2: Type) {
16970-
return !!(t1.flags & (TypeFlags.String | TypeFlags.Number | TypeFlags.BigInt)) && t2 === emptyTypeLiteralType;
16973+
function areIntersectedTypesAvoidingPrimitiveReduction(types: Type[], primitiveFlags = TypeFlags.String | TypeFlags.Number | TypeFlags.BigInt): boolean {
16974+
if (types.length !== 2) {
16975+
return false;
16976+
}
16977+
const [t1, t2] = types;
16978+
return !!(t1.flags & primitiveFlags) && t2 === emptyTypeLiteralType || !!(t2.flags & primitiveFlags) && t1 === emptyTypeLiteralType;
1697116979
}
1697216980

1697316981
function getTypeFromIntersectionTypeNode(node: IntersectionTypeNode): Type {
1697416982
const links = getNodeLinks(node);
1697516983
if (!links.resolvedType) {
1697616984
const aliasSymbol = getAliasSymbolForTypeNode(node);
1697716985
const types = map(node.types, getTypeFromTypeNode);
16978-
const noSupertypeReduction = types.length === 2 && (areIntersectedTypesAvoidingPrimitiveReduction(types[0], types[1]) || areIntersectedTypesAvoidingPrimitiveReduction(types[1], types[0]));
16986+
const noSupertypeReduction = areIntersectedTypesAvoidingPrimitiveReduction(types);
1697916987
links.resolvedType = getIntersectionType(types, aliasSymbol, getTypeArgumentsForAliasSymbol(aliasSymbol), noSupertypeReduction);
1698016988
}
1698116989
return links.resolvedType;
@@ -24362,12 +24370,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2436224370
if (source === target || target.flags & (TypeFlags.Any | TypeFlags.String)) {
2436324371
return true;
2436424372
}
24373+
if (target.flags & TypeFlags.Intersection) {
24374+
return every((target as IntersectionType).types, t => t === emptyTypeLiteralType || isValidTypeForTemplateLiteralPlaceholder(source, t));
24375+
}
2436524376
if (source.flags & TypeFlags.StringLiteral) {
2436624377
const value = (source as StringLiteralType).value;
2436724378
return !!(target.flags & TypeFlags.Number && isValidNumberString(value, /*roundTripOnly*/ false) ||
2436824379
target.flags & TypeFlags.BigInt && isValidBigIntString(value, /*roundTripOnly*/ false) ||
2436924380
target.flags & (TypeFlags.BooleanLiteral | TypeFlags.Nullable) && value === (target as IntrinsicType).intrinsicName ||
24370-
target.flags & TypeFlags.StringMapping && isMemberOfStringMapping(getStringLiteralType(value), target));
24381+
target.flags & TypeFlags.StringMapping && isMemberOfStringMapping(getStringLiteralType(value), target) ||
24382+
target.flags & TypeFlags.TemplateLiteral && isTypeMatchedByTemplateLiteralType(source, target as TemplateLiteralType));
2437124383
}
2437224384
if (source.flags & TypeFlags.TemplateLiteral) {
2437324385
const texts = (source as TemplateLiteralType).texts;

tests/baselines/reference/templateLiteralTypesPatterns.errors.txt

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,10 @@ templateLiteralTypesPatterns.ts(129,9): error TS2345: Argument of type '"1.1e-10
5555
templateLiteralTypesPatterns.ts(140,1): error TS2322: Type '`a${string}`' is not assignable to type '`a${number}`'.
5656
templateLiteralTypesPatterns.ts(141,1): error TS2322: Type '"bno"' is not assignable to type '`a${any}`'.
5757
templateLiteralTypesPatterns.ts(160,7): error TS2322: Type '"anything"' is not assignable to type '`${number} ${number}`'.
58+
templateLiteralTypesPatterns.ts(211,5): error TS2345: Argument of type '"abcTest"' is not assignable to parameter of type '`${`a${string}` & `${string}a`}Test`'.
5859

5960

60-
==== templateLiteralTypesPatterns.ts (57 errors) ====
61+
==== templateLiteralTypesPatterns.ts (58 errors) ====
6162
type RequiresLeadingSlash = `/${string}`;
6263

6364
// ok
@@ -373,4 +374,15 @@ templateLiteralTypesPatterns.ts(160,7): error TS2322: Type '"anything"' is not a
373374
this.get(id!);
374375
}
375376
}
376-
377+
378+
// repro from https://github.com/microsoft/TypeScript/issues/54177#issuecomment-1538436654
379+
function conversionTest(groupName: | "downcast" | "dataDowncast" | "editingDowncast" | `${string & {}}Downcast`) {}
380+
conversionTest("testDowncast");
381+
function conversionTest2(groupName: | "downcast" | "dataDowncast" | "editingDowncast" | `${{} & string}Downcast`) {}
382+
conversionTest2("testDowncast");
383+
384+
function foo(str: `${`a${string}` & `${string}a`}Test`) {}
385+
foo("abaTest"); // ok
386+
foo("abcTest"); // error
387+
~~~~~~~~~
388+
!!! error TS2345: Argument of type '"abcTest"' is not assignable to parameter of type '`${`a${string}` & `${string}a`}Test`'.

tests/baselines/reference/templateLiteralTypesPatterns.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,16 @@ export abstract class BB {
202202
this.get(id!);
203203
}
204204
}
205-
205+
206+
// repro from https://github.com/microsoft/TypeScript/issues/54177#issuecomment-1538436654
207+
function conversionTest(groupName: | "downcast" | "dataDowncast" | "editingDowncast" | `${string & {}}Downcast`) {}
208+
conversionTest("testDowncast");
209+
function conversionTest2(groupName: | "downcast" | "dataDowncast" | "editingDowncast" | `${{} & string}Downcast`) {}
210+
conversionTest2("testDowncast");
211+
212+
function foo(str: `${`a${string}` & `${string}a`}Test`) {}
213+
foo("abaTest"); // ok
214+
foo("abcTest"); // error
206215

207216
//// [templateLiteralTypesPatterns.js]
208217
"use strict";
@@ -353,3 +362,11 @@ var BB = /** @class */ (function () {
353362
return BB;
354363
}());
355364
exports.BB = BB;
365+
// repro from https://github.com/microsoft/TypeScript/issues/54177#issuecomment-1538436654
366+
function conversionTest(groupName) { }
367+
conversionTest("testDowncast");
368+
function conversionTest2(groupName) { }
369+
conversionTest2("testDowncast");
370+
function foo(str) { }
371+
foo("abaTest"); // ok
372+
foo("abcTest"); // error

tests/baselines/reference/templateLiteralTypesPatterns.symbols

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,3 +487,28 @@ export abstract class BB {
487487
}
488488
}
489489

490+
// repro from https://github.com/microsoft/TypeScript/issues/54177#issuecomment-1538436654
491+
function conversionTest(groupName: | "downcast" | "dataDowncast" | "editingDowncast" | `${string & {}}Downcast`) {}
492+
>conversionTest : Symbol(conversionTest, Decl(templateLiteralTypesPatterns.ts, 200, 1))
493+
>groupName : Symbol(groupName, Decl(templateLiteralTypesPatterns.ts, 203, 24))
494+
495+
conversionTest("testDowncast");
496+
>conversionTest : Symbol(conversionTest, Decl(templateLiteralTypesPatterns.ts, 200, 1))
497+
498+
function conversionTest2(groupName: | "downcast" | "dataDowncast" | "editingDowncast" | `${{} & string}Downcast`) {}
499+
>conversionTest2 : Symbol(conversionTest2, Decl(templateLiteralTypesPatterns.ts, 204, 31))
500+
>groupName : Symbol(groupName, Decl(templateLiteralTypesPatterns.ts, 205, 25))
501+
502+
conversionTest2("testDowncast");
503+
>conversionTest2 : Symbol(conversionTest2, Decl(templateLiteralTypesPatterns.ts, 204, 31))
504+
505+
function foo(str: `${`a${string}` & `${string}a`}Test`) {}
506+
>foo : Symbol(foo, Decl(templateLiteralTypesPatterns.ts, 206, 32))
507+
>str : Symbol(str, Decl(templateLiteralTypesPatterns.ts, 208, 13))
508+
509+
foo("abaTest"); // ok
510+
>foo : Symbol(foo, Decl(templateLiteralTypesPatterns.ts, 206, 32))
511+
512+
foo("abcTest"); // error
513+
>foo : Symbol(foo, Decl(templateLiteralTypesPatterns.ts, 206, 32))
514+

tests/baselines/reference/templateLiteralTypesPatterns.types

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -635,3 +635,36 @@ export abstract class BB {
635635
}
636636
}
637637

638+
// repro from https://github.com/microsoft/TypeScript/issues/54177#issuecomment-1538436654
639+
function conversionTest(groupName: | "downcast" | "dataDowncast" | "editingDowncast" | `${string & {}}Downcast`) {}
640+
>conversionTest : (groupName: "downcast" | "dataDowncast" | "editingDowncast" | `${string & {}}Downcast`) => void
641+
>groupName : `${string & {}}Downcast` | "downcast" | "dataDowncast" | "editingDowncast"
642+
643+
conversionTest("testDowncast");
644+
>conversionTest("testDowncast") : void
645+
>conversionTest : (groupName: `${string & {}}Downcast` | "downcast" | "dataDowncast" | "editingDowncast") => void
646+
>"testDowncast" : "testDowncast"
647+
648+
function conversionTest2(groupName: | "downcast" | "dataDowncast" | "editingDowncast" | `${{} & string}Downcast`) {}
649+
>conversionTest2 : (groupName: "downcast" | "dataDowncast" | "editingDowncast" | `${{} & string}Downcast`) => void
650+
>groupName : "downcast" | "dataDowncast" | "editingDowncast" | `${{} & string}Downcast`
651+
652+
conversionTest2("testDowncast");
653+
>conversionTest2("testDowncast") : void
654+
>conversionTest2 : (groupName: "downcast" | "dataDowncast" | "editingDowncast" | `${{} & string}Downcast`) => void
655+
>"testDowncast" : "testDowncast"
656+
657+
function foo(str: `${`a${string}` & `${string}a`}Test`) {}
658+
>foo : (str: `${`a${string}` & `${string}a`}Test`) => void
659+
>str : `${`a${string}` & `${string}a`}Test`
660+
661+
foo("abaTest"); // ok
662+
>foo("abaTest") : void
663+
>foo : (str: `${`a${string}` & `${string}a`}Test`) => void
664+
>"abaTest" : "abaTest"
665+
666+
foo("abcTest"); // error
667+
>foo("abcTest") : void
668+
>foo : (str: `${`a${string}` & `${string}a`}Test`) => void
669+
>"abcTest" : "abcTest"
670+

tests/cases/conformance/types/literal/templateLiteralTypesPatterns.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,3 +200,13 @@ export abstract class BB {
200200
this.get(id!);
201201
}
202202
}
203+
204+
// repro from https://github.com/microsoft/TypeScript/issues/54177#issuecomment-1538436654
205+
function conversionTest(groupName: | "downcast" | "dataDowncast" | "editingDowncast" | `${string & {}}Downcast`) {}
206+
conversionTest("testDowncast");
207+
function conversionTest2(groupName: | "downcast" | "dataDowncast" | "editingDowncast" | `${{} & string}Downcast`) {}
208+
conversionTest2("testDowncast");
209+
210+
function foo(str: `${`a${string}` & `${string}a`}Test`) {}
211+
foo("abaTest"); // ok
212+
foo("abcTest"); // error
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
//// function conversionTest(groupName: | "downcast" | "dataDowncast" | "editingDowncast" | `${string & {}}Downcast`) {}
4+
//// conversionTest("/**/");
5+
6+
verify.completions({ marker: "", exact: ["downcast", "dataDowncast", "editingDowncast"] });

0 commit comments

Comments
 (0)