Skip to content

Commit ba3d2b0

Browse files
TypeScript Botahejlsberg
TypeScript Bot
andauthored
🤖 Pick PR #56434 (Fix support for intersections in te...) into release-5.3 (#56491)
Co-authored-by: Anders Hejlsberg <[email protected]>
1 parent f3808c4 commit ba3d2b0

8 files changed

+101
-49
lines changed

Diff for: ‎src/compiler/checker.ts

+28-22
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
}

Diff for: ‎src/compiler/types.ts

+3-5
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
}

Diff for: ‎tests/baselines/reference/templateLiteralTypesPatterns.errors.txt

+7-3
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

Diff for: ‎tests/baselines/reference/templateLiteralTypesPatterns.js

+10-2
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

Diff for: ‎tests/baselines/reference/templateLiteralTypesPatterns.symbols

+20-6
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

Diff for: ‎tests/baselines/reference/templateLiteralTypesPatterns.types

+26-8
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)