diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9003ebc18dad7..21e03819e6ade 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16439,7 +16439,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function removeStringLiteralsMatchedByTemplateLiterals(types: Type[]) { - const templates = filter(types, t => !!(t.flags & TypeFlags.TemplateLiteral) && isPatternLiteralType(t)) as TemplateLiteralType[]; + const templates = filter(types, t => + !!(t.flags & TypeFlags.TemplateLiteral) && + isPatternLiteralType(t) && + (t as TemplateLiteralType).types.every(t => !(t.flags & TypeFlags.Intersection) || !areIntersectedTypesAvoidingPrimitiveReduction((t as IntersectionType).types)) + ) as TemplateLiteralType[]; if (templates.length) { let i = types.length; while (i > 0) { @@ -16948,8 +16952,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return reduceLeft(types, (n, t) => n + getConstituentCount(t), 0); } - function areIntersectedTypesAvoidingPrimitiveReduction(t1: Type, t2: Type) { - return !!(t1.flags & (TypeFlags.String | TypeFlags.Number | TypeFlags.BigInt)) && t2 === emptyTypeLiteralType; + function areIntersectedTypesAvoidingPrimitiveReduction(types: Type[], primitiveFlags = TypeFlags.String | TypeFlags.Number | TypeFlags.BigInt): boolean { + if (types.length !== 2) { + return false; + } + const [t1, t2] = types; + return !!(t1.flags & primitiveFlags) && t2 === emptyTypeLiteralType || !!(t2.flags & primitiveFlags) && t1 === emptyTypeLiteralType; } function getTypeFromIntersectionTypeNode(node: IntersectionTypeNode): Type { @@ -16957,7 +16965,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (!links.resolvedType) { const aliasSymbol = getAliasSymbolForTypeNode(node); const types = map(node.types, getTypeFromTypeNode); - const noSupertypeReduction = types.length === 2 && (areIntersectedTypesAvoidingPrimitiveReduction(types[0], types[1]) || areIntersectedTypesAvoidingPrimitiveReduction(types[1], types[0])); + const noSupertypeReduction = areIntersectedTypesAvoidingPrimitiveReduction(types); links.resolvedType = getIntersectionType(types, aliasSymbol, getTypeArgumentsForAliasSymbol(aliasSymbol), noSupertypeReduction); } return links.resolvedType; @@ -24312,12 +24320,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (source === target || target.flags & (TypeFlags.Any | TypeFlags.String)) { return true; } + if (target.flags & TypeFlags.Intersection) { + return every((target as IntersectionType).types, t => t === emptyTypeLiteralType || isValidTypeForTemplateLiteralPlaceholder(source, t)); + } if (source.flags & TypeFlags.StringLiteral) { const value = (source as StringLiteralType).value; return !!(target.flags & TypeFlags.Number && isValidNumberString(value, /*roundTripOnly*/ false) || target.flags & TypeFlags.BigInt && isValidBigIntString(value, /*roundTripOnly*/ false) || target.flags & (TypeFlags.BooleanLiteral | TypeFlags.Nullable) && value === (target as IntrinsicType).intrinsicName || - target.flags & TypeFlags.StringMapping && isMemberOfStringMapping(getStringLiteralType(value), target)); + target.flags & TypeFlags.StringMapping && isMemberOfStringMapping(getStringLiteralType(value), target) || + target.flags & TypeFlags.TemplateLiteral && isTypeMatchedByTemplateLiteralType(source, target as TemplateLiteralType)); } if (source.flags & TypeFlags.TemplateLiteral) { const texts = (source as TemplateLiteralType).texts; diff --git a/tests/baselines/reference/templateLiteralTypesPatterns.errors.txt b/tests/baselines/reference/templateLiteralTypesPatterns.errors.txt index e92c040be43ac..cd82afc83981b 100644 --- a/tests/baselines/reference/templateLiteralTypesPatterns.errors.txt +++ b/tests/baselines/reference/templateLiteralTypesPatterns.errors.txt @@ -55,9 +55,10 @@ templateLiteralTypesPatterns.ts(129,9): error TS2345: Argument of type '"1.1e-10 templateLiteralTypesPatterns.ts(140,1): error TS2322: Type '`a${string}`' is not assignable to type '`a${number}`'. templateLiteralTypesPatterns.ts(141,1): error TS2322: Type '"bno"' is not assignable to type '`a${any}`'. templateLiteralTypesPatterns.ts(160,7): error TS2322: Type '"anything"' is not assignable to type '`${number} ${number}`'. +templateLiteralTypesPatterns.ts(211,5): error TS2345: Argument of type '"abcTest"' is not assignable to parameter of type '`${`a${string}` & `${string}a`}Test`'. -==== templateLiteralTypesPatterns.ts (57 errors) ==== +==== templateLiteralTypesPatterns.ts (58 errors) ==== type RequiresLeadingSlash = `/${string}`; // ok @@ -373,4 +374,15 @@ templateLiteralTypesPatterns.ts(160,7): error TS2322: Type '"anything"' is not a this.get(id!); } } - \ No newline at end of file + + // repro from https://github.com/microsoft/TypeScript/issues/54177#issuecomment-1538436654 + function conversionTest(groupName: | "downcast" | "dataDowncast" | "editingDowncast" | `${string & {}}Downcast`) {} + conversionTest("testDowncast"); + function conversionTest2(groupName: | "downcast" | "dataDowncast" | "editingDowncast" | `${{} & string}Downcast`) {} + conversionTest2("testDowncast"); + + function foo(str: `${`a${string}` & `${string}a`}Test`) {} + foo("abaTest"); // ok + foo("abcTest"); // error + ~~~~~~~~~ +!!! error TS2345: Argument of type '"abcTest"' is not assignable to parameter of type '`${`a${string}` & `${string}a`}Test`'. \ No newline at end of file diff --git a/tests/baselines/reference/templateLiteralTypesPatterns.js b/tests/baselines/reference/templateLiteralTypesPatterns.js index 7b3cb228da0da..d66c794e287a8 100644 --- a/tests/baselines/reference/templateLiteralTypesPatterns.js +++ b/tests/baselines/reference/templateLiteralTypesPatterns.js @@ -202,7 +202,16 @@ export abstract class BB { this.get(id!); } } - + +// repro from https://github.com/microsoft/TypeScript/issues/54177#issuecomment-1538436654 +function conversionTest(groupName: | "downcast" | "dataDowncast" | "editingDowncast" | `${string & {}}Downcast`) {} +conversionTest("testDowncast"); +function conversionTest2(groupName: | "downcast" | "dataDowncast" | "editingDowncast" | `${{} & string}Downcast`) {} +conversionTest2("testDowncast"); + +function foo(str: `${`a${string}` & `${string}a`}Test`) {} +foo("abaTest"); // ok +foo("abcTest"); // error //// [templateLiteralTypesPatterns.js] "use strict"; @@ -353,3 +362,11 @@ var BB = /** @class */ (function () { return BB; }()); exports.BB = BB; +// repro from https://github.com/microsoft/TypeScript/issues/54177#issuecomment-1538436654 +function conversionTest(groupName) { } +conversionTest("testDowncast"); +function conversionTest2(groupName) { } +conversionTest2("testDowncast"); +function foo(str) { } +foo("abaTest"); // ok +foo("abcTest"); // error diff --git a/tests/baselines/reference/templateLiteralTypesPatterns.symbols b/tests/baselines/reference/templateLiteralTypesPatterns.symbols index 802d5ff263b72..cc508d2680959 100644 --- a/tests/baselines/reference/templateLiteralTypesPatterns.symbols +++ b/tests/baselines/reference/templateLiteralTypesPatterns.symbols @@ -487,3 +487,28 @@ export abstract class BB { } } +// repro from https://github.com/microsoft/TypeScript/issues/54177#issuecomment-1538436654 +function conversionTest(groupName: | "downcast" | "dataDowncast" | "editingDowncast" | `${string & {}}Downcast`) {} +>conversionTest : Symbol(conversionTest, Decl(templateLiteralTypesPatterns.ts, 200, 1)) +>groupName : Symbol(groupName, Decl(templateLiteralTypesPatterns.ts, 203, 24)) + +conversionTest("testDowncast"); +>conversionTest : Symbol(conversionTest, Decl(templateLiteralTypesPatterns.ts, 200, 1)) + +function conversionTest2(groupName: | "downcast" | "dataDowncast" | "editingDowncast" | `${{} & string}Downcast`) {} +>conversionTest2 : Symbol(conversionTest2, Decl(templateLiteralTypesPatterns.ts, 204, 31)) +>groupName : Symbol(groupName, Decl(templateLiteralTypesPatterns.ts, 205, 25)) + +conversionTest2("testDowncast"); +>conversionTest2 : Symbol(conversionTest2, Decl(templateLiteralTypesPatterns.ts, 204, 31)) + +function foo(str: `${`a${string}` & `${string}a`}Test`) {} +>foo : Symbol(foo, Decl(templateLiteralTypesPatterns.ts, 206, 32)) +>str : Symbol(str, Decl(templateLiteralTypesPatterns.ts, 208, 13)) + +foo("abaTest"); // ok +>foo : Symbol(foo, Decl(templateLiteralTypesPatterns.ts, 206, 32)) + +foo("abcTest"); // error +>foo : Symbol(foo, Decl(templateLiteralTypesPatterns.ts, 206, 32)) + diff --git a/tests/baselines/reference/templateLiteralTypesPatterns.types b/tests/baselines/reference/templateLiteralTypesPatterns.types index d9176480bc7b0..d9fbfeec9d8b2 100644 --- a/tests/baselines/reference/templateLiteralTypesPatterns.types +++ b/tests/baselines/reference/templateLiteralTypesPatterns.types @@ -635,3 +635,36 @@ export abstract class BB { } } +// repro from https://github.com/microsoft/TypeScript/issues/54177#issuecomment-1538436654 +function conversionTest(groupName: | "downcast" | "dataDowncast" | "editingDowncast" | `${string & {}}Downcast`) {} +>conversionTest : (groupName: "downcast" | "dataDowncast" | "editingDowncast" | `${string & {}}Downcast`) => void +>groupName : `${string & {}}Downcast` | "downcast" | "dataDowncast" | "editingDowncast" + +conversionTest("testDowncast"); +>conversionTest("testDowncast") : void +>conversionTest : (groupName: `${string & {}}Downcast` | "downcast" | "dataDowncast" | "editingDowncast") => void +>"testDowncast" : "testDowncast" + +function conversionTest2(groupName: | "downcast" | "dataDowncast" | "editingDowncast" | `${{} & string}Downcast`) {} +>conversionTest2 : (groupName: "downcast" | "dataDowncast" | "editingDowncast" | `${{} & string}Downcast`) => void +>groupName : "downcast" | "dataDowncast" | "editingDowncast" | `${{} & string}Downcast` + +conversionTest2("testDowncast"); +>conversionTest2("testDowncast") : void +>conversionTest2 : (groupName: "downcast" | "dataDowncast" | "editingDowncast" | `${{} & string}Downcast`) => void +>"testDowncast" : "testDowncast" + +function foo(str: `${`a${string}` & `${string}a`}Test`) {} +>foo : (str: `${`a${string}` & `${string}a`}Test`) => void +>str : `${`a${string}` & `${string}a`}Test` + +foo("abaTest"); // ok +>foo("abaTest") : void +>foo : (str: `${`a${string}` & `${string}a`}Test`) => void +>"abaTest" : "abaTest" + +foo("abcTest"); // error +>foo("abcTest") : void +>foo : (str: `${`a${string}` & `${string}a`}Test`) => void +>"abcTest" : "abcTest" + diff --git a/tests/cases/conformance/types/literal/templateLiteralTypesPatterns.ts b/tests/cases/conformance/types/literal/templateLiteralTypesPatterns.ts index a98facd4359a3..aeb05ead7ccca 100644 --- a/tests/cases/conformance/types/literal/templateLiteralTypesPatterns.ts +++ b/tests/cases/conformance/types/literal/templateLiteralTypesPatterns.ts @@ -200,3 +200,13 @@ export abstract class BB { this.get(id!); } } + +// repro from https://github.com/microsoft/TypeScript/issues/54177#issuecomment-1538436654 +function conversionTest(groupName: | "downcast" | "dataDowncast" | "editingDowncast" | `${string & {}}Downcast`) {} +conversionTest("testDowncast"); +function conversionTest2(groupName: | "downcast" | "dataDowncast" | "editingDowncast" | `${{} & string}Downcast`) {} +conversionTest2("testDowncast"); + +function foo(str: `${`a${string}` & `${string}a`}Test`) {} +foo("abaTest"); // ok +foo("abcTest"); // error \ No newline at end of file diff --git a/tests/cases/fourslash/stringLiteralCompletionsForOpenEndedTemplateLiteralType.ts b/tests/cases/fourslash/stringLiteralCompletionsForOpenEndedTemplateLiteralType.ts new file mode 100644 index 0000000000000..92c02a23a2854 --- /dev/null +++ b/tests/cases/fourslash/stringLiteralCompletionsForOpenEndedTemplateLiteralType.ts @@ -0,0 +1,6 @@ +/// + +//// function conversionTest(groupName: | "downcast" | "dataDowncast" | "editingDowncast" | `${string & {}}Downcast`) {} +//// conversionTest("/**/"); + +verify.completions({ marker: "", exact: ["downcast", "dataDowncast", "editingDowncast"] });