Skip to content

[ModuleInterface] Propagate availability for synthesized extensions #24753

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 61 additions & 29 deletions lib/Frontend/ParseableInterfaceSupport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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'.
}
Expand Down Expand Up @@ -243,7 +266,7 @@ class InheritedProtocolCollector {
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())
Expand All @@ -264,7 +287,7 @@ class InheritedProtocolCollector {
if (!isPublicOrUsableFromInline(nominal))
return;

map[nominal].recordConditionalConformances(extension->getInherited());
map[nominal].recordConditionalConformances(extension);
// No recursion here because extensions are never nested.
}

Expand Down Expand Up @@ -307,16 +330,19 @@ class InheritedProtocolCollector {
// 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;
}

Expand All @@ -326,14 +352,20 @@ class InheritedProtocolCollector {
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,
Expand All @@ -346,7 +378,7 @@ class InheritedProtocolCollector {
return false;
assert(nominal->isGenericContext());

out << "extension ";
out << "@available(*, unavailable)\nextension ";
nominal->getDeclaredType().print(out, printOptions);
out << " : ";
swift::interleave(ConditionalConformanceProtocols,
Expand Down
36 changes: 31 additions & 5 deletions test/ParseableInterface/conformances.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {}
Expand All @@ -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

Expand Down