From 65a3b883f35ae77f0fb0bedc9da7d67cc5b2cddb Mon Sep 17 00:00:00 2001 From: Allan Shortlidge Date: Sun, 26 Oct 2025 21:22:41 -0700 Subject: [PATCH] Sema: Fix a regression in retroactive conformance diagnostic suppression. Module qualifying a protocol name in an inheritance clause should suppress retroactive conformance diagnostics for the protocol and any protocols it inherits from, just like adding the `@retroactive` attribute. Fixes a regression in Swift 6.2 introduced by https://github.com/swiftlang/swift/pull/81576. Resolves rdar://162295268 and https://github.com/swiftlang/swift/issues/85153. --- lib/Sema/TypeCheckDeclPrimary.cpp | 39 +++++++++++-------- .../extension_retroactive_conformances.swift | 29 +++++++++++--- 2 files changed, 46 insertions(+), 22 deletions(-) diff --git a/lib/Sema/TypeCheckDeclPrimary.cpp b/lib/Sema/TypeCheckDeclPrimary.cpp index fa1a9f71ac021..a7f0db6066251 100644 --- a/lib/Sema/TypeCheckDeclPrimary.cpp +++ b/lib/Sema/TypeCheckDeclPrimary.cpp @@ -1720,11 +1720,16 @@ static TypeRepr *unwrapAttributedRepr(TypeRepr *repr) { return repr; } +struct ProtocolAndRetroactiveStatus { + ProtocolDecl *proto = nullptr; + bool isMarkedRetroactive = false; +}; + static void collectProtocolsFromInheritedEntry( - const InheritedEntry &entry, - Type inheritedTy, - llvm::SmallPtrSetImpl &protocolsWithRetroactiveAttr, - SmallVectorImpl &protos) { + const InheritedEntry &entry, Type inheritedTy, + SmallVectorImpl &protos) { + + bool isMarkedRetroactive = entry.isRetroactive(); if (auto *protoTy = inheritedTy->getAs()) { auto *proto = protoTy->getDecl(); @@ -1732,18 +1737,18 @@ static void collectProtocolsFromInheritedEntry( // As a fallback, to support previous language versions, also allow // this through if the protocol has been explicitly module-qualified. TypeRepr *repr = unwrapAttributedRepr(entry.getTypeRepr()); - if (isModuleQualified(repr, proto->getParentModule())) { - protocolsWithRetroactiveAttr.insert(proto); - } + if (isModuleQualified(repr, proto->getParentModule())) + isMarkedRetroactive = true; - protos.push_back(proto); + protos.push_back({proto, isMarkedRetroactive}); } else if (auto *pct = inheritedTy->getAs()) { for (auto member : pct->getMembers()) { - collectProtocolsFromInheritedEntry(entry, member, - protocolsWithRetroactiveAttr, protos); + // FIXME: Check for module qualification on each composed protocol. + collectProtocolsFromInheritedEntry(entry, member, protos); } } else if (auto *ppt = inheritedTy->getAs()) { - protos.push_back(ppt->getProtocol()); + // FIXME: Check for module qualification. + protos.push_back({ppt->getProtocol(), isMarkedRetroactive}); } } @@ -1810,11 +1815,12 @@ static void diagnoseRetroactiveConformances( continue; } - SmallVector protos; - collectProtocolsFromInheritedEntry(entry, inheritedTy, - protocolsWithRetroactiveAttr, protos); + SmallVector protosAndStatuses; + collectProtocolsFromInheritedEntry(entry, inheritedTy, protosAndStatuses); + + for (auto protoAndStatus : protosAndStatuses) { + auto proto = protoAndStatus.proto; - for (auto *proto : protos) { proto->walkInheritedProtocols([&](ProtocolDecl *decl) { // If this isn't a retroactive conformance, skip it. auto found = protocols.find(proto); @@ -1846,8 +1852,7 @@ static void diagnoseRetroactiveConformances( return TypeWalker::Action::Continue; } - // If it's marked @retroactive, no need to warn. - if (entry.isRetroactive()) { + if (protoAndStatus.isMarkedRetroactive) { // Note that we encountered this protocol through a conformance marked // @retroactive in case multiple clauses cause the protocol to be // inherited. diff --git a/test/Sema/extension_retroactive_conformances.swift b/test/Sema/extension_retroactive_conformances.swift index cd2f3af72d977..56f939dc564cb 100644 --- a/test/Sema/extension_retroactive_conformances.swift +++ b/test/Sema/extension_retroactive_conformances.swift @@ -24,15 +24,20 @@ public struct Sample3 {} public struct Sample4 {} public struct Sample5 {} public struct Sample6 {} +public struct Sample6a {} +public struct Sample6b {} +public struct Sample6c {} public struct Sample7 {} public struct Sample8 {} +public struct Sample8a {} public struct SampleAlreadyConforms: SampleProtocol1 {} public struct GenericSample1 {} public struct Sample9 {} -public struct Sample10 {} +public struct Sample9a {} +public struct Sample9b {} #else @@ -83,9 +88,13 @@ typealias MySample6 = Sample6 extension MySample6: SampleProtocol1 {} // expected-warning {{extension declares a conformance of imported type 'Sample6' to imported protocol 'SampleProtocol1'}} // expected-note @-1 {{add '@retroactive' to silence this warning}} {{22-37=@retroactive SampleProtocol1}} -// Ensure module-qualifying both types still silences the warning +// Ensure module-qualifying the protocol silences the warning -extension Library.Sample6: Library.SampleProtocol2 {} // ok, module-qualified. +extension Library.Sample6: Library.SampleProtocol2 {} // ok, both types are module-qualified. +extension Sample6a: Library.SampleProtocol2 {} // ok, protocol is module qualified. +extension Library.Sample6b: SampleProtocol2 {} // expected-warning {{extension declares a conformance of imported type 'Sample6b' to imported protocol 'SampleProtocol2'; this will not behave correctly if the owners of 'Library' introduce this conformance in the future}} +// expected-note @-1 {{add '@retroactive' to silence this warning}} +extension Sample6c: Library.SampleProtocol1a {} // ok, protocol is module qualified. protocol ClientProtocol {} @@ -125,10 +134,20 @@ extension Sample7: SampleProtocol1 & SampleProtocol2 {} extension Sample8: @retroactive SampleProtocol1 & SampleProtocol2 {} // ok +// FIXME: Module qualification should suppress this warning +extension Sample8a: Library.SampleProtocol1 & Library.SampleProtocol2 {} // ok +// expected-warning@-1 {{extension declares a conformance of imported type 'Sample8a' to imported protocols 'SampleProtocol1', 'SampleProtocol2'; this will not behave correctly if the owners of 'Library' introduce this conformance in the future}} +// expected-note@-2 {{add '@retroactive' to silence this warning}} + extension Sample9: SampleProtocol3 {} // expected-warning@-1 {{extension declares a conformance of imported type 'Sample9' to imported protocol 'SampleProtocol3'; this will not behave correctly if the owners of 'Library' introduce this conformance in the future}} // expected-note@-2 {{add '@retroactive' to silence this warning}} -extension Sample10: @retroactive SampleProtocol3 {} +extension Sample9a: @retroactive SampleProtocol3 {} -#endif \ No newline at end of file +// FIXME: Module qualification should suppress this warning +extension Sample9b: Library.SampleProtocol3 {} +// expected-warning@-1 {{extension declares a conformance of imported type 'Sample9b' to imported protocol 'SampleProtocol3'; this will not behave correctly if the owners of 'Library' introduce this conformance in the future}} +// expected-note@-2 {{add '@retroactive' to silence this warning}} + +#endif