[clang] 1e8afba - [clang] Add support for attribute 'swift_async_error'

Erik Pilkington via cfe-commits cfe-commits at lists.llvm.org
Wed Feb 10 10:23:22 PST 2021


Author: Erik Pilkington
Date: 2021-02-10T13:18:13-05:00
New Revision: 1e8afba6f176653abcbda4f0485d9419c8407afb

URL: https://github.com/llvm/llvm-project/commit/1e8afba6f176653abcbda4f0485d9419c8407afb
DIFF: https://github.com/llvm/llvm-project/commit/1e8afba6f176653abcbda4f0485d9419c8407afb.diff

LOG: [clang] Add support for attribute 'swift_async_error'

This attribute specifies how an error is represented for a swift async method.
rdar://71941280

Differential revision: https://reviews.llvm.org/D96175

Added: 
    clang/test/SemaObjC/attr-swift-async-error.m

Modified: 
    clang/include/clang/Basic/Attr.td
    clang/include/clang/Basic/AttrDocs.td
    clang/include/clang/Basic/DiagnosticSemaKinds.td
    clang/lib/Sema/SemaDeclAttr.cpp
    clang/test/Misc/pragma-attribute-supported-attributes-list.test

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 5eb8db516305..bc2d8ceeeb6c 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -2401,6 +2401,16 @@ def SwiftAsync : InheritableAttr {
   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
index 170a0fe3d4c4..9de204956190 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -4486,6 +4486,47 @@ argument is the index of the completion handler parameter.
   }];
 }
 
+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 
diff erent 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
index 3ec38a2858ea..f2ae74bb5f6e 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -4107,6 +4107,17 @@ def err_swift_async_bad_block_type : Error<
   "'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
index 30d08b3d4ac0..fcb8c20f406d 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -5856,6 +5856,125 @@ static void handleSwiftError(Sema &S, Decl *D, const ParsedAttr &AL) {
   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<ObjCMethodDecl>(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<BlockPointerType>()
+                           ->getPointeeType()
+                           ->getAs<FunctionProtoType>();
+  ArrayRef<QualType> 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<ObjCObjectPointerType>()) {
+        if (const auto *ID = ObjCPtrTy->getInterfaceDecl()) {
+          if (ID->getIdentifier() == S.getNSErrorIdent()) {
+            AnyErrorParams = true;
+            break;
+          }
+        }
+      }
+      // Check for CFError *.
+      if (const auto *PtrTy = Param->getAs<PointerType>()) {
+        if (const auto *RT = PtrTy->getPointeeType()->getAs<RecordType>()) {
+          if (S.isCFError(RT->getDecl())) {
+            AnyErrorParams = true;
+            break;
+          }
+        }
+      }
+    }
+
+    if (!AnyErrorParams) {
+      S.Diag(ErrorAttr->getLocation(),
+             diag::err_swift_async_error_no_error_parameter)
+          << ErrorAttr << isa<ObjCMethodDecl>(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<SwiftAsyncAttr>())
+    checkSwiftAsyncErrorBlock(S, D, ErrorAttr, AsyncAttr);
+}
+
 // For a function, this will validate a compound Swift name, e.g.
 // <code>init(foo:bar:baz:)</code> or <code>controllerForName(_:)</code>, and
 // the function will output the number of parameter names, and whether this is a
@@ -6240,7 +6359,12 @@ static void handleSwiftAsyncAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
     }
   }
 
-  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<SwiftAsyncErrorAttr>())
+    checkSwiftAsyncErrorBlock(S, D, ErrorAttr, AsyncAttr);
 }
 
 //===----------------------------------------------------------------------===//
@@ -8268,6 +8392,9 @@ static void ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D,
   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
index cb62f56912aa..dd9cd79e69dc 100644
--- 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
index 000000000000..7a608b24682b
--- /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));
+
+ at 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}}
+
+ at 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}}
+ at 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 **);
+
+ at interface TestNoSwiftAsync
+// swift_async_error can make sense without swift_async.
+-(void)doAThingWithCompletion:(void (^)(NSError *))completion
+  ASYNC_ERROR(nonnull_error);
+ at end


        


More information about the cfe-commits mailing list