Skip to content

Commit c020b4a

Browse files
ahejlsbergtypescript-bot
authored andcommitted
Cherry-pick PR microsoft#56434 into release-5.3
Component commits: 7fa9179 Fix support for intersections in template literal placeholder types f45b6dd Accept new baselines 2cf8379 Update test ca7e9c4 Accept new baselines 8ab01eb Update fourslash test b464619 Add more tests per code review
1 parent b4fe221 commit c020b4a

8 files changed

+101
-49
lines changed

src/compiler/checker.ts

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16893,10 +16893,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1689316893
}
1689416894

1689516895
function removeStringLiteralsMatchedByTemplateLiterals(types: Type[]) {
16896-
const templates = filter(types, t =>
16897-
!!(t.flags & TypeFlags.TemplateLiteral) &&
16898-
isPatternLiteralType(t) &&
16899-
(t as TemplateLiteralType).types.every(t => !(t.flags & TypeFlags.Intersection) || !areIntersectedTypesAvoidingPrimitiveReduction((t as IntersectionType).types))) as TemplateLiteralType[];
16896+
const templates = filter(types, t => !!(t.flags & TypeFlags.TemplateLiteral) && isPatternLiteralType(t)) as TemplateLiteralType[];
1690016897
if (templates.length) {
1690116898
let i = types.length;
1690216899
while (i > 0) {
@@ -17407,20 +17404,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1740717404
return reduceLeft(types, (n, t) => n + getConstituentCount(t), 0);
1740817405
}
1740917406

17410-
function areIntersectedTypesAvoidingPrimitiveReduction(types: Type[], primitiveFlags = TypeFlags.String | TypeFlags.Number | TypeFlags.BigInt): boolean {
17411-
if (types.length !== 2) {
17412-
return false;
17413-
}
17414-
const [t1, t2] = types;
17415-
return !!(t1.flags & primitiveFlags) && t2 === emptyTypeLiteralType || !!(t2.flags & primitiveFlags) && t1 === emptyTypeLiteralType;
17416-
}
17417-
1741817407
function getTypeFromIntersectionTypeNode(node: IntersectionTypeNode): Type {
1741917408
const links = getNodeLinks(node);
1742017409
if (!links.resolvedType) {
1742117410
const aliasSymbol = getAliasSymbolForTypeNode(node);
1742217411
const types = map(node.types, getTypeFromTypeNode);
17423-
const noSupertypeReduction = areIntersectedTypesAvoidingPrimitiveReduction(types);
17412+
// We perform no supertype reduction for X & {} or {} & X, where X is one of string, number, bigint,
17413+
// or a pattern literal template type. This enables union types like "a" | "b" | string & {} or
17414+
// "aa" | "ab" | `a${string}` which preserve the literal types for purposes of statement completion.
17415+
const emptyIndex = types.length === 2 ? types.indexOf(emptyTypeLiteralType) : -1;
17416+
const t = emptyIndex >= 0 ? types[1 - emptyIndex] : unknownType;
17417+
const noSupertypeReduction = !!(t.flags & (TypeFlags.String | TypeFlags.Number | TypeFlags.BigInt) || t.flags & TypeFlags.TemplateLiteral && isPatternLiteralType(t));
1742417418
links.resolvedType = getIntersectionType(types, aliasSymbol, getTypeArgumentsForAliasSymbol(aliasSymbol), noSupertypeReduction);
1742517419
}
1742617420
return links.resolvedType;
@@ -17703,7 +17697,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1770317697

1770417698
function createTemplateLiteralType(texts: readonly string[], types: readonly Type[]) {
1770517699
const type = createType(TypeFlags.TemplateLiteral) as TemplateLiteralType;
17706-
type.objectFlags = getPropagatingFlagsOfTypes(types, /*excludeKinds*/ TypeFlags.Nullable);
1770717700
type.texts = texts;
1770817701
type.types = types;
1770917702
return type;
@@ -18028,12 +18021,25 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1802818021

1802918022
function isPatternLiteralPlaceholderType(type: Type): boolean {
1803018023
if (type.flags & TypeFlags.Intersection) {
18031-
return !isGenericType(type) && some((type as IntersectionType).types, t => !!(t.flags & (TypeFlags.Literal | TypeFlags.Nullable)) || isPatternLiteralPlaceholderType(t));
18024+
// Return true if the intersection consists of one or more placeholders and zero or
18025+
// more object type tags.
18026+
let seenPlaceholder = false;
18027+
for (const t of (type as IntersectionType).types) {
18028+
if (t.flags & (TypeFlags.Literal | TypeFlags.Nullable) || isPatternLiteralPlaceholderType(t)) {
18029+
seenPlaceholder = true;
18030+
}
18031+
else if (!(t.flags & TypeFlags.Object)) {
18032+
return false;
18033+
}
18034+
}
18035+
return seenPlaceholder;
1803218036
}
1803318037
return !!(type.flags & (TypeFlags.Any | TypeFlags.String | TypeFlags.Number | TypeFlags.BigInt)) || isPatternLiteralType(type);
1803418038
}
1803518039

1803618040
function isPatternLiteralType(type: Type) {
18041+
// A pattern literal type is a template literal or a string mapping type that contains only
18042+
// non-generic pattern literal placeholders.
1803718043
return !!(type.flags & TypeFlags.TemplateLiteral) && every((type as TemplateLiteralType).types, isPatternLiteralPlaceholderType) ||
1803818044
!!(type.flags & TypeFlags.StringMapping) && isPatternLiteralPlaceholderType((type as StringMappingType).type);
1803918045
}
@@ -18051,12 +18057,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1805118057
}
1805218058

1805318059
function getGenericObjectFlags(type: Type): ObjectFlags {
18054-
if (type.flags & (TypeFlags.UnionOrIntersection | TypeFlags.TemplateLiteral)) {
18055-
if (!((type as UnionOrIntersectionType | TemplateLiteralType).objectFlags & ObjectFlags.IsGenericTypeComputed)) {
18056-
(type as UnionOrIntersectionType | TemplateLiteralType).objectFlags |= ObjectFlags.IsGenericTypeComputed |
18057-
reduceLeft((type as UnionOrIntersectionType | TemplateLiteralType).types, (flags, t) => flags | getGenericObjectFlags(t), 0);
18060+
if (type.flags & (TypeFlags.UnionOrIntersection)) {
18061+
if (!((type as UnionOrIntersectionType).objectFlags & ObjectFlags.IsGenericTypeComputed)) {
18062+
(type as UnionOrIntersectionType).objectFlags |= ObjectFlags.IsGenericTypeComputed |
18063+
reduceLeft((type as UnionOrIntersectionType).types, (flags, t) => flags | getGenericObjectFlags(t), 0);
1805818064
}
18059-
return (type as UnionOrIntersectionType | TemplateLiteralType).objectFlags & ObjectFlags.IsGenericType;
18065+
return (type as UnionOrIntersectionType).objectFlags & ObjectFlags.IsGenericType;
1806018066
}
1806118067
if (type.flags & TypeFlags.Substitution) {
1806218068
if (!((type as SubstitutionType).objectFlags & ObjectFlags.IsGenericTypeComputed)) {
@@ -18066,7 +18072,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1806618072
return (type as SubstitutionType).objectFlags & ObjectFlags.IsGenericType;
1806718073
}
1806818074
return (type.flags & TypeFlags.InstantiableNonPrimitive || isGenericMappedType(type) || isGenericTupleType(type) ? ObjectFlags.IsGenericObjectType : 0) |
18069-
(type.flags & (TypeFlags.InstantiableNonPrimitive | TypeFlags.Index | TypeFlags.StringMapping) && !isPatternLiteralType(type) ? ObjectFlags.IsGenericIndexType : 0);
18075+
(type.flags & (TypeFlags.InstantiableNonPrimitive | TypeFlags.Index | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) && !isPatternLiteralType(type) ? ObjectFlags.IsGenericIndexType : 0);
1807018076
}
1807118077

1807218078
function getSimplifiedType(type: Type, writing: boolean): Type {
@@ -24734,7 +24740,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2473424740
objectFlags & ObjectFlags.Anonymous && type.symbol && type.symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral) && type.symbol.declarations ||
2473524741
objectFlags & (ObjectFlags.Mapped | ObjectFlags.ReverseMapped | ObjectFlags.ObjectRestType | ObjectFlags.InstantiationExpressionType)
2473624742
) ||
24737-
type.flags & (TypeFlags.UnionOrIntersection | TypeFlags.TemplateLiteral) && !(type.flags & TypeFlags.EnumLiteral) && !isNonGenericTopLevelType(type) && some((type as UnionOrIntersectionType | TemplateLiteralType).types, couldContainTypeVariables));
24743+
type.flags & TypeFlags.UnionOrIntersection && !(type.flags & TypeFlags.EnumLiteral) && !isNonGenericTopLevelType(type) && some((type as UnionOrIntersectionType).types, couldContainTypeVariables));
2473824744
if (type.flags & TypeFlags.ObjectFlagsType) {
2473924745
(type as ObjectFlagsType).objectFlags |= ObjectFlags.CouldContainTypeVariablesComputed | (result ? ObjectFlags.CouldContainTypeVariables : 0);
2474024746
}

src/compiler/types.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6129,7 +6129,7 @@ export const enum TypeFlags {
61296129
Instantiable = InstantiableNonPrimitive | InstantiablePrimitive,
61306130
StructuredOrInstantiable = StructuredType | Instantiable,
61316131
/** @internal */
6132-
ObjectFlagsType = Any | Nullable | Never | Object | Union | Intersection | TemplateLiteral,
6132+
ObjectFlagsType = Any | Nullable | Never | Object | Union | Intersection,
61336133
/** @internal */
61346134
Simplifiable = IndexedAccess | Conditional,
61356135
/** @internal */
@@ -6288,7 +6288,7 @@ export const enum ObjectFlags {
62886288
/** @internal */
62896289
IdenticalBaseTypeExists = 1 << 26, // has a defined cachedEquivalentBaseType member
62906290

6291-
// Flags that require TypeFlags.UnionOrIntersection, TypeFlags.Substitution, or TypeFlags.TemplateLiteral
6291+
// Flags that require TypeFlags.UnionOrIntersection or TypeFlags.Substitution
62926292
/** @internal */
62936293
IsGenericTypeComputed = 1 << 21, // IsGenericObjectType flag has been computed
62946294
/** @internal */
@@ -6315,7 +6315,7 @@ export const enum ObjectFlags {
63156315
}
63166316

63176317
/** @internal */
6318-
export type ObjectFlagsType = NullableType | ObjectType | UnionType | IntersectionType | TemplateLiteralType;
6318+
export type ObjectFlagsType = NullableType | ObjectType | UnionType | IntersectionType;
63196319

63206320
// Object types (TypeFlags.ObjectType)
63216321
// dprint-ignore
@@ -6674,8 +6674,6 @@ export interface ConditionalType extends InstantiableType {
66746674
}
66756675

66766676
export interface TemplateLiteralType extends InstantiableType {
6677-
/** @internal */
6678-
objectFlags: ObjectFlags;
66796677
texts: readonly string[]; // Always one element longer than types
66806678
types: readonly Type[]; // Always at least one element
66816679
}

tests/baselines/reference/templateLiteralTypesPatterns.errors.txt

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ 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`'.
58+
templateLiteralTypesPatterns.ts(215,5): error TS2345: Argument of type '"abcTest"' is not assignable to parameter of type '`${`a${string}` & `${string}a`}Test`'.
5959

6060

6161
==== templateLiteralTypesPatterns.ts (58 errors) ====
@@ -376,10 +376,14 @@ templateLiteralTypesPatterns.ts(211,5): error TS2345: Argument of type '"abcTest
376376
}
377377

378378
// repro from https://github.com/microsoft/TypeScript/issues/54177#issuecomment-1538436654
379-
function conversionTest(groupName: | "downcast" | "dataDowncast" | "editingDowncast" | `${string & {}}Downcast`) {}
379+
function conversionTest(groupName: | "downcast" | "dataDowncast" | "editingDowncast" | `${string}Downcast` & {}) {}
380380
conversionTest("testDowncast");
381-
function conversionTest2(groupName: | "downcast" | "dataDowncast" | "editingDowncast" | `${{} & string}Downcast`) {}
381+
function conversionTest2(groupName: | "downcast" | "dataDowncast" | "editingDowncast" | {} & `${string}Downcast`) {}
382382
conversionTest2("testDowncast");
383+
function conversionTest3(groupName: | "downcast" | "dataDowncast" | "editingDowncast" | `${string & {}}Downcast`) {}
384+
conversionTest3("testDowncast");
385+
function conversionTest4(groupName: | "downcast" | "dataDowncast" | "editingDowncast" | `${{} & string}Downcast`) {}
386+
conversionTest4("testDowncast");
383387

384388
function foo(str: `${`a${string}` & `${string}a`}Test`) {}
385389
foo("abaTest"); // ok

tests/baselines/reference/templateLiteralTypesPatterns.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -204,10 +204,14 @@ export abstract class BB {
204204
}
205205

206206
// repro from https://github.com/microsoft/TypeScript/issues/54177#issuecomment-1538436654
207-
function conversionTest(groupName: | "downcast" | "dataDowncast" | "editingDowncast" | `${string & {}}Downcast`) {}
207+
function conversionTest(groupName: | "downcast" | "dataDowncast" | "editingDowncast" | `${string}Downcast` & {}) {}
208208
conversionTest("testDowncast");
209-
function conversionTest2(groupName: | "downcast" | "dataDowncast" | "editingDowncast" | `${{} & string}Downcast`) {}
209+
function conversionTest2(groupName: | "downcast" | "dataDowncast" | "editingDowncast" | {} & `${string}Downcast`) {}
210210
conversionTest2("testDowncast");
211+
function conversionTest3(groupName: | "downcast" | "dataDowncast" | "editingDowncast" | `${string & {}}Downcast`) {}
212+
conversionTest3("testDowncast");
213+
function conversionTest4(groupName: | "downcast" | "dataDowncast" | "editingDowncast" | `${{} & string}Downcast`) {}
214+
conversionTest4("testDowncast");
211215

212216
function foo(str: `${`a${string}` & `${string}a`}Test`) {}
213217
foo("abaTest"); // ok
@@ -367,6 +371,10 @@ function conversionTest(groupName) { }
367371
conversionTest("testDowncast");
368372
function conversionTest2(groupName) { }
369373
conversionTest2("testDowncast");
374+
function conversionTest3(groupName) { }
375+
conversionTest3("testDowncast");
376+
function conversionTest4(groupName) { }
377+
conversionTest4("testDowncast");
370378
function foo(str) { }
371379
foo("abaTest"); // ok
372380
foo("abcTest"); // error

tests/baselines/reference/templateLiteralTypesPatterns.symbols

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -488,27 +488,41 @@ export abstract class BB {
488488
}
489489

490490
// repro from https://github.com/microsoft/TypeScript/issues/54177#issuecomment-1538436654
491-
function conversionTest(groupName: | "downcast" | "dataDowncast" | "editingDowncast" | `${string & {}}Downcast`) {}
491+
function conversionTest(groupName: | "downcast" | "dataDowncast" | "editingDowncast" | `${string}Downcast` & {}) {}
492492
>conversionTest : Symbol(conversionTest, Decl(templateLiteralTypesPatterns.ts, 200, 1))
493493
>groupName : Symbol(groupName, Decl(templateLiteralTypesPatterns.ts, 203, 24))
494494

495495
conversionTest("testDowncast");
496496
>conversionTest : Symbol(conversionTest, Decl(templateLiteralTypesPatterns.ts, 200, 1))
497497

498-
function conversionTest2(groupName: | "downcast" | "dataDowncast" | "editingDowncast" | `${{} & string}Downcast`) {}
498+
function conversionTest2(groupName: | "downcast" | "dataDowncast" | "editingDowncast" | {} & `${string}Downcast`) {}
499499
>conversionTest2 : Symbol(conversionTest2, Decl(templateLiteralTypesPatterns.ts, 204, 31))
500500
>groupName : Symbol(groupName, Decl(templateLiteralTypesPatterns.ts, 205, 25))
501501

502502
conversionTest2("testDowncast");
503503
>conversionTest2 : Symbol(conversionTest2, Decl(templateLiteralTypesPatterns.ts, 204, 31))
504504

505+
function conversionTest3(groupName: | "downcast" | "dataDowncast" | "editingDowncast" | `${string & {}}Downcast`) {}
506+
>conversionTest3 : Symbol(conversionTest3, Decl(templateLiteralTypesPatterns.ts, 206, 32))
507+
>groupName : Symbol(groupName, Decl(templateLiteralTypesPatterns.ts, 207, 25))
508+
509+
conversionTest3("testDowncast");
510+
>conversionTest3 : Symbol(conversionTest3, Decl(templateLiteralTypesPatterns.ts, 206, 32))
511+
512+
function conversionTest4(groupName: | "downcast" | "dataDowncast" | "editingDowncast" | `${{} & string}Downcast`) {}
513+
>conversionTest4 : Symbol(conversionTest4, Decl(templateLiteralTypesPatterns.ts, 208, 32))
514+
>groupName : Symbol(groupName, Decl(templateLiteralTypesPatterns.ts, 209, 25))
515+
516+
conversionTest4("testDowncast");
517+
>conversionTest4 : Symbol(conversionTest4, Decl(templateLiteralTypesPatterns.ts, 208, 32))
518+
505519
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))
520+
>foo : Symbol(foo, Decl(templateLiteralTypesPatterns.ts, 210, 32))
521+
>str : Symbol(str, Decl(templateLiteralTypesPatterns.ts, 212, 13))
508522

509523
foo("abaTest"); // ok
510-
>foo : Symbol(foo, Decl(templateLiteralTypesPatterns.ts, 206, 32))
524+
>foo : Symbol(foo, Decl(templateLiteralTypesPatterns.ts, 210, 32))
511525

512526
foo("abcTest"); // error
513-
>foo : Symbol(foo, Decl(templateLiteralTypesPatterns.ts, 206, 32))
527+
>foo : Symbol(foo, Decl(templateLiteralTypesPatterns.ts, 210, 32))
514528

tests/baselines/reference/templateLiteralTypesPatterns.types

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -636,22 +636,40 @@ export abstract class BB {
636636
}
637637

638638
// 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"
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"
642642

643643
conversionTest("testDowncast");
644644
>conversionTest("testDowncast") : void
645-
>conversionTest : (groupName: `${string & {}}Downcast` | "downcast" | "dataDowncast" | "editingDowncast") => void
645+
>conversionTest : (groupName: (`${string}Downcast` & {}) | "downcast" | "dataDowncast" | "editingDowncast") => void
646646
>"testDowncast" : "testDowncast"
647647

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`
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`)
651651

652652
conversionTest2("testDowncast");
653653
>conversionTest2("testDowncast") : void
654-
>conversionTest2 : (groupName: "downcast" | "dataDowncast" | "editingDowncast" | `${{} & string}Downcast`) => void
654+
>conversionTest2 : (groupName: "downcast" | "dataDowncast" | "editingDowncast" | ({} & `${string}Downcast`)) => void
655+
>"testDowncast" : "testDowncast"
656+
657+
function conversionTest3(groupName: | "downcast" | "dataDowncast" | "editingDowncast" | `${string & {}}Downcast`) {}
658+
>conversionTest3 : (groupName: "downcast" | "dataDowncast" | "editingDowncast" | `${string & {}}Downcast`) => void
659+
>groupName : "downcast" | `${string & {}}Downcast`
660+
661+
conversionTest3("testDowncast");
662+
>conversionTest3("testDowncast") : void
663+
>conversionTest3 : (groupName: "downcast" | `${string & {}}Downcast`) => void
664+
>"testDowncast" : "testDowncast"
665+
666+
function conversionTest4(groupName: | "downcast" | "dataDowncast" | "editingDowncast" | `${{} & string}Downcast`) {}
667+
>conversionTest4 : (groupName: "downcast" | "dataDowncast" | "editingDowncast" | `${{} & string}Downcast`) => void
668+
>groupName : "downcast" | `${{} & string}Downcast`
669+
670+
conversionTest4("testDowncast");
671+
>conversionTest4("testDowncast") : void
672+
>conversionTest4 : (groupName: "downcast" | `${{} & string}Downcast`) => void
655673
>"testDowncast" : "testDowncast"
656674

657675
function foo(str: `${`a${string}` & `${string}a`}Test`) {}

0 commit comments

Comments
 (0)