@@ -14295,9 +14295,7 @@ namespace ts {
14295
14295
return type.flags & TypeFlags.StringLiteral ? (<StringLiteralType>type).value :
14296
14296
type.flags & TypeFlags.NumberLiteral ? "" + (<NumberLiteralType>type).value :
14297
14297
type.flags & TypeFlags.BigIntLiteral ? pseudoBigIntToString((<BigIntLiteralType>type).value) :
14298
- type.flags & TypeFlags.BooleanLiteral ? (<IntrinsicType>type).intrinsicName :
14299
- type.flags & TypeFlags.Null ? "null" :
14300
- type.flags & TypeFlags.Undefined ? "undefined" :
14298
+ type.flags & (TypeFlags.BooleanLiteral | TypeFlags.Nullable) ? (<IntrinsicType>type).intrinsicName :
14301
14299
undefined;
14302
14300
}
14303
14301
@@ -14577,7 +14575,7 @@ namespace ts {
14577
14575
}
14578
14576
14579
14577
function isPatternLiteralPlaceholderType(type: Type) {
14580
- return templateConstraintType.types.indexOf (type) !== -1 || !!(type.flags & TypeFlags.Any );
14578
+ return !! (type.flags & (TypeFlags.Any | TypeFlags.String | TypeFlags.Number | TypeFlags.BigInt) );
14581
14579
}
14582
14580
14583
14581
function isPatternLiteralType(type: Type) {
@@ -18275,13 +18273,10 @@ namespace ts {
18275
18273
return localResult;
18276
18274
}
18277
18275
}
18278
- else if (target.flags & TypeFlags.TemplateLiteral && source.flags & TypeFlags.StringLiteral) {
18279
- if (isPatternLiteralType(target)) {
18280
- // match all non-`string` segments
18281
- const result = inferLiteralsFromTemplateLiteralType(source as StringLiteralType, target as TemplateLiteralType);
18282
- if (result && every(result, (r, i) => isStringLiteralTypeValueParsableAsType(r, (target as TemplateLiteralType).types[i]))) {
18283
- return Ternary.True;
18284
- }
18276
+ else if (target.flags & TypeFlags.TemplateLiteral) {
18277
+ const result = inferTypesFromTemplateLiteralType(source, target as TemplateLiteralType);
18278
+ if (result && every(result, (r, i) => isValidTypeForTemplateLiteralPlaceholder(r, (target as TemplateLiteralType).types[i]))) {
18279
+ return Ternary.True;
18285
18280
}
18286
18281
}
18287
18282
@@ -20688,43 +20683,108 @@ namespace ts {
20688
20683
return success && result === SyntaxKind.BigIntLiteral && scanner.getTextPos() === (s.length + 1) && !(flags & TokenFlags.ContainsSeparator);
20689
20684
}
20690
20685
20691
- function isStringLiteralTypeValueParsableAsType(s: StringLiteralType, target: Type): boolean {
20692
- if (target.flags & TypeFlags.Union) {
20693
- return someType(target, t => isStringLiteralTypeValueParsableAsType(s, t));
20694
- }
20695
- switch (target) {
20696
- case stringType: return true;
20697
- case numberType: return s.value !== "" && isFinite(+(s.value));
20698
- case bigintType: return s.value !== "" && isValidBigIntString(s.value);
20699
- // the next 4 should be handled in `getTemplateLiteralType`, as they are all exactly one value, but are here for completeness, just in case
20700
- // this function is ever used on types which don't come from template literal holes
20701
- case trueType: return s.value === "true";
20702
- case falseType: return s.value === "false";
20703
- case undefinedType: return s.value === "undefined";
20704
- case nullType: return s.value === "null";
20705
- default: return !!(target.flags & TypeFlags.Any);
20706
- }
20707
- }
20708
-
20709
- function inferLiteralsFromTemplateLiteralType(source: StringLiteralType, target: TemplateLiteralType): StringLiteralType[] | undefined {
20710
- const value = source.value;
20711
- const texts = target.texts;
20712
- const lastIndex = texts.length - 1;
20713
- const startText = texts[0];
20714
- const endText = texts[lastIndex];
20715
- if (!(value.startsWith(startText) && value.slice(startText.length).endsWith(endText))) return undefined;
20716
- const matches = [];
20717
- const str = value.slice(startText.length, value.length - endText.length);
20718
- let pos = 0;
20719
- for (let i = 1; i < lastIndex; i++) {
20720
- const delim = texts[i];
20721
- const delimPos = delim.length > 0 ? str.indexOf(delim, pos) : pos < str.length ? pos + 1 : -1;
20722
- if (delimPos < 0) return undefined;
20723
- matches.push(getLiteralType(str.slice(pos, delimPos)));
20724
- pos = delimPos + delim.length;
20725
- }
20726
- matches.push(getLiteralType(str.slice(pos)));
20686
+ function isValidTypeForTemplateLiteralPlaceholder(source: Type, target: Type): boolean {
20687
+ if (source === target || target.flags & (TypeFlags.Any | TypeFlags.String)) {
20688
+ return true;
20689
+ }
20690
+ if (source.flags & TypeFlags.StringLiteral) {
20691
+ const value = (<StringLiteralType>source).value;
20692
+ return !!(target.flags & TypeFlags.Number && value !== "" && isFinite(+value) ||
20693
+ target.flags & TypeFlags.BigInt && value !== "" && isValidBigIntString(value) ||
20694
+ target.flags & (TypeFlags.BooleanLiteral | TypeFlags.Nullable) && value === (<IntrinsicType>target).intrinsicName);
20695
+ }
20696
+ if (source.flags & TypeFlags.TemplateLiteral) {
20697
+ const texts = (<TemplateLiteralType>source).texts;
20698
+ return texts.length === 2 && texts[0] === "" && texts[1] === "" && isTypeAssignableTo((<TemplateLiteralType>source).types[0], target);
20699
+ }
20700
+ return isTypeAssignableTo(source, target);
20701
+ }
20702
+
20703
+ function inferTypesFromTemplateLiteralType(source: Type, target: TemplateLiteralType): Type[] | undefined {
20704
+ return source.flags & TypeFlags.StringLiteral ? inferFromLiteralPartsToTemplateLiteral([(<StringLiteralType>source).value], emptyArray, target) :
20705
+ source.flags & TypeFlags.TemplateLiteral ?
20706
+ arraysEqual((<TemplateLiteralType>source).texts, target.texts) ? map((<TemplateLiteralType>source).types, getStringLikeTypeForType) :
20707
+ inferFromLiteralPartsToTemplateLiteral((<TemplateLiteralType>source).texts, (<TemplateLiteralType>source).types, target) :
20708
+ undefined;
20709
+ }
20710
+
20711
+ function getStringLikeTypeForType(type: Type) {
20712
+ return type.flags & (TypeFlags.Any | TypeFlags.StringLike) ? type : getTemplateLiteralType(["", ""], [type]);
20713
+ }
20714
+
20715
+ // This function infers from the text parts and type parts of a source literal to a target template literal. The number
20716
+ // of text parts is always one more than the number of type parts, and a source string literal is treated as a source
20717
+ // with one text part and zero type parts. The function returns an array of inferred string or template literal types
20718
+ // corresponding to the placeholders in the target template literal, or undefined if the source doesn't match the target.
20719
+ //
20720
+ // We first check that the starting source text part matches the starting target text part, and that the ending source
20721
+ // text part ends matches the ending target text part. We then iterate through the remaining target text parts, finding
20722
+ // a match for each in the source and inferring string or template literal types created from the segments of the source
20723
+ // that occur between the matches. During this iteration, seg holds the index of the current text part in the sourceTexts
20724
+ // array and pos holds the current character position in the current text part.
20725
+ //
20726
+ // Consider inference from type `<<${string}>.<${number}-${number}>>` to type `<${string}.${string}>`, i.e.
20727
+ // sourceTexts = ['<<', '>.<', '-', '>>']
20728
+ // sourceTypes = [string, number, number]
20729
+ // target.texts = ['<', '.', '>']
20730
+ // We first match '<' in the target to the start of '<<' in the source and '>' in the target to the end of '>>' in
20731
+ // the source. The first match for the '.' in target occurs at character 1 in the source text part at index 1, and thus
20732
+ // the first inference is the template literal type `<${string}>`. The remainder of the source makes up the second
20733
+ // inference, the template literal type `<${number}-${number}>`.
20734
+ function inferFromLiteralPartsToTemplateLiteral(sourceTexts: readonly string[], sourceTypes: readonly Type[], target: TemplateLiteralType): Type[] | undefined {
20735
+ const lastSourceIndex = sourceTexts.length - 1;
20736
+ const sourceStartText = sourceTexts[0];
20737
+ const sourceEndText = sourceTexts[lastSourceIndex];
20738
+ const targetTexts = target.texts;
20739
+ const lastTargetIndex = targetTexts.length - 1;
20740
+ const targetStartText = targetTexts[0];
20741
+ const targetEndText = targetTexts[lastTargetIndex];
20742
+ if (lastSourceIndex === 0 && sourceStartText.length < targetStartText.length + targetEndText.length ||
20743
+ !sourceStartText.startsWith(targetStartText) || !sourceEndText.endsWith(targetEndText)) return undefined;
20744
+ const remainingEndText = sourceEndText.slice(0, sourceEndText.length - targetEndText.length);
20745
+ const matches: Type[] = [];
20746
+ let seg = 0;
20747
+ let pos = targetStartText.length;
20748
+ for (let i = 1; i < lastTargetIndex; i++) {
20749
+ const delim = targetTexts[i];
20750
+ if (delim.length > 0) {
20751
+ let s = seg;
20752
+ let p = pos;
20753
+ while (true) {
20754
+ p = getSourceText(s).indexOf(delim, p);
20755
+ if (p >= 0) break;
20756
+ s++;
20757
+ if (s === sourceTexts.length) return undefined;
20758
+ p = 0;
20759
+ }
20760
+ addMatch(s, p);
20761
+ pos += delim.length;
20762
+ }
20763
+ else if (pos < getSourceText(seg).length) {
20764
+ addMatch(seg, pos + 1);
20765
+ }
20766
+ else if (seg < lastSourceIndex) {
20767
+ addMatch(seg + 1, 0);
20768
+ }
20769
+ else {
20770
+ return undefined;
20771
+ }
20772
+ }
20773
+ addMatch(lastSourceIndex, getSourceText(lastSourceIndex).length);
20727
20774
return matches;
20775
+ function getSourceText(index: number) {
20776
+ return index < lastSourceIndex ? sourceTexts[index] : remainingEndText;
20777
+ }
20778
+ function addMatch(s: number, p: number) {
20779
+ const matchType = s === seg ?
20780
+ getLiteralType(getSourceText(s).slice(pos, p)) :
20781
+ getTemplateLiteralType(
20782
+ [sourceTexts[seg].slice(pos), ...sourceTexts.slice(seg + 1, s), getSourceText(s).slice(0, p)],
20783
+ sourceTypes.slice(seg, s));
20784
+ matches.push(matchType);
20785
+ seg = s;
20786
+ pos = p;
20787
+ }
20728
20788
}
20729
20789
20730
20790
function inferTypes(inferences: InferenceInfo[], originalSource: Type, originalTarget: Type, priority: InferencePriority = 0, contravariant = false) {
@@ -21189,9 +21249,7 @@ namespace ts {
21189
21249
}
21190
21250
21191
21251
function inferToTemplateLiteralType(source: Type, target: TemplateLiteralType) {
21192
- const matches = source.flags & TypeFlags.StringLiteral ? inferLiteralsFromTemplateLiteralType(<StringLiteralType>source, target) :
21193
- source.flags & TypeFlags.TemplateLiteral && arraysEqual((<TemplateLiteralType>source).texts, target.texts) ? (<TemplateLiteralType>source).types :
21194
- undefined;
21252
+ const matches = inferTypesFromTemplateLiteralType(source, target);
21195
21253
const types = target.types;
21196
21254
for (let i = 0; i < types.length; i++) {
21197
21255
inferFromTypes(matches ? matches[i] : neverType, types[i]);
0 commit comments