Skip to content

Commit

Permalink
Allow intersections to be used as valid types for template literal pl…
Browse files Browse the repository at this point in the history
…aceholders
  • Loading branch information
Andarist committed May 8, 2023
1 parent 04d4580 commit 2877ff5
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 7 deletions.
16 changes: 12 additions & 4 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16918,16 +16918,20 @@ 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 {
const links = getNodeLinks(node);
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;
Expand Down Expand Up @@ -24270,12 +24274,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;
Expand Down
16 changes: 14 additions & 2 deletions tests/baselines/reference/templateLiteralTypesPatterns.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,10 @@ tests/cases/conformance/types/literal/templateLiteralTypesPatterns.ts(129,9): er
tests/cases/conformance/types/literal/templateLiteralTypesPatterns.ts(140,1): error TS2322: Type '`a${string}`' is not assignable to type '`a${number}`'.
tests/cases/conformance/types/literal/templateLiteralTypesPatterns.ts(141,1): error TS2322: Type '"bno"' is not assignable to type '`a${any}`'.
tests/cases/conformance/types/literal/templateLiteralTypesPatterns.ts(160,7): error TS2322: Type '"anything"' is not assignable to type '`${number} ${number}`'.
tests/cases/conformance/types/literal/templateLiteralTypesPatterns.ts(211,5): error TS2345: Argument of type '"abcTest"' is not assignable to parameter of type '`${`a${string}` & `${string}a`}Test`'.


==== tests/cases/conformance/types/literal/templateLiteralTypesPatterns.ts (57 errors) ====
==== tests/cases/conformance/types/literal/templateLiteralTypesPatterns.ts (58 errors) ====
type RequiresLeadingSlash = `/${string}`;

// ok
Expand Down Expand Up @@ -373,4 +374,15 @@ tests/cases/conformance/types/literal/templateLiteralTypesPatterns.ts(160,7): er
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`'.
19 changes: 18 additions & 1 deletion tests/baselines/reference/templateLiteralTypesPatterns.js
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,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";
Expand Down Expand Up @@ -351,3 +360,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
25 changes: 25 additions & 0 deletions tests/baselines/reference/templateLiteralTypesPatterns.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -485,3 +485,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))

33 changes: 33 additions & 0 deletions tests/baselines/reference/templateLiteralTypesPatterns.types
Original file line number Diff line number Diff line change
Expand Up @@ -633,3 +633,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"

conversionTest("testDowncast");
>conversionTest("testDowncast") : void
>conversionTest : (groupName: `${string & {}}Downcast` | "downcast") => void
>"testDowncast" : "testDowncast"

function conversionTest2(groupName: | "downcast" | "dataDowncast" | "editingDowncast" | `${{} & string}Downcast`) {}
>conversionTest2 : (groupName: "downcast" | "dataDowncast" | "editingDowncast" | `${{} & string}Downcast`) => void
>groupName : "downcast" | `${{} & string}Downcast`

conversionTest2("testDowncast");
>conversionTest2("testDowncast") : void
>conversionTest2 : (groupName: "downcast" | `${{} & 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"

Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit 2877ff5

Please sign in to comment.