mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-04 12:32:08 -06:00
Monomorphic flow nodes (#57977)
This commit is contained in:
parent
cf4bb589e1
commit
39daa13099
@ -65,10 +65,15 @@ import {
|
||||
Expression,
|
||||
ExpressionStatement,
|
||||
findAncestor,
|
||||
FlowArrayMutation,
|
||||
FlowAssignment,
|
||||
FlowCall,
|
||||
FlowCondition,
|
||||
FlowFlags,
|
||||
FlowLabel,
|
||||
FlowNode,
|
||||
FlowReduceLabel,
|
||||
FlowSwitchClause,
|
||||
forEach,
|
||||
forEachChild,
|
||||
ForInOrOfStatement,
|
||||
@ -494,9 +499,9 @@ export const enum ContainerFlags {
|
||||
IsObjectLiteralOrClassExpressionMethodOrAccessor = 1 << 7,
|
||||
}
|
||||
|
||||
function initFlowNode<T extends FlowNode>(node: T) {
|
||||
Debug.attachFlowNodeDebugInfo(node);
|
||||
return node;
|
||||
/** @internal */
|
||||
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();
|
||||
@ -557,8 +562,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, /*node*/ undefined, /*antecedent*/ undefined);
|
||||
var reportedUnreachableFlow = createFlowNode(FlowFlags.Unreachable, /*node*/ undefined, /*antecedent*/ undefined);
|
||||
var bindBinaryExpressionFlow = createBindBinaryExpressionFlow();
|
||||
/* eslint-enable no-var */
|
||||
|
||||
@ -1013,7 +1018,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, /*node*/ undefined, /*antecedent*/ undefined);
|
||||
if (containerFlags & (ContainerFlags.IsFunctionExpression | ContainerFlags.IsObjectLiteralOrClassExpressionMethodOrAccessor)) {
|
||||
currentFlow.node = node as FunctionExpression | ArrowFunction | MethodDeclaration | GetAccessorDeclaration | SetAccessorDeclaration;
|
||||
}
|
||||
@ -1332,16 +1337,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, /*node*/ undefined, /*antecedent*/ undefined) as FlowLabel;
|
||||
}
|
||||
|
||||
function createLoopLabel(): FlowLabel {
|
||||
return initFlowNode({ flags: FlowFlags.LoopLabel, antecedents: undefined });
|
||||
function createLoopLabel() {
|
||||
return createFlowNode(FlowFlags.LoopLabel, /*node*/ undefined, /*antecedent*/ undefined) as FlowLabel;
|
||||
}
|
||||
|
||||
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, antecedents }, antecedent) as FlowReduceLabel;
|
||||
}
|
||||
|
||||
function setFlowNodeReferenced(flow: FlowNode) {
|
||||
@ -1350,13 +1355,13 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@ -1374,32 +1379,32 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
|
||||
return antecedent;
|
||||
}
|
||||
setFlowNodeReferenced(antecedent);
|
||||
return initFlowNode({ flags, antecedent, node: expression });
|
||||
return createFlowNode(flags, expression, antecedent) as FlowCondition;
|
||||
}
|
||||
|
||||
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) as FlowSwitchClause;
|
||||
}
|
||||
|
||||
function createFlowMutation(flags: FlowFlags, antecedent: FlowNode, node: Expression | VariableDeclaration | ArrayBindingElement): FlowNode {
|
||||
function createFlowMutation(flags: FlowFlags.Assignment | FlowFlags.ArrayMutation, antecedent: FlowNode, node: Expression | VariableDeclaration | ArrayBindingElement) {
|
||||
setFlowNodeReferenced(antecedent);
|
||||
hasFlowEffects = true;
|
||||
const result = initFlowNode({ flags, antecedent, node });
|
||||
const result = createFlowNode(flags, node, antecedent) 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);
|
||||
hasFlowEffects = true;
|
||||
return initFlowNode({ flags: FlowFlags.Call, antecedent, node });
|
||||
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;
|
||||
}
|
||||
@ -1658,7 +1663,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) {
|
||||
@ -1668,18 +1673,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 {
|
||||
@ -1700,7 +1705,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));
|
||||
}
|
||||
@ -1712,7 +1717,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;
|
||||
@ -2432,7 +2437,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, /*node*/ undefined, /*antecedent*/ undefined);
|
||||
parent = typeAlias;
|
||||
bind(typeAlias.typeExpression);
|
||||
const declName = getNameOfDeclaration(typeAlias);
|
||||
@ -2504,7 +2509,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, /*node*/ undefined, /*antecedent*/ undefined);
|
||||
parent = jsDocImportTag;
|
||||
bind(jsDocImportTag.importClause);
|
||||
}
|
||||
|
||||
@ -111,6 +111,7 @@ import {
|
||||
createEmptyExports,
|
||||
createEvaluator,
|
||||
createFileDiagnostic,
|
||||
createFlowNode,
|
||||
createGetCanonicalFileName,
|
||||
createGetSymbolWalker,
|
||||
createModeAwareCacheKey,
|
||||
@ -211,6 +212,7 @@ import {
|
||||
FlowReduceLabel,
|
||||
FlowStart,
|
||||
FlowSwitchClause,
|
||||
FlowSwitchClauseData,
|
||||
FlowType,
|
||||
forEach,
|
||||
forEachChild,
|
||||
@ -27143,7 +27145,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++;
|
||||
}
|
||||
@ -27942,10 +27944,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;
|
||||
}
|
||||
@ -27955,7 +27957,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).switchStatement)) {
|
||||
const data = (flow as FlowSwitchClause).node;
|
||||
if (data.clauseStart === data.clauseEnd && isExhaustiveSwitchStatement(data.switchStatement)) {
|
||||
return false;
|
||||
}
|
||||
flow = (flow as FlowSwitchClause).antecedent;
|
||||
@ -27963,11 +27966,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).target;
|
||||
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 {
|
||||
@ -28000,18 +28003,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).target;
|
||||
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 {
|
||||
@ -28124,8 +28127,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 ?
|
||||
@ -28140,11 +28143,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
}
|
||||
}
|
||||
else if (flags & FlowFlags.ReduceLabel) {
|
||||
const target = (flow as FlowReduceLabel).target;
|
||||
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.
|
||||
@ -28331,30 +28334,30 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
}
|
||||
|
||||
function getTypeAtSwitchClause(flow: FlowSwitchClause): FlowType {
|
||||
const expr = skipParentheses(flow.switchStatement.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.switchStatement, 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.switchStatement, flow.clauseStart, flow.clauseEnd);
|
||||
type = narrowTypeBySwitchOnTypeOf(type, flow.node);
|
||||
}
|
||||
else if (expr.kind === SyntaxKind.TrueKeyword) {
|
||||
type = narrowTypeBySwitchOnTrue(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd);
|
||||
type = narrowTypeBySwitchOnTrue(type, flow.node);
|
||||
}
|
||||
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, 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.switchStatement, 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.switchStatement, flow.clauseStart, flow.clauseEnd);
|
||||
type = narrowTypeBySwitchOnDiscriminantProperty(type, access, flow.node);
|
||||
}
|
||||
}
|
||||
return createFlowType(type, isIncomplete(flowType));
|
||||
@ -28365,8 +28368,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;
|
||||
@ -28397,7 +28400,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;
|
||||
}
|
||||
@ -28445,7 +28448,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
|
||||
@ -28609,15 +28612,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 {
|
||||
@ -28857,12 +28860,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
|
||||
@ -28943,7 +28946,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;
|
||||
@ -28961,7 +28964,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);
|
||||
|
||||
@ -37831,23 +37834,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,
|
||||
antecedent,
|
||||
node: expr,
|
||||
};
|
||||
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: FlowCondition = {
|
||||
flags: FlowFlags.FalseCondition,
|
||||
antecedent,
|
||||
node: expr,
|
||||
};
|
||||
const falseCondition = createFlowNode(FlowFlags.FalseCondition, expr, antecedent);
|
||||
const falseSubtype = getFlowTypeOfReference(param.name, initType, trueType, func, falseCondition);
|
||||
return falseSubtype.flags & TypeFlags.Never ? trueType : undefined;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
@ -560,6 +559,7 @@ export namespace Debug {
|
||||
attachFlowNodeDebugInfoWorker(flowNode);
|
||||
}
|
||||
}
|
||||
return flowNode;
|
||||
}
|
||||
|
||||
let nodeArrayProto: NodeArray<Node> | undefined;
|
||||
@ -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<FlowNode, { antecedent: FlowNode; }> {
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -1097,15 +1097,11 @@ 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.clauseStart; i < flowNode.clauseEnd; i++) {
|
||||
const clause = flowNode.switchStatement.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");
|
||||
}
|
||||
@ -1115,6 +1111,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;
|
||||
}
|
||||
|
||||
|
||||
@ -4092,6 +4092,7 @@ export const enum FlowFlags {
|
||||
|
||||
/** @internal */
|
||||
export type FlowNode =
|
||||
| FlowUnreachable
|
||||
| FlowStart
|
||||
| FlowLabel
|
||||
| FlowAssignment
|
||||
@ -4104,7 +4105,15 @@ export type FlowNode =
|
||||
/** @internal */
|
||||
export interface FlowNodeBase {
|
||||
flags: FlowFlags;
|
||||
id?: number; // Node id used by flow type cache in checker
|
||||
id: number; // Node id used by flow type cache in checker
|
||||
node: unknown; // Node or other data
|
||||
antecedent: FlowNode | FlowNode[] | undefined;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface FlowUnreachable extends FlowNodeBase {
|
||||
node: undefined;
|
||||
antecedent: undefined;
|
||||
}
|
||||
|
||||
// FlowStart represents the start of a control flow. For a function expression or arrow
|
||||
@ -4112,13 +4121,15 @@ export interface FlowNodeBase {
|
||||
// property for the containing control flow).
|
||||
/** @internal */
|
||||
export interface FlowStart extends FlowNodeBase {
|
||||
node?: FunctionExpression | ArrowFunction | MethodDeclaration | GetAccessorDeclaration | SetAccessorDeclaration;
|
||||
node: FunctionExpression | ArrowFunction | MethodDeclaration | GetAccessorDeclaration | SetAccessorDeclaration | undefined;
|
||||
antecedent: undefined;
|
||||
}
|
||||
|
||||
// FlowLabel represents a junction with multiple possible preceding control flows.
|
||||
/** @internal */
|
||||
export interface FlowLabel extends FlowNodeBase {
|
||||
antecedents: FlowNode[] | undefined;
|
||||
node: undefined;
|
||||
antecedent: FlowNode[] | undefined;
|
||||
}
|
||||
|
||||
// FlowAssignment represents a node that assigns a value to a narrowable reference,
|
||||
@ -4146,12 +4157,17 @@ export interface FlowCondition extends FlowNodeBase {
|
||||
// dprint-ignore
|
||||
/** @internal */
|
||||
export interface FlowSwitchClause extends FlowNodeBase {
|
||||
switchStatement: SwitchStatement;
|
||||
clauseStart: number; // Start index of case/default clause range
|
||||
clauseEnd: number; // End index of case/default clause range
|
||||
node: FlowSwitchClauseData;
|
||||
antecedent: FlowNode;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface FlowSwitchClauseData {
|
||||
switchStatement: SwitchStatement;
|
||||
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
|
||||
// operation of the form 'x.push(value)', 'x.unshift(value)' or 'x[n] = value'.
|
||||
/** @internal */
|
||||
@ -4162,9 +4178,14 @@ export interface FlowArrayMutation extends FlowNodeBase {
|
||||
|
||||
/** @internal */
|
||||
export interface FlowReduceLabel extends FlowNodeBase {
|
||||
node: FlowReduceLabelData;
|
||||
antecedent: FlowNode;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface FlowReduceLabelData {
|
||||
target: FlowLabel;
|
||||
antecedents: FlowNode[];
|
||||
antecedent: FlowNode;
|
||||
}
|
||||
|
||||
export type FlowType = Type | IncompleteType;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user