diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 16e982bfff38b..29804b039ceb9 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -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; } } @@ -17117,6 +17109,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; @@ -17124,6 +17117,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 @@ -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); } @@ -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)) { @@ -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 { @@ -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); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index b84d13a064299..c6b59c3481537 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -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 } diff --git a/tests/baselines/reference/deeplyNestedTemplateLiteralIntersection.errors.txt b/tests/baselines/reference/deeplyNestedTemplateLiteralIntersection.errors.txt new file mode 100644 index 0000000000000..b05c63b4b4be1 --- /dev/null +++ b/tests/baselines/reference/deeplyNestedTemplateLiteralIntersection.errors.txt @@ -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 }; + \ No newline at end of file diff --git a/tests/baselines/reference/templateLiteralIntersection.types b/tests/baselines/reference/templateLiteralIntersection.types index 0c716d40a731d..eb20f0977a8b9 100644 --- a/tests/baselines/reference/templateLiteralIntersection.types +++ b/tests/baselines/reference/templateLiteralIntersection.types @@ -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 = >MixD : `${T & { foo: string; }}` @@ -46,7 +46,7 @@ type MixD = >foo : string type OriginD = `${MixD & { foo: string }}`; ->OriginD : "a" +>OriginD : `${`${"a" & { foo: string; } & { foo: string; }}` & { foo: string; }}` >foo : string >foo : string diff --git a/tests/baselines/reference/templateLiteralIntersection2.errors.txt b/tests/baselines/reference/templateLiteralIntersection2.errors.txt new file mode 100644 index 0000000000000..fce5339a694e0 --- /dev/null +++ b/tests/baselines/reference/templateLiteralIntersection2.errors.txt @@ -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"); + \ No newline at end of file diff --git a/tests/baselines/reference/templateLiteralIntersection2.symbols b/tests/baselines/reference/templateLiteralIntersection2.symbols new file mode 100644 index 0000000000000..5e97ed68f8899 --- /dev/null +++ b/tests/baselines/reference/templateLiteralIntersection2.symbols @@ -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)) + diff --git a/tests/baselines/reference/templateLiteralIntersection2.types b/tests/baselines/reference/templateLiteralIntersection2.types new file mode 100644 index 0000000000000..a561c4e453632 --- /dev/null +++ b/tests/baselines/reference/templateLiteralIntersection2.types @@ -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" + diff --git a/tests/baselines/reference/templateLiteralIntersection3.symbols b/tests/baselines/reference/templateLiteralIntersection3.symbols new file mode 100644 index 0000000000000..1734daee2704d --- /dev/null +++ b/tests/baselines/reference/templateLiteralIntersection3.symbols @@ -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}` +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)) + diff --git a/tests/baselines/reference/templateLiteralIntersection3.types b/tests/baselines/reference/templateLiteralIntersection3.types new file mode 100644 index 0000000000000..4c89a8c82c2f7 --- /dev/null +++ b/tests/baselines/reference/templateLiteralIntersection3.types @@ -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}` +declare const lowercasePath: Lowercase<`foo/${Path}`>; +>lowercasePath : `foo/${Lowercase & { _pathBrand: any; }}` + +options1[lowercasePath] = false; +>options1[lowercasePath] = false : false +>options1[lowercasePath] : boolean +>options1 : { prop: number; } & { [k: string]: boolean; } +>lowercasePath : `foo/${Lowercase & { _pathBrand: any; }}` +>false : false + diff --git a/tests/cases/compiler/deeplyNestedTemplateLiteralIntersection.ts b/tests/cases/compiler/deeplyNestedTemplateLiteralIntersection.ts new file mode 100644 index 0000000000000..9af003f26c303 --- /dev/null +++ b/tests/cases/compiler/deeplyNestedTemplateLiteralIntersection.ts @@ -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 }; diff --git a/tests/cases/compiler/templateLiteralIntersection2.ts b/tests/cases/compiler/templateLiteralIntersection2.ts new file mode 100644 index 0000000000000..7b9ca947bd2bf --- /dev/null +++ b/tests/cases/compiler/templateLiteralIntersection2.ts @@ -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"); diff --git a/tests/cases/compiler/templateLiteralIntersection3.ts b/tests/cases/compiler/templateLiteralIntersection3.ts new file mode 100644 index 0000000000000..a9d71693ef18a --- /dev/null +++ b/tests/cases/compiler/templateLiteralIntersection3.ts @@ -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}` +declare const lowercasePath: Lowercase<`foo/${Path}`>; + +options1[lowercasePath] = false;