TypeScript/src/compiler/expressionToTypeNode.ts

1331 lines
68 KiB
TypeScript

import {
AccessorDeclaration,
AllAccessorDeclarations,
ArrayLiteralExpression,
ArrowFunction,
AsExpression,
BinaryExpression,
ClassExpression,
CompilerOptions,
ConditionalTypeNode,
countWhere,
Debug,
Declaration,
ElementAccessExpression,
EmitFlags,
Expression,
factory,
findAncestor,
forEachReturnStatement,
FunctionExpression,
FunctionFlags,
FunctionLikeDeclaration,
GetAccessorDeclaration,
getEffectiveReturnTypeNode,
getEffectiveSetAccessorTypeAnnotationNode,
getEffectiveTypeAnnotationNode,
getEmitFlags,
getEmitScriptTarget,
getFunctionFlags,
getJSDocType,
getJSDocTypeAssertionType,
getSourceFileOfNode,
getStrictOptionValue,
hasDynamicName,
HasInferredType,
Identifier,
ImportTypeNode,
IndexedAccessTypeNode,
IntersectionTypeNode,
IntroducesNewScopeNode,
isAsExpression,
isBlock,
isCallExpression,
isComputedPropertyName,
isConditionalTypeNode,
isConstTypeReference,
isDeclaration,
isDeclarationReadonly,
isEntityName,
isEntityNameExpression,
isExpressionWithTypeArguments,
isFunctionLike,
isFunctionLikeDeclaration,
isGetAccessor,
isIdentifier,
isIdentifierText,
isImportAttributes,
isImportTypeNode,
isIndexedAccessTypeNode,
isInJSFile,
isJSDocAllType,
isJSDocConstructSignature,
isJSDocFunctionType,
isJSDocIndexSignature,
isJSDocNonNullableType,
isJSDocNullableType,
isJSDocOptionalType,
isJSDocTypeAssertion,
isJSDocTypeExpression,
isJSDocTypeLiteral,
isJSDocUnknownType,
isJSDocVariadicType,
isJsxElement,
isJsxExpression,
isKeyword,
isLiteralImportTypeNode,
isLiteralTypeNode,
isMappedTypeNode,
isModifier,
isNamedDeclaration,
isNewScopeNode,
isOptionalDeclaration,
isParameter,
isPrimitiveLiteralValue,
isPropertyDeclaration,
isPropertySignature,
isShorthandPropertyAssignment,
isSpreadAssignment,
isStringLiteral,
isThisTypeNode,
isTupleTypeNode,
isTypeAssertionExpression,
isTypeLiteralNode,
isTypeNode,
isTypeOperatorNode,
isTypeParameterDeclaration,
isTypePredicateNode,
isTypeQueryNode,
isTypeReferenceNode,
isUnionTypeNode,
isValueSignatureDeclaration,
isVarConstLike,
isVariableDeclaration,
JSDocParameterTag,
JSDocPropertyTag,
JSDocSignature,
JsxAttributeValue,
KeywordTypeSyntaxKind,
map,
mapDefined,
MethodDeclaration,
Mutable,
Node,
NodeArray,
NodeBuilderFlags,
NodeFlags,
nodeIsMissing,
NoSubstitutionTemplateLiteral,
ObjectLiteralExpression,
ParameterDeclaration,
ParenthesizedExpression,
ParenthesizedTypeNode,
PrefixUnaryExpression,
PrimitiveLiteral,
PropertyAccessExpression,
PropertyAssignment,
PropertyDeclaration,
PropertyName,
PropertySignature,
SetAccessorDeclaration,
setCommentRange,
setEmitFlags,
setOriginalNode,
setTextRangePosEnd,
ShorthandPropertyAssignment,
SignatureDeclaration,
skipTypeParentheses,
StringLiteral,
Symbol,
SyntacticNodeBuilder,
SyntacticTypeNodeBuilderContext,
SyntacticTypeNodeBuilderResolver,
SyntaxKind,
TypeAssertion,
TypeElement,
TypeNode,
TypeOperatorNode,
TypeParameterDeclaration,
TypeQueryNode,
TypeReferenceNode,
UnionTypeNode,
VariableDeclaration,
visitEachChild as visitEachChildWorker,
visitNode,
visitNodes,
Visitor,
walkUpParenthesizedExpressions,
} from "./_namespaces/ts.js";
type SyntacticResult =
| { type: TypeNode; reportFallback: undefined; }
| { type: undefined; reportFallback: true; }
| { type: undefined; reportFallback: false; };
function syntacticResult(type: TypeNode | undefined): SyntacticResult;
function syntacticResult(type: undefined, reportFallback: boolean): SyntacticResult;
function syntacticResult(type: TypeNode | undefined, reportFallback: boolean = true) {
return { type, reportFallback } as SyntacticResult;
}
const notImplemented: SyntacticResult = syntacticResult(/*type*/ undefined, /*reportFallback*/ false);
const alreadyReported: SyntacticResult = syntacticResult(/*type*/ undefined, /*reportFallback*/ false);
const failed: SyntacticResult = syntacticResult(/*type*/ undefined, /*reportFallback*/ true);
/** @internal */
export function createSyntacticTypeNodeBuilder(
options: CompilerOptions,
resolver: SyntacticTypeNodeBuilderResolver,
): SyntacticNodeBuilder {
const strictNullChecks = getStrictOptionValue(options, "strictNullChecks");
return {
serializeTypeOfDeclaration,
serializeReturnTypeForSignature,
serializeTypeOfExpression,
serializeTypeOfAccessor,
tryReuseExistingTypeNode(context: SyntacticTypeNodeBuilderContext, existing: TypeNode): TypeNode | undefined {
if (!resolver.canReuseTypeNode(context, existing)) {
return undefined;
}
return tryReuseExistingTypeNode(context, existing);
},
};
function reuseNode<T extends Node>(context: SyntacticTypeNodeBuilderContext, node: T, range?: Node): T;
function reuseNode<T extends Node>(context: SyntacticTypeNodeBuilderContext, node: T | undefined, range?: Node): T | undefined;
function reuseNode<T extends Node>(context: SyntacticTypeNodeBuilderContext, node: T | undefined, range: Node | undefined = node) {
return node === undefined ? undefined : resolver.markNodeReuse(context, node.flags & NodeFlags.Synthesized ? node : factory.cloneNode(node), range ?? node);
}
function tryReuseExistingTypeNode(context: SyntacticTypeNodeBuilderContext, existing: TypeNode): TypeNode | undefined {
const { finalizeBoundary, startRecoveryScope, hadError, markError } = resolver.createRecoveryBoundary(context);
const transformed = visitNode(existing, visitExistingNodeTreeSymbols, isTypeNode);
if (!finalizeBoundary()) {
return undefined;
}
context.approximateLength += existing.end - existing.pos;
return transformed;
function visitExistingNodeTreeSymbols(node: Node): Node | undefined {
// If there was an error in a sibling node bail early, the result will be discarded anyway
if (hadError()) return node;
const recover = startRecoveryScope();
const onExitNewScope = isNewScopeNode(node) ? resolver.enterNewScope(context, node) : undefined;
const result = visitExistingNodeTreeSymbolsWorker(node);
onExitNewScope?.();
// If there was an error, maybe we can recover by serializing the actual type of the node
if (hadError()) {
if (isTypeNode(node) && !isTypePredicateNode(node)) {
recover();
return resolver.serializeExistingTypeNode(context, node);
}
return node;
}
// We want to clone the subtree, so when we mark it up with __pos and __end in quickfixes,
// we don't get odd behavior because of reused nodes. We also need to clone to _remove_
// the position information if the node comes from a different file than the one the node builder
// is set to build for (even though we are reusing the node structure, the position information
// would make the printer print invalid spans for literals and identifiers, and the formatter would
// choke on the mismatched positonal spans between a parent and an injected child from another file).
return result ? resolver.markNodeReuse(context, result, node) : undefined;
}
function tryVisitSimpleTypeNode(node: TypeNode): TypeNode | undefined {
const innerNode = skipTypeParentheses(node);
switch (innerNode.kind) {
case SyntaxKind.TypeReference:
return tryVisitTypeReference(innerNode as TypeReferenceNode);
case SyntaxKind.TypeQuery:
return tryVisitTypeQuery(innerNode as TypeQueryNode);
case SyntaxKind.IndexedAccessType:
return tryVisitIndexedAccess(innerNode as IndexedAccessTypeNode);
case SyntaxKind.TypeOperator:
const typeOperatorNode = innerNode as TypeOperatorNode;
if (typeOperatorNode.operator === SyntaxKind.KeyOfKeyword) {
return tryVisitKeyOf(typeOperatorNode);
}
}
return visitNode(node, visitExistingNodeTreeSymbols, isTypeNode);
}
function tryVisitIndexedAccess(node: IndexedAccessTypeNode): TypeNode | undefined {
const resultObjectType = tryVisitSimpleTypeNode(node.objectType);
if (resultObjectType === undefined) {
return undefined;
}
return factory.updateIndexedAccessTypeNode(node, resultObjectType, visitNode(node.indexType, visitExistingNodeTreeSymbols, isTypeNode)!);
}
function tryVisitKeyOf(node: TypeOperatorNode): TypeNode | undefined {
Debug.assertEqual(node.operator, SyntaxKind.KeyOfKeyword);
const type = tryVisitSimpleTypeNode(node.type);
if (type === undefined) {
return undefined;
}
return factory.updateTypeOperatorNode(node, type);
}
function tryVisitTypeQuery(node: TypeQueryNode): TypeNode | undefined {
const { introducesError, node: exprName } = resolver.trackExistingEntityName(context, node.exprName);
if (!introducesError) {
return factory.updateTypeQueryNode(
node,
exprName,
visitNodes(node.typeArguments, visitExistingNodeTreeSymbols, isTypeNode),
);
}
const serializedName = resolver.serializeTypeName(context, node.exprName, /*isTypeOf*/ true);
if (serializedName) {
return resolver.markNodeReuse(context, serializedName, node.exprName);
}
}
function tryVisitTypeReference(node: TypeReferenceNode): TypeNode | undefined {
if (resolver.canReuseTypeNode(context, node)) {
const { introducesError, node: newName } = resolver.trackExistingEntityName(context, node.typeName);
const typeArguments = visitNodes(node.typeArguments, visitExistingNodeTreeSymbols, isTypeNode);
if (!introducesError) {
const updated = factory.updateTypeReferenceNode(
node,
newName,
typeArguments,
);
return resolver.markNodeReuse(context, updated, node);
}
else {
const serializedName = resolver.serializeTypeName(context, node.typeName, /*isTypeOf*/ false, typeArguments);
if (serializedName) {
return resolver.markNodeReuse(context, serializedName, node.typeName);
}
}
}
}
function visitExistingNodeTreeSymbolsWorker(node: Node): Node | undefined {
if (isJSDocTypeExpression(node)) {
// Unwrap JSDocTypeExpressions
return visitNode(node.type, visitExistingNodeTreeSymbols, isTypeNode);
}
// We don't _actually_ support jsdoc namepath types, emit `any` instead
if (isJSDocAllType(node) || node.kind === SyntaxKind.JSDocNamepathType) {
return factory.createKeywordTypeNode(SyntaxKind.AnyKeyword);
}
if (isJSDocUnknownType(node)) {
return factory.createKeywordTypeNode(SyntaxKind.UnknownKeyword);
}
if (isJSDocNullableType(node)) {
return factory.createUnionTypeNode([visitNode(node.type, visitExistingNodeTreeSymbols, isTypeNode)!, factory.createLiteralTypeNode(factory.createNull())]);
}
if (isJSDocOptionalType(node)) {
return factory.createUnionTypeNode([visitNode(node.type, visitExistingNodeTreeSymbols, isTypeNode)!, factory.createKeywordTypeNode(SyntaxKind.UndefinedKeyword)]);
}
if (isJSDocNonNullableType(node)) {
return visitNode(node.type, visitExistingNodeTreeSymbols);
}
if (isJSDocVariadicType(node)) {
return factory.createArrayTypeNode(visitNode(node.type, visitExistingNodeTreeSymbols, isTypeNode)!);
}
if (isJSDocTypeLiteral(node)) {
return factory.createTypeLiteralNode(map(node.jsDocPropertyTags, t => {
const name = visitNode(isIdentifier(t.name) ? t.name : t.name.right, visitExistingNodeTreeSymbols, isIdentifier)!;
const overrideTypeNode = resolver.getJsDocPropertyOverride(context, node, t);
return factory.createPropertySignature(
/*modifiers*/ undefined,
name,
t.isBracketed || t.typeExpression && isJSDocOptionalType(t.typeExpression.type) ? factory.createToken(SyntaxKind.QuestionToken) : undefined,
overrideTypeNode || (t.typeExpression && visitNode(t.typeExpression.type, visitExistingNodeTreeSymbols, isTypeNode)) || factory.createKeywordTypeNode(SyntaxKind.AnyKeyword),
);
}));
}
if (isTypeReferenceNode(node) && isIdentifier(node.typeName) && node.typeName.escapedText === "") {
return setOriginalNode(factory.createKeywordTypeNode(SyntaxKind.AnyKeyword), node);
}
if ((isExpressionWithTypeArguments(node) || isTypeReferenceNode(node)) && isJSDocIndexSignature(node)) {
return factory.createTypeLiteralNode([factory.createIndexSignature(
/*modifiers*/ undefined,
[factory.createParameterDeclaration(
/*modifiers*/ undefined,
/*dotDotDotToken*/ undefined,
"x",
/*questionToken*/ undefined,
visitNode(node.typeArguments![0], visitExistingNodeTreeSymbols, isTypeNode),
)],
visitNode(node.typeArguments![1], visitExistingNodeTreeSymbols, isTypeNode),
)]);
}
if (isJSDocFunctionType(node)) {
if (isJSDocConstructSignature(node)) {
let newTypeNode: TypeNode | undefined;
return factory.createConstructorTypeNode(
/*modifiers*/ undefined,
visitNodes(node.typeParameters, visitExistingNodeTreeSymbols, isTypeParameterDeclaration),
mapDefined(node.parameters, (p, i) =>
p.name && isIdentifier(p.name) && p.name.escapedText === "new" ? (newTypeNode = p.type, undefined) : factory.createParameterDeclaration(
/*modifiers*/ undefined,
getEffectiveDotDotDotForParameter(p),
resolver.markNodeReuse(context, factory.createIdentifier(getNameForJSDocFunctionParameter(p, i)), p),
factory.cloneNode(p.questionToken),
visitNode(p.type, visitExistingNodeTreeSymbols, isTypeNode),
/*initializer*/ undefined,
)),
visitNode(newTypeNode || node.type, visitExistingNodeTreeSymbols, isTypeNode) || factory.createKeywordTypeNode(SyntaxKind.AnyKeyword),
);
}
else {
return factory.createFunctionTypeNode(
visitNodes(node.typeParameters, visitExistingNodeTreeSymbols, isTypeParameterDeclaration),
map(node.parameters, (p, i) =>
factory.createParameterDeclaration(
/*modifiers*/ undefined,
getEffectiveDotDotDotForParameter(p),
resolver.markNodeReuse(context, factory.createIdentifier(getNameForJSDocFunctionParameter(p, i)), p),
factory.cloneNode(p.questionToken),
visitNode(p.type, visitExistingNodeTreeSymbols, isTypeNode),
/*initializer*/ undefined,
)),
visitNode(node.type, visitExistingNodeTreeSymbols, isTypeNode) || factory.createKeywordTypeNode(SyntaxKind.AnyKeyword),
);
}
}
if (isThisTypeNode(node)) {
if (resolver.canReuseTypeNode(context, node)) {
return node;
}
markError();
return node;
}
if (isTypeParameterDeclaration(node)) {
const { node: newName } = resolver.trackExistingEntityName(context, node.name);
return factory.updateTypeParameterDeclaration(
node,
visitNodes(node.modifiers, visitExistingNodeTreeSymbols, isModifier),
// resolver.markNodeReuse(context, typeParameterToName(getDeclaredTypeOfSymbol(getSymbolOfDeclaration(node)), context), node),
newName,
visitNode(node.constraint, visitExistingNodeTreeSymbols, isTypeNode),
visitNode(node.default, visitExistingNodeTreeSymbols, isTypeNode),
);
}
if (isIndexedAccessTypeNode(node)) {
const result = tryVisitIndexedAccess(node);
if (!result) {
markError();
return node;
}
return result;
}
if (isTypeReferenceNode(node)) {
const result = tryVisitTypeReference(node);
if (result) {
return result;
}
markError();
return node;
}
if (isLiteralImportTypeNode(node)) {
// assert keyword in imported attributes is deprecated, so we don't reuse types that contain it
// Ex: import("pkg", { assert: {} }
if (node.attributes?.token === SyntaxKind.AssertKeyword) {
markError();
return node;
}
if (!resolver.canReuseTypeNode(context, node)) {
return resolver.serializeExistingTypeNode(context, node);
}
const specifier = rewriteModuleSpecifier(node, node.argument.literal);
const literal = specifier === node.argument.literal ? reuseNode(context, node.argument.literal) : specifier;
return factory.updateImportTypeNode(
node,
literal === node.argument.literal ? reuseNode(context, node.argument) : factory.createLiteralTypeNode(literal),
visitNode(node.attributes, visitExistingNodeTreeSymbols, isImportAttributes),
visitNode(node.qualifier, visitExistingNodeTreeSymbols, isEntityName),
visitNodes(node.typeArguments, visitExistingNodeTreeSymbols, isTypeNode),
node.isTypeOf,
);
}
if (isNamedDeclaration(node) && node.name.kind === SyntaxKind.ComputedPropertyName && !resolver.hasLateBindableName(node)) {
if (!hasDynamicName(node)) {
return visitEachChild(node, visitExistingNodeTreeSymbols);
}
if (resolver.shouldRemoveDeclaration(context, node)) {
return undefined;
}
}
if (
(isFunctionLike(node) && !node.type)
|| (isPropertyDeclaration(node) && !node.type && !node.initializer)
|| (isPropertySignature(node) && !node.type && !node.initializer)
|| (isParameter(node) && !node.type && !node.initializer)
) {
let visited = visitEachChild(node, visitExistingNodeTreeSymbols);
if (visited === node) {
visited = resolver.markNodeReuse(context, factory.cloneNode(node), node);
}
(visited as Mutable<typeof visited>).type = factory.createKeywordTypeNode(SyntaxKind.AnyKeyword);
if (isParameter(node)) {
(visited as Mutable<ParameterDeclaration>).modifiers = undefined;
}
return visited;
}
if (isTypeQueryNode(node)) {
const result = tryVisitTypeQuery(node);
if (!result) {
markError();
return node;
}
return result;
}
if (isComputedPropertyName(node) && isEntityNameExpression(node.expression)) {
const { node: result, introducesError } = resolver.trackExistingEntityName(context, node.expression);
if (!introducesError) {
return factory.updateComputedPropertyName(node, result);
}
else {
const computedPropertyNameType = resolver.serializeTypeOfExpression(context, node.expression);
let literal;
if (isLiteralTypeNode(computedPropertyNameType)) {
literal = computedPropertyNameType.literal;
}
else {
const evaluated = resolver.evaluateEntityNameExpression(node.expression);
const literalNode = typeof evaluated.value === "string" ? factory.createStringLiteral(evaluated.value, /*isSingleQuote*/ undefined) :
typeof evaluated.value === "number" ? factory.createNumericLiteral(evaluated.value, /*numericLiteralFlags*/ 0) :
undefined;
if (!literalNode) {
if (isImportTypeNode(computedPropertyNameType)) {
resolver.trackComputedName(context, node.expression);
}
return node;
}
literal = literalNode;
}
if (literal.kind === SyntaxKind.StringLiteral && isIdentifierText(literal.text, getEmitScriptTarget(options))) {
return factory.createIdentifier(literal.text);
}
if (literal.kind === SyntaxKind.NumericLiteral && !literal.text.startsWith("-")) {
return literal;
}
return factory.updateComputedPropertyName(node, literal);
}
}
if (isTypePredicateNode(node)) {
let parameterName;
if (isIdentifier(node.parameterName)) {
const { node: result, introducesError } = resolver.trackExistingEntityName(context, node.parameterName);
// Should not usually happen the only case is when a type predicate comes from a JSDoc type annotation with it's own parameter symbol definition.
// /** @type {(v: unknown) => v is undefined} */
// const isUndef = v => v === undefined;
if (introducesError) markError();
parameterName = result;
}
else {
parameterName = factory.cloneNode(node.parameterName);
}
return factory.updateTypePredicateNode(node, factory.cloneNode(node.assertsModifier), parameterName, visitNode(node.type, visitExistingNodeTreeSymbols, isTypeNode));
}
if (isTupleTypeNode(node) || isTypeLiteralNode(node) || isMappedTypeNode(node)) {
const visited = visitEachChild(node, visitExistingNodeTreeSymbols);
const clone = resolver.markNodeReuse(context, visited === node ? factory.cloneNode(node) : visited, node);
const flags = getEmitFlags(clone);
setEmitFlags(clone, flags | (context.flags & NodeBuilderFlags.MultilineObjectLiterals && isTypeLiteralNode(node) ? 0 : EmitFlags.SingleLine));
return clone;
}
if (isStringLiteral(node) && !!(context.flags & NodeBuilderFlags.UseSingleQuotesForStringLiteralType) && !node.singleQuote) {
const clone = factory.cloneNode(node);
(clone as Mutable<typeof clone>).singleQuote = true;
return clone;
}
if (isConditionalTypeNode(node)) {
const checkType = visitNode(node.checkType, visitExistingNodeTreeSymbols, isTypeNode)!;
const disposeScope = resolver.enterNewScope(context, node);
const extendType = visitNode(node.extendsType, visitExistingNodeTreeSymbols, isTypeNode)!;
const trueType = visitNode(node.trueType, visitExistingNodeTreeSymbols, isTypeNode)!;
disposeScope();
const falseType = visitNode(node.falseType, visitExistingNodeTreeSymbols, isTypeNode)!;
return factory.updateConditionalTypeNode(
node,
checkType,
extendType,
trueType,
falseType,
);
}
if (isTypeOperatorNode(node)) {
if (node.operator === SyntaxKind.UniqueKeyword && node.type.kind === SyntaxKind.SymbolKeyword) {
if (!resolver.canReuseTypeNode(context, node)) {
markError();
return node;
}
}
else if (node.operator === SyntaxKind.KeyOfKeyword) {
const result = tryVisitKeyOf(node);
if (!result) {
markError();
return node;
}
return result;
}
}
return visitEachChild(node, visitExistingNodeTreeSymbols);
function visitEachChild<T extends Node>(node: T, visitor: Visitor): T;
function visitEachChild<T extends Node>(node: T | undefined, visitor: Visitor): T | undefined;
function visitEachChild<T extends Node>(node: T | undefined, visitor: Visitor): T | undefined {
const nonlocalNode = !context.enclosingFile || context.enclosingFile !== getSourceFileOfNode(node);
return visitEachChildWorker(node, visitor, /*context*/ undefined, nonlocalNode ? visitNodesWithoutCopyingPositions : undefined);
}
function visitNodesWithoutCopyingPositions(
nodes: NodeArray<Node> | undefined,
visitor: Visitor,
test?: (node: Node) => boolean,
start?: number,
count?: number,
): NodeArray<Node> | undefined {
let result = visitNodes(nodes, visitor, test, start, count);
if (result) {
if (result.pos !== -1 || result.end !== -1) {
if (result === nodes) {
result = factory.createNodeArray(nodes.slice(), nodes.hasTrailingComma);
}
setTextRangePosEnd(result, -1, -1);
}
}
return result;
}
function getEffectiveDotDotDotForParameter(p: ParameterDeclaration) {
return p.dotDotDotToken || (p.type && isJSDocVariadicType(p.type) ? factory.createToken(SyntaxKind.DotDotDotToken) : undefined);
}
/** Note that `new:T` parameters are not handled, but should be before calling this function. */
function getNameForJSDocFunctionParameter(p: ParameterDeclaration, index: number) {
return p.name && isIdentifier(p.name) && p.name.escapedText === "this" ? "this"
: getEffectiveDotDotDotForParameter(p) ? `args`
: `arg${index}`;
}
function rewriteModuleSpecifier(parent: ImportTypeNode, lit: StringLiteral) {
const newName = resolver.getModuleSpecifierOverride(context, parent, lit);
return newName ? setOriginalNode(factory.createStringLiteral(newName), lit) : lit;
}
}
}
function serializeExistingTypeNode(typeNode: TypeNode, context: SyntacticTypeNodeBuilderContext, addUndefined?: boolean): TypeNode;
function serializeExistingTypeNode(typeNode: TypeNode | undefined, context: SyntacticTypeNodeBuilderContext, addUndefined?: boolean): TypeNode | undefined;
function serializeExistingTypeNode(typeNode: TypeNode | undefined, context: SyntacticTypeNodeBuilderContext, addUndefined?: boolean): TypeNode | undefined {
if (!typeNode) return undefined;
let result;
if (
(!addUndefined || canAddUndefined(typeNode)) && resolver.canReuseTypeNode(context, typeNode)
) {
result = tryReuseExistingTypeNode(context, typeNode);
if (result !== undefined) {
result = addUndefinedIfNeeded(result, addUndefined, /*owner*/ undefined, context);
}
}
return result;
}
function serializeTypeAnnotationOfDeclaration(declaredType: TypeNode | undefined, context: SyntacticTypeNodeBuilderContext, node: Declaration, symbol: Symbol | undefined, requiresAddingUndefined?: boolean, useFallback = requiresAddingUndefined !== undefined) {
if (!declaredType) return undefined;
if (!resolver.canReuseTypeNodeAnnotation(context, node, declaredType, symbol, requiresAddingUndefined)) {
// If we need to add undefined, can add undefined, and the resolver says we can reuse the type, we reuse the type
// If we don't know syntactically that we can add the undefined, we will report the fallback below.
if (!requiresAddingUndefined || !resolver.canReuseTypeNodeAnnotation(context, node, declaredType, symbol, /*requiresAddingUndefined*/ false)) {
return undefined;
}
}
let result;
if (!requiresAddingUndefined || canAddUndefined(declaredType)) {
result = serializeExistingTypeNode(declaredType, context, requiresAddingUndefined);
}
if (result !== undefined || !useFallback) {
return result;
}
context.tracker.reportInferenceFallback(node);
return resolver.serializeExistingTypeNode(context, declaredType, requiresAddingUndefined) ?? factory.createKeywordTypeNode(SyntaxKind.AnyKeyword);
}
function serializeExistingTypeNodeWithFallback(typeNode: TypeNode | undefined, context: SyntacticTypeNodeBuilderContext, addUndefined?: boolean, targetNode?: Node) {
if (!typeNode) return undefined;
const result = serializeExistingTypeNode(typeNode, context, addUndefined);
if (result !== undefined) {
return result;
}
context.tracker.reportInferenceFallback(targetNode ?? typeNode);
return resolver.serializeExistingTypeNode(context, typeNode, addUndefined) ?? factory.createKeywordTypeNode(SyntaxKind.AnyKeyword);
}
function serializeTypeOfAccessor(accessor: AccessorDeclaration, symbol: Symbol | undefined, context: SyntacticTypeNodeBuilderContext) {
return typeFromAccessor(accessor, symbol, context) ?? inferAccessorType(accessor, resolver.getAllAccessorDeclarations(accessor), context, symbol);
}
function serializeTypeOfExpression(expr: Expression, context: SyntacticTypeNodeBuilderContext, addUndefined?: boolean, preserveLiterals?: boolean) {
const result = typeFromExpression(expr, context, /*isConstContext*/ false, addUndefined, preserveLiterals);
return result.type !== undefined ? result.type : inferExpressionType(expr, context, result.reportFallback);
}
function serializeTypeOfDeclaration(node: HasInferredType, symbol: Symbol, context: SyntacticTypeNodeBuilderContext) {
switch (node.kind) {
case SyntaxKind.Parameter:
case SyntaxKind.JSDocParameterTag:
return typeFromParameter(node, symbol, context);
case SyntaxKind.VariableDeclaration:
return typeFromVariable(node, symbol, context);
case SyntaxKind.PropertySignature:
case SyntaxKind.JSDocPropertyTag:
case SyntaxKind.PropertyDeclaration:
return typeFromProperty(node, symbol, context);
case SyntaxKind.BindingElement:
return inferTypeOfDeclaration(node, symbol, context);
case SyntaxKind.ExportAssignment:
return serializeTypeOfExpression(node.expression, context, /*addUndefined*/ undefined, /*preserveLiterals*/ true);
case SyntaxKind.PropertyAccessExpression:
case SyntaxKind.ElementAccessExpression:
case SyntaxKind.BinaryExpression:
return typeFromExpandoProperty(node, symbol, context);
case SyntaxKind.PropertyAssignment:
case SyntaxKind.ShorthandPropertyAssignment:
return typeFromPropertyAssignment(node, symbol, context);
default:
Debug.assertNever(node, `Node needs to be an inferrable node, found ${Debug.formatSyntaxKind((node as Node).kind)}`);
}
}
function typeFromPropertyAssignment(node: PropertyAssignment | ShorthandPropertyAssignment, symbol: Symbol, context: SyntacticTypeNodeBuilderContext) {
const typeAnnotation = getEffectiveTypeAnnotationNode(node);
let result;
if (typeAnnotation && resolver.canReuseTypeNodeAnnotation(context, node, typeAnnotation, symbol)) {
result = serializeExistingTypeNode(typeAnnotation, context);
}
if (!result && node.kind === SyntaxKind.PropertyAssignment) {
const initializer = node.initializer;
const assertionNode = isJSDocTypeAssertion(initializer) ? getJSDocTypeAssertionType(initializer) :
initializer.kind === SyntaxKind.AsExpression || initializer.kind === SyntaxKind.TypeAssertionExpression ? (initializer as AsExpression | TypeAssertion).type :
undefined;
if (assertionNode && !isConstTypeReference(assertionNode) && resolver.canReuseTypeNodeAnnotation(context, node, assertionNode, symbol)) {
result = serializeExistingTypeNode(assertionNode, context);
}
}
return result ?? inferTypeOfDeclaration(node, symbol, context, /*reportFallback*/ false);
}
function serializeReturnTypeForSignature(node: SignatureDeclaration | JSDocSignature, symbol: Symbol, context: SyntacticTypeNodeBuilderContext) {
switch (node.kind) {
case SyntaxKind.GetAccessor:
return serializeTypeOfAccessor(node, symbol, context);
case SyntaxKind.MethodDeclaration:
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.ConstructSignature:
case SyntaxKind.MethodSignature:
case SyntaxKind.CallSignature:
case SyntaxKind.Constructor:
case SyntaxKind.SetAccessor:
case SyntaxKind.IndexSignature:
case SyntaxKind.FunctionType:
case SyntaxKind.ConstructorType:
case SyntaxKind.FunctionExpression:
case SyntaxKind.ArrowFunction:
case SyntaxKind.JSDocFunctionType:
case SyntaxKind.JSDocSignature:
return createReturnFromSignature(node, symbol, context);
default:
Debug.assertNever(node, `Node needs to be an inferrable node, found ${Debug.formatSyntaxKind((node as Node).kind)}`);
}
}
function getTypeAnnotationFromAccessor(accessor: AccessorDeclaration): TypeNode | undefined {
if (accessor) {
return accessor.kind === SyntaxKind.GetAccessor
? (isInJSFile(accessor) && getJSDocType(accessor)) || getEffectiveReturnTypeNode(accessor)
: getEffectiveSetAccessorTypeAnnotationNode(accessor);
}
}
function getTypeAnnotationFromAllAccessorDeclarations(node: AccessorDeclaration, accessors: AllAccessorDeclarations) {
let accessorType = getTypeAnnotationFromAccessor(node);
if (!accessorType && node !== accessors.firstAccessor) {
accessorType = getTypeAnnotationFromAccessor(accessors.firstAccessor);
}
if (!accessorType && accessors.secondAccessor && node !== accessors.secondAccessor) {
accessorType = getTypeAnnotationFromAccessor(accessors.secondAccessor);
}
return accessorType;
}
function typeFromAccessor(node: AccessorDeclaration, symbol: Symbol | undefined, context: SyntacticTypeNodeBuilderContext): TypeNode | undefined {
const accessorDeclarations = resolver.getAllAccessorDeclarations(node);
const accessorType = getTypeAnnotationFromAllAccessorDeclarations(node, accessorDeclarations);
if (accessorType && !isTypePredicateNode(accessorType)) {
return withNewScope(context, node, () => serializeTypeAnnotationOfDeclaration(accessorType, context, node, symbol) ?? inferTypeOfDeclaration(node, symbol, context));
}
if (accessorDeclarations.getAccessor) {
return withNewScope(context, accessorDeclarations.getAccessor, () => createReturnFromSignature(accessorDeclarations.getAccessor!, symbol, context));
}
return undefined;
}
function typeFromVariable(node: VariableDeclaration, symbol: Symbol, context: SyntacticTypeNodeBuilderContext): TypeNode | undefined {
const declaredType = getEffectiveTypeAnnotationNode(node);
let resultType = failed;
if (declaredType) {
resultType = syntacticResult(serializeTypeAnnotationOfDeclaration(declaredType, context, node, symbol));
}
else if (node.initializer && (symbol.declarations?.length === 1 || countWhere(symbol.declarations, isVariableDeclaration) === 1)) {
if (!resolver.isExpandoFunctionDeclaration(node) && !isContextuallyTyped(node)) {
resultType = typeFromExpression(node.initializer, context, /*isConstContext*/ undefined, /*requiresAddingUndefined*/ undefined, isVarConstLike(node));
}
}
return resultType.type !== undefined ? resultType.type : inferTypeOfDeclaration(node, symbol, context, resultType.reportFallback);
}
function typeFromParameter(node: ParameterDeclaration | JSDocParameterTag, symbol: Symbol | undefined, context: SyntacticTypeNodeBuilderContext): TypeNode | undefined {
const parent = node.parent;
if (parent.kind === SyntaxKind.SetAccessor) {
return serializeTypeOfAccessor(parent, /*symbol*/ undefined, context);
}
const declaredType = getEffectiveTypeAnnotationNode(node);
const addUndefined = resolver.requiresAddingImplicitUndefined(node, symbol, context.enclosingDeclaration);
let resultType = failed;
if (declaredType) {
resultType = syntacticResult(serializeTypeAnnotationOfDeclaration(declaredType, context, node, symbol, addUndefined));
}
else if (isParameter(node) && node.initializer && isIdentifier(node.name) && !isContextuallyTyped(node)) {
resultType = typeFromExpression(node.initializer, context, /*isConstContext*/ undefined, addUndefined);
}
return resultType.type !== undefined ? resultType.type : inferTypeOfDeclaration(node, symbol, context, resultType.reportFallback);
}
/**
* While expando poperies are errors in TSC, in JS we try to extract the type from the binary expression;
*/
function typeFromExpandoProperty(node: PropertyAccessExpression | BinaryExpression | ElementAccessExpression, symbol: Symbol, context: SyntacticTypeNodeBuilderContext) {
const declaredType = getEffectiveTypeAnnotationNode(node);
let result;
if (declaredType) {
result = serializeTypeAnnotationOfDeclaration(declaredType, context, node, symbol);
}
const oldSuppressReportInferenceFallback = context.suppressReportInferenceFallback;
context.suppressReportInferenceFallback = true;
const resultType = result ?? inferTypeOfDeclaration(node, symbol, context, /*reportFallback*/ false);
context.suppressReportInferenceFallback = oldSuppressReportInferenceFallback;
return resultType;
}
function typeFromProperty(node: PropertyDeclaration | PropertySignature | JSDocPropertyTag, symbol: Symbol, context: SyntacticTypeNodeBuilderContext) {
const declaredType = getEffectiveTypeAnnotationNode(node);
const requiresAddingUndefined = resolver.requiresAddingImplicitUndefined(node, symbol, context.enclosingDeclaration);
let resultType = failed;
if (declaredType) {
resultType = syntacticResult(serializeTypeAnnotationOfDeclaration(declaredType, context, node, symbol, requiresAddingUndefined));
}
else {
const initializer = isPropertyDeclaration(node) ? node.initializer : undefined;
if (initializer && !isContextuallyTyped(node)) {
const isReadonly = isDeclarationReadonly(node);
resultType = typeFromExpression(initializer, context, /*isConstContext*/ undefined, requiresAddingUndefined, isReadonly);
}
}
return resultType.type !== undefined ? resultType.type : inferTypeOfDeclaration(node, symbol, context, resultType.reportFallback);
}
function inferTypeOfDeclaration(
node: HasInferredType | GetAccessorDeclaration | SetAccessorDeclaration,
symbol: Symbol | undefined,
context: SyntacticTypeNodeBuilderContext,
reportFallback = true,
) {
if (reportFallback) {
context.tracker.reportInferenceFallback(node);
}
if (context.noInferenceFallback === true) {
return factory.createKeywordTypeNode(SyntaxKind.AnyKeyword);
}
return resolver.serializeTypeOfDeclaration(context, node, symbol);
}
function inferExpressionType(node: Expression, context: SyntacticTypeNodeBuilderContext, reportFallback = true, requiresAddingUndefined?: boolean) {
Debug.assert(!requiresAddingUndefined);
if (reportFallback) {
context.tracker.reportInferenceFallback(node);
}
if (context.noInferenceFallback === true) {
return factory.createKeywordTypeNode(SyntaxKind.AnyKeyword);
}
return resolver.serializeTypeOfExpression(context, node) ?? factory.createKeywordTypeNode(SyntaxKind.AnyKeyword);
}
function inferReturnTypeOfSignatureSignature(node: SignatureDeclaration | JSDocSignature, context: SyntacticTypeNodeBuilderContext, symbol: Symbol | undefined, reportFallback: boolean) {
if (reportFallback) {
context.tracker.reportInferenceFallback(node);
}
if (context.noInferenceFallback === true) {
return factory.createKeywordTypeNode(SyntaxKind.AnyKeyword);
}
return resolver.serializeReturnTypeForSignature(context, node, symbol) ?? factory.createKeywordTypeNode(SyntaxKind.AnyKeyword);
}
function inferAccessorType(node: GetAccessorDeclaration | SetAccessorDeclaration, allAccessors: AllAccessorDeclarations, context: SyntacticTypeNodeBuilderContext, symbol: Symbol | undefined, reportFallback: boolean = true): TypeNode | undefined {
if (node.kind === SyntaxKind.GetAccessor) {
return createReturnFromSignature(node, symbol, context, reportFallback);
}
else {
if (reportFallback) {
context.tracker.reportInferenceFallback(node);
}
const result = allAccessors.getAccessor && createReturnFromSignature(allAccessors.getAccessor, symbol, context, reportFallback);
return result ?? resolver.serializeTypeOfDeclaration(context, node, symbol) ?? factory.createKeywordTypeNode(SyntaxKind.AnyKeyword);
}
}
function withNewScope<R>(context: SyntacticTypeNodeBuilderContext, node: IntroducesNewScopeNode | ConditionalTypeNode, fn: () => R) {
const cleanup = resolver.enterNewScope(context, node);
const result = fn();
cleanup();
return result;
}
function typeFromTypeAssertion(expression: Expression, type: TypeNode, context: SyntacticTypeNodeBuilderContext, requiresAddingUndefined: boolean): SyntacticResult {
if (isConstTypeReference(type)) {
return typeFromExpression(expression, context, /*isConstContext*/ true, requiresAddingUndefined);
}
return syntacticResult(serializeExistingTypeNodeWithFallback(type, context, requiresAddingUndefined));
}
function typeFromExpression(node: Expression | JsxAttributeValue, context: SyntacticTypeNodeBuilderContext, isConstContext = false, requiresAddingUndefined = false, preserveLiterals = false): SyntacticResult {
switch (node.kind) {
case SyntaxKind.ParenthesizedExpression:
if (isJSDocTypeAssertion(node)) {
return typeFromTypeAssertion(node.expression, getJSDocTypeAssertionType(node), context, requiresAddingUndefined);
}
return typeFromExpression((node as ParenthesizedExpression).expression, context, isConstContext, requiresAddingUndefined);
case SyntaxKind.Identifier:
if (resolver.isUndefinedIdentifierExpression(node as Identifier)) {
return syntacticResult(createUndefinedTypeNode());
}
break;
case SyntaxKind.NullKeyword:
if (strictNullChecks) {
return syntacticResult(addUndefinedIfNeeded(factory.createLiteralTypeNode(factory.createNull()), requiresAddingUndefined, node, context));
}
else {
return syntacticResult(factory.createKeywordTypeNode(SyntaxKind.AnyKeyword));
}
case SyntaxKind.ArrowFunction:
case SyntaxKind.FunctionExpression:
Debug.type<ArrowFunction | FunctionExpression>(node);
return withNewScope(context, node, () => typeFromFunctionLikeExpression(node, context));
case SyntaxKind.TypeAssertionExpression:
case SyntaxKind.AsExpression:
const asExpression = node as AsExpression | TypeAssertion;
return typeFromTypeAssertion(asExpression.expression, asExpression.type, context, requiresAddingUndefined);
case SyntaxKind.PrefixUnaryExpression:
const unaryExpression = node as PrefixUnaryExpression;
if (isPrimitiveLiteralValue(unaryExpression)) {
return typeFromPrimitiveLiteral(
unaryExpression.operator === SyntaxKind.PlusToken ? unaryExpression.operand : unaryExpression,
unaryExpression.operand.kind === SyntaxKind.BigIntLiteral ? SyntaxKind.BigIntKeyword : SyntaxKind.NumberKeyword,
context,
isConstContext || preserveLiterals,
requiresAddingUndefined,
);
}
break;
case SyntaxKind.ArrayLiteralExpression:
return typeFromArrayLiteral(node as ArrayLiteralExpression, context, isConstContext, requiresAddingUndefined);
case SyntaxKind.ObjectLiteralExpression:
return typeFromObjectLiteral(node as ObjectLiteralExpression, context, isConstContext, requiresAddingUndefined);
case SyntaxKind.ClassExpression:
return syntacticResult(inferExpressionType(node as ClassExpression, context, /*reportFallback*/ true, requiresAddingUndefined));
case SyntaxKind.TemplateExpression:
if (!isConstContext && !preserveLiterals) {
return syntacticResult(factory.createKeywordTypeNode(SyntaxKind.StringKeyword));
}
break;
default:
let typeKind: KeywordTypeSyntaxKind | undefined;
let primitiveNode = node as PrimitiveLiteral;
switch (node.kind) {
case SyntaxKind.NumericLiteral:
typeKind = SyntaxKind.NumberKeyword;
break;
case SyntaxKind.NoSubstitutionTemplateLiteral:
primitiveNode = factory.createStringLiteral((node as NoSubstitutionTemplateLiteral).text);
typeKind = SyntaxKind.StringKeyword;
break;
case SyntaxKind.StringLiteral:
typeKind = SyntaxKind.StringKeyword;
break;
case SyntaxKind.BigIntLiteral:
typeKind = SyntaxKind.BigIntKeyword;
break;
case SyntaxKind.TrueKeyword:
case SyntaxKind.FalseKeyword:
typeKind = SyntaxKind.BooleanKeyword;
break;
}
if (typeKind) {
return typeFromPrimitiveLiteral(primitiveNode, typeKind, context, isConstContext || preserveLiterals, requiresAddingUndefined);
}
}
return failed;
}
function typeFromFunctionLikeExpression(fnNode: FunctionExpression | ArrowFunction, context: SyntacticTypeNodeBuilderContext) {
const returnType = createReturnFromSignature(fnNode, /*symbol*/ undefined, context);
const typeParameters = reuseTypeParameters(fnNode.typeParameters, context);
const parameters = fnNode.parameters.map(p => ensureParameter(p, context));
return syntacticResult(
factory.createFunctionTypeNode(
typeParameters,
parameters,
returnType,
),
);
}
function canGetTypeFromArrayLiteral(arrayLiteral: ArrayLiteralExpression, context: SyntacticTypeNodeBuilderContext, isConstContext: boolean) {
if (!isConstContext) {
context.tracker.reportInferenceFallback(arrayLiteral);
return false;
}
for (const element of arrayLiteral.elements) {
if (element.kind === SyntaxKind.SpreadElement) {
context.tracker.reportInferenceFallback(element);
return false;
}
}
return true;
}
function typeFromArrayLiteral(arrayLiteral: ArrayLiteralExpression, context: SyntacticTypeNodeBuilderContext, isConstContext: boolean, requiresAddingUndefined: boolean): SyntacticResult {
if (!canGetTypeFromArrayLiteral(arrayLiteral, context, isConstContext)) {
if (requiresAddingUndefined || isDeclaration(walkUpParenthesizedExpressions(arrayLiteral).parent)) {
return alreadyReported;
}
return syntacticResult(inferExpressionType(arrayLiteral, context, /*reportFallback*/ false, requiresAddingUndefined));
}
// Disable any inference fallback since we won't actually use the resulting type and we don't want to generate errors
const oldNoInferenceFallback = context.noInferenceFallback;
context.noInferenceFallback = true;
const elementTypesInfo: TypeNode[] = [];
for (const element of arrayLiteral.elements) {
Debug.assert(element.kind !== SyntaxKind.SpreadElement);
if (element.kind === SyntaxKind.OmittedExpression) {
elementTypesInfo.push(
createUndefinedTypeNode(),
);
}
else {
const expressionType = typeFromExpression(element, context, isConstContext);
const elementType = expressionType.type !== undefined ? expressionType.type : inferExpressionType(element, context, expressionType.reportFallback);
elementTypesInfo.push(elementType);
}
}
const tupleType = factory.createTupleTypeNode(elementTypesInfo);
tupleType.emitNode = { flags: 1, autoGenerate: undefined, internalFlags: 0 };
context.noInferenceFallback = oldNoInferenceFallback;
return notImplemented;
}
function canGetTypeFromObjectLiteral(objectLiteral: ObjectLiteralExpression, context: SyntacticTypeNodeBuilderContext) {
let result = true;
for (const prop of objectLiteral.properties) {
if (prop.flags & NodeFlags.ThisNodeHasError) {
result = false;
break; // Bail if parse errors
}
if (prop.kind === SyntaxKind.ShorthandPropertyAssignment || prop.kind === SyntaxKind.SpreadAssignment) {
context.tracker.reportInferenceFallback(prop);
result = false;
}
else if (prop.name.flags & NodeFlags.ThisNodeHasError) {
result = false;
break; // Bail if parse errors
}
else if (prop.name.kind === SyntaxKind.PrivateIdentifier) {
// Not valid in object literals but the compiler will complain about this, we just ignore it here.
result = false;
}
else if (prop.name.kind === SyntaxKind.ComputedPropertyName) {
const expression = prop.name.expression;
if (!isPrimitiveLiteralValue(expression, /*includeBigInt*/ false) && !resolver.isDefinitelyReferenceToGlobalSymbolObject(expression)) {
context.tracker.reportInferenceFallback(prop.name);
result = false;
}
}
}
return result;
}
function typeFromObjectLiteral(objectLiteral: ObjectLiteralExpression, context: SyntacticTypeNodeBuilderContext, isConstContext: boolean, requiresAddingUndefined: boolean) {
if (!canGetTypeFromObjectLiteral(objectLiteral, context)) {
if (requiresAddingUndefined || isDeclaration(walkUpParenthesizedExpressions(objectLiteral).parent)) {
return alreadyReported;
}
return syntacticResult(inferExpressionType(objectLiteral, context, /*reportFallback*/ false, requiresAddingUndefined));
}
// Disable any inference fallback since we won't actually use the resulting type and we don't want to generate errors
const oldNoInferenceFallback = context.noInferenceFallback;
context.noInferenceFallback = true;
const properties: TypeElement[] = [];
const oldFlags = context.flags;
context.flags |= NodeBuilderFlags.InObjectTypeLiteral;
for (const prop of objectLiteral.properties) {
Debug.assert(!isShorthandPropertyAssignment(prop) && !isSpreadAssignment(prop));
const name = prop.name;
let newProp;
switch (prop.kind) {
case SyntaxKind.MethodDeclaration:
newProp = withNewScope(context, prop, () => typeFromObjectLiteralMethod(prop, name, context, isConstContext));
break;
case SyntaxKind.PropertyAssignment:
newProp = typeFromObjectLiteralPropertyAssignment(prop, name, context, isConstContext);
break;
case SyntaxKind.SetAccessor:
case SyntaxKind.GetAccessor:
newProp = typeFromObjectLiteralAccessor(prop, name, context);
break;
}
if (newProp) {
setCommentRange(newProp, prop);
properties.push(newProp);
}
}
context.flags = oldFlags;
const typeNode = factory.createTypeLiteralNode(properties);
if (!(context.flags & NodeBuilderFlags.MultilineObjectLiterals)) {
setEmitFlags(typeNode, EmitFlags.SingleLine);
}
context.noInferenceFallback = oldNoInferenceFallback;
return notImplemented;
}
function typeFromObjectLiteralPropertyAssignment(prop: PropertyAssignment, name: PropertyName, context: SyntacticTypeNodeBuilderContext, isConstContext: boolean) {
const modifiers = isConstContext ?
[factory.createModifier(SyntaxKind.ReadonlyKeyword)] :
[];
const expressionResult = typeFromExpression(prop.initializer, context, isConstContext);
const typeNode = expressionResult.type !== undefined ? expressionResult.type : inferTypeOfDeclaration(prop, /*symbol*/ undefined, context, expressionResult.reportFallback);
return factory.createPropertySignature(
modifiers,
reuseNode(context, name),
/*questionToken*/ undefined,
typeNode,
);
}
function ensureParameter(p: ParameterDeclaration, context: SyntacticTypeNodeBuilderContext) {
return factory.updateParameterDeclaration(
p,
/*modifiers*/ undefined,
reuseNode(context, p.dotDotDotToken),
resolver.serializeNameOfParameter(context, p),
resolver.isOptionalParameter(p) ? factory.createToken(SyntaxKind.QuestionToken) : undefined,
typeFromParameter(p, /*symbol*/ undefined, context), // Ignore private param props, since this type is going straight back into a param
/*initializer*/ undefined,
);
}
function reuseTypeParameters(typeParameters: NodeArray<TypeParameterDeclaration> | undefined, context: SyntacticTypeNodeBuilderContext) {
return typeParameters?.map(tp => {
const { node: tpName } = resolver.trackExistingEntityName(context, tp.name);
return factory.updateTypeParameterDeclaration(
tp,
tp.modifiers?.map(m => reuseNode(context, m)),
tpName,
serializeExistingTypeNodeWithFallback(tp.constraint, context),
serializeExistingTypeNodeWithFallback(tp.default, context),
);
});
}
function typeFromObjectLiteralMethod(method: MethodDeclaration, name: PropertyName, context: SyntacticTypeNodeBuilderContext, isConstContext: boolean) {
const returnType = createReturnFromSignature(method, /*symbol*/ undefined, context);
const typeParameters = reuseTypeParameters(method.typeParameters, context);
const parameters = method.parameters.map(p => ensureParameter(p, context));
if (isConstContext) {
return factory.createPropertySignature(
[factory.createModifier(SyntaxKind.ReadonlyKeyword)],
reuseNode(context, name),
reuseNode(context, method.questionToken),
factory.createFunctionTypeNode(
typeParameters,
parameters,
returnType,
),
);
}
else {
if (isIdentifier(name) && name.escapedText === "new") {
name = factory.createStringLiteral("new");
}
return factory.createMethodSignature(
[],
reuseNode(context, name),
reuseNode(context, method.questionToken),
typeParameters,
parameters,
returnType,
);
}
}
function typeFromObjectLiteralAccessor(accessor: GetAccessorDeclaration | SetAccessorDeclaration, name: PropertyName, context: SyntacticTypeNodeBuilderContext) {
const allAccessors = resolver.getAllAccessorDeclarations(accessor);
const getAccessorType = allAccessors.getAccessor && getTypeAnnotationFromAccessor(allAccessors.getAccessor);
const setAccessorType = allAccessors.setAccessor && getTypeAnnotationFromAccessor(allAccessors.setAccessor);
// We have types for both accessors, we can't know if they are the same type so we keep both accessors
if (getAccessorType !== undefined && setAccessorType !== undefined) {
return withNewScope(context, accessor, () => {
const parameters = accessor.parameters.map(p => ensureParameter(p, context));
if (isGetAccessor(accessor)) {
return factory.updateGetAccessorDeclaration(
accessor,
[],
reuseNode(context, name),
parameters,
serializeExistingTypeNodeWithFallback(getAccessorType, context),
/*body*/ undefined,
);
}
else {
return factory.updateSetAccessorDeclaration(
accessor,
[],
reuseNode(context, name),
parameters,
/*body*/ undefined,
);
}
});
}
else if (allAccessors.firstAccessor === accessor) {
const foundType = getAccessorType ? withNewScope(context, allAccessors.getAccessor!, () => serializeExistingTypeNodeWithFallback(getAccessorType, context)) :
setAccessorType ? withNewScope(context, allAccessors.setAccessor!, () => serializeExistingTypeNodeWithFallback(setAccessorType, context)) :
undefined;
const propertyType = foundType ?? inferAccessorType(accessor, allAccessors, context, /*symbol*/ undefined);
const propertySignature = factory.createPropertySignature(
allAccessors.setAccessor === undefined ? [factory.createModifier(SyntaxKind.ReadonlyKeyword)] : [],
reuseNode(context, name),
/*questionToken*/ undefined,
propertyType,
);
return propertySignature;
}
}
function createUndefinedTypeNode() {
if (strictNullChecks) {
return factory.createKeywordTypeNode(SyntaxKind.UndefinedKeyword);
}
else {
return factory.createKeywordTypeNode(SyntaxKind.AnyKeyword);
}
}
function typeFromPrimitiveLiteral(node: PrimitiveLiteral, baseType: KeywordTypeSyntaxKind, context: SyntacticTypeNodeBuilderContext, preserveLiterals: boolean, requiresAddingUndefined: boolean) {
let result;
if (preserveLiterals) {
if (node.kind === SyntaxKind.PrefixUnaryExpression && node.operator === SyntaxKind.PlusToken) {
result = factory.createLiteralTypeNode(reuseNode(context, node.operand));
}
result = factory.createLiteralTypeNode(reuseNode(context, node));
}
else {
result = factory.createKeywordTypeNode(baseType);
}
return syntacticResult(addUndefinedIfNeeded(result, requiresAddingUndefined, node, context));
}
function addUndefinedIfNeeded(node: TypeNode, addUndefined: boolean | undefined, owner: Node | undefined, context: SyntacticTypeNodeBuilderContext) {
const parentDeclaration = owner && walkUpParenthesizedExpressions(owner).parent;
const optionalDeclaration = parentDeclaration && isDeclaration(parentDeclaration) && isOptionalDeclaration(parentDeclaration);
if (!strictNullChecks || !(addUndefined || optionalDeclaration)) return node;
if (!canAddUndefined(node)) {
context.tracker.reportInferenceFallback(node);
}
if (isUnionTypeNode(node)) {
return factory.createUnionTypeNode([...node.types, factory.createKeywordTypeNode(SyntaxKind.UndefinedKeyword)]);
}
return factory.createUnionTypeNode([node, factory.createKeywordTypeNode(SyntaxKind.UndefinedKeyword)]);
}
function canAddUndefined(node: TypeNode): boolean {
if (!strictNullChecks) return true;
if (
isKeyword(node.kind)
|| node.kind === SyntaxKind.LiteralType
|| node.kind === SyntaxKind.FunctionType
|| node.kind === SyntaxKind.ConstructorType
|| node.kind === SyntaxKind.ArrayType
|| node.kind === SyntaxKind.TupleType
|| node.kind === SyntaxKind.TypeLiteral
|| node.kind === SyntaxKind.TemplateLiteralType
|| node.kind === SyntaxKind.ThisType
) {
return true;
}
if (node.kind === SyntaxKind.ParenthesizedType) {
return canAddUndefined((node as ParenthesizedTypeNode).type);
}
if (node.kind === SyntaxKind.UnionType || node.kind === SyntaxKind.IntersectionType) {
return (node as UnionTypeNode | IntersectionTypeNode).types.every(canAddUndefined);
}
return false;
}
function createReturnFromSignature(fn: SignatureDeclaration | JSDocSignature, symbol: Symbol | undefined, context: SyntacticTypeNodeBuilderContext, reportFallback: boolean = true): TypeNode {
let returnType = failed;
const returnTypeNode = isJSDocConstructSignature(fn) ? getEffectiveTypeAnnotationNode(fn.parameters[0]) : getEffectiveReturnTypeNode(fn);
if (returnTypeNode) {
returnType = syntacticResult(serializeTypeAnnotationOfDeclaration(returnTypeNode, context, fn, symbol));
}
else if (isValueSignatureDeclaration(fn)) {
returnType = typeFromSingleReturnExpression(fn, context);
}
return returnType.type !== undefined ? returnType.type : inferReturnTypeOfSignatureSignature(fn, context, symbol, reportFallback && returnType.reportFallback && !returnTypeNode);
}
function typeFromSingleReturnExpression(declaration: FunctionLikeDeclaration | undefined, context: SyntacticTypeNodeBuilderContext): SyntacticResult {
let candidateExpr: Expression | undefined;
if (declaration && !nodeIsMissing(declaration.body)) {
const flags = getFunctionFlags(declaration);
if (flags & FunctionFlags.AsyncGenerator) return failed;
const body = declaration.body;
if (body && isBlock(body)) {
forEachReturnStatement(body, s => {
if (s.parent !== body) {
candidateExpr = undefined;
return true;
}
if (!candidateExpr) {
candidateExpr = s.expression;
}
else {
candidateExpr = undefined;
return true;
}
});
}
else {
candidateExpr = body;
}
}
if (candidateExpr) {
if (isContextuallyTyped(candidateExpr)) {
const type = isJSDocTypeAssertion(candidateExpr) ? getJSDocTypeAssertionType(candidateExpr) :
isAsExpression(candidateExpr) || isTypeAssertionExpression(candidateExpr) ? candidateExpr.type :
undefined;
if (type && !isConstTypeReference(type)) {
return syntacticResult(serializeExistingTypeNode(type, context));
}
}
else {
return typeFromExpression(candidateExpr, context);
}
}
return failed;
}
function isContextuallyTyped(node: Node) {
return findAncestor(node.parent, n => {
// Functions calls or parent type annotations (but not the return type of a function expression) may impact the inferred type and local inference is unreliable
return isCallExpression(n) || (!isFunctionLikeDeclaration(n) && !!getEffectiveTypeAnnotationNode(n)) || isJsxElement(n) || isJsxExpression(n);
});
}
}