Skip to content

Commit 5b32099

Browse files
committed
[Constraint solver] Use a constraint system to apply all function builders.
When type checking the body of a function declaration that has a function builder on it (e.g., `@ViewBuilder var body: some View { ... }`), create a constraint system that is responsible for constraint generation and application, sending function declarations through the same code paths used by closures.
1 parent ac24491 commit 5b32099

File tree

10 files changed

+249
-56
lines changed

10 files changed

+249
-56
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4808,6 +4808,13 @@ NOTE(previous_function_builder_here, none,
48084808
"previous function builder specified here", ())
48094809
ERROR(function_builder_arguments, none,
48104810
"function builder attributes cannot have arguments", ())
4811+
WARNING(function_builder_disabled_by_return, none,
4812+
"application of function builder %0 disabled by explicit 'return' "
4813+
"statement", (Type))
4814+
NOTE(function_builder_remove_attr, none,
4815+
"remove the attribute to explicitly disable the function builder", ())
4816+
NOTE(function_builder_remove_returns, none,
4817+
"remove 'return' statements to apply the function builder", ())
48114818

48124819
//------------------------------------------------------------------------------
48134820
// MARK: differentiable programming diagnostics

lib/Sema/BuilderTransform.cpp

Lines changed: 149 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
//===----------------------------------------------------------------------===//
1717

1818
#include "ConstraintSystem.h"
19+
#include "SolutionResult.h"
1920
#include "TypeChecker.h"
2021
#include "swift/AST/ASTVisitor.h"
2122
#include "swift/AST/ASTWalker.h"
@@ -487,31 +488,128 @@ class BuilderClosureVisitor
487488

488489
} // end anonymous namespace
489490

