-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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" | ||
|
@@ -400,6 +401,157 @@ static void printImports(raw_ostream &out, ModuleDecl *M) { | |
} | ||
} | ||
|
||
// FIXME: Copied from ASTPrinter.cpp... | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) { | ||
|
@@ -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; | ||
} |
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 { | ||
|
@@ -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 {} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seems the There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 {} |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 offindIf
that passes GenericTypeDecls.