Skip to content

Commit 33fbfaf

Browse files
committed
Function builders: pre-check the original closure body in-place.
Prior to this patch, we pre-checked the result of applying the function-builder transformation, but only when we hadn't already pre-checked the closure before. This causes two problems to arise when the transformation is applied to the same closure along multiple branches of a disjunction. The first is that any expressions that are synthesized by the transformation will not be pre-checked the second time through, which is a problem if we try to apply different builder types to the same closure (we do cache expressions for identical builder types). The second is that the pre-check will rewrite sub-expressions in place *in the synthesized expression*, which means that top-level expressions in the original closure body (including `if` conditions) that are now nested in the synthesized expression will not be rewritten in the original closure and therefore will be encountered in their raw state the second time through. This patch causes all expressions in the original closure body to be pre-checked before doing any other work. We then pre-check the synthesized expression immediately before generating constraints for it in order to set up the AST appropriately for CSGen; this could be skipped if we just synthesized expressions the way that CSGen wants them, but that seems to be somewhat involved. Pre-checking is safe to apply to an expression multiple times, so it's fine if we take this path and then decide not to use a function builder. I've also merged the check for `return` statements into this same walk, which was convenient. Fixes rdar://53325810 at least, and probably also some bugs with applying different function builders to the same closure.
1 parent 755c02c commit 33fbfaf

File tree

4 files changed

+186
-61
lines changed

4 files changed

+186
-61
lines changed

lib/Sema/BuilderTransform.cpp

Lines changed: 108 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -451,45 +451,6 @@ class BuilderClosureVisitor
451451

452452
} // end anonymous namespace
453453

