Skip to content

ModuleInterface: Wrap synthesized extensions in swiftinterfaces with feature guards #42276

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
6 changes: 6 additions & 0 deletions include/swift/AST/PrintOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,12 @@ struct PrintOptions {
/// Whether to use an empty line to separate two members in a single decl.
bool EmptyLineBetweenMembers = false;

/// Whether to print empty members of a declaration on a single line, e.g.:
/// ```
/// extension Foo: Bar {}
/// ```
bool PrintEmptyMembersOnSameLine = false;

/// Whether to print the extensions from conforming protocols.
bool PrintExtensionFromConformingProtocols = false;

Expand Down
25 changes: 12 additions & 13 deletions lib/AST/ASTPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2335,7 +2335,8 @@ void PrintAST::printMembers(ArrayRef<Decl *> members, bool needComma,
bool openBracket, bool closeBracket) {
if (openBracket) {
Printer << " {";
Printer.printNewline();
if (!Options.PrintEmptyMembersOnSameLine || !members.empty())
Printer.printNewline();
}
{
IndentRAII indentMore(*this);
Expand Down Expand Up @@ -3128,13 +3129,12 @@ static FeatureSet getUniqueFeaturesUsed(Decl *decl) {
return features;
}

static void printCompatibilityCheckIf(ASTPrinter &printer,
bool isElsif,
static void printCompatibilityCheckIf(ASTPrinter &printer, bool isElseIf,
bool includeCompilerCheck,
const BasicFeatureSet &features) {
assert(!features.empty());

printer << (isElsif ? "#elsif " : "#if ");
printer << (isElseIf ? "#elseif " : "#if ");
if (includeCompilerCheck)
printer << "compiler(>=5.3) && ";

Expand All @@ -3150,7 +3150,7 @@ static void printCompatibilityCheckIf(ASTPrinter &printer,
printer.printNewline();
}

/// Generate a #if ... #elsif ... #endif chain for the given
/// Generate a #if ... #elseif ... #endif chain for the given
/// suppressible feature checks.
static void printWithSuppressibleFeatureChecks(ASTPrinter &printer,
PrintOptions &options,
Expand All @@ -3171,18 +3171,17 @@ static void printWithSuppressibleFeatureChecks(ASTPrinter &printer,
return;
}

// Otherwise, enter a `#if` or `#elsif` for the next feature.
// Otherwise, enter a `#if` or `#elseif` for the next feature.
Feature feature = generator.next();
printCompatibilityCheckIf(printer, /*elsif*/ !firstInChain,
includeCompilerCheck,
{feature});
printCompatibilityCheckIf(printer, /*elseif*/ !firstInChain,
includeCompilerCheck, {feature});

// Print the body.
printBody();
printer.printNewline();

// Start suppressing the feature and recurse to either generate
// more `#elsif` clauses or finish off with `#endif`.
// more `#elseif` clauses or finish off with `#endif`.
suppressingFeature(options, feature, [&] {
printWithSuppressibleFeatureChecks(printer, options, /*first*/ false,
includeCompilerCheck, generator,
Expand All @@ -3195,13 +3194,13 @@ static void printWithSuppressibleFeatureChecks(ASTPrinter &printer,
///
/// In the most general form, with both required features and multiple
/// suppressible features in play, the generated code pattern looks like
/// the following (assuming that feaature $bar implies feature $baz):
/// the following (assuming that feature $bar implies feature $baz):
///
/// ```
/// #if compiler(>=5.3) && $foo
/// #if $bar
/// @foo @bar @baz func @test() {}
/// #elsif $baz
/// #elseif $baz
/// @foo @baz func @test() {}
/// #else
/// @foo func @test() {}
Expand Down Expand Up @@ -3229,7 +3228,7 @@ void swift::printWithCompatibilityFeatureChecks(ASTPrinter &printer,
bool hasRequiredFeatures = features.hasAnyRequired();
if (hasRequiredFeatures) {
printCompatibilityCheckIf(printer,
/*elsif*/ false,
/*elseif*/ false,
/*compiler check*/ true,
features.requiredFeatures());
}
Expand Down
103 changes: 56 additions & 47 deletions lib/Frontend/ModuleInterfaceSupport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
#include "swift/AST/FileSystem.h"
#include "swift/AST/Module.h"
#include "swift/AST/ModuleNameLookup.h"
#include "swift/AST/NameLookupRequests.h"
#include "swift/AST/ProtocolConformance.h"
#include "swift/AST/TypeCheckRequests.h"
#include "swift/AST/TypeRepr.h"
#include "swift/Basic/STLExtras.h"
#include "swift/Frontend/Frontend.h"
Expand Down Expand Up @@ -569,13 +571,14 @@ class InheritedProtocolCollector {
});
}

// Preserve the behavior of previous implementations which formatted of
// empty extensions compactly with '{}' on the same line.
PrintOptions extensionPrintOptions = printOptions;
extensionPrintOptions.PrintEmptyMembersOnSameLine = true;

// 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.
// FIXME: This will pick the availability attributes from the first sight
// of a protocol rather than the maximally available case.
SmallVector<ProtocolAndAvailability, 16> protocolsToPrint;
for (const auto &protoAndAvailability : ExtraProtocols) {
auto proto = std::get<0>(protoAndAvailability);
auto availability = std::get<1>(protoAndAvailability);
Expand All @@ -601,58 +604,64 @@ class InheritedProtocolCollector {
if (isPublicOrUsableFromInline(inherited) &&
conformanceDeclaredInModule(M, nominal, inherited) &&
!M->isImportedImplementationOnly(inherited->getParentModule())) {
protocolsToPrint.push_back(
ProtocolAndAvailability(inherited, availability, isUnchecked,
otherAttrs));
auto protoAndAvailability = ProtocolAndAvailability(
inherited, availability, isUnchecked, otherAttrs);
printSynthesizedExtension(out, extensionPrintOptions, M, nominal,
protoAndAvailability);
return TypeWalker::Action::SkipChildren;
}

return TypeWalker::Action::Continue;
});
}
if (protocolsToPrint.empty())
return;

for (const auto &protoAndAvailability : protocolsToPrint) {
StreamPrinter printer(out);
auto proto = std::get<0>(protoAndAvailability);
auto availability = std::get<1>(protoAndAvailability);
auto isUnchecked = std::get<2>(protoAndAvailability);
auto otherAttrs = std::get<3>(protoAndAvailability);

PrintOptions curPrintOptions = printOptions;
auto printBody = [&] {
// FIXME: Shouldn't this be an implicit conversion?
TinyPtrVector<const DeclAttribute *> attrs;
attrs.insert(attrs.end(), availability.begin(), availability.end());
auto spiAttributes = proto->getAttrs().getAttributes<SPIAccessControlAttr>();
attrs.insert(attrs.end(), spiAttributes.begin(), spiAttributes.end());
attrs.insert(attrs.end(), otherAttrs.begin(), otherAttrs.end());
DeclAttributes::print(printer, curPrintOptions, attrs);

printer << "extension ";
{
bool oldFullyQualifiedTypesIfAmbiguous =
curPrintOptions.FullyQualifiedTypesIfAmbiguous;
curPrintOptions.FullyQualifiedTypesIfAmbiguous =
curPrintOptions.FullyQualifiedExtendedTypesIfAmbiguous;
nominal->getDeclaredType().print(printer, curPrintOptions);
curPrintOptions.FullyQualifiedTypesIfAmbiguous =
oldFullyQualifiedTypesIfAmbiguous;
}
printer << " : ";

if (isUnchecked)
printer << "@unchecked ";
}

proto->getDeclaredInterfaceType()->print(printer, curPrintOptions);
/// Prints a dummy extension on \p nominal to \p out for a public conformance
/// to the protocol contained by \p protoAndAvailability.
static void
printSynthesizedExtension(raw_ostream &out, const PrintOptions &printOptions,
ModuleDecl *M, const NominalTypeDecl *nominal,
ProtocolAndAvailability &protoAndAvailability) {
StreamPrinter printer(out);

auto proto = std::get<0>(protoAndAvailability);
auto availability = std::get<1>(protoAndAvailability);
auto isUnchecked = std::get<2>(protoAndAvailability);
auto otherAttrs = std::get<3>(protoAndAvailability);

// Create a synthesized ExtensionDecl for the conformance.
ASTContext &ctx = M->getASTContext();
auto inherits = ctx.AllocateCopy(llvm::makeArrayRef(InheritedEntry(
TypeLoc::withoutLoc(proto->getDeclaredInterfaceType()), isUnchecked)));
auto extension =
ExtensionDecl::create(ctx, SourceLoc(), nullptr, inherits,
nominal->getModuleScopeContext(), nullptr);
extension->setImplicit();

// Build up synthesized DeclAttributes for the extension.
TinyPtrVector<const DeclAttribute *> attrs;
attrs.insert(attrs.end(), availability.begin(), availability.end());
auto spiAttributes =
proto->getAttrs().getAttributes<SPIAccessControlAttr>();
attrs.insert(attrs.end(), spiAttributes.begin(), spiAttributes.end());
attrs.insert(attrs.end(), otherAttrs.begin(), otherAttrs.end());

// Since DeclAttributes is a linked list where each added attribute becomes
// the head, we need to add these attributes in reverse order to reproduce
// the order in which previous implementations printed these attributes.
DeclAttributes declAttrs;
for (auto attr = attrs.rbegin(), end = attrs.rend(); attr != end; ++attr) {
declAttrs.add(const_cast<DeclAttribute *>(*attr));
}
extension->getAttrs() = declAttrs;

printer << " {}";
};
ctx.evaluator.cacheOutput(ExtendedTypeRequest{extension},
nominal->getDeclaredType());
ctx.evaluator.cacheOutput(ExtendedNominalRequest{extension},
const_cast<NominalTypeDecl *>(nominal));

printBody();
printer << "\n";
}
extension->print(printer, printOptions);
printer << "\n";
}

/// If there were any conditional conformances that couldn't be printed,
Expand Down
11 changes: 8 additions & 3 deletions test/ModuleInterface/features.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// RUN: %empty-directory(%t)

// RUN: %target-swift-frontend -typecheck -swift-version 5 -module-name FeatureTest -emit-module-interface-path - -enable-library-evolution -disable-availability-checking %s | %FileCheck %s
// REQUIRES: concurrency
// RUN: %target-swift-frontend -typecheck -swift-version 5 -module-name FeatureTest -emit-module-interface-path %t/FeatureTest.swiftinterface -enable-library-evolution -disable-availability-checking %s
// RUN: %FileCheck %s < %t/FeatureTest.swiftinterface
// RUN: %target-swift-frontend -typecheck-module-from-interface -disable-availability-checking -swift-version 5 -module-name FeatureTest %t/FeatureTest.swiftinterface

// REQUIRES: concurrency

Expand Down Expand Up @@ -166,7 +167,7 @@ public func unsafeInheritExecutor() async {}
// CHECK-NEXT: #if $UnsafeInheritExecutor
// CHECK-NEXT: @_specialize{{.*}}
// CHECK-NEXT: @_unsafeInheritExecutor public func multipleSuppressible<T>(value: T) async
// CHECK-NEXT: #elsif $SpecializeAttributeWithAvailability
// CHECK-NEXT: #elseif $SpecializeAttributeWithAvailability
// CHECK-NEXT: @_specialize{{.*}}
// CHECK-NEXT: public func multipleSuppressible<T>(value: T) async
// CHECK-NEXT: #else
Expand Down Expand Up @@ -195,3 +196,7 @@ public func unavailableFromAsyncFunc() { }
public func noAsyncFunc() { }

// CHECK-NOT: extension FeatureTest.MyActor : Swift.Sendable

// CHECK: #if compiler(>=5.3) && $GlobalActors
// CHECK-NEXT: extension FeatureTest.SomeGlobalActor : _Concurrency.GlobalActor {}
// CHECK-NEXT: #endif