diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -2401,6 +2401,16 @@ let Documentation = [SwiftAsyncDocs]; } +def SwiftAsyncError : InheritableAttr { + let Spellings = [Clang<"swift_async_error">]; + let Subjects = SubjectList<[Function, ObjCMethod]>; + let Args = [EnumArgument<"Convention", "ConventionKind", + ["none", "nonnull_error", "zero_argument", "nonzero_argument"], + ["None", "NonNullError", "ZeroArgument", "NonZeroArgument"]>, + UnsignedArgument<"HandlerParamIdx", /*opt=*/1>]; + let Documentation = [SwiftAsyncErrorDocs]; +} + def Suppress : StmtAttr { let Spellings = [CXX11<"gsl", "suppress">]; let Args = [VariadicStringArgument<"DiagnosticIdentifiers">]; diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -4486,6 +4486,47 @@ }]; } +def SwiftAsyncErrorDocs : Documentation { + let Category = SwiftDocs; + let Heading = "swift_async_error"; + let Content = [{ +The ``swift_async_error`` attribute specifies how an error state will be +represented in a swift async method. It's a bit analogous to the ``swift_error`` +attribute for the generated async method. The ``swift_async_error`` attribute +can indicate a variety of different ways of representing an error. + +- ``__attribute__((swift_async_error(zero_argument, N)))``, specifies that the + async method is considered to have failed if the Nth argument to the + completion handler is zero. + +- ``__attribute__((swift_async_error(nonzero_argument, N)))``, specifies that + the async method is considered to have failed if the Nth argument to the + completion handler is non-zero. + +- ``__attribute__((swift_async_error(nonnull_error)))``, specifies that the + async method is considered to have failed if the ``NSError *`` argument to the + completion handler is non-null. + +- ``__attribute__((swift_async_error(none)))``, specifies that the async method + cannot fail. + + +For instance: + +.. code-block:: objc + + @interface MyClass : NSObject + -(void)asyncMethod:(void (^)(char, int, float))handler + __attribute__((swift_async(swift_private, 1))) + __attribute__((swift_async_error(zero_argument, 2))); + @end + +Here, the ``swift_async`` attribute specifies that ``handler`` is the completion +handler for this method, and the ``swift_async_error`` attribute specifies that +the ``int`` parameter is the one that represents the error. +}]; +} + def SuppressDocs : Documentation { let Category = DocCatStmt; let Content = [{ diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -4107,6 +4107,17 @@ "'swift_async' completion handler parameter must have block type returning" " 'void', type here is %0">; +def err_swift_async_error_without_swift_async : Error< + "%0 attribute must be applied to a %select{function|method}1 annotated " + "with non-'none' attribute 'swift_async'">; +def err_swift_async_error_no_error_parameter : Error< + "%0 attribute with 'nonnull_error' convention can only be applied to a " + "%select{function|method}1 with a completion handler with an error " + "parameter">; +def err_swift_async_error_non_integral : Error< + "%0 attribute with '%1' convention must have an integral-typed parameter " + "in completion handler at index %2, type here is %3">; + def warn_ignored_objc_externally_retained : Warning< "'objc_externally_retained' can only be applied to local variables " "%select{of retainable type|with strong ownership}0">, diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -5856,6 +5856,125 @@ D->addAttr(::new (S.Context) SwiftErrorAttr(S.Context, AL, Convention)); } +static void checkSwiftAsyncErrorBlock(Sema &S, Decl *D, + const SwiftAsyncErrorAttr *ErrorAttr, + const SwiftAsyncAttr *AsyncAttr) { + if (AsyncAttr->getKind() == SwiftAsyncAttr::None) { + if (ErrorAttr->getConvention() != SwiftAsyncErrorAttr::None) { + S.Diag(AsyncAttr->getLocation(), + diag::err_swift_async_error_without_swift_async) + << AsyncAttr << isa(D); + } + return; + } + + const ParmVarDecl *HandlerParam = getFunctionOrMethodParam( + D, AsyncAttr->getCompletionHandlerIndex().getASTIndex()); + // handleSwiftAsyncAttr already verified the type is correct, so no need to + // double-check it here. + const auto *FuncTy = HandlerParam->getType() + ->getAs() + ->getPointeeType() + ->getAs(); + ArrayRef BlockParams; + if (FuncTy) + BlockParams = FuncTy->getParamTypes(); + + switch (ErrorAttr->getConvention()) { + case SwiftAsyncErrorAttr::ZeroArgument: + case SwiftAsyncErrorAttr::NonZeroArgument: { + uint32_t ParamIdx = ErrorAttr->getHandlerParamIdx(); + if (ParamIdx == 0 || ParamIdx > BlockParams.size()) { + S.Diag(ErrorAttr->getLocation(), + diag::err_attribute_argument_out_of_bounds) << ErrorAttr << 2; + return; + } + QualType ErrorParam = BlockParams[ParamIdx - 1]; + if (!ErrorParam->isIntegralType(S.Context)) { + StringRef ConvStr = + ErrorAttr->getConvention() == SwiftAsyncErrorAttr::ZeroArgument + ? "zero_argument" + : "nonzero_argument"; + S.Diag(ErrorAttr->getLocation(), diag::err_swift_async_error_non_integral) + << ErrorAttr << ConvStr << ParamIdx << ErrorParam; + return; + } + break; + } + case SwiftAsyncErrorAttr::NonNullError: { + bool AnyErrorParams = false; + for (QualType Param : BlockParams) { + // Check for NSError *. + if (const auto *ObjCPtrTy = Param->getAs()) { + if (const auto *ID = ObjCPtrTy->getInterfaceDecl()) { + if (ID->getIdentifier() == S.getNSErrorIdent()) { + AnyErrorParams = true; + break; + } + } + } + // Check for CFError *. + if (const auto *PtrTy = Param->getAs()) { + if (const auto *RT = PtrTy->getPointeeType()->getAs()) { + if (S.isCFError(RT->getDecl())) { + AnyErrorParams = true; + break; + } + } + } + } + + if (!AnyErrorParams) { + S.Diag(ErrorAttr->getLocation(), + diag::err_swift_async_error_no_error_parameter) + << ErrorAttr << isa(D); + return; + } + break; + } + case SwiftAsyncErrorAttr::None: + break; + } +} + +static void handleSwiftAsyncError(Sema &S, Decl *D, const ParsedAttr &AL) { + IdentifierLoc *IDLoc = AL.getArgAsIdent(0); + SwiftAsyncErrorAttr::ConventionKind ConvKind; + if (!SwiftAsyncErrorAttr::ConvertStrToConventionKind(IDLoc->Ident->getName(), + ConvKind)) { + S.Diag(AL.getLoc(), diag::warn_attribute_type_not_supported) + << AL << IDLoc->Ident; + return; + } + + uint32_t ParamIdx = 0; + switch (ConvKind) { + case SwiftAsyncErrorAttr::ZeroArgument: + case SwiftAsyncErrorAttr::NonZeroArgument: { + if (!checkAttributeNumArgs(S, AL, 2)) + return; + + Expr *IdxExpr = AL.getArgAsExpr(1); + if (!checkUInt32Argument(S, AL, IdxExpr, ParamIdx)) + return; + break; + } + case SwiftAsyncErrorAttr::NonNullError: + case SwiftAsyncErrorAttr::None: { + if (!checkAttributeNumArgs(S, AL, 1)) + return; + break; + } + } + + auto *ErrorAttr = + ::new (S.Context) SwiftAsyncErrorAttr(S.Context, AL, ConvKind, ParamIdx); + D->addAttr(ErrorAttr); + + if (auto *AsyncAttr = D->getAttr()) + checkSwiftAsyncErrorBlock(S, D, ErrorAttr, AsyncAttr); +} + // For a function, this will validate a compound Swift name, e.g. // init(foo:bar:baz:) or controllerForName(_:), and // the function will output the number of parameter names, and whether this is a @@ -6240,7 +6359,12 @@ } } - D->addAttr(::new (S.Context) SwiftAsyncAttr(S.Context, AL, Kind, Idx)); + auto *AsyncAttr = + ::new (S.Context) SwiftAsyncAttr(S.Context, AL, Kind, Idx); + D->addAttr(AsyncAttr); + + if (auto *ErrorAttr = D->getAttr()) + checkSwiftAsyncErrorBlock(S, D, ErrorAttr, AsyncAttr); } //===----------------------------------------------------------------------===// @@ -8268,6 +8392,9 @@ case ParsedAttr::AT_SwiftAsync: handleSwiftAsyncAttr(S, D, AL); break; + case ParsedAttr::AT_SwiftAsyncError: + handleSwiftAsyncError(S, D, AL); + break; // XRay attributes. case ParsedAttr::AT_XRayLogArgs: diff --git a/clang/test/Misc/pragma-attribute-supported-attributes-list.test b/clang/test/Misc/pragma-attribute-supported-attributes-list.test --- a/clang/test/Misc/pragma-attribute-supported-attributes-list.test +++ b/clang/test/Misc/pragma-attribute-supported-attributes-list.test @@ -155,6 +155,7 @@ // CHECK-NEXT: SetTypestate (SubjectMatchRule_function_is_member) // CHECK-NEXT: SpeculativeLoadHardening (SubjectMatchRule_function, SubjectMatchRule_objc_method) // CHECK-NEXT: SwiftAsync (SubjectMatchRule_function, SubjectMatchRule_objc_method) +// CHECK-NEXT: SwiftAsyncError (SubjectMatchRule_function, SubjectMatchRule_objc_method) // CHECK-NEXT: SwiftAsyncName (SubjectMatchRule_objc_method, SubjectMatchRule_function) // CHECK-NEXT: SwiftBridgedTypedef (SubjectMatchRule_type_alias) // CHECK-NEXT: SwiftContext (SubjectMatchRule_variable_is_parameter) diff --git a/clang/test/SemaObjC/attr-swift-async-error.m b/clang/test/SemaObjC/attr-swift-async-error.m new file mode 100644 --- /dev/null +++ b/clang/test/SemaObjC/attr-swift-async-error.m @@ -0,0 +1,102 @@ +// RUN: %clang_cc1 %s -fblocks -fsyntax-only -verify + +#define ASYNC(...) __attribute__((swift_async(__VA_ARGS__))) +#define ASYNC_ERROR(...) __attribute__((swift_async_error(__VA_ARGS__))) + +ASYNC(swift_private, 1) +ASYNC_ERROR(zero_argument, 1) +void test_good(void (^handler)(int)); + +ASYNC(swift_private, 2) +ASYNC_ERROR(nonzero_argument, 2) +void test_good2(double, void (^handler)(double, int, double)); + +enum SomeEnum { SE_a, SE_b }; + +ASYNC(swift_private, 1) +ASYNC_ERROR(nonzero_argument, 1) +void test_good3(void (^handler)(enum SomeEnum, double)); + +ASYNC_ERROR(zero_argument, 1) +ASYNC(swift_private, 1) +void test_rev_order(void (^handler)(int)); + +@class NSError; + +ASYNC(swift_private, 1) +ASYNC_ERROR(nonnull_error) +void test_nserror(void (^handler)(NSError *)); + +typedef struct __attribute__((objc_bridge(NSError))) __CFError * CFErrorRef; + +ASYNC(swift_private, 1) +ASYNC_ERROR(nonnull_error) +void test_cferror(void (^handler)(CFErrorRef)); + +ASYNC(swift_private, 1) +ASYNC_ERROR(nonnull_error) // expected-error {{'swift_async_error' attribute with 'nonnull_error' convention can only be applied to a function with a completion handler with an error parameter}} +void test_interror(void (^handler)(int)); + +ASYNC(swift_private, 1) +ASYNC_ERROR(zero_argument, 1) // expected-error {{'swift_async_error' attribute with 'zero_argument' convention must have an integral-typed parameter in completion handler at index 1, type here is 'double'}} +void test_not_integral(void (^handler)(double)); + +ASYNC(swift_private, 1) +ASYNC_ERROR(none) +void test_none(void (^)()); + +ASYNC(none) +ASYNC_ERROR(none) +void test_double_none(void (^)()); + +ASYNC(none) +ASYNC_ERROR(none, 1) // expected-error {{'swift_async_error' attribute takes one argument}} +void test_double_none_args(); + +ASYNC(swift_private, 1) +ASYNC_ERROR(nonnull_error, 1) // expected-error{{'swift_async_error' attribute takes one argument}} +void test_args(void (^)(void)); + +ASYNC(swift_private, 1) +ASYNC_ERROR(zero_argument, 1, 1) // expected-error{{'swift_async_error' attribute takes no more than 2 arguments}} +void test_args2(void (^)(int)); + +ASYNC_ERROR(none) int x; // expected-warning{{'swift_async_error' attribute only applies to functions and Objective-C methods}} + +@interface ObjC +-(void)m1:(void (^)(int))handler + ASYNC(swift_private, 1) + ASYNC_ERROR(zero_argument, 1); + +-(void)m2:(int)first withSecond:(void (^)(int))handler + ASYNC(swift_private, 2) + ASYNC_ERROR(nonzero_argument, 1); + +-(void)m3:(void (^)(void))block + ASYNC_ERROR(zero_argument, 1) // expected-error {{'swift_async_error' attribute parameter 2 is out of bounds}} + ASYNC(swift_private, 1); + +-(void)m4:(void (^)(double, int, float))handler + ASYNC(swift_private, 1) + ASYNC_ERROR(nonzero_argument, 1); // expected-error{{swift_async_error' attribute with 'nonzero_argument' convention must have an integral-typed parameter in completion handler at index 1, type here is 'double'}} + +-(void)m5:(void (^)(NSError *))handler + ASYNC(swift_private, 1) + ASYNC_ERROR(nonnull_error); + +-(void)m6:(void (^)(void *))handler + ASYNC(swift_private, 1) + ASYNC_ERROR(nonnull_error); // expected-error{{'swift_async_error' attribute with 'nonnull_error' convention can only be applied to a method with a completion handler with an error parameter}} +@end + +// 'swift_error' and 'swift_async_error' are OK on one function. +ASYNC(swift_private, 1) +ASYNC_ERROR(nonnull_error) +__attribute__((swift_error(nonnull_error))) +void swift_error_and_swift_async_error(void (^handler)(NSError *), NSError **); + +@interface TestNoSwiftAsync +// swift_async_error can make sense without swift_async. +-(void)doAThingWithCompletion:(void (^)(NSError *))completion + ASYNC_ERROR(nonnull_error); +@end