Allow intersections to be used as valid types for template literal placeholders (#54188)

This commit is contained in:
Mateusz Burzyński 2023-08-10 21:50:21 +02:00 committed by GitHub
parent cd391b066b
commit 28cd1fbd13
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 123 additions and 8 deletions

View File

@ -16457,7 +16457,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) {
@ -16966,8 +16970,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 {
@ -16975,7 +16983,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;
@ -24362,12 +24370,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;

View File

@ -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!);
}
}
// 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`'.

View File

@ -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

View File

@ -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))

View File

@ -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"

View File

@ -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

View File

@ -0,0 +1,6 @@
/// <reference path="fourslash.ts" />
//// function conversionTest(groupName: | "downcast" | "dataDowncast" | "editingDowncast" | `${string & {}}Downcast`) {}
//// conversionTest("/**/");
verify.completions({ marker: "", exact: ["downcast", "dataDowncast", "editingDowncast"] });