@@ -771,7 +771,7 @@ namespace ts {
771771 const stringNumberSymbolType = getUnionType([stringType, numberType, esSymbolType]);
772772 const keyofConstraintType = keyofStringsOnly ? stringType : stringNumberSymbolType;
773773 const numberOrBigIntType = getUnionType([numberType, bigintType]);
774- const templateConstraintType = getUnionType([stringType, numberType, booleanType, bigintType]) ;
774+ const templateConstraintType = getUnionType([stringType, numberType, booleanType, bigintType, nullType, undefinedType]) as UnionType ;
775775
776776 const restrictiveMapper: TypeMapper = makeFunctionTypeMapper(t => t.flags & TypeFlags.TypeParameter ? getRestrictiveTypeParameter(<TypeParameter>t) : t);
777777 const permissiveMapper: TypeMapper = makeFunctionTypeMapper(t => t.flags & TypeFlags.TypeParameter ? wildcardType : t);
@@ -13207,6 +13207,30 @@ namespace ts {
1320713207 return true;
1320813208 }
1320913209
13210+ /**
13211+ * Returns `true` if the intersection of the template literals and string literals is the empty set, eg `get${string}` & "setX", and should reduce to `never`
13212+ */
13213+ function extractRedundantTemplateLiterals(types: Type[]): boolean {
13214+ let i = types.length;
13215+ const literals = filter(types, t => !!(t.flags & TypeFlags.StringLiteral));
13216+ while (i > 0) {
13217+ i--;
13218+ const t = types[i];
13219+ if (!(t.flags & TypeFlags.TemplateLiteral)) continue;
13220+ for (const t2 of literals) {
13221+ if (isTypeSubtypeOf(t2, t)) {
13222+ // eg, ``get${T}` & "getX"` is just `"getX"`
13223+ orderedRemoveItemAt(types, i);
13224+ break;
13225+ }
13226+ else if (isPatternLiteralType(t)) {
13227+ return true;
13228+ }
13229+ }
13230+ }
13231+ return false;
13232+ }
13233+
1321013234 function extractIrreducible(types: Type[], flag: TypeFlags) {
1321113235 if (every(types, t => !!(t.flags & TypeFlags.Union) && some((t as UnionType).types, tt => !!(tt.flags & flag)))) {
1321213236 for (let i = 0; i < types.length; i++) {
@@ -13355,7 +13379,12 @@ namespace ts {
1335513379 }
1335613380 }
1335713381 else {
13358- result = createIntersectionType(typeSet, aliasSymbol, aliasTypeArguments);
13382+ if (includes & TypeFlags.TemplateLiteral && includes & TypeFlags.StringLiteral && extractRedundantTemplateLiterals(typeSet)) {
13383+ result = neverType;
13384+ }
13385+ else {
13386+ result = createIntersectionType(typeSet, aliasSymbol, aliasTypeArguments);
13387+ }
1335913388 }
1336013389 intersectionTypes.set(id, result);
1336113390 }
@@ -13531,7 +13560,7 @@ namespace ts {
1353113560 function addSpans(texts: readonly string[], types: readonly Type[]): boolean {
1353213561 for (let i = 0; i < types.length; i++) {
1353313562 const t = types[i];
13534- if (t.flags & TypeFlags.Literal) {
13563+ if (t.flags & ( TypeFlags.Literal | TypeFlags.Null | TypeFlags.Undefined) ) {
1353513564 text += getTemplateStringForType(t) || "";
1353613565 text += texts[i + 1];
1353713566 }
@@ -13540,7 +13569,7 @@ namespace ts {
1354013569 if (!addSpans((<TemplateLiteralType>t).texts, (<TemplateLiteralType>t).types)) return false;
1354113570 text += texts[i + 1];
1354213571 }
13543- else if (isGenericIndexType(t)) {
13572+ else if (isGenericIndexType(t) || isPatternLiteralPlaceholderType(t) ) {
1354413573 newTypes.push(t);
1354513574 newTexts.push(text);
1354613575 text = texts[i + 1];
@@ -13558,6 +13587,8 @@ namespace ts {
1355813587 type.flags & TypeFlags.NumberLiteral ? "" + (<NumberLiteralType>type).value :
1355913588 type.flags & TypeFlags.BigIntLiteral ? pseudoBigIntToString((<BigIntLiteralType>type).value) :
1356013589 type.flags & TypeFlags.BooleanLiteral ? (<IntrinsicType>type).intrinsicName :
13590+ type.flags & TypeFlags.Null ? "null" :
13591+ type.flags & TypeFlags.Undefined ? "undefined" :
1356113592 undefined;
1356213593 }
1356313594
@@ -13817,6 +13848,14 @@ namespace ts {
1381713848 accessNode;
1381813849 }
1381913850
13851+ function isPatternLiteralPlaceholderType(type: Type) {
13852+ return templateConstraintType.types.indexOf(type) !== -1 || !!(type.flags & TypeFlags.Any);
13853+ }
13854+
13855+ function isPatternLiteralType(type: Type) {
13856+ return !!(type.flags & TypeFlags.TemplateLiteral) && every((type as TemplateLiteralType).types, isPatternLiteralPlaceholderType);
13857+ }
13858+
1382013859 function isGenericObjectType(type: Type): boolean {
1382113860 if (type.flags & TypeFlags.UnionOrIntersection) {
1382213861 if (!((<UnionOrIntersectionType>type).objectFlags & ObjectFlags.IsGenericObjectTypeComputed)) {
@@ -13836,7 +13875,7 @@ namespace ts {
1383613875 }
1383713876 return !!((<UnionOrIntersectionType>type).objectFlags & ObjectFlags.IsGenericIndexType);
1383813877 }
13839- return !!(type.flags & (TypeFlags.InstantiableNonPrimitive | TypeFlags.Index | TypeFlags.TemplateLiteral | TypeFlags.StringMapping));
13878+ return !!(type.flags & (TypeFlags.InstantiableNonPrimitive | TypeFlags.Index | TypeFlags.TemplateLiteral | TypeFlags.StringMapping)) && !isPatternLiteralType(type) ;
1384013879 }
1384113880
1384213881 function isThisTypeParameter(type: Type): boolean {
@@ -14562,6 +14601,8 @@ namespace ts {
1456214601 return !!(type.flags & TypeFlags.Literal) && (<LiteralType>type).freshType === type;
1456314602 }
1456414603
14604+ function getLiteralType(value: string): StringLiteralType;
14605+ function getLiteralType(value: string | number | PseudoBigInt, enumId?: number, symbol?: Symbol): LiteralType;
1456514606 function getLiteralType(value: string | number | PseudoBigInt, enumId?: number, symbol?: Symbol) {
1456614607 // We store all literal types in a single map with keys of the form '#NNN' and '@SSS',
1456714608 // where NNN is the text representation of a numeric literal and SSS are the characters
@@ -17346,6 +17387,15 @@ namespace ts {
1734617387 }
1734717388 }
1734817389 }
17390+ else if (target.flags & TypeFlags.TemplateLiteral && source.flags & TypeFlags.StringLiteral) {
17391+ if (isPatternLiteralType(target)) {
17392+ // match all non-`string` segments
17393+ const result = inferLiteralsFromTemplateLiteralType(source as StringLiteralType, target as TemplateLiteralType);
17394+ if (result && every(result, (r, i) => isStringLiteralTypeValueParsableAsType(r, (target as TemplateLiteralType).types[i]))) {
17395+ return Ternary.True;
17396+ }
17397+ }
17398+ }
1734917399
1735017400 if (source.flags & TypeFlags.TypeVariable) {
1735117401 if (source.flags & TypeFlags.IndexedAccess && target.flags & TypeFlags.IndexedAccess) {
@@ -17386,8 +17436,15 @@ namespace ts {
1738617436 }
1738717437 }
1738817438 else if (source.flags & TypeFlags.TemplateLiteral) {
17439+ if (target.flags & TypeFlags.TemplateLiteral &&
17440+ (source as TemplateLiteralType).texts.length === (target as TemplateLiteralType).texts.length &&
17441+ (source as TemplateLiteralType).types.length === (target as TemplateLiteralType).types.length &&
17442+ every((source as TemplateLiteralType).texts, (t, i) => t === (target as TemplateLiteralType).texts[i]) &&
17443+ every((instantiateType(source, makeFunctionTypeMapper(reportUnreliableMarkers)) as TemplateLiteralType).types, (t, i) => !!((target as TemplateLiteralType).types[i].flags & (TypeFlags.Any | TypeFlags.String)) || !!isRelatedTo(t, (target as TemplateLiteralType).types[i], /*reportErrors*/ false))) {
17444+ return Ternary.True;
17445+ }
1738917446 const constraint = getBaseConstraintOfType(source);
17390- if (constraint && (result = isRelatedTo(constraint, target, reportErrors))) {
17447+ if (constraint && constraint !== source && (result = isRelatedTo(constraint, target, reportErrors))) {
1739117448 resetErrorInfo(saveErrorInfo);
1739217449 return result;
1739317450 }
@@ -18308,12 +18365,12 @@ namespace ts {
1830818365
1830918366 if (type.flags & TypeFlags.Instantiable) {
1831018367 const constraint = getConstraintOfType(type);
18311- if (constraint) {
18368+ if (constraint && constraint !== type ) {
1831218369 return typeCouldHaveTopLevelSingletonTypes(constraint);
1831318370 }
1831418371 }
1831518372
18316- return isUnitType(type);
18373+ return isUnitType(type) || !!(type.flags & TypeFlags.TemplateLiteral) ;
1831718374 }
1831818375
1831918376 function getBestMatchingType(source: Type, target: UnionOrIntersectionType, isRelatedTo = compareTypesAssignable) {
@@ -19693,6 +19750,63 @@ namespace ts {
1969319750 return !!(type.symbol && some(type.symbol.declarations, hasSkipDirectInferenceFlag));
1969419751 }
1969519752
19753+ function isValidBigIntString(s: string): boolean {
19754+ const scanner = createScanner(ScriptTarget.ESNext, /*skipTrivia*/ false);
19755+ let success = true;
19756+ scanner.setOnError(() => success = false);
19757+ scanner.setText(s + "n");
19758+ let result = scanner.scan();
19759+ if (result === SyntaxKind.MinusToken) {
19760+ result = scanner.scan();
19761+ }
19762+ const flags = scanner.getTokenFlags();
19763+ // validate that
19764+ // * scanning proceeded without error
19765+ // * a bigint can be scanned, and that when it is scanned, it is
19766+ // * the full length of the input string (so the scanner is one character beyond the augmented input length)
19767+ // * it does not contain a numeric seperator (the `BigInt` constructor does not accept a numeric seperator in its input)
19768+ return success && result === SyntaxKind.BigIntLiteral && scanner.getTextPos() === (s.length + 1) && !(flags & TokenFlags.ContainsSeparator);
19769+ }
19770+
19771+ function isStringLiteralTypeValueParsableAsType(s: StringLiteralType, target: Type): boolean {
19772+ if (target.flags & TypeFlags.Union) {
19773+ return !!forEachType(target, t => isStringLiteralTypeValueParsableAsType(s, t));
19774+ }
19775+ switch (target) {
19776+ case stringType: return true;
19777+ case numberType: return s.value !== "" && isFinite(+(s.value));
19778+ case bigintType: return s.value !== "" && isValidBigIntString(s.value);
19779+ // the next 4 should be handled in `getTemplateLiteralType`, as they are all exactly one value, but are here for completeness, just in case
19780+ // this function is ever used on types which don't come from template literal holes
19781+ case trueType: return s.value === "true";
19782+ case falseType: return s.value === "false";
19783+ case undefinedType: return s.value === "undefined";
19784+ case nullType: return s.value === "null";
19785+ default: return !!(target.flags & TypeFlags.Any);
19786+ }
19787+ }
19788+
19789+ function inferLiteralsFromTemplateLiteralType(source: StringLiteralType, target: TemplateLiteralType): StringLiteralType[] | undefined {
19790+ const value = source.value;
19791+ const texts = target.texts;
19792+ const lastIndex = texts.length - 1;
19793+ const startText = texts[0];
19794+ const endText = texts[lastIndex];
19795+ if (!(value.startsWith(startText) && value.endsWith(endText))) return undefined;
19796+ const matches = [];
19797+ const str = value.slice(startText.length, value.length - endText.length);
19798+ let pos = 0;
19799+ for (let i = 1; i < lastIndex; i++) {
19800+ const delim = texts[i];
19801+ const delimPos = delim.length > 0 ? str.indexOf(delim, pos) : pos < str.length ? pos + 1 : -1;
19802+ if (delimPos < 0) return undefined;
19803+ matches.push(getLiteralType(str.slice(pos, delimPos)));
19804+ pos = delimPos + delim.length;
19805+ }
19806+ matches.push(getLiteralType(str.slice(pos)));
19807+ return matches;
19808+ }
19809+
1969619810 function inferTypes(inferences: InferenceInfo[], originalSource: Type, originalTarget: Type, priority: InferencePriority = 0, contravariant = false) {
1969719811 let bivariant = false;
1969819812 let propagationType: Type;
@@ -20170,27 +20284,6 @@ namespace ts {
2017020284 }
2017120285 }
2017220286
20173- function inferLiteralsFromTemplateLiteralType(source: StringLiteralType, target: TemplateLiteralType): Type[] | undefined {
20174- const value = source.value;
20175- const texts = target.texts;
20176- const lastIndex = texts.length - 1;
20177- const startText = texts[0];
20178- const endText = texts[lastIndex];
20179- if (!(value.startsWith(startText) && value.endsWith(endText))) return undefined;
20180- const matches = [];
20181- const str = value.slice(startText.length, value.length - endText.length);
20182- let pos = 0;
20183- for (let i = 1; i < lastIndex; i++) {
20184- const delim = texts[i];
20185- const delimPos = delim.length > 0 ? str.indexOf(delim, pos) : pos < str.length ? pos + 1 : -1;
20186- if (delimPos < 0) return undefined;
20187- matches.push(getLiteralType(str.slice(pos, delimPos)));
20188- pos = delimPos + delim.length;
20189- }
20190- matches.push(getLiteralType(str.slice(pos)));
20191- return matches;
20192- }
20193-
2019420287 function inferFromObjectTypes(source: Type, target: Type) {
2019520288 if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (
2019620289 (<TypeReference>source).target === (<TypeReference>target).target || isArrayType(source) && isArrayType(target))) {
@@ -31688,9 +31781,6 @@ namespace ts {
3168831781 checkSourceElement(span.type);
3168931782 const type = getTypeFromTypeNode(span.type);
3169031783 checkTypeAssignableTo(type, templateConstraintType, span.type);
31691- if (!everyType(type, t => !!(t.flags & TypeFlags.Literal) || isGenericIndexType(t))) {
31692- error(span.type, Diagnostics.Template_literal_type_argument_0_is_not_literal_type_or_a_generic_type, typeToString(type));
31693- }
3169431784 }
3169531785 getTypeFromTypeNode(node);
3169631786 }
0 commit comments