Allow intersections to be used as valid types for template literal placeholders (#54188)

This commit is contained in:
Mateusz Burzyński
2023-08-10 21:50:21 +02:00
committed by GitHub
parent cd391b066b
commit 28cd1fbd13
7 changed files with 123 additions and 8 deletions

View File

@@ -16457,7 +16457,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
function removeStringLiteralsMatchedByTemplateLiterals(types: Type[]) {
const templates = filter(types, t => !!(t.flags & TypeFlags.TemplateLiteral) && isPatternLiteralType(t)) as TemplateLiteralType[];
const templates = filter(types, t =>
!!(t.flags & TypeFlags.TemplateLiteral) &&
isPatternLiteralType(t) &&
(t as TemplateLiteralType).types.every(t => !(t.flags & TypeFlags.Intersection) || !areIntersectedTypesAvoidingPrimitiveReduction((t as IntersectionType).types))
) as TemplateLiteralType[];
if (templates.length) {
let i = types.length;
while (i > 0) {
@@ -16966,8 +16970,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return reduceLeft(types, (n, t) => n + getConstituentCount(t), 0);
}
function areIntersectedTypesAvoidingPrimitiveReduction(t1: Type, t2: Type) {
return !!(t1.flags & (TypeFlags.String | TypeFlags.Number | TypeFlags.BigInt)) && t2 === emptyTypeLiteralType;
function areIntersectedTypesAvoidingPrimitiveReduction(types: Type[], primitiveFlags = TypeFlags.String | TypeFlags.Number | TypeFlags.BigInt): boolean {
if (types.length !== 2) {
return false;
}
const [t1, t2] = types;
return !!(t1.flags & primitiveFlags) && t2 === emptyTypeLiteralType || !!(t2.flags & primitiveFlags) && t1 === emptyTypeLiteralType;
}
function getTypeFromIntersectionTypeNode(node: IntersectionTypeNode): Type {
@@ -16975,7 +16983,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (!links.resolvedType) {
const aliasSymbol = getAliasSymbolForTypeNode(node);
const types = map(node.types, getTypeFromTypeNode);
const noSupertypeReduction = types.length === 2 && (areIntersectedTypesAvoidingPrimitiveReduction(types[0], types[1]) || areIntersectedTypesAvoidingPrimitiveReduction(types[1], types[0]));
const noSupertypeReduction = areIntersectedTypesAvoidingPrimitiveReduction(types);
links.resolvedType = getIntersectionType(types, aliasSymbol, getTypeArgumentsForAliasSymbol(aliasSymbol), noSupertypeReduction);
}
return links.resolvedType;
@@ -24362,12 +24370,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (source === target || target.flags & (TypeFlags.Any | TypeFlags.String)) {
return true;
}
if (target.flags & TypeFlags.Intersection) {
return every((target as IntersectionType).types, t => t === emptyTypeLiteralType || isValidTypeForTemplateLiteralPlaceholder(source, t));
}
if (source.flags & TypeFlags.StringLiteral) {
const value = (source as StringLiteralType).value;
return !!(target.flags & TypeFlags.Number && isValidNumberString(value, /*roundTripOnly*/ false) ||
target.flags & TypeFlags.BigInt && isValidBigIntString(value, /*roundTripOnly*/ false) ||
target.flags & (TypeFlags.BooleanLiteral | TypeFlags.Nullable) && value === (target as IntrinsicType).intrinsicName ||
target.flags & TypeFlags.StringMapping && isMemberOfStringMapping(getStringLiteralType(value), target));
target.flags & TypeFlags.StringMapping && isMemberOfStringMapping(getStringLiteralType(value), target) ||
target.flags & TypeFlags.TemplateLiteral && isTypeMatchedByTemplateLiteralType(source, target as TemplateLiteralType));
}
if (source.flags & TypeFlags.TemplateLiteral) {
const texts = (source as TemplateLiteralType).texts;