Skip to content

Commit 0532d8c

Browse files
harlanhaskinsHarlan Haskins
authored and
Harlan Haskins
committed
Warn when a resilient extension declares a retroactive conformance
It's not particularly _great_ when executable targets declare these, but it's certainly not okay when a library that's meant to be distributed declares these. When library evolution is enabled, warn about retroactive conformances. To silence the warning, add the @retroactive attribute to the extended type, similarly to the @unchecked attribute for Sendable. It is an error to use @retroactive for anything but a retroactive conformance declaration in an extension. Fixes rdar://74543558
1 parent 517c0de commit 0532d8c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+459
-58
lines changed

include/swift/AST/Decl.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1563,10 +1563,14 @@ struct InheritedEntry : public TypeLoc {
15631563
/// Whether there was an @unchecked attribute.
15641564
bool isUnchecked = false;
15651565

1566+
/// Whether there was an @retroactive attribute.
1567+
bool isRetroactive = false;
1568+
15661569
InheritedEntry(const TypeLoc &typeLoc);
15671570

1568-
InheritedEntry(const TypeLoc &typeLoc, bool isUnchecked)
1569-
: TypeLoc(typeLoc), isUnchecked(isUnchecked) { }
1571+
InheritedEntry(const TypeLoc &typeLoc, bool isUnchecked, bool isRetroactive)
1572+
: TypeLoc(typeLoc), isUnchecked(isUnchecked), isRetroactive(isRetroactive) {
1573+
}
15701574
};
15711575

15721576
/// A wrapper for the collection of inherited types for either a `TypeDecl` or

