Skip to content

Adding optional message to _unavailableFromAsync #40378

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
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
2 changes: 1 addition & 1 deletion include/swift/AST/Attr.def
Original file line number Diff line number Diff line change
Expand Up @@ -699,7 +699,7 @@ CONTEXTUAL_SIMPLE_DECL_ATTR(_const, CompileTimeConst,
ABIStableToAdd | ABIStableToRemove | APIBreakingToAdd | APIStableToRemove,
126)

SIMPLE_DECL_ATTR(_unavailableFromAsync, UnavailableFromAsync,
DECL_ATTR(_unavailableFromAsync, UnavailableFromAsync,
OnFunc | OnConstructor | UserInaccessible |
ABIStableToAdd | ABIStableToRemove |
APIBreakingToAdd | APIStableToRemove,
Expand Down
20 changes: 20 additions & 0 deletions include/swift/AST/Attr.h
Original file line number Diff line number Diff line change
Expand Up @@ -2085,6 +2085,26 @@ class TypeSequenceAttr : public DeclAttribute {
}
};

/// The @_unavailableFromAsync attribute, used to make function declarations
/// unavailable from async contexts.
class UnavailableFromAsyncAttr : public DeclAttribute {
public:
UnavailableFromAsyncAttr(StringRef Message, SourceLoc AtLoc,
SourceRange Range, bool Implicit)
: DeclAttribute(DAK_UnavailableFromAsync, AtLoc, Range, Implicit),
Message(Message) {}
UnavailableFromAsyncAttr(StringRef Message, bool Implicit)
: UnavailableFromAsyncAttr(Message, SourceLoc(), SourceRange(),
Implicit) {}
const StringRef Message;

bool hasMessage() const { return !Message.empty(); }

static bool classof(const DeclAttribute *DA) {
return DA->getKind() == DAK_UnavailableFromAsync;
}
};

/// Attributes that may be applied to declarations.
class DeclAttributes {
/// Linked list of declaration attributes.
Expand Down
2 changes: 2 additions & 0 deletions include/swift/AST/DiagnosticsParse.def
Original file line number Diff line number Diff line change
Expand Up @@ -1435,6 +1435,8 @@ ERROR(attr_missing_label,PointsToFirstBadToken,
"missing label '%0:' in '@%1' attribute", (StringRef, StringRef))
ERROR(attr_expected_label,none,
"expected label '%0:' in '@%1' attribute", (StringRef, StringRef))
ERROR(attr_expected_colon_after_label,none,
"expected ':' after label '%0'", (StringRef))

ERROR(alignment_must_be_positive_integer,none,
"alignment value must be a positive integer literal", ())
Expand Down
3 changes: 2 additions & 1 deletion include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -4719,7 +4719,8 @@ ERROR(async_named_decl_must_be_available_from_async,none,
"asynchronous %0 %1 must be available from asynchronous contexts",
(DescriptiveDeclKind, DeclName))
ERROR(async_unavailable_decl,none,
"%0 %1 is unavailable from asynchronous contexts", (DescriptiveDeclKind, DeclBaseName))
"%0 %1 is unavailable from asynchronous contexts%select{|; %3}2",
(DescriptiveDeclKind, DeclBaseName, bool, StringRef))

//------------------------------------------------------------------------------
// MARK: Type Check Types
Expand Down
13 changes: 13 additions & 0 deletions lib/AST/Attr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1164,6 +1164,17 @@ bool DeclAttribute::printImpl(ASTPrinter &Printer, const PrintOptions &Options,
break;
}

case DAK_UnavailableFromAsync: {
Printer.printAttrName("@_unavailableFromAsync");
const UnavailableFromAsyncAttr *attr = cast<UnavailableFromAsyncAttr>(this);
if (attr->hasMessage()) {
Printer << "(message: \"";
Printer << attr->Message;
Printer << "\")";
}
break;
}

case DAK_Count:
llvm_unreachable("exceed declaration attribute kinds");

Expand Down Expand Up @@ -1317,6 +1328,8 @@ StringRef DeclAttribute::getAttrName() const {
return "transpose";
case DAK_TypeSequence:
return "_typeSequence";
case DAK_UnavailableFromAsync:
return "_unavailableFromAsync";
}
llvm_unreachable("bad DeclAttrKind");
}
Expand Down
47 changes: 47 additions & 0 deletions lib/Parse/ParseDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2749,6 +2749,53 @@ bool Parser::parseNewDeclAttribute(DeclAttributes &Attributes, SourceLoc AtLoc,
Attributes.add(TypeSequenceAttr::create(Context, AtLoc, range));
break;
}

