Skip to content

Commit f5acd13

Browse files
committed
[Refactoring] Replace lifted breaks/returns with placeholders
If we're lifting them outside of the control flow structure they're dealing with, turn them into placeholders, as they will no longer perform the control flow the user is expecting. This handles: - Return statements at the top-level of the callback. - Break statements in switches that we re-write. Resolves rdar://74014897.
1 parent 8a05239 commit f5acd13

File tree

3 files changed

+125
-11
lines changed

3 files changed

+125
-11
lines changed

lib/IDE/Refactoring.cpp

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4517,6 +4517,7 @@ struct CallbackClassifier {
45174517
/// names from `Body`. Errors are added through `DiagEngine`, possibly
45184518
/// resulting in partially filled out blocks.
45194519
static void classifyInto(ClassifiedBlocks &Blocks,
4520+
llvm::DenseSet<SwitchStmt *> &HandledSwitches,
45204521
DiagnosticEngine &DiagEngine,
45214522
ArrayRef<const ParamDecl *> SuccessParams,
45224523
const ParamDecl *ErrParam, HandlerType ResultType,
@@ -4528,25 +4529,30 @@ struct CallbackClassifier {
45284529
if (ErrParam)
45294530
ParamsSet.insert(ErrParam);
45304531

4531-
CallbackClassifier Classifier(Blocks, DiagEngine, ParamsSet, ErrParam,
4532+
CallbackClassifier Classifier(Blocks, HandledSwitches, DiagEngine,
4533+
ParamsSet, ErrParam,
45324534
ResultType == HandlerType::RESULT);
45334535
Classifier.classifyNodes(Body);
45344536
}
45354537

45364538
private:
45374539
ClassifiedBlocks &Blocks;
4540+
llvm::DenseSet<SwitchStmt *> &HandledSwitches;
45384541
DiagnosticEngine &DiagEngine;
45394542
ClassifiedBlock *CurrentBlock;
45404543
llvm::DenseSet<const Decl *> ParamsSet;
45414544
const ParamDecl *ErrParam;
45424545
bool IsResultParam;
45434546

4544-
CallbackClassifier(ClassifiedBlocks &Blocks, DiagnosticEngine &DiagEngine,
4547+
CallbackClassifier(ClassifiedBlocks &Blocks,
4548+
llvm::DenseSet<SwitchStmt *> &HandledSwitches,
4549+
DiagnosticEngine &DiagEngine,
45454550
llvm::DenseSet<const Decl *> ParamsSet,
45464551
const ParamDecl *ErrParam, bool IsResultParam)
4547-
: Blocks(Blocks), DiagEngine(DiagEngine),
4548-
CurrentBlock(&Blocks.SuccessBlock), ParamsSet(ParamsSet),
4549-
ErrParam(ErrParam), IsResultParam(IsResultParam) {}
4552+
: Blocks(Blocks), HandledSwitches(HandledSwitches),
4553+
DiagEngine(DiagEngine), CurrentBlock(&Blocks.SuccessBlock),
4554+
ParamsSet(ParamsSet), ErrParam(ErrParam), IsResultParam(IsResultParam) {
4555+
}
45504556

45514557
void classifyNodes(ArrayRef<ASTNode> Nodes) {
45524558
for (auto I = Nodes.begin(), E = Nodes.end(); I < E; ++I) {
@@ -4718,6 +4724,8 @@ struct CallbackClassifier {
47184724
if (DiagEngine.hadAnyError())
47194725
return;
47204726
}
4727+
// Mark this switch statement as having been transformed.
4728+
HandledSwitches.insert(SS);
47214729
}
47224730
};
47234731

@@ -4776,6 +4784,9 @@ class AsyncConverter : private SourceEntityWalker {
47764784
// references to it
47774785
llvm::DenseMap<const Decl *, std::string> Names;
47784786

4787+
/// The switch statements that have been re-written by this transform.
4788+
llvm::DenseSet<SwitchStmt *> HandledSwitches;
4789+
47794790
// These are per-node (ie. are saved and restored on each convertNode call)
47804791
SourceLoc LastAddedLoc;
47814792
int NestedExprCount = 0;
@@ -4846,6 +4857,9 @@ class AsyncConverter : private SourceEntityWalker {
48464857
NestedExprCount++;
48474858
return true;
48484859
}
4860+
// Note we don't walk into any nested local function decls. If we start
4861+
// doing so in the future, be sure to update the logic that deals with
4862+
// converting unhandled returns into placeholders in walkToStmtPre.
48494863
return false;
48504864
}
48514865

@@ -4905,6 +4919,38 @@ class AsyncConverter : private SourceEntityWalker {
49054919
NestedExprCount++;
49064920
return true;
49074921
}
4922+
4923+
bool replaceRangeWithPlaceholder(SourceRange range) {
4924+
return addCustom(range, [&]() {
4925+
OS << PLACEHOLDER_START;
4926+
addRange(range, /*toEndOfToken*/ true);
4927+
OS << PLACEHOLDER_END;
4928+
});
4929+
}
4930+
4931+
bool walkToStmtPre(Stmt *S) override {
4932+
// Some break and return statements need to be turned into placeholders,
4933+
// as they may no longer perform the control flow that the user is
4934+
// expecting.
4935+
if (!S->isImplicit()) {
4936+
// For a break, if it's jumping out of a switch statement that we've
4937+
// re-written as a part of the transform, turn it into a placeholder, as
4938+
// it would have been lifted out of the switch statement.
4939+
if (auto *BS = dyn_cast<BreakStmt>(S)) {
4940+
if (auto *SS = dyn_cast<SwitchStmt>(BS->getTarget())) {
4941+
if (HandledSwitches.contains(SS))
4942+
replaceRangeWithPlaceholder(S->getSourceRange());
4943+
}
4944+
}
4945+
4946+
// For a return, if it's not nested inside another closure or function,
4947+
// turn it into a placeholder, as it will be lifted out of the callback.
4948+
if (isa<ReturnStmt>(S) && NestedExprCount == 0)
4949+
replaceRangeWithPlaceholder(S->getSourceRange());
4950+
}
4951+
return true;
4952+
}
4953+
49084954
#undef PLACEHOLDER_START
49094955
#undef PLACEHOLDER_END
49104956

@@ -5107,9 +5153,9 @@ class AsyncConverter : private SourceEntityWalker {
51075153
if (!HandlerDesc.HasError) {
51085154
Blocks.SuccessBlock.addAllNodes(CallbackBody);
51095155
} else if (!CallbackBody.empty()) {
5110-
CallbackClassifier::classifyInto(Blocks, DiagEngine, SuccessParams,
5111-
ErrParam, HandlerDesc.Type,
5112-
CallbackBody);
5156+
CallbackClassifier::classifyInto(Blocks, HandledSwitches, DiagEngine,
5157+
SuccessParams, ErrParam,
5158+
HandlerDesc.Type, CallbackBody);
51135159
if (DiagEngine.hadAnyError()) {
51145160
// Can only fallback when the results are params, in which case only
51155161
// the names are used (defaulted to the names of the params if none)

test/refactoring/ConvertAsync/convert_params_single.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,7 @@ withError { res, err in
357357
// NESTEDRET-NEXT: let str = try await withError()
358358
// NESTEDRET-NEXT: print("before")
359359
// NESTEDRET-NEXT: if test(str) {
360-
// NESTEDRET-NEXT: return
360+
// NESTEDRET-NEXT: <#return#>
361361
// NESTEDRET-NEXT: }
362362
// NESTEDRET-NEXT: print("got result \(str)")
363363
// NESTEDRET-NEXT: print("after")

test/refactoring/ConvertAsync/convert_result.swift

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
func simple(_ completion: (Result<String, Error>) -> Void) { }
2+
func simpleWithArg(_ arg: Int, _ completion: (Result<String, Error>) -> Void) { }
23
func noError(_ completion: (Result<String, Never>) -> Void) { }
34
func test(_ str: String) -> Bool { return false }
45

@@ -279,7 +280,7 @@ simple { res in
279280
// NESTEDRET-NEXT: let str = try await simple()
280281
// NESTEDRET-NEXT: print("before")
281282
// NESTEDRET-NEXT: if test(str) {
282-
// NESTEDRET-NEXT: return
283+
// NESTEDRET-NEXT: <#return#>
283284
// NESTEDRET-NEXT: }
284285
// NESTEDRET-NEXT: print("result \(str)")
285286
// NESTEDRET-NEXT: print("after")
@@ -303,7 +304,7 @@ simple { res in
303304
// NESTEDBREAK-NEXT: let str = try await simple()
304305
// NESTEDBREAK-NEXT: print("before")
305306
// NESTEDBREAK-NEXT: if test(str) {
306-
// NESTEDBREAK-NEXT: break
307+
// NESTEDBREAK-NEXT: <#break#>
307308
// NESTEDBREAK-NEXT: }
308309
// NESTEDBREAK-NEXT: print("result \(str)")
309310
// NESTEDBREAK-NEXT: print("after")
@@ -344,3 +345,70 @@ simple { res in
344345
// IGNORE-UNRELATED-NEXT: {{^}} break{{$}}
345346
// IGNORE-UNRELATED-NEXT: }
346347
// IGNORE-UNRELATED-NEXT: print("after")
348+
349+
// RUN: %refactor -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+1):1 | %FileCheck -check-prefix=BREAK-RET-PLACEHOLDER %s
350+
simpleWithArg({ return 0 }()) { res in
351+
switch res {
352+
case .success:
353+
if .random() { break }
354+
x: if .random() { break x }
355+
case .failure:
356+
break
357+
}
358+
359+
func foo<T>(_ x: T) {
360+
if .random() { return }
361+
}
362+
foo(res)
363+
364+
let fn = {
365+
if .random() { return }
366+
return
367+
}
368+
fn()
369+
370+
_ = { return }()
371+
372+
switch Bool.random() {
373+
case true:
374+
break
375+
case false:
376+
if .random() { break }
377+
y: if .random() { break y }
378+
return
379+
}
380+
381+
x: if .random() {
382+
break x
383+
}
384+
if .random() { return }
385+
}
386+
387+
// Make sure we replace lifted break/returns with placeholders, but keep nested
388+
// break/returns in e.g closures or labelled control flow in place.
389+
390+
// BREAK-RET-PLACEHOLDER: let res = try await simpleWithArg({ return 0 }())
391+
// BREAK-RET-PLACEHOLDER-NEXT: if .random() { <#break#> }
392+
// BREAK-RET-PLACEHOLDER-NEXT: x: if .random() { break x }
393+
// BREAK-RET-PLACEHOLDER-NEXT: func foo<T>(_ x: T) {
394+
// BREAK-RET-PLACEHOLDER-NEXT: if .random() { return }
395+
// BREAK-RET-PLACEHOLDER-NEXT: }
396+
// BREAK-RET-PLACEHOLDER-NEXT: foo(<#res#>)
397+
// BREAK-RET-PLACEHOLDER-NEXT: let fn = {
398+
// BREAK-RET-PLACEHOLDER-NEXT: if .random() { return }
399+
// BREAK-RET-PLACEHOLDER-NEXT: {{^}} return{{$}}
400+
// BREAK-RET-PLACEHOLDER-NEXT: }
401+
// BREAK-RET-PLACEHOLDER-NEXT: fn()
402+
// BREAK-RET-PLACEHOLDER-NEXT: _ = { return }()
403+
// BREAK-RET-PLACEHOLDER-NEXT: switch Bool.random() {
404+
// BREAK-RET-PLACEHOLDER-NEXT: case true:
405+
// BREAK-RET-PLACEHOLDER-NEXT: {{^}} break{{$}}
406+
// BREAK-RET-PLACEHOLDER-NEXT: case false:
407+
// BREAK-RET-PLACEHOLDER-NEXT: if .random() { break }
408+
// BREAK-RET-PLACEHOLDER-NEXT: y: if .random() { break y }
409+
// BREAK-RET-PLACEHOLDER-NEXT: <#return#>
410+
// BREAK-RET-PLACEHOLDER-NEXT: }
411+
// BREAK-RET-PLACEHOLDER-NEXT: x: if .random() {
412+
// BREAK-RET-PLACEHOLDER-NEXT: {{^}} break x{{$}}
413+
// BREAK-RET-PLACEHOLDER-NEXT: }
414+
// BREAK-RET-PLACEHOLDER-NEXT: if .random() { <#return#> }

0 commit comments

Comments
 (0)