include/swift/AST/DiagnosticsSema.def

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2444,6 +2444,23 @@ NOTE(invalid_extension_rewrite,none,
24442444
ERROR(synthesized_nominal_extension,none,
24452445
"cannot extend synthesized type %0", (Type))
24462446

2447+
ERROR(retroactive_not_in_extension_inheritance_clause,none,
2448+
"'retroactive' attribute only applies in inheritance clauses in "
2449+
"extensions", ())
2450+
2451+
ERROR(retroactive_attr_does_not_apply,none,
2452+
"'retroactive' attribute does not apply; %0 is declared in this module",
2453+
(Identifier))
2454+
2455+
WARNING(extension_retroactive_conformance,none,
2456+
"extension declares a conformance of imported type %0 to imported "
2457+
"%select{protocols|protocol}1 %2; this will not behave correctly if "
2458+
"the owners of %3 introduce this conformance in the future",
2459+
(Identifier, bool, StringRef, Identifier))
2460+
2461+
NOTE(extension_retroactive_conformance_silence,none,
2462+
"add '@retroactive' to silence this warning", ())
2463+
24472464
// Protocols
24482465
ERROR(type_does_not_conform,none,
24492466
"type %0 does not conform to protocol %1", (Type, Type))

include/swift/AST/Module.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,13 @@ class ModuleDecl
540540
/// module if one exists.
541541
ModuleDecl *getUnderlyingModuleIfOverlay() const;
542542

543+
/// Returns true if this module is the Clang overlay of \p other.
544+
bool isClangOverlayOf(ModuleDecl *other);
545+
546+
/// Returns true if this module is the same module or either module is a clang
547+
/// overlay of the other.
548+
bool isSameModuleLookingThroughOverlays(ModuleDecl *other);
549+
543550
/// Returns true if this module is an underscored cross import overlay
544551
/// declared by \p other or its underlying clang module, either directly or
545552
/// transitively (via intermediate cross-import overlays - for cross-imports

include/swift/AST/ProtocolConformance.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,11 @@ class alignas(1 << DeclAlignInBits) ProtocolConformance
285285
/// is either the default definition or was otherwise deduced.
286286
bool usesDefaultDefinition(AssociatedTypeDecl *requirement) const;
287287

288+
/// Determines whether this conformance is retroactive; that is, if the
289+
/// conformance's declaration is in a different module from both the
290+
/// conforming type and the protocol.
291+
bool isRetroactive() const;
292+
288293
/// Print a parseable and human-readable description of the identifying
289294
/// information of the protocol conformance.
290295
void printName(raw_ostream &os,

include/swift/AST/TypeRepr.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,9 @@ class alignas(1 << TypeReprAlignInBits) TypeRepr
147147
SourceLoc getEndLoc() const;
148148
SourceRange getSourceRange() const;
149149

150-
/// Find an @unchecked attribute and return its source location, or return
151-
/// an invalid source location if there is no such attribute.
152-
SourceLoc findUncheckedAttrLoc() const;
150+
/// Find an attribute with the provided kind and return its source location,
151+
/// or return an invalid source location if there is no such attribute.
152+
SourceLoc findAttrLoc(TypeAttrKind kind) const;
153153

154154
/// Is this type grammatically a type-simple?
155155
inline bool isSimple() const; // bottom of this file

lib/AST/ASTPrinter.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,7 @@ PrintOptions PrintOptions::printSwiftInterfaceFile(ModuleDecl *ModuleToPrint,
343343
DAK_StaticInitializeObjCMetadata,
344344
DAK_RestatedObjCConformance,
345345
DAK_NonSendable,
346+
TAK_retroactive
346347
};
347348

348349
return result;
@@ -2579,6 +2580,9 @@ void PrintAST::printInherited(const Decl *decl) {
25792580
interleave(TypesToPrint, [&](InheritedEntry inherited) {
25802581
if (inherited.isUnchecked)
25812582
Printer << "@unchecked ";
2583+
if (inherited.isRetroactive &&
2584+
!llvm::is_contained(Options.ExcludeAttrList, TAK_retroactive))
2585+
Printer << "@retroactive ";
25822586

25832587
printTypeLoc(inherited);
25842588
}, [&]() {
@@ -7871,7 +7875,8 @@ swift::getInheritedForPrinting(
78717875
}
78727876

78737877
Results.push_back({TypeLoc::withoutLoc(proto->getDeclaredInterfaceType()),
7874-
isUnchecked});
7878+
isUnchecked,
7879+
/*isRetroactive=*/false});
78757880
}
78767881
}
78777882

lib/AST/Decl.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1544,8 +1544,10 @@ NominalTypeDecl::takeConformanceLoaderSlow() {
15441544

15451545
InheritedEntry::InheritedEntry(const TypeLoc &typeLoc)
15461546
: TypeLoc(typeLoc), isUnchecked(false) {
1547-
if (auto typeRepr = typeLoc.getTypeRepr())
1548-
isUnchecked = typeRepr->findUncheckedAttrLoc().isValid();
1547+
if (auto typeRepr = typeLoc.getTypeRepr()) {
1548+
isUnchecked = typeRepr->findAttrLoc(TAK_unchecked).isValid();
1549+
isRetroactive = typeRepr->findAttrLoc(TAK_retroactive).isValid();
1550+
}
15491551
}
15501552

15511553
InheritedTypes::InheritedTypes(

lib/AST/Module.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2909,6 +2909,34 @@ ModuleDecl::getDeclaringModuleAndBystander() {
29092909
return *(declaringModuleAndBystander = {nullptr, Identifier()});
29102910
}
29112911

2912+
bool ModuleDecl::isClangOverlayOf(ModuleDecl *potentialUnderlying) {
2913+
return getUnderlyingModuleIfOverlay() == potentialUnderlying;
2914+
}
2915+
2916+
bool ModuleDecl::isSameModuleLookingThroughOverlays(
2917+
ModuleDecl *other) {
2918+
if (this == other) {
2919+
return true;
2920+
}
2921+
2922+
if (this->isClangOverlayOf(other) || other->isClangOverlayOf(this)) {
2923+
return true;
2924+
}
2925+
2926+
// If the type has the @_originallyDefinedIn attribute, check if this is the
2927+
// "originally defined in" module.
2928+
2929+
if (getName().is(other->getAlternateModuleName())) {
2930+
return true;
2931+
}
2932+
2933+
if (other->getName().is(getAlternateModuleName())) {
2934+
return true;
2935+
}
2936+
2937+
return false;
2938+
}
2939+
29122940
bool ModuleDecl::isCrossImportOverlayOf(ModuleDecl *other) {
29132941
ModuleDecl *current = this;
29142942
ModuleDecl *otherClang = other->getUnderlyingModuleIfOverlay();

lib/AST/NameLookup.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3664,7 +3664,7 @@ void swift::getDirectlyInheritedNominalTypeDecls(
36643664
auto inheritedTypes = InheritedTypes(decl);
36653665
if (TypeRepr *typeRepr = inheritedTypes.getTypeRepr(i)) {
36663666
loc = typeRepr->getLoc();
3667-
uncheckedLoc = typeRepr->findUncheckedAttrLoc();
3667+
uncheckedLoc = typeRepr->findAttrLoc(TAK_unchecked);
36683668
}
36693669

36703670
// Form the result.

lib/AST/ProtocolConformance.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,26 @@ usesDefaultDefinition(AssociatedTypeDecl *requirement) const {
178178
CONFORMANCE_SUBCLASS_DISPATCH(usesDefaultDefinition, (requirement))
179179
}
180180

181+
bool ProtocolConformance::isRetroactive() const {
182+
auto extensionModule = getDeclContext()->getParentModule();
183+
auto protocolModule = getProtocol()->getParentModule();
184+
if (extensionModule->isSameModuleLookingThroughOverlays(protocolModule)) {
185+
return false;
186+
}
187+
188+
auto conformingTypeDecl =
189+
ConformingType->getNominalOrBoundGenericNominal();
190+
if (conformingTypeDecl) {
191+
auto conformingTypeModule = conformingTypeDecl->getParentModule();
192+
if (extensionModule->
193+
isSameModuleLookingThroughOverlays(conformingTypeModule)) {
194+
return false;
195+
}
196+
}
197+
198+
return true;
199+
}
200+
181201
GenericEnvironment *ProtocolConformance::getGenericEnvironment() const {
182202
switch (getKind()) {
183203
case ProtocolConformanceKind::Inherited:

lib/AST/TypeRepr.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,11 +119,11 @@ TypeRepr *TypeRepr::getWithoutParens() const {
119119
return repr;
120120
}
121121

122-
SourceLoc TypeRepr::findUncheckedAttrLoc() const {
122+
SourceLoc TypeRepr::findAttrLoc(TypeAttrKind kind) const {
123123
auto typeRepr = this;
124124
while (auto attrTypeRepr = dyn_cast<AttributedTypeRepr>(typeRepr)) {
125-
if (attrTypeRepr->getAttrs().has(TAK_unchecked)) {
126-
return attrTypeRepr->getAttrs().getLoc(TAK_unchecked);
125+
if (attrTypeRepr->getAttrs().has(kind)) {
126+
return attrTypeRepr->getAttrs().getLoc(kind);
127127
}
128128

129129
typeRepr = attrTypeRepr->getTypeRepr();

lib/ClangImporter/ImportDecl.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7094,7 +7094,7 @@ void SwiftDeclConverter::importObjCProtocols(
70947094
inheritedTypes.push_back(
70957095
InheritedEntry(
70967096
TypeLoc::withoutLoc(proto->getDeclaredInterfaceType()),
7097-
/*isUnchecked=*/false));
7097+
/*isUnchecked=*/false, /*isRetroactive=*/false));
70987098
}
70997099
}
71007100

lib/Frontend/ModuleInterfaceSupport.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -735,7 +735,8 @@ class InheritedProtocolCollector {
735735
// Create a synthesized ExtensionDecl for the conformance.
736736
ASTContext &ctx = M->getASTContext();
737737
auto inherits = ctx.AllocateCopy(llvm::makeArrayRef(InheritedEntry(
738-
TypeLoc::withoutLoc(proto->getDeclaredInterfaceType()), isUnchecked)));
738+
TypeLoc::withoutLoc(proto->getDeclaredInterfaceType()), isUnchecked,
739+
/*isRetroactive=*/false)));
739740
auto extension =
740741
ExtensionDecl::create(ctx, SourceLoc(), nullptr, inherits,
741742
nominal->getModuleScopeContext(), nullptr);

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5145,7 +5145,7 @@ ProtocolConformance *GetImplicitSendableRequest::evaluate(
51455145
// FIXME: This is a hack--we should give conformances real availability.
51465146
auto inherits = ctx.AllocateCopy(makeArrayRef(
51475147
InheritedEntry(TypeLoc::withoutLoc(proto->getDeclaredInterfaceType()),
5148-
/*isUnchecked*/true)));
5148+
/*isUnchecked*/true, /*isRetroactive=*/false)));
51495149
// If you change the use of AtLoc in the ExtensionDecl, make sure you
51505150
// update isNonSendableExtension() in ASTPrinter.
51515151
auto extension = ExtensionDecl::create(ctx, attrMakingUnavailable->AtLoc,

0 commit comments

Comments
 (0)