454-
/// Determine whether the given statement contains a 'return' statement anywhere.
455-
static bool hasReturnStmt(Stmt *stmt) {
456-
class ReturnStmtFinder : public ASTWalker {
457-
public:
458-
bool hasReturnStmt = false;
459-
460-
std::pair<bool, Expr *> walkToExprPre(Expr *expr) override {
461-
return { false, expr };
462-
}
463-
464-
std::pair<bool, Stmt *> walkToStmtPre(Stmt *stmt) override {
465-
// Did we find a 'return' statement?
466-
if (isa<ReturnStmt>(stmt)) {
467-
hasReturnStmt = true;
468-
}
469-
470-
return { !hasReturnStmt, stmt };
471-
}
472-
473-
Stmt *walkToStmtPost(Stmt *stmt) override {
474-
return hasReturnStmt ? nullptr : stmt;
475-
}
476-
477-
std::pair<bool, Pattern*> walkToPatternPre(Pattern *pattern) override {
478-
return { false, pattern };
479-
}
480-
481-
bool walkToDeclPre(Decl *D) override { return false; }
482-
483-
bool walkToTypeLocPre(TypeLoc &TL) override { return false; }
484-
485-
bool walkToTypeReprPre(TypeRepr *T) override { return false; }
486-
};
487-
488-
ReturnStmtFinder finder{};
489-
stmt->walk(finder);
490-
return finder.hasReturnStmt;
491-
}
492-
493454
BraceStmt *
494455
TypeChecker::applyFunctionBuilderBodyTransform(FuncDecl *FD,
495456
BraceStmt *body,
@@ -520,26 +481,36 @@ ConstraintSystem::TypeMatchResult ConstraintSystem::applyFunctionBuilder(
520481
assert(builder && "Bad function builder type");
521482
assert(builder->getAttrs().hasAttribute<FunctionBuilderAttr>());
522483

523-
// Check the form of this closure to see if we can apply the function-builder
524-
// translation at all.
525-
{
526-
// FIXME: Right now, single-expression closures suppress the function
527-
// builder translation.
528-
if (closure->hasSingleExpressionBody())
529-
return getTypeMatchSuccess();
530-
531-
// The presence of an explicit return suppresses the function builder
532-
// translation.
533-
if (hasReturnStmt(closure->getBody())) {
534-
return getTypeMatchSuccess();
535-
}
484+
// FIXME: Right now, single-expression closures suppress the function
485+
// builder translation.
486+
if (closure->hasSingleExpressionBody())
487+
return getTypeMatchSuccess();
488+
489+
// Pre-check the closure body: pre-check any expressions in it and look
490+
// for return statements.
491+
switch (TC.preCheckFunctionBuilderClosureBody(closure)) {
492+
case FunctionBuilderClosurePreCheck::Okay:
493+
// If the pre-check was okay, apply the function-builder transform.
494+
break;
536495

537-
// Check whether we can apply this function builder.
496+
case FunctionBuilderClosurePreCheck::Error:
497+
// If the pre-check had an error, flag that.
498+
return getTypeMatchFailure(locator);
499+
500+
case FunctionBuilderClosurePreCheck::HasReturnStmt:
501+
// If the closure has a return statement, suppress the transform but
502+
// continue solving the constraint system.
503+
return getTypeMatchSuccess();
504+
}
505+
506+
// Check the form of this closure to see if we can apply the
507+
// function-builder translation at all.
508+
{
509+
// Check whether we can apply this specific function builder.
538510
BuilderClosureVisitor visitor(getASTContext(), this,
539511
/*wantExpr=*/false, builderType);
540512
(void)visitor.visit(closure->getBody());
541513

542-
543514
// If we saw a control-flow statement or declaration that the builder
544515
// cannot handle, we don't have a well-formed function builder application.
545516
if (visitor.unhandledNode) {
@@ -584,8 +555,12 @@ ConstraintSystem::TypeMatchResult ConstraintSystem::applyFunctionBuilder(
584555
/*wantExpr=*/true, builderType);
585556
Expr *singleExpr = visitor.visit(closure->getBody());
586557

587-
if (TC.precheckedClosures.insert(closure).second &&
588-
TC.preCheckExpression(singleExpr, closure))
558+
// We've already pre-checked all the original expressions, but do the
559+
// pre-check to the generated expression just to set up any preconditions
560+
// that CSGen might have.
561+
//
562+
// TODO: just build the AST the way we want it in the first place.
563+
if (TC.preCheckExpression(singleExpr, closure))
589564
return getTypeMatchFailure(locator);
590565

591566
singleExpr = generateConstraints(singleExpr, closure);
@@ -613,3 +588,80 @@ ConstraintSystem::TypeMatchResult ConstraintSystem::applyFunctionBuilder(
613588
locator);
614589
return getTypeMatchSuccess();
615590
}
591+
592+
namespace {
593+
594+
/// Pre-check all the expressions in the closure body.
595+
class PreCheckFunctionBuilderClosure : public ASTWalker {
596+
TypeChecker &TC;
597+
ClosureExpr *Closure;
598+
bool HasReturnStmt = false;
599+
bool HasError = false;
600+
public:
601+
PreCheckFunctionBuilderClosure(TypeChecker &tc, ClosureExpr *closure)
602+
: TC(tc), Closure(closure) {}
603+
604+
FunctionBuilderClosurePreCheck run() {
605+
Stmt *oldBody = Closure->getBody();
606+
607+
Stmt *newBody = oldBody->walk(*this);
608+
609+
// If the walk was aborted, it was because we had a problem of some kind.
610+
assert((newBody == nullptr) == (HasError || HasReturnStmt) &&
611+
"unexpected short-circuit while walking closure body");
612+
if (!newBody) {
613+
if (HasError)
614+
return FunctionBuilderClosurePreCheck::Error;
615+
616+
return FunctionBuilderClosurePreCheck::HasReturnStmt;
617+
}
618+
619+
assert(oldBody == newBody && "pre-check walk wasn't in-place?");
620+
621+
return FunctionBuilderClosurePreCheck::Okay;
622+
}
623+
624+
std::pair<bool, Expr *> walkToExprPre(Expr *E) override {
625+
// Pre-check the expression. If this fails, abort the walk immediately.
626+
// Otherwise, replace the expression with the result of pre-checking.
627+
// In either case, don't recurse into the expression.
628+
if (TC.preCheckExpression(E, /*DC*/ Closure)) {
629+
HasError = true;
630+
return std::make_pair(false, nullptr);
631+
}
632+
633+
return std::make_pair(false, E);
634+
}
635+
636+
std::pair<bool, Stmt *> walkToStmtPre(Stmt *S) override {
637+
// If we see a return statement, abort the walk immediately.
638+
if (isa<ReturnStmt>(S)) {
639+
HasReturnStmt = true;
640+
return std::make_pair(false, nullptr);
641+
}
642+
643+
// Otherwise, recurse into the statement normally.
644+
return std::make_pair(true, S);
645+
}
646+
};
647+
648+
}
649+
650+
FunctionBuilderClosurePreCheck
651+
TypeChecker::preCheckFunctionBuilderClosureBody(ClosureExpr *closure) {
652+
// Single-expression closures should already have been pre-checked.
653+
if (closure->hasSingleExpressionBody())
654+
return FunctionBuilderClosurePreCheck::Okay;
655+
656+
// Check whether we've already done this analysis.
657+
auto it = precheckedFunctionBuilderClosures.find(closure);
658+
if (it != precheckedFunctionBuilderClosures.end())
659+
return it->second;
660+
661+
auto result = PreCheckFunctionBuilderClosure(*this, closure).run();
662+
663+
// Cache the result.
664+
precheckedFunctionBuilderClosures.insert(std::make_pair(closure, result));
665+
666+
return result;
667+
}

lib/Sema/TypeChecker.h

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,17 @@ enum class CheckedCastContextKind {
529529
EnumElementPattern,
530530
};
531531

532+
enum class FunctionBuilderClosurePreCheck : uint8_t {
533+
/// There were no problems pre-checking the closure.
534+
Okay,
535+
536+
/// There was an error pre-checking the closure.
537+
Error,
538+
539+
/// The closure has a return statement.
540+
HasReturnStmt,
541+
};
542+
532543
/// The Swift type checker, which takes a parsed AST and performs name binding,
533544
/// type checking, and semantic analysis to produce a type-annotated AST.
534545
class TypeChecker final : public LazyResolver {
@@ -617,8 +628,10 @@ class TypeChecker final : public LazyResolver {
617628
/// when executing scripts.
618629
bool InImmediateMode = false;
619630

620-
/// Closure expressions that have already been prechecked.
621-
llvm::SmallPtrSet<ClosureExpr *, 2> precheckedClosures;
631+
/// Closure expressions whose bodies have already been prechecked as
632+
/// part of trying to apply a function builder.
633+
llvm::DenseMap<ClosureExpr *, FunctionBuilderClosurePreCheck>
634+
precheckedFunctionBuilderClosures;
622635

623636
TypeChecker(ASTContext &Ctx);
624637
friend class ASTContext;
@@ -1169,6 +1182,16 @@ class TypeChecker final : public LazyResolver {
11691182
/// expression and folding sequence expressions.
11701183
bool preCheckExpression(Expr *&expr, DeclContext *dc);
11711184

1185+
/// Pre-check the body of the given closure, which we are about to
1186+
/// generate constraints for.
1187+
///
1188+
/// This mutates the body of the closure, but only in ways that should be
1189+
/// valid even if we end up not actually applying the function-builder
1190+
/// transform: it just does a normal pre-check of all the expressions in
1191+
/// the closure.
1192+
FunctionBuilderClosurePreCheck
1193+
preCheckFunctionBuilderClosureBody(ClosureExpr *closure);
1194+
11721195
/// \name Name lookup
11731196
///
11741197
/// Routines that perform name lookup.

test/Constraints/function_builder.swift

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,3 +301,52 @@ func testAcceptColorTagged(b: Bool, i: Int, s: String, d: Double) {
301301
}
302302

303303
testAcceptColorTagged(b: true, i: 17, s: "Hello", d: 3.14159)
304+
305+
// rdar://53325810
306+
307+
// Test that we don't have problems with expression pre-checking when
308+
// type-checking an overloaded function-builder call. In particular,
309+
// we need to make sure that expressions in the closure are pre-checked
310+
// before we build constraints for them. Note that top-level expressions
311+
// that need to be rewritten by expression prechecking (such as the operator
312+
// sequences in the boolean conditions and statements below) won't be
313+
// rewritten in the original closure body if we just precheck the
314+
// expressions produced by the function-builder transformation.
315+
struct ForEach1<Data : RandomAccessCollection, Content> {
316+
var data: Data
317+
var content: (Data.Element) -> Content
318+
319+
func show() {
320+
print(content(data.first!))
321+
print(content(data.last!))
322+
}
323+
}
324+
extension ForEach1 where Data.Element: StringProtocol {
325+
// Checking this overload shouldn't trigger inappropriate caching that
326+
// affects checking the next overload.
327+
init(_ data: Data,
328+
@TupleBuilder content: @escaping (Data.Element) -> Content) {
329+
self.init(data: data, content: content)
330+
}
331+
}
332+
extension ForEach1 where Data == Range<Int> {
333+
// This is the overload we actually want.
334+
init(_ data: Data,
335+
@TupleBuilder content: @escaping (Int) -> Content) {
336+
self.init(data: data, content: content)
337+
}
338+
}
339+
let testForEach1 = ForEach1(-10 ..< 10) { i in
340+
"testForEach1"
341+
if i < 0 {
342+
"begin"
343+
i < -5
344+
} else {
345+
i > 5
346+
"end"
347+
}
348+
}
349+
testForEach1.show()
350+
351+
// CHECK: ("testForEach1", main.Either<(Swift.String, Swift.Bool), (Swift.Bool, Swift.String)>.first("begin", true))
352+
// CHECK: ("testForEach1", main.Either<(Swift.String, Swift.Bool), (Swift.Bool, Swift.String)>.second(true, "end"))

test/Constraints/function_builder_diags.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ func testDiags() {
7676
tuplify(true) { _ in
7777
17
7878
for c in name { // expected-error{{closure containing control flow statement cannot be used with function builder 'TupleBuilder'}}
79+
// expected-error@-1 {{use of unresolved identifier 'name'}}
7980
}
8081
}
8182

@@ -97,11 +98,11 @@ func testDiags() {
9798
struct A { }
9899
struct B { }
99100

100-
func overloadedTuplify<T>(_ cond: Bool, @TupleBuilder body: (Bool) -> T) -> A {
101+
func overloadedTuplify<T>(_ cond: Bool, @TupleBuilder body: (Bool) -> T) -> A { // expected-note {{found this candidate}}
101102
return A()
102103
}
103104

104-
func overloadedTuplify<T>(_ cond: Bool, @TupleBuilderWithoutIf body: (Bool) -> T) -> B {
105+
func overloadedTuplify<T>(_ cond: Bool, @TupleBuilderWithoutIf body: (Bool) -> T) -> B { // expected-note {{found this candidate}}
105106
return B()
106107
}
107108

@@ -114,7 +115,7 @@ func testOverloading(name: String) {
114115

115116
let _: A = a1
116117

117-
_ = overloadedTuplify(true) { b in
118+
_ = overloadedTuplify(true) { b in // expected-error {{ambiguous use of 'overloadedTuplify(_:body:)'}}
118119
b ? "Hello, \(name)" : "Goodbye"
119120
42
120121
overloadedTuplify(false) {

0 commit comments

Comments
 (0)