Skip to content

[Module interface] Use features in module interface generation. #35839

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

Conversation

DougGregor
Copy link
Member

When generating a module interface, emit #if around any declarations
that are tied to specific, named language features. This allows module
interfaces to be processed by older Swift compilers that do not
support these newer features, such as async/await or actors.

The amount of effort required to correctly handle a new kind of
feature varies somewhat drastically based on the feature itself. The
"simple" case is where a particular declaration can only exist if a
feature is available. For example, and async declaration is fairly
easy to handle; a @_marker protocol's conformances are not.

Fixes rdar://73326633.

Design credit to @beccadax, bugs are my own.

When generating a module interface, emit `#if` around any declarations
that are tied to specific, named language features. This allows module
interfaces to be processed by older Swift compilers that do not
support these newer features, such as async/await or actors.

The amount of effort required to correctly handle a new kind of
feature varies somewhat drastically based on the feature itself. The
"simple" case is where a particular declaration can only exist if a
feature is available. For example, and `async` declaration is fairly
easy to handle; a `@_marker` protocol's conformances are not.

Fixes rdar://73326633.
@DougGregor
Copy link
Member Author

@swift-ci please smoke test

Comment on lines 2402 to 2454
static bool usesFeatureAsyncAwait(Decl *decl) {
if (auto func = dyn_cast<AbstractFunctionDecl>(decl)) {
if (func->hasAsync())
return true;
}

return false;
}

static bool usesFeatureMarkerProtocol(Decl *decl) {
if (auto proto = dyn_cast<ProtocolDecl>(decl)) {
if (proto->isMarkerProtocol())
return true;
}

if (auto ext = dyn_cast<ExtensionDecl>(decl)) {
for (const auto &req: ext->getGenericRequirements()) {
if (req.getKind() == RequirementKind::Conformance &&
req.getSecondType()->castTo<ProtocolType>()->getDecl()
->isMarkerProtocol())
return true;
}

for (const auto &inherited : ext->getInherited()) {
if (auto inheritedType = inherited.getType()) {
if (inheritedType->isExistentialType()) {
auto layout = inheritedType->getExistentialLayout();
for (ProtocolType *protoTy : layout.getProtocols()) {
if (protoTy->getDecl()->isMarkerProtocol())
return true;
}
}
}
}
}

return false;
}

static bool usesFeatureActors(Decl *decl) {
if (auto classDecl = dyn_cast<ClassDecl>(decl)) {
if (classDecl->isActor())
return true;
}

if (auto ext = dyn_cast<ExtensionDecl>(decl)) {
if (auto classDecl = ext->getSelfClassDecl())
if (classDecl->isActor())
return true;
}

return false;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Unless I'm mistaken, all three of these probably ought to walk into any parameter or result types and look for the feature being used there, too. For instance, if you write:

public func runAsync(_ fn: () async -> Void) { ... }

That probably ought to be guarded by #if $AsyncAwait, even though runAsync(_:) itself isn't an async function. Similarly:

public func transact(with account: BankAccount)

Probably ought to be guarded by #if $Actors if BankAccount is an actor class, even though transact(with:) isn't itself an actor class.

Copy link
Member Author

Choose a reason for hiding this comment

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

Good point!

Copy link
Member Author

Choose a reason for hiding this comment

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

I went ahead and did a bunch of this checking. It is not comprehensive, I'm sure, although we could probably do some additional meta programming to make it less boilerplate-y and more comprehensive.

static std::vector<Feature> getFeaturesUsed(Decl *decl) {
std::vector<Feature> features;

// Only type- and module-scope declarations have any features to speak of.
Copy link
Contributor

Choose a reason for hiding this comment

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

Note that, by my expanded definition of "having a feature", this comment is no longer true.

Copy link
Member Author

Choose a reason for hiding this comment

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

Right, I've zapped this. Thanks!

@DougGregor
Copy link
Member Author

@swift-ci please smoke test

1 similar comment
@DougGregor
Copy link
Member Author

@swift-ci please smoke test

@DougGregor
Copy link
Member Author

@swift-ci please smoke test Linux

@DougGregor DougGregor merged commit bd2064a into swiftlang:main Feb 9, 2021
@DougGregor DougGregor deleted the backward-compatible-module-interfaces branch February 9, 2021 15:17
@davezarzycki
Copy link
Contributor

Hi @DougGregor – The test is broken and confusing. Superficially, it needs REQUIRES: concurrency but the comments in the test make it sound like we're testing the opposite: "Make sure we can parse the file without concurrency enabled". What's going on?

Also, FYI: -emit-module-interface-path supports the traditional Unix stdout trick - if you want to avoid needless temp files (which on Apple platforms, aren't as fast as they could/should be).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants