Skip to content

[ParseableInterface] Print conformances inherited via private protos #20169

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 1 commit into from
Nov 8, 2018
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
16 changes: 11 additions & 5 deletions lib/AST/ASTPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4418,12 +4418,18 @@ swift::getInheritedForPrinting(const Decl *decl,
inherited = ed->getInherited();
}

// Collect explicit inheritted types.
// Collect explicit inherited types.
for (auto TL: inherited) {
if (auto Ty = TL.getType()) {
if (auto NTD = Ty->getAnyNominal())
if (!shouldPrint(NTD))
continue;
if (auto ty = TL.getType()) {
bool foundUnprintable = ty.findIf([shouldPrint](Type subTy) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you factor this out? Especially if it's gonna be reused in #20146.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not exactly the same query, though: the shouldPrint here is an argument to the function the way ASTPrinter is currently written. I suppose Type itself could have an overload of findIf that passes GenericTypeDecls.

if (auto aliasTy = dyn_cast<NameAliasType>(subTy.getPointer()))
return !shouldPrint(aliasTy->getDecl());
if (auto NTD = subTy->getAnyNominal())
return !shouldPrint(NTD);
return false;
});
if (foundUnprintable)
continue;
}
Results.push_back(TL);
}
Expand Down
165 changes: 165 additions & 0 deletions lib/Frontend/ParseableInterfaceSupport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "swift/AST/ASTContext.h"
#include "swift/AST/Decl.h"
#include "swift/AST/DiagnosticsFrontend.h"
#include "swift/AST/ExistentialLayout.h"
#include "swift/AST/FileSystem.h"
#include "swift/AST/Module.h"
#include "swift/Frontend/Frontend.h"
Expand Down Expand Up @@ -400,6 +401,157 @@ static void printImports(raw_ostream &out, ModuleDecl *M) {
}
}

// FIXME: Copied from ASTPrinter.cpp...
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Harlan's moving these into a header file anyway in another PR. Once that lands, I'll move PrintOptions::printParseableInterfaceFile up to Frontend, and then we'll only need one copy of these helpers.

static bool isPublicOrUsableFromInline(const ValueDecl *VD) {
AccessScope scope =
VD->getFormalAccessScope(/*useDC*/nullptr,
/*treatUsableFromInlineAsPublic*/true);
return scope.isPublic();
}

static bool isPublicOrUsableFromInline(Type ty) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah! Okay, yes, this copy should probably make its way into the header introduced in #20004 (or whichever PR I land that contains just the cleanup)

// Note the double negative here: we're looking for any referenced decls that
// are *not* public-or-usableFromInline.
return !ty.findIf([](Type typePart) -> bool {
// FIXME: If we have an internal typealias for a non-internal type, we ought
// to be able to print it by desugaring.
if (auto *aliasTy = dyn_cast<NameAliasType>(typePart.getPointer()))
return !isPublicOrUsableFromInline(aliasTy->getDecl());
if (auto *nominal = typePart->getAnyNominal())
return !isPublicOrUsableFromInline(nominal);
return false;
});
}

namespace {
/// Collects protocols that are conformed to by a particular nominal. Since
/// ASTPrinter will only print the public ones, the non-public ones get left by
/// the wayside. This is a problem when a non-public protocol inherits from a
/// public protocol; the generated parseable interface still needs to make that
/// dependency public.
///
/// The solution implemented here is to generate synthetic extensions that
/// declare the extra conformances. This isn't perfect (it loses the sugared
/// spelling of the protocol type, as well as the locality in the file), but it
/// does work.
class InheritedProtocolCollector {
/// 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;

/// 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) {
for (TypeLoc inherited : directlyInherited) {
Type inheritedTy = inherited.getType();
if (!inheritedTy || !inheritedTy->isExistentialType())
continue;

bool canPrintNormally = isPublicOrUsableFromInline(inheritedTy);
SmallVectorImpl<ProtocolDecl *> &whichProtocols =
canPrintNormally ? IncludedProtocols : ExtraProtocols;

ExistentialLayout layout = inheritedTy->getExistentialLayout();
for (ProtocolType *protoTy : layout.getProtocols())
whichProtocols.push_back(protoTy->getDecl());
// FIXME: This ignores layout constraints, but currently we don't support
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're ignoring superclass constraints too.

// any of those besides 'AnyObject'.
}
}

public:
using PerTypeMap = llvm::MapVector<const NominalTypeDecl *,
InheritedProtocolCollector>;

/// Given that we're about to print \p D, record its protocols in \p map.
///
/// \sa recordProtocols
static void collectProtocols(PerTypeMap &map, const Decl *D) {
ArrayRef<TypeLoc> directlyInherited;
const NominalTypeDecl *nominal;
const IterableDeclContext *memberContext;

if ((nominal = dyn_cast<NominalTypeDecl>(D))) {
directlyInherited = nominal->getInherited();
memberContext = nominal;

} else if (auto *extension = dyn_cast<ExtensionDecl>(D)) {
if (extension->isConstrainedExtension()) {
// Conditional conformances never apply to inherited protocols, nor
// can they provide unconditional conformances that might be used in
// other extensions.
return;
}
nominal = extension->getExtendedNominal();
directlyInherited = extension->getInherited();
memberContext = extension;

} else {
return;
}

map[nominal].recordProtocols(directlyInherited);

// Recurse to find any nested types.
for (const Decl *member : memberContext->getMembers())
collectProtocols(map, member);
}

/// 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.
void
printSynthesizedExtensionIfNeeded(raw_ostream &out,
const PrintOptions &printOptions,
const NominalTypeDecl *nominal) const {
if (ExtraProtocols.empty())
return;

SmallPtrSet<ProtocolDecl *, 16> handledProtocols;

// First record all protocols that have already been handled.
for (ProtocolDecl *proto : IncludedProtocols) {
proto->walkInheritedProtocols(
[&handledProtocols](ProtocolDecl *inherited) -> TypeWalker::Action {
handledProtocols.insert(inherited);
return TypeWalker::Action::Continue;
});
}

// Then walk the remaining ones, and see what we need to print.
// 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(
[&](ProtocolDecl *inherited) -> TypeWalker::Action {
if (!handledProtocols.insert(inherited).second)
return TypeWalker::Action::SkipChildren;
if (isPublicOrUsableFromInline(inherited)) {
protocolsToPrint.push_back(inherited);
return TypeWalker::Action::SkipChildren;
}
return TypeWalker::Action::Continue;
});
}
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";
}
};
} // end anonymous namespace

bool swift::emitParseableInterface(raw_ostream &out,
ParseableInterfaceOptions const &Opts,
ModuleDecl *M) {
Expand All @@ -409,13 +561,26 @@ bool swift::emitParseableInterface(raw_ostream &out,
printImports(out, M);

const PrintOptions printOptions = PrintOptions::printParseableInterfaceFile();
InheritedProtocolCollector::PerTypeMap inheritedProtocolMap;

SmallVector<Decl *, 16> topLevelDecls;
M->getTopLevelDecls(topLevelDecls);
for (const Decl *D : topLevelDecls) {
if (!D->shouldPrintInContext(printOptions))
continue;

D->print(out, printOptions);
out << "\n";

InheritedProtocolCollector::collectProtocols(inheritedProtocolMap, D);
}

// Print dummy extensions for any protocols that were indirectly conformed to.
for (const auto &nominalAndCollector : inheritedProtocolMap) {
const InheritedProtocolCollector &collector = nominalAndCollector.second;
collector.printSynthesizedExtensionIfNeeded(out, printOptions,
nominalAndCollector.first);
}

return false;
}
133 changes: 131 additions & 2 deletions test/ParseableInterface/conformances.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// RUN: %target-swift-frontend-typecheck -emit-parseable-module-interface-path %t.swiftinterface %s
// RUN: %FileCheck %s < %t.swiftinterface
// RUN: %FileCheck -check-prefix CHECK-END %s < %t.swiftinterface
// RUN: %FileCheck -check-prefix NEGATIVE %s < %t.swiftinterface

// CHECK-LABEL: public protocol SimpleProto {
Expand All @@ -11,10 +12,138 @@ public protocol SimpleProto {
func inference(_: Inferred)
} // CHECK: {{^}$}}

// CHECK-LABEL: public struct SimplImpl<Element> : SimpleProto {
public struct SimplImpl<Element>: SimpleProto {
// CHECK-LABEL: public struct SimpleImpl<Element> : SimpleProto {
public struct SimpleImpl<Element>: SimpleProto {
// NEGATIVE-NOT: typealias Element =
// CHECK: public func inference(_: Int){{$}}
public func inference(_: Int) {}
// CHECK: public typealias Inferred = Swift.Int
} // CHECK: {{^}$}}


public protocol PublicProto {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems the CHECKs are missing here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

…right. I was manually inspecting the output but didn't actually write down the CHECK lines I wanted.

private protocol PrivateProto {}

// CHECK: public struct A1 : PublicProto {
// NEGATIVE-NOT: extension conformances.A1
public struct A1: PublicProto, PrivateProto {}
// CHECK: public struct A2 : PublicProto {
// NEGATIVE-NOT: extension conformances.A2
public struct A2: PrivateProto, PublicProto {}
// CHECK: public struct A3 {
// CHECK-END: extension conformances.A3 : PublicProto {}
public struct A3: PublicProto & PrivateProto {}
// CHECK: public struct A4 {
// CHECK-END: extension conformances.A4 : PublicProto {}
public struct A4: PrivateProto & PublicProto {}

public protocol PublicBaseProto {}
private protocol PrivateSubProto: PublicBaseProto {}

// CHECK: public struct B1 {
// CHECK-END: extension conformances.B1 : PublicBaseProto {}
public struct B1: PrivateSubProto {}
// CHECK: public struct B2 : PublicBaseProto {
// NEGATIVE-NOT: extension conformances.B2
public struct B2: PublicBaseProto, PrivateSubProto {}
// CHECK: public struct B3 {
// CHECK-END: extension conformances.B3 : PublicBaseProto {}
public struct B3: PublicBaseProto & PrivateSubProto {}
// CHECK: public struct B4 : PublicBaseProto {
// CHECK: extension B4 {
// NEGATIVE-NOT: extension conformances.B4
public struct B4: PublicBaseProto {}
extension B4: PrivateSubProto {}
// CHECK: public struct B5 {
// CHECK: extension B5 : PublicBaseProto {
// NEGATIVE-NOT: extension conformances.B5
public struct B5: PrivateSubProto {}
extension B5: PublicBaseProto {}
// CHECK: public struct B6 {
// CHECK: extension B6 {
// CHECK: extension B6 : PublicBaseProto {
// NEGATIVE-NOT: extension conformances.B6
public struct B6 {}
extension B6: PrivateSubProto {}
extension B6: PublicBaseProto {}
// CHECK: public struct B7 {
// CHECK: extension B7 : PublicBaseProto {
// CHECK: extension B7 {
// NEGATIVE-NOT: extension conformances.B7
public struct B7 {}
extension B7: PublicBaseProto {}
extension B7: PrivateSubProto {}

// CHECK-LABEL: public struct OuterGeneric<T> {
public struct OuterGeneric<T> {
// CHECK-NEXT: public struct Inner {
public struct Inner: PrivateSubProto {}
// CHECK-NEXT: {{^ }$}}
}
// CHECK-NEXT: {{^}$}}
// CHECK-END: extension conformances.OuterGeneric.Inner : PublicBaseProto {}

private protocol AnotherPrivateSubProto: PublicBaseProto {}

// CHECK: public struct C1 {
// CHECK-END: extension conformances.C1 : PublicBaseProto {}
public struct C1: PrivateSubProto, AnotherPrivateSubProto {}
// CHECK: public struct C2 {
// CHECK-END: extension conformances.C2 : PublicBaseProto {}
public struct C2: PrivateSubProto & AnotherPrivateSubProto {}
// CHECK: public struct C3 {
// CHECK: extension C3 {
// CHECK-END: extension conformances.C3 : PublicBaseProto {}
public struct C3: PrivateSubProto {}
extension C3: AnotherPrivateSubProto {}

public protocol PublicSubProto: PublicBaseProto {}
public protocol APublicSubProto: PublicBaseProto {}

// CHECK: public struct D1 : PublicSubProto {
// NEGATIVE-NOT: extension conformances.D1
public struct D1: PublicSubProto, PrivateSubProto {}
// CHECK: public struct D2 : PublicSubProto {
// NEGATIVE-NOT: extension conformances.D2
public struct D2: PrivateSubProto, PublicSubProto {}
// CHECK: public struct D3 {
// CHECK-END: extension conformances.D3 : PublicBaseProto, PublicSubProto {}
public struct D3: PrivateSubProto & PublicSubProto {}
// CHECK: public struct D4 {
// CHECK-END: extension conformances.D4 : APublicSubProto, PublicBaseProto {}
public struct D4: APublicSubProto & PrivateSubProto {}
// CHECK: public struct D5 {
// CHECK: extension D5 : PublicSubProto {
// NEGATIVE-NOT: extension conformances.D5
public struct D5: PrivateSubProto {}
extension D5: PublicSubProto {}
// CHECK: public struct D6 : PublicSubProto {
// CHECK: extension D6 {
// NEGATIVE-NOT: extension conformances.D6
public struct D6: PublicSubProto {}
extension D6: PrivateSubProto {}

private typealias PrivateProtoAlias = PublicProto

// CHECK: public struct E1 {
// CHECK-END: extension conformances.E1 : PublicProto {}
public struct E1: PrivateProtoAlias {}

private typealias PrivateSubProtoAlias = PrivateSubProto

// CHECK: public struct F1 {
// CHECK-END: extension conformances.F1 : PublicBaseProto {}
public struct F1: PrivateSubProtoAlias {}

private protocol ClassConstrainedProto: PublicProto, AnyObject {}

public class G1: ClassConstrainedProto {}
// CHECK: public class G1 {
// CHECK-END: extension conformances.G1 : PublicProto {}

public class Base {}
private protocol BaseConstrainedProto: Base, PublicProto {}

public class H1: Base, ClassConstrainedProto {}
// CHECK: public class H1 : Base {
// CHECK-END: extension conformances.H1 : PublicProto {}