Skip to content

Commit d1fb307

Browse files
authored
Merge pull request #83632 from hamishknight/complete-result
[CS] Better handle unsupported result builder elements for completion
2 parents 62d2241 + 27e2514 commit d1fb307

File tree

2 files changed

+72
-29
lines changed

2 files changed

+72
-29
lines changed

lib/Sema/BuilderTransform.cpp

Lines changed: 21 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -269,12 +269,7 @@ class ResultBuilderTransform
269269
for (auto element : braceStmt->getElements()) {
270270
if (auto unsupported =
271271
transformBraceElement(element, newBody, buildBlockArguments)) {
272-
// When in code completion mode, simply ignore unsported constructs to
273-
// get results for anything that's unrelated to the unsupported
274-
// constructs.
275-
if (!ctx.CompletionCallback) {
276-
return failTransform(*unsupported);
277-
}
272+
return failTransform(*unsupported);
278273
}
279274
}
280275

@@ -780,16 +775,17 @@ class ResultBuilderTransform
780775
#undef UNSUPPORTED_STMT
781776

782777
private:
783-
static bool isBuildableIfChainRecursive(IfStmt *ifStmt, unsigned &numPayloads,
784-
bool &isOptional) {
778+
static void checkBuildableIfChainRecursive(IfStmt *ifStmt,
779+
unsigned &numPayloads,
780+
bool &isOptional) {
785781
// The 'then' clause contributes a payload.
786782
++numPayloads;
787783

788784
// If there's an 'else' clause, it contributes payloads:
789785
if (auto elseStmt = ifStmt->getElseStmt()) {
790786
// If it's 'else if', it contributes payloads recursively.
791787
if (auto elseIfStmt = dyn_cast<IfStmt>(elseStmt)) {
792-
return isBuildableIfChainRecursive(elseIfStmt, numPayloads, isOptional);
788+
checkBuildableIfChainRecursive(elseIfStmt, numPayloads, isOptional);
793789
// Otherwise it's just the one.
794790
} else {
795791
++numPayloads;
@@ -799,8 +795,6 @@ class ResultBuilderTransform
799795
} else {
800796
isOptional = true;
801797
}
802-
803-
return true;
804798
}
805799

806800
static bool hasUnconditionalElse(IfStmt *ifStmt) {
@@ -815,8 +809,7 @@ class ResultBuilderTransform
815809

816810
bool isBuildableIfChain(IfStmt *ifStmt, unsigned &numPayloads,
817811
bool &isOptional) {
818-
if (!isBuildableIfChainRecursive(ifStmt, numPayloads, isOptional))
819-
return false;
812+
checkBuildableIfChainRecursive(ifStmt, numPayloads, isOptional);
820813

821814
// If there's a missing 'else', we need 'buildOptional' to exist.
822815
if (isOptional && !builder.supportsOptional())
@@ -991,11 +984,11 @@ TypeChecker::applyResultBuilderBodyTransform(FuncDecl *func, Type builderType) {
991984
// of this decl; it's not part of the interface type.
992985
builderType = func->mapTypeIntoContext(builderType);
993986

994-
if (auto result = cs.matchResultBuilder(
995-
func, builderType, resultContextType, resultConstraintKind,
996-
/*contextualType=*/Type(),
997-
cs.getConstraintLocator(func->getBody()))) {
998-
if (result->isFailure())
987+
{
988+
auto result = cs.matchResultBuilder(
989+
func, builderType, resultContextType, resultConstraintKind,
990+
/*contextualType=*/Type(), cs.getConstraintLocator(func->getBody()));
991+
if (!result || result->isFailure())
999992
return nullptr;
1000993
}
1001994

@@ -1132,8 +1125,9 @@ ConstraintSystem::matchResultBuilder(AnyFunctionRef fn, Type builderType,
11321125
if (fn.bodyHasExplicitReturnStmt()) {
11331126
// Diagnostic mode means that solver couldn't reach any viable
11341127
// solution, so let's diagnose presence of a `return` statement
1135-
// in the closure body.
1136-
if (shouldAttemptFixes()) {
1128+
// in the closure body. Avoid doing this for completion since we need to
1129+
// continue solving the body.
1130+
if (shouldAttemptFixes() && !isForCodeCompletion()) {
11371131
if (recordFix(IgnoreResultBuilderWithReturnStmts::create(
11381132
*this, builderType,
11391133
getConstraintLocator(fn.getAbstractClosureExpr()))))
@@ -1157,18 +1151,17 @@ ConstraintSystem::matchResultBuilder(AnyFunctionRef fn, Type builderType,
11571151
auto *body = transform.apply(fn.getBody());
11581152

11591153
if (auto unsupported = transform.getUnsupportedElement()) {
1160-
assert(!body || getASTContext().CompletionCallback);
1161-
1162-
// If we aren't supposed to attempt fixes, fail.
1163-
if (!shouldAttemptFixes()) {
1164-
return getTypeMatchFailure(locator);
1165-
}
1154+
assert(!body);
11661155

11671156
// If we're solving for code completion and the body contains the code
1168-
// completion location, skipping it won't get us to a useful solution so
1169-
// just bail.
1157+
// completion location, fall back to solving as a regular function body.
11701158
if (isForCodeCompletion() &&
11711159
containsIDEInspectionTarget(fn.getBody())) {
1160+
return std::nullopt;
1161+
}
1162+
1163+
// If we aren't supposed to attempt fixes, fail.
1164+
if (!shouldAttemptFixes()) {
11721165
return getTypeMatchFailure(locator);
11731166
}
11741167

test/IDE/complete_in_result_builder.swift

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@ struct TupleBuilder<T> {
1616
}
1717
}
1818

19-
func buildStringTuple<Result>(@TupleBuilder<String> x: () -> Result) {}
19+
@discardableResult
20+
func buildStringTuple<Result>(@TupleBuilder<String> x: () -> Result) -> Result {
21+
x()
22+
}
2023

2124
enum StringFactory {
2225
static func makeString(x: String) -> String { return x }
@@ -50,6 +53,12 @@ func testGlobalLookup() {
5053
}
5154
}
5255

56+
buildStringTuple {
57+
if {
58+
#^GLOBAL_LOOKUP_IN_IF_BODY_WITHOUT_CONDITION_CLOSURE?check=GLOBAL_LOOKUP_IN_IF_BODY^#
59+
}
60+
}
61+
5362
@TupleBuilder<String> var x4 {
5463
guard else {
5564
#^GLOBAL_LOOKUP_IN_GUARD_BODY_WITHOUT_CONDITION?check=GLOBAL_LOOKUP_IN_IF_BODY^#
@@ -98,6 +107,21 @@ struct FooStruct {
98107
func intGen() -> Int { return 1 }
99108
}
100109
110+
func testReturn() {
111+
// Make sure we can still complete even with return.
112+
buildStringTuple {
113+
StringFactory.#^COMPLETE_STATIC_MEMBER_WITH_RETURN^#
114+
// COMPLETE_STATIC_MEMBER_WITH_RETURN: Decl[StaticMethod]/CurrNominal: makeString({#x: String#})[#String#];
115+
return ""
116+
}
117+
118+
buildStringTuple {
119+
""
120+
return FooStruct()
121+
}.#^COMPLETE_INSTANCE_MEMBER_ON_RETURN_BUILDER^#
122+
// COMPLETE_INSTANCE_MEMBER_ON_RETURN_BUILDER: Decl[InstanceVar]/CurrNominal: instanceVar[#Int#]; name=instanceVar
123+
}
124+
101125
func testPatternMatching() {
102126
@TupleBuilder<String> var x1 {
103127
let x = Letters.b
@@ -128,6 +152,11 @@ func testPatternMatching() {
128152
let x: FooStruct? = FooStruct()
129153
guard case .#^GUARD_CASE_PATTERN_2?check=OPTIONAL_FOOSTRUCT^#some() = x {}
130154
}
155+
156+
buildStringTuple {
157+
let x: FooStruct? = FooStruct()
158+
guard case .#^GUARD_CASE_PATTERN_3?check=OPTIONAL_FOOSTRUCT^#some() = x {}
159+
}
131160
}
132161
133162
func testCompleteFunctionArgumentLabels() {
@@ -410,3 +439,24 @@ func testInForLoop(_ x: [Int]) {
410439
}
411440
}
412441
}
442+
443+
func testUnsupportedForLoop() {
444+
@resultBuilder
445+
struct Builder {
446+
static func buildBlock<T>(_ components: T...) -> T {
447+
components.first!
448+
}
449+
}
450+
451+
func foo(@Builder fn: () -> Int) {}
452+
453+
foo {
454+
for i in [0].#^COMPLETE_IN_UNSUPPORTED_FOR^# {}
455+
}
456+
// COMPLETE_IN_UNSUPPORTED_FOR: Decl[InstanceVar]/Super/IsSystem: first[#Int?#]; name=first
457+
458+
foo {
459+
for i in [0].#^COMPLETE_IN_UNSUPPORTED_FOR_2?check=COMPLETE_IN_UNSUPPORTED_FOR^# {}
460+
return 0
461+
}
462+
}

0 commit comments

Comments
 (0)