Skip to content

Commit f39b884

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

File tree

4 files changed

+56
-46
lines changed

4 files changed

+56
-46
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: 43 additions & 45 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
@@ -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,
1669916708
expression: Expression | QualifiedName,
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,7 @@ 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 ? removeOptionalTypeMarker(checkOptionalExpression(node.expression)) :
1876018755
undefined;
1876118756
const signatures = getSignaturesOfType(funcType && getApparentType(funcType) || unknownType, SignatureKind.Call);
1876218757
const candidate = signatures.length === 1 && !signatures[0].typeParameters ? signatures[0] :
@@ -19718,7 +19713,7 @@ namespace ts {
1971819713
// will be a subtype or the same type as the argument.
1971919714
function narrowType(type: Type, expr: Expression, assumeTrue: boolean): Type {
1972019715
// for `a?.b`, we emulate a synthetic `a !== null && a !== undefined` condition for `a`
19721-
if (isOptionalChainRoot(expr.parent) ||
19716+
if (isOptionalExpression(expr) ||
1972219717
isBinaryExpression(expr.parent) && expr.parent.operatorToken.kind === SyntaxKind.QuestionQuestionToken && expr.parent.left === expr) {
1972319718
return narrowTypeByOptionality(type, expr, assumeTrue);
1972419719
}
@@ -22687,7 +22682,8 @@ namespace ts {
2268722682
}
2268822683

2268922684
function checkPropertyAccessExpressionOrQualifiedName(node: PropertyAccessExpression | QualifiedName, left: Expression | QualifiedName, right: Identifier) {
22690-
const { isOptional, type: leftType } = checkOptionalExpression(node, left);
22685+
const optionalLeftType = checkOptionalExpression(left);
22686+
const leftType = removeOptionalTypeMarker(optionalLeftType);
2269122687
const parentSymbol = getNodeLinks(left).resolvedSymbol;
2269222688
const assignmentKind = getAssignmentTargetKind(node);
2269322689
const apparentType = getApparentType(assignmentKind !== AssignmentKind.None || isMethodAccessForCall(node) ? getWidenedType(leftType) : leftType);
@@ -22741,7 +22737,7 @@ namespace ts {
2274122737
}
2274222738
propType = getConstraintForLocation(getTypeOfSymbol(prop), node);
2274322739
}
22744-
return propagateOptionalTypeMarker(getFlowTypeOfAccessExpression(node, prop, propType, right), isOptional);
22740+
return propagateOptionalTypeMarker(getFlowTypeOfAccessExpression(node, prop, propType, right), leftType !== optionalLeftType);
2274522741
}
2274622742

2274722743
function getFlowTypeOfAccessExpression(node: ElementAccessExpression | PropertyAccessExpression | QualifiedName, prop: Symbol | undefined, propType: Type, errorNode: Node) {
@@ -23098,7 +23094,8 @@ namespace ts {
2309823094
}
2309923095

2310023096
function checkIndexedAccess(node: ElementAccessExpression): Type {
23101-
const { isOptional, type: exprType } = checkOptionalExpression(node, node.expression);
23097+
const optionalExprType = checkOptionalExpression(node.expression);
23098+
const exprType = removeOptionalTypeMarker(optionalExprType);
2310223099
const objectType = getAssignmentTargetKind(node) !== AssignmentKind.None || isMethodAccessForCall(node) ? getWidenedType(exprType) : exprType;
2310323100
const indexExpression = node.argumentExpression;
2310423101
const indexType = checkExpression(indexExpression);
@@ -23117,7 +23114,7 @@ namespace ts {
2311723114
AccessFlags.Writing | (isGenericObjectType(objectType) && !isThisTypeParameter(objectType) ? AccessFlags.NoIndexSignatures : 0) :
2311823115
AccessFlags.None;
2311923116
const indexedAccessType = getIndexedAccessTypeOrUndefined(objectType, effectiveIndexType, node, accessFlags) || errorType;
23120-
return propagateOptionalTypeMarker(checkIndexedAccessIndexType(getFlowTypeOfAccessExpression(node, indexedAccessType.symbol, indexedAccessType, indexExpression), node), isOptional);
23117+
return propagateOptionalTypeMarker(checkIndexedAccessIndexType(getFlowTypeOfAccessExpression(node, indexedAccessType.symbol, indexedAccessType, indexExpression), node), exprType !== optionalExprType);
2312123118
}
2312223119

2312323120
function checkThatExpressionIsProperSymbolReference(expression: Expression, expressionType: Type, reportError: boolean): boolean {
@@ -24304,13 +24301,13 @@ namespace ts {
2430424301
return resolveUntypedCall(node);
2430524302
}
2430624303

24307-
const { isOptional, type: funcType } = checkOptionalExpression(
24308-
node,
24304+
const optionalFuncType = checkOptionalExpression(
2430924305
node.expression,
2431024306
Diagnostics.Cannot_invoke_an_object_which_is_possibly_null,
2431124307
Diagnostics.Cannot_invoke_an_object_which_is_possibly_undefined,
2431224308
Diagnostics.Cannot_invoke_an_object_which_is_possibly_null_or_undefined
2431324309
);
24310+
const funcType = removeOptionalTypeMarker(optionalFuncType);
2431424311

2431524312
if (funcType === silentNeverType) {
2431624313
return silentNeverSignature;
@@ -24381,7 +24378,7 @@ namespace ts {
2438124378
return resolveErrorCall(node);
2438224379
}
2438324380

24384-
return resolveCall(node, callSignatures, candidatesOutArray, checkMode, isOptional);
24381+
return resolveCall(node, callSignatures, candidatesOutArray, checkMode, funcType !== optionalFuncType);
2438524382
}
2438624383

2438724384
function isGenericFunctionReturningFunction(signature: Signature) {
@@ -27276,10 +27273,11 @@ namespace ts {
2727627273
// Optimize for the common case of a call to a function with a single non-generic call
2727727274
// signature where we can just fetch the return type without checking the arguments.
2727827275
if (isCallExpression(expr) && expr.expression.kind !== SyntaxKind.SuperKeyword && !isRequireCall(expr, /*checkArgumentIsStringLiteralLike*/ true) && !isSymbolOrSymbolForCall(expr)) {
27279-
const { isOptional, type: funcType } = checkOptionalExpression(expr, expr.expression);
27276+
const optionalFuncType = checkOptionalExpression(expr.expression);
27277+
const funcType = removeOptionalTypeMarker(optionalFuncType);
2728027278
const signature = getSingleCallSignature(funcType);
2728127279
if (signature && !signature.typeParameters) {
27282-
return propagateOptionalTypeMarker(getReturnTypeOfSignature(signature), isOptional);
27280+
return propagateOptionalTypeMarker(getReturnTypeOfSignature(signature), funcType !== optionalFuncType);
2728327281
}
2728427282
}
2728527283
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 */

src/compiler/utilities.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5913,6 +5913,14 @@ namespace ts {
59135913
|| kind === SyntaxKind.CallExpression);
59145914
}
59155915

5916+
/**
5917+
* Determines whether a node is the expression preceding an optional chain (i.e. `a` in `a?.b`).
5918+
*/
5919+
/* @internal */
5920+
export function isOptionalExpression(node: Node): node is Expression & { parent: OptionalChainRoot } {
5921+
return isOptionalChainRoot(node.parent) && node === node.parent.expression;
5922+
}
5923+
59165924
export function isNewExpression(node: Node): node is NewExpression {
59175925
return node.kind === SyntaxKind.NewExpression;
59185926
}

0 commit comments

Comments
 (0)