Skip to content

Commit e1e6c37

Browse files
authored
[ResultBuilders] Diagnose pre-check errors inline (#37353)
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 (cherry picked from commit 4b0d9a2)
1 parent 455cf7d commit e1e6c37

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
@@ -2183,17 +2183,9 @@ class AllowKeyPathWithoutComponents final : public ConstraintFix {
21832183

21842184
class IgnoreInvalidResultBuilderBody : public ConstraintFix {
21852185
protected:
2186-
enum class ErrorInPhase {
2187-
PreCheck,
2188-
ConstraintGeneration,
2189-
};
2190-
2191-
ErrorInPhase Phase;
2192-
2193-
IgnoreInvalidResultBuilderBody(ConstraintSystem &cs, ErrorInPhase phase,
2194-
ConstraintLocator *locator)
2195-
: ConstraintFix(cs, FixKind::IgnoreInvalidResultBuilderBody, locator),
2196-
Phase(phase) {}
2186+
IgnoreInvalidResultBuilderBody(ConstraintSystem &cs,
2187+
ConstraintLocator *locator)
2188+
: ConstraintFix(cs, FixKind::IgnoreInvalidResultBuilderBody, locator) {}
21972189

21982190
public:
21992191
std::string getName() const override {
@@ -2206,19 +2198,8 @@ class IgnoreInvalidResultBuilderBody : public ConstraintFix {
22062198
return diagnose(*commonFixes.front().first);
22072199
}
22082200

2209-
static IgnoreInvalidResultBuilderBody *
2210-
duringPreCheck(ConstraintSystem &cs, ConstraintLocator *locator) {
2211-
return create(cs, ErrorInPhase::PreCheck, locator);
2212-
}
2213-
2214-
static IgnoreInvalidResultBuilderBody *
2215-
duringConstraintGeneration(ConstraintSystem &cs, ConstraintLocator *locator) {
2216-
return create(cs, ErrorInPhase::ConstraintGeneration, locator);
2217-
}
2218-
2219-
private:
2220-
static IgnoreInvalidResultBuilderBody *
2221-
create(ConstraintSystem &cs, ErrorInPhase phase, ConstraintLocator *locator);
2201+
static IgnoreInvalidResultBuilderBody *create(ConstraintSystem &cs,
2202+
ConstraintLocator *locator);
22222203
};
22232204

22242205
class IgnoreResultBuilderWithReturnStmts final
@@ -2227,8 +2208,7 @@ class IgnoreResultBuilderWithReturnStmts final
22272208

22282209
IgnoreResultBuilderWithReturnStmts(ConstraintSystem &cs, Type builderTy,
22292210
ConstraintLocator *locator)
2230-
: IgnoreInvalidResultBuilderBody(cs, ErrorInPhase::PreCheck, locator),
2231-
BuilderType(builderTy) {}
2211+
: IgnoreInvalidResultBuilderBody(cs, locator), BuilderType(builderTy) {}
22322212

22332213
public:
22342214
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
@@ -1722,78 +1722,14 @@ AllowKeyPathWithoutComponents::create(ConstraintSystem &cs,
17221722
}
17231723

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

1793-
IgnoreInvalidResultBuilderBody *IgnoreInvalidResultBuilderBody::create(
1794-
ConstraintSystem &cs, ErrorInPhase phase, ConstraintLocator *locator) {
1795-
return new (cs.getAllocator())
1796-
IgnoreInvalidResultBuilderBody(cs, phase, locator);
1729+
IgnoreInvalidResultBuilderBody *
1730+
IgnoreInvalidResultBuilderBody::create(ConstraintSystem &cs,
1731+
ConstraintLocator *locator) {
1732+
return new (cs.getAllocator()) IgnoreInvalidResultBuilderBody(cs, locator);
17971733
}
17981734

17991735
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)