Monomorphic flow nodes (#57977)

This commit is contained in:
Anders Hejlsberg 2024-04-04 16:53:15 -07:00 committed by GitHub
parent cf4bb589e1
commit 39daa13099
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 131 additions and 109 deletions

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;