case DAK_UnavailableFromAsync: {
StringRef message;
if (consumeIf(tok::l_paren)) {
if (!Tok.is(tok::identifier)) {
llvm_unreachable("Flag must start with an indentifier");
}

StringRef flag = Tok.getText();

if (flag != "message") {
diagnose(Tok.getLoc(), diag::attr_unknown_option, flag, AttrName);
return true;
}
consumeToken();
if (!consumeIf(tok::colon)) {
if (!Tok.is(tok::equal)) {
diagnose(Tok.getLoc(), diag::attr_expected_colon_after_label, flag);
return false;
}
diagnose(Tok.getLoc(), diag::replace_equal_with_colon_for_value)
.fixItReplace(Tok.getLoc(), ": ");
consumeToken();
}
if (!Tok.is(tok::string_literal)) {
diagnose(Tok.getLoc(), diag::attr_expected_string_literal, AttrName);
return false;
}

Optional<StringRef> value = getStringLiteralIfNotInterpolated(
Tok.getLoc(), flag);
if (!value)
return false;
Token stringTok = Tok;
consumeToken();
message = *value;

if (!consumeIf(tok::r_paren))
diagnose(stringTok.getRange().getEnd(), diag::attr_expected_rparen,
AttrName, /*isModifiler*/false)
.fixItInsertAfter(stringTok.getLoc(), ")");
}

Attributes.add(new (Context) UnavailableFromAsyncAttr(
message, AtLoc, SourceRange(Loc, Tok.getLoc()), false));
break;
}
}

if (DuplicateAttribute) {
Expand Down
4 changes: 3 additions & 1 deletion lib/Sema/TypeCheckAvailability.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3149,10 +3149,12 @@ diagnoseDeclUnavailableFromAsync(const ValueDecl *D, SourceRange R,
return false;

ASTContext &ctx = Where.getDeclContext()->getASTContext();
const UnavailableFromAsyncAttr *attr =
D->getAttrs().getAttribute<UnavailableFromAsyncAttr>();
SourceLoc diagLoc = call ? call->getLoc() : R.Start;
ctx.Diags
.diagnose(diagLoc, diag::async_unavailable_decl, D->getDescriptiveKind(),
D->getBaseName())
D->getBaseName(), attr->hasMessage(), attr->Message)
.warnUntilSwiftVersion(6);
D->diagnose(diag::decl_declared_here, D->getName());
return true;
Expand Down
8 changes: 8 additions & 0 deletions lib/Serialization/Deserialization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4747,6 +4747,14 @@ llvm::Error DeclDeserializer::deserializeDeclCommon() {
break;
}

case decls_block::UnavailableFromAsync_DECL_ATTR: {
bool isImplicit;
serialization::decls_block::UnavailableFromAsyncDeclAttrLayout::
readRecord(scratch, isImplicit);
Attr = new (ctx) UnavailableFromAsyncAttr(blobData, isImplicit);
break;
}

#define SIMPLE_DECL_ATTR(NAME, CLASS, ...) \
case decls_block::CLASS##_DECL_ATTR: { \
bool isImplicit; \
Expand Down
8 changes: 7 additions & 1 deletion lib/Serialization/ModuleFormat.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ const uint16_t SWIFTMODULE_VERSION_MAJOR = 0;
/// describe what change you made. The content of this comment isn't important;
/// it just ensures a conflict if two people change the module format.
/// Don't worry about adhering to the 80-column limit for this line.
const uint16_t SWIFTMODULE_VERSION_MINOR = 648; // mark_unresolved_move_addr
const uint16_t SWIFTMODULE_VERSION_MINOR = 649; // _unavailableFromAsync message

/// A standard hash seed used for all string hashes in a serialized module.
///
Expand Down Expand Up @@ -2022,6 +2022,12 @@ namespace decls_block {
TypeIDField, // type referenced by this custom attribute
BCFixed<1> // is the argument (unsafe)
>;

using UnavailableFromAsyncDeclAttrLayout = BCRecordLayout<
UnavailableFromAsync_DECL_ATTR,
BCFixed<1>, // Implicit flag
BCBlob // Message
>;
}

/// Returns the encoding kind for the given decl.
Expand Down
10 changes: 10 additions & 0 deletions lib/Serialization/Serialization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2754,6 +2754,16 @@ class Serializer::DeclSerializer : public DeclVisitor<DeclSerializer> {
TypeSequenceDeclAttrLayout::emitRecord(S.Out, S.ScratchRecord, abbrCode);
return;
}

case DAK_UnavailableFromAsync: {
auto abbrCode =
S.DeclTypeAbbrCodes[UnavailableFromAsyncDeclAttrLayout::Code];
auto *theAttr = cast<UnavailableFromAsyncAttr>(DA);
UnavailableFromAsyncDeclAttrLayout::emitRecord(
S.Out, S.ScratchRecord, abbrCode, theAttr->isImplicit(),
theAttr->Message);
return;
}
}
}

