Skip to content

Commit 7fa9179

Browse files
committed
Fix support for intersections in template literal placeholder types
1 parent 32b618c commit 7fa9179

File tree

2 files changed

+31
-27
lines changed

2 files changed

+31
-27
lines changed

src/compiler/checker.ts

+28-22
Original file line numberDiff line numberDiff line change
@@ -16925,10 +16925,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1692516925
}
1692616926

1692716927
function removeStringLiteralsMatchedByTemplateLiterals(types: Type[]) {
16928-
const templates = filter(types, t =>
16929-
!!(t.flags & TypeFlags.TemplateLiteral) &&
16930-
isPatternLiteralType(t) &&
16931-
(t as TemplateLiteralType).types.every(t => !(t.flags & TypeFlags.Intersection) || !areIntersectedTypesAvoidingPrimitiveReduction((t as IntersectionType).types))) as TemplateLiteralType[];
16928+
const templates = filter(types, t => !!(t.flags & TypeFlags.TemplateLiteral) && isPatternLiteralType(t)) as TemplateLiteralType[];
1693216929
if (templates.length) {
1693316930
let i = types.length;
1693416931
while (i > 0) {
@@ -17439,20 +17436,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1743917436
return reduceLeft(types, (n, t) => n + getConstituentCount(t), 0);
1744017437
}
1744117438

17442-
function areIntersectedTypesAvoidingPrimitiveReduction(types: Type[], primitiveFlags = TypeFlags.String | TypeFlags.Number | TypeFlags.BigInt): boolean {
17443-
if (types.length !== 2) {
17444-
return false;
17445-
}
17446-
const [t1, t2] = types;
17447-
return !!(t1.flags & primitiveFlags) && t2 === emptyTypeLiteralType || !!(t2.flags & primitiveFlags) && t1 === emptyTypeLiteralType;
17448-
}
17449-
1745017439
function getTypeFromIntersectionTypeNode(node: IntersectionTypeNode): Type {
1745117440
const links = getNodeLinks(node);
1745217441
if (!links.resolvedType) {
1745317442
const aliasSymbol = getAliasSymbolForTypeNode(node);
1745417443
const types = map(node.types, getTypeFromTypeNode);
17455-
const noSupertypeReduction = areIntersectedTypesAvoidingPrimitiveReduction(types);
17444+
// We perform no supertype reduction for X & {} or {} & X, where X is one of string, number, bigint,
17445+
// or a pattern literal template type. This enables union types like "a" | "b" | string & {} or
17446+
// "aa" | "ab" | `a${string}` which preserve the literal types for purposes of statement completion.
17447+
const emptyIndex = types.length === 2 ? types.indexOf(emptyTypeLiteralType) : -1;
17448+
const t = emptyIndex >= 0 ? types[1 - emptyIndex] : unknownType;
17449+
const noSupertypeReduction = !!(t.flags & (TypeFlags.String | TypeFlags.Number | TypeFlags.BigInt) || t.flags & TypeFlags.TemplateLiteral && isPatternLiteralType(t));
1745617450
links.resolvedType = getIntersectionType(types, aliasSymbol, getTypeArgumentsForAliasSymbol(aliasSymbol), noSupertypeReduction);
1745717451
}
1745817452
return links.resolvedType;
@@ -17735,7 +17729,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1773517729

1773617730
function createTemplateLiteralType(texts: readonly string[], types: readonly Type[]) {
1773717731
const type = createType(TypeFlags.TemplateLiteral) as TemplateLiteralType;
17738-
type.objectFlags = getPropagatingFlagsOfTypes(types, /*excludeKinds*/ TypeFlags.Nullable);
1773917732
type.texts = texts;
1774017733
type.types = types;
1774117734
return type;
@@ -18060,12 +18053,25 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1806018053

1806118054
function isPatternLiteralPlaceholderType(type: Type): boolean {
1806218055
if (type.flags & TypeFlags.Intersection) {
18063-
return !isGenericType(type) && some((type as IntersectionType).types, t => !!(t.flags & (TypeFlags.Literal | TypeFlags.Nullable)) || isPatternLiteralPlaceholderType(t));
18056+
// Return true if the intersection consists of one or more placeholders and zero or
18057+
// more object type tags.
18058+
let seenPlaceholder = false;
18059+
for (const t of (type as IntersectionType).types) {
18060+
if (t.flags & (TypeFlags.Literal | TypeFlags.Nullable) || isPatternLiteralPlaceholderType(t)) {
18061+
seenPlaceholder = true;
18062+
}
18063+
else if (!(t.flags & TypeFlags.Object)) {
18064+
return false;
18065+
}
18066+
}
18067+
return seenPlaceholder;
1806418068
}
1806518069
return !!(type.flags & (TypeFlags.Any | TypeFlags.String | TypeFlags.Number | TypeFlags.BigInt)) || isPatternLiteralType(type);
1806618070
}
1806718071

1806818072
function isPatternLiteralType(type: Type) {
18073+
// A pattern literal type is a template literal or a string mapping type that contains only
18074+
// non-generic pattern literal placeholders.
1806918075
return !!(type.flags & TypeFlags.TemplateLiteral) && every((type as TemplateLiteralType).types, isPatternLiteralPlaceholderType) ||
1807018076
!!(type.flags & TypeFlags.StringMapping) && isPatternLiteralPlaceholderType((type as StringMappingType).type);
1807118077
}
@@ -18083,12 +18089,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1808318089
}
1808418090

1808518091
function getGenericObjectFlags(type: Type): ObjectFlags {
18086-
if (type.flags & (TypeFlags.UnionOrIntersection | TypeFlags.TemplateLiteral)) {
18087-
if (!((type as UnionOrIntersectionType | TemplateLiteralType).objectFlags & ObjectFlags.IsGenericTypeComputed)) {
18088-
(type as UnionOrIntersectionType | TemplateLiteralType).objectFlags |= ObjectFlags.IsGenericTypeComputed |
18089-
reduceLeft((type as UnionOrIntersectionType | TemplateLiteralType).types, (flags, t) => flags | getGenericObjectFlags(t), 0);
18092+
if (type.flags & (TypeFlags.UnionOrIntersection)) {
18093+
if (!((type as UnionOrIntersectionType).objectFlags & ObjectFlags.IsGenericTypeComputed)) {
18094+
(type as UnionOrIntersectionType).objectFlags |= ObjectFlags.IsGenericTypeComputed |
18095+
reduceLeft((type as UnionOrIntersectionType).types, (flags, t) => flags | getGenericObjectFlags(t), 0);
1809018096
}
18091-
return (type as UnionOrIntersectionType | TemplateLiteralType).objectFlags & ObjectFlags.IsGenericType;
18097+
return (type as UnionOrIntersectionType).objectFlags & ObjectFlags.IsGenericType;
1809218098
}
1809318099
if (type.flags & TypeFlags.Substitution) {
1809418100
if (!((type as SubstitutionType).objectFlags & ObjectFlags.IsGenericTypeComputed)) {
@@ -18098,7 +18104,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1809818104
return (type as SubstitutionType).objectFlags & ObjectFlags.IsGenericType;
1809918105
}
1810018106
return (type.flags & TypeFlags.InstantiableNonPrimitive || isGenericMappedType(type) || isGenericTupleType(type) ? ObjectFlags.IsGenericObjectType : 0) |
18101-
(type.flags & (TypeFlags.InstantiableNonPrimitive | TypeFlags.Index | TypeFlags.StringMapping) && !isPatternLiteralType(type) ? ObjectFlags.IsGenericIndexType : 0);
18107+
(type.flags & (TypeFlags.InstantiableNonPrimitive | TypeFlags.Index | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) && !isPatternLiteralType(type) ? ObjectFlags.IsGenericIndexType : 0);
1810218108
}
1810318109

1810418110
function getSimplifiedType(type: Type, writing: boolean): Type {
@@ -24768,7 +24774,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2476824774
objectFlags & ObjectFlags.Anonymous && type.symbol && type.symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral) && type.symbol.declarations ||
2476924775
objectFlags & (ObjectFlags.Mapped | ObjectFlags.ReverseMapped | ObjectFlags.ObjectRestType | ObjectFlags.InstantiationExpressionType)
2477024776
) ||
24771-
type.flags & (TypeFlags.UnionOrIntersection | TypeFlags.TemplateLiteral) && !(type.flags & TypeFlags.EnumLiteral) && !isNonGenericTopLevelType(type) && some((type as UnionOrIntersectionType | TemplateLiteralType).types, couldContainTypeVariables));
24777+
type.flags & TypeFlags.UnionOrIntersection && !(type.flags & TypeFlags.EnumLiteral) && !isNonGenericTopLevelType(type) && some((type as UnionOrIntersectionType).types, couldContainTypeVariables));
2477224778
if (type.flags & TypeFlags.ObjectFlagsType) {
2477324779
(type as ObjectFlagsType).objectFlags |= ObjectFlags.CouldContainTypeVariablesComputed | (result ? ObjectFlags.CouldContainTypeVariables : 0);
2477424780
}

src/compiler/types.ts

+3-5
Original file line numberDiff line numberDiff line change
@@ -6130,7 +6130,7 @@ export const enum TypeFlags {
61306130
Instantiable = InstantiableNonPrimitive | InstantiablePrimitive,
61316131
StructuredOrInstantiable = StructuredType | Instantiable,
61326132
/** @internal */
6133-
ObjectFlagsType = Any | Nullable | Never | Object | Union | Intersection | TemplateLiteral,
6133+
ObjectFlagsType = Any | Nullable | Never | Object | Union | Intersection,
61346134
/** @internal */
61356135
Simplifiable = IndexedAccess | Conditional,
61366136
/** @internal */
@@ -6289,7 +6289,7 @@ export const enum ObjectFlags {
62896289
/** @internal */
62906290
IdenticalBaseTypeExists = 1 << 26, // has a defined cachedEquivalentBaseType member
62916291

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

63186318
/** @internal */
6319-
export type ObjectFlagsType = NullableType | ObjectType | UnionType | IntersectionType | TemplateLiteralType;
6319+
export type ObjectFlagsType = NullableType | ObjectType | UnionType | IntersectionType;
63206320

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

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

0 commit comments

Comments
 (0)