mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
[ModuleInterface] Propagate availability for synthesized extensions (#24753)
Otherwise, we can synthesize an extension that's extending a type that's unavailable on a particular platform, or that conforms to a protocol that hasn't been introduced on the minimum deployment target.
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "swift/AST/ASTContext.h"
|
||||
#include "swift/AST/ASTPrinter.h"
|
||||
#include "swift/AST/Decl.h"
|
||||
#include "swift/AST/DiagnosticsFrontend.h"
|
||||
#include "swift/AST/DiagnosticsSema.h"
|
||||
@@ -163,47 +164,69 @@ namespace {
|
||||
class InheritedProtocolCollector {
|
||||
static const StringLiteral DummyProtocolName;
|
||||
|
||||
using AvailableAttrList = TinyPtrVector<const AvailableAttr *>;
|
||||
using ProtocolAndAvailability =
|
||||
std::pair<ProtocolDecl *, AvailableAttrList>;
|
||||
|
||||
/// Protocols that will be included by the ASTPrinter without any extra work.
|
||||
SmallVector<ProtocolDecl *, 8> IncludedProtocols;
|
||||
/// Protocols that will not be printed by the ASTPrinter.
|
||||
SmallVector<ProtocolDecl *, 8> ExtraProtocols;
|
||||
/// Protocols that will not be printed by the ASTPrinter, along with the
|
||||
/// availability they were declared with.
|
||||
SmallVector<ProtocolAndAvailability, 8> ExtraProtocols;
|
||||
/// Protocols that can be printed, but whose conformances are constrained with
|
||||
/// something that \e can't be printed.
|
||||
SmallVector<const ProtocolType *, 8> ConditionalConformanceProtocols;
|
||||
|
||||
/// Helper to extract the `@available` attributes on a decl.
|
||||
AvailableAttrList getAvailabilityAttrs(const Decl *D) {
|
||||
AvailableAttrList result;
|
||||
auto attrRange = D->getAttrs().getAttributes<AvailableAttr>();
|
||||
result.insert(result.begin(), attrRange.begin(), attrRange.end());
|
||||
return result;
|
||||
}
|
||||
|
||||
/// For each type in \p directlyInherited, classify the protocols it refers to
|
||||
/// as included for printing or not, and record them in the appropriate
|
||||
/// vectors.
|
||||
void recordProtocols(ArrayRef<TypeLoc> directlyInherited) {
|
||||
void recordProtocols(ArrayRef<TypeLoc> directlyInherited, const Decl *D) {
|
||||
Optional<AvailableAttrList> availableAttrs;
|
||||
|
||||
for (TypeLoc inherited : directlyInherited) {
|
||||
Type inheritedTy = inherited.getType();
|
||||
if (!inheritedTy || !inheritedTy->isExistentialType())
|
||||
continue;
|
||||
|
||||
bool canPrintNormally = isPublicOrUsableFromInline(inheritedTy);
|
||||
SmallVectorImpl<ProtocolDecl *> &whichProtocols =
|
||||
canPrintNormally ? IncludedProtocols : ExtraProtocols;
|
||||
if (!canPrintNormally && !availableAttrs.hasValue())
|
||||
availableAttrs = getAvailabilityAttrs(D);
|
||||
|
||||
ExistentialLayout layout = inheritedTy->getExistentialLayout();
|
||||
for (ProtocolType *protoTy : layout.getProtocols())
|
||||
whichProtocols.push_back(protoTy->getDecl());
|
||||
for (ProtocolType *protoTy : layout.getProtocols()) {
|
||||
if (canPrintNormally)
|
||||
IncludedProtocols.push_back(protoTy->getDecl());
|
||||
else
|
||||
ExtraProtocols.push_back({protoTy->getDecl(),
|
||||
availableAttrs.getValue()});
|
||||
}
|
||||
// FIXME: This ignores layout constraints, but currently we don't support
|
||||
// any of those besides 'AnyObject'.
|
||||
}
|
||||
}
|
||||
|
||||
/// For each type in \p directlyInherited, record any protocols that we would
|
||||
/// have printed in ConditionalConformanceProtocols.
|
||||
void recordConditionalConformances(ArrayRef<TypeLoc> directlyInherited) {
|
||||
for (TypeLoc inherited : directlyInherited) {
|
||||
/// For each type directly inherited by \p extension, record any protocols
|
||||
/// that we would have printed in ConditionalConformanceProtocols.
|
||||
void recordConditionalConformances(const ExtensionDecl *extension) {
|
||||
for (TypeLoc inherited : extension->getInherited()) {
|
||||
Type inheritedTy = inherited.getType();
|
||||
if (!inheritedTy || !inheritedTy->isExistentialType())
|
||||
continue;
|
||||
|
||||
ExistentialLayout layout = inheritedTy->getExistentialLayout();
|
||||
for (ProtocolType *protoTy : layout.getProtocols())
|
||||
if (isPublicOrUsableFromInline(protoTy))
|
||||
ConditionalConformanceProtocols.push_back(protoTy);
|
||||
for (ProtocolType *protoTy : layout.getProtocols()) {
|
||||
if (!isPublicOrUsableFromInline(protoTy))
|
||||
continue;
|
||||
ConditionalConformanceProtocols.push_back(protoTy);
|
||||
}
|
||||
// FIXME: This ignores layout constraints, but currently we don't support
|
||||
// any of those besides 'AnyObject'.
|
||||
}
|
||||
@@ -243,7 +266,7 @@ public:
|
||||
if (!isPublicOrUsableFromInline(nominal))
|
||||
return;
|
||||
|
||||
map[nominal].recordProtocols(directlyInherited);
|
||||
map[nominal].recordProtocols(directlyInherited, D);
|
||||
|
||||
// Recurse to find any nested types.
|
||||
for (const Decl *member : memberContext->getMembers())
|
||||
@@ -264,7 +287,7 @@ public:
|
||||
if (!isPublicOrUsableFromInline(nominal))
|
||||
return;
|
||||
|
||||
map[nominal].recordConditionalConformances(extension->getInherited());
|
||||
map[nominal].recordConditionalConformances(extension);
|
||||
// No recursion here because extensions are never nested.
|
||||
}
|
||||
|
||||
@@ -307,16 +330,19 @@ public:
|
||||
// Note: We could do this in one pass, but the logic is easier to
|
||||
// understand if we build up the list and then print it, even if it takes
|
||||
// a bit more memory.
|
||||
SmallVector<ProtocolDecl *, 16> protocolsToPrint;
|
||||
for (ProtocolDecl *proto : ExtraProtocols) {
|
||||
proto->walkInheritedProtocols(
|
||||
// FIXME: This will pick the availability attributes from the first sight
|
||||
// of a protocol rather than the maximally available case.
|
||||
SmallVector<std::pair<ProtocolDecl *, AvailableAttrList>, 16>
|
||||
protocolsToPrint;
|
||||
for (const auto &protoAndAvailability : ExtraProtocols) {
|
||||
protoAndAvailability.first->walkInheritedProtocols(
|
||||
[&](ProtocolDecl *inherited) -> TypeWalker::Action {
|
||||
if (!handledProtocols.insert(inherited).second)
|
||||
return TypeWalker::Action::SkipChildren;
|
||||
|
||||
if (isPublicOrUsableFromInline(inherited) &&
|
||||
conformanceDeclaredInModule(M, nominal, inherited)) {
|
||||
protocolsToPrint.push_back(inherited);
|
||||
protocolsToPrint.push_back({inherited, protoAndAvailability.second});
|
||||
return TypeWalker::Action::SkipChildren;
|
||||
}
|
||||
|
||||
@@ -326,14 +352,20 @@ public:
|
||||
if (protocolsToPrint.empty())
|
||||
return;
|
||||
|
||||
out << "extension ";
|
||||
nominal->getDeclaredType().print(out, printOptions);
|
||||
out << " : ";
|
||||
swift::interleave(protocolsToPrint,
|
||||
[&out, &printOptions](ProtocolDecl *proto) {
|
||||
proto->getDeclaredType()->print(out, printOptions);
|
||||
}, [&out] { out << ", "; });
|
||||
out << " {}\n";
|
||||
for (const auto &protoAndAvailability : protocolsToPrint) {
|
||||
StreamPrinter printer(out);
|
||||
for (const AvailableAttr *attr : protoAndAvailability.second)
|
||||
attr->print(printer, printOptions);
|
||||
|
||||
printer << "extension ";
|
||||
nominal->getDeclaredType().print(printer, printOptions);
|
||||
printer << " : ";
|
||||
|
||||
ProtocolDecl *proto = protoAndAvailability.first;
|
||||
proto->getDeclaredType()->print(printer, printOptions);
|
||||
|
||||
printer << " {}\n";
|
||||
}
|
||||
}
|
||||
|
||||
/// If there were any conditional conformances that couldn't be printed,
|
||||
@@ -346,7 +378,7 @@ public:
|
||||
return false;
|
||||
assert(nominal->isGenericContext());
|
||||
|
||||
out << "extension ";
|
||||
out << "@available(*, unavailable)\nextension ";
|
||||
nominal->getDeclaredType().print(out, printOptions);
|
||||
out << " : ";
|
||||
swift::interleave(ConditionalConformanceProtocols,
|
||||
|
||||
@@ -87,12 +87,14 @@ public struct OuterGeneric<T> {
|
||||
public protocol ConditionallyConformed {}
|
||||
public protocol ConditionallyConformedAgain {}
|
||||
|
||||
// CHECK-END: extension conformances.OuterGeneric : conformances.ConditionallyConformed, conformances.ConditionallyConformedAgain where T : _ConstraintThatIsNotPartOfTheAPIOfThisLibrary {}
|
||||
// CHECK-END: @available(*, unavailable)
|
||||
// CHECK-END-NEXT: extension conformances.OuterGeneric : conformances.ConditionallyConformed, conformances.ConditionallyConformedAgain where T : _ConstraintThatIsNotPartOfTheAPIOfThisLibrary {}
|
||||
extension OuterGeneric: ConditionallyConformed where T: PrivateProto {}
|
||||
extension OuterGeneric: ConditionallyConformedAgain where T == PrivateProto {}
|
||||
|
||||
// CHECK-END: extension conformances.OuterGeneric.Inner : conformances.PublicBaseProto {}
|
||||
// CHECK-END: extension conformances.OuterGeneric.Inner : conformances.ConditionallyConformed, conformances.ConditionallyConformedAgain where T : _ConstraintThatIsNotPartOfTheAPIOfThisLibrary {}
|
||||
// CHECK-END: @available(*, unavailable)
|
||||
// CHECK-END-NEXT: extension conformances.OuterGeneric.Inner : conformances.ConditionallyConformed, conformances.ConditionallyConformedAgain where T : _ConstraintThatIsNotPartOfTheAPIOfThisLibrary {}
|
||||
extension OuterGeneric.Inner: ConditionallyConformed where T: PrivateProto {}
|
||||
extension OuterGeneric.Inner: ConditionallyConformedAgain where T == PrivateProto {}
|
||||
|
||||
@@ -120,10 +122,12 @@ public struct D1: PublicSubProto, PrivateSubProto {}
|
||||
// NEGATIVE-NOT: extension conformances.D2
|
||||
public struct D2: PrivateSubProto, PublicSubProto {}
|
||||
// CHECK: public struct D3 {
|
||||
// CHECK-END: extension conformances.D3 : conformances.PublicBaseProto, conformances.PublicSubProto {}
|
||||
// CHECK-END: extension conformances.D3 : conformances.PublicBaseProto {}
|
||||
// CHECK-END: extension conformances.D3 : conformances.PublicSubProto {}
|
||||
public struct D3: PrivateSubProto & PublicSubProto {}
|
||||
// CHECK: public struct D4 {
|
||||
// CHECK-END: extension conformances.D4 : conformances.APublicSubProto, conformances.PublicBaseProto {}
|
||||
// CHECK-END: extension conformances.D4 : conformances.APublicSubProto {}
|
||||
// CHECK-END: extension conformances.D4 : conformances.PublicBaseProto {}
|
||||
public struct D4: APublicSubProto & PrivateSubProto {}
|
||||
// CHECK: public struct D5 {
|
||||
// CHECK: extension D5 : PublicSubProto {
|
||||
@@ -165,7 +169,8 @@ public struct MultiGeneric<T, U, V> {}
|
||||
extension MultiGeneric: PublicProto where U: PrivateProto {}
|
||||
|
||||
// CHECK: public struct MultiGeneric<T, U, V> {
|
||||
// CHECK-END: extension conformances.MultiGeneric : conformances.PublicProto where T : _ConstraintThatIsNotPartOfTheAPIOfThisLibrary {}
|
||||
// CHECK-END: @available(*, unavailable)
|
||||
// CHECK-END-NEXT: extension conformances.MultiGeneric : conformances.PublicProto where T : _ConstraintThatIsNotPartOfTheAPIOfThisLibrary {}
|
||||
|
||||
|
||||
internal struct InternalImpl_BAD: PrivateSubProto {}
|
||||
@@ -186,6 +191,27 @@ extension WrapperForInternal.InternalImplConstrained2_BAD: PublicProto where T:
|
||||
internal protocol ExtraHashable: Hashable {}
|
||||
extension Bool: ExtraHashable {}
|
||||
|
||||
@available(iOS, unavailable)
|
||||
@available(macOS, unavailable)
|
||||
public struct CoolTVType: PrivateSubProto {}
|
||||
// CHECK: public struct CoolTVType {
|
||||
// CHECK-END: @available(OSX, unavailable)
|
||||
// CHECK-END-NEXT: @available(iOS, unavailable)
|
||||
// CHECK-END-NEXT: extension conformances.CoolTVType : conformances.PublicBaseProto {}
|
||||
|
||||
@available(macOS 10.99, *)
|
||||
public struct VeryNewMacType: PrivateSubProto {}
|
||||
// CHECK: public struct VeryNewMacType {
|
||||
// CHECK-END: @available(OSX, introduced: 10.99)
|
||||
// CHECK-END-NEXT: extension conformances.VeryNewMacType : conformances.PublicBaseProto {}
|
||||
|
||||
public struct VeryNewMacProto {}
|
||||
@available(macOS 10.98, *)
|
||||
extension VeryNewMacProto: PrivateSubProto {}
|
||||
// CHECK: public struct VeryNewMacProto {
|
||||
// CHECK-END: @available(OSX, introduced: 10.98)
|
||||
// CHECK-END-NEXT: extension conformances.VeryNewMacProto : conformances.PublicBaseProto {}
|
||||
|
||||
// NEGATIVE-NOT: extension {{(Swift.)?}}Bool{{.+}}Hashable
|
||||
// NEGATIVE-NOT: extension {{(Swift.)?}}Bool{{.+}}Equatable
|
||||
|
||||
|
||||
Reference in New Issue
Block a user