Skip to content

Commit b86485b

Browse files
committed
Improve optional chaining checker performance
1 parent acf7aee commit b86485b

File tree

5 files changed

+130
-72
lines changed

5 files changed

+130
-72
lines changed

src/compiler/binder.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -948,7 +948,7 @@ namespace ts {
948948
}
949949
if (expression.kind === SyntaxKind.TrueKeyword && flags & FlowFlags.FalseCondition ||
950950
expression.kind === SyntaxKind.FalseKeyword && flags & FlowFlags.TrueCondition) {
951-
if (!isOptionalChainRoot(expression.parent)) {
951+
if (!isOptionalExpression(expression)) {
952952
return unreachableFlow;
953953
}
954954
}

src/compiler/checker.ts

Lines changed: 115 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -333,10 +333,6 @@ namespace ts {
333333
/** This will be set during calls to `getResolvedSignature` where services determines an apparent number of arguments greater than what is actually provided. */
334334
let apparentArgumentCount: number | undefined;
335335

336-
// This object is reused for `checkOptionalExpression` return values to avoid frequent GC due to nursery object allocations.
337-
// This object represents a pool-size of 1.
338-
const pooledOptionalTypeResult: { isOptional: boolean, type: Type } = { isOptional: false, type: undefined! };
339-
340336
// for public members that accept a Node or one of its subtypes, we must guard against
341337
// synthetic nodes created during transformations by calling `getParseTreeNode`.
342338
// for most of these, we perform the guard only on `checker` to avoid any possible
@@ -10265,7 +10261,7 @@ namespace ts {
1026510261
getReturnTypeFromAnnotation(signature.declaration!) ||
1026610262
(nodeIsMissing((<FunctionLikeDeclaration>signature.declaration).body) ? anyType : getReturnTypeFromBody(<FunctionLikeDeclaration>signature.declaration));
1026710263
if (signature.isOptionalCall) {
10268-
type = propagateOptionalTypeMarker(type, /*wasOptional*/ true);
10264+
type = addOptionalTypeMarker(type);
1026910265
}
1027010266
if (!popTypeResolution()) {
1027110267
if (signature.declaration) {
@@ -16677,51 +16673,50 @@ namespace ts {
1667716673
}
1667816674

1667916675
function addOptionalTypeMarker(type: Type) {
16680-
return strictNullChecks ? getUnionType([type, optionalType]) : type;
16676+
if (strictNullChecks) {
16677+
if (isTypeAny(type)) return type;
16678+
if (type.optionalTypeOfType) {
16679+
// The type aleady has an optional type cache.
16680+
return type.optionalTypeOfType;
16681+
}
16682+
if (type.flags & TypeFlags.Union && type.nonOptionalTypeOfType) {
16683+
// The type *is* the type produced by marking a type with the optional type.
16684+
return type;
16685+
}
16686+
type.optionalTypeOfType = getUnionType([type, optionalType]);
16687+
type.optionalTypeOfType.nonOptionalTypeOfType = type;
16688+
return type.optionalTypeOfType;
16689+
}
16690+
return type;
1668116691
}
1668216692

1668316693
function removeOptionalTypeMarker(type: Type): Type {
16684-
return strictNullChecks ? filterType(type, t => t !== optionalType) : type;
16694+
if (strictNullChecks) {
16695+
if (type.flags & TypeFlags.Union && type.nonOptionalTypeOfType) {
16696+
return type.nonOptionalTypeOfType;
16697+
}
16698+
return filterType(type, t => t !== optionalType);
16699+
}
16700+
return type;
1668516701
}
1668616702

1668716703
function propagateOptionalTypeMarker(type: Type, wasOptional: boolean) {
1668816704
return wasOptional ? addOptionalTypeMarker(type) : type;
1668916705
}
1669016706

16691-
function createPooledOptionalTypeResult(isOptional: boolean, type: Type) {
16692-
pooledOptionalTypeResult.isOptional = isOptional;
16693-
pooledOptionalTypeResult.type = type;
16694-
return pooledOptionalTypeResult;
16695-
}
16696-
1669716707
function checkOptionalExpression(
16698-
parent: PropertyAccessExpression | QualifiedName | ElementAccessExpression | CallExpression,
16699-
expression: Expression | QualifiedName,
16708+
expression: Expression,
1670016709
nullDiagnostic?: DiagnosticMessage,
1670116710
undefinedDiagnostic?: DiagnosticMessage,
1670216711
nullOrUndefinedDiagnostic?: DiagnosticMessage,
1670316712
) {
16704-
let isOptional = false;
16705-
let type = checkExpression(expression);
16706-
if (isOptionalChain(parent)) {
16707-
if (parent.questionDotToken) {
16708-
// If we have a questionDotToken then we are an OptionalExpression and should remove `null` and
16709-
// `undefined` from the type and add the optionalType to the result, if needed.
16710-
isOptional = isNullableType(type);
16711-
return createPooledOptionalTypeResult(isOptional, isOptional ? getNonNullableType(type) : type);
16712-
}
16713-
16714-
// If we do not have a questionDotToken, then we are an OptionalChain and we remove the optionalType and
16715-
// indicate whether we need to add optionalType back into the result.
16716-
const nonOptionalType = removeOptionalTypeMarker(type);
16717-
if (nonOptionalType !== type) {
16718-
isOptional = true;
16719-
type = nonOptionalType;
16720-
}
16721-
}
16722-
16723-
type = checkNonNullType(type, expression, nullDiagnostic, undefinedDiagnostic, nullOrUndefinedDiagnostic);
16724-
return createPooledOptionalTypeResult(isOptional, type);
16713+
const exprType = checkExpression(expression);
16714+
const nonOptionalType =
16715+
isOptionalExpression(expression) ? getNonNullableType(exprType) : // 'a' in 'a?.b.c'
16716+
isOptionalChain(expression) ? removeOptionalTypeMarker(exprType) : // 'a?.b' in 'a?.b.c'
16717+
exprType;
16718+
const nonNullType = checkNonNullType(nonOptionalType, expression, nullDiagnostic, undefinedDiagnostic, nullOrUndefinedDiagnostic);
16719+
return exprType !== nonOptionalType ? addOptionalTypeMarker(nonNullType) : nonNullType;
1672516720
}
1672616721

1672716722
/**
@@ -18756,7 +18751,8 @@ namespace ts {
1875618751
// circularities in control flow analysis, we use getTypeOfDottedName when resolving the call
1875718752
// target expression of an assertion.
1875818753
const funcType = node.parent.kind === SyntaxKind.ExpressionStatement ? getTypeOfDottedName(node.expression, /*diagnostic*/ undefined) :
18759-
node.expression.kind !== SyntaxKind.SuperKeyword ? checkOptionalExpression(node, node.expression).type :
18754+
node.expression.kind !== SyntaxKind.SuperKeyword ? isOptionalChain(node) ? removeOptionalTypeMarker(checkOptionalExpression(node.expression)) :
18755+
checkNonNullExpression(node.expression) :
1876018756
undefined;
1876118757
const signatures = getSignaturesOfType(funcType && getApparentType(funcType) || unknownType, SignatureKind.Call);
1876218758
const candidate = signatures.length === 1 && !signatures[0].typeParameters ? signatures[0] :
@@ -19718,7 +19714,7 @@ namespace ts {
1971819714
// will be a subtype or the same type as the argument.
1971919715
function narrowType(type: Type, expr: Expression, assumeTrue: boolean): Type {
1972019716
// for `a?.b`, we emulate a synthetic `a !== null && a !== undefined` condition for `a`
19721-
if (isOptionalChainRoot(expr.parent) ||
19717+
if (isOptionalExpression(expr) ||
1972219718
isBinaryExpression(expr.parent) && expr.parent.operatorToken.kind === SyntaxKind.QuestionQuestionToken && expr.parent.left === expr) {
1972319719
return narrowTypeByOptionality(type, expr, assumeTrue);
1972419720
}
@@ -22672,11 +22668,18 @@ namespace ts {
2267222668
}
2267322669

2267422670
function checkPropertyAccessExpression(node: PropertyAccessExpression) {
22675-
return checkPropertyAccessExpressionOrQualifiedName(node, node.expression, node.name);
22671+
return isPropertyAccessChain(node) ? checkPropertyAccessChain(node) :
22672+
checkPropertyAccessExpressionOrQualifiedName(node, node.expression, checkNonNullExpression(node.expression), node.name);
22673+
}
22674+
22675+
function checkPropertyAccessChain(node: PropertyAccessChain) {
22676+
const optionalType = checkOptionalExpression(node.expression);
22677+
const nonOptionalType = removeOptionalTypeMarker(optionalType);
22678+
return propagateOptionalTypeMarker(checkPropertyAccessExpressionOrQualifiedName(node, node.expression, nonOptionalType, node.name), nonOptionalType !== optionalType);
2267622679
}
2267722680

2267822681
function checkQualifiedName(node: QualifiedName) {
22679-
return checkPropertyAccessExpressionOrQualifiedName(node, node.left, node.right);
22682+
return checkPropertyAccessExpressionOrQualifiedName(node, node.left, checkNonNullExpression(node.left), node.right);
2268022683
}
2268122684

2268222685
function isMethodAccessForCall(node: Node) {
@@ -22686,8 +22689,7 @@ namespace ts {
2268622689
return isCallOrNewExpression(node.parent) && node.parent.expression === node;
2268722690
}
2268822691

22689-
function checkPropertyAccessExpressionOrQualifiedName(node: PropertyAccessExpression | QualifiedName, left: Expression | QualifiedName, right: Identifier) {
22690-
const { isOptional, type: leftType } = checkOptionalExpression(node, left);
22692+
function checkPropertyAccessExpressionOrQualifiedName(node: PropertyAccessExpression | QualifiedName, left: Expression | QualifiedName, leftType: Type, right: Identifier) {
2269122693
const parentSymbol = getNodeLinks(left).resolvedSymbol;
2269222694
const assignmentKind = getAssignmentTargetKind(node);
2269322695
const apparentType = getApparentType(assignmentKind !== AssignmentKind.None || isMethodAccessForCall(node) ? getWidenedType(leftType) : leftType);
@@ -22741,7 +22743,7 @@ namespace ts {
2274122743
}
2274222744
propType = getConstraintForLocation(getTypeOfSymbol(prop), node);
2274322745
}
22744-
return propagateOptionalTypeMarker(getFlowTypeOfAccessExpression(node, prop, propType, right), isOptional);
22746+
return getFlowTypeOfAccessExpression(node, prop, propType, right);
2274522747
}
2274622748

2274722749
function getFlowTypeOfAccessExpression(node: ElementAccessExpression | PropertyAccessExpression | QualifiedName, prop: Symbol | undefined, propType: Type, errorNode: Node) {
@@ -23098,7 +23100,17 @@ namespace ts {
2309823100
}
2309923101

2310023102
function checkIndexedAccess(node: ElementAccessExpression): Type {
23101-
const { isOptional, type: exprType } = checkOptionalExpression(node, node.expression);
23103+
return isElementAccessChain(node) ? checkElementAccessChain(node) :
23104+
checkElementAccessExpression(node, checkNonNullExpression(node.expression));
23105+
}
23106+
23107+
function checkElementAccessChain(node: ElementAccessChain): Type {
23108+
const optionalType = checkOptionalExpression(node.expression);
23109+
const nonOptionalType = removeOptionalTypeMarker(optionalType);
23110+
return propagateOptionalTypeMarker(checkElementAccessExpression(node, nonOptionalType), nonOptionalType !== optionalType);
23111+
}
23112+
23113+
function checkElementAccessExpression(node: ElementAccessExpression, exprType: Type): Type {
2310223114
const objectType = getAssignmentTargetKind(node) !== AssignmentKind.None || isMethodAccessForCall(node) ? getWidenedType(exprType) : exprType;
2310323115
const indexExpression = node.argumentExpression;
2310423116
const indexType = checkExpression(indexExpression);
@@ -23117,7 +23129,7 @@ namespace ts {
2311723129
AccessFlags.Writing | (isGenericObjectType(objectType) && !isThisTypeParameter(objectType) ? AccessFlags.NoIndexSignatures : 0) :
2311823130
AccessFlags.None;
2311923131
const indexedAccessType = getIndexedAccessTypeOrUndefined(objectType, effectiveIndexType, node, accessFlags) || errorType;
23120-
return propagateOptionalTypeMarker(checkIndexedAccessIndexType(getFlowTypeOfAccessExpression(node, indexedAccessType.symbol, indexedAccessType, indexExpression), node), isOptional);
23132+
return checkIndexedAccessIndexType(getFlowTypeOfAccessExpression(node, indexedAccessType.symbol, indexedAccessType, indexExpression), node);
2312123133
}
2312223134

2312323135
function checkThatExpressionIsProperSymbolReference(expression: Expression, expressionType: Type, reportError: boolean): boolean {
@@ -24284,34 +24296,53 @@ namespace ts {
2428424296
}
2428524297

2428624298
function resolveCallExpression(node: CallExpression, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature {
24299+
if (isOptionalChain(node)) {
24300+
return resolveCallChain(node, candidatesOutArray, checkMode);
24301+
}
2428724302
if (node.expression.kind === SyntaxKind.SuperKeyword) {
24288-
const superType = checkSuperExpression(node.expression);
24289-
if (isTypeAny(superType)) {
24290-
for (const arg of node.arguments) {
24291-
checkExpression(arg); // Still visit arguments so they get marked for visibility, etc
24292-
}
24293-
return anySignature;
24294-
}
24295-
if (superType !== errorType) {
24296-
// In super call, the candidate signatures are the matching arity signatures of the base constructor function instantiated
24297-
// with the type arguments specified in the extends clause.
24298-
const baseTypeNode = getEffectiveBaseTypeNode(getContainingClass(node)!);
24299-
if (baseTypeNode) {
24300-
const baseConstructors = getInstantiatedConstructorsForTypeArguments(superType, baseTypeNode.typeArguments, baseTypeNode);
24301-
return resolveCall(node, baseConstructors, candidatesOutArray, checkMode, /*isOptional*/ false);
24302-
}
24303-
}
24304-
return resolveUntypedCall(node);
24303+
return resolveSuperCall(node as SuperCall, candidatesOutArray, checkMode);
2430524304
}
24305+
const funcType = checkNonNullExpression(
24306+
node.expression,
24307+
Diagnostics.Cannot_invoke_an_object_which_is_possibly_null,
24308+
Diagnostics.Cannot_invoke_an_object_which_is_possibly_undefined,
24309+
Diagnostics.Cannot_invoke_an_object_which_is_possibly_null_or_undefined
24310+
);
24311+
return resolveCallExpressionWorker(node, funcType, candidatesOutArray, checkMode, /*isOptionalCall*/ false);
24312+
}
2430624313

24307-
const { isOptional, type: funcType } = checkOptionalExpression(
24308-
node,
24314+
function resolveCallChain(node: CallChain, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode) {
24315+
const optionalType = checkOptionalExpression(
2430924316
node.expression,
2431024317
Diagnostics.Cannot_invoke_an_object_which_is_possibly_null,
2431124318
Diagnostics.Cannot_invoke_an_object_which_is_possibly_undefined,
2431224319
Diagnostics.Cannot_invoke_an_object_which_is_possibly_null_or_undefined
2431324320
);
24321+
const nonOptionalType = removeOptionalTypeMarker(optionalType);
24322+
return resolveCallExpressionWorker(node, nonOptionalType, candidatesOutArray, checkMode, nonOptionalType !== optionalType);
24323+
}
24324+
24325+
function resolveSuperCall(node: SuperCall, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode) {
24326+
const superType = checkSuperExpression(node.expression);
24327+
if (isTypeAny(superType)) {
24328+
for (const arg of node.arguments) {
24329+
checkExpression(arg); // Still visit arguments so they get marked for visibility, etc
24330+
}
24331+
return anySignature;
24332+
}
24333+
if (superType !== errorType) {
24334+
// In super call, the candidate signatures are the matching arity signatures of the base constructor function instantiated
24335+
// with the type arguments specified in the extends clause.
24336+
const baseTypeNode = getEffectiveBaseTypeNode(getContainingClass(node)!);
24337+
if (baseTypeNode) {
24338+
const baseConstructors = getInstantiatedConstructorsForTypeArguments(superType, baseTypeNode.typeArguments, baseTypeNode);
24339+
return resolveCall(node, baseConstructors, candidatesOutArray, checkMode, /*isOptional*/ false);
24340+
}
24341+
}
24342+
return resolveUntypedCall(node);
24343+
}
2431424344

24345+
function resolveCallExpressionWorker(node: CallExpression, funcType: Type, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode, isOptionalCall: boolean): Signature {
2431524346
if (funcType === silentNeverType) {
2431624347
return silentNeverSignature;
2431724348
}
@@ -24381,7 +24412,7 @@ namespace ts {
2438124412
return resolveErrorCall(node);
2438224413
}
2438324414

24384-
return resolveCall(node, callSignatures, candidatesOutArray, checkMode, isOptional);
24415+
return resolveCall(node, callSignatures, candidatesOutArray, checkMode, isOptionalCall);
2438524416
}
2438624417

2438724418
function isGenericFunctionReturningFunction(signature: Signature) {
@@ -27265,6 +27296,20 @@ namespace ts {
2726527296
}
2726627297
}
2726727298

27299+
function getReturnTypeOfSingleNonGenericCallSignatureOfCallChain(node: CallChain) {
27300+
const optionalType = checkOptionalExpression(node.expression);
27301+
const nonOptionalType = removeOptionalTypeMarker(optionalType);
27302+
const returnType = getReturnTypeOfSingleNonGenericCallSignature(nonOptionalType);
27303+
return returnType && propagateOptionalTypeMarker(returnType, nonOptionalType !== optionalType);
27304+
}
27305+
27306+
function getReturnTypeOfSingleNonGenericCallSignature(funcType: Type) {
27307+
const signature = getSingleCallSignature(funcType);
27308+
if (signature && !signature.typeParameters) {
27309+
return getReturnTypeOfSignature(signature);
27310+
}
27311+
}
27312+
2726827313
/**
2726927314
* Returns the type of an expression. Unlike checkExpression, this function is simply concerned
2727027315
* with computing the type and may not fully check all contained sub-expressions for errors.
@@ -27276,10 +27321,11 @@ namespace ts {
2727627321
// Optimize for the common case of a call to a function with a single non-generic call
2727727322
// signature where we can just fetch the return type without checking the arguments.
2727827323
if (isCallExpression(expr) && expr.expression.kind !== SyntaxKind.SuperKeyword && !isRequireCall(expr, /*checkArgumentIsStringLiteralLike*/ true) && !isSymbolOrSymbolForCall(expr)) {
27279-
const { isOptional, type: funcType } = checkOptionalExpression(expr, expr.expression);
27280-
const signature = getSingleCallSignature(funcType);
27281-
if (signature && !signature.typeParameters) {
27282-
return propagateOptionalTypeMarker(getReturnTypeOfSignature(signature), isOptional);
27324+
const type = isCallChain(expr) ?
27325+
getReturnTypeOfSingleNonGenericCallSignatureOfCallChain(expr) :
27326+
getReturnTypeOfSingleNonGenericCallSignature(checkNonNullExpression(expr.expression));
27327+
if (type) {
27328+
return type;
2728327329
}
2728427330
}
2728527331
else if (isAssertionExpression(expr) && !isConstTypeReference(expr.type)) {

src/compiler/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4265,6 +4265,10 @@ namespace ts {
42654265
immediateBaseConstraint?: Type; // Immediate base constraint cache
42664266
/* @internal */
42674267
widened?: Type; // Cached widened form of the type
4268+
/* @internal */
4269+
optionalTypeOfType?: Type; // A cached copy of the type unioned with the optionalType marker.
4270+
/* @internal */
4271+
nonOptionalTypeOfType?: Type; // A cached copy of the type with the optionalType marker removed.
42684272
}
42694273

42704274
/* @internal */

0 commit comments

Comments
 (0)