Skip to content

Commit be561fc

Browse files
committed
[SILOptimizer] Don't diagnose unreachable instructions that belong to no-return func
The pass is attempting to diagnose any user-written code that appears after the result of no-return function call. It has to skip any instructions that happen after the no-return call but are associated with it, such as `alloc_stack` to pass result of such call indirectly. This helps to avoid extraneous diagnostics for synthesized code i.e. a result builder with `buildExpression`: ``` static func buildExpression<T>(_ e: T) -> T { e } ``` The following example would produce extraneous warning: ``` switch <value> { case ... default: fatalError() } ``` because it is translated into: ``` switch <value> { case ... default: { var $__builderDefault = buildExpression(fatalError()) $__builderSwitch = buildEither(second: $__builderDefault) } } ``` In such cases all instructions that follow `fatalError()` call are synthesized except to `alloc_stack $Never` which is passed to `buildExpression`, that instruction is anchored on the `fatalError()` itself which is where the diagnostic would point, which is incorrect. Resolves: rdar://104775183
1 parent 3fd895a commit be561fc

File tree

2 files changed

+90
-20
lines changed

2 files changed

+90
-20
lines changed

lib/SILOptimizer/Mandatory/DiagnoseUnreachable.cpp

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -745,6 +745,42 @@ static bool simplifyBlocksWithCallsToNoReturn(SILBasicBlock &BB,
745745
// function, the entire block is dead.
746746
NoReturnCall = getPrecedingCallToNoReturn(BB);
747747

748+
// Diagnose the unreachable code within the same block as the call to
749+
// noreturn.
750+
auto diagnoseUnreachableCode = [&](SILInstruction *noReturnCall,
751+
SILInstruction *currInst) {
752+
if (DiagnosedUnreachableCode)
753+
return false;
754+
755+
// If current instruction belongs to a no-return call itself, skip it.
756+
// It could happen when result has to be copied.
757+
if (currInst->getLoc().hasSameSourceLocation(noReturnCall->getLoc()))
758+
return false;
759+
760+
if (!isUserCode(currInst))
761+
return false;
762+
763+
// If we have an instruction that is an end_borrow, ignore it. This
764+
// happens when passing a guaranteed argument through generic code paths
765+
// to no return functions.
766+
if (isa<EndBorrowInst>(currInst))
767+
return false;
768+
769+
// If no-return instruction is not something we can point in code or
770+
// it's an explicit cast, skip it.
771+
if (!noReturnCall->getLoc().is<RegularLocation>() ||
772+
noReturnCall->getLoc().isASTNode<ExplicitCastExpr>())
773+
return false;
774+
775+
diagnose(BB.getModule().getASTContext(), currInst->getLoc().getSourceLoc(),
776+
diag::unreachable_code);
777+
diagnose(BB.getModule().getASTContext(),
778+
noReturnCall->getLoc().getSourceLoc(),
779+
diag::call_to_noreturn_note);
780+
781+
return true;
782+
};
783+
748784
// Does this block contain a call to a noreturn function?
749785
while (I != E) {
750786
auto *CurrentInst = &*I;
@@ -758,26 +794,8 @@ static bool simplifyBlocksWithCallsToNoReturn(SILBasicBlock &BB,
758794
// We will need to delete the instruction later on.
759795
ToBeDeleted.push_back(CurrentInst);
760796

761-
// Diagnose the unreachable code within the same block as the call to
762-
// noreturn.
763-
if (isUserCode(CurrentInst) && !DiagnosedUnreachableCode) {
764-
// If we have an instruction that is an end_borrow, ignore it. This
765-
// happens when passing a guaranteed argument through generic code paths
766-
// to no return functions.
767-
if (!isa<EndBorrowInst>(CurrentInst)) {
768-
if (NoReturnCall->getLoc().is<RegularLocation>()) {
769-
if (!NoReturnCall->getLoc().isASTNode<ExplicitCastExpr>()) {
770-
diagnose(BB.getModule().getASTContext(),
771-
CurrentInst->getLoc().getSourceLoc(),
772-
diag::unreachable_code);
773-
diagnose(BB.getModule().getASTContext(),
774-
NoReturnCall->getLoc().getSourceLoc(),
775-
diag::call_to_noreturn_note);
776-
DiagnosedUnreachableCode = true;
777-
}
778-
}
779-
}
780-
}
797+
DiagnosedUnreachableCode |=
798+
diagnoseUnreachableCode(NoReturnCall, CurrentInst);
781799

782800
// We are going to bluntly remove these instructions. Change uses in
783801
// different basic blocks to undef. This is safe because all control flow

test/SILOptimizer/diagnose_unreachable.swift

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,3 +518,55 @@ func keypathWithDynamicLookup() {
518518
// when used in conjunction with a dynamicMemberLookup enum.
519519
let _ = \DynamicLookupEnum.innerEnum // no warning
520520
}
521+
522+
func test_no_warnings_with_fatalError_when_wrapped_in_buildExpression() {
523+
enum Either<T,U> {
524+
case first(T)
525+
case second(U)
526+
}
527+
528+
@resultBuilder
529+
struct MyBuilder {
530+
static func buildExpression<T>(_ e: T) -> T { e }
531+
static func buildBlock() -> () { }
532+
533+
static func buildBlock<T1>(_ t1: T1) -> T1 {
534+
return t1
535+
}
536+
537+
static func buildBlock<T1, T2>(_ t1: T1, _ t2: T2) -> (T1, T2) {
538+
return (t1, t2)
539+
}
540+
541+
static func buildBlock<T1, T2, T3>(_ t1: T1, _ t2: T2, _ t3: T3) -> (T1, T2, T3) {
542+
return (t1, t2, t3)
543+
}
544+
545+
static func buildEither<T,U>(first value: T) -> Either<T, U> {
546+
return .first(value)
547+
}
548+
549+
static func buildEither<T,U>(second value: U) -> Either<T, U> {
550+
return .second(value)
551+
}
552+
}
553+
554+
func test<T>(@MyBuilder _: (Int) -> T) {}
555+
556+
test {
557+
if $0 < 0 {
558+
fatalError() // ok, no warning even though fatalError() is wrapped
559+
} else if $0 > 0 {
560+
42
561+
} else {
562+
0
563+
}
564+
}
565+
566+
test {
567+
switch $0 {
568+
case 0: "0"
569+
default: fatalError() // Ok, no warning even though fatalError() is wrapped
570+
}
571+
}
572+
}

0 commit comments

Comments
 (0)