Skip to content

Commit d8debe7

Browse files
committed
[Diagnostics] Make sure that fixes associated with function builders are found and diagnosed
Current logic in `applySolutionFixes` didn't actually look inside of single-expression closures (because of brace statement) or closures which are transformed by function builders. Resolves: rdar://problem/51167632
1 parent 77a642a commit d8debe7

File tree

2 files changed

+109
-30
lines changed

2 files changed

+109
-30
lines changed

lib/Sema/CSApply.cpp

Lines changed: 63 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -7458,44 +7458,78 @@ Expr *ConstraintSystem::coerceToRValue(Expr *expr) {
74587458
/// Emit the fixes computed as part of the solution, returning true if we were
74597459
/// able to emit an error message, or false if none of the fixits worked out.
74607460
bool ConstraintSystem::applySolutionFixes(Expr *E, const Solution &solution) {
7461-
llvm::SmallDenseMap<Expr *, SmallVector<const ConstraintFix *, 4>>
7462-
fixesPerExpr;
7463-
7461+
// First transfer all of the deduced information back
7462+
// to the constraint system.
74647463
applySolution(solution);
74657464

7466-
for (auto *fix : solution.Fixes)
7467-
fixesPerExpr[fix->getAnchor()].push_back(fix);
7465+
class DiagnosticWalker : public ASTWalker {
7466+
Expr *root;
7467+
const Solution &solution;
7468+
llvm::SmallDenseMap<Expr *, SmallVector<ConstraintFix *, 4>> fixesPerExpr;
74687469

7469-
auto diagnoseExprFailures = [&](Expr *expr) -> bool {
7470-
auto fixes = fixesPerExpr.find(expr);
7471-
if (fixes == fixesPerExpr.end())
7472-
return false;
7470+
/// Determines whether any error have been diagnosed while
7471+
/// trying to apply fixes associated with a given solution.
7472+
bool DiagnosedAnyErrors = false;
7473+
7474+
public:
7475+
DiagnosticWalker(Expr *expr, const Solution &solution)
7476+
: root(expr), solution(solution) {
7477+
for (auto *fix : solution.Fixes)
7478+
fixesPerExpr[fix->getAnchor()].push_back(fix);
7479+
}
7480+
7481+
std::pair<bool, Expr *> walkToExprPre(Expr *E) override {
7482+
// Diagnose root expression last.
7483+
if (E == root)
7484+
return {true, E};
7485+
7486+
if (auto *closure = dyn_cast<ClosureExpr>(E)) {
7487+
auto result = solution.builderTransformedClosures.find(closure);
7488+
if (result != solution.builderTransformedClosures.end()) {
7489+
auto *transformedExpr = result->second.second;
7490+
// Since this closure has been transformed into something
7491+
// else let's look inside transformed expression instead.
7492+
return {true, transformedExpr};
7493+
}
7494+
}
74737495

7474-
bool diagnosedError = false;
7475-
for (const auto *fix : fixes->second) {
7476-
auto diagnosed = fix->diagnose(E);
7496+
diagnose(E);
7497+
return {true, E};
7498+
}
74777499

7478-
if (fix->isWarning()) {
7479-
assert(diagnosed && "warnings should always be diagnosed");
7480-
(void)diagnosed;
7481-
} else {
7482-
diagnosedError |= diagnosed;
7500+
Expr *walkToExprPost(Expr *E) override {
7501+
if (E == root)
7502+
diagnose(E);
7503+
return E;
7504+
}
7505+
7506+
std::pair<bool, Stmt *> walkToStmtPre(Stmt *S) override {
7507+
return {true, S};
7508+
}
7509+
7510+
bool hadErrors() const { return DiagnosedAnyErrors; }
7511+
7512+
private:
7513+
void diagnose(Expr *E) {
7514+
auto fixes = fixesPerExpr.find(E);
7515+
if (fixes == fixesPerExpr.end())
7516+
return;
7517+
7518+
for (const auto *fix : fixes->second) {
7519+
auto diagnosed = fix->diagnose(root);
7520+
if (fix->isWarning()) {
7521+
assert(diagnosed && "warnings should always be diagnosed");
7522+
(void)diagnosed;
7523+
} else {
7524+
DiagnosedAnyErrors |= diagnosed;
7525+
}
74837526
}
74847527
}
7485-
return diagnosedError;
74867528
};
74877529

7488-
bool diagnosedError = false;
7489-
E->forEachChildExpr([&](Expr *subExpr) -> Expr * {
7490-
// Diagnose root expression at the end to
7491-
// preserve ordering.
7492-
if (subExpr != E)
7493-
diagnosedError |= diagnoseExprFailures(subExpr);
7494-
return subExpr;
7495-
});
7496-
7497-
diagnosedError |= diagnoseExprFailures(E);
7498-
return diagnosedError;
7530+
DiagnosticWalker diagnostics(E, solution);
7531+
E->walk(diagnostics);
7532+
return diagnostics.hadErrors();
74997533
}
75007534

75017535
/// Apply a given solution to the expression, producing a fully

test/Constraints/function_builder_diags.swift

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %target-typecheck-verify-swift
1+
// RUN: %target-typecheck-verify-swift -disable-availability-checking -enable-opaque-result-types
22

33
@_functionBuilder
44
struct TupleBuilder { // expected-note 2{{struct 'TupleBuilder' declared here}}
@@ -126,3 +126,48 @@ func testOverloading(name: String) {
126126
}
127127
}
128128
}
129+
130+
protocol P {
131+
associatedtype T
132+
}
133+
134+
struct AnyP : P {
135+
typealias T = Any
136+
init<T>(_: T) where T : P {}
137+
}
138+
139+
struct TupleP<U> : P {
140+
typealias T = U
141+
init(_: U) {}
142+
}
143+
144+
@_functionBuilder
145+
struct Builder {
146+
static func buildBlock<S0, S1>(_ stmt1: S0, _ stmt2: S1) // expected-note {{where 'S1' = 'Label<Any>.Type'}}
147+
-> TupleP<(S0, S1)> where S0: P, S1: P {
148+
return TupleP((stmt1, stmt2))
149+
}
150+
}
151+
152+
struct G<C> : P where C : P {
153+
typealias T = C
154+
init(@Builder _: () -> C) {}
155+
}
156+
157+
struct Text : P {
158+
typealias T = String
159+
init(_: T) {}
160+
}
161+
162+
struct Label<L> : P where L : P { // expected-note {{'L' declared as parameter to type 'Label'}}
163+
typealias T = L
164+
init(@Builder _: () -> L) {}
165+
}
166+
167+
func test_51167632() -> some P {
168+
AnyP(G { // expected-error {{static method 'buildBlock' requires that 'Label<Any>.Type' conform to 'P'}}
169+
Text("hello")
170+
Label // expected-error {{generic parameter 'L' could not be inferred}}
171+
// expected-note@-1 {{explicitly specify the generic arguments to fix this issue}} {{10-10=<<#L: P#>>}}
172+
})
173+
}

0 commit comments

Comments
 (0)