Skip to content

Commit f16b5c8

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 f16b5c8

Some content is hidden

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

47 files changed

+511
-66
lines changed

include/swift/AST/Attr.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ TYPE_ATTR(differentiable)
5555
TYPE_ATTR(noDerivative)
5656
TYPE_ATTR(async)
5757
TYPE_ATTR(Sendable)
58+
TYPE_ATTR(retroactive)
5859
TYPE_ATTR(unchecked)
5960
TYPE_ATTR(_local)
6061
TYPE_ATTR(_noMetadata)

include/swift/AST/CASTBridging.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ typedef enum ENUM_EXTENSIBILITY_ATTR(closed) BridgedTypeAttrKind : size_t {
154154
BridgedTypeAttrKind_noDerivative,
155155
BridgedTypeAttrKind_async,
156156
BridgedTypeAttrKind_Sendable,
157+
BridgedTypeAttrKind_retroactive,
157158
BridgedTypeAttrKind_unchecked,
158159
BridgedTypeAttrKind__local,
159160
BridgedTypeAttrKind__noMetadata,

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

include/swift/Basic/Features.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ LANGUAGE_FEATURE(MoveOnly, 390, "noncopyable types", true)
110110
LANGUAGE_FEATURE(ParameterPacks, 393, "Value and type parameter packs", true)
111111
SUPPRESSIBLE_LANGUAGE_FEATURE(LexicalLifetimes, 0, "@_eagerMove/@_noEagerMove/@_lexicalLifetimes annotations", true)
112112
LANGUAGE_FEATURE(FreestandingMacros, 397, "freestanding declaration macros", true)
113+
SUPPRESSIBLE_LANGUAGE_FEATURE(RetroactiveAttribute, 364, "@retroactive", true)
113114

114115
UPCOMING_FEATURE(ConciseMagicFile, 274, 6)
115116
UPCOMING_FEATURE(ForwardTrailingClosures, 286, 6)

lib/AST/ASTPrinter.cpp

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,7 @@ PrintOptions PrintOptions::printSwiftInterfaceFile(ModuleDecl *ModuleToPrint,
342342
DAK_ObjCImplementation,
343343
DAK_StaticInitializeObjCMetadata,
344344
DAK_RestatedObjCConformance,
345-
DAK_NonSendable,
345+
DAK_NonSendable
346346
};
347347

348348
return result;
@@ -2579,6 +2579,9 @@ void PrintAST::printInherited(const Decl *decl) {
25792579
interleave(TypesToPrint, [&](InheritedEntry inherited) {
25802580
if (inherited.isUnchecked)
25812581
Printer << "@unchecked ";
2582+
if (inherited.isRetroactive &&
2583+
!llvm::is_contained(Options.ExcludeAttrList, TAK_retroactive))
2584+
Printer << "@retroactive ";
25822585

25832586
printTypeLoc(inherited);
25842587
}, [&]() {
@@ -3049,6 +3052,18 @@ static bool usesFeatureGlobalActors(Decl *decl) {
30493052
return false;
30503053
}
30513054

3055+
static bool usesFeatureRetroactiveAttribute(Decl *decl) {
3056+
auto ext = dyn_cast<ExtensionDecl>(decl);
3057+
if (!ext)
3058+
return false;
3059+
3060+
ArrayRef<InheritedEntry> entries = ext->getInherited().getEntries();
3061+
return std::find_if(entries.begin(), entries.end(),
3062+
[](const InheritedEntry &entry) {
3063+
return entry.isRetroactive;
3064+
}) != entries.end();
3065+
}
3066+
30523067
static bool usesBuiltinType(Decl *decl, BuiltinTypeKind kind) {
30533068
auto typeMatches = [kind](Type type) {
30543069
return type.findIf([&](Type type) {
@@ -3475,6 +3490,14 @@ suppressingFeatureNoAsyncAvailability(PrintOptions &options,
34753490
action();
34763491
}
34773492

3493+
static void suppressingFeatureRetroactiveAttribute(
3494+
PrintOptions &options,
3495+
llvm::function_ref<void()> action) {
3496+
llvm::SaveAndRestore<PrintOptions> originalOptions(options);
3497+
options.ExcludeAttrList.push_back(TAK_retroactive);
3498+
action();
3499+
}
3500+
34783501
static bool usesFeatureReferenceBindings(Decl *decl) {
34793502
auto *vd = dyn_cast<VarDecl>(decl);
34803503
return vd && vd->getIntroducer() == VarDecl::Introducer::InOut;
@@ -7871,7 +7894,8 @@ swift::getInheritedForPrinting(
78717894
}
78727895

78737896
Results.push_back({TypeLoc::withoutLoc(proto->getDeclaredInterfaceType()),
7874-
isUnchecked});
7897+
isUnchecked,
7898+
/*isRetroactive=*/false});
78757899
}
78767900
}
78777901

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/ASTGen/Sources/ASTGen/Types.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -246,10 +246,10 @@ extension ASTGenVisitor {
246246
fallthrough
247247

248248
case .autoclosure, .escaping, .noescape, .noDerivative, .async,
249-
.sendable, .unchecked, ._local, ._noMetadata, .pack_owned,
250-
.pack_guaranteed, .pack_inout, .pack_out, .pseudogeneric,
251-
.yields, .yield_once, .yield_many, .thin, .thick, .count,
252-
.unimplementable:
249+
.sendable, .retroactive, .unchecked, ._local, ._noMetadata,
250+
.pack_owned, .pack_guaranteed, .pack_inout, .pack_out,
251+
.pseudogeneric, .yields, .yield_once, .yield_many, .thin, .thick,
252+
.count, .unimplementable:
253253
TypeAttributes_addSimpleAttr(typeAttributes, typeAttrKind, atLoc, attrLoc)
254254

255255
case .opened, .pack_element, .differentiable, .convention,

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)