490-
BraceStmt *
491-
TypeChecker::applyFunctionBuilderBodyTransform(FuncDecl *FD,
492-
BraceStmt *body,
493-
Type builderType) {
494-
// Try to build a single result expression.
495-
auto &ctx = FD->getASTContext();
496-
BuilderClosureVisitor visitor(ctx, nullptr,
497-
/*wantExpr=*/true, builderType);
498-
Expr *returnExpr = visitor.visit(body);
499-
if (!returnExpr)
500-
return nullptr;
491+
/// Find the return statements in the given body, which block the application
492+
/// of a function builder.
493+
static std::vector<ReturnStmt *> findReturnStatements(AnyFunctionRef fn);
494+
495+
Optional<BraceStmt *> TypeChecker::applyFunctionBuilderBodyTransform(
496+
FuncDecl *func, Type builderType) {
497+
// Pre-check the body: pre-check any expressions in it and look
498+
// for return statements.
499+
//
500+
// If we encountered an error or there was an explicit result type,
501+
// bail out and report that to the caller.
502+
auto &ctx = func->getASTContext();
503+
auto request = PreCheckFunctionBuilderRequest{func};
504+
switch (evaluateOrDefault(
505+
ctx.evaluator, request, FunctionBuilderClosurePreCheck::Error)) {
506+
case FunctionBuilderClosurePreCheck::Okay:
507+
// If the pre-check was okay, apply the function-builder transform.
508+
break;
501509

502-
// Make sure we have a usable result type for the body.
503-
Type returnType = AnyFunctionRef(FD).getBodyResultType();
504-
if (!returnType || returnType->hasError())
510+
case FunctionBuilderClosurePreCheck::Error:
505511
return nullptr;
506512

507-
auto loc = returnExpr->getStartLoc();
508-
auto returnStmt = new (ctx) ReturnStmt(loc, returnExpr, /*implicit*/ true);
509-
return BraceStmt::create(ctx, body->getLBraceLoc(), { returnStmt },
510-
body->getRBraceLoc());
513+
case FunctionBuilderClosurePreCheck::HasReturnStmt: {
514+
// One or more explicit 'return' statements were encountered, which
515+
// disables the function builder transform. Warn when we do this.
516+
auto returnStmts = findReturnStatements(func);
517+
assert(!returnStmts.empty());
518+
519+
ctx.Diags.diagnose(
520+
returnStmts.front()->getReturnLoc(),
521+
diag::function_builder_disabled_by_return, builderType);
522+
523+
// Note that one can remove the function builder attribute.
524+
auto attr = func->getAttachedFunctionBuilder();
525+
if (!attr) {
526+
if (auto accessor = dyn_cast<AccessorDecl>(func)) {
527+
attr = accessor->getStorage()->getAttachedFunctionBuilder();
528+
}
529+
}
530+
531+
if (attr) {
532+
ctx.Diags.diagnose(
533+
attr->getLocation(), diag::function_builder_remove_attr)
534+
.fixItRemove(attr->getRangeWithAt());
535+
attr->setInvalid();
536+
}
537+
538+
// Note that one can remove all of the return statements.
539+
{
540+
auto diag = ctx.Diags.diagnose(
541+
returnStmts.front()->getReturnLoc(),
542+
diag::function_builder_remove_returns);
543+
for (auto returnStmt : returnStmts) {
544+
diag.fixItRemove(returnStmt->getReturnLoc());
545+
}
546+
}
547+
548+
return None;
549+
}
550+
}
551+
552+
ConstraintSystemOptions options = ConstraintSystemFlags::AllowFixes;
553+
auto resultInterfaceTy = func->getResultInterfaceType();
554+
auto resultContextType = func->mapTypeIntoContext(resultInterfaceTy);
555+
556+
// Determine whether we're inferring the underlying type for the opaque
557+
// result type of this function.
558+
ConstraintKind resultConstraintKind = ConstraintKind::Conversion;
559+
if (auto opaque = resultContextType->getAs<OpaqueTypeArchetypeType>()) {
560+
if (opaque->getDecl()->isOpaqueReturnTypeOfFunction(func)) {
561+
options |= ConstraintSystemFlags::UnderlyingTypeForOpaqueReturnType;
562+
resultConstraintKind = ConstraintKind::OpaqueUnderlyingType;
563+
}
564+
}
565+
566+
// Build a constraint system in which we can check the body of the function.
567+
ConstraintSystem cs(func, options);
568+
569+
// FIXME: check the result
570+
cs.matchFunctionBuilder(func, builderType, resultContextType,
571+
resultConstraintKind,
572+
/*calleeLocator=*/nullptr,
573+
/*FIXME:*/ConstraintLocatorBuilder(nullptr));
574+
575+
// Solve the constraint system.
576+
SmallVector<Solution, 4> solutions;
577+
if (cs.solve(solutions) || solutions.size() != 1) {
578+
// Try to fix the system or provide a decent diagnostic.
579+
auto salvagedResult = cs.salvage();
580+
switch (salvagedResult.getKind()) {
581+
case SolutionResult::Kind::Success:
582+
solutions.clear();
583+
solutions.push_back(std::move(salvagedResult).takeSolution());
584+
break;
585+
586+
case SolutionResult::Kind::Error:
587+
case SolutionResult::Kind::Ambiguous:
588+
return nullptr;
589+
590+
case SolutionResult::Kind::UndiagnosedError:
591+
cs.diagnoseFailureFor(SolutionApplicationTarget(func));
592+
salvagedResult.markAsDiagnosed();
593+
return nullptr;
594+
595+
case SolutionResult::Kind::TooComplex:
596+
func->diagnose(diag::expression_too_complex)
597+
.highlight(func->getBodySourceRange());
598+
salvagedResult.markAsDiagnosed();
599+
return nullptr;
600+
}
601+
602+
// The system was salvaged; continue on as if nothing happened.
603+
}
604+
605+
// Apply the solution to the function body.
606+
return cast_or_null<BraceStmt>(
607+
cs.applySolutionToBody(solutions.front(), func));
511608
}
512609

513610
ConstraintSystem::TypeMatchResult ConstraintSystem::matchFunctionBuilder(
514611
AnyFunctionRef fn, Type builderType, Type bodyResultType,
612+
ConstraintKind bodyResultConstraintKind,
515613
ConstraintLocator *calleeLocator, ConstraintLocatorBuilder locator) {
516614
auto builder = builderType->getAnyNominal();
517615
assert(builder && "Bad function builder type");
@@ -524,7 +622,7 @@ ConstraintSystem::TypeMatchResult ConstraintSystem::matchFunctionBuilder(
524622
return getTypeMatchSuccess();
525623
}
526624

527-
// Pre-check the closure body: pre-check any expressions in it and look
625+
// Pre-check the body: pre-check any expressions in it and look
528626
// for return statements.
529627
auto request = PreCheckFunctionBuilderRequest{fn};
530628
switch (evaluateOrDefault(getASTContext().evaluator, request,
@@ -614,11 +712,12 @@ ConstraintSystem::TypeMatchResult ConstraintSystem::matchFunctionBuilder(
614712
}) == functionBuilderTransformed.end() &&
615713
"already transformed this closure along this path!?!");
616714
functionBuilderTransformed.push_back(
617-
std::make_pair(fn,
618-
AppliedBuilderTransform{builderType, singleExpr}));
715+
std::make_pair(
716+
fn,
717+
AppliedBuilderTransform{builderType, singleExpr, bodyResultType}));
619718

620719
// Bind the body result type to the type of the transformed expression.
621-
addConstraint(ConstraintKind::Equal, bodyResultType, transformedType,
720+
addConstraint(bodyResultConstraintKind, transformedType, bodyResultType,
622721
locator);
623722
return getTypeMatchSuccess();
624723
}
@@ -628,25 +727,31 @@ namespace {
628727
/// Pre-check all the expressions in the closure body.
629728
class PreCheckFunctionBuilderApplication : public ASTWalker {
630729
AnyFunctionRef Fn;
631-
bool HasReturnStmt = false;
730+
bool SkipPrecheck = false;
731+
std::vector<ReturnStmt *> ReturnStmts;
632732
bool HasError = false;
733+
734+
bool hasReturnStmt() const { return !ReturnStmts.empty(); }
735+
633736
public:
634-
PreCheckFunctionBuilderApplication(AnyFunctionRef fn) : Fn(fn) {}
737+
PreCheckFunctionBuilderApplication(AnyFunctionRef fn, bool skipPrecheck)
738+
: Fn(fn), SkipPrecheck(skipPrecheck) {}
739+
740+
const std::vector<ReturnStmt *> getReturnStmts() const { return ReturnStmts; }
635741

636742
FunctionBuilderClosurePreCheck run() {
637743
Stmt *oldBody = Fn.getBody();
638744

639745
Stmt *newBody = oldBody->walk(*this);
640746

641747
// If the walk was aborted, it was because we had a problem of some kind.
642-
assert((newBody == nullptr) == (HasError || HasReturnStmt) &&
748+
assert((newBody == nullptr) == HasError &&
643749
"unexpected short-circuit while walking body");
644-
if (!newBody) {
645-
if (HasError)
646-
return FunctionBuilderClosurePreCheck::Error;
750+
if (HasError)
751+
return FunctionBuilderClosurePreCheck::Error;
647752

753+
if (hasReturnStmt())
648754
return FunctionBuilderClosurePreCheck::HasReturnStmt;
649-
}
650755

651756
assert(oldBody == newBody && "pre-check walk wasn't in-place?");
652757

@@ -657,7 +762,8 @@ class PreCheckFunctionBuilderApplication : public ASTWalker {
657762
// Pre-check the expression. If this fails, abort the walk immediately.
658763
// Otherwise, replace the expression with the result of pre-checking.
659764
// In either case, don't recurse into the expression.
660-
if (ConstraintSystem::preCheckExpression(E, /*DC*/ Fn.getAsDeclContext())) {
765+
if (!SkipPrecheck &&
766+
ConstraintSystem::preCheckExpression(E, /*DC*/ Fn.getAsDeclContext())) {
661767
HasError = true;
662768
return std::make_pair(false, nullptr);
663769
}
@@ -666,10 +772,12 @@ class PreCheckFunctionBuilderApplication : public ASTWalker {
666772
}
667773

668774
std::pair<bool, Stmt *> walkToStmtPre(Stmt *S) override {
669-
// If we see a return statement, abort the walk immediately.
670-
if (isa<ReturnStmt>(S)) {
671-
HasReturnStmt = true;
672-
return std::make_pair(false, nullptr);
775+
// If we see a return statement, note it..
776+
if (auto returnStmt = dyn_cast<ReturnStmt>(S)) {
777+
if (!returnStmt->isImplicit()) {
778+
ReturnStmts.push_back(returnStmt);
779+
return std::make_pair(false, S);
780+
}
673781
}
674782

675783
// Otherwise, recurse into the statement normally.
@@ -688,5 +796,11 @@ PreCheckFunctionBuilderRequest::evaluate(Evaluator &eval,
688796
return FunctionBuilderClosurePreCheck::Okay;
689797
}
690798

691-
return PreCheckFunctionBuilderApplication(fn).run();
799+
return PreCheckFunctionBuilderApplication(fn, false).run();
800+
}
801+
802+
std::vector<ReturnStmt *> findReturnStatements(AnyFunctionRef fn) {
803+
PreCheckFunctionBuilderApplication precheck(fn, true);
804+
(void)precheck.run();
805+
return precheck.getReturnStmts();
692806
}

lib/Sema/CSApply.cpp

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7239,15 +7239,30 @@ llvm::PointerUnion<Expr *, Stmt *> ConstraintSystem::applySolutionImpl(
72397239
if (auto expr = target.getAsExpr()) {
72407240
result = expr->walk(walker);
72417241
} else {
7242-
// FIXME: Implement this!
7243-
llvm_unreachable("Not yet implemented");
7244-
7245-
#if false
72467242
auto fn = *target.getAsFunction();
7247-
auto transform = rewriter.getAppliedBuilderTransform(fn);
7248-
assert(transform && "Not applying builder transform?");
7249-
result = walker.applyFunctionBuilderBodyTransform(fn, transform);
7250-
#endif
7243+
7244+
// Dig out the function builder transformation we applied.
7245+
auto transformed = solution.functionBuilderTransformed.find(fn);
7246+
assert(transformed != solution.functionBuilderTransformed.end());
7247+
7248+
auto singleExpr = transformed->second.singleExpr;
7249+
singleExpr = singleExpr->walk(walker);
7250+
if (!singleExpr)
7251+
return result;
7252+
7253+
singleExpr = rewriter.coerceToType(singleExpr,
7254+
transformed->second.bodyResultType,
7255+
getConstraintLocator(singleExpr));
7256+
if (!singleExpr)
7257+
return result;
7258+
7259+
ASTContext &ctx = getASTContext();
7260+
auto returnStmt = new (ctx) ReturnStmt(
7261+
singleExpr->getStartLoc(), singleExpr, /*implicit=*/true);
7262+
auto braceStmt = BraceStmt::create(
7263+
ctx, returnStmt->getStartLoc(), ASTNode(returnStmt),
7264+
returnStmt->getEndLoc(), /*implicit=*/true);
7265+
result = braceStmt;
72517266
}
72527267

72537268
if (result.isNull())

lib/Sema/CSBindings.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -969,7 +969,8 @@ bool TypeVariableBinding::attempt(ConstraintSystem &cs) const {
969969
if (Binding.hasDefaultedLiteralProtocol()) {
970970
type = cs.openUnboundGenericType(type, dstLocator);
971971
type = type->reconstituteSugar(/*recursive=*/false);
972-
} else if (srcLocator->isLastElement<LocatorPathElt::ApplyArgToParam>() &&
972+
} else if (srcLocator &&
973+
srcLocator->isLastElement<LocatorPathElt::ApplyArgToParam>() &&
973974
!type->hasTypeVariable() && cs.isCollectionType(type)) {
974975
// If the type binding comes from the argument conversion, let's
975976
// instead of binding collection types directly, try to bind

lib/Sema/CSDiag.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2455,6 +2455,10 @@ void ConstraintSystem::diagnoseFailureFor(SolutionApplicationTarget target) {
24552455
// then it must be well-formed... but is ambiguous. Handle this by diagnostic
24562456
// various cases that come up.
24572457
diagnosis.diagnoseAmbiguity(expr);
2458+
} else {
2459+
// Emit a poor fallback message.
2460+
getASTContext().Diags.diagnose(
2461+
target.getAsFunction()->getLoc(), diag::failed_to_produce_diagnostic);
24582462
}
24592463
}
24602464

lib/Sema/CSSimplify.cpp

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1149,7 +1149,7 @@ ConstraintSystem::TypeMatchResult constraints::matchCallArguments(
11491149
auto result = cs.matchFunctionBuilder(
11501150
closure, functionBuilderType,
11511151
closureType->castTo<FunctionType>()->getResult(),
1152-
calleeLocator, loc);
1152+
ConstraintKind::Conversion, calleeLocator, loc);
11531153
if (result.isFailure())
11541154
return result;
11551155
}
@@ -6468,6 +6468,16 @@ ConstraintSystem::simplifyOneWayConstraint(
64686468
return SolutionKind::Unsolved;
64696469
}
64706470

6471+
// Propagate holes through one-way constraints.
6472+
if (secondSimplified->isHole()) {
6473+
first.visit([&](Type subType) {
6474+
if (auto *typeVar = subType->getAs<TypeVariableType>())
6475+
recordPotentialHole(typeVar);
6476+
});
6477+
6478+
return SolutionKind::Solved;
6479+
}
6480+
64716481
// Translate this constraint into a one-way binding constraint.
64726482
return matchTypes(first, secondSimplified, ConstraintKind::Equal, flags,
64736483
locator);
@@ -7447,8 +7457,9 @@ ConstraintSystem::simplifyApplicableFnConstraint(
74477457
}
74487458

74497459
// If right-hand side is a type variable, the constraint is unsolved.
7450-
if (desugar2->isTypeVariableOrMember())
7460+
if (desugar2->isTypeVariableOrMember()) {
74517461
return formUnsolved();
7462+
}
74527463

74537464
// Strip the 'ApplyFunction' off the locator.
74547465
// FIXME: Perhaps ApplyFunction can go away entirely?

0 commit comments

Comments
 (0)