Skip to content

Commit 4b0d9a2

Browse files
committed
[ResultBuilders] Diagnose pre-check errors inline
Not all of the pre-check errors could be diagnosed by re-running `PreCheckExpression` e.g. key path expressions are mutated to a particular form after an error has been produced. To make the behavior consistent, let's allow pre-check to emit diagnostics and unify pre-check and constraint generation fixes. Resolves: rdar://77466241
1 parent 6f44ba4 commit 4b0d9a2

File tree

5 files changed

+34
-112
lines changed

5 files changed

+34
-112
lines changed

include/swift/Sema/CSFix.h

Lines changed: 6 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2203,17 +2203,9 @@ class AllowKeyPathWithoutComponents final : public ConstraintFix {
22032203

22042204
class IgnoreInvalidResultBuilderBody : public ConstraintFix {
22052205
protected:
2206-
enum class ErrorInPhase {
2207-
PreCheck,
2208-
ConstraintGeneration,
2209-
};
2210-
2211-
ErrorInPhase Phase;
2212-
2213-
IgnoreInvalidResultBuilderBody(ConstraintSystem &cs, ErrorInPhase phase,
2214-
ConstraintLocator *locator)
2215-
: ConstraintFix(cs, FixKind::IgnoreInvalidResultBuilderBody, locator),
2216-
Phase(phase) {}
2206+
IgnoreInvalidResultBuilderBody(ConstraintSystem &cs,
2207+
ConstraintLocator *locator)
2208+
: ConstraintFix(cs, FixKind::IgnoreInvalidResultBuilderBody, locator) {}
22172209

22182210
public:
22192211
std::string getName() const override {
@@ -2226,19 +2218,8 @@ class IgnoreInvalidResultBuilderBody : public ConstraintFix {
22262218
return diagnose(*commonFixes.front().first);
22272219
}
22282220

2229-
static IgnoreInvalidResultBuilderBody *
2230-
duringPreCheck(ConstraintSystem &cs, ConstraintLocator *locator) {
2231-
return create(cs, ErrorInPhase::PreCheck, locator);
2232-
}
2233-
2234-
static IgnoreInvalidResultBuilderBody *
2235-
duringConstraintGeneration(ConstraintSystem &cs, ConstraintLocator *locator) {
2236-
return create(cs, ErrorInPhase::ConstraintGeneration, locator);
2237-
}
2238-
2239-
private:
2240-
static IgnoreInvalidResultBuilderBody *
2241-
create(ConstraintSystem &cs, ErrorInPhase phase, ConstraintLocator *locator);
2221+
static IgnoreInvalidResultBuilderBody *create(ConstraintSystem &cs,
2222+
ConstraintLocator *locator);
22422223
};
22432224

22442225
class IgnoreResultBuilderWithReturnStmts final
@@ -2247,8 +2228,7 @@ class IgnoreResultBuilderWithReturnStmts final
22472228

22482229
IgnoreResultBuilderWithReturnStmts(ConstraintSystem &cs, Type builderTy,
22492230
ConstraintLocator *locator)
2250-
: IgnoreInvalidResultBuilderBody(cs, ErrorInPhase::PreCheck, locator),
2251-
BuilderType(builderTy) {}
2231+
: IgnoreInvalidResultBuilderBody(cs, locator), BuilderType(builderTy) {}
22522232

22532233
public:
22542234
bool diagnose(const Solution &solution, bool asNote = false) const override;

lib/Sema/BuilderTransform.cpp

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1691,27 +1691,28 @@ ConstraintSystem::matchResultBuilder(
16911691
assert(builder->getAttrs().hasAttribute<ResultBuilderAttr>());
16921692

16931693
if (InvalidResultBuilderBodies.count(fn)) {
1694-
(void)recordFix(
1695-
IgnoreInvalidResultBuilderBody::duringConstraintGeneration(
1696-
*this, getConstraintLocator(fn.getAbstractClosureExpr())));
1694+
(void)recordFix(IgnoreInvalidResultBuilderBody::create(
1695+
*this, getConstraintLocator(fn.getAbstractClosureExpr())));
16971696
return getTypeMatchSuccess();
16981697
}
16991698

17001699
// Pre-check the body: pre-check any expressions in it and look
17011700
// for return statements.
17021701
auto request =
1703-
PreCheckResultBuilderRequest{{fn, /*SuppressDiagnostics=*/true}};
1702+
PreCheckResultBuilderRequest{{fn, /*SuppressDiagnostics=*/false}};
17041703
switch (evaluateOrDefault(getASTContext().evaluator, request,
17051704
ResultBuilderBodyPreCheck::Error)) {
17061705
case ResultBuilderBodyPreCheck::Okay:
17071706
// If the pre-check was okay, apply the result-builder transform.
17081707
break;
17091708

17101709
case ResultBuilderBodyPreCheck::Error: {
1710+
InvalidResultBuilderBodies.insert(fn);
1711+
17111712
if (!shouldAttemptFixes())
17121713
return getTypeMatchFailure(locator);
17131714

1714-
if (recordFix(IgnoreInvalidResultBuilderBody::duringPreCheck(
1715+
if (recordFix(IgnoreInvalidResultBuilderBody::create(
17151716
*this, getConstraintLocator(fn.getAbstractClosureExpr()))))
17161717
return getTypeMatchFailure(locator);
17171718

@@ -1776,9 +1777,8 @@ ConstraintSystem::matchResultBuilder(
17761777
if (transaction.hasErrors()) {
17771778
InvalidResultBuilderBodies.insert(fn);
17781779

1779-
if (recordFix(
1780-
IgnoreInvalidResultBuilderBody::duringConstraintGeneration(
1781-
*this, getConstraintLocator(fn.getAbstractClosureExpr()))))
1780+
if (recordFix(IgnoreInvalidResultBuilderBody::create(
1781+
*this, getConstraintLocator(fn.getAbstractClosureExpr()))))
17821782
return getTypeMatchFailure(locator);
17831783

17841784
return getTypeMatchSuccess();
@@ -1869,7 +1869,7 @@ class PreCheckResultBuilderApplication : public ASTWalker {
18691869
DiagnosticTransaction transaction(diagEngine);
18701870

18711871
HasError |= ConstraintSystem::preCheckExpression(
1872-
E, DC, /*replaceInvalidRefsWithErrors=*/false);
1872+
E, DC, /*replaceInvalidRefsWithErrors=*/true);
18731873
HasError |= transaction.hasErrors();
18741874

18751875
if (!HasError) {

lib/Sema/CSFix.cpp

Lines changed: 6 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1734,78 +1734,14 @@ AllowKeyPathWithoutComponents::create(ConstraintSystem &cs,
17341734
}
17351735

17361736
bool IgnoreInvalidResultBuilderBody::diagnose(const Solution &solution,
1737-
bool asNote) const {
1738-
switch (Phase) {
1739-
// Handled below
1740-
case ErrorInPhase::PreCheck:
1741-
break;
1742-
case ErrorInPhase::ConstraintGeneration:
1743-
return true; // Already diagnosed by `matchResultBuilder`.
1744-
}
1745-
1746-
auto *S = castToExpr<ClosureExpr>(getAnchor())->getBody();
1747-
1748-
class PreCheckWalker : public ASTWalker {
1749-
DeclContext *DC;
1750-
DiagnosticTransaction Transaction;
1751-
// Check whether expression(s) in the body of the
1752-
// result builder had any `ErrorExpr`s before pre-check.
1753-
bool FoundErrorExpr = false;
1754-
1755-
public:
1756-
PreCheckWalker(DeclContext *dc)
1757-
: DC(dc), Transaction(dc->getASTContext().Diags) {}
1758-
1759-
std::pair<bool, Expr *> walkToExprPre(Expr *E) override {
1760-
// This has to be checked before `preCheckExpression`
1761-
// because pre-check would convert invalid references
1762-
// into `ErrorExpr`s.
1763-
E->forEachChildExpr([&](Expr *expr) {
1764-
FoundErrorExpr |= isa<ErrorExpr>(expr);
1765-
return FoundErrorExpr ? nullptr : expr;
1766-
});
1767-
1768-
auto hasError = ConstraintSystem::preCheckExpression(
1769-
E, DC, /*replaceInvalidRefsWithErrors=*/true);
1770-
1771-
return std::make_pair(false, hasError ? nullptr : E);
1772-
}
1773-
1774-
std::pair<bool, Stmt *> walkToStmtPre(Stmt *S) override {
1775-
return std::make_pair(true, S);
1776-
}
1777-
1778-
// Ignore patterns because result builder pre-check does so as well.
1779-
std::pair<bool, Pattern *> walkToPatternPre(Pattern *P) override {
1780-
return std::make_pair(false, P);
1781-
}
1782-
1783-
bool diagnosed() const {
1784-
// pre-check produced an error.
1785-
if (Transaction.hasErrors())
1786-
return true;
1787-
1788-
// If there were `ErrorExpr`s before pre-check
1789-
// they should have been diagnosed already by parser.
1790-
if (FoundErrorExpr) {
1791-
auto &DE = DC->getASTContext().Diags;
1792-
return DE.hadAnyError();
1793-
}
1794-
1795-
return false;
1796-
}
1797-
};
1798-
1799-
PreCheckWalker walker(solution.getDC());
1800-
S->walk(walker);
1801-
1802-
return walker.diagnosed();
1737+
bool asNote) const {
1738+
return true; // Already diagnosed by `matchResultBuilder`.
18031739
}
18041740

1805-
IgnoreInvalidResultBuilderBody *IgnoreInvalidResultBuilderBody::create(
1806-
ConstraintSystem &cs, ErrorInPhase phase, ConstraintLocator *locator) {
1807-
return new (cs.getAllocator())
1808-
IgnoreInvalidResultBuilderBody(cs, phase, locator);
1741+
IgnoreInvalidResultBuilderBody *
1742+
IgnoreInvalidResultBuilderBody::create(ConstraintSystem &cs,
1743+
ConstraintLocator *locator) {
1744+
return new (cs.getAllocator()) IgnoreInvalidResultBuilderBody(cs, locator);
18091745
}
18101746

18111747
bool SpecifyContextualTypeForNil::diagnose(const Solution &solution,

test/Constraints/rdar65320500.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,20 +24,20 @@ func test(_: Int) -> Bool {
2424
}
2525

2626
test_builder {
27-
let totalSeconds = 42000
28-
test(totalseconds / 3600) // expected-error {{cannot find 'totalseconds' in scope}}
27+
let totalSeconds = 42000 // expected-note {{'totalSeconds' declared here}}
28+
test(totalseconds / 3600) // expected-error {{cannot find 'totalseconds' in scope; did you mean 'totalSeconds'?}}
2929
}
3030

3131
test_builder {
3232
test(doesntExist()) // expected-error {{cannot find 'doesntExist' in scope}}
3333

34-
if let result = doesntExist() { // expected-error {{cannot find 'doesntExist' in scope}}
34+
if let result = doesntExist() {
3535
}
3636

37-
if bar = test(42) {} // expected-error {{cannot find 'bar' in scope}}
37+
if bar = test(42) {}
3838

39-
let foo = bar() // expected-error {{cannot find 'bar' in scope}}
39+
let foo = bar()
4040

41-
switch (doesntExist()) { // expected-error {{cannot find 'doesntExist' in scope}}
41+
switch (doesntExist()) {
4242
}
4343
}

test/Constraints/result_builder_diags.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,12 @@ func testOverloading(name: String) {
142142
}
143143
}
144144

145+
_ = overloadedTuplify(true) { cond in
146+
if cond {
147+
print(\"hello") // expected-error {{invalid component of Swift key path}}
148+
}
149+
}
150+
145151
let _: A = a1
146152

147153
_ = overloadedTuplify(true) { b in // expected-error {{ambiguous use of 'overloadedTuplify(_:body:)'}}
@@ -461,7 +467,7 @@ struct TestConstraintGenerationErrors {
461467
func buildTupleClosure() {
462468
tuplify(true) { _ in
463469
let a = nothing // expected-error {{cannot find 'nothing' in scope}}
464-
String(nothing) // expected-error {{cannot find 'nothing' in scope}}
470+
String(nothing)
465471
}
466472
}
467473
}

0 commit comments

Comments
 (0)