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

This commit is contained in:
Jake Bailey
2023-03-19 19:48:32 -07:00
committed by GitHub
parent b70784ef8f
commit 716b592e55
12 changed files with 359 additions and 27 deletions
+17 -20
View File
@@ -17070,32 +17070,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;
}
}
@@ -17113,6 +17105,7 @@ 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;
@@ -17120,6 +17113,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
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>
@@ -17423,6 +17417,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);
}
@@ -17444,12 +17441,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)) {
@@ -17459,7 +17456,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 {
@@ -23896,7 +23893,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
objectFlags & ObjectFlags.Reference && ((type as TypeReference).node || some(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);
}
+5 -3
View File
@@ -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 */
@@ -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 */
@@ -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 {
@@ -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
}
@@ -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 };
@@ -16,7 +16,7 @@ type OriginA1 = `${A}`
>OriginA1 : "a"
type OriginA2 = `${MixA}`
>OriginA2 : "a"
>OriginA2 : `${MixA}`
type B = `${typeof a}`
>B : "a"
@@ -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; }}`
@@ -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
@@ -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");
@@ -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))
@@ -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"
@@ -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))
@@ -0,0 +1,40 @@
=== tests/cases/compiler/templateLiteralIntersection3.ts ===
type Path = string & { _pathBrand: any };
>Path : string & { _pathBrand: any; }
>_pathBrand : any
declare const path: Path;
>path : Path
declare const options1: { prop: number; } & { [k: string]: boolean; };
>options1 : { prop: number; } & { [k: string]: boolean; }
>prop : number
>k : string
options1[`foo`] = false;
>options1[`foo`] = false : false
>options1[`foo`] : boolean
>options1 : { prop: number; } & { [k: string]: boolean; }
>`foo` : "foo"
>false : false
options1[`foo/${path}`] = false;
>options1[`foo/${path}`] = false : false
>options1[`foo/${path}`] : boolean
>options1 : { prop: number; } & { [k: string]: boolean; }
>`foo/${path}` : `foo/${Path}`
>path : Path
>false : false
// Lowercase<`foo/${Path}`> => `foo/${Lowercase<Path>}`
declare const lowercasePath: Lowercase<`foo/${Path}`>;
>lowercasePath : `foo/${Lowercase<string> & { _pathBrand: any; }}`
options1[lowercasePath] = false;
>options1[lowercasePath] = false : false
>options1[lowercasePath] : boolean
>options1 : { prop: number; } & { [k: string]: boolean; }
>lowercasePath : `foo/${Lowercase<string> & { _pathBrand: any; }}`
>false : false
@@ -0,0 +1,27 @@
// @strict: true
// @noEmit: true
// @noTypesAndSymbols: true
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 };
export { b };
@@ -0,0 +1,27 @@
// @strict: true
// @noEmit: true
type Path = string & { _pathBrand: any };
type JoinedPath = `${Path}/${Path}`;
declare function joinedPath(p: JoinedPath): void;
joinedPath("foo/bar");
declare const somePath: Path;
joinedPath(`${somePath}/${somePath}`);
type StartsWithA = `a${string}`;
type EndsWithA = `${string}a`;
declare function withinAs(p: StartsWithA & EndsWithA): void;
withinAs("");
withinAs("a");
withinAs("ab");
withinAs("aba");
withinAs("abavvvva");
@@ -0,0 +1,17 @@
// @strict: true
// @noEmit: true
type Path = string & { _pathBrand: any };
declare const path: Path;
declare const options1: { prop: number; } & { [k: string]: boolean; };
options1[`foo`] = false;
options1[`foo/${path}`] = false;
// Lowercase<`foo/${Path}`> => `foo/${Lowercase<Path>}`
declare const lowercasePath: Lowercase<`foo/${Path}`>;
options1[lowercasePath] = false;