Skip to content

Commit a73e44a

Browse files
committed
[Sema] Split out result builder pre-checking
Use `preCheckTarget` to pre-check the body, allowing us to replace `PreCheckResultBuilderRequest` with a request that only checks the brace for ReturnStmts.
1 parent 23e8340 commit a73e44a

File tree

5 files changed

+36
-223
lines changed

5 files changed

+36
-223
lines changed

include/swift/AST/TypeCheckRequests.h

Lines changed: 3 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -3028,60 +3028,8 @@ class AssociatedConformanceRequest
30283028
void cacheResult(ProtocolConformanceRef value) const;
30293029
};
30303030

3031-
struct PreCheckResultBuilderDescriptor {
3032-
AnyFunctionRef Fn;
3033-
3034-
private:
3035-
// NOTE: Since source tooling (e.g. code completion) might replace the body,
3036-
// we need to take the body into account to calculate 'hash_value' and '=='.
3037-
// Also, we cannot 'getBody()' inside 'hash_value' and '==' because it invokes
3038-
// another request (even if it's cached).
3039-
BraceStmt *Body;
3040-
3041-
public:
3042-
PreCheckResultBuilderDescriptor(AnyFunctionRef Fn)
3043-
: Fn(Fn), Body(Fn.getBody()) {}
3044-
3045-
friend llvm::hash_code
3046-
hash_value(const PreCheckResultBuilderDescriptor &owner) {
3047-
return llvm::hash_combine(owner.Fn, owner.Body);
3048-
}
3049-
3050-
friend bool operator==(const PreCheckResultBuilderDescriptor &lhs,
3051-
const PreCheckResultBuilderDescriptor &rhs) {
3052-
return lhs.Fn == rhs.Fn && lhs.Body == rhs.Body;
3053-
}
3054-
3055-
friend bool operator!=(const PreCheckResultBuilderDescriptor &lhs,
3056-
const PreCheckResultBuilderDescriptor &rhs) {
3057-
return !(lhs == rhs);
3058-
}
3059-
3060-
friend SourceLoc extractNearestSourceLoc(PreCheckResultBuilderDescriptor d) {
3061-
return extractNearestSourceLoc(d.Fn);
3062-
}
3063-
3064-
friend void simple_display(llvm::raw_ostream &out,
3065-
const PreCheckResultBuilderDescriptor &d) {
3066-
simple_display(out, d.Fn);
3067-
}
3068-
};
3069-
3070-
enum class ResultBuilderBodyPreCheck : uint8_t {
3071-
/// There were no problems pre-checking the closure.
3072-
Okay,
3073-
3074-
/// There was an error pre-checking the closure.
3075-
Error,
3076-
3077-
/// The closure has a return statement.
3078-
HasReturnStmt,
3079-
};
3080-
3081-
class PreCheckResultBuilderRequest
3082-
: public SimpleRequest<PreCheckResultBuilderRequest,
3083-
ResultBuilderBodyPreCheck(
3084-
PreCheckResultBuilderDescriptor),
3031+
class BraceHasReturnRequest
3032+
: public SimpleRequest<BraceHasReturnRequest, bool(const BraceStmt *),
30853033
RequestFlags::Cached> {
30863034
public:
30873035
using SimpleRequest::SimpleRequest;
@@ -3090,8 +3038,7 @@ class PreCheckResultBuilderRequest
30903038
friend SimpleRequest;
30913039

30923040
// Evaluation.
3093-
ResultBuilderBodyPreCheck
3094-
evaluate(Evaluator &evaluator, PreCheckResultBuilderDescriptor owner) const;
3041+
bool evaluate(Evaluator &evaluator, const BraceStmt *BS) const;
30953042

30963043
public:
30973044
// Separate caching.

include/swift/AST/TypeCheckerTypeIDZone.def

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -381,8 +381,8 @@ SWIFT_REQUEST(TypeChecker, HasUserDefinedDesignatedInitRequest,
381381
bool(NominalTypeDecl *), Cached, NoLocationInfo)
382382
SWIFT_REQUEST(TypeChecker, HasMemberwiseInitRequest,
383383
bool(StructDecl *), Cached, NoLocationInfo)
384-
SWIFT_REQUEST(TypeChecker, PreCheckResultBuilderRequest,
385-
ResultBuilderBodyPreCheck(PreCheckResultBuilderDescriptor),
384+
SWIFT_REQUEST(TypeChecker, BraceHasReturnRequest,
385+
bool(const BraceStmt *),
386386
Cached, NoLocationInfo)
387387
SWIFT_REQUEST(TypeChecker, ResolveImplicitMemberRequest,
388388
evaluator::SideEffect(NominalTypeDecl *, ImplicitMemberAction),

lib/AST/TypeCheckRequests.cpp

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1317,25 +1317,6 @@ void AssociatedConformanceRequest::cacheResult(
13171317
conformance->setAssociatedConformance(index, assocConf);
13181318
}
13191319

1320-
//----------------------------------------------------------------------------//
1321-
// PreCheckResultBuilderRequest computation.
1322-
//----------------------------------------------------------------------------//
1323-
1324-
void swift::simple_display(llvm::raw_ostream &out,
1325-
ResultBuilderBodyPreCheck value) {
1326-
switch (value) {
1327-
case ResultBuilderBodyPreCheck::Okay:
1328-
out << "okay";
1329-
break;
1330-
case ResultBuilderBodyPreCheck::HasReturnStmt:
1331-
out << "has return statement";
1332-
break;
1333-
case ResultBuilderBodyPreCheck::Error:
1334-
out << "error";
1335-
break;
1336-
}
1337-
}
1338-
13391320
//----------------------------------------------------------------------------//
13401321
// HasCircularInheritedProtocolsRequest computation.
13411322
//----------------------------------------------------------------------------//

lib/Sema/BuilderTransform.cpp

Lines changed: 28 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -920,23 +920,10 @@ class ResultBuilderTransform
920920

921921
std::optional<BraceStmt *>
922922
TypeChecker::applyResultBuilderBodyTransform(FuncDecl *func, Type builderType) {
923-
// Pre-check the body: pre-check any expressions in it and look
924-
// for return statements.
925-
//
926-
// If we encountered an error or there was an explicit result type,
927-
// bail out and report that to the caller.
923+
// First look for any return statements, and bail if we have any.
928924
auto &ctx = func->getASTContext();
929-
auto request = PreCheckResultBuilderRequest{AnyFunctionRef(func)};
930-
switch (evaluateOrDefault(ctx.evaluator, request,
931-
ResultBuilderBodyPreCheck::Error)) {
932-
case ResultBuilderBodyPreCheck::Okay:
933-
// If the pre-check was okay, apply the result-builder transform.
934-
break;
935-
936-
case ResultBuilderBodyPreCheck::Error:
937-
return nullptr;
938-
939-
case ResultBuilderBodyPreCheck::HasReturnStmt: {
925+
if (evaluateOrDefault(ctx.evaluator, BraceHasReturnRequest{func->getBody()},
926+
false)) {
940927
// One or more explicit 'return' statements were encountered, which
941928
// disables the result builder transform. Warn when we do this.
942929
auto returnStmts = findReturnStatements(func);
@@ -970,7 +957,10 @@ TypeChecker::applyResultBuilderBodyTransform(FuncDecl *func, Type builderType) {
970957

971958
return std::nullopt;
972959
}
973-
}
960+
961+
auto target = SyntacticElementTarget(func);
962+
if (ConstraintSystem::preCheckTarget(target))
963+
return nullptr;
974964

975965
ConstraintSystemOptions options = ConstraintSystemFlags::AllowFixes;
976966
auto resultInterfaceTy = func->getResultInterfaceType();
@@ -1018,8 +1008,7 @@ TypeChecker::applyResultBuilderBodyTransform(FuncDecl *func, Type builderType) {
10181008
cs.Options |= ConstraintSystemFlags::ForCodeCompletion;
10191009
cs.solveForCodeCompletion(solutions);
10201010

1021-
SyntacticElementTarget funcTarget(func);
1022-
CompletionContextFinder analyzer(funcTarget, func->getDeclContext());
1011+
CompletionContextFinder analyzer(target, func->getDeclContext());
10231012
if (analyzer.hasCompletion()) {
10241013
filterSolutionsForCodeCompletion(solutions, analyzer);
10251014
for (const auto &solution : solutions) {
@@ -1066,7 +1055,7 @@ TypeChecker::applyResultBuilderBodyTransform(FuncDecl *func, Type builderType) {
10661055

10671056
case SolutionResult::Kind::UndiagnosedError:
10681057
reportSolutionsToSolutionCallback(salvagedResult);
1069-
cs.diagnoseFailureFor(SyntacticElementTarget(func));
1058+
cs.diagnoseFailureFor(target);
10701059
salvagedResult.markAsDiagnosed();
10711060
return nullptr;
10721061

@@ -1100,8 +1089,7 @@ TypeChecker::applyResultBuilderBodyTransform(FuncDecl *func, Type builderType) {
11001089
cs.applySolution(solutions.front());
11011090

11021091
// Apply the solution to the function body.
1103-
if (auto result =
1104-
cs.applySolution(solutions.front(), SyntacticElementTarget(func))) {
1092+
if (auto result = cs.applySolution(solutions.front(), target)) {
11051093
performSyntacticDiagnosticsForTarget(*result, /*isExprStmt*/ false);
11061094
auto *body = result->getFunctionBody();
11071095

@@ -1142,21 +1130,8 @@ ConstraintSystem::matchResultBuilder(AnyFunctionRef fn, Type builderType,
11421130
// not apply the result builder transform if it contained an explicit return.
11431131
// To maintain source compatibility, we still need to check for HasReturnStmt.
11441132
// https://github.com/apple/swift/issues/64332.
1145-
switch (evaluateOrDefault(getASTContext().evaluator,
1146-
PreCheckResultBuilderRequest{fn},
1147-
ResultBuilderBodyPreCheck::Error)) {
1148-
case ResultBuilderBodyPreCheck::Okay:
1149-
// If the pre-check was okay, apply the result-builder transform.
1150-
break;
1151-
1152-
case ResultBuilderBodyPreCheck::Error: {
1153-
llvm_unreachable(
1154-
"Running PreCheckResultBuilderRequest on a function shouldn't run "
1155-
"preCheckExpression and thus we should never enter this case.");
1156-
break;
1157-
}
1158-
1159-
case ResultBuilderBodyPreCheck::HasReturnStmt:
1133+
if (evaluateOrDefault(getASTContext().evaluator,
1134+
BraceHasReturnRequest{fn.getBody()}, false)) {
11601135
// Diagnostic mode means that solver couldn't reach any viable
11611136
// solution, so let's diagnose presence of a `return` statement
11621137
// in the closure body.
@@ -1257,138 +1232,48 @@ ConstraintSystem::matchResultBuilder(AnyFunctionRef fn, Type builderType,
12571232
}
12581233

12591234
namespace {
1260-
1261-
/// Pre-check all the expressions in the body.
1262-
class PreCheckResultBuilderApplication : public ASTWalker {
1263-
AnyFunctionRef Fn;
1264-
bool SkipPrecheck = false;
1265-
bool SuppressDiagnostics = false;
1235+
class ReturnStmtFinder : public ASTWalker {
12661236
std::vector<ReturnStmt *> ReturnStmts;
1267-
bool HasError = false;
1268-
1269-
bool hasReturnStmt() const { return !ReturnStmts.empty(); }
12701237

12711238
public:
1272-
PreCheckResultBuilderApplication(AnyFunctionRef fn, bool skipPrecheck,
1273-
bool suppressDiagnostics)
1274-
: Fn(fn), SkipPrecheck(skipPrecheck),
1275-
SuppressDiagnostics(suppressDiagnostics) {}
1276-
1277-
const std::vector<ReturnStmt *> getReturnStmts() const { return ReturnStmts; }
1278-
1279-
ResultBuilderBodyPreCheck run() {
1280-
Stmt *oldBody = Fn.getBody();
1281-
1282-
Stmt *newBody = oldBody->walk(*this);
1283-
1284-
// If the walk was aborted, it was because we had a problem of some kind.
1285-
assert((newBody == nullptr) == HasError &&
1286-
"unexpected short-circuit while walking body");
1287-
if (HasError)
1288-
return ResultBuilderBodyPreCheck::Error;
1289-
1290-
assert(oldBody == newBody && "pre-check walk wasn't in-place?");
1291-
1292-
if (hasReturnStmt())
1293-
return ResultBuilderBodyPreCheck::HasReturnStmt;
1294-
1295-
return ResultBuilderBodyPreCheck::Okay;
1239+
static std::vector<ReturnStmt *> find(const BraceStmt *BS) {
1240+
ReturnStmtFinder finder;
1241+
const_cast<BraceStmt *>(BS)->walk(finder);
1242+
return std::move(finder.ReturnStmts);
12961243
}
12971244

12981245
MacroWalking getMacroWalkingBehavior() const override {
12991246
return MacroWalking::Arguments;
13001247
}
13011248

13021249
PreWalkResult<Expr *> walkToExprPre(Expr *E) override {
1303-
if (SkipPrecheck)
1304-
return Action::SkipNode(E);
1305-
1306-
// Pre-check the expression. If this fails, abort the walk immediately.
1307-
// Otherwise, replace the expression with the result of pre-checking.
1308-
// In either case, don't recurse into the expression.
1309-
{
1310-
auto *DC = Fn.getAsDeclContext();
1311-
auto &diagEngine = DC->getASTContext().Diags;
1312-
1313-
// Suppress any diagnostics which could be produced by this expression.
1314-
DiagnosticTransaction transaction(diagEngine);
1315-
1316-
HasError |= ConstraintSystem::preCheckExpression(E, DC);
1317-
1318-
HasError |= transaction.hasErrors();
1319-
1320-
if (!HasError)
1321-
HasError |= containsErrorExpr(E);
1322-
1323-
if (SuppressDiagnostics)
1324-
transaction.abort();
1325-
1326-
if (HasError)
1327-
return Action::Stop();
1328-
1329-
return Action::SkipNode(E);
1330-
}
1250+
return Action::SkipNode(E);
13311251
}
13321252

13331253
PreWalkResult<Stmt *> walkToStmtPre(Stmt *S) override {
13341254
// If we see a return statement, note it..
1335-
if (auto returnStmt = dyn_cast<ReturnStmt>(S)) {
1336-
if (!returnStmt->isImplicit()) {
1337-
ReturnStmts.push_back(returnStmt);
1338-
return Action::SkipNode(S);
1339-
}
1340-
}
1341-
1342-
// Otherwise, recurse into the statement normally.
1343-
return Action::Continue(S);
1344-
}
1345-
1346-
/// Check whether given expression (including single-statement
1347-
/// closures) contains `ErrorExpr` as one of its sub-expressions.
1348-
bool containsErrorExpr(Expr *expr) {
1349-
bool hasError = false;
1350-
1351-
expr->forEachChildExpr([&](Expr *expr) -> Expr * {
1352-
hasError |= isa<ErrorExpr>(expr);
1353-
if (hasError)
1354-
return nullptr;
1255+
auto *returnStmt = dyn_cast<ReturnStmt>(S);
1256+
if (!returnStmt || returnStmt->isImplicit())
1257+
return Action::Continue(S);
13551258

1356-
if (auto *closure = dyn_cast<ClosureExpr>(expr)) {
1357-
if (closure->hasSingleExpressionBody()) {
1358-
hasError |= containsErrorExpr(closure->getSingleExpressionBody());
1359-
return hasError ? nullptr : expr;
1360-
}
1361-
}
1362-
1363-
return expr;
1364-
});
1365-
1366-
return hasError;
1259+
ReturnStmts.push_back(returnStmt);
1260+
return Action::SkipNode(S);
13671261
}
13681262

13691263
/// Ignore patterns.
13701264
PreWalkResult<Pattern *> walkToPatternPre(Pattern *pat) override {
13711265
return Action::SkipNode(pat);
13721266
}
13731267
};
1268+
} // end anonymous namespace
13741269

1375-
}
1376-
1377-
ResultBuilderBodyPreCheck PreCheckResultBuilderRequest::evaluate(
1378-
Evaluator &evaluator, PreCheckResultBuilderDescriptor owner) const {
1379-
// Closures should already be pre-checked when we run this, so there's no need
1380-
// to pre-check them again.
1381-
bool skipPrecheck = owner.Fn.getAbstractClosureExpr();
1382-
return PreCheckResultBuilderApplication(
1383-
owner.Fn, skipPrecheck, /*suppressDiagnostics=*/false)
1384-
.run();
1270+
bool BraceHasReturnRequest::evaluate(Evaluator &evaluator,
1271+
const BraceStmt *BS) const {
1272+
return !ReturnStmtFinder::find(BS).empty();
13851273
}
13861274

13871275
std::vector<ReturnStmt *> TypeChecker::findReturnStatements(AnyFunctionRef fn) {
1388-
PreCheckResultBuilderApplication precheck(fn, /*skipPreCheck=*/true,
1389-
/*SuppressDiagnostics=*/true);
1390-
(void)precheck.run();
1391-
return precheck.getReturnStmts();
1276+
return ReturnStmtFinder::find(fn.getBody());
13921277
}
13931278

13941279
ResultBuilderOpSupport TypeChecker::checkBuilderOpSupport(

test/Constraints/result_builder_diags.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ struct TupleBuilderWithoutIf { // expected-note 3{{struct 'TupleBuilderWithoutIf
7979
static func buildDo<T>(_ value: T) -> T { return value }
8080
}
8181

82-
func tuplify<T>(_ cond: Bool, @TupleBuilder body: (Bool) -> T) { // expected-note {{'tuplify(_:body:)' declared here}}
82+
func tuplify<T>(_ cond: Bool, @TupleBuilder body: (Bool) -> T) { // expected-note 2{{'tuplify(_:body:)' declared here}}
8383
print(body(cond))
8484
}
8585

@@ -455,7 +455,7 @@ func testNonExhaustiveSwitch(e: E) {
455455
// rdar://problem/59856491
456456
struct TestConstraintGenerationErrors {
457457
@TupleBuilder var buildTupleFnBody: String {
458-
let a = nil // There is no diagnostic here because next line fails to pre-check, so body is invalid
458+
let a = nil // expected-error {{'nil' requires a contextual type}}
459459
String(nothing) // expected-error {{cannot find 'nothing' in scope}}
460460
}
461461

@@ -722,7 +722,7 @@ struct TuplifiedStructWithInvalidClosure {
722722

723723
@TupleBuilder var errorsDiagnosedByParser: some Any {
724724
if let _ = condition {
725-
tuplify { _ in
725+
tuplify { _ in // expected-error {{missing argument for parameter #1 in call}}
726726
self. // expected-error {{expected member name following '.'}}
727727
}
728728
42

0 commit comments

Comments
 (0)