TypeScript/src/compiler/expressionToTypeNode.ts
Isabel Duan 52eaa7b02f
Enable --isolatedDeclarations on TS codebase (#59635)
Co-authored-by: Sheetal Nandi <shkamat@microsoft.com>
Co-authored-by: Andrew Branch <andrew@wheream.io>
2024-09-15 18:10:52 -07:00

501 lines
23 KiB
TypeScript

import {
AccessorDeclaration,
AllAccessorDeclarations,
ArrayLiteralExpression,
ArrowFunction,
AsExpression,
BinaryExpression,
BindingElement,
ClassExpression,
CompilerOptions,
Debug,
ElementAccessExpression,
ExportAssignment,
Expression,
forEachReturnStatement,
FunctionExpression,
FunctionFlags,
FunctionLikeDeclaration,
GetAccessorDeclaration,
getEffectiveReturnTypeNode,
getEffectiveTypeAnnotationNode,
getFunctionFlags,
getJSDocTypeAssertionType,
getStrictOptionValue,
HasInferredType,
Identifier,
IntersectionTypeNode,
isBlock,
isConstTypeReference,
isDeclarationReadonly,
isGetAccessor,
isIdentifier,
isJSDocTypeAssertion,
isKeyword,
isPrimitiveLiteralValue,
isShorthandPropertyAssignment,
isSpreadAssignment,
isValueSignatureDeclaration,
isVarConstLike,
JSDocSignature,
MethodDeclaration,
Node,
NodeArray,
NodeFlags,
nodeIsMissing,
ObjectLiteralExpression,
ParameterDeclaration,
ParenthesizedExpression,
ParenthesizedTypeNode,
PrefixUnaryExpression,
PropertyAccessExpression,
PropertyAssignment,
PropertyDeclaration,
PropertyName,
PropertySignature,
SetAccessorDeclaration,
SignatureDeclaration,
SyntacticNodeBuilder,
SyntacticTypeNodeBuilderContext,
SyntacticTypeNodeBuilderResolver,
SyntaxKind,
TypeAssertion,
TypeNode,
TypeParameterDeclaration,
UnionTypeNode,
VariableDeclaration,
} from "./_namespaces/ts.js";
/** @internal */
export function createSyntacticTypeNodeBuilder(
options: CompilerOptions,
resolver: SyntacticTypeNodeBuilderResolver,
): SyntacticNodeBuilder {
const strictNullChecks = getStrictOptionValue(options, "strictNullChecks");
return {
typeFromExpression,
serializeTypeOfDeclaration,
serializeReturnTypeForSignature,
serializeTypeOfExpression,
};
function serializeExistingTypeAnnotation(type: TypeNode | undefined, addUndefined?: boolean) {
return type !== undefined && (!addUndefined || (type && canAddUndefined(type))) ? true : undefined;
}
function serializeTypeOfExpression(expr: Expression, context: SyntacticTypeNodeBuilderContext, addUndefined?: boolean, preserveLiterals?: boolean) {
return typeFromExpression(expr, context, /*isConstContext*/ false, addUndefined, preserveLiterals) ?? inferExpressionType(expr, context);
}
function serializeTypeOfDeclaration(node: HasInferredType, context: SyntacticTypeNodeBuilderContext) {
switch (node.kind) {
case SyntaxKind.PropertySignature:
return serializeExistingTypeAnnotation(getEffectiveTypeAnnotationNode(node));
case SyntaxKind.Parameter:
return typeFromParameter(node, context);
case SyntaxKind.VariableDeclaration:
return typeFromVariable(node, context);
case SyntaxKind.PropertyDeclaration:
return typeFromProperty(node, context);
case SyntaxKind.BindingElement:
return inferTypeOfDeclaration(node, context);
case SyntaxKind.ExportAssignment:
return serializeTypeOfExpression(node.expression, context, /*addUndefined*/ undefined, /*preserveLiterals*/ true);
case SyntaxKind.PropertyAccessExpression:
case SyntaxKind.ElementAccessExpression:
case SyntaxKind.BinaryExpression:
return serializeExistingTypeAnnotation(getEffectiveTypeAnnotationNode(node)) || inferTypeOfDeclaration(node, context);
case SyntaxKind.PropertyAssignment:
return typeFromExpression(node.initializer, context) || inferTypeOfDeclaration(node, context);
default:
Debug.assertNever(node, `Node needs to be an inferrable node, found ${Debug.formatSyntaxKind((node as Node).kind)}`);
}
}
function serializeReturnTypeForSignature(node: SignatureDeclaration | JSDocSignature, context: SyntacticTypeNodeBuilderContext): boolean | undefined {
switch (node.kind) {
case SyntaxKind.GetAccessor:
return typeFromAccessor(node, 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, 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
? getEffectiveReturnTypeNode(accessor) // Getter - return type
: accessor.parameters.length > 0
? getEffectiveTypeAnnotationNode(accessor.parameters[0]) // Setter parameter type
: undefined;
}
}
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, context: SyntacticTypeNodeBuilderContext) {
const accessorDeclarations = resolver.getAllAccessorDeclarations(node);
const accessorType = getTypeAnnotationFromAllAccessorDeclarations(node, accessorDeclarations);
if (accessorType) {
return serializeExistingTypeAnnotation(accessorType);
}
if (accessorDeclarations.getAccessor) {
return createReturnFromSignature(accessorDeclarations.getAccessor, context);
}
return false;
}
function typeFromVariable(node: VariableDeclaration, context: SyntacticTypeNodeBuilderContext) {
const declaredType = getEffectiveTypeAnnotationNode(node);
if (declaredType) {
return serializeExistingTypeAnnotation(declaredType);
}
let resultType;
if (node.initializer) {
if (!resolver.isExpandoFunctionDeclaration(node)) {
resultType = typeFromExpression(node.initializer, context, /*isConstContext*/ undefined, /*requiresAddingUndefined*/ undefined, isVarConstLike(node));
}
}
return resultType ?? inferTypeOfDeclaration(node, context);
}
function typeFromParameter(node: ParameterDeclaration, context: SyntacticTypeNodeBuilderContext) {
const parent = node.parent;
if (parent.kind === SyntaxKind.SetAccessor) {
return typeFromAccessor(parent, context);
}
const declaredType = getEffectiveTypeAnnotationNode(node);
const addUndefined = resolver.requiresAddingImplicitUndefined(node, context.enclosingDeclaration);
let resultType;
if (declaredType) {
resultType = serializeExistingTypeAnnotation(declaredType, addUndefined);
}
else {
if (node.initializer && isIdentifier(node.name)) {
resultType = typeFromExpression(node.initializer, context, /*isConstContext*/ undefined, addUndefined);
}
}
return resultType ?? inferTypeOfDeclaration(node, context);
}
function typeFromProperty(node: PropertyDeclaration, context: SyntacticTypeNodeBuilderContext) {
const declaredType = getEffectiveTypeAnnotationNode(node);
if (declaredType) {
return serializeExistingTypeAnnotation(declaredType);
}
let resultType;
if (node.initializer) {
const isReadonly = isDeclarationReadonly(node);
resultType = typeFromExpression(node.initializer, context, /*isConstContext*/ undefined, /*requiresAddingUndefined*/ undefined, isReadonly);
}
return resultType ?? inferTypeOfDeclaration(node, context);
}
function inferTypeOfDeclaration(
node: PropertyAssignment | PropertyAccessExpression | BinaryExpression | ElementAccessExpression | VariableDeclaration | ParameterDeclaration | BindingElement | PropertyDeclaration | PropertySignature | ExportAssignment,
context: SyntacticTypeNodeBuilderContext,
) {
context.tracker.reportInferenceFallback(node);
return false;
}
function inferExpressionType(node: Expression, context: SyntacticTypeNodeBuilderContext) {
context.tracker.reportInferenceFallback(node);
return false;
}
function inferReturnTypeOfSignatureSignature(node: SignatureDeclaration | JSDocSignature, context: SyntacticTypeNodeBuilderContext) {
context.tracker.reportInferenceFallback(node);
return false;
}
function inferAccessorType(node: GetAccessorDeclaration | SetAccessorDeclaration, allAccessors: AllAccessorDeclarations, context: SyntacticTypeNodeBuilderContext) {
if (node.kind === SyntaxKind.GetAccessor) {
return createReturnFromSignature(node, context);
}
else {
context.tracker.reportInferenceFallback(node);
return false;
}
}
function typeFromTypeAssertion(expression: Expression, type: TypeNode, context: SyntacticTypeNodeBuilderContext, requiresAddingUndefined: boolean) {
if (isConstTypeReference(type)) {
return typeFromExpression(expression, context, /*isConstContext*/ true, requiresAddingUndefined);
}
if (requiresAddingUndefined && !canAddUndefined(type)) {
context.tracker.reportInferenceFallback(type);
}
return serializeExistingTypeAnnotation(type);
}
function typeFromExpression(node: Expression, context: SyntacticTypeNodeBuilderContext, isConstContext = false, requiresAddingUndefined = false, preserveLiterals = false): boolean | undefined {
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 true;
}
break;
case SyntaxKind.NullKeyword:
return true;
case SyntaxKind.ArrowFunction:
case SyntaxKind.FunctionExpression:
return typeFromFunctionLikeExpression(node as ArrowFunction | FunctionExpression, 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)) {
if (unaryExpression.operand.kind === SyntaxKind.BigIntLiteral) {
return typeFromPrimitiveLiteral();
}
if (unaryExpression.operand.kind === SyntaxKind.NumericLiteral) {
return typeFromPrimitiveLiteral();
}
}
break;
case SyntaxKind.NumericLiteral:
return typeFromPrimitiveLiteral();
case SyntaxKind.TemplateExpression:
if (!isConstContext && !preserveLiterals) {
return true;
}
break;
case SyntaxKind.NoSubstitutionTemplateLiteral:
case SyntaxKind.StringLiteral:
return typeFromPrimitiveLiteral();
case SyntaxKind.BigIntLiteral:
return typeFromPrimitiveLiteral();
case SyntaxKind.TrueKeyword:
case SyntaxKind.FalseKeyword:
return typeFromPrimitiveLiteral();
case SyntaxKind.ArrayLiteralExpression:
return typeFromArrayLiteral(node as ArrayLiteralExpression, context, isConstContext);
case SyntaxKind.ObjectLiteralExpression:
return typeFromObjectLiteral(node as ObjectLiteralExpression, context, isConstContext);
case SyntaxKind.ClassExpression:
return inferExpressionType(node as ClassExpression, context);
}
return undefined;
}
function typeFromFunctionLikeExpression(fnNode: FunctionExpression | ArrowFunction, context: SyntacticTypeNodeBuilderContext) {
const returnType = serializeExistingTypeAnnotation(fnNode.type) ?? createReturnFromSignature(fnNode, context);
const typeParameters = reuseTypeParameters(fnNode.typeParameters);
const parameters = fnNode.parameters.every(p => ensureParameter(p, context));
return returnType && typeParameters && parameters;
}
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) {
if (!canGetTypeFromArrayLiteral(arrayLiteral, context, isConstContext)) {
return false;
}
let canInferArray = true;
for (const element of arrayLiteral.elements) {
Debug.assert(element.kind !== SyntaxKind.SpreadElement);
if (element.kind !== SyntaxKind.OmittedExpression) {
canInferArray = (typeFromExpression(element, context, isConstContext) ?? inferExpressionType(element, context)) && canInferArray;
}
}
return true;
}
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): boolean {
if (!canGetTypeFromObjectLiteral(objectLiteral, context)) return false;
let canInferObjectLiteral = true;
for (const prop of objectLiteral.properties) {
Debug.assert(!isShorthandPropertyAssignment(prop) && !isSpreadAssignment(prop));
const name = prop.name;
switch (prop.kind) {
case SyntaxKind.MethodDeclaration:
canInferObjectLiteral = !!typeFromObjectLiteralMethod(prop, name, context) && canInferObjectLiteral;
break;
case SyntaxKind.PropertyAssignment:
canInferObjectLiteral = !!typeFromObjectLiteralPropertyAssignment(prop, name, context, isConstContext) && canInferObjectLiteral;
break;
case SyntaxKind.SetAccessor:
case SyntaxKind.GetAccessor:
canInferObjectLiteral = !!typeFromObjectLiteralAccessor(prop, name, context) && canInferObjectLiteral;
break;
}
}
return canInferObjectLiteral;
}
function typeFromObjectLiteralPropertyAssignment(prop: PropertyAssignment, name: PropertyName, context: SyntacticTypeNodeBuilderContext, isConstContext: boolean) {
return typeFromExpression(prop.initializer, context, isConstContext) ?? inferTypeOfDeclaration(prop, context);
}
function ensureParameter(p: ParameterDeclaration, context: SyntacticTypeNodeBuilderContext) {
return typeFromParameter(p, context);
}
function reuseTypeParameters(typeParameters: NodeArray<TypeParameterDeclaration> | undefined) {
// TODO: We will probably need to add a fake scopes for the signature (to hold the type parameters and the parameter)
// For now this is good enough since the new serialization is used for Nodes in the same context.
return typeParameters?.every(tp =>
serializeExistingTypeAnnotation(tp.constraint) &&
serializeExistingTypeAnnotation(tp.default)
) ?? true;
}
function typeFromObjectLiteralMethod(method: MethodDeclaration, name: PropertyName, context: SyntacticTypeNodeBuilderContext): boolean {
const returnType = createReturnFromSignature(method, context);
const typeParameters = reuseTypeParameters(method.typeParameters);
const parameters = method.parameters.every(p => ensureParameter(p, context));
return returnType && typeParameters && parameters;
}
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) {
const parameters = accessor.parameters.every(p => ensureParameter(p, context));
if (isGetAccessor(accessor)) {
return parameters && serializeExistingTypeAnnotation(getAccessorType);
}
else {
return parameters;
}
}
else if (allAccessors.firstAccessor === accessor) {
const foundType = getAccessorType ?? setAccessorType;
const propertyType = foundType ? serializeExistingTypeAnnotation(foundType) : inferAccessorType(accessor, allAccessors, context);
return propertyType;
}
return false;
}
function typeFromPrimitiveLiteral() {
return true;
}
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, context: SyntacticTypeNodeBuilderContext) {
let returnType;
const returnTypeNode = getEffectiveReturnTypeNode(fn);
if (returnTypeNode) {
returnType = serializeExistingTypeAnnotation(returnTypeNode);
}
if (!returnType && isValueSignatureDeclaration(fn)) {
returnType = typeFromSingleReturnExpression(fn, context);
}
return returnType ?? inferReturnTypeOfSignatureSignature(fn, context);
}
function typeFromSingleReturnExpression(declaration: FunctionLikeDeclaration | undefined, context: SyntacticTypeNodeBuilderContext): boolean | undefined {
let candidateExpr: Expression | undefined;
if (declaration && !nodeIsMissing(declaration.body)) {
if (getFunctionFlags(declaration) & FunctionFlags.AsyncGenerator) return undefined;
const body = declaration.body;
if (body && isBlock(body)) {
forEachReturnStatement(body, s => {
if (!candidateExpr) {
candidateExpr = s.expression;
}
else {
candidateExpr = undefined;
return true;
}
});
}
else {
candidateExpr = body;
}
}
if (candidateExpr) {
return typeFromExpression(candidateExpr, context);
}
return undefined;
}
}