-
Notifications
You must be signed in to change notification settings - Fork 10.5k
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
Add _unavailableFromAsync
attribute
#40149
Conversation
@swift-ci please test |
Windows bot disk is full. |
lib/Sema/TypeCheckConcurrency.cpp
Outdated
} | ||
|
||
public: | ||
UsageWalker(AbstractFunctionDecl &afd) : baseDecl(afd), ctx(afd.getASTContext()) {} |
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.
There are a bunch of other kinds of "declaration references" that can show up in the AST that aren't DeclRefExpr
s, 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.
lib/Sema/TypeCheckStmt.cpp
Outdated
@@ -2058,6 +2058,7 @@ TypeCheckFunctionBodyRequest::evaluate(Evaluator &evaluator, | |||
TypeChecker::computeCaptures(AFD); | |||
if (!AFD->getDeclContext()->isLocalContext()) { | |||
checkFunctionActorIsolation(AFD); | |||
checkAsyncAvailability(*AFD); |
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.
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.
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.
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.
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.
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, |
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.
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, |
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.
Minor nit: it's an error but it's prefixed with warn_
.
lib/Sema/TypeCheckConcurrency.cpp
Outdated
if (decl.hasAsync() && decl.getAttrs().hasAttribute<UnavailableFromAsyncAttr>()) | ||
decl.getASTContext().Diags.diagnose(decl.getLoc(), | ||
diag::warn_async_function_must_be_available_from_async, | ||
decl.getName()); |
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.
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?
lib/Sema/TypeCheckAttr.cpp
Outdated
@@ -119,6 +119,7 @@ class AttributeChecker : public AttributeVisitor<AttributeChecker> { | |||
IGNORED_ATTR(ImplicitSelfCapture) | |||
IGNORED_ATTR(InheritActorContext) | |||
IGNORED_ATTR(Isolated) | |||
IGNORED_ATTR(UnavailableFromAsync) |
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.
... here's where I think we should do the "you can't put this attribute on an async
declaration" check.
@swift-ci please test |
Build failed |
Build failed |
154d4bc
to
39e71d2
Compare
@swift-ci please test |
Build failed |
Build failed |
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.
39e71d2
to
49e225f
Compare
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.
49e225f
to
c57b80a
Compare
Build failed |
Failure was due to the warning
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. |
@swift-ci please test macOS |
@swift-ci please test |
unrelated windows failure. @swift-ci please test windows |
@swift-ci please test windows |
Requires #40368 to pass windows test. |
@swift-ci please test windows |
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.
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 |
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.
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.
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.
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.
@swift-ci please test |
Build failed |
@swift-ci please clean test Linux |
Build failed |
@swift-ci please test Linux |
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