From db40d91375b984bf6d07e1a6ed7b188a731c607f Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 27 Mar 2024 18:03:14 -0700 Subject: [PATCH 01/14] All flow nodes created by single createFlowNode function --- src/compiler/binder.ts | 59 ++++++++++++++++++++++++++--------------- src/compiler/checker.ts | 41 +++++++++++++--------------- src/compiler/debug.ts | 19 +++++++------ src/compiler/types.ts | 28 ++++++++++++++++--- 4 files changed, 88 insertions(+), 59 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 94f9be9c02c58..71b8f93c45cdb 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -65,10 +65,18 @@ import { Expression, ExpressionStatement, findAncestor, + FlowArrayMutation, + FlowAssignment, + FlowCall, + FlowCondition, FlowFlags, FlowLabel, FlowNode, FlowReduceLabel, + FlowStart, + FlowSwitchClause, + FlowSwitchClauseInfo, + FlowUnreachable, forEach, forEachChild, ForInOrOfStatement, @@ -496,9 +504,16 @@ export const enum ContainerFlags { IsObjectLiteralOrClassExpressionMethodOrAccessor = 1 << 7, } -function initFlowNode(node: T) { - Debug.attachFlowNodeDebugInfo(node); - return node; +export function createFlowNode(flags: FlowFlags.Unreachable): FlowUnreachable; +export function createFlowNode(flags: FlowFlags.Start): FlowStart; +export function createFlowNode(flags: FlowFlags.BranchLabel | FlowFlags.LoopLabel): FlowLabel; +export function createFlowNode(flags: FlowFlags.Assignment | FlowFlags.ArrayMutation, node: Expression | VariableDeclaration | BindingElement, antecedent: FlowNode): FlowAssignment | FlowArrayMutation; +export function createFlowNode(flags: FlowFlags.TrueCondition | FlowFlags.FalseCondition, node: Expression, antecedent: FlowNode): FlowCondition; +export function createFlowNode(flags: FlowFlags.SwitchClause, node: FlowSwitchClauseInfo, antecedent: FlowNode): FlowSwitchClause; +export function createFlowNode(flags: FlowFlags.Call, node: CallExpression, antecedent: FlowNode): FlowCall; +export function createFlowNode(flags: FlowFlags.ReduceLabel, node: FlowLabel, antecedent: FlowNode, antecedents: FlowNode[]): FlowReduceLabel; +export function createFlowNode(flags: FlowFlags, node?: unknown, antecedent?: FlowNode, antecedents?: FlowNode[]): FlowNode { + return { flags, id: undefined, node, antecedent, antecedents } as FlowNode; } const binder = /* @__PURE__ */ createBinder(); @@ -558,8 +573,8 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { var Symbol: new (flags: SymbolFlags, name: __String) => Symbol; var classifiableNames: Set<__String>; - var unreachableFlow: FlowNode = { flags: FlowFlags.Unreachable }; - var reportedUnreachableFlow: FlowNode = { flags: FlowFlags.Unreachable }; + var unreachableFlow = createFlowNode(FlowFlags.Unreachable); + var reportedUnreachableFlow = createFlowNode(FlowFlags.Unreachable); var bindBinaryExpressionFlow = createBindBinaryExpressionFlow(); /* eslint-enable no-var */ @@ -1013,7 +1028,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { // 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) { - currentFlow = initFlowNode({ flags: FlowFlags.Start }); + currentFlow = createFlowNode(FlowFlags.Start); if (containerFlags & (ContainerFlags.IsFunctionExpression | ContainerFlags.IsObjectLiteralOrClassExpressionMethodOrAccessor)) { currentFlow.node = node as FunctionExpression | ArrowFunction | MethodDeclaration | GetAccessorDeclaration | SetAccessorDeclaration; } @@ -1319,16 +1334,16 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { return containsNarrowableReference(expr); } - function createBranchLabel(): FlowLabel { - return initFlowNode({ flags: FlowFlags.BranchLabel, antecedents: undefined }); + function createBranchLabel() { + return createFlowNode(FlowFlags.BranchLabel); } - function createLoopLabel(): FlowLabel { - return initFlowNode({ flags: FlowFlags.LoopLabel, antecedents: undefined }); + function createLoopLabel() { + return createFlowNode(FlowFlags.LoopLabel); } - function createReduceLabel(target: FlowLabel, antecedents: FlowNode[], antecedent: FlowNode): FlowReduceLabel { - return initFlowNode({ flags: FlowFlags.ReduceLabel, target, antecedents, antecedent }); + function createReduceLabel(target: FlowLabel, antecedents: FlowNode[], antecedent: FlowNode) { + return createFlowNode(FlowFlags.ReduceLabel, target, antecedent, antecedents); } function setFlowNodeReferenced(flow: FlowNode) { @@ -1343,7 +1358,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { } } - function createFlowCondition(flags: FlowFlags, antecedent: FlowNode, expression: Expression | undefined): FlowNode { + function createFlowCondition(flags: FlowFlags.TrueCondition | FlowFlags.FalseCondition, antecedent: FlowNode, expression: Expression | undefined) { if (antecedent.flags & FlowFlags.Unreachable) { return antecedent; } @@ -1361,17 +1376,17 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { return antecedent; } setFlowNodeReferenced(antecedent); - return initFlowNode({ flags, antecedent, node: expression }); + return createFlowNode(flags, expression, antecedent); } - function createFlowSwitchClause(antecedent: FlowNode, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number): FlowNode { + function createFlowSwitchClause(antecedent: FlowNode, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number) { setFlowNodeReferenced(antecedent); - return initFlowNode({ flags: FlowFlags.SwitchClause, antecedent, switchStatement, clauseStart, clauseEnd }); + return createFlowNode(FlowFlags.SwitchClause, { switchStatement, clauseStart, clauseEnd }, antecedent); } - function createFlowMutation(flags: FlowFlags, antecedent: FlowNode, node: Expression | VariableDeclaration | ArrayBindingElement): FlowNode { + function createFlowMutation(flags: FlowFlags.Assignment | FlowFlags.ArrayMutation, antecedent: FlowNode, node: Expression | VariableDeclaration | ArrayBindingElement): FlowNode { setFlowNodeReferenced(antecedent); - const result = initFlowNode({ flags, antecedent, node }); + const result = createFlowNode(flags, node, antecedent); if (currentExceptionTarget) { addAntecedent(currentExceptionTarget, result); } @@ -1380,7 +1395,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { function createFlowCall(antecedent: FlowNode, node: CallExpression): FlowNode { setFlowNodeReferenced(antecedent); - return initFlowNode({ flags: FlowFlags.Call, antecedent, node }); + return createFlowNode(FlowFlags.Call, node, antecedent); } function finishFlowLabel(flow: FlowLabel): FlowNode { @@ -1695,7 +1710,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { function bindCaseBlock(node: CaseBlock): void { const clauses = node.clauses; const isNarrowingSwitch = node.parent.expression.kind === SyntaxKind.TrueKeyword || isNarrowingExpression(node.parent.expression); - let fallthroughFlow = unreachableFlow; + let fallthroughFlow: FlowNode = unreachableFlow; for (let i = 0; i < clauses.length; i++) { const clauseStart = i; @@ -2404,7 +2419,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { const host = typeAlias.parent.parent; container = (getEnclosingContainer(host) as IsContainer | undefined) || file; blockScopeContainer = (getEnclosingBlockScopeContainer(host) as IsBlockScopedContainer | undefined) || file; - currentFlow = initFlowNode({ flags: FlowFlags.Start }); + currentFlow = createFlowNode(FlowFlags.Start); parent = typeAlias; bind(typeAlias.typeExpression); const declName = getNameOfDeclaration(typeAlias); @@ -2476,7 +2491,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { const enclosingBlockScopeContainer = host ? getEnclosingBlockScopeContainer(host) as IsBlockScopedContainer | undefined : undefined; container = enclosingContainer || file; blockScopeContainer = enclosingBlockScopeContainer || file; - currentFlow = initFlowNode({ flags: FlowFlags.Start }); + currentFlow = createFlowNode(FlowFlags.Start); parent = jsDocImportTag; bind(jsDocImportTag.importClause); } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3f20314adbb65..2f5a0636d6eaa 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -111,6 +111,7 @@ import { createEmptyExports, createEvaluator, createFileDiagnostic, + createFlowNode, createGetCanonicalFileName, createGetSymbolWalker, createModeAwareCacheKey, @@ -27793,7 +27794,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { else if (flags & FlowFlags.SwitchClause) { // The control flow path representing an unmatched value in a switch statement with // no default clause is unreachable if the switch statement is exhaustive. - if ((flow as FlowSwitchClause).clauseStart === (flow as FlowSwitchClause).clauseEnd && isExhaustiveSwitchStatement((flow as FlowSwitchClause).switchStatement)) { + if ((flow as FlowSwitchClause).node.clauseStart === (flow as FlowSwitchClause).node.clauseEnd && isExhaustiveSwitchStatement((flow as FlowSwitchClause).node.switchStatement)) { return false; } flow = (flow as FlowSwitchClause).antecedent; @@ -27801,7 +27802,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { else if (flags & FlowFlags.ReduceLabel) { // Cache is unreliable once we start adjusting labels lastFlowNode = undefined; - const target = (flow as FlowReduceLabel).target; + const target = (flow as FlowReduceLabel).node; const saveAntecedents = target.antecedents; target.antecedents = (flow as FlowReduceLabel).antecedents; const result = isReachableFlowNodeWorker((flow as FlowReduceLabel).antecedent, /*noCacheCheck*/ false); @@ -27845,7 +27846,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { flow = (flow as FlowLabel).antecedents![0]; } else if (flags & FlowFlags.ReduceLabel) { - const target = (flow as FlowReduceLabel).target; + const target = (flow as FlowReduceLabel).node; const saveAntecedents = target.antecedents; target.antecedents = (flow as FlowReduceLabel).antecedents; const result = isPostSuperFlowNode((flow as FlowReduceLabel).antecedent, /*noCacheCheck*/ false); @@ -27978,7 +27979,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } else if (flags & FlowFlags.ReduceLabel) { - const target = (flow as FlowReduceLabel).target; + const target = (flow as FlowReduceLabel).node; const saveAntecedents = target.antecedents; target.antecedents = (flow as FlowReduceLabel).antecedents; type = getTypeAtFlowNode((flow as FlowReduceLabel).antecedent); @@ -28169,30 +28170,31 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function getTypeAtSwitchClause(flow: FlowSwitchClause): FlowType { - const expr = skipParentheses(flow.switchStatement.expression); + const { switchStatement, clauseStart, clauseEnd } = flow.node; + const expr = skipParentheses(switchStatement.expression); const flowType = getTypeAtFlowNode(flow.antecedent); let type = getTypeFromFlowType(flowType); if (isMatchingReference(reference, expr)) { - type = narrowTypeBySwitchOnDiscriminant(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd); + type = narrowTypeBySwitchOnDiscriminant(type, switchStatement, clauseStart, clauseEnd); } else if (expr.kind === SyntaxKind.TypeOfExpression && isMatchingReference(reference, (expr as TypeOfExpression).expression)) { - type = narrowTypeBySwitchOnTypeOf(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd); + type = narrowTypeBySwitchOnTypeOf(type, switchStatement, clauseStart, clauseEnd); } else if (expr.kind === SyntaxKind.TrueKeyword) { - type = narrowTypeBySwitchOnTrue(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd); + type = narrowTypeBySwitchOnTrue(type, switchStatement, clauseStart, clauseEnd); } else { if (strictNullChecks) { if (optionalChainContainsReference(expr, reference)) { - type = narrowTypeBySwitchOptionalChainContainment(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd, t => !(t.flags & (TypeFlags.Undefined | TypeFlags.Never))); + type = narrowTypeBySwitchOptionalChainContainment(type, switchStatement, clauseStart, clauseEnd, t => !(t.flags & (TypeFlags.Undefined | TypeFlags.Never))); } else if (expr.kind === SyntaxKind.TypeOfExpression && optionalChainContainsReference((expr as TypeOfExpression).expression, reference)) { - type = narrowTypeBySwitchOptionalChainContainment(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd, t => !(t.flags & TypeFlags.Never || t.flags & TypeFlags.StringLiteral && (t as StringLiteralType).value === "undefined")); + type = narrowTypeBySwitchOptionalChainContainment(type, switchStatement, clauseStart, clauseEnd, t => !(t.flags & TypeFlags.Never || t.flags & TypeFlags.StringLiteral && (t as StringLiteralType).value === "undefined")); } } const access = getDiscriminantPropertyAccess(expr, type); if (access) { - type = narrowTypeBySwitchOnDiscriminantProperty(type, access, flow.switchStatement, flow.clauseStart, flow.clauseEnd); + type = narrowTypeBySwitchOnDiscriminantProperty(type, access, switchStatement, clauseStart, clauseEnd); } } return createFlowType(type, isIncomplete(flowType)); @@ -28204,7 +28206,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { let seenIncomplete = false; let bypassFlow: FlowSwitchClause | undefined; for (const antecedent of flow.antecedents!) { - if (!bypassFlow && antecedent.flags & FlowFlags.SwitchClause && (antecedent as FlowSwitchClause).clauseStart === (antecedent as FlowSwitchClause).clauseEnd) { + if (!bypassFlow && antecedent.flags & FlowFlags.SwitchClause && (antecedent as FlowSwitchClause).node.clauseStart === (antecedent as FlowSwitchClause).node.clauseEnd) { // The antecedent is the bypass branch of a potentially exhaustive switch statement. bypassFlow = antecedent as FlowSwitchClause; continue; @@ -28235,7 +28237,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // If the bypass flow contributes a type we haven't seen yet and the switch statement // isn't exhaustive, process the bypass flow type. Since exhaustiveness checks increase // the risk of circularities, we only want to perform them when they make a difference. - if (!(type.flags & TypeFlags.Never) && !contains(antecedentTypes, type) && !isExhaustiveSwitchStatement(bypassFlow.switchStatement)) { + if (!(type.flags & TypeFlags.Never) && !contains(antecedentTypes, type) && !isExhaustiveSwitchStatement(bypassFlow.node.switchStatement)) { if (type === declaredType && declaredType === initialType) { return type; } @@ -37665,22 +37667,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function checkIfExpressionRefinesParameter(func: FunctionLikeDeclaration, expr: Expression, param: ParameterDeclaration, initType: Type): Type | undefined { const antecedent = (expr as Expression & { flowNode?: FlowNode; }).flowNode || expr.parent.kind === SyntaxKind.ReturnStatement && (expr.parent as ReturnStatement).flowNode || - { flags: FlowFlags.Start }; - const trueCondition: FlowCondition = { - flags: FlowFlags.TrueCondition, - node: expr, - antecedent, - }; + createFlowNode(FlowFlags.Start); + const trueCondition = createFlowNode(FlowFlags.TrueCondition, expr, antecedent); const trueType = getFlowTypeOfReference(param.name, initType, initType, func, trueCondition); if (trueType === initType) return undefined; // "x is T" means that x is T if and only if it returns true. If it returns false then x is not T. // This means that if the function is called with an argument of type trueType, there can't be anything left in the `else` branch. It must reduce to `never`. - const falseCondition: FlowCondition = { - ...trueCondition, - flags: FlowFlags.FalseCondition, - }; + const falseCondition = createFlowNode(FlowFlags.FalseCondition, expr, antecedent); const falseSubtype = getFlowTypeOfReference(param.name, trueType, trueType, func, falseCondition); return falseSubtype.flags & TypeFlags.Never ? trueType : undefined; } diff --git a/src/compiler/debug.ts b/src/compiler/debug.ts index 8912ffee391f1..522cfdd11c7b6 100644 --- a/src/compiler/debug.ts +++ b/src/compiler/debug.ts @@ -10,7 +10,6 @@ import { FlowFlags, FlowLabel, FlowNode, - FlowNodeBase, FlowSwitchClause, getEffectiveModifierFlagsNoCache, getEmitFlags, @@ -506,14 +505,14 @@ export namespace Debug { let isDebugInfoEnabled = false; - let flowNodeProto: FlowNodeBase | undefined; + let flowNodeProto: FlowNode | undefined; - function attachFlowNodeDebugInfoWorker(flowNode: FlowNodeBase) { + function attachFlowNodeDebugInfoWorker(flowNode: FlowNode) { if (!("__debugFlowFlags" in flowNode)) { // eslint-disable-line local/no-in-operator Object.defineProperties(flowNode, { // for use with vscode-js-debug's new customDescriptionGenerator in launch.json __tsDebuggerDisplay: { - value(this: FlowNodeBase) { + value(this: FlowNode) { const flowHeader = this.flags & FlowFlags.Start ? "FlowStart" : this.flags & FlowFlags.BranchLabel ? "FlowBranchLabel" : this.flags & FlowFlags.LoopLabel ? "FlowLoopLabel" : @@ -531,12 +530,12 @@ export namespace Debug { }, }, __debugFlowFlags: { - get(this: FlowNodeBase) { + get(this: FlowNode) { return formatEnum(this.flags, (ts as any).FlowFlags, /*isFlags*/ true); }, }, __debugToString: { - value(this: FlowNodeBase) { + value(this: FlowNode) { return formatControlFlowGraph(this); }, }, @@ -544,13 +543,13 @@ export namespace Debug { } } - export function attachFlowNodeDebugInfo(flowNode: FlowNodeBase) { + export function attachFlowNodeDebugInfo(flowNode: FlowNode) { if (isDebugInfoEnabled) { if (typeof Object.setPrototypeOf === "function") { // if we're in es2015, attach the method to a shared prototype for `FlowNode` // so the method doesn't show up in the watch window. if (!flowNodeProto) { - flowNodeProto = Object.create(Object.prototype) as FlowNodeBase; + flowNodeProto = Object.create(Object.prototype) as FlowNode; attachFlowNodeDebugInfoWorker(flowNodeProto); } Object.setPrototypeOf(flowNode, flowNodeProto); @@ -1104,8 +1103,8 @@ m2: ${(this.mapper2 as unknown as DebugTypeMapper).__debugToString().split("\n") } else if (isFlowSwitchClause(flowNode)) { const clauses: string[] = []; - for (let i = flowNode.clauseStart; i < flowNode.clauseEnd; i++) { - const clause = flowNode.switchStatement.caseBlock.clauses[i]; + for (let i = flowNode.node.clauseStart; i < flowNode.node.clauseEnd; i++) { + const clause = flowNode.node.switchStatement.caseBlock.clauses[i]; if (isDefaultClause(clause)) { clauses.push("default"); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 9e06679f0185f..720240d51a128 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4090,6 +4090,7 @@ export const enum FlowFlags { } export type FlowNode = + | FlowUnreachable | FlowStart | FlowLabel | FlowAssignment @@ -4101,18 +4102,28 @@ export type FlowNode = export interface FlowNodeBase { flags: FlowFlags; - id?: number; // Node id used by flow type cache in checker + id: number | undefined; // Node id used by flow type cache in checker +} + +export interface FlowUnreachable extends FlowNodeBase { + node: undefined, + antecedent: undefined; + antecedents: undefined; } // FlowStart represents the start of a control flow. For a function expression or arrow // function, the node property references the function (which in turn has a flowNode // property for the containing control flow). export interface FlowStart extends FlowNodeBase { - node?: FunctionExpression | ArrowFunction | MethodDeclaration | GetAccessorDeclaration | SetAccessorDeclaration; + node: FunctionExpression | ArrowFunction | MethodDeclaration | GetAccessorDeclaration | SetAccessorDeclaration | undefined; + antecedent: undefined; + antecedents: undefined; } // FlowLabel represents a junction with multiple possible preceding control flows. export interface FlowLabel extends FlowNodeBase { + node: undefined; + antecedent: undefined; antecedents: FlowNode[] | undefined; } @@ -4121,11 +4132,13 @@ export interface FlowLabel extends FlowNodeBase { export interface FlowAssignment extends FlowNodeBase { node: Expression | VariableDeclaration | BindingElement; antecedent: FlowNode; + antecedents: undefined; } export interface FlowCall extends FlowNodeBase { node: CallExpression; antecedent: FlowNode; + antecedents: undefined; } // FlowCondition represents a condition that is known to be true or false at the @@ -4133,14 +4146,20 @@ export interface FlowCall extends FlowNodeBase { export interface FlowCondition extends FlowNodeBase { node: Expression; antecedent: FlowNode; + antecedents: undefined; } // dprint-ignore export interface FlowSwitchClause extends FlowNodeBase { + node: FlowSwitchClauseInfo; + antecedent: FlowNode; + antecedents: undefined; +} + +export interface FlowSwitchClauseInfo { switchStatement: SwitchStatement; clauseStart: number; // Start index of case/default clause range clauseEnd: number; // End index of case/default clause range - antecedent: FlowNode; } // FlowArrayMutation represents a node potentially mutates an array, i.e. an @@ -4148,10 +4167,11 @@ export interface FlowSwitchClause extends FlowNodeBase { export interface FlowArrayMutation extends FlowNodeBase { node: CallExpression | BinaryExpression; antecedent: FlowNode; + antecedents: undefined; } export interface FlowReduceLabel extends FlowNodeBase { - target: FlowLabel; + node: FlowLabel; antecedents: FlowNode[]; antecedent: FlowNode; } From 2deadcb688b903b765d64598666bb4e409aabf8e Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 27 Mar 2024 18:04:00 -0700 Subject: [PATCH 02/14] Accept new API baselines --- tests/baselines/reference/api/typescript.d.ts | 35 ++++++++++++++++--- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 7f7bf51496508..7cd352e911ff1 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -5810,41 +5810,58 @@ declare namespace ts { Label = 12, Condition = 96, } - type FlowNode = FlowStart | FlowLabel | FlowAssignment | FlowCondition | FlowSwitchClause | FlowArrayMutation | FlowCall | FlowReduceLabel; + type FlowNode = FlowUnreachable | FlowStart | FlowLabel | FlowAssignment | FlowCondition | FlowSwitchClause | FlowArrayMutation | FlowCall | FlowReduceLabel; interface FlowNodeBase { flags: FlowFlags; - id?: number; + id: number | undefined; + } + interface FlowUnreachable extends FlowNodeBase { + node: undefined; + antecedent: undefined; + antecedents: undefined; } interface FlowStart extends FlowNodeBase { - node?: FunctionExpression | ArrowFunction | MethodDeclaration | GetAccessorDeclaration | SetAccessorDeclaration; + node: FunctionExpression | ArrowFunction | MethodDeclaration | GetAccessorDeclaration | SetAccessorDeclaration | undefined; + antecedent: undefined; + antecedents: undefined; } interface FlowLabel extends FlowNodeBase { + node: undefined; + antecedent: undefined; antecedents: FlowNode[] | undefined; } interface FlowAssignment extends FlowNodeBase { node: Expression | VariableDeclaration | BindingElement; antecedent: FlowNode; + antecedents: undefined; } interface FlowCall extends FlowNodeBase { node: CallExpression; antecedent: FlowNode; + antecedents: undefined; } interface FlowCondition extends FlowNodeBase { node: Expression; antecedent: FlowNode; + antecedents: undefined; } interface FlowSwitchClause extends FlowNodeBase { + node: FlowSwitchClauseInfo; + antecedent: FlowNode; + antecedents: undefined; + } + interface FlowSwitchClauseInfo { switchStatement: SwitchStatement; clauseStart: number; clauseEnd: number; - antecedent: FlowNode; } interface FlowArrayMutation extends FlowNodeBase { node: CallExpression | BinaryExpression; antecedent: FlowNode; + antecedents: undefined; } interface FlowReduceLabel extends FlowNodeBase { - target: FlowLabel; + node: FlowLabel; antecedents: FlowNode[]; antecedent: FlowNode; } @@ -9222,6 +9239,14 @@ declare namespace ts { clear(): void; } type PerModuleNameCache = PerNonRelativeNameCache; + function createFlowNode(flags: FlowFlags.Unreachable): FlowUnreachable; + function createFlowNode(flags: FlowFlags.Start): FlowStart; + function createFlowNode(flags: FlowFlags.BranchLabel | FlowFlags.LoopLabel): FlowLabel; + function createFlowNode(flags: FlowFlags.Assignment | FlowFlags.ArrayMutation, node: Expression | VariableDeclaration | BindingElement, antecedent: FlowNode): FlowAssignment | FlowArrayMutation; + function createFlowNode(flags: FlowFlags.TrueCondition | FlowFlags.FalseCondition, node: Expression, antecedent: FlowNode): FlowCondition; + function createFlowNode(flags: FlowFlags.SwitchClause, node: FlowSwitchClauseInfo, antecedent: FlowNode): FlowSwitchClause; + function createFlowNode(flags: FlowFlags.Call, node: CallExpression, antecedent: FlowNode): FlowCall; + function createFlowNode(flags: FlowFlags.ReduceLabel, node: FlowLabel, antecedent: FlowNode, antecedents: FlowNode[]): FlowReduceLabel; /** * Visits a Node using the supplied visitor, possibly returning a new Node in its place. * From 773c98bf365cdf25e7e6ff7e4ab37e484f804ae5 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 28 Mar 2024 07:04:06 -0700 Subject: [PATCH 03/14] Use constructor function --- src/compiler/binder.ts | 12 +++++++++++- src/compiler/types.ts | 3 +++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 71b8f93c45cdb..9c73817b462f5 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -72,6 +72,7 @@ import { FlowFlags, FlowLabel, FlowNode, + FlowNodeBase, FlowReduceLabel, FlowStart, FlowSwitchClause, @@ -512,8 +513,17 @@ export function createFlowNode(flags: FlowFlags.TrueCondition | FlowFlags.FalseC export function createFlowNode(flags: FlowFlags.SwitchClause, node: FlowSwitchClauseInfo, antecedent: FlowNode): FlowSwitchClause; export function createFlowNode(flags: FlowFlags.Call, node: CallExpression, antecedent: FlowNode): FlowCall; export function createFlowNode(flags: FlowFlags.ReduceLabel, node: FlowLabel, antecedent: FlowNode, antecedents: FlowNode[]): FlowReduceLabel; +export function createFlowNode(flags: FlowFlags, node?: unknown, antecedent?: FlowNode, antecedents?: FlowNode[]): FlowNode; export function createFlowNode(flags: FlowFlags, node?: unknown, antecedent?: FlowNode, antecedents?: FlowNode[]): FlowNode { - return { flags, id: undefined, node, antecedent, antecedents } as FlowNode; + return new (FlowNode as any)(flags, node, antecedent, antecedents); +} + +function FlowNode(this: FlowNodeBase, flags: FlowFlags, node?: unknown, antecedent?: FlowNode, antecedents?: FlowNode[]) { + this.flags = flags; + this.id = undefined; + this.node = node; + this.antecedent = antecedent; + this.antecedents = antecedents; } const binder = /* @__PURE__ */ createBinder(); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 720240d51a128..73c69b83e5adc 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4103,6 +4103,9 @@ export type FlowNode = export interface FlowNodeBase { flags: FlowFlags; id: number | undefined; // Node id used by flow type cache in checker + node: unknown; + antecedent: FlowNode | undefined; + antecedents: FlowNode[] | undefined; } export interface FlowUnreachable extends FlowNodeBase { From 7e4fee760246a213cb26e83fd2283e09338a58b7 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 28 Mar 2024 09:25:38 -0700 Subject: [PATCH 04/14] Exclude optional 'id' property --- src/compiler/binder.ts | 20 +++++++------------- src/compiler/checker.ts | 21 ++++++++++----------- src/compiler/debug.ts | 16 ++++++++-------- src/compiler/types.ts | 10 +++------- 4 files changed, 28 insertions(+), 39 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 9c73817b462f5..d571a507facfb 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -76,7 +76,6 @@ import { FlowReduceLabel, FlowStart, FlowSwitchClause, - FlowSwitchClauseInfo, FlowUnreachable, forEach, forEachChild, @@ -510,20 +509,12 @@ export function createFlowNode(flags: FlowFlags.Start): FlowStart; export function createFlowNode(flags: FlowFlags.BranchLabel | FlowFlags.LoopLabel): FlowLabel; export function createFlowNode(flags: FlowFlags.Assignment | FlowFlags.ArrayMutation, node: Expression | VariableDeclaration | BindingElement, antecedent: FlowNode): FlowAssignment | FlowArrayMutation; export function createFlowNode(flags: FlowFlags.TrueCondition | FlowFlags.FalseCondition, node: Expression, antecedent: FlowNode): FlowCondition; -export function createFlowNode(flags: FlowFlags.SwitchClause, node: FlowSwitchClauseInfo, antecedent: FlowNode): FlowSwitchClause; +export function createFlowNode(flags: FlowFlags.SwitchClause, node: SwitchStatement, antecedent: FlowNode): FlowSwitchClause; export function createFlowNode(flags: FlowFlags.Call, node: CallExpression, antecedent: FlowNode): FlowCall; export function createFlowNode(flags: FlowFlags.ReduceLabel, node: FlowLabel, antecedent: FlowNode, antecedents: FlowNode[]): FlowReduceLabel; export function createFlowNode(flags: FlowFlags, node?: unknown, antecedent?: FlowNode, antecedents?: FlowNode[]): FlowNode; -export function createFlowNode(flags: FlowFlags, node?: unknown, antecedent?: FlowNode, antecedents?: FlowNode[]): FlowNode { - return new (FlowNode as any)(flags, node, antecedent, antecedents); -} - -function FlowNode(this: FlowNodeBase, flags: FlowFlags, node?: unknown, antecedent?: FlowNode, antecedents?: FlowNode[]) { - this.flags = flags; - this.id = undefined; - this.node = node; - this.antecedent = antecedent; - this.antecedents = antecedents; +export function createFlowNode(flags: FlowFlags, node?: unknown, antecedent?: FlowNode, antecedents?: FlowNode[]): FlowNodeBase { + return { flags, node, antecedent, antecedents }; } const binder = /* @__PURE__ */ createBinder(); @@ -1391,7 +1382,10 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { function createFlowSwitchClause(antecedent: FlowNode, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number) { setFlowNodeReferenced(antecedent); - return createFlowNode(FlowFlags.SwitchClause, { switchStatement, clauseStart, clauseEnd }, antecedent); + const result = createFlowNode(FlowFlags.SwitchClause, switchStatement, antecedent); + result.clauseStart = clauseStart; + result.clauseEnd = clauseEnd; + return result; } function createFlowMutation(flags: FlowFlags.Assignment | FlowFlags.ArrayMutation, antecedent: FlowNode, node: Expression | VariableDeclaration | ArrayBindingElement): FlowNode { diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2f5a0636d6eaa..ac9e5a8c44c77 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -27794,7 +27794,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { else if (flags & FlowFlags.SwitchClause) { // The control flow path representing an unmatched value in a switch statement with // no default clause is unreachable if the switch statement is exhaustive. - if ((flow as FlowSwitchClause).node.clauseStart === (flow as FlowSwitchClause).node.clauseEnd && isExhaustiveSwitchStatement((flow as FlowSwitchClause).node.switchStatement)) { + if ((flow as FlowSwitchClause).clauseStart === (flow as FlowSwitchClause).clauseEnd && isExhaustiveSwitchStatement((flow as FlowSwitchClause).node)) { return false; } flow = (flow as FlowSwitchClause).antecedent; @@ -28170,31 +28170,30 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function getTypeAtSwitchClause(flow: FlowSwitchClause): FlowType { - const { switchStatement, clauseStart, clauseEnd } = flow.node; - const expr = skipParentheses(switchStatement.expression); + const expr = skipParentheses(flow.node.expression); const flowType = getTypeAtFlowNode(flow.antecedent); let type = getTypeFromFlowType(flowType); if (isMatchingReference(reference, expr)) { - type = narrowTypeBySwitchOnDiscriminant(type, switchStatement, clauseStart, clauseEnd); + type = narrowTypeBySwitchOnDiscriminant(type, flow.node, flow.clauseStart, flow.clauseEnd); } else if (expr.kind === SyntaxKind.TypeOfExpression && isMatchingReference(reference, (expr as TypeOfExpression).expression)) { - type = narrowTypeBySwitchOnTypeOf(type, switchStatement, clauseStart, clauseEnd); + type = narrowTypeBySwitchOnTypeOf(type, flow.node, flow.clauseStart, flow.clauseEnd); } else if (expr.kind === SyntaxKind.TrueKeyword) { - type = narrowTypeBySwitchOnTrue(type, switchStatement, clauseStart, clauseEnd); + type = narrowTypeBySwitchOnTrue(type, flow.node, flow.clauseStart, flow.clauseEnd); } else { if (strictNullChecks) { if (optionalChainContainsReference(expr, reference)) { - type = narrowTypeBySwitchOptionalChainContainment(type, switchStatement, clauseStart, clauseEnd, t => !(t.flags & (TypeFlags.Undefined | TypeFlags.Never))); + type = narrowTypeBySwitchOptionalChainContainment(type, flow.node, flow.clauseStart, flow.clauseEnd, t => !(t.flags & (TypeFlags.Undefined | TypeFlags.Never))); } else if (expr.kind === SyntaxKind.TypeOfExpression && optionalChainContainsReference((expr as TypeOfExpression).expression, reference)) { - type = narrowTypeBySwitchOptionalChainContainment(type, switchStatement, clauseStart, clauseEnd, t => !(t.flags & TypeFlags.Never || t.flags & TypeFlags.StringLiteral && (t as StringLiteralType).value === "undefined")); + type = narrowTypeBySwitchOptionalChainContainment(type, flow.node, flow.clauseStart, flow.clauseEnd, t => !(t.flags & TypeFlags.Never || t.flags & TypeFlags.StringLiteral && (t as StringLiteralType).value === "undefined")); } } const access = getDiscriminantPropertyAccess(expr, type); if (access) { - type = narrowTypeBySwitchOnDiscriminantProperty(type, access, switchStatement, clauseStart, clauseEnd); + type = narrowTypeBySwitchOnDiscriminantProperty(type, access, flow.node, flow.clauseStart, flow.clauseEnd); } } return createFlowType(type, isIncomplete(flowType)); @@ -28206,7 +28205,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { let seenIncomplete = false; let bypassFlow: FlowSwitchClause | undefined; for (const antecedent of flow.antecedents!) { - if (!bypassFlow && antecedent.flags & FlowFlags.SwitchClause && (antecedent as FlowSwitchClause).node.clauseStart === (antecedent as FlowSwitchClause).node.clauseEnd) { + if (!bypassFlow && antecedent.flags & FlowFlags.SwitchClause && (antecedent as FlowSwitchClause).clauseStart === (antecedent as FlowSwitchClause).clauseEnd) { // The antecedent is the bypass branch of a potentially exhaustive switch statement. bypassFlow = antecedent as FlowSwitchClause; continue; @@ -28237,7 +28236,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // If the bypass flow contributes a type we haven't seen yet and the switch statement // isn't exhaustive, process the bypass flow type. Since exhaustiveness checks increase // the risk of circularities, we only want to perform them when they make a difference. - if (!(type.flags & TypeFlags.Never) && !contains(antecedentTypes, type) && !isExhaustiveSwitchStatement(bypassFlow.node.switchStatement)) { + if (!(type.flags & TypeFlags.Never) && !contains(antecedentTypes, type) && !isExhaustiveSwitchStatement(bypassFlow.node)) { if (type === declaredType && declaredType === initialType) { return type; } diff --git a/src/compiler/debug.ts b/src/compiler/debug.ts index 522cfdd11c7b6..d9c425dcaa97d 100644 --- a/src/compiler/debug.ts +++ b/src/compiler/debug.ts @@ -1096,15 +1096,10 @@ m2: ${(this.mapper2 as unknown as DebugTypeMapper).__debugToString().split("\n") if (circular) { text = `${text}#${getDebugFlowNodeId(flowNode)}`; } - if (hasNode(flowNode)) { - if (flowNode.node) { - text += ` (${getNodeText(flowNode.node)})`; - } - } - else if (isFlowSwitchClause(flowNode)) { + if (isFlowSwitchClause(flowNode)) { const clauses: string[] = []; - for (let i = flowNode.node.clauseStart; i < flowNode.node.clauseEnd; i++) { - const clause = flowNode.node.switchStatement.caseBlock.clauses[i]; + for (let i = flowNode.clauseStart; i < flowNode.clauseEnd; i++) { + const clause = flowNode.node.caseBlock.clauses[i]; if (isDefaultClause(clause)) { clauses.push("default"); } @@ -1114,6 +1109,11 @@ m2: ${(this.mapper2 as unknown as DebugTypeMapper).__debugToString().split("\n") } text += ` (${clauses.join(", ")})`; } + else if (hasNode(flowNode)) { + if (flowNode.node) { + text += ` (${getNodeText(flowNode.node)})`; + } + } return circular === "circularity" ? `Circular(${text})` : text; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 73c69b83e5adc..86fa302d3f500 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4102,14 +4102,14 @@ export type FlowNode = export interface FlowNodeBase { flags: FlowFlags; - id: number | undefined; // Node id used by flow type cache in checker + id?: number; // Node id used by flow type cache in checker node: unknown; antecedent: FlowNode | undefined; antecedents: FlowNode[] | undefined; } export interface FlowUnreachable extends FlowNodeBase { - node: undefined, + node: undefined; antecedent: undefined; antecedents: undefined; } @@ -4154,13 +4154,9 @@ export interface FlowCondition extends FlowNodeBase { // dprint-ignore export interface FlowSwitchClause extends FlowNodeBase { - node: FlowSwitchClauseInfo; + node: SwitchStatement; antecedent: FlowNode; antecedents: undefined; -} - -export interface FlowSwitchClauseInfo { - switchStatement: SwitchStatement; clauseStart: number; // Start index of case/default clause range clauseEnd: number; // End index of case/default clause range } From 87c46f03f767af594ff053df95f18d6101e6e3fa Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 28 Mar 2024 09:25:48 -0700 Subject: [PATCH 05/14] Accept new API baselines --- tests/baselines/reference/api/typescript.d.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 7cd352e911ff1..6bb4c70419695 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -5813,7 +5813,10 @@ declare namespace ts { type FlowNode = FlowUnreachable | FlowStart | FlowLabel | FlowAssignment | FlowCondition | FlowSwitchClause | FlowArrayMutation | FlowCall | FlowReduceLabel; interface FlowNodeBase { flags: FlowFlags; - id: number | undefined; + id?: number; + node: unknown; + antecedent: FlowNode | undefined; + antecedents: FlowNode[] | undefined; } interface FlowUnreachable extends FlowNodeBase { node: undefined; @@ -5846,12 +5849,9 @@ declare namespace ts { antecedents: undefined; } interface FlowSwitchClause extends FlowNodeBase { - node: FlowSwitchClauseInfo; + node: SwitchStatement; antecedent: FlowNode; antecedents: undefined; - } - interface FlowSwitchClauseInfo { - switchStatement: SwitchStatement; clauseStart: number; clauseEnd: number; } @@ -9244,9 +9244,10 @@ declare namespace ts { function createFlowNode(flags: FlowFlags.BranchLabel | FlowFlags.LoopLabel): FlowLabel; function createFlowNode(flags: FlowFlags.Assignment | FlowFlags.ArrayMutation, node: Expression | VariableDeclaration | BindingElement, antecedent: FlowNode): FlowAssignment | FlowArrayMutation; function createFlowNode(flags: FlowFlags.TrueCondition | FlowFlags.FalseCondition, node: Expression, antecedent: FlowNode): FlowCondition; - function createFlowNode(flags: FlowFlags.SwitchClause, node: FlowSwitchClauseInfo, antecedent: FlowNode): FlowSwitchClause; + function createFlowNode(flags: FlowFlags.SwitchClause, node: SwitchStatement, antecedent: FlowNode): FlowSwitchClause; function createFlowNode(flags: FlowFlags.Call, node: CallExpression, antecedent: FlowNode): FlowCall; function createFlowNode(flags: FlowFlags.ReduceLabel, node: FlowLabel, antecedent: FlowNode, antecedents: FlowNode[]): FlowReduceLabel; + function createFlowNode(flags: FlowFlags, node?: unknown, antecedent?: FlowNode, antecedents?: FlowNode[]): FlowNode; /** * Visits a Node using the supplied visitor, possibly returning a new Node in its place. * From 654df904364925ecf5bc1ff5d95d54faa2cb7b35 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 28 Mar 2024 09:52:02 -0700 Subject: [PATCH 06/14] Cache incomplete types --- src/compiler/checker.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ac9e5a8c44c77..019ccc6cab8cb 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -418,6 +418,7 @@ import { ImportOrExportSpecifier, ImportSpecifier, ImportTypeNode, + IncompleteType, IndexedAccessType, IndexedAccessTypeNode, IndexFlags, @@ -1953,6 +1954,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { var subtypeReductionCache = new Map(); var decoratorContextOverrideTypeCache = new Map(); var cachedTypes = new Map(); + var incompleteTypes: IncompleteType[] = []; var evolvingArrayTypes: EvolvingArrayType[] = []; var undefinedProperties: SymbolTable = new Map(); var markerTypes = new Set(); @@ -27517,7 +27519,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function createFlowType(type: Type, incomplete: boolean): FlowType { - return incomplete ? { flags: 0, type: type.flags & TypeFlags.Never ? silentNeverType : type } : type; + return incomplete ? + (incompleteTypes[type.id] ??= { flags: 0, type: type.flags & TypeFlags.Never ? silentNeverType : type }) : + type; } // An evolving array type tracks the element types that have so far been seen in an From efe8335875fe770fd30ab07241bab355f766567f Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sun, 31 Mar 2024 15:02:20 -0700 Subject: [PATCH 07/14] Same shape for all nodes except FlowSwitchClause --- src/compiler/binder.ts | 41 ++++++++++++++++------------------------- src/compiler/checker.ts | 6 +++--- src/compiler/debug.ts | 1 + 3 files changed, 20 insertions(+), 28 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index d571a507facfb..0fc6657454d9b 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -504,17 +504,8 @@ export const enum ContainerFlags { IsObjectLiteralOrClassExpressionMethodOrAccessor = 1 << 7, } -export function createFlowNode(flags: FlowFlags.Unreachable): FlowUnreachable; -export function createFlowNode(flags: FlowFlags.Start): FlowStart; -export function createFlowNode(flags: FlowFlags.BranchLabel | FlowFlags.LoopLabel): FlowLabel; -export function createFlowNode(flags: FlowFlags.Assignment | FlowFlags.ArrayMutation, node: Expression | VariableDeclaration | BindingElement, antecedent: FlowNode): FlowAssignment | FlowArrayMutation; -export function createFlowNode(flags: FlowFlags.TrueCondition | FlowFlags.FalseCondition, node: Expression, antecedent: FlowNode): FlowCondition; -export function createFlowNode(flags: FlowFlags.SwitchClause, node: SwitchStatement, antecedent: FlowNode): FlowSwitchClause; -export function createFlowNode(flags: FlowFlags.Call, node: CallExpression, antecedent: FlowNode): FlowCall; -export function createFlowNode(flags: FlowFlags.ReduceLabel, node: FlowLabel, antecedent: FlowNode, antecedents: FlowNode[]): FlowReduceLabel; -export function createFlowNode(flags: FlowFlags, node?: unknown, antecedent?: FlowNode, antecedents?: FlowNode[]): FlowNode; -export function createFlowNode(flags: FlowFlags, node?: unknown, antecedent?: FlowNode, antecedents?: FlowNode[]): FlowNodeBase { - return { flags, node, antecedent, antecedents }; +export function createFlowNode(flags: FlowFlags, node: unknown, antecedent: FlowNode | undefined, antecedents: FlowNode[] | undefined): FlowNode { + return Debug.attachFlowNodeDebugInfo({ flags, id: undefined, node, antecedent, antecedents } as FlowNode); } const binder = /* @__PURE__ */ createBinder(); @@ -574,8 +565,8 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { var Symbol: new (flags: SymbolFlags, name: __String) => Symbol; var classifiableNames: Set<__String>; - var unreachableFlow = createFlowNode(FlowFlags.Unreachable); - var reportedUnreachableFlow = createFlowNode(FlowFlags.Unreachable); + var unreachableFlow = createFlowNode(FlowFlags.Unreachable, /*node*/ undefined, /*antecedent*/ undefined, /*antecedents*/ undefined); + var reportedUnreachableFlow = createFlowNode(FlowFlags.Unreachable, /*node*/ undefined, /*antecedent*/ undefined, /*antecedents*/ undefined); var bindBinaryExpressionFlow = createBindBinaryExpressionFlow(); /* eslint-enable no-var */ @@ -1029,7 +1020,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { // 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) { - currentFlow = createFlowNode(FlowFlags.Start); + currentFlow = createFlowNode(FlowFlags.Start, /*node*/ undefined, /*antecedent*/ undefined, /*antecedents*/ undefined); if (containerFlags & (ContainerFlags.IsFunctionExpression | ContainerFlags.IsObjectLiteralOrClassExpressionMethodOrAccessor)) { currentFlow.node = node as FunctionExpression | ArrowFunction | MethodDeclaration | GetAccessorDeclaration | SetAccessorDeclaration; } @@ -1336,15 +1327,15 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { } function createBranchLabel() { - return createFlowNode(FlowFlags.BranchLabel); + return createFlowNode(FlowFlags.BranchLabel, /*node*/ undefined, /*antecedent*/ undefined, /*antecedents*/ undefined) as FlowLabel; } function createLoopLabel() { - return createFlowNode(FlowFlags.LoopLabel); + return createFlowNode(FlowFlags.LoopLabel, /*node*/ undefined, /*antecedent*/ undefined, /*antecedents*/ undefined) as FlowLabel; } function createReduceLabel(target: FlowLabel, antecedents: FlowNode[], antecedent: FlowNode) { - return createFlowNode(FlowFlags.ReduceLabel, target, antecedent, antecedents); + return createFlowNode(FlowFlags.ReduceLabel, target, antecedent, antecedents) as FlowReduceLabel; } function setFlowNodeReferenced(flow: FlowNode) { @@ -1377,29 +1368,29 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { return antecedent; } setFlowNodeReferenced(antecedent); - return createFlowNode(flags, expression, antecedent); + return createFlowNode(flags, expression, antecedent, /*antecedents*/ undefined) as FlowCondition; } function createFlowSwitchClause(antecedent: FlowNode, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number) { setFlowNodeReferenced(antecedent); - const result = createFlowNode(FlowFlags.SwitchClause, switchStatement, antecedent); + const result = createFlowNode(FlowFlags.SwitchClause, switchStatement, antecedent, /*antecedents*/ undefined) as FlowSwitchClause; result.clauseStart = clauseStart; result.clauseEnd = clauseEnd; return result; } - function createFlowMutation(flags: FlowFlags.Assignment | FlowFlags.ArrayMutation, antecedent: FlowNode, node: Expression | VariableDeclaration | ArrayBindingElement): FlowNode { + function createFlowMutation(flags: FlowFlags.Assignment | FlowFlags.ArrayMutation, antecedent: FlowNode, node: Expression | VariableDeclaration | ArrayBindingElement) { setFlowNodeReferenced(antecedent); - const result = createFlowNode(flags, node, antecedent); + const result = createFlowNode(flags, node, antecedent, /*antecedents*/ undefined) as FlowAssignment | FlowArrayMutation; if (currentExceptionTarget) { addAntecedent(currentExceptionTarget, result); } return result; } - function createFlowCall(antecedent: FlowNode, node: CallExpression): FlowNode { + function createFlowCall(antecedent: FlowNode, node: CallExpression) { setFlowNodeReferenced(antecedent); - return createFlowNode(FlowFlags.Call, node, antecedent); + return createFlowNode(FlowFlags.Call, node, antecedent, /*antecedents*/ undefined) as FlowCall; } function finishFlowLabel(flow: FlowLabel): FlowNode { @@ -2423,7 +2414,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { const host = typeAlias.parent.parent; container = (getEnclosingContainer(host) as IsContainer | undefined) || file; blockScopeContainer = (getEnclosingBlockScopeContainer(host) as IsBlockScopedContainer | undefined) || file; - currentFlow = createFlowNode(FlowFlags.Start); + currentFlow = createFlowNode(FlowFlags.Start, /*node*/ undefined, /*antecedent*/ undefined, /*antecedents*/ undefined); parent = typeAlias; bind(typeAlias.typeExpression); const declName = getNameOfDeclaration(typeAlias); @@ -2495,7 +2486,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { const enclosingBlockScopeContainer = host ? getEnclosingBlockScopeContainer(host) as IsBlockScopedContainer | undefined : undefined; container = enclosingContainer || file; blockScopeContainer = enclosingBlockScopeContainer || file; - currentFlow = createFlowNode(FlowFlags.Start); + currentFlow = createFlowNode(FlowFlags.Start, /*node*/ undefined, /*antecedent*/ undefined, /*antecedents*/ undefined); parent = jsDocImportTag; bind(jsDocImportTag.importClause); } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 019ccc6cab8cb..3d70559a1ad7b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -37670,15 +37670,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function checkIfExpressionRefinesParameter(func: FunctionLikeDeclaration, expr: Expression, param: ParameterDeclaration, initType: Type): Type | undefined { const antecedent = (expr as Expression & { flowNode?: FlowNode; }).flowNode || expr.parent.kind === SyntaxKind.ReturnStatement && (expr.parent as ReturnStatement).flowNode || - createFlowNode(FlowFlags.Start); - const trueCondition = createFlowNode(FlowFlags.TrueCondition, expr, antecedent); + createFlowNode(FlowFlags.Start, /*node*/ undefined, /*antecedent*/ undefined, /*antecedents*/ undefined); + const trueCondition = createFlowNode(FlowFlags.TrueCondition, expr, antecedent, /*antecedents*/ undefined); const trueType = getFlowTypeOfReference(param.name, initType, initType, func, trueCondition); if (trueType === initType) return undefined; // "x is T" means that x is T if and only if it returns true. If it returns false then x is not T. // This means that if the function is called with an argument of type trueType, there can't be anything left in the `else` branch. It must reduce to `never`. - const falseCondition = createFlowNode(FlowFlags.FalseCondition, expr, antecedent); + const falseCondition = createFlowNode(FlowFlags.FalseCondition, expr, antecedent, /*antecedents*/ undefined); const falseSubtype = getFlowTypeOfReference(param.name, trueType, trueType, func, falseCondition); return falseSubtype.flags & TypeFlags.Never ? trueType : undefined; } diff --git a/src/compiler/debug.ts b/src/compiler/debug.ts index d9c425dcaa97d..9e902fd4aa56f 100644 --- a/src/compiler/debug.ts +++ b/src/compiler/debug.ts @@ -559,6 +559,7 @@ export namespace Debug { attachFlowNodeDebugInfoWorker(flowNode); } } + return flowNode; } let nodeArrayProto: NodeArray | undefined; From 5109c05bafb1ac573035df75220d469e6961e33d Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sun, 31 Mar 2024 15:13:21 -0700 Subject: [PATCH 08/14] Update imports --- src/compiler/binder.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 0fc6657454d9b..d7bece1dad59e 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -72,11 +72,8 @@ import { FlowFlags, FlowLabel, FlowNode, - FlowNodeBase, FlowReduceLabel, - FlowStart, FlowSwitchClause, - FlowUnreachable, forEach, forEachChild, ForInOrOfStatement, From d0d2e3dcc4ac0bbc84a70437b42c1f938d916245 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sun, 31 Mar 2024 15:13:46 -0700 Subject: [PATCH 09/14] Accept new baselines --- tests/baselines/reference/api/typescript.d.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 6bb4c70419695..00e5ca70ecaf1 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -9239,15 +9239,7 @@ declare namespace ts { clear(): void; } type PerModuleNameCache = PerNonRelativeNameCache; - function createFlowNode(flags: FlowFlags.Unreachable): FlowUnreachable; - function createFlowNode(flags: FlowFlags.Start): FlowStart; - function createFlowNode(flags: FlowFlags.BranchLabel | FlowFlags.LoopLabel): FlowLabel; - function createFlowNode(flags: FlowFlags.Assignment | FlowFlags.ArrayMutation, node: Expression | VariableDeclaration | BindingElement, antecedent: FlowNode): FlowAssignment | FlowArrayMutation; - function createFlowNode(flags: FlowFlags.TrueCondition | FlowFlags.FalseCondition, node: Expression, antecedent: FlowNode): FlowCondition; - function createFlowNode(flags: FlowFlags.SwitchClause, node: SwitchStatement, antecedent: FlowNode): FlowSwitchClause; - function createFlowNode(flags: FlowFlags.Call, node: CallExpression, antecedent: FlowNode): FlowCall; - function createFlowNode(flags: FlowFlags.ReduceLabel, node: FlowLabel, antecedent: FlowNode, antecedents: FlowNode[]): FlowReduceLabel; - function createFlowNode(flags: FlowFlags, node?: unknown, antecedent?: FlowNode, antecedents?: FlowNode[]): FlowNode; + function createFlowNode(flags: FlowFlags, node: unknown, antecedent: FlowNode | undefined, antecedents: FlowNode[] | undefined): FlowNode; /** * Visits a Node using the supplied visitor, possibly returning a new Node in its place. * From 2abbf9bf304c242cb55afc46b748fc85a5f2c8a1 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 2 Apr 2024 06:58:52 -0700 Subject: [PATCH 10/14] Single antecedent property --- src/compiler/binder.ts | 51 ++++++++++++------------ src/compiler/checker.ts | 86 +++++++++++++++++++++-------------------- src/compiler/debug.ts | 11 +++--- src/compiler/types.ts | 32 +++++++-------- 4 files changed, 89 insertions(+), 91 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index d7bece1dad59e..08910f718f914 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -501,8 +501,8 @@ export const enum ContainerFlags { IsObjectLiteralOrClassExpressionMethodOrAccessor = 1 << 7, } -export function createFlowNode(flags: FlowFlags, node: unknown, antecedent: FlowNode | undefined, antecedents: FlowNode[] | undefined): FlowNode { - return Debug.attachFlowNodeDebugInfo({ flags, id: undefined, node, antecedent, antecedents } as FlowNode); +export function createFlowNode(flags: FlowFlags, node: unknown, antecedent: FlowNode | FlowNode[] | undefined): FlowNode { + return Debug.attachFlowNodeDebugInfo({ flags, id: 0, node, antecedent } as FlowNode); } const binder = /* @__PURE__ */ createBinder(); @@ -562,8 +562,8 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { var Symbol: new (flags: SymbolFlags, name: __String) => Symbol; var classifiableNames: Set<__String>; - var unreachableFlow = createFlowNode(FlowFlags.Unreachable, /*node*/ undefined, /*antecedent*/ undefined, /*antecedents*/ undefined); - var reportedUnreachableFlow = createFlowNode(FlowFlags.Unreachable, /*node*/ undefined, /*antecedent*/ undefined, /*antecedents*/ undefined); + var unreachableFlow = createFlowNode(FlowFlags.Unreachable, /*node*/ undefined, /*antecedent*/ undefined); + var reportedUnreachableFlow = createFlowNode(FlowFlags.Unreachable, /*node*/ undefined, /*antecedent*/ undefined); var bindBinaryExpressionFlow = createBindBinaryExpressionFlow(); /* eslint-enable no-var */ @@ -1017,7 +1017,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { // 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) { - currentFlow = createFlowNode(FlowFlags.Start, /*node*/ undefined, /*antecedent*/ undefined, /*antecedents*/ undefined); + currentFlow = createFlowNode(FlowFlags.Start, /*node*/ undefined, /*antecedent*/ undefined); if (containerFlags & (ContainerFlags.IsFunctionExpression | ContainerFlags.IsObjectLiteralOrClassExpressionMethodOrAccessor)) { currentFlow.node = node as FunctionExpression | ArrowFunction | MethodDeclaration | GetAccessorDeclaration | SetAccessorDeclaration; } @@ -1324,15 +1324,15 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { } function createBranchLabel() { - return createFlowNode(FlowFlags.BranchLabel, /*node*/ undefined, /*antecedent*/ undefined, /*antecedents*/ undefined) as FlowLabel; + return createFlowNode(FlowFlags.BranchLabel, /*node*/ undefined, /*antecedent*/ undefined) as FlowLabel; } function createLoopLabel() { - return createFlowNode(FlowFlags.LoopLabel, /*node*/ undefined, /*antecedent*/ undefined, /*antecedents*/ undefined) as FlowLabel; + return createFlowNode(FlowFlags.LoopLabel, /*node*/ undefined, /*antecedent*/ undefined) as FlowLabel; } function createReduceLabel(target: FlowLabel, antecedents: FlowNode[], antecedent: FlowNode) { - return createFlowNode(FlowFlags.ReduceLabel, target, antecedent, antecedents) as FlowReduceLabel; + return createFlowNode(FlowFlags.ReduceLabel, { target, antecedents }, antecedent) as FlowReduceLabel; } function setFlowNodeReferenced(flow: FlowNode) { @@ -1341,8 +1341,8 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { } function addAntecedent(label: FlowLabel, antecedent: FlowNode): void { - if (!(antecedent.flags & FlowFlags.Unreachable) && !contains(label.antecedents, antecedent)) { - (label.antecedents || (label.antecedents = [])).push(antecedent); + if (!(antecedent.flags & FlowFlags.Unreachable) && !contains(label.antecedent, antecedent)) { + (label.antecedent || (label.antecedent = [])).push(antecedent); setFlowNodeReferenced(antecedent); } } @@ -1365,20 +1365,17 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { return antecedent; } setFlowNodeReferenced(antecedent); - return createFlowNode(flags, expression, antecedent, /*antecedents*/ undefined) as FlowCondition; + return createFlowNode(flags, expression, antecedent) as FlowCondition; } function createFlowSwitchClause(antecedent: FlowNode, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number) { setFlowNodeReferenced(antecedent); - const result = createFlowNode(FlowFlags.SwitchClause, switchStatement, antecedent, /*antecedents*/ undefined) as FlowSwitchClause; - result.clauseStart = clauseStart; - result.clauseEnd = clauseEnd; - return result; + return createFlowNode(FlowFlags.SwitchClause, { switchStatement, clauseStart, clauseEnd }, antecedent) as FlowSwitchClause; } function createFlowMutation(flags: FlowFlags.Assignment | FlowFlags.ArrayMutation, antecedent: FlowNode, node: Expression | VariableDeclaration | ArrayBindingElement) { setFlowNodeReferenced(antecedent); - const result = createFlowNode(flags, node, antecedent, /*antecedents*/ undefined) as FlowAssignment | FlowArrayMutation; + const result = createFlowNode(flags, node, antecedent) as FlowAssignment | FlowArrayMutation; if (currentExceptionTarget) { addAntecedent(currentExceptionTarget, result); } @@ -1387,11 +1384,11 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { function createFlowCall(antecedent: FlowNode, node: CallExpression) { setFlowNodeReferenced(antecedent); - return createFlowNode(FlowFlags.Call, node, antecedent, /*antecedents*/ undefined) as FlowCall; + return createFlowNode(FlowFlags.Call, node, antecedent) as FlowCall; } function finishFlowLabel(flow: FlowLabel): FlowNode { - const antecedents = flow.antecedents; + const antecedents = flow.antecedent; if (!antecedents) { return unreachableFlow; } @@ -1648,7 +1645,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { // set of antecedents for the pre-finally label. As control flow analysis passes by a ReduceLabel // node, the pre-finally label is temporarily switched to the reduced antecedent set. const finallyLabel = createBranchLabel(); - finallyLabel.antecedents = concatenate(concatenate(normalExitLabel.antecedents, exceptionLabel.antecedents), returnLabel.antecedents); + finallyLabel.antecedent = concatenate(concatenate(normalExitLabel.antecedent, exceptionLabel.antecedent), returnLabel.antecedent); currentFlow = finallyLabel; bind(node.finallyBlock); if (currentFlow.flags & FlowFlags.Unreachable) { @@ -1658,18 +1655,18 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { else { // If we have an IIFE return target and return statements in the try or catch blocks, add a control // flow that goes back through the finally block and back through only the return statements. - if (currentReturnTarget && returnLabel.antecedents) { - addAntecedent(currentReturnTarget, createReduceLabel(finallyLabel, returnLabel.antecedents, currentFlow)); + if (currentReturnTarget && returnLabel.antecedent) { + addAntecedent(currentReturnTarget, createReduceLabel(finallyLabel, returnLabel.antecedent, currentFlow)); } // If we have an outer exception target (i.e. a containing try-finally or try-catch-finally), add a // control flow that goes back through the finally blok and back through each possible exception source. - if (currentExceptionTarget && exceptionLabel.antecedents) { - addAntecedent(currentExceptionTarget, createReduceLabel(finallyLabel, exceptionLabel.antecedents, currentFlow)); + if (currentExceptionTarget && exceptionLabel.antecedent) { + addAntecedent(currentExceptionTarget, createReduceLabel(finallyLabel, exceptionLabel.antecedent, currentFlow)); } // If the end of the finally block is reachable, but the end of the try and catch blocks are not, // convert the current flow to unreachable. For example, 'try { return 1; } finally { ... }' should // result in an unreachable current control flow. - currentFlow = normalExitLabel.antecedents ? createReduceLabel(finallyLabel, normalExitLabel.antecedents, currentFlow) : unreachableFlow; + currentFlow = normalExitLabel.antecedent ? createReduceLabel(finallyLabel, normalExitLabel.antecedent, currentFlow) : unreachableFlow; } } else { @@ -1690,7 +1687,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { // We mark a switch statement as possibly exhaustive if it has no default clause and if all // case clauses have unreachable end points (e.g. they all return). Note, we no longer need // this property in control flow analysis, it's there only for backwards compatibility. - node.possiblyExhaustive = !hasDefault && !postSwitchLabel.antecedents; + node.possiblyExhaustive = !hasDefault && !postSwitchLabel.antecedent; if (!hasDefault) { addAntecedent(postSwitchLabel, createFlowSwitchClause(preSwitchCaseFlow, node, 0, 0)); } @@ -2411,7 +2408,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { const host = typeAlias.parent.parent; container = (getEnclosingContainer(host) as IsContainer | undefined) || file; blockScopeContainer = (getEnclosingBlockScopeContainer(host) as IsBlockScopedContainer | undefined) || file; - currentFlow = createFlowNode(FlowFlags.Start, /*node*/ undefined, /*antecedent*/ undefined, /*antecedents*/ undefined); + currentFlow = createFlowNode(FlowFlags.Start, /*node*/ undefined, /*antecedent*/ undefined); parent = typeAlias; bind(typeAlias.typeExpression); const declName = getNameOfDeclaration(typeAlias); @@ -2483,7 +2480,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { const enclosingBlockScopeContainer = host ? getEnclosingBlockScopeContainer(host) as IsBlockScopedContainer | undefined : undefined; container = enclosingContainer || file; blockScopeContainer = enclosingBlockScopeContainer || file; - currentFlow = createFlowNode(FlowFlags.Start, /*node*/ undefined, /*antecedent*/ undefined, /*antecedents*/ undefined); + currentFlow = createFlowNode(FlowFlags.Start, /*node*/ undefined, /*antecedent*/ undefined); parent = jsDocImportTag; bind(jsDocImportTag.importClause); } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3d70559a1ad7b..936a582d572b9 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -210,6 +210,7 @@ import { FlowReduceLabel, FlowStart, FlowSwitchClause, + FlowSwitchClauseData, FlowType, forEach, forEachChild, @@ -26984,7 +26985,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function getFlowNodeId(flow: FlowNode): number { - if (!flow.id || flow.id < 0) { + if (flow.id <= 0) { flow.id = nextFlowId; nextFlowId++; } @@ -27785,10 +27786,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } else if (flags & FlowFlags.BranchLabel) { // A branching point is reachable if any branch is reachable. - return some((flow as FlowLabel).antecedents, f => isReachableFlowNodeWorker(f, /*noCacheCheck*/ false)); + return some((flow as FlowLabel).antecedent, f => isReachableFlowNodeWorker(f, /*noCacheCheck*/ false)); } else if (flags & FlowFlags.LoopLabel) { - const antecedents = (flow as FlowLabel).antecedents; + const antecedents = (flow as FlowLabel).antecedent; if (antecedents === undefined || antecedents.length === 0) { return false; } @@ -27798,7 +27799,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { else if (flags & FlowFlags.SwitchClause) { // The control flow path representing an unmatched value in a switch statement with // no default clause is unreachable if the switch statement is exhaustive. - if ((flow as FlowSwitchClause).clauseStart === (flow as FlowSwitchClause).clauseEnd && isExhaustiveSwitchStatement((flow as FlowSwitchClause).node)) { + const data = (flow as FlowSwitchClause).node; + if (data.clauseStart === data.clauseEnd && isExhaustiveSwitchStatement(data.switchStatement)) { return false; } flow = (flow as FlowSwitchClause).antecedent; @@ -27806,11 +27808,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { else if (flags & FlowFlags.ReduceLabel) { // Cache is unreliable once we start adjusting labels lastFlowNode = undefined; - const target = (flow as FlowReduceLabel).node; - const saveAntecedents = target.antecedents; - target.antecedents = (flow as FlowReduceLabel).antecedents; + const target = (flow as FlowReduceLabel).node.target; + const saveAntecedents = target.antecedent; + target.antecedent = (flow as FlowReduceLabel).node.antecedents; const result = isReachableFlowNodeWorker((flow as FlowReduceLabel).antecedent, /*noCacheCheck*/ false); - target.antecedents = saveAntecedents; + target.antecedent = saveAntecedents; return result; } else { @@ -27843,18 +27845,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } else if (flags & FlowFlags.BranchLabel) { // A branching point is post-super if every branch is post-super. - return every((flow as FlowLabel).antecedents, f => isPostSuperFlowNode(f, /*noCacheCheck*/ false)); + return every((flow as FlowLabel).antecedent, f => isPostSuperFlowNode(f, /*noCacheCheck*/ false)); } else if (flags & FlowFlags.LoopLabel) { // A loop is post-super if the control flow path that leads to the top is post-super. - flow = (flow as FlowLabel).antecedents![0]; + flow = (flow as FlowLabel).antecedent![0]; } else if (flags & FlowFlags.ReduceLabel) { - const target = (flow as FlowReduceLabel).node; - const saveAntecedents = target.antecedents; - target.antecedents = (flow as FlowReduceLabel).antecedents; + const target = (flow as FlowReduceLabel).node.target; + const saveAntecedents = target.antecedent; + target.antecedent = (flow as FlowReduceLabel).node.antecedents; const result = isPostSuperFlowNode((flow as FlowReduceLabel).antecedent, /*noCacheCheck*/ false); - target.antecedents = saveAntecedents; + target.antecedent = saveAntecedents; return result; } else { @@ -27967,8 +27969,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { type = getTypeAtSwitchClause(flow as FlowSwitchClause); } else if (flags & FlowFlags.Label) { - if ((flow as FlowLabel).antecedents!.length === 1) { - flow = (flow as FlowLabel).antecedents![0]; + if ((flow as FlowLabel).antecedent!.length === 1) { + flow = (flow as FlowLabel).antecedent![0]; continue; } type = flags & FlowFlags.BranchLabel ? @@ -27983,11 +27985,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } else if (flags & FlowFlags.ReduceLabel) { - const target = (flow as FlowReduceLabel).node; - const saveAntecedents = target.antecedents; - target.antecedents = (flow as FlowReduceLabel).antecedents; + const target = (flow as FlowReduceLabel).node.target; + const saveAntecedents = target.antecedent; + target.antecedent = (flow as FlowReduceLabel).node.antecedents; type = getTypeAtFlowNode((flow as FlowReduceLabel).antecedent); - target.antecedents = saveAntecedents; + target.antecedent = saveAntecedents; } else if (flags & FlowFlags.Start) { // Check if we should continue with the control flow of the containing function. @@ -28174,30 +28176,30 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function getTypeAtSwitchClause(flow: FlowSwitchClause): FlowType { - const expr = skipParentheses(flow.node.expression); + const expr = skipParentheses(flow.node.switchStatement.expression); const flowType = getTypeAtFlowNode(flow.antecedent); let type = getTypeFromFlowType(flowType); if (isMatchingReference(reference, expr)) { - type = narrowTypeBySwitchOnDiscriminant(type, flow.node, flow.clauseStart, flow.clauseEnd); + type = narrowTypeBySwitchOnDiscriminant(type, flow.node); } else if (expr.kind === SyntaxKind.TypeOfExpression && isMatchingReference(reference, (expr as TypeOfExpression).expression)) { - type = narrowTypeBySwitchOnTypeOf(type, flow.node, flow.clauseStart, flow.clauseEnd); + type = narrowTypeBySwitchOnTypeOf(type, flow.node); } else if (expr.kind === SyntaxKind.TrueKeyword) { - type = narrowTypeBySwitchOnTrue(type, flow.node, flow.clauseStart, flow.clauseEnd); + type = narrowTypeBySwitchOnTrue(type, flow.node); } else { if (strictNullChecks) { if (optionalChainContainsReference(expr, reference)) { - type = narrowTypeBySwitchOptionalChainContainment(type, flow.node, flow.clauseStart, flow.clauseEnd, t => !(t.flags & (TypeFlags.Undefined | TypeFlags.Never))); + type = narrowTypeBySwitchOptionalChainContainment(type, flow.node, t => !(t.flags & (TypeFlags.Undefined | TypeFlags.Never))); } else if (expr.kind === SyntaxKind.TypeOfExpression && optionalChainContainsReference((expr as TypeOfExpression).expression, reference)) { - type = narrowTypeBySwitchOptionalChainContainment(type, flow.node, flow.clauseStart, flow.clauseEnd, t => !(t.flags & TypeFlags.Never || t.flags & TypeFlags.StringLiteral && (t as StringLiteralType).value === "undefined")); + type = narrowTypeBySwitchOptionalChainContainment(type, flow.node, t => !(t.flags & TypeFlags.Never || t.flags & TypeFlags.StringLiteral && (t as StringLiteralType).value === "undefined")); } } const access = getDiscriminantPropertyAccess(expr, type); if (access) { - type = narrowTypeBySwitchOnDiscriminantProperty(type, access, flow.node, flow.clauseStart, flow.clauseEnd); + type = narrowTypeBySwitchOnDiscriminantProperty(type, access, flow.node); } } return createFlowType(type, isIncomplete(flowType)); @@ -28208,8 +28210,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { let subtypeReduction = false; let seenIncomplete = false; let bypassFlow: FlowSwitchClause | undefined; - for (const antecedent of flow.antecedents!) { - if (!bypassFlow && antecedent.flags & FlowFlags.SwitchClause && (antecedent as FlowSwitchClause).clauseStart === (antecedent as FlowSwitchClause).clauseEnd) { + for (const antecedent of flow.antecedent!) { + if (!bypassFlow && antecedent.flags & FlowFlags.SwitchClause && (antecedent as FlowSwitchClause).node.clauseStart === (antecedent as FlowSwitchClause).node.clauseEnd) { // The antecedent is the bypass branch of a potentially exhaustive switch statement. bypassFlow = antecedent as FlowSwitchClause; continue; @@ -28240,7 +28242,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // If the bypass flow contributes a type we haven't seen yet and the switch statement // isn't exhaustive, process the bypass flow type. Since exhaustiveness checks increase // the risk of circularities, we only want to perform them when they make a difference. - if (!(type.flags & TypeFlags.Never) && !contains(antecedentTypes, type) && !isExhaustiveSwitchStatement(bypassFlow.node)) { + if (!(type.flags & TypeFlags.Never) && !contains(antecedentTypes, type) && !isExhaustiveSwitchStatement(bypassFlow.node.switchStatement)) { if (type === declaredType && declaredType === initialType) { return type; } @@ -28288,7 +28290,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const antecedentTypes: Type[] = []; let subtypeReduction = false; let firstAntecedentType: FlowType | undefined; - for (const antecedent of flow.antecedents!) { + for (const antecedent of flow.antecedent!) { let flowType; if (!firstAntecedentType) { // The first antecedent of a loop junction is always the non-looping control @@ -28452,15 +28454,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return narrowTypeByDiscriminant(type, access, t => narrowTypeByEquality(t, operator, value, assumeTrue)); } - function narrowTypeBySwitchOnDiscriminantProperty(type: Type, access: AccessExpression | BindingElement | ParameterDeclaration, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number) { - if (clauseStart < clauseEnd && type.flags & TypeFlags.Union && getKeyPropertyName(type as UnionType) === getAccessedPropertyName(access)) { - const clauseTypes = getSwitchClauseTypes(switchStatement).slice(clauseStart, clauseEnd); + function narrowTypeBySwitchOnDiscriminantProperty(type: Type, access: AccessExpression | BindingElement | ParameterDeclaration, data: FlowSwitchClauseData) { + if (data.clauseStart < data.clauseEnd && type.flags & TypeFlags.Union && getKeyPropertyName(type as UnionType) === getAccessedPropertyName(access)) { + const clauseTypes = getSwitchClauseTypes(data.switchStatement).slice(data.clauseStart, data.clauseEnd); const candidate = getUnionType(map(clauseTypes, t => getConstituentTypeForKeyType(type as UnionType, t) || unknownType)); if (candidate !== unknownType) { return candidate; } } - return narrowTypeByDiscriminant(type, access, t => narrowTypeBySwitchOnDiscriminant(t, switchStatement, clauseStart, clauseEnd)); + return narrowTypeByDiscriminant(type, access, t => narrowTypeBySwitchOnDiscriminant(t, data)); } function narrowTypeByTruthiness(type: Type, expr: Expression, assumeTrue: boolean): Type { @@ -28700,12 +28702,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { getAdjustedTypeWithFacts(type, typeofNEFacts.get(literal.text) || TypeFacts.TypeofNEHostObject); } - function narrowTypeBySwitchOptionalChainContainment(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number, clauseCheck: (type: Type) => boolean) { + function narrowTypeBySwitchOptionalChainContainment(type: Type, { switchStatement, clauseStart, clauseEnd }: FlowSwitchClauseData, clauseCheck: (type: Type) => boolean) { const everyClauseChecks = clauseStart !== clauseEnd && every(getSwitchClauseTypes(switchStatement).slice(clauseStart, clauseEnd), clauseCheck); return everyClauseChecks ? getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type; } - function narrowTypeBySwitchOnDiscriminant(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number) { + function narrowTypeBySwitchOnDiscriminant(type: Type, { switchStatement, clauseStart, clauseEnd }: FlowSwitchClauseData) { // We only narrow if all case expressions specify // values with unit types, except for the case where // `type` is unknown. In this instance we map object @@ -28786,7 +28788,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { neverType); } - function narrowTypeBySwitchOnTypeOf(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number): Type { + function narrowTypeBySwitchOnTypeOf(type: Type, { switchStatement, clauseStart, clauseEnd }: FlowSwitchClauseData): Type { const witnesses = getSwitchClauseTypeOfWitnesses(switchStatement); if (!witnesses) { return type; @@ -28804,7 +28806,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return getUnionType(map(clauseWitnesses, text => text ? narrowTypeByTypeName(type, text) : neverType)); } - function narrowTypeBySwitchOnTrue(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number): Type { + function narrowTypeBySwitchOnTrue(type: Type, { switchStatement, clauseStart, clauseEnd }: FlowSwitchClauseData): Type { const defaultIndex = findIndex(switchStatement.caseBlock.clauses, clause => clause.kind === SyntaxKind.DefaultClause); const hasDefaultClause = clauseStart === clauseEnd || (defaultIndex >= clauseStart && defaultIndex < clauseEnd); @@ -37670,15 +37672,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function checkIfExpressionRefinesParameter(func: FunctionLikeDeclaration, expr: Expression, param: ParameterDeclaration, initType: Type): Type | undefined { const antecedent = (expr as Expression & { flowNode?: FlowNode; }).flowNode || expr.parent.kind === SyntaxKind.ReturnStatement && (expr.parent as ReturnStatement).flowNode || - createFlowNode(FlowFlags.Start, /*node*/ undefined, /*antecedent*/ undefined, /*antecedents*/ undefined); - const trueCondition = createFlowNode(FlowFlags.TrueCondition, expr, antecedent, /*antecedents*/ undefined); + createFlowNode(FlowFlags.Start, /*node*/ undefined, /*antecedent*/ undefined); + const trueCondition = createFlowNode(FlowFlags.TrueCondition, expr, antecedent); const trueType = getFlowTypeOfReference(param.name, initType, initType, func, trueCondition); if (trueType === initType) return undefined; // "x is T" means that x is T if and only if it returns true. If it returns false then x is not T. // This means that if the function is called with an argument of type trueType, there can't be anything left in the `else` branch. It must reduce to `never`. - const falseCondition = createFlowNode(FlowFlags.FalseCondition, expr, antecedent, /*antecedents*/ undefined); + const falseCondition = createFlowNode(FlowFlags.FalseCondition, expr, antecedent); const falseSubtype = getFlowTypeOfReference(param.name, trueType, trueType, func, falseCondition); return falseSubtype.flags & TypeFlags.Never ? trueType : undefined; } diff --git a/src/compiler/debug.ts b/src/compiler/debug.ts index 9e902fd4aa56f..084c322637458 100644 --- a/src/compiler/debug.ts +++ b/src/compiler/debug.ts @@ -953,8 +953,8 @@ m2: ${(this.mapper2 as unknown as DebugTypeMapper).__debugToString().split("\n") return !!(f.flags & FlowFlags.SwitchClause); } - function hasAntecedents(f: FlowNode): f is FlowLabel & { antecedents: FlowNode[]; } { - return !!(f.flags & FlowFlags.Label) && !!(f as FlowLabel).antecedents; + function hasAntecedents(f: FlowNode): f is FlowLabel & { antecedent: FlowNode[] } { + return !!(f.flags & FlowFlags.Label) && !!(f as FlowLabel).antecedent; } function hasAntecedent(f: FlowNode): f is Extract { @@ -1008,7 +1008,7 @@ m2: ${(this.mapper2 as unknown as DebugTypeMapper).__debugToString().split("\n") links[id] = graphNode = { id, flowNode, edges: [], text: "", lane: -1, endLane: -1, level: -1, circular: false }; nodes.push(graphNode); if (hasAntecedents(flowNode)) { - for (const antecedent of flowNode.antecedents) { + for (const antecedent of flowNode.antecedent) { buildGraphEdge(graphNode, antecedent, seen); } } @@ -1099,8 +1099,9 @@ m2: ${(this.mapper2 as unknown as DebugTypeMapper).__debugToString().split("\n") } if (isFlowSwitchClause(flowNode)) { const clauses: string[] = []; - for (let i = flowNode.clauseStart; i < flowNode.clauseEnd; i++) { - const clause = flowNode.node.caseBlock.clauses[i]; + const { switchStatement, clauseStart, clauseEnd } = flowNode.node; + for (let i = clauseStart; i < clauseEnd; i++) { + const clause = switchStatement.caseBlock.clauses[i]; if (isDefaultClause(clause)) { clauses.push("default"); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 86fa302d3f500..2acd6a5b7c832 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4102,16 +4102,14 @@ export type FlowNode = export interface FlowNodeBase { flags: FlowFlags; - id?: number; // Node id used by flow type cache in checker - node: unknown; - antecedent: FlowNode | undefined; - antecedents: FlowNode[] | undefined; + id: number; // Node id used by flow type cache in checker + node: unknown; // Node or other data + antecedent: FlowNode | FlowNode[] | undefined; } export interface FlowUnreachable extends FlowNodeBase { node: undefined; antecedent: undefined; - antecedents: undefined; } // FlowStart represents the start of a control flow. For a function expression or arrow @@ -4120,14 +4118,12 @@ export interface FlowUnreachable extends FlowNodeBase { export interface FlowStart extends FlowNodeBase { node: FunctionExpression | ArrowFunction | MethodDeclaration | GetAccessorDeclaration | SetAccessorDeclaration | undefined; antecedent: undefined; - antecedents: undefined; } // FlowLabel represents a junction with multiple possible preceding control flows. export interface FlowLabel extends FlowNodeBase { node: undefined; - antecedent: undefined; - antecedents: FlowNode[] | undefined; + antecedent: FlowNode[] | undefined; } // FlowAssignment represents a node that assigns a value to a narrowable reference, @@ -4135,13 +4131,11 @@ export interface FlowLabel extends FlowNodeBase { export interface FlowAssignment extends FlowNodeBase { node: Expression | VariableDeclaration | BindingElement; antecedent: FlowNode; - antecedents: undefined; } export interface FlowCall extends FlowNodeBase { node: CallExpression; antecedent: FlowNode; - antecedents: undefined; } // FlowCondition represents a condition that is known to be true or false at the @@ -4149,14 +4143,15 @@ export interface FlowCall extends FlowNodeBase { export interface FlowCondition extends FlowNodeBase { node: Expression; antecedent: FlowNode; - antecedents: undefined; } -// dprint-ignore export interface FlowSwitchClause extends FlowNodeBase { - node: SwitchStatement; + node: FlowSwitchClauseData; antecedent: FlowNode; - antecedents: undefined; +} + +export interface FlowSwitchClauseData { + switchStatement: SwitchStatement; clauseStart: number; // Start index of case/default clause range clauseEnd: number; // End index of case/default clause range } @@ -4166,15 +4161,18 @@ export interface FlowSwitchClause extends FlowNodeBase { export interface FlowArrayMutation extends FlowNodeBase { node: CallExpression | BinaryExpression; antecedent: FlowNode; - antecedents: undefined; } export interface FlowReduceLabel extends FlowNodeBase { - node: FlowLabel; - antecedents: FlowNode[]; + node: FlowReduceLabelData; antecedent: FlowNode; } +export interface FlowReduceLabelData { + target: FlowLabel; + antecedents: FlowNode[]; +} + export type FlowType = Type | IncompleteType; // Incomplete types occur during control flow analysis of loops. An IncompleteType From a732459e79f758f0fb6aa7af4612adf40a8a38fa Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 2 Apr 2024 06:59:27 -0700 Subject: [PATCH 11/14] Accept new API baselines --- tests/baselines/reference/api/typescript.d.ts | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 00e5ca70ecaf1..629ec6f0c188a 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -5813,58 +5813,55 @@ declare namespace ts { type FlowNode = FlowUnreachable | FlowStart | FlowLabel | FlowAssignment | FlowCondition | FlowSwitchClause | FlowArrayMutation | FlowCall | FlowReduceLabel; interface FlowNodeBase { flags: FlowFlags; - id?: number; + id: number; node: unknown; - antecedent: FlowNode | undefined; - antecedents: FlowNode[] | undefined; + antecedent: FlowNode | FlowNode[] | undefined; } interface FlowUnreachable extends FlowNodeBase { node: undefined; antecedent: undefined; - antecedents: undefined; } interface FlowStart extends FlowNodeBase { node: FunctionExpression | ArrowFunction | MethodDeclaration | GetAccessorDeclaration | SetAccessorDeclaration | undefined; antecedent: undefined; - antecedents: undefined; } interface FlowLabel extends FlowNodeBase { node: undefined; - antecedent: undefined; - antecedents: FlowNode[] | undefined; + antecedent: FlowNode[] | undefined; } interface FlowAssignment extends FlowNodeBase { node: Expression | VariableDeclaration | BindingElement; antecedent: FlowNode; - antecedents: undefined; } interface FlowCall extends FlowNodeBase { node: CallExpression; antecedent: FlowNode; - antecedents: undefined; } interface FlowCondition extends FlowNodeBase { node: Expression; antecedent: FlowNode; - antecedents: undefined; } interface FlowSwitchClause extends FlowNodeBase { - node: SwitchStatement; + node: FlowSwitchClauseData; antecedent: FlowNode; - antecedents: undefined; + } + interface FlowSwitchClauseData { + switchStatement: SwitchStatement; clauseStart: number; clauseEnd: number; } interface FlowArrayMutation extends FlowNodeBase { node: CallExpression | BinaryExpression; antecedent: FlowNode; - antecedents: undefined; } interface FlowReduceLabel extends FlowNodeBase { - node: FlowLabel; - antecedents: FlowNode[]; + node: FlowReduceLabelData; antecedent: FlowNode; } + interface FlowReduceLabelData { + target: FlowLabel; + antecedents: FlowNode[]; + } type FlowType = Type | IncompleteType; interface IncompleteType { flags: TypeFlags | 0; @@ -9239,7 +9236,7 @@ declare namespace ts { clear(): void; } type PerModuleNameCache = PerNonRelativeNameCache; - function createFlowNode(flags: FlowFlags, node: unknown, antecedent: FlowNode | undefined, antecedents: FlowNode[] | undefined): FlowNode; + function createFlowNode(flags: FlowFlags, node: unknown, antecedent: FlowNode | FlowNode[] | undefined): FlowNode; /** * Visits a Node using the supplied visitor, possibly returning a new Node in its place. * From 486c8f4517246bf37c3a06ec86224aa9c4dd6574 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 2 Apr 2024 07:04:18 -0700 Subject: [PATCH 12/14] Fix formatting --- src/compiler/debug.ts | 2 +- src/compiler/types.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/compiler/debug.ts b/src/compiler/debug.ts index 084c322637458..5f516bbfbc321 100644 --- a/src/compiler/debug.ts +++ b/src/compiler/debug.ts @@ -953,7 +953,7 @@ m2: ${(this.mapper2 as unknown as DebugTypeMapper).__debugToString().split("\n") return !!(f.flags & FlowFlags.SwitchClause); } - function hasAntecedents(f: FlowNode): f is FlowLabel & { antecedent: FlowNode[] } { + function hasAntecedents(f: FlowNode): f is FlowLabel & { antecedent: FlowNode[]; } { return !!(f.flags & FlowFlags.Label) && !!(f as FlowLabel).antecedent; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 2acd6a5b7c832..74b26819dd456 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4152,8 +4152,8 @@ export interface FlowSwitchClause extends FlowNodeBase { export interface FlowSwitchClauseData { switchStatement: SwitchStatement; - clauseStart: number; // Start index of case/default clause range - clauseEnd: number; // End index of case/default clause range + clauseStart: number; // Start index of case/default clause range + clauseEnd: number; // End index of case/default clause range } // FlowArrayMutation represents a node potentially mutates an array, i.e. an From b3d1af14e8611ec11e83e61b2bd7c0bd302e1935 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 2 Apr 2024 07:14:29 -0700 Subject: [PATCH 13/14] Remove flow type cache --- src/compiler/checker.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 936a582d572b9..9e4a660153a78 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -419,7 +419,6 @@ import { ImportOrExportSpecifier, ImportSpecifier, ImportTypeNode, - IncompleteType, IndexedAccessType, IndexedAccessTypeNode, IndexFlags, @@ -1955,7 +1954,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { var subtypeReductionCache = new Map(); var decoratorContextOverrideTypeCache = new Map(); var cachedTypes = new Map(); - var incompleteTypes: IncompleteType[] = []; var evolvingArrayTypes: EvolvingArrayType[] = []; var undefinedProperties: SymbolTable = new Map(); var markerTypes = new Set(); @@ -27520,9 +27518,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function createFlowType(type: Type, incomplete: boolean): FlowType { - return incomplete ? - (incompleteTypes[type.id] ??= { flags: 0, type: type.flags & TypeFlags.Never ? silentNeverType : type }) : - type; + return incomplete ? { flags: 0, type: type.flags & TypeFlags.Never ? silentNeverType : type } : type; } // An evolving array type tracks the element types that have so far been seen in an From 423f96d47757dc748cb8f6497ea522f23e9ac40e Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 4 Apr 2024 16:16:15 -0700 Subject: [PATCH 14/14] Make createFlowNode internal --- src/compiler/binder.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 08910f718f914..37c57ec1371e6 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -501,6 +501,7 @@ export const enum ContainerFlags { IsObjectLiteralOrClassExpressionMethodOrAccessor = 1 << 7, } +/** @internal */ export function createFlowNode(flags: FlowFlags, node: unknown, antecedent: FlowNode | FlowNode[] | undefined): FlowNode { return Debug.attachFlowNodeDebugInfo({ flags, id: 0, node, antecedent } as FlowNode); }