Skip to content

Add _unavailableFromAsync attribute #40149

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

Conversation

etcwilde
Copy link
Member

This PR introduces a new underscored attribute _unavailableFromAsync, which marks synchronous API as being unavailable from asynchronous contexts. Using an annotated API directly in an asynchronous context will result in a compiler warning message.

https://forums.swift.org/t/discussion-unavailability-from-asynchronous-contexts/53088
rdar://70256865

@etcwilde etcwilde requested a review from DougGregor November 12, 2021 02:56
@etcwilde
Copy link
Member Author

@swift-ci please test

@etcwilde
Copy link
Member Author

Windows bot disk is full.

}

public:
UsageWalker(AbstractFunctionDecl &afd) : baseDecl(afd), ctx(afd.getASTContext()) {}
Copy link
Member

Choose a reason for hiding this comment

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

There are a bunch of other kinds of "declaration references" that can show up in the AST that aren't DeclRefExprs, such as MemberRefExpr and SubscriptExpr, and using your own usage walker is likely to get tedious as you find all of the corner cases. I recommend hooking into an existing place where we do checking (e.g., swift::diagnoseDeclAvailability) so you can have just one check.

@@ -2058,6 +2058,7 @@ TypeCheckFunctionBodyRequest::evaluate(Evaluator &evaluator,
TypeChecker::computeCaptures(AFD);
if (!AFD->getDeclContext()->isLocalContext()) {
checkFunctionActorIsolation(AFD);
checkAsyncAvailability(*AFD);
Copy link
Member

Choose a reason for hiding this comment

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

Similar to my comment above, there are actually a couple of different places where we would need to add this call (see, e.g., the other check<something>ActorIsolation function callers), but if you hook into swift::diagnoseDeclAvailability you'll get all of that for free.

Copy link
Member Author

Choose a reason for hiding this comment

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

The challenge with moving the diagnostic to diagnoseDeclAvailability is that we need to be able to ask the declContext whether we're directly in an async context. For AbstractClosureExpr::isAsyncBody, there needs to be a type assigned to the expression. diganoseDeclAvailability is run at a time where we may not have a type or see the expression with a type associated with it.

https://github.com/apple/swift/blob/05fa143a8894bd66c6179abb2928444ae49c04cf/lib/Sema/TypeCheckConstraints.cpp#L400-L418

In TypeChecker::typeCheckExpression, we attempt to apply a solution before calling performSyntacticDiagnosticsForTarget, which eventually calls into diagnoseDeclAvailability. For the simpler test cases, that goes through and the closure expression gets updated with its type. For the more complicated closures like the ones in https://github.com/apple/swift/blob/05fa143a8894bd66c6179abb2928444ae49c04cf/test/Constraints/result_builder.swift, the updated expression doesn't have a type. The constraint system is able to produce a type, but doesn't apply it to the expression until after the children have all type checked, so we don't see the type applied to the decl-context.

https://github.com/apple/swift/blob/cd8da12e0af806642816afefbcd7fd3dbe2c81b3/lib/Sema/CSClosure.cpp#L1393-L1394

There is a type somewhere, though I'm not sure if driving the type from the constraint system, through the availability checking, and into the async checker is the right solution. For the time being, I'm skipping the check and saying that everything is fine.

@@ -4699,6 +4699,11 @@ ERROR(actor_isolation_superclass_mismatch,none,
"%0 class %1 has different actor isolation from %2 superclass %3",
(ActorIsolation, DeclName, ActorIsolation, DeclName))

ERROR(warn_async_function_must_be_available_from_async,none,
"asynchronous function %0 must be available from asynchronous contexts", (DeclName))
WARNING(warn_async_unavailable_decl,none,
Copy link
Member

Choose a reason for hiding this comment

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

Do we think this should be an error in Swift 6 mode, or will it always be a warning? Or should there be more of a distinction for the user about, e.g., "deprecated" (warning) vs. "unavailable" (error, albeit maybe only in Swift 6)?

@@ -4699,6 +4699,11 @@ ERROR(actor_isolation_superclass_mismatch,none,
"%0 class %1 has different actor isolation from %2 superclass %3",
(ActorIsolation, DeclName, ActorIsolation, DeclName))

ERROR(warn_async_function_must_be_available_from_async,none,
Copy link
Member

Choose a reason for hiding this comment

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

Minor nit: it's an error but it's prefixed with warn_.

Comment on lines 4291 to 4294
if (decl.hasAsync() && decl.getAttrs().hasAttribute<UnavailableFromAsyncAttr>())
decl.getASTContext().Diags.diagnose(decl.getLoc(),
diag::warn_async_function_must_be_available_from_async,
decl.getName());
Copy link
Member

Choose a reason for hiding this comment

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

Presumably we need the same kind of check for an unavailable-from-async property, subscript, or initializer, all of which can be async. Could you move this to TypeCheckAttr.cpp, where we validate the correctness of attributes that are applied to declarations?

@@ -119,6 +119,7 @@ class AttributeChecker : public AttributeVisitor<AttributeChecker> {
IGNORED_ATTR(ImplicitSelfCapture)
IGNORED_ATTR(InheritActorContext)
IGNORED_ATTR(Isolated)
IGNORED_ATTR(UnavailableFromAsync)
Copy link
Member

Choose a reason for hiding this comment

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

... here's where I think we should do the "you can't put this attribute on an async declaration" check.

@etcwilde
Copy link
Member Author

@swift-ci please test

@swift-ci
Copy link
Contributor

Build failed
Swift Test Linux Platform
Git Sha - 154d4bc105d727ff1cb2715bda13932f5bb3a1f7

@swift-ci
Copy link
Contributor

Build failed
Swift Test OS X Platform
Git Sha - 154d4bc105d727ff1cb2715bda13932f5bb3a1f7

@etcwilde etcwilde force-pushed the ewilde/concurrency/underscored-unavailablefromasync branch from 154d4bc to 39e71d2 Compare November 29, 2021 18:59
@etcwilde
Copy link
Member Author

@swift-ci please test

@swift-ci
Copy link
Contributor

Build failed
Swift Test Linux Platform
Git Sha - 39e71d210f5687fa5398a395864dc03bea894121

@swift-ci
Copy link
Contributor

Build failed
Swift Test OS X Platform
Git Sha - 39e71d210f5687fa5398a395864dc03bea894121

Querying the context for whether it is an async context is the easiest
way to detect if the context is async. Many decl contexts can be checked
at any point, but AbstractClosureExpr contexts must have been
typechecked. If this is called on an AbstractClosureExpr before the
types have been assigned, it may either crash or do bad things.
@etcwilde etcwilde force-pushed the ewilde/concurrency/underscored-unavailablefromasync branch from 39e71d2 to 49e225f Compare November 30, 2021 21:49
This patch adds a bunch of tests to verify the behaviour of the
@_unavailableFromAsync attribute.

The attribute is only allowed on functions, so we verify that an error
is emitted when it is applied to structs, extensions, classes, and
actors. The attribute may be applied to constructors to disallow
construction in an async context, but cannot be applied to destructors
since a destructor must be callable from anywhere. Additionally, the
attribute cannot be applied to asynchronous functions since an async
function _must_ be called from an async context.

Specific checks include
 - Errors are emitted in an async context (global function, e.g.)
 - Errors are emitted when the async context is nested in a sync context
 - Errors are not emitted from a sync context nested in an async context

This patch also includes verification that the attribute is propagated
across module boundaries and is be imported from ObjC functions.

Lastly, this patch adds the IDE completion testing to verify that the
attribute is considered.
The core part of the check runs as follows
 1. Are we in an async context
 2. Does the decl we're calling have the unavailableFromAsync attribute
 3. Emit a diagnostic

There are a couple challenges that muddy the implementation. First,
single-expression closures are typechecked differently than
multi-expression closures. The single-expression closure is never added
to the closure stack. We have to record it separately upon entry to
verify separately. This is necessary for `_ = { foo() }()` where `foo`
is unavailable from async, and the surrounding context is async.

The second challenge is regarding when things get typechecked. A type
must be assigned to AbstractClosureExprs to determine whether they are
an async context or not. Unfortunately, availability checking runs
before types are fully assigned in some cases. This specifically happens
when working with result builders in specific conditions. I haven't been
able to figure out how to reduce the issue further.
Instead of tracking the single-expression closures in a separate
structure and passing that in under the right conditions, it makes more
sense to simply set the 'Where' decl context to the single-expr closure
and use the correct declaration context to determine whether the context
is async. The reduces the number of variables that need to get plumbed
through to the actual unavailable-from-async check and simplifies the
actual check from figuring out whether we're in a single-expr closure or
in an async context.
@etcwilde etcwilde force-pushed the ewilde/concurrency/underscored-unavailablefromasync branch from 49e225f to c57b80a Compare November 30, 2021 22:31
@etcwilde
Copy link
Member Author

#40341

@swift-ci please test

@etcwilde etcwilde requested a review from DougGregor November 30, 2021 23:17
@swift-ci
Copy link
Contributor

swift-ci commented Dec 1, 2021

Build failed
Swift Test OS X Platform
Git Sha - c57b80a

@etcwilde
Copy link
Member Author

etcwilde commented Dec 1, 2021

Failure was due to the warning

/Users/buildnode/jenkins/workspace/swift-PR-macos/branch-main/swift/test/ClangImporter/objc_async.swift:127:3: error: unexpected warning produced: conformance of 'NonSendableClass' to 'Sendable' is unavailable
  takesSendable(nonSendableClass)     // expected-FIXME-warning{{something about missing conformance}}
  ^

CI apparently doesn't pick up branches made to the same repo, so we will have to wait for #40341 to merge before this will pass.

@etcwilde
Copy link
Member Author

etcwilde commented Dec 1, 2021

@swift-ci please test macOS

@etcwilde
Copy link
Member Author

etcwilde commented Dec 1, 2021

@swift-ci please test

@etcwilde
Copy link
Member Author

etcwilde commented Dec 1, 2021

unrelated windows failure.

@swift-ci please test windows

@etcwilde
Copy link
Member Author

etcwilde commented Dec 2, 2021

@swift-ci please test windows

@etcwilde
Copy link
Member Author

etcwilde commented Dec 2, 2021

Requires #40368 to pass windows test.

@etcwilde
Copy link
Member Author

etcwilde commented Dec 2, 2021

@swift-ci please test windows

Copy link
Member

@DougGregor DougGregor left a comment

Choose a reason for hiding this comment

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

This is looking great!

static bool
diagnoseDeclUnavailableFromAsync(const ValueDecl *D, SourceRange R,
const Expr *call, const ExportContext &Where) {
// FIXME: I don't think this is right, but I don't understand the issue well
Copy link
Member

Choose a reason for hiding this comment

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

This is a bit of a depressing comment, but... okay. There's certainly a bug somewhere if we're getting this code before constraint application.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, I'm probably going to need some help with someone more familiar with the constraint solver and result builders to get this fully debugged.

Just making this user inaccessible until I go through the full proposal
pipeline.
@etcwilde
Copy link
Member Author

etcwilde commented Dec 3, 2021

@swift-ci please test

@swift-ci
Copy link
Contributor

swift-ci commented Dec 4, 2021

Build failed
Swift Test Linux Platform
Git Sha - a84650b

@etcwilde
Copy link
Member Author

etcwilde commented Dec 4, 2021

@swift-ci please clean test Linux

@swift-ci
Copy link
Contributor

swift-ci commented Dec 4, 2021

Build failed
Swift Test Linux Platform
Git Sha - a84650b

@etcwilde
Copy link
Member Author

etcwilde commented Dec 6, 2021

@swift-ci please test Linux

@etcwilde etcwilde merged commit cce3e8a into swiftlang:main Dec 6, 2021
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