Skip to content

Revert fix for intersections in template literals, fix differently #52836

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Mar 20, 2023
Merged
37 changes: 17 additions & 20 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17074,32 +17074,24 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
return type;

function addSpans(texts: readonly string[] | string, types: readonly Type[]): boolean {
const isTextsArray = isArray(texts);
function addSpans(texts: readonly string[], types: readonly Type[]): boolean {
for (let i = 0; i < types.length; i++) {
const t = types[i];
const addText = isTextsArray ? texts[i + 1] : texts;
if (t.flags & (TypeFlags.Literal | TypeFlags.Null | TypeFlags.Undefined)) {
text += getTemplateStringForType(t) || "";
text += addText;
if (!isTextsArray) return true;
text += texts[i + 1];
}
else if (t.flags & TypeFlags.TemplateLiteral) {
text += (t as TemplateLiteralType).texts[0];
if (!addSpans((t as TemplateLiteralType).texts, (t as TemplateLiteralType).types)) return false;
text += addText;
if (!isTextsArray) return true;
text += texts[i + 1];
}
else if (isGenericIndexType(t) || isPatternLiteralPlaceholderType(t)) {
newTypes.push(t);
newTexts.push(text);
text = addText;
text = texts[i + 1];
}
else if (t.flags & TypeFlags.Intersection) {
const added = addSpans(texts[i + 1], (t as IntersectionType).types);
if (!added) return false;
}
else if (isTextsArray) {
else {
return false;
}
}
Expand All @@ -17117,13 +17109,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {

function createTemplateLiteralType(texts: readonly string[], types: readonly Type[]) {
const type = createType(TypeFlags.TemplateLiteral) as TemplateLiteralType;
type.objectFlags = getPropagatingFlagsOfTypes(types, /*excludeKinds*/ TypeFlags.Nullable);
type.texts = texts;
type.types = types;
return type;
}

function getStringMappingType(symbol: Symbol, type: Type): Type {
return type.flags & (TypeFlags.Union | TypeFlags.Never) ? mapType(type, t => getStringMappingType(symbol, t)) :
type.flags & TypeFlags.Intersection ? getIntersectionType(map((type as IntersectionType).types, t => getStringMappingType(symbol, t))) :
type.flags & TypeFlags.StringLiteral ? getStringLiteralType(applyStringMapping(symbol, (type as StringLiteralType).value)) :
type.flags & TypeFlags.TemplateLiteral ? getTemplateLiteralType(...applyTemplateStringMapping(symbol, (type as TemplateLiteralType).texts, (type as TemplateLiteralType).types)) :
// Mapping<Mapping<T>> === Mapping<T>
Expand Down Expand Up @@ -17427,6 +17421,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}

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

Expand All @@ -17448,12 +17445,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}

function getGenericObjectFlags(type: Type): ObjectFlags {
if (type.flags & TypeFlags.UnionOrIntersection) {
if (!((type as UnionOrIntersectionType).objectFlags & ObjectFlags.IsGenericTypeComputed)) {
(type as UnionOrIntersectionType).objectFlags |= ObjectFlags.IsGenericTypeComputed |
reduceLeft((type as UnionOrIntersectionType).types, (flags, t) => flags | getGenericObjectFlags(t), 0);
if (type.flags & (TypeFlags.UnionOrIntersection | TypeFlags.TemplateLiteral)) {
if (!((type as UnionOrIntersectionType | TemplateLiteralType).objectFlags & ObjectFlags.IsGenericTypeComputed)) {
(type as UnionOrIntersectionType | TemplateLiteralType).objectFlags |= ObjectFlags.IsGenericTypeComputed |
reduceLeft((type as UnionOrIntersectionType | TemplateLiteralType).types, (flags, t) => flags | getGenericObjectFlags(t), 0);
}
return (type as UnionOrIntersectionType).objectFlags & ObjectFlags.IsGenericType;
return (type as UnionOrIntersectionType | TemplateLiteralType).objectFlags & ObjectFlags.IsGenericType;
}
if (type.flags & TypeFlags.Substitution) {
if (!((type as SubstitutionType).objectFlags & ObjectFlags.IsGenericTypeComputed)) {
Expand All @@ -17463,7 +17460,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return (type as SubstitutionType).objectFlags & ObjectFlags.IsGenericType;
}
return (type.flags & TypeFlags.InstantiableNonPrimitive || isGenericMappedType(type) || isGenericTupleType(type) ? ObjectFlags.IsGenericObjectType : 0) |
(type.flags & (TypeFlags.InstantiableNonPrimitive | TypeFlags.Index | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) && !isPatternLiteralType(type) ? ObjectFlags.IsGenericIndexType : 0);
(type.flags & (TypeFlags.InstantiableNonPrimitive | TypeFlags.Index | TypeFlags.StringMapping) && !isPatternLiteralType(type) ? ObjectFlags.IsGenericIndexType : 0);
}

function getSimplifiedType(type: Type, writing: boolean): Type {
Expand Down Expand Up @@ -23879,7 +23876,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
objectFlags & ObjectFlags.Reference && ((type as TypeReference).node || forEach(getTypeArguments(type as TypeReference), couldContainTypeVariables)) ||
objectFlags & ObjectFlags.Anonymous && type.symbol && type.symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral) && type.symbol.declarations ||
objectFlags & (ObjectFlags.Mapped | ObjectFlags.ReverseMapped | ObjectFlags.ObjectRestType | ObjectFlags.InstantiationExpressionType)) ||
type.flags & TypeFlags.UnionOrIntersection && !(type.flags & TypeFlags.EnumLiteral) && !isNonGenericTopLevelType(type) && some((type as UnionOrIntersectionType).types, couldContainTypeVariables));
type.flags & (TypeFlags.UnionOrIntersection | TypeFlags.TemplateLiteral) && !(type.flags & TypeFlags.EnumLiteral) && !isNonGenericTopLevelType(type) && some((type as UnionOrIntersectionType | TemplateLiteralType).types, couldContainTypeVariables));
if (type.flags & TypeFlags.ObjectFlagsType) {
(type as ObjectFlagsType).objectFlags |= ObjectFlags.CouldContainTypeVariablesComputed | (result ? ObjectFlags.CouldContainTypeVariables : 0);
}
Expand Down
8 changes: 5 additions & 3 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6107,7 +6107,7 @@ export const enum TypeFlags {
Instantiable = InstantiableNonPrimitive | InstantiablePrimitive,
StructuredOrInstantiable = StructuredType | Instantiable,
/** @internal */
ObjectFlagsType = Any | Nullable | Never | Object | Union | Intersection,
ObjectFlagsType = Any | Nullable | Never | Object | Union | Intersection | TemplateLiteral,
/** @internal */
Simplifiable = IndexedAccess | Conditional,
/** @internal */
Expand Down Expand Up @@ -6261,7 +6261,7 @@ export const enum ObjectFlags {
/** @internal */
IdenticalBaseTypeExists = 1 << 26, // has a defined cachedEquivalentBaseType member

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

/** @internal */
export type ObjectFlagsType = NullableType | ObjectType | UnionType | IntersectionType;
export type ObjectFlagsType = NullableType | ObjectType | UnionType | IntersectionType | TemplateLiteralType;

// Object types (TypeFlags.ObjectType)
export interface ObjectType extends Type {
Expand Down Expand Up @@ -6636,6 +6636,8 @@ export interface ConditionalType extends InstantiableType {
}

export interface TemplateLiteralType extends InstantiableType {
/** @internal */
objectFlags: ObjectFlags;
texts: readonly string[]; // Always one element longer than types
types: readonly Type[]; // Always at least one element
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
tests/cases/compiler/deeplyNestedTemplateLiteralIntersection.ts(20,11): error TS2590: Expression produces a union type that is too complex to represent.


==== tests/cases/compiler/deeplyNestedTemplateLiteralIntersection.ts (1 errors) ====
type R = `${number}a` & {
_thing: true;
};

type _S = "1" | "2" | "3" | "4" | "5" | "6";

type S = `${_S}${_S}${_S}`;


type T = R | S;
type X = `${T} ${T}`;

export type Props = Partial<{
x: X;
}>;

const a1: Props = {};
const a2: Props = {};

const b = { ...a1, ...a2 };
~~~~~~~~~~~~~~~~
!!! error TS2590: Expression produces a union type that is too complex to represent.

export { b };

8 changes: 4 additions & 4 deletions tests/baselines/reference/templateLiteralIntersection.types
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type OriginA1 = `${A}`
>OriginA1 : "a"

type OriginA2 = `${MixA}`
>OriginA2 : "a"
>OriginA2 : `${MixA}`

type B = `${typeof a}`
>B : "a"
Expand All @@ -30,14 +30,14 @@ type OriginB1 = `${B}`
>OriginB1 : "a"

type OriginB2 = `${MixB}`
>OriginB2 : "a"
>OriginB2 : `${MixB}`

type MixC = { foo: string } & A
>MixC : { foo: string; } & "a"
>foo : string

type OriginC = `${MixC}`
>OriginC : "a"
>OriginC : `${MixC}`

type MixD<T extends string> =
>MixD : `${T & { foo: string; }}`
Expand All @@ -46,7 +46,7 @@ type MixD<T extends string> =
>foo : string

type OriginD = `${MixD<A & { foo: string }> & { foo: string }}`;
>OriginD : "a"
>OriginD : `${`${"a" & { foo: string; } & { foo: string; }}` & { foo: string; }}`
>foo : string
>foo : string

Expand Down
41 changes: 41 additions & 0 deletions tests/baselines/reference/templateLiteralIntersection2.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
tests/cases/compiler/templateLiteralIntersection2.ts(7,12): error TS2345: Argument of type '"foo/bar"' is not assignable to parameter of type '`${Path}/${Path}`'.
tests/cases/compiler/templateLiteralIntersection2.ts(20,10): error TS2345: Argument of type '""' is not assignable to parameter of type '`a${string}` & `${string}a`'.
Type '""' is not assignable to type '`a${string}`'.
tests/cases/compiler/templateLiteralIntersection2.ts(22,10): error TS2345: Argument of type '"ab"' is not assignable to parameter of type '`a${string}` & `${string}a`'.
Type '"ab"' is not assignable to type '`${string}a`'.


==== tests/cases/compiler/templateLiteralIntersection2.ts (3 errors) ====
type Path = string & { _pathBrand: any };

type JoinedPath = `${Path}/${Path}`;

declare function joinedPath(p: JoinedPath): void;

joinedPath("foo/bar");
~~~~~~~~~
!!! error TS2345: Argument of type '"foo/bar"' is not assignable to parameter of type '`${Path}/${Path}`'.

declare const somePath: Path;

joinedPath(`${somePath}/${somePath}`);


type StartsWithA = `a${string}`;
type EndsWithA = `${string}a`;


declare function withinAs(p: StartsWithA & EndsWithA): void;

withinAs("");
~~
!!! error TS2345: Argument of type '""' is not assignable to parameter of type '`a${string}` & `${string}a`'.
!!! error TS2345: Type '""' is not assignable to type '`a${string}`'.
withinAs("a");
withinAs("ab");
~~~~
!!! error TS2345: Argument of type '"ab"' is not assignable to parameter of type '`a${string}` & `${string}a`'.
!!! error TS2345: Type '"ab"' is not assignable to type '`${string}a`'.
withinAs("aba");
withinAs("abavvvva");

56 changes: 56 additions & 0 deletions tests/baselines/reference/templateLiteralIntersection2.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
=== tests/cases/compiler/templateLiteralIntersection2.ts ===
type Path = string & { _pathBrand: any };
>Path : Symbol(Path, Decl(templateLiteralIntersection2.ts, 0, 0))
>_pathBrand : Symbol(_pathBrand, Decl(templateLiteralIntersection2.ts, 0, 22))

type JoinedPath = `${Path}/${Path}`;
>JoinedPath : Symbol(JoinedPath, Decl(templateLiteralIntersection2.ts, 0, 41))
>Path : Symbol(Path, Decl(templateLiteralIntersection2.ts, 0, 0))
>Path : Symbol(Path, Decl(templateLiteralIntersection2.ts, 0, 0))

declare function joinedPath(p: JoinedPath): void;
>joinedPath : Symbol(joinedPath, Decl(templateLiteralIntersection2.ts, 2, 36))
>p : Symbol(p, Decl(templateLiteralIntersection2.ts, 4, 28))
>JoinedPath : Symbol(JoinedPath, Decl(templateLiteralIntersection2.ts, 0, 41))

joinedPath("foo/bar");
>joinedPath : Symbol(joinedPath, Decl(templateLiteralIntersection2.ts, 2, 36))

declare const somePath: Path;
>somePath : Symbol(somePath, Decl(templateLiteralIntersection2.ts, 8, 13))
>Path : Symbol(Path, Decl(templateLiteralIntersection2.ts, 0, 0))

joinedPath(`${somePath}/${somePath}`);
>joinedPath : Symbol(joinedPath, Decl(templateLiteralIntersection2.ts, 2, 36))
>somePath : Symbol(somePath, Decl(templateLiteralIntersection2.ts, 8, 13))
>somePath : Symbol(somePath, Decl(templateLiteralIntersection2.ts, 8, 13))


type StartsWithA = `a${string}`;
>StartsWithA : Symbol(StartsWithA, Decl(templateLiteralIntersection2.ts, 10, 38))

type EndsWithA = `${string}a`;
>EndsWithA : Symbol(EndsWithA, Decl(templateLiteralIntersection2.ts, 13, 32))


declare function withinAs(p: StartsWithA & EndsWithA): void;
>withinAs : Symbol(withinAs, Decl(templateLiteralIntersection2.ts, 14, 30))
>p : Symbol(p, Decl(templateLiteralIntersection2.ts, 17, 26))
>StartsWithA : Symbol(StartsWithA, Decl(templateLiteralIntersection2.ts, 10, 38))
>EndsWithA : Symbol(EndsWithA, Decl(templateLiteralIntersection2.ts, 13, 32))

withinAs("");
>withinAs : Symbol(withinAs, Decl(templateLiteralIntersection2.ts, 14, 30))

withinAs("a");
>withinAs : Symbol(withinAs, Decl(templateLiteralIntersection2.ts, 14, 30))

withinAs("ab");
>withinAs : Symbol(withinAs, Decl(templateLiteralIntersection2.ts, 14, 30))

withinAs("aba");
>withinAs : Symbol(withinAs, Decl(templateLiteralIntersection2.ts, 14, 30))

withinAs("abavvvva");
>withinAs : Symbol(withinAs, Decl(templateLiteralIntersection2.ts, 14, 30))

64 changes: 64 additions & 0 deletions tests/baselines/reference/templateLiteralIntersection2.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
=== tests/cases/compiler/templateLiteralIntersection2.ts ===
type Path = string & { _pathBrand: any };
>Path : string & { _pathBrand: any; }
>_pathBrand : any

type JoinedPath = `${Path}/${Path}`;
>JoinedPath : `${Path}/${Path}`

declare function joinedPath(p: JoinedPath): void;
>joinedPath : (p: JoinedPath) => void
>p : `${Path}/${Path}`

joinedPath("foo/bar");
>joinedPath("foo/bar") : void
>joinedPath : (p: `${Path}/${Path}`) => void
>"foo/bar" : "foo/bar"

declare const somePath: Path;
>somePath : Path

joinedPath(`${somePath}/${somePath}`);
>joinedPath(`${somePath}/${somePath}`) : void
>joinedPath : (p: `${Path}/${Path}`) => void
>`${somePath}/${somePath}` : `${Path}/${Path}`
>somePath : Path
>somePath : Path


type StartsWithA = `a${string}`;
>StartsWithA : `a${string}`

type EndsWithA = `${string}a`;
>EndsWithA : `${string}a`


declare function withinAs(p: StartsWithA & EndsWithA): void;
>withinAs : (p: StartsWithA & EndsWithA) => void
>p : `a${string}` & `${string}a`

withinAs("");
>withinAs("") : void
>withinAs : (p: `a${string}` & `${string}a`) => void
>"" : ""

withinAs("a");
>withinAs("a") : void
>withinAs : (p: `a${string}` & `${string}a`) => void
>"a" : "a"

withinAs("ab");
>withinAs("ab") : void
>withinAs : (p: `a${string}` & `${string}a`) => void
>"ab" : "ab"

withinAs("aba");
>withinAs("aba") : void
>withinAs : (p: `a${string}` & `${string}a`) => void
>"aba" : "aba"

withinAs("abavvvva");
>withinAs("abavvvva") : void
>withinAs : (p: `a${string}` & `${string}a`) => void
>"abavvvva" : "abavvvva"

32 changes: 32 additions & 0 deletions tests/baselines/reference/templateLiteralIntersection3.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
=== tests/cases/compiler/templateLiteralIntersection3.ts ===
type Path = string & { _pathBrand: any };
>Path : Symbol(Path, Decl(templateLiteralIntersection3.ts, 0, 0))
>_pathBrand : Symbol(_pathBrand, Decl(templateLiteralIntersection3.ts, 0, 22))

declare const path: Path;
>path : Symbol(path, Decl(templateLiteralIntersection3.ts, 1, 13))
>Path : Symbol(Path, Decl(templateLiteralIntersection3.ts, 0, 0))

declare const options1: { prop: number; } & { [k: string]: boolean; };
>options1 : Symbol(options1, Decl(templateLiteralIntersection3.ts, 3, 13))
>prop : Symbol(prop, Decl(templateLiteralIntersection3.ts, 3, 25))
>k : Symbol(k, Decl(templateLiteralIntersection3.ts, 3, 47))

options1[`foo`] = false;
>options1 : Symbol(options1, Decl(templateLiteralIntersection3.ts, 3, 13))

options1[`foo/${path}`] = false;
>options1 : Symbol(options1, Decl(templateLiteralIntersection3.ts, 3, 13))
>path : Symbol(path, Decl(templateLiteralIntersection3.ts, 1, 13))


// Lowercase<`foo/${Path}`> => `foo/${Lowercase<Path>}`
declare const lowercasePath: Lowercase<`foo/${Path}`>;
>lowercasePath : Symbol(lowercasePath, Decl(templateLiteralIntersection3.ts, 11, 13))
>Lowercase : Symbol(Lowercase, Decl(lib.es5.d.ts, --, --))
>Path : Symbol(Path, Decl(templateLiteralIntersection3.ts, 0, 0))

options1[lowercasePath] = false;
>options1 : Symbol(options1, Decl(templateLiteralIntersection3.ts, 3, 13))
>lowercasePath : Symbol(lowercasePath, Decl(templateLiteralIntersection3.ts, 11, 13))

Loading