Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Analyze control flow effects of lambdas passed as arguments #58729

Open
wants to merge 33 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
8ccfb4a
Analyze control flow effects of lambdas passed as arguments
ahejlsberg May 31, 2024
e4d20f8
Accept new baselines
ahejlsberg May 31, 2024
de9171e
Formatting + handle unreachable code
ahejlsberg Jun 3, 2024
18f1ba9
Add Deferred<T> intrinsic + only check lambdas with mutation flow nodes
ahejlsberg Jun 5, 2024
46f6706
Accept new baselines
ahejlsberg Jun 5, 2024
dd59710
Local declarations don't mutate captured outer variables
ahejlsberg Jun 5, 2024
63ef249
Less aggressive call resolution
ahejlsberg Jun 6, 2024
0523c63
Optimize lambda assignment analysis using isMutableFlowNode walk
ahejlsberg Jun 7, 2024
3dbb4f4
Allocation-free caching in isMutationFlowNode
ahejlsberg Jun 8, 2024
738f6b5
Only analyze lambdas when widening is possible
ahejlsberg Jun 10, 2024
3ae7cb2
Implement 'deferred' modifier, remove Deferred<T> marker type
ahejlsberg Jun 13, 2024
0288e18
Accept new baselines
ahejlsberg Jun 13, 2024
fc9c454
Use comparable relation for check + fix parsing issue
ahejlsberg Jun 16, 2024
b6fb469
Add 'deferred' modifiers to Promise methods
ahejlsberg Jun 16, 2024
41883a1
Accept new baselines
ahejlsberg Jun 17, 2024
35aced6
Validation, print back, support for JSDoc
ahejlsberg Jun 20, 2024
89715f2
Accept new API baselines
ahejlsberg Jun 20, 2024
329d3fc
Accept Promise<T> print back changes
ahejlsberg Jun 20, 2024
8d8e60d
Merge branch 'main' into fix11498
ahejlsberg Jun 20, 2024
fde340b
Merge branch 'main' into fix11498
ahejlsberg Jul 10, 2024
96e6921
Support /** @deferred */ in .ts files
ahejlsberg Jul 11, 2024
37e6258
Switch lib.d.ts to use /** @deferred */
ahejlsberg Jul 11, 2024
22e9458
Accept new baselines
ahejlsberg Jul 11, 2024
3508e0e
Fix formatting
ahejlsberg Jul 11, 2024
01670b0
Add tests
ahejlsberg Jul 12, 2024
f392aa3
Merge branch 'main' into fix11498
ahejlsberg Aug 7, 2024
5ae48a2
Switch from 'deferred' modifier to 'immediate' modifier
ahejlsberg Aug 8, 2024
c16fc38
Update library types
ahejlsberg Aug 8, 2024
b60fbc0
Update tests
ahejlsberg Aug 8, 2024
7f7ccf6
Accept new baselines
ahejlsberg Aug 8, 2024
22faf4e
Delete old test baselines
ahejlsberg Aug 8, 2024
e037d82
Handle immediate modifier in strictSubtypeRelation and union signatures
ahejlsberg Aug 12, 2024
64e0459
Add tests
ahejlsberg Aug 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 29 additions & 8 deletions src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ import {
getImmediatelyInvokedFunctionExpression,
getJSDocHost,
getJSDocTypeTag,
getLambdaArgument,
getLeftmostAccessExpression,
getNameOfDeclaration,
getNameOrArgument,
Expand Down Expand Up @@ -318,6 +319,7 @@ import {
unreachableCodeIsError,
unusedLabelIsError,
VariableDeclaration,
walkUpParenthesizedExpressions,
WhileStatement,
WithStatement,
} from "./_namespaces/ts.js";
Expand Down Expand Up @@ -545,6 +547,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
var activeLabelList: ActiveLabel | undefined;
var hasExplicitReturn: boolean;
var hasFlowEffects: boolean;
var hasFlowMutation: boolean;

// state used for emit helpers
var emitFlags: NodeFlags;
Expand Down Expand Up @@ -624,6 +627,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
activeLabelList = undefined;
hasExplicitReturn = false;
hasFlowEffects = false;
hasFlowMutation = false;
inAssignmentPattern = false;
emitFlags = NodeFlags.None;
}
Expand Down Expand Up @@ -1010,12 +1014,11 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
const saveExceptionTarget = currentExceptionTarget;
const saveActiveLabelList = activeLabelList;
const saveHasExplicitReturn = hasExplicitReturn;
const isImmediatelyInvoked = (
containerFlags & ContainerFlags.IsFunctionExpression &&
const isRegularFunctionExpression = containerFlags & ContainerFlags.IsFunctionExpression &&
!hasSyntacticModifier(node, ModifierFlags.Async) &&
!(node as FunctionLikeDeclaration).asteriskToken &&
!!getImmediatelyInvokedFunctionExpression(node)
) || node.kind === SyntaxKind.ClassStaticBlockDeclaration;
!(node as FunctionLikeDeclaration).asteriskToken;
const isImmediatelyInvoked = isRegularFunctionExpression && !!getImmediatelyInvokedFunctionExpression(node) || node.kind === SyntaxKind.ClassStaticBlockDeclaration;
const isLambdaArgument = isRegularFunctionExpression && walkUpParenthesizedExpressions(node.parent).kind === SyntaxKind.CallExpression;
// A non-async, non-generator IIFE is considered part of the containing control flow. Return statements behave
// similarly to break statements that exit to a label just past the statement body.
if (!isImmediatelyInvoked) {
Expand All @@ -1026,7 +1029,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
}
// We create a return control flow graph for IIFEs and constructors. For constructors
// we use the return control flow graph in strict property initialization checks.
currentReturnTarget = isImmediatelyInvoked || node.kind === SyntaxKind.Constructor || (isInJSFile(node) && (node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.FunctionExpression)) ? createBranchLabel() : undefined;
currentReturnTarget = isImmediatelyInvoked || isLambdaArgument || node.kind === SyntaxKind.Constructor || (isInJSFile(node) && (node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.FunctionExpression)) ? createBranchLabel() : undefined;
currentExceptionTarget = undefined;
currentBreakTarget = undefined;
currentContinueTarget = undefined;
Expand All @@ -1048,7 +1051,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
if (currentReturnTarget) {
addAntecedent(currentReturnTarget, currentFlow);
currentFlow = finishFlowLabel(currentReturnTarget);
if (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;
}
}
Expand Down Expand Up @@ -1391,6 +1394,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
function createFlowMutation(flags: FlowFlags.Assignment | FlowFlags.ArrayMutation, antecedent: FlowNode, node: Expression | VariableDeclaration | ArrayBindingElement) {
setFlowNodeReferenced(antecedent);
hasFlowEffects = true;
if (node.kind !== SyntaxKind.VariableDeclaration && node.kind !== SyntaxKind.BindingElement) hasFlowMutation = true;
const result = createFlowNode(flags, node, antecedent) as FlowAssignment | FlowArrayMutation;
if (currentExceptionTarget) {
addAntecedent(currentExceptionTarget, result);
Expand All @@ -1404,6 +1408,11 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
return createFlowNode(FlowFlags.Call, node, antecedent) as FlowCall;
}

function createFlowLambdaArgs(antecedent: FlowNode, node: CallExpression) {
setFlowNodeReferenced(antecedent);
return createFlowNode(FlowFlags.LambdaArgs, node, antecedent) as FlowCall;
}

function finishFlowLabel(flow: FlowLabel): FlowNode {
const antecedents = flow.antecedent;
if (!antecedents) {
Expand Down Expand Up @@ -2209,10 +2218,22 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
bind(node.expression);
}
else {
bindEachChild(node);
bind(node.expression);
bindEach(node.typeArguments);
let hasLambdaArgs = false;
for (const arg of node.arguments) {
const saveHasFlowMutation = hasFlowMutation;
hasFlowMutation = false;
bind(arg);
hasLambdaArgs ||= !!getLambdaArgument(arg) && hasFlowMutation;
hasFlowMutation ||= saveHasFlowMutation;
}
if (node.expression.kind === SyntaxKind.SuperKeyword) {
currentFlow = createFlowCall(currentFlow, node);
}
if (hasLambdaArgs) {
currentFlow = createFlowLambdaArgs(currentFlow, node);
}
}
}
if (node.expression.kind === SyntaxKind.PropertyAccessExpression) {
Expand Down
Loading