Optimize lambda assignment analysis using isMutableFlowNode walk

This commit is contained in:
Anders Hejlsberg
2024-06-07 13:55:23 -07:00
parent 63ef249680
commit 0523c63c6c
2 changed files with 65 additions and 7 deletions
+1 -1
View File
@@ -1050,7 +1050,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
if (currentReturnTarget) {
addAntecedent(currentReturnTarget, currentFlow);
currentFlow = finishFlowLabel(currentReturnTarget);
if (isLambdaArgument || node.kind === SyntaxKind.Constructor || node.kind === SyntaxKind.ClassStaticBlockDeclaration || (isInJSFile(node) && (node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.FunctionExpression))) {
if (isLambdaArgument && hasFlowMutation || node.kind === SyntaxKind.Constructor || node.kind === SyntaxKind.ClassStaticBlockDeclaration || (isInJSFile(node) && (node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.FunctionExpression))) {
(node as FunctionLikeDeclaration | ClassStaticBlockDeclaration).returnFlowNode = currentFlow;
}
}
+64 -6
View File
@@ -27928,6 +27928,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
function getFlowTypeOfReference(reference: Node, declaredType: Type, initialType = declaredType, flowContainer?: Node, flowNode = tryCast(reference, canHaveFlowNode)?.flowNode) {
let key: string | undefined;
let hasMutation: boolean[] | undefined;
let isKeySet = false;
let inLambdaArg = false;
let flowDepth = 0;
@@ -28163,21 +28164,24 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
function getTypeAtFlowLambdaArgs(flow: FlowCall): FlowType {
const signatures = getSignaturesOfType(getTypeOfExpression(flow.node.expression), SignatureKind.Call);
const flowType = getTypeAtFlowNode(flow.antecedent);
const saveInitialType = initialType;
const saveInLambdaArg = inLambdaArg;
initialType = getTypeFromFlowType(flowType);
inLambdaArg = true;
let lambdaTypes: Type[] | undefined;
let signatures: readonly Signature[] | undefined;
const args = flow.node.arguments;
for (let i = 0; i < args.length; i++) {
const lambda = getLambdaArgument(args[i]);
if (lambda && lambda.returnFlowNode && !some(signatures, sig => !!(getObjectFlags(getTypeAtPosition(sig, i)) & ObjectFlags.DeferredCallback))) {
const lambdaType = getTypeFromFlowType(getTypeAtFlowNode(lambda.returnFlowNode));
if (lambdaType !== initialType) {
lambdaTypes ??= [initialType];
lambdaTypes.push(lambdaType);
if (lambda && lambda.returnFlowNode && isMutationFlowNode(lambda.returnFlowNode, /*noCacheCheck*/ false)) {
signatures ??= getSignaturesOfType(getTypeOfExpression(flow.node.expression), SignatureKind.Call);
if (!some(signatures, sig => !!(getObjectFlags(getTypeAtPosition(sig, i)) & ObjectFlags.DeferredCallback))) {
const lambdaType = getTypeFromFlowType(getTypeAtFlowNode(lambda.returnFlowNode));
if (lambdaType !== initialType) {
lambdaTypes ??= [initialType];
lambdaTypes.push(lambdaType);
}
}
}
}
@@ -28186,6 +28190,60 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return lambdaTypes ? createFlowType(getUnionOrEvolvingArrayType(lambdaTypes, UnionReduction.Literal), isIncomplete(flowType)) : flowType;
}
function isMutationFlowNode(flow: FlowNode, noCacheCheck: boolean): boolean {
while (true) {
const flags = flow.flags;
if (flags & FlowFlags.Shared) {
if (!noCacheCheck) {
const id = getFlowNodeId(flow);
const cached = (hasMutation ??= [])[id];
return cached !== undefined ? cached : (hasMutation[id] = isMutationFlowNode(flow, /*noCacheCheck*/ true));
}
noCacheCheck = false;
}
if (flags & (FlowFlags.Call | FlowFlags.Condition | FlowFlags.SwitchClause | FlowFlags.ReduceLabel)) {
flow = (flow as FlowCall | FlowCondition | FlowSwitchClause | FlowReduceLabel).antecedent;
}
else if (flags & FlowFlags.Assignment) {
if (isOrContainsMatchingReference(reference, (flow as FlowArrayMutation).node)) {
return true;
}
flow = (flow as FlowAssignment).antecedent;
}
else if (flags & FlowFlags.BranchLabel) {
return some((flow as FlowLabel).antecedent, f => isMutationFlowNode(f, /*noCacheCheck*/ false));
}
else if (flags & FlowFlags.LoopLabel) {
const id = getFlowNodeId(flow);
const cached = (hasMutation ??= [])[id];
if (cached !== undefined) return cached;
hasMutation[id] = false;
return hasMutation[id] = some((flow as FlowLabel).antecedent, f => isMutationFlowNode(f, /*noCacheCheck*/ false));
}
else if (flags & FlowFlags.ArrayMutation) {
if (declaredType === autoType || declaredType === autoArrayType) {
const node = (flow as FlowArrayMutation).node;
const expr = node.kind === SyntaxKind.CallExpression ?
(node.expression as PropertyAccessExpression).expression :
(node.left as ElementAccessExpression).expression;
if (isMatchingReference(reference, getReferenceCandidate(expr))) {
return true;
}
}
flow = (flow as FlowArrayMutation).antecedent;
}
else if (flags & FlowFlags.LambdaArgs) {
return some((flow as FlowCall).node.arguments, arg => {
const lambda = getLambdaArgument(arg);
return !!(lambda && lambda.returnFlowNode && isMutationFlowNode(lambda.returnFlowNode, /*noCacheCheck*/ false));
});
}
else {
return false;
}
}
}
function getTypeAtFlowArrayMutation(flow: FlowArrayMutation): FlowType | undefined {
if (declaredType === autoType || declaredType === autoArrayType) {
const node = flow.node;