Skip to content

Commit 11d299c

Browse files
authored
Merge pull request #84698 from hamishknight/carousel
[Evaluator] Avoid emitting duplicate "through reference here" notes
2 parents 312caa3 + f643f3d commit 11d299c

22 files changed

+148
-99
lines changed

include/swift/AST/DiagnosticEngine.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,9 @@ namespace swift {
217217
const class Decl *getDecl() const { return Decl; }
218218
DiagnosticBehavior getBehaviorLimit() const { return BehaviorLimit; }
219219

220+
/// Retrieve the stored SourceLoc, or the location of the stored Decl.
221+
SourceLoc getLocOrDeclLoc() const;
222+
220223
void setLoc(SourceLoc loc) { Loc = loc; }
221224
void setIsChildNote(bool isChildNote) { IsChildNote = isChildNote; }
222225
void setDecl(const class Decl *decl) { Decl = decl; }
@@ -1473,6 +1476,13 @@ namespace swift {
14731476
/// Retrieve the underlying engine which will receive the diagnostics.
14741477
DiagnosticEngine &getUnderlyingDiags() const { return UnderlyingEngine; }
14751478

1479+
/// Iterates over each captured diagnostic, running a lambda with it.
1480+
void forEach(llvm::function_ref<void(const Diagnostic &)> body) const;
1481+
1482+
/// Filters the queued diagnostics, dropping any where the predicate
1483+
/// returns \c false.
1484+
void filter(llvm::function_ref<bool(const Diagnostic &)> predicate);
1485+
14761486
/// Clear this queue and erase all diagnostics recorded.
14771487
void clear() {
14781488
assert(QueueEngine.TransactionCount == 1 &&

include/swift/AST/DiagnosticsCommon.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ NOTE(kind_declname_declared_here,none,
2727
"%0 %1 declared here", (DescriptiveDeclKind, DeclName))
2828
NOTE(decl_declared_here_with_kind,none,
2929
"%kind0 declared here", (const Decl *))
30+
NOTE(through_decl_declared_here_with_kind,none,
31+
"through %kind0 declared here", (const Decl *))
3032

3133
ERROR(not_implemented,none,
3234
"INTERNAL ERROR: feature not implemented: %0", (StringRef))

lib/AST/DiagnosticEngine.cpp

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,16 @@ std::optional<const DiagnosticInfo *> Diagnostic::getWrappedDiagnostic() const {
177177
return std::nullopt;
178178
}
179179

180+
SourceLoc Diagnostic::getLocOrDeclLoc() const {
181+
if (auto loc = getLoc())
182+
return loc;
183+
184+
if (auto *D = getDecl())
185+
return D->getLoc();
186+
187+
return SourceLoc();
188+
}
189+
180190
static CharSourceRange toCharSourceRange(SourceManager &SM, SourceRange SR) {
181191
return CharSourceRange(SM, SR.Start, Lexer::getLocForEndOfToken(SM, SR.End));
182192
}
@@ -1438,24 +1448,18 @@ DiagnosticEngine::diagnosticInfoForDiagnostic(const Diagnostic &diagnostic,
14381448
return std::nullopt;
14391449

14401450
// Figure out the source location.
1441-
SourceLoc loc = diagnostic.getLoc();
1451+
SourceLoc loc = diagnostic.getLocOrDeclLoc();
14421452
if (loc.isInvalid() && diagnostic.getDecl()) {
1453+
// If the location of the decl is invalid, try to pretty-print it into a
1454+
// buffer and capture the source location there. Make sure we don't have an
1455+
// active request running since printing AST can kick requests that may
1456+
// themselves emit diagnostics. This won't help the underlying cycle, but it
1457+
// at least stops us from overflowing the stack.
14431458
const Decl *decl = diagnostic.getDecl();
1444-
// If a declaration was provided instead of a location, and that declaration
1445-
// has a location we can point to, use that location.
1446-
loc = decl->getLoc();
1447-
1448-
// If the location of the decl is invalid still, try to pretty-print the
1449-
// declaration into a buffer and capture the source location there. Make
1450-
// sure we don't have an active request running since printing AST can
1451-
// kick requests that may themselves emit diagnostics. This won't help the
1452-
// underlying cycle, but it at least stops us from overflowing the stack.
1453-
if (loc.isInvalid()) {
1454-
PrettyPrintDeclRequest req(decl);
1455-
auto &eval = decl->getASTContext().evaluator;
1456-
if (!eval.hasActiveRequest(req))
1457-
loc = evaluateOrDefault(eval, req, SourceLoc());
1458-
}
1459+
PrettyPrintDeclRequest req(decl);
1460+
auto &eval = decl->getASTContext().evaluator;
1461+
if (!eval.hasActiveRequest(req))
1462+
loc = evaluateOrDefault(eval, req, SourceLoc());
14591463
}
14601464

14611465
auto groupID = diagnostic.getGroupID();
@@ -1785,6 +1789,20 @@ void DiagnosticEngine::onTentativeDiagnosticFlush(Diagnostic &diagnostic) {
17851789
}
17861790
}
17871791

1792+
void DiagnosticQueue::forEach(
1793+
llvm::function_ref<void(const Diagnostic &)> body) const {
1794+
for (auto &activeDiag : QueueEngine.TentativeDiagnostics)
1795+
body(activeDiag.Diag);
1796+
}
1797+
1798+
void DiagnosticQueue::filter(
1799+
llvm::function_ref<bool(const Diagnostic &)> predicate) {
1800+
llvm::erase_if(QueueEngine.TentativeDiagnostics,
1801+
[&](detail::ActiveDiagnostic &activeDiag) {
1802+
return !predicate(activeDiag.Diag);
1803+
});
1804+
}
1805+
17881806
EncodedDiagnosticMessage::EncodedDiagnosticMessage(StringRef S)
17891807
: Message(Lexer::getEncodedStringSegment(S, Buf, /*IsFirstSegment=*/true,
17901808
/*IsLastSegment=*/true,

lib/AST/Evaluator.cpp

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "swift/AST/DiagnosticEngine.h"
2020
#include "swift/AST/TypeCheckRequests.h" // for ResolveMacroRequest
2121
#include "swift/Basic/Assertions.h"
22+
#include "swift/Basic/Defer.h"
2223
#include "swift/Basic/LangOptions.h"
2324
#include "swift/Basic/Range.h"
2425
#include "swift/Basic/SourceManager.h"
@@ -123,17 +124,40 @@ void Evaluator::diagnoseCycle(const ActiveRequest &request) {
123124
OS << "\n";
124125
}
125126

126-
request.diagnoseCycle(diags);
127-
for (const auto &step : llvm::reverse(activeRequests)) {
128-
if (step == request) return;
129-
130-
// Reporting the lifetime dependence location generates a redundant
131-
// diagnostic.
132-
if (step.getAs<LifetimeDependenceInfoRequest>()) {
133-
continue;
134-
}
127+
// Perform some filtering to avoid emitting duplicate 'through reference here'
128+
// notes.
129+
DiagnosticQueue cycleDiags(diags, /*emitOnDestruction*/ true);
130+
SWIFT_DEFER {
131+
auto isDefaultStepNote = [](const Diagnostic &diag) -> bool {
132+
return diag.getID() == diag::circular_reference_through.ID;
133+
};
134+
// First populate seen locs for everything except default step notes. If
135+
// we have a diagnostic or custom note at a given location we want to prefer
136+
// that over the default step note.
137+
llvm::DenseSet<SourceLoc> seenLocs;
138+
cycleDiags.forEach([&](const Diagnostic &diag) {
139+
if (isDefaultStepNote(diag))
140+
return;
141+
142+
if (auto loc = diag.getLocOrDeclLoc())
143+
seenLocs.insert(loc);
144+
});
145+
// Then we can filter out unnecessary default step notes.
146+
cycleDiags.filter([&](const Diagnostic &diag) -> bool {
147+
if (!isDefaultStepNote(diag))
148+
return true;
149+
150+
auto loc = diag.getLocOrDeclLoc();
151+
return loc && seenLocs.insert(loc).second;
152+
});
153+
};
154+
155+
request.diagnoseCycle(cycleDiags.getDiags());
135156

136-
step.noteCycleStep(diags);
157+
for (const auto &step : llvm::reverse(activeRequests)) {
158+
if (step == request)
159+
return;
160+
step.noteCycleStep(cycleDiags.getDiags());
137161
}
138162

139163
llvm_unreachable("Diagnosed a cycle but it wasn't represented in the stack");

lib/AST/NameLookupRequests.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ void SuperclassDeclRequest::diagnoseCycle(DiagnosticEngine &diags) const {
5858

5959
void SuperclassDeclRequest::noteCycleStep(DiagnosticEngine &diags) const {
6060
auto decl = std::get<0>(getStorage());
61-
diags.diagnose(decl, diag::decl_declared_here_with_kind, decl);
61+
diags.diagnose(decl, diag::through_decl_declared_here_with_kind, decl);
6262
}
6363

6464
std::optional<ClassDecl *> SuperclassDeclRequest::getCachedResult() const {

lib/AST/TypeCheckRequests.cpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ void EnumRawTypeRequest::diagnoseCycle(DiagnosticEngine &diags) const {
217217

218218
void EnumRawTypeRequest::noteCycleStep(DiagnosticEngine &diags) const {
219219
auto *decl = std::get<0>(getStorage());
220-
diags.diagnose(decl, diag::decl_declared_here_with_kind, decl);
220+
diags.diagnose(decl, diag::through_decl_declared_here_with_kind, decl);
221221
}
222222

223223
//----------------------------------------------------------------------------//
@@ -248,7 +248,7 @@ void ProtocolRequiresClassRequest::diagnoseCycle(DiagnosticEngine &diags) const
248248

249249
void ProtocolRequiresClassRequest::noteCycleStep(DiagnosticEngine &diags) const {
250250
auto *proto = std::get<0>(getStorage());
251-
diags.diagnose(proto, diag::decl_declared_here_with_kind, proto);
251+
diags.diagnose(proto, diag::through_decl_declared_here_with_kind, proto);
252252
}
253253

254254
std::optional<bool> ProtocolRequiresClassRequest::getCachedResult() const {
@@ -272,7 +272,7 @@ void ExistentialConformsToSelfRequest::diagnoseCycle(DiagnosticEngine &diags) co
272272

273273
void ExistentialConformsToSelfRequest::noteCycleStep(DiagnosticEngine &diags) const {
274274
auto *proto = std::get<0>(getStorage());
275-
diags.diagnose(proto, diag::decl_declared_here_with_kind, proto);
275+
diags.diagnose(proto, diag::through_decl_declared_here_with_kind, proto);
276276
}
277277

278278
std::optional<bool> ExistentialConformsToSelfRequest::getCachedResult() const {
@@ -298,7 +298,7 @@ void HasSelfOrAssociatedTypeRequirementsRequest::diagnoseCycle(
298298
void HasSelfOrAssociatedTypeRequirementsRequest::noteCycleStep(
299299
DiagnosticEngine &diags) const {
300300
auto *proto = std::get<0>(getStorage());
301-
diags.diagnose(proto, diag::decl_declared_here_with_kind, proto);
301+
diags.diagnose(proto, diag::through_decl_declared_here_with_kind, proto);
302302
}
303303

304304
std::optional<bool>
@@ -1448,7 +1448,7 @@ void HasCircularInheritedProtocolsRequest::diagnoseCycle(
14481448
void HasCircularInheritedProtocolsRequest::noteCycleStep(
14491449
DiagnosticEngine &diags) const {
14501450
auto *decl = std::get<0>(getStorage());
1451-
diags.diagnose(decl, diag::decl_declared_here_with_kind, decl);
1451+
diags.diagnose(decl, diag::through_decl_declared_here_with_kind, decl);
14521452
}
14531453

14541454
//----------------------------------------------------------------------------//
@@ -1462,7 +1462,7 @@ void HasCircularRawValueRequest::diagnoseCycle(DiagnosticEngine &diags) const {
14621462

14631463
void HasCircularRawValueRequest::noteCycleStep(DiagnosticEngine &diags) const {
14641464
auto *decl = std::get<0>(getStorage());
1465-
diags.diagnose(decl, diag::decl_declared_here_with_kind, decl);
1465+
diags.diagnose(decl, diag::through_decl_declared_here_with_kind, decl);
14661466
}
14671467

14681468
//----------------------------------------------------------------------------//
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// RUN: %target-typecheck-verify-swift
22

3-
typealias A = B // expected-error {{type alias 'A' references itself}} expected-note {{while resolving type 'B'}} expected-note {{through reference here}}
4-
typealias C = D // expected-note {{through reference here}} expected-note {{while resolving type 'D'}} expected-note {{through reference here}}
5-
typealias D = (A, Int) // expected-note {{through reference here}} expected-note {{while resolving type '(A, Int)'}} expected-note {{through reference here}}
6-
typealias B = C // expected-note {{through reference here}} expected-note {{while resolving type 'C'}} expected-note {{through reference here}}
3+
typealias A = B // expected-error {{type alias 'A' references itself}} expected-note {{while resolving type 'B'}}
4+
typealias C = D // expected-note {{through reference here}} expected-note {{while resolving type 'D'}}
5+
typealias D = (A, Int) // expected-note {{through reference here}} expected-note {{while resolving type '(A, Int)'}}
6+
typealias B = C // expected-note {{through reference here}} expected-note {{while resolving type 'C'}}

test/Generics/issue-69318.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public protocol ChildlessView: View where NodeChildren == EmptyViewGraphNodeChil
1515
// expected-error@-1 {{circular reference}}
1616

1717
public struct EmptyViewGraphNodeChildren<ParentView: ChildlessView>: ViewGraphNodeChildren {}
18-
// expected-note@-1 3{{through reference here}}
19-
// expected-error@-2 {{type 'EmptyViewGraphNodeChildren<ParentView>' does not conform to protocol 'ViewGraphNodeChildren'}}
20-
// expected-note@-3 {{add stubs for conformance}}
18+
// expected-note@-1:15 {{through reference here}}
19+
// expected-note@-2:70 {{through reference here}}
20+
// expected-error@-3 {{type 'EmptyViewGraphNodeChildren<ParentView>' does not conform to protocol 'ViewGraphNodeChildren'}}
21+
// expected-note@-4 {{add stubs for conformance}}

test/Generics/rdar123013710.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ public protocol P {
88
static func f() -> Any?
99
}
1010

11-
protocol Q: P where B == Never {} // expected-error {{circular reference}}
11+
protocol Q: P where B == Never {} // expected-error@:10 {{circular reference}}
1212

13-
extension Never: Q, P { // expected-note 2{{through reference here}}
13+
extension Never: Q, P { // expected-note@:1 {{through reference here}}
1414
public typealias A = Never
1515
public static func f() -> Any? { nil }
1616
}

test/Generics/rdar94848868.swift

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@
33
// This is too circular to work, but it shouldn't crash.
44

55
protocol MyCollectionProtocol: Collection where Iterator == MyCollectionIterator<Self> {}
6-
// expected-error@-1 {{circular reference}}
6+
// expected-error@-1:10 {{circular reference}}
77

88
struct MyCollectionIterator<MyCollection: MyCollectionProtocol>: IteratorProtocol {
9-
// expected-note@-1 3{{through reference here}}
10-
// expected-error@-2 {{type 'MyCollectionIterator<MyCollection>' does not conform to protocol 'IteratorProtocol'}}
11-
// expected-note@-3 {{add stubs for conformance}}
9+
// expected-note@-1:8 {{through reference here}}
10+
// expected-note@-2:66 {{through reference here}}
11+
// expected-error@-3 {{type 'MyCollectionIterator<MyCollection>' does not conform to protocol 'IteratorProtocol'}}
12+
// expected-note@-4 {{add stubs for conformance}}
1213
mutating func next() -> MyCollection.Element? {
1314
// expected-error@-1 {{'Element' is not a member type of type 'MyCollection'}}
1415
return nil

0 commit comments

Comments
 (0)