Merge pull request #84698 from hamishknight/carousel

[Evaluator] Avoid emitting duplicate "through reference here" notes
This commit is contained in:
Hamish Knight
2025-10-07 20:14:52 +01:00
committed by GitHub
22 changed files with 148 additions and 99 deletions

View File

@@ -217,6 +217,9 @@ namespace swift {
const class Decl *getDecl() const { return Decl; }
DiagnosticBehavior getBehaviorLimit() const { return BehaviorLimit; }
/// Retrieve the stored SourceLoc, or the location of the stored Decl.
SourceLoc getLocOrDeclLoc() const;
void setLoc(SourceLoc loc) { Loc = loc; }
void setIsChildNote(bool isChildNote) { IsChildNote = isChildNote; }
void setDecl(const class Decl *decl) { Decl = decl; }
@@ -1473,6 +1476,13 @@ namespace swift {
/// Retrieve the underlying engine which will receive the diagnostics.
DiagnosticEngine &getUnderlyingDiags() const { return UnderlyingEngine; }
/// Iterates over each captured diagnostic, running a lambda with it.
void forEach(llvm::function_ref<void(const Diagnostic &)> body) const;
/// Filters the queued diagnostics, dropping any where the predicate
/// returns \c false.
void filter(llvm::function_ref<bool(const Diagnostic &)> predicate);
/// Clear this queue and erase all diagnostics recorded.
void clear() {
assert(QueueEngine.TransactionCount == 1 &&

View File

@@ -27,6 +27,8 @@ NOTE(kind_declname_declared_here,none,
"%0 %1 declared here", (DescriptiveDeclKind, DeclName))
NOTE(decl_declared_here_with_kind,none,
"%kind0 declared here", (const Decl *))
NOTE(through_decl_declared_here_with_kind,none,
"through %kind0 declared here", (const Decl *))
ERROR(not_implemented,none,
"INTERNAL ERROR: feature not implemented: %0", (StringRef))

View File

@@ -177,6 +177,16 @@ std::optional<const DiagnosticInfo *> Diagnostic::getWrappedDiagnostic() const {
return std::nullopt;
}
SourceLoc Diagnostic::getLocOrDeclLoc() const {
if (auto loc = getLoc())
return loc;
if (auto *D = getDecl())
return D->getLoc();
return SourceLoc();
}
static CharSourceRange toCharSourceRange(SourceManager &SM, SourceRange SR) {
return CharSourceRange(SM, SR.Start, Lexer::getLocForEndOfToken(SM, SR.End));
}
@@ -1438,24 +1448,18 @@ DiagnosticEngine::diagnosticInfoForDiagnostic(const Diagnostic &diagnostic,
return std::nullopt;
// Figure out the source location.
SourceLoc loc = diagnostic.getLoc();
SourceLoc loc = diagnostic.getLocOrDeclLoc();
if (loc.isInvalid() && diagnostic.getDecl()) {
// If the location of the decl is invalid, try to pretty-print it into a
// buffer and capture the source location there. Make sure we don't have an
// active request running since printing AST can kick requests that may
// themselves emit diagnostics. This won't help the underlying cycle, but it
// at least stops us from overflowing the stack.
const Decl *decl = diagnostic.getDecl();
// If a declaration was provided instead of a location, and that declaration
// has a location we can point to, use that location.
loc = decl->getLoc();
// If the location of the decl is invalid still, try to pretty-print the
// declaration into a buffer and capture the source location there. Make
// sure we don't have an active request running since printing AST can
// kick requests that may themselves emit diagnostics. This won't help the
// underlying cycle, but it at least stops us from overflowing the stack.
if (loc.isInvalid()) {
PrettyPrintDeclRequest req(decl);
auto &eval = decl->getASTContext().evaluator;
if (!eval.hasActiveRequest(req))
loc = evaluateOrDefault(eval, req, SourceLoc());
}
PrettyPrintDeclRequest req(decl);
auto &eval = decl->getASTContext().evaluator;
if (!eval.hasActiveRequest(req))
loc = evaluateOrDefault(eval, req, SourceLoc());
}
auto groupID = diagnostic.getGroupID();
@@ -1785,6 +1789,20 @@ void DiagnosticEngine::onTentativeDiagnosticFlush(Diagnostic &diagnostic) {
}
}
void DiagnosticQueue::forEach(
llvm::function_ref<void(const Diagnostic &)> body) const {
for (auto &activeDiag : QueueEngine.TentativeDiagnostics)
body(activeDiag.Diag);
}
void DiagnosticQueue::filter(
llvm::function_ref<bool(const Diagnostic &)> predicate) {
llvm::erase_if(QueueEngine.TentativeDiagnostics,
[&](detail::ActiveDiagnostic &activeDiag) {
return !predicate(activeDiag.Diag);
});
}
EncodedDiagnosticMessage::EncodedDiagnosticMessage(StringRef S)
: Message(Lexer::getEncodedStringSegment(S, Buf, /*IsFirstSegment=*/true,
/*IsLastSegment=*/true,

View File

@@ -19,6 +19,7 @@
#include "swift/AST/DiagnosticEngine.h"
#include "swift/AST/TypeCheckRequests.h" // for ResolveMacroRequest
#include "swift/Basic/Assertions.h"
#include "swift/Basic/Defer.h"
#include "swift/Basic/LangOptions.h"
#include "swift/Basic/Range.h"
#include "swift/Basic/SourceManager.h"
@@ -123,17 +124,40 @@ void Evaluator::diagnoseCycle(const ActiveRequest &request) {
OS << "\n";
}
request.diagnoseCycle(diags);
// Perform some filtering to avoid emitting duplicate 'through reference here'
// notes.
DiagnosticQueue cycleDiags(diags, /*emitOnDestruction*/ true);
SWIFT_DEFER {
auto isDefaultStepNote = [](const Diagnostic &diag) -> bool {
return diag.getID() == diag::circular_reference_through.ID;
};
// First populate seen locs for everything except default step notes. If
// we have a diagnostic or custom note at a given location we want to prefer
// that over the default step note.
llvm::DenseSet<SourceLoc> seenLocs;
cycleDiags.forEach([&](const Diagnostic &diag) {
if (isDefaultStepNote(diag))
return;
if (auto loc = diag.getLocOrDeclLoc())
seenLocs.insert(loc);
});
// Then we can filter out unnecessary default step notes.
cycleDiags.filter([&](const Diagnostic &diag) -> bool {
if (!isDefaultStepNote(diag))
return true;
auto loc = diag.getLocOrDeclLoc();
return loc && seenLocs.insert(loc).second;
});
};
request.diagnoseCycle(cycleDiags.getDiags());
for (const auto &step : llvm::reverse(activeRequests)) {
if (step == request) return;
// Reporting the lifetime dependence location generates a redundant
// diagnostic.
if (step.getAs<LifetimeDependenceInfoRequest>()) {
continue;
}
step.noteCycleStep(diags);
if (step == request)
return;
step.noteCycleStep(cycleDiags.getDiags());
}
llvm_unreachable("Diagnosed a cycle but it wasn't represented in the stack");

View File

@@ -58,7 +58,7 @@ void SuperclassDeclRequest::diagnoseCycle(DiagnosticEngine &diags) const {
void SuperclassDeclRequest::noteCycleStep(DiagnosticEngine &diags) const {
auto decl = std::get<0>(getStorage());
diags.diagnose(decl, diag::decl_declared_here_with_kind, decl);
diags.diagnose(decl, diag::through_decl_declared_here_with_kind, decl);
}
std::optional<ClassDecl *> SuperclassDeclRequest::getCachedResult() const {

View File

@@ -217,7 +217,7 @@ void EnumRawTypeRequest::diagnoseCycle(DiagnosticEngine &diags) const {
void EnumRawTypeRequest::noteCycleStep(DiagnosticEngine &diags) const {
auto *decl = std::get<0>(getStorage());
diags.diagnose(decl, diag::decl_declared_here_with_kind, decl);
diags.diagnose(decl, diag::through_decl_declared_here_with_kind, decl);
}
//----------------------------------------------------------------------------//
@@ -248,7 +248,7 @@ void ProtocolRequiresClassRequest::diagnoseCycle(DiagnosticEngine &diags) const
void ProtocolRequiresClassRequest::noteCycleStep(DiagnosticEngine &diags) const {
auto *proto = std::get<0>(getStorage());
diags.diagnose(proto, diag::decl_declared_here_with_kind, proto);
diags.diagnose(proto, diag::through_decl_declared_here_with_kind, proto);
}
std::optional<bool> ProtocolRequiresClassRequest::getCachedResult() const {
@@ -272,7 +272,7 @@ void ExistentialConformsToSelfRequest::diagnoseCycle(DiagnosticEngine &diags) co
void ExistentialConformsToSelfRequest::noteCycleStep(DiagnosticEngine &diags) const {
auto *proto = std::get<0>(getStorage());
diags.diagnose(proto, diag::decl_declared_here_with_kind, proto);
diags.diagnose(proto, diag::through_decl_declared_here_with_kind, proto);
}
std::optional<bool> ExistentialConformsToSelfRequest::getCachedResult() const {
@@ -298,7 +298,7 @@ void HasSelfOrAssociatedTypeRequirementsRequest::diagnoseCycle(
void HasSelfOrAssociatedTypeRequirementsRequest::noteCycleStep(
DiagnosticEngine &diags) const {
auto *proto = std::get<0>(getStorage());
diags.diagnose(proto, diag::decl_declared_here_with_kind, proto);
diags.diagnose(proto, diag::through_decl_declared_here_with_kind, proto);
}
std::optional<bool>
@@ -1448,7 +1448,7 @@ void HasCircularInheritedProtocolsRequest::diagnoseCycle(
void HasCircularInheritedProtocolsRequest::noteCycleStep(
DiagnosticEngine &diags) const {
auto *decl = std::get<0>(getStorage());
diags.diagnose(decl, diag::decl_declared_here_with_kind, decl);
diags.diagnose(decl, diag::through_decl_declared_here_with_kind, decl);
}
//----------------------------------------------------------------------------//
@@ -1462,7 +1462,7 @@ void HasCircularRawValueRequest::diagnoseCycle(DiagnosticEngine &diags) const {
void HasCircularRawValueRequest::noteCycleStep(DiagnosticEngine &diags) const {
auto *decl = std::get<0>(getStorage());
diags.diagnose(decl, diag::decl_declared_here_with_kind, decl);
diags.diagnose(decl, diag::through_decl_declared_here_with_kind, decl);
}
//----------------------------------------------------------------------------//

View File

@@ -1,6 +1,6 @@
// RUN: %target-typecheck-verify-swift
typealias A = B // expected-error {{type alias 'A' references itself}} expected-note {{while resolving type 'B'}} expected-note {{through reference here}}
typealias C = D // expected-note {{through reference here}} expected-note {{while resolving type 'D'}} expected-note {{through reference here}}
typealias D = (A, Int) // expected-note {{through reference here}} expected-note {{while resolving type '(A, Int)'}} expected-note {{through reference here}}
typealias B = C // expected-note {{through reference here}} expected-note {{while resolving type 'C'}} expected-note {{through reference here}}
typealias A = B // expected-error {{type alias 'A' references itself}} expected-note {{while resolving type 'B'}}
typealias C = D // expected-note {{through reference here}} expected-note {{while resolving type 'D'}}
typealias D = (A, Int) // expected-note {{through reference here}} expected-note {{while resolving type '(A, Int)'}}
typealias B = C // expected-note {{through reference here}} expected-note {{while resolving type 'C'}}

View File

@@ -15,6 +15,7 @@ public protocol ChildlessView: View where NodeChildren == EmptyViewGraphNodeChil
// expected-error@-1 {{circular reference}}
public struct EmptyViewGraphNodeChildren<ParentView: ChildlessView>: ViewGraphNodeChildren {}
// expected-note@-1 3{{through reference here}}
// expected-error@-2 {{type 'EmptyViewGraphNodeChildren<ParentView>' does not conform to protocol 'ViewGraphNodeChildren'}}
// expected-note@-3 {{add stubs for conformance}}
// expected-note@-1:15 {{through reference here}}
// expected-note@-2:70 {{through reference here}}
// expected-error@-3 {{type 'EmptyViewGraphNodeChildren<ParentView>' does not conform to protocol 'ViewGraphNodeChildren'}}
// expected-note@-4 {{add stubs for conformance}}

View File

@@ -8,9 +8,9 @@ public protocol P {
static func f() -> Any?
}
protocol Q: P where B == Never {} // expected-error {{circular reference}}
protocol Q: P where B == Never {} // expected-error@:10 {{circular reference}}
extension Never: Q, P { // expected-note 2{{through reference here}}
extension Never: Q, P { // expected-note@:1 {{through reference here}}
public typealias A = Never
public static func f() -> Any? { nil }
}

View File

@@ -3,12 +3,13 @@
// This is too circular to work, but it shouldn't crash.
protocol MyCollectionProtocol: Collection where Iterator == MyCollectionIterator<Self> {}
// expected-error@-1 {{circular reference}}
// expected-error@-1:10 {{circular reference}}
struct MyCollectionIterator<MyCollection: MyCollectionProtocol>: IteratorProtocol {
// expected-note@-1 3{{through reference here}}
// expected-error@-2 {{type 'MyCollectionIterator<MyCollection>' does not conform to protocol 'IteratorProtocol'}}
// expected-note@-3 {{add stubs for conformance}}
// expected-note@-1:8 {{through reference here}}
// expected-note@-2:66 {{through reference here}}
// expected-error@-3 {{type 'MyCollectionIterator<MyCollection>' does not conform to protocol 'IteratorProtocol'}}
// expected-note@-4 {{add stubs for conformance}}
mutating func next() -> MyCollection.Element? {
// expected-error@-1 {{'Element' is not a member type of type 'MyCollection'}}
return nil

View File

@@ -637,17 +637,14 @@ struct PatternBindingWithTwoVars2 { var x = y, y = 3 }
// expected-error@-1 {{cannot use instance member 'y' within property initializer; property initializers run before 'self' is available}}
struct PatternBindingWithTwoVars3 { var x = y, y = x }
// expected-error@-1 {{circular reference}}
// expected-note@-2 {{through reference here}}
// expected-note@-3 {{through reference here}}
// expected-note@-4 {{through reference here}}
// expected-note@-5 {{through reference here}}
// expected-note@-6 {{through reference here}}
// expected-error@-1:41 {{circular reference}}
// expected-note@-2:37 {{through reference here}}
// expected-note@-3:48 {{through reference here}}
// https://github.com/apple/swift/issues/51518
do {
let closure1 = { closure2() } // expected-error {{circular reference}} expected-note {{through reference here}} expected-note {{through reference here}}
let closure2 = { closure1() } // expected-note {{through reference here}} expected-note {{through reference here}} expected-note {{through reference here}}
let closure1 = { closure2() } // expected-error {{circular reference}} expected-note {{through reference here}}
let closure2 = { closure1() } // expected-note {{through reference here}} expected-note {{through reference here}}
}
func color(with value: Int) -> Int {

View File

@@ -2,4 +2,4 @@
struct S {}
typealias S = S // expected-error {{type alias 'S' references itself}} expected-note {{while resolving type 'S'}} expected-note {{through reference here}}
typealias S = S // expected-error {{type alias 'S' references itself}} expected-note {{while resolving type 'S'}}

View File

@@ -68,12 +68,11 @@ class C1 {
func run(a: Int) {}
}
class C2: C1, P {
// expected-note@-1 2{{through reference here}}
class C2: C1, P { // expected-note@:7 {{through reference here}}
override func run(a: A) {}
// expected-error@-1 {{circular reference}}
// expected-note@-2 {{while resolving type 'A'}}
// expected-note@-3 2{{through reference here}}
// expected-error@-1:19 {{circular reference}}
// expected-note@-2:26 {{while resolving type 'A'}}
// expected-note@-3:23 {{through reference here}}
}
// Another crash to the above
@@ -84,12 +83,12 @@ open class G1<A> {
class C3: G1<A>, P {
// expected-error@-1 {{type 'C3' does not conform to protocol 'P'}}
// expected-error@-2 {{cannot find type 'A' in scope}}
// expected-note@-3 2{{through reference here}}
// expected-note@-3:7 {{through reference here}}
// expected-note@-4 {{add stubs for conformance}}
override func run(a: A) {}
// expected-error@-1 {{circular reference}}
// expected-note@-2 2 {{through reference here}}
// expected-note@-3 {{while resolving type 'A'}}
// expected-error@-1:19 {{circular reference}}
// expected-note@-2:26 {{while resolving type 'A'}}
// expected-note@-3:23 {{through reference here}}
}
// Another case that triggers circular override checking.
@@ -102,10 +101,10 @@ class C4 {
required init(x: Int) {}
}
class D4 : C4, P1 { // expected-note 4 {{through reference here}}
required init(x: X) { // expected-error {{circular reference}}
// expected-note@-1 {{while resolving type 'X'}}
// expected-note@-2 2{{through reference here}}
class D4 : C4, P1 { // expected-note@:7 {{through reference here}}
required init(x: X) { // expected-error@:12 {{circular reference}}
// expected-note@-1:20 {{while resolving type 'X'}}
// expected-note@-2:17 {{through reference here}}
super.init(x: x)
}
}
@@ -114,6 +113,6 @@ class D4 : C4, P1 { // expected-note 4 {{through reference here}}
// N.B. This used to compile in 5.1.
protocol P_54662 { }
class C_54662 { // expected-note {{through reference here}}
typealias Nest = P_54662 // expected-error {{circular reference}} expected-note {{through reference here}}
typealias Nest = P_54662 // expected-error {{circular reference}}
}
extension C_54662: C_54662.Nest { }

View File

@@ -1,18 +1,18 @@
// RUN: %target-typecheck-verify-swift
class Left // expected-error {{'Left' inherits from itself}} expected-note {{through reference here}}
class Left // expected-error {{'Left' inherits from itself}}
: Right.Hand { // expected-note {{through reference here}}
class Hand {}
}
class Right // expected-note {{through reference here}} expected-note{{class 'Right' declared here}}
class Right // expected-note {{through class 'Right' declared here}}
: Left.Hand { // expected-note {{through reference here}}
class Hand {}
}
class C : B { } // expected-error{{'C' inherits from itself}}
class B : A { } // expected-note{{class 'B' declared here}}
class A : C { } // expected-note{{class 'A' declared here}}
class B : A { } // expected-note{{through class 'B' declared here}}
class A : C { } // expected-note{{through class 'A' declared here}}
class TrivialCycle : TrivialCycle {} // expected-error{{'TrivialCycle' inherits from itself}}
protocol P : P {} // expected-error {{protocol 'P' refines itself}}
@@ -26,13 +26,13 @@ class Outer {
class Inner : Outer {}
}
class Outer2 // expected-error {{'Outer2' inherits from itself}} expected-note {{through reference here}}
class Outer2 // expected-error {{'Outer2' inherits from itself}}
: Outer2.Inner { // expected-note {{through reference here}}
class Inner {}
}
class Outer3 // expected-error {{'Outer3' inherits from itself}} expected-note {{through reference here}}
class Outer3 // expected-error {{'Outer3' inherits from itself}}
: Outer3.Inner<Int> { // expected-note {{through reference here}}
class Inner<T> {}
}

View File

@@ -10,7 +10,7 @@ class C {
init() {}
}
typealias t = t // expected-error {{type alias 't' references itself}} expected-note {{while resolving type 't'}} expected-note {{through reference here}}
typealias t = t // expected-error {{type alias 't' references itself}} expected-note {{while resolving type 't'}}
extension Foo {
convenience init() {} // expected-error{{invalid redeclaration of synthesized 'init()'}}

View File

@@ -113,8 +113,8 @@ struct DoesNotConform : Up {
protocol CircleStart : CircleEnd { func circle_start() } // expected-error 2 {{protocol 'CircleStart' refines itself}}
protocol CircleMiddle : CircleStart { func circle_middle() }
// expected-note@-1 2 {{protocol 'CircleMiddle' declared here}}
protocol CircleEnd : CircleMiddle { func circle_end()} // expected-note 2 {{protocol 'CircleEnd' declared here}}
// expected-note@-1 2 {{through protocol 'CircleMiddle' declared here}}
protocol CircleEnd : CircleMiddle { func circle_end()} // expected-note 2 {{through protocol 'CircleEnd' declared here}}
protocol CircleEntry : CircleTrivial { }
protocol CircleTrivial : CircleTrivial { } // expected-error {{protocol 'CircleTrivial' refines itself}}

View File

@@ -15,7 +15,7 @@ var bfx : Int, bfy : Int
_ = 10
var self1 = self1
// expected-note@-1 2{{through reference here}}
// expected-note@-1 {{through reference here}}
// expected-error@-2 {{circular reference}}
var self2 : Int = self2
@@ -23,15 +23,15 @@ var (self3) : Int = self3
var (self4) : Int = self4
var self5 = self5 + self5
// expected-note@-1 2{{through reference here}}
// expected-note@-1 {{through reference here}}
// expected-error@-2 {{circular reference}}
var self6 = !self6
// expected-note@-1 2{{through reference here}}
// expected-note@-1 {{through reference here}}
// expected-error@-2 {{circular reference}}
var (self7a, self7b) = (self7b, self7a)
// expected-note@-1 2{{through reference here}}
// expected-note@-1 {{through reference here}}
// expected-error@-2 {{circular reference}}
var self8 = 0
@@ -94,12 +94,10 @@ weak let V = SomeClass() // ok since SE-0481
// expected-note@-3 {{a strong reference is required to prevent the instance from being deallocated}}
let a = b ; let b = a
// expected-error@-1 {{circular reference}}
// expected-note@-2 {{through reference here}}
// expected-note@-3 {{through reference here}}
// expected-note@-4 {{through reference here}}
// expected-note@-5 {{through reference here}}
// expected-note@-6 {{through reference here}}
// expected-error@-1:1 {{circular reference}}
// expected-note@-2:5 {{through reference here}}
// expected-note@-3:13 {{through reference here}}
// expected-note@-4:17 {{through reference here}}
// <rdar://problem/17501765> Swift should warn about immutable default initialized values
let uselessValue : String?

View File

@@ -3,7 +3,7 @@
_ = "HI!
// expected-error@-1{{unterminated string literal}}
var self1 = self1
// expected-note@-1 2{{through reference here}}
// expected-note@-1 {{through reference here}}
// expected-error@-2 {{circular reference}}
struct Broken {

View File

@@ -8,7 +8,7 @@ _ = "HI!
// FIXME: This used to produce a localized diagnostic.
var self1 = self1 // expected-note 2{{through reference here}}
var self1 = self1 // expected-note {{through reference here}}
// expected-error@-1 {{circular reference}}
struct Broken {

View File

@@ -6,7 +6,7 @@
_ = "HI!
// expected-error@-1{{unterminated string literal}}
var self1 = self1
// expected-note@-1 2{{through reference here}}
// expected-note@-1 {{through reference here}}
// expected-error@-2 {{circular reference}}
struct Broken {

View File

@@ -9,8 +9,8 @@
do {
do {
protocol P1 : P2 {}
// expected-no-explicit-any-note@-1 2 {{protocol 'P1' declared here}}
// expected-explicit-any-note@-2 1 {{protocol 'P1' declared here}}
// expected-no-explicit-any-note@-1 2 {{through protocol 'P1' declared here}}
// expected-explicit-any-note@-2 1 {{through protocol 'P1' declared here}}
protocol P2 : P1 {}
// expected-no-explicit-any-error@-1 2 {{protocol 'P2' refines itself}}
// expected-explicit-any-error@-2 1 {{protocol 'P2' refines itself}}
@@ -23,8 +23,8 @@ do {
do {
protocol P0 { associatedtype A }
protocol P1 : P2, P0 {}
// expected-no-explicit-any-note@-1 2 {{protocol 'P1' declared here}}
// expected-explicit-any-note@-2 1 {{protocol 'P1' declared here}}
// expected-no-explicit-any-note@-1 2 {{through protocol 'P1' declared here}}
// expected-explicit-any-note@-2 1 {{through protocol 'P1' declared here}}
protocol P2 : P1 {}
// expected-no-explicit-any-error@-1 2 {{protocol 'P2' refines itself}}
// expected-explicit-any-error@-2 1 {{protocol 'P2' refines itself}}

View File

@@ -2,9 +2,8 @@
public enum E : E.R {
// expected-error@-1 {{'E' has a raw type that depends on itself}}
// expected-note@-2 2{{through reference here}}
// expected-note@-3 {{while resolving type 'E.R'}}
// expected-error@-4 {{'R' is not a member type of enum 'rdar123543175.E'}}
// expected-note@-5 {{'E' declared here}}
// expected-note@-2 {{while resolving type 'E.R'}}
// expected-error@-3 {{'R' is not a member type of enum 'rdar123543175.E'}}
// expected-note@-4 {{'E' declared here}}
case x
}