Skip to content

Commit 716b592

Browse files
authored
Revert fix for intersections in template literals, fix differently (microsoft#52836)
1 parent b70784e commit 716b592

12 files changed

+359
-27
lines changed

src/compiler/checker.ts

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17070,32 +17070,24 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1707017070
}
1707117071
return type;
1707217072

17073-
function addSpans(texts: readonly string[] | string, types: readonly Type[]): boolean {
17074-
const isTextsArray = isArray(texts);
17073+
function addSpans(texts: readonly string[], types: readonly Type[]): boolean {
1707517074
for (let i = 0; i < types.length; i++) {
1707617075
const t = types[i];
17077-
const addText = isTextsArray ? texts[i + 1] : texts;
1707817076
if (t.flags & (TypeFlags.Literal | TypeFlags.Null | TypeFlags.Undefined)) {
1707917077
text += getTemplateStringForType(t) || "";
17080-
text += addText;
17081-
if (!isTextsArray) return true;
17078+
text += texts[i + 1];
1708217079
}
1708317080
else if (t.flags & TypeFlags.TemplateLiteral) {
1708417081
text += (t as TemplateLiteralType).texts[0];
1708517082
if (!addSpans((t as TemplateLiteralType).texts, (t as TemplateLiteralType).types)) return false;
17086-
text += addText;
17087-
if (!isTextsArray) return true;
17083+
text += texts[i + 1];
1708817084
}
1708917085
else if (isGenericIndexType(t) || isPatternLiteralPlaceholderType(t)) {
1709017086
newTypes.push(t);
1709117087
newTexts.push(text);
17092-
text = addText;
17088+
text = texts[i + 1];
1709317089
}
17094-
else if (t.flags & TypeFlags.Intersection) {
17095-
const added = addSpans(texts[i + 1], (t as IntersectionType).types);
17096-
if (!added) return false;
17097-
}
17098-
else if (isTextsArray) {
17090+
else {
1709917091
return false;
1710017092
}
1710117093
}
@@ -17113,13 +17105,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1711317105

1711417106
function createTemplateLiteralType(texts: readonly string[], types: readonly Type[]) {
1711517107
const type = createType(TypeFlags.TemplateLiteral) as TemplateLiteralType;
17108+
type.objectFlags = getPropagatingFlagsOfTypes(types, /*excludeKinds*/ TypeFlags.Nullable);
1711617109
type.texts = texts;
1711717110
type.types = types;
1711817111
return type;
1711917112
}
1712017113

1712117114
function getStringMappingType(symbol: Symbol, type: Type): Type {
1712217115
return type.flags & (TypeFlags.Union | TypeFlags.Never) ? mapType(type, t => getStringMappingType(symbol, t)) :
17116+
type.flags & TypeFlags.Intersection ? getIntersectionType(map((type as IntersectionType).types, t => getStringMappingType(symbol, t))) :
1712317117
type.flags & TypeFlags.StringLiteral ? getStringLiteralType(applyStringMapping(symbol, (type as StringLiteralType).value)) :
1712417118
type.flags & TypeFlags.TemplateLiteral ? getTemplateLiteralType(...applyTemplateStringMapping(symbol, (type as TemplateLiteralType).texts, (type as TemplateLiteralType).types)) :
1712517119
// Mapping<Mapping<T>> === Mapping<T>
@@ -17423,6 +17417,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1742317417
}
1742417418

1742517419
function isPatternLiteralPlaceholderType(type: Type): boolean {
17420+
if (type.flags & TypeFlags.Intersection) {
17421+
return some((type as IntersectionType).types, t => !!(t.flags & (TypeFlags.Literal | TypeFlags.Null | TypeFlags.Undefined)) || isPatternLiteralPlaceholderType(t));
17422+
}
1742617423
return !!(type.flags & (TypeFlags.Any | TypeFlags.String | TypeFlags.Number | TypeFlags.BigInt)) || isPatternLiteralType(type);
1742717424
}
1742817425

@@ -17444,12 +17441,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1744417441
}
1744517442

1744617443
function getGenericObjectFlags(type: Type): ObjectFlags {
17447-
if (type.flags & TypeFlags.UnionOrIntersection) {
17448-
if (!((type as UnionOrIntersectionType).objectFlags & ObjectFlags.IsGenericTypeComputed)) {
17449-
(type as UnionOrIntersectionType).objectFlags |= ObjectFlags.IsGenericTypeComputed |
17450-
reduceLeft((type as UnionOrIntersectionType).types, (flags, t) => flags | getGenericObjectFlags(t), 0);
17444+
if (type.flags & (TypeFlags.UnionOrIntersection | TypeFlags.TemplateLiteral)) {
17445+
if (!((type as UnionOrIntersectionType | TemplateLiteralType).objectFlags & ObjectFlags.IsGenericTypeComputed)) {
17446+
(type as UnionOrIntersectionType | TemplateLiteralType).objectFlags |= ObjectFlags.IsGenericTypeComputed |
17447+
reduceLeft((type as UnionOrIntersectionType | TemplateLiteralType).types, (flags, t) => flags | getGenericObjectFlags(t), 0);
1745117448
}
17452-
return (type as UnionOrIntersectionType).objectFlags & ObjectFlags.IsGenericType;
17449+
return (type as UnionOrIntersectionType | TemplateLiteralType).objectFlags & ObjectFlags.IsGenericType;
1745317450
}
1745417451
if (type.flags & TypeFlags.Substitution) {
1745517452
if (!((type as SubstitutionType).objectFlags & ObjectFlags.IsGenericTypeComputed)) {
@@ -17459,7 +17456,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1745917456
return (type as SubstitutionType).objectFlags & ObjectFlags.IsGenericType;
1746017457
}
1746117458
return (type.flags & TypeFlags.InstantiableNonPrimitive || isGenericMappedType(type) || isGenericTupleType(type) ? ObjectFlags.IsGenericObjectType : 0) |
17462-
(type.flags & (TypeFlags.InstantiableNonPrimitive | TypeFlags.Index | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) && !isPatternLiteralType(type) ? ObjectFlags.IsGenericIndexType : 0);
17459+
(type.flags & (TypeFlags.InstantiableNonPrimitive | TypeFlags.Index | TypeFlags.StringMapping) && !isPatternLiteralType(type) ? ObjectFlags.IsGenericIndexType : 0);
1746317460
}
1746417461

1746517462
function getSimplifiedType(type: Type, writing: boolean): Type {
@@ -23896,7 +23893,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2389623893
objectFlags & ObjectFlags.Reference && ((type as TypeReference).node || some(getTypeArguments(type as TypeReference), couldContainTypeVariables)) ||
2389723894
objectFlags & ObjectFlags.Anonymous && type.symbol && type.symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral) && type.symbol.declarations ||
2389823895
objectFlags & (ObjectFlags.Mapped | ObjectFlags.ReverseMapped | ObjectFlags.ObjectRestType | ObjectFlags.InstantiationExpressionType)) ||
23899-
type.flags & TypeFlags.UnionOrIntersection && !(type.flags & TypeFlags.EnumLiteral) && !isNonGenericTopLevelType(type) && some((type as UnionOrIntersectionType).types, couldContainTypeVariables));
23896+
type.flags & (TypeFlags.UnionOrIntersection | TypeFlags.TemplateLiteral) && !(type.flags & TypeFlags.EnumLiteral) && !isNonGenericTopLevelType(type) && some((type as UnionOrIntersectionType | TemplateLiteralType).types, couldContainTypeVariables));
2390023897
if (type.flags & TypeFlags.ObjectFlagsType) {
2390123898
(type as ObjectFlagsType).objectFlags |= ObjectFlags.CouldContainTypeVariablesComputed | (result ? ObjectFlags.CouldContainTypeVariables : 0);
2390223899
}

src/compiler/types.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6107,7 +6107,7 @@ export const enum TypeFlags {
61076107
Instantiable = InstantiableNonPrimitive | InstantiablePrimitive,
61086108
StructuredOrInstantiable = StructuredType | Instantiable,
61096109
/** @internal */
6110-
ObjectFlagsType = Any | Nullable | Never | Object | Union | Intersection,
6110+
ObjectFlagsType = Any | Nullable | Never | Object | Union | Intersection | TemplateLiteral,
61116111
/** @internal */
61126112
Simplifiable = IndexedAccess | Conditional,
61136113
/** @internal */
@@ -6261,7 +6261,7 @@ export const enum ObjectFlags {
62616261
/** @internal */
62626262
IdenticalBaseTypeExists = 1 << 26, // has a defined cachedEquivalentBaseType member
62636263

6264-
// Flags that require TypeFlags.UnionOrIntersection or TypeFlags.Substitution
6264+
// Flags that require TypeFlags.UnionOrIntersection, TypeFlags.Substitution, or TypeFlags.TemplateLiteral
62656265
/** @internal */
62666266
IsGenericTypeComputed = 1 << 21, // IsGenericObjectType flag has been computed
62676267
/** @internal */
@@ -6288,7 +6288,7 @@ export const enum ObjectFlags {
62886288
}
62896289

62906290
/** @internal */
6291-
export type ObjectFlagsType = NullableType | ObjectType | UnionType | IntersectionType;
6291+
export type ObjectFlagsType = NullableType | ObjectType | UnionType | IntersectionType | TemplateLiteralType;
62926292

62936293
// Object types (TypeFlags.ObjectType)
62946294
export interface ObjectType extends Type {
@@ -6636,6 +6636,8 @@ export interface ConditionalType extends InstantiableType {
66366636
}
66376637

66386638
export interface TemplateLiteralType extends InstantiableType {
6639+
/** @internal */
6640+
objectFlags: ObjectFlags;
66396641
texts: readonly string[]; // Always one element longer than types
66406642
types: readonly Type[]; // Always at least one element
66416643
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
tests/cases/compiler/deeplyNestedTemplateLiteralIntersection.ts(20,11): error TS2590: Expression produces a union type that is too complex to represent.
2+
3+
4+
==== tests/cases/compiler/deeplyNestedTemplateLiteralIntersection.ts (1 errors) ====
5+
type R = `${number}a` & {
6+
_thing: true;
7+
};
8+
9+
type _S = "1" | "2" | "3" | "4" | "5" | "6";
10+
11+
type S = `${_S}${_S}${_S}`;
12+
13+
14+
type T = R | S;
15+
type X = `${T} ${T}`;
16+
17+
export type Props = Partial<{
18+
x: X;
19+
}>;
20+
21+
const a1: Props = {};
22+
const a2: Props = {};
23+
24+
const b = { ...a1, ...a2 };
25+
~~~~~~~~~~~~~~~~
26+
!!! error TS2590: Expression produces a union type that is too complex to represent.
27+
28+
export { b };
29+

tests/baselines/reference/templateLiteralIntersection.types

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ type OriginA1 = `${A}`
1616
>OriginA1 : "a"
1717

1818
type OriginA2 = `${MixA}`
19-
>OriginA2 : "a"
19+
>OriginA2 : `${MixA}`
2020

2121
type B = `${typeof a}`
2222
>B : "a"
@@ -30,14 +30,14 @@ type OriginB1 = `${B}`
3030
>OriginB1 : "a"
3131

3232
type OriginB2 = `${MixB}`
33-
>OriginB2 : "a"
33+
>OriginB2 : `${MixB}`
3434

3535
type MixC = { foo: string } & A
3636
>MixC : { foo: string; } & "a"
3737
>foo : string
3838

3939
type OriginC = `${MixC}`
40-
>OriginC : "a"
40+
>OriginC : `${MixC}`
4141

4242
type MixD<T extends string> =
4343
>MixD : `${T & { foo: string; }}`
@@ -46,7 +46,7 @@ type MixD<T extends string> =
4646
>foo : string
4747

4848
type OriginD = `${MixD<A & { foo: string }> & { foo: string }}`;
49-
>OriginD : "a"
49+
>OriginD : `${`${"a" & { foo: string; } & { foo: string; }}` & { foo: string; }}`
5050
>foo : string
5151
>foo : string
5252

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
tests/cases/compiler/templateLiteralIntersection2.ts(7,12): error TS2345: Argument of type '"foo/bar"' is not assignable to parameter of type '`${Path}/${Path}`'.
2+
tests/cases/compiler/templateLiteralIntersection2.ts(20,10): error TS2345: Argument of type '""' is not assignable to parameter of type '`a${string}` & `${string}a`'.
3+
Type '""' is not assignable to type '`a${string}`'.
4+
tests/cases/compiler/templateLiteralIntersection2.ts(22,10): error TS2345: Argument of type '"ab"' is not assignable to parameter of type '`a${string}` & `${string}a`'.
5+
Type '"ab"' is not assignable to type '`${string}a`'.
6+
7+
8+
==== tests/cases/compiler/templateLiteralIntersection2.ts (3 errors) ====
9+
type Path = string & { _pathBrand: any };
10+
11+
type JoinedPath = `${Path}/${Path}`;
12+
13+
declare function joinedPath(p: JoinedPath): void;
14+
15+
joinedPath("foo/bar");
16+
~~~~~~~~~
17+
!!! error TS2345: Argument of type '"foo/bar"' is not assignable to parameter of type '`${Path}/${Path}`'.
18+
19+
declare const somePath: Path;
20+
21+
joinedPath(`${somePath}/${somePath}`);
22+
23+
24+
type StartsWithA = `a${string}`;
25+
type EndsWithA = `${string}a`;
26+
27+
28+
declare function withinAs(p: StartsWithA & EndsWithA): void;
29+
30+
withinAs("");
31+
~~
32+
!!! error TS2345: Argument of type '""' is not assignable to parameter of type '`a${string}` & `${string}a`'.
33+
!!! error TS2345: Type '""' is not assignable to type '`a${string}`'.
34+
withinAs("a");
35+
withinAs("ab");
36+
~~~~
37+
!!! error TS2345: Argument of type '"ab"' is not assignable to parameter of type '`a${string}` & `${string}a`'.
38+
!!! error TS2345: Type '"ab"' is not assignable to type '`${string}a`'.
39+
withinAs("aba");
40+
withinAs("abavvvva");
41+
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
=== tests/cases/compiler/templateLiteralIntersection2.ts ===
2+
type Path = string & { _pathBrand: any };
3+
>Path : Symbol(Path, Decl(templateLiteralIntersection2.ts, 0, 0))
4+
>_pathBrand : Symbol(_pathBrand, Decl(templateLiteralIntersection2.ts, 0, 22))
5+
6+
type JoinedPath = `${Path}/${Path}`;
7+
>JoinedPath : Symbol(JoinedPath, Decl(templateLiteralIntersection2.ts, 0, 41))
8+
>Path : Symbol(Path, Decl(templateLiteralIntersection2.ts, 0, 0))
9+
>Path : Symbol(Path, Decl(templateLiteralIntersection2.ts, 0, 0))
10+
11+
declare function joinedPath(p: JoinedPath): void;
12+
>joinedPath : Symbol(joinedPath, Decl(templateLiteralIntersection2.ts, 2, 36))
13+
>p : Symbol(p, Decl(templateLiteralIntersection2.ts, 4, 28))
14+
>JoinedPath : Symbol(JoinedPath, Decl(templateLiteralIntersection2.ts, 0, 41))
15+
16+
joinedPath("foo/bar");
17+
>joinedPath : Symbol(joinedPath, Decl(templateLiteralIntersection2.ts, 2, 36))
18+
19+
declare const somePath: Path;
20+
>somePath : Symbol(somePath, Decl(templateLiteralIntersection2.ts, 8, 13))
21+
>Path : Symbol(Path, Decl(templateLiteralIntersection2.ts, 0, 0))
22+
23+
joinedPath(`${somePath}/${somePath}`);
24+
>joinedPath : Symbol(joinedPath, Decl(templateLiteralIntersection2.ts, 2, 36))
25+
>somePath : Symbol(somePath, Decl(templateLiteralIntersection2.ts, 8, 13))
26+
>somePath : Symbol(somePath, Decl(templateLiteralIntersection2.ts, 8, 13))
27+
28+
29+
type StartsWithA = `a${string}`;
30+
>StartsWithA : Symbol(StartsWithA, Decl(templateLiteralIntersection2.ts, 10, 38))
31+
32+
type EndsWithA = `${string}a`;
33+
>EndsWithA : Symbol(EndsWithA, Decl(templateLiteralIntersection2.ts, 13, 32))
34+
35+
36+
declare function withinAs(p: StartsWithA & EndsWithA): void;
37+
>withinAs : Symbol(withinAs, Decl(templateLiteralIntersection2.ts, 14, 30))
38+
>p : Symbol(p, Decl(templateLiteralIntersection2.ts, 17, 26))
39+
>StartsWithA : Symbol(StartsWithA, Decl(templateLiteralIntersection2.ts, 10, 38))
40+
>EndsWithA : Symbol(EndsWithA, Decl(templateLiteralIntersection2.ts, 13, 32))
41+
42+
withinAs("");
43+
>withinAs : Symbol(withinAs, Decl(templateLiteralIntersection2.ts, 14, 30))
44+
45+
withinAs("a");
46+
>withinAs : Symbol(withinAs, Decl(templateLiteralIntersection2.ts, 14, 30))
47+
48+
withinAs("ab");
49+
>withinAs : Symbol(withinAs, Decl(templateLiteralIntersection2.ts, 14, 30))
50+
51+
withinAs("aba");
52+
>withinAs : Symbol(withinAs, Decl(templateLiteralIntersection2.ts, 14, 30))
53+
54+
withinAs("abavvvva");
55+
>withinAs : Symbol(withinAs, Decl(templateLiteralIntersection2.ts, 14, 30))
56+
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
=== tests/cases/compiler/templateLiteralIntersection2.ts ===
2+
type Path = string & { _pathBrand: any };
3+
>Path : string & { _pathBrand: any; }
4+
>_pathBrand : any
5+
6+
type JoinedPath = `${Path}/${Path}`;
7+
>JoinedPath : `${Path}/${Path}`
8+
9+
declare function joinedPath(p: JoinedPath): void;
10+
>joinedPath : (p: JoinedPath) => void
11+
>p : `${Path}/${Path}`
12+
13+
joinedPath("foo/bar");
14+
>joinedPath("foo/bar") : void
15+
>joinedPath : (p: `${Path}/${Path}`) => void
16+
>"foo/bar" : "foo/bar"
17+
18+
declare const somePath: Path;
19+
>somePath : Path
20+
21+
joinedPath(`${somePath}/${somePath}`);
22+
>joinedPath(`${somePath}/${somePath}`) : void
23+
>joinedPath : (p: `${Path}/${Path}`) => void
24+
>`${somePath}/${somePath}` : `${Path}/${Path}`
25+
>somePath : Path
26+
>somePath : Path
27+
28+
29+
type StartsWithA = `a${string}`;
30+
>StartsWithA : `a${string}`
31+
32+
type EndsWithA = `${string}a`;
33+
>EndsWithA : `${string}a`
34+
35+
36+
declare function withinAs(p: StartsWithA & EndsWithA): void;
37+
>withinAs : (p: StartsWithA & EndsWithA) => void
38+
>p : `a${string}` & `${string}a`
39+
40+
withinAs("");
41+
>withinAs("") : void
42+
>withinAs : (p: `a${string}` & `${string}a`) => void
43+
>"" : ""
44+
45+
withinAs("a");
46+
>withinAs("a") : void
47+
>withinAs : (p: `a${string}` & `${string}a`) => void
48+
>"a" : "a"
49+
50+
withinAs("ab");
51+
>withinAs("ab") : void
52+
>withinAs : (p: `a${string}` & `${string}a`) => void
53+
>"ab" : "ab"
54+
55+
withinAs("aba");
56+
>withinAs("aba") : void
57+
>withinAs : (p: `a${string}` & `${string}a`) => void
58+
>"aba" : "aba"
59+
60+
withinAs("abavvvva");
61+
>withinAs("abavvvva") : void
62+
>withinAs : (p: `a${string}` & `${string}a`) => void
63+
>"abavvvva" : "abavvvva"
64+
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
=== tests/cases/compiler/templateLiteralIntersection3.ts ===
2+
type Path = string & { _pathBrand: any };
3+
>Path : Symbol(Path, Decl(templateLiteralIntersection3.ts, 0, 0))
4+
>_pathBrand : Symbol(_pathBrand, Decl(templateLiteralIntersection3.ts, 0, 22))
5+
6+
declare const path: Path;
7+
>path : Symbol(path, Decl(templateLiteralIntersection3.ts, 1, 13))
8+
>Path : Symbol(Path, Decl(templateLiteralIntersection3.ts, 0, 0))
9+
10+
declare const options1: { prop: number; } & { [k: string]: boolean; };
11+
>options1 : Symbol(options1, Decl(templateLiteralIntersection3.ts, 3, 13))
12+
>prop : Symbol(prop, Decl(templateLiteralIntersection3.ts, 3, 25))
13+
>k : Symbol(k, Decl(templateLiteralIntersection3.ts, 3, 47))
14+
15+
options1[`foo`] = false;
16+
>options1 : Symbol(options1, Decl(templateLiteralIntersection3.ts, 3, 13))
17+
18+
options1[`foo/${path}`] = false;
19+
>options1 : Symbol(options1, Decl(templateLiteralIntersection3.ts, 3, 13))
20+
>path : Symbol(path, Decl(templateLiteralIntersection3.ts, 1, 13))
21+
22+
23+
// Lowercase<`foo/${Path}`> => `foo/${Lowercase<Path>}`
24+
declare const lowercasePath: Lowercase<`foo/${Path}`>;
25+
>lowercasePath : Symbol(lowercasePath, Decl(templateLiteralIntersection3.ts, 11, 13))
26+
>Lowercase : Symbol(Lowercase, Decl(lib.es5.d.ts, --, --))
27+
>Path : Symbol(Path, Decl(templateLiteralIntersection3.ts, 0, 0))
28+
29+
options1[lowercasePath] = false;
30+
>options1 : Symbol(options1, Decl(templateLiteralIntersection3.ts, 3, 13))
31+
>lowercasePath : Symbol(lowercasePath, Decl(templateLiteralIntersection3.ts, 11, 13))
32+

0 commit comments

Comments
 (0)