Skip to content

[ParseableInterfaces] Handle unsatisfiable conditional conformances #20433

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
87 changes: 84 additions & 3 deletions lib/Frontend/ParseableInterfaceSupport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -466,10 +466,15 @@ namespace {
/// spelling of the protocol type, as well as the locality in the file), but it
/// does work.
class InheritedProtocolCollector {
static const StringLiteral DummyProtocolName;

/// 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 can be printed, but whose conformances are constrained with
/// something that \e can't be printed.
SmallVector<const ProtocolType *, 8> ConditionalConformanceProtocols;

/// For each type in \p directlyInherited, classify the protocols it refers to
/// as included for printing or not, and record them in the appropriate
Expand All @@ -492,6 +497,23 @@ class InheritedProtocolCollector {
}
}

/// 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) {
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);
// FIXME: This ignores layout constraints, but currently we don't support
// any of those besides 'AnyObject'.
}
}

public:
using PerTypeMap = llvm::MapVector<const NominalTypeDecl *,
InheritedProtocolCollector>;
Expand Down Expand Up @@ -530,6 +552,21 @@ class InheritedProtocolCollector {
collectProtocols(map, member);
}

/// If \p D is an extension providing conditional conformances, record those
/// in \p map.
///
/// \sa recordConditionalConformances
static void collectSkippedConditionalConformances(PerTypeMap &map,
const Decl *D) {
auto *extension = dyn_cast<ExtensionDecl>(D);
if (!extension || !extension->isConstrainedExtension())
return;

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

/// If there were any public protocols that need to be printed (i.e. they
/// weren't conformed to explicitly or inherited by another printed protocol),
/// do so now by printing a dummy extension on \p nominal to \p out.
Expand Down Expand Up @@ -580,7 +617,40 @@ class InheritedProtocolCollector {
}, [&out] { out << ", "; });
out << " {}\n";
}

/// If there were any conditional conformances that couldn't be printed,
/// make a dummy extension that conforms to all of them, constrained by a
/// fake protocol.
bool printInaccessibleConformanceExtensionIfNeeded(
raw_ostream &out, const PrintOptions &printOptions,
const NominalTypeDecl *nominal) const {
if (ConditionalConformanceProtocols.empty())
return false;
assert(nominal->isGenericContext());

out << "extension ";
nominal->getDeclaredType().print(out, printOptions);
out << " : ";
swift::interleave(ConditionalConformanceProtocols,
[&out, &printOptions](const ProtocolType *protoTy) {
protoTy->print(out, printOptions);
}, [&out] { out << ", "; });
out << " where "
<< nominal->getGenericParamsOfContext()->getParams().front()->getName()
<< " : " << DummyProtocolName << " {}\n";
return true;
}

/// Print a fake protocol declaration for use by
/// #printInaccessibleConformanceExtensionIfNeeded.
static void printDummyProtocolDeclaration(raw_ostream &out) {
out << "\n@usableFromInline\ninternal protocol " << DummyProtocolName
<< " {}\n";
}
};

const StringLiteral InheritedProtocolCollector::DummyProtocolName =
"_ConstraintThatIsNotPartOfTheAPIOfThisLibrary";
} // end anonymous namespace

bool swift::emitParseableInterface(raw_ostream &out,
Expand All @@ -597,8 +667,12 @@ bool swift::emitParseableInterface(raw_ostream &out,
SmallVector<Decl *, 16> topLevelDecls;
M->getTopLevelDecls(topLevelDecls);
for (const Decl *D : topLevelDecls) {
if (!D->shouldPrintInContext(printOptions))
if (!D->shouldPrintInContext(printOptions) ||
!printOptions.CurrentPrintabilityChecker->shouldPrint(D, printOptions)){
InheritedProtocolCollector::collectSkippedConditionalConformances(
inheritedProtocolMap, D);
continue;
}

D->print(out, printOptions);
out << "\n";
Expand All @@ -607,11 +681,18 @@ bool swift::emitParseableInterface(raw_ostream &out,
}

// Print dummy extensions for any protocols that were indirectly conformed to.
bool needDummyProtocolDeclaration = false;
for (const auto &nominalAndCollector : inheritedProtocolMap) {
const NominalTypeDecl *nominal = nominalAndCollector.first;
const InheritedProtocolCollector &collector = nominalAndCollector.second;
collector.printSynthesizedExtensionIfNeeded(out, printOptions,
nominalAndCollector.first);
collector.printSynthesizedExtensionIfNeeded(out, printOptions, nominal);
needDummyProtocolDeclaration |=
collector.printInaccessibleConformanceExtensionIfNeeded(out,
printOptions,
nominal);
}
if (needDummyProtocolDeclaration)
InheritedProtocolCollector::printDummyProtocolDeclaration(out);

return false;
}
21 changes: 21 additions & 0 deletions test/ParseableInterface/conformances.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,18 @@ public struct OuterGeneric<T> {
// CHECK-NEXT: {{^ }$}}
}
// CHECK-NEXT: {{^}$}}

public protocol ConditionallyConformed {}
public protocol ConditionallyConformedAgain {}

// CHECK-END: extension conformances.OuterGeneric : ConditionallyConformed, ConditionallyConformedAgain where T : _ConstraintThatIsNotPartOfTheAPIOfThisLibrary {}
extension OuterGeneric: ConditionallyConformed where T: PrivateProto {}
extension OuterGeneric: ConditionallyConformedAgain where T == PrivateProto {}

// CHECK-END: extension conformances.OuterGeneric.Inner : PublicBaseProto {}
// CHECK-END: extension conformances.OuterGeneric.Inner : ConditionallyConformed, ConditionallyConformedAgain where T : _ConstraintThatIsNotPartOfTheAPIOfThisLibrary {}
extension OuterGeneric.Inner: ConditionallyConformed where T: PrivateProto {}
extension OuterGeneric.Inner: ConditionallyConformedAgain where T == PrivateProto {}

private protocol AnotherPrivateSubProto: PublicBaseProto {}

Expand Down Expand Up @@ -147,3 +158,13 @@ private protocol BaseConstrainedProto: Base, PublicProto {}
public class H1: Base, ClassConstrainedProto {}
// CHECK: public class H1 : Base {
// CHECK-END: extension conformances.H1 : PublicProto {}

public struct MultiGeneric<T, U, V> {}
extension MultiGeneric: PublicProto where U: PrivateProto {}

// CHECK: public struct MultiGeneric<T, U, V> {
// CHECK-END: extension conformances.MultiGeneric : PublicProto where T : _ConstraintThatIsNotPartOfTheAPIOfThisLibrary {}


// CHECK-END: @usableFromInline
// CHECK-END-NEXT: internal protocol _ConstraintThatIsNotPartOfTheAPIOfThisLibrary {}