Preserve the distributivity of inlined conditional types in declaration emit (#48592)

This commit is contained in:
Wesley Wigham
2022-04-06 17:21:46 -07:00
committed by GitHub
parent 94d33ba85d
commit 4d2fb5407c
5 changed files with 252 additions and 1 deletions

View File

@@ -5102,13 +5102,49 @@ namespace ts {
function conditionalTypeToTypeNode(type: ConditionalType) {
const checkTypeNode = typeToTypeNodeHelper(type.checkType, context);
context.approximateLength += 15;
if (context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams && type.root.isDistributive && !(type.checkType.flags & TypeFlags.TypeParameter)) {
const newParam = createTypeParameter(createSymbol(SymbolFlags.TypeParameter, "T" as __String));
const name = typeParameterToName(newParam, context);
const newTypeVariable = factory.createTypeReferenceNode(name);
context.approximateLength += 37; // 15 each for two added conditionals, 7 for an added infer type
const newMapper = prependTypeMapping(type.root.checkType, newParam, type.combinedMapper || type.mapper);
const saveInferTypeParameters = context.inferTypeParameters;
context.inferTypeParameters = type.root.inferTypeParameters;
const extendsTypeNode = typeToTypeNodeHelper(instantiateType(type.root.extendsType, newMapper), context);
context.inferTypeParameters = saveInferTypeParameters;
const trueTypeNode = typeToTypeNodeOrCircularityElision(instantiateType(getTypeFromTypeNode(type.root.node.trueType), newMapper));
const falseTypeNode = typeToTypeNodeOrCircularityElision(instantiateType(getTypeFromTypeNode(type.root.node.falseType), newMapper));
// outermost conditional makes `T` a type parameter, allowing the inner conditionals to be distributive
// second conditional makes `T` have `T & checkType` substitution, so it is correctly usable as the checkType
// inner conditional runs the check the user provided on the check type (distributively) and returns the result
// checkType extends infer T ? T extends checkType ? T extends extendsType<T> ? trueType<T> : falseType<T> : never : never;
// this is potentially simplifiable to
// checkType extends infer T ? T extends checkType & extendsType<T> ? trueType<T> : falseType<T> : never;
// but that may confuse users who read the output more.
// On the other hand,
// checkType extends infer T extends checkType ? T extends extendsType<T> ? trueType<T> : falseType<T> : never;
// may also work with `infer ... extends ...` in, but would produce declarations only compatible with the latest TS.
return factory.createConditionalTypeNode(
checkTypeNode,
factory.createInferTypeNode(factory.createTypeParameterDeclaration(/*modifiers*/ undefined, factory.cloneNode(newTypeVariable.typeName) as Identifier)),
factory.createConditionalTypeNode(
factory.createTypeReferenceNode(factory.cloneNode(name)),
typeToTypeNodeHelper(type.checkType, context),
factory.createConditionalTypeNode(newTypeVariable, extendsTypeNode, trueTypeNode, falseTypeNode),
factory.createKeywordTypeNode(SyntaxKind.NeverKeyword)
),
factory.createKeywordTypeNode(SyntaxKind.NeverKeyword)
);
}
const saveInferTypeParameters = context.inferTypeParameters;
context.inferTypeParameters = type.root.inferTypeParameters;
const extendsTypeNode = typeToTypeNodeHelper(type.extendsType, context);
context.inferTypeParameters = saveInferTypeParameters;
const trueTypeNode = typeToTypeNodeOrCircularityElision(getTrueTypeFromConditionalType(type));
const falseTypeNode = typeToTypeNodeOrCircularityElision(getFalseTypeFromConditionalType(type));
context.approximateLength += 15;
return factory.createConditionalTypeNode(checkTypeNode, extendsTypeNode, trueTypeNode, falseTypeNode);
}