diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index bb3b7b10508..e93fef838e8 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -569,7 +569,7 @@ namespace ts { } // We create a return control flow graph for IIFEs and constructors. For constructors // we use the return control flow graph in strict property initialization checks. - currentReturnTarget = containerFlags & ContainerFlags.IsFunctionLike && nodeIsPresent((node).body) ? createBranchLabel() : undefined; + currentReturnTarget = isIIFE || node.kind === SyntaxKind.Constructor ? createBranchLabel() : undefined; currentBreakTarget = undefined; currentContinueTarget = undefined; activeLabels = undefined; @@ -581,6 +581,7 @@ namespace ts { if (!(currentFlow.flags & FlowFlags.Unreachable) && containerFlags & ContainerFlags.IsFunctionLike && nodeIsPresent((node).body)) { node.flags |= NodeFlags.HasImplicitReturn; if (hasExplicitReturn) node.flags |= NodeFlags.HasExplicitReturn; + (node).endFlowNode = currentFlow; } if (node.kind === SyntaxKind.SourceFile) { node.flags |= emitFlags; @@ -589,7 +590,9 @@ namespace ts { if (currentReturnTarget) { addAntecedent(currentReturnTarget, currentFlow); currentFlow = finishFlowLabel(currentReturnTarget); - (node).returnFlowNode = currentFlow; + if (node.kind === SyntaxKind.Constructor) { + (node).returnFlowNode = currentFlow; + } } if (!isIIFE) { currentFlow = saveCurrentFlow; diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5e491b2e730..0d9397eb6b3 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16996,16 +16996,19 @@ namespace ts { return isReachableFlowNodeWorker(flow, /*skipCacheCheck*/ false); } - function isReachableFlowNodeWorker(flow: FlowNode, skipCacheCheck: boolean): boolean { + function isReachableFlowNodeWorker(flow: FlowNode, noCacheCheck: boolean): boolean { while (true) { const flags = flow.flags; - if (flags & FlowFlags.Shared && !skipCacheCheck) { - const id = getFlowNodeId(flow); - const reachable = flowNodeReachable[id]; - return reachable !== undefined ? reachable : (flowNodeReachable[id] = isReachableFlowNodeWorker(flow, /*skipCacheCheck*/ true)); + if (flags & FlowFlags.Shared | flags & FlowFlags.SwitchClause) { + if (!noCacheCheck) { + const id = getFlowNodeId(flow); + const reachable = flowNodeReachable[id]; + return reachable !== undefined ? reachable : (flowNodeReachable[id] = isReachableFlowNodeWorker(flow, /*skipCacheCheck*/ true)); + } + noCacheCheck = false; } - if (flags & (FlowFlags.Assignment | FlowFlags.Condition | FlowFlags.SwitchClause | FlowFlags.ArrayMutation | FlowFlags.PreFinally | FlowFlags.AfterFinally)) { - flow = (flow).antecedent; + if (flags & (FlowFlags.Assignment | FlowFlags.Condition | FlowFlags.ArrayMutation | FlowFlags.PreFinally | FlowFlags.AfterFinally)) { + flow = (flow).antecedent; } else if (flags & FlowFlags.Call) { const signature = getEffectsSignature((flow).node); @@ -17014,14 +17017,20 @@ namespace ts { } flow = (flow).antecedent; } + else if (flags & FlowFlags.BranchLabel) { + return some((flow).antecedents!, isReachableFlowNode); + } else if (flags & FlowFlags.LoopLabel) { flow = (flow).antecedents![0]; } - else if (flags & FlowFlags.BranchLabel) { - return every((flow).antecedents!, isReachableFlowNode); + else if (flags & FlowFlags.SwitchClause) { + if ((flow).clauseStart === (flow).clauseEnd && isExhaustiveSwitchStatement((flow).switchStatement)) { + return false; + } + flow = (flow).antecedent; } else { - return true; + return !(flags & FlowFlags.Unreachable); } } } @@ -17044,7 +17053,11 @@ namespace ts { // on empty arrays are possible without implicit any errors and new element types can be inferred without // type mismatch errors. const resultType = getObjectFlags(evolvedType) & ObjectFlags.EvolvingArray && isEvolvingArrayOperationTarget(reference) ? autoArrayType : finalizeEvolvingArrayType(evolvedType); - if (resultType === unreachableNeverType || reference.parent && reference.parent.kind === SyntaxKind.NonNullExpression && getTypeWithFacts(resultType, TypeFacts.NEUndefinedOrNull).flags & TypeFlags.Never) { + if (resultType === unreachableNeverType) { + error(reference, Diagnostics.Unreachable_code_detected); + return declaredType; + } + if (reference.parent && reference.parent.kind === SyntaxKind.NonNullExpression && getTypeWithFacts(resultType, TypeFacts.NEUndefinedOrNull).flags & TypeFlags.Never) { return declaredType; } return resultType; @@ -23804,9 +23817,7 @@ namespace ts { } function functionHasImplicitReturn(func: FunctionLikeDeclaration) { - return !!(func.flags & NodeFlags.HasImplicitReturn && - !some((func.body).statements, s => s.kind === SyntaxKind.SwitchStatement && isExhaustiveSwitchStatement(s)) && - !(func.returnFlowNode && !isReachableFlowNode(func.returnFlowNode))); + return func.endFlowNode && isReachableFlowNode(func.endFlowNode); } /** NOTE: Return value of `[]` means a different thing than `undefined`. `[]` means func returns `void`, `undefined` means it returns `never`. */ diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 214c2d58788..3ba88e3e45d 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1040,7 +1040,7 @@ namespace ts { questionToken?: QuestionToken; exclamationToken?: ExclamationToken; body?: Block | Expression; - /* @internal */ returnFlowNode?: FlowNode; + /* @internal */ endFlowNode?: FlowNode; } export type FunctionLikeDeclaration = @@ -1086,6 +1086,7 @@ namespace ts { kind: SyntaxKind.Constructor; parent: ClassLikeDeclaration; body?: FunctionBody; + /* @internal */ returnFlowNode?: FlowNode; } /** For when we encounter a semicolon in a class declaration. ES6 allows these as class elements. */