From e43a9f5475fc7ed3efbcf552e66b7f4c3a6c38d3 Mon Sep 17 00:00:00 2001 From: Jordan Rose Date: Mon, 13 May 2019 19:55:00 -0700 Subject: [PATCH] [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. --- lib/Frontend/ParseableInterfaceSupport.cpp | 90 +++++++++++++++------- test/ParseableInterface/conformances.swift | 36 +++++++-- 2 files changed, 92 insertions(+), 34 deletions(-) diff --git a/lib/Frontend/ParseableInterfaceSupport.cpp b/lib/Frontend/ParseableInterfaceSupport.cpp index e89132ad805..e5030e65d48 100644 --- a/lib/Frontend/ParseableInterfaceSupport.cpp +++ b/lib/Frontend/ParseableInterfaceSupport.cpp @@ -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; + using ProtocolAndAvailability = + std::pair; + /// Protocols that will be included by the ASTPrinter without any extra work. SmallVector IncludedProtocols; - /// Protocols that will not be printed by the ASTPrinter. - SmallVector ExtraProtocols; + /// Protocols that will not be printed by the ASTPrinter, along with the + /// availability they were declared with. + SmallVector ExtraProtocols; /// Protocols that can be printed, but whose conformances are constrained with /// something that \e can't be printed. SmallVector ConditionalConformanceProtocols; + /// Helper to extract the `@available` attributes on a decl. + AvailableAttrList getAvailabilityAttrs(const Decl *D) { + AvailableAttrList result; + auto attrRange = D->getAttrs().getAttributes(); + 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 directlyInherited) { + void recordProtocols(ArrayRef directlyInherited, const Decl *D) { + Optional availableAttrs; + for (TypeLoc inherited : directlyInherited) { Type inheritedTy = inherited.getType(); if (!inheritedTy || !inheritedTy->isExistentialType()) continue; bool canPrintNormally = isPublicOrUsableFromInline(inheritedTy); - SmallVectorImpl &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 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 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, 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, diff --git a/test/ParseableInterface/conformances.swift b/test/ParseableInterface/conformances.swift index f006bf9fbc7..00374dc0079 100644 --- a/test/ParseableInterface/conformances.swift +++ b/test/ParseableInterface/conformances.swift @@ -87,12 +87,14 @@ public struct OuterGeneric { 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 {} extension MultiGeneric: PublicProto where U: PrivateProto {} // CHECK: public struct MultiGeneric { -// 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