Expand Down
3 changes: 2 additions & 1 deletion test/ClangImporter/objc_async.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ func testSlowServer(slowServer: SlowServer) async throws {
let _: Int = await slowServer.bestName("hello")
let _: Int = await slowServer.customize("hello")

slowServer.unavailableMethod() // expected-warning{{'unavailableMethod' is unavailable from asynchronous contexts}}
slowServer.unavailableMethod() // expected-warning{{instance method 'unavailableMethod' is unavailable from asynchronous contexts}}
slowServer.unavailableMethodWithMessage() // expected-warning{{instance method 'unavailableMethodWithMessage' is unavailable from asynchronous contexts; Blarpy!}}

let _: String = await slowServer.dance("slide")
let _: String = await slowServer.__leap(17)
Expand Down
40 changes: 35 additions & 5 deletions test/Concurrency/unavailable_from_async.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ actor Baz { }

@available(SwiftStdlib 5.1, *)
struct Bop {
@_unavailableFromAsync
@_unavailableFromAsync(message: "Use Bop(a: Int) instead")
init() {} // expected-note 4 {{'init()' declared here}}

init(a: Int) { }
Expand All @@ -59,7 +59,7 @@ func foo() {} // expected-note 4 {{'foo()' declared here}}
func makeAsyncClosuresSynchronously(bop: inout Bop) -> (() async -> Void) {
return { () async -> Void in
// Unavailable methods
_ = Bop() // expected-warning@:9{{'init' is unavailable from asynchronous contexts}}
_ = Bop() // expected-warning@:9{{'init' is unavailable from asynchronous contexts; Use Bop(a: Int) instead}}
_ = Bop(a: 32)
bop.foo() // expected-warning@:9{{'foo' is unavailable from asynchronous contexts}}
bop.muppet() // expected-warning@:9{{'muppet' is unavailable from asynchronous contexts}}
Expand All @@ -83,7 +83,7 @@ func makeAsyncClosuresSynchronously(bop: inout Bop) -> (() async -> Void) {
func asyncFunc() async { // expected-error{{asynchronous global function 'asyncFunc()' must be available from asynchronous contexts}}

var bop = Bop(a: 32)
_ = Bop() // expected-warning@:7{{'init' is unavailable from asynchronous contexts}}
_ = Bop() // expected-warning@:7{{'init' is unavailable from asynchronous contexts; Use Bop(a: Int) instead}}
bop.foo() // expected-warning@:7{{'foo' is unavailable from asynchronous contexts}}
bop.muppet() // expected-warning@:7{{'muppet' is unavailable from asynchronous contexts}}
unavailableFunction() // expected-warning@:3{{'unavailableFunction' is unavailable from asynchronous contexts}}
Expand All @@ -107,13 +107,13 @@ func asyncFunc() async { // expected-error{{asynchronous global function 'asyncF
foo() // expected-warning@:7{{'foo' is unavailable from asynchronous contexts}}
bop.foo() // expected-warning@:11{{'foo' is unavailable from asynchronous contexts}}
bop.muppet() // expected-warning@:11{{'muppet' is unavailable from asynchronous contexts}}
_ = Bop() // expected-warning@:11{{'init' is unavailable from asynchronous contexts}}
_ = Bop() // expected-warning@:11{{'init' is unavailable from asynchronous contexts; Use Bop(a: Int) instead}}
unavailableFunction() // expected-warning@:7{{'unavailableFunction' is unavailable from asynchronous contexts}}
}
}

_ = { () async -> Void in
_ = Bop() // expected-warning@:9{{'init' is unavailable from asynchronous contexts}}
_ = Bop() // expected-warning@:9{{'init' is unavailable from asynchronous contexts; Use Bop(a: Int) instead}}
foo() // expected-warning@:5{{'foo' is unavailable from asynchronous contexts}}
bop.foo() // expected-warning@:9{{'foo' is unavailable from asynchronous contexts}}
bop.muppet() // expected-warning@:9{{'muppet' is unavailable from asynchronous contexts}}
Expand All @@ -128,3 +128,33 @@ func asyncFunc() async { // expected-error{{asynchronous global function 'asyncF
}

}

// Parsing tests

// expected-error@+2 {{expected declaration}}
// expected-error@+1:24{{unknown option 'nope' for attribute '_unavailableFromAsync'}}
@_unavailableFromAsync(nope: "almost right, but not quite")
func blarp1() {}

// expected-error@+2 {{expected declaration}}
// expected-error@+1 {{expected ':' after label 'message'}}
@_unavailableFromAsync(message; "almost right, but not quite")
func blarp2() {}

// expected-error@+1:31 {{'=' has been replaced with ':' in attribute arguments}}{{31-32=: }}
@_unavailableFromAsync(message="almost right, but not quite")
func blarp3() {}

// expected-error@+2 {{expected declaration}}
// expected-error@+1 {{expected string literal in '_unavailableFromAsync' attribute}}
@_unavailableFromAsync(message: 32)
func blarp4() {}

// expected-error@+2 {{expected declaration}}
// expected-error@+1 {{message cannot be an interpolated string}}
@_unavailableFromAsync(message: "blarppy blarp \(31 + 10)")
func blarp5() {}

// expected-error@+1:48 {{expected ')' in '_unavailableFromAsync' attribute}}{{48-48=)}}
@_unavailableFromAsync(message: "blarppy blarp"
func blarp6() {}
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ typedef void (^CompletionHandler)(NSString * _Nullable, NSString * _Nullable_res
-(void)customizedWithString:(NSString *)operation completionHandler:(void (^)(NSInteger))handler __attribute__((swift_name("customize(with:completionHandler:)"))) __attribute__((swift_async_name("customize(_:)")));

-(void)unavailableMethod __attribute__((__swift_attr__("@_unavailableFromAsync")));
-(void)unavailableMethodWithMessage __attribute__((__swift_attr__("@_unavailableFromAsync(message: \"Blarpy!\")")));

-(void)dance:(NSString *)step andThen:(void (^)(NSString *))doSomething __attribute__((swift_async(not_swift_private,2)));
-(void)leap:(NSInteger)height andThen:(void (^)(NSString *))doSomething __attribute__((swift_async(swift_private,2)));
Expand Down