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 @@ -1864,6 +1864,13 @@ let Documentation = [Undocumented]; } +def NSErrorDomain : InheritableAttr { + let Spellings = [GNU<"ns_error_domain">]; + let Subjects = SubjectList<[Enum], ErrorDiag>; + let Args = [IdentifierArgument<"ErrorDomain">]; + let Documentation = [NSErrorDomainDocs]; +} + def NSReturnsRetained : DeclOrTypeAttr { let Spellings = [Clang<"ns_returns_retained">]; // let Subjects = SubjectList<[ObjCMethod, ObjCProperty, Function]>; 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 @@ -3313,6 +3313,38 @@ }]; } +def NSErrorDomainDocs : Documentation { + let Category = DocCatDecl; + let Content = [{ +In Cocoa frameworks in Objective-C, one can group related error codes in enums +and categorize these enums with error domains. + +The ``ns_error_domain`` attribute indicates a global ``NSString`` constant +representing the error domain that an error code belongs to. For pointer +uniqueness and code size this is a constant symbol, not a literal. + +The domain and error code need to be used together. The ``ns_error_domain`` +attribute links error codes to their domain at the source level. + +This metadata is useful for documentation purposes, for static analysis, and for +improving interoperability between Objective-C and Swift. It is not used for +code generation in Objective-C. + +For example: + + .. code-block:: objc + + #define NS_ERROR_ENUM(_type, _name, _domain) \ + enum _name : _type _name; enum __attribute__((ns_error_domain(_domain))) _name : _type + + extern NSString *const MyErrorDomain; + typedef NS_ERROR_ENUM(unsigned char, MyErrorEnum, MyErrorDomain) { + MyErrFirst, + MyErrSecond, + }; + }]; +} + def OMPDeclareSimdDocs : Documentation { let Category = DocCatFunction; let Heading = "#pragma omp declare simd"; 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 @@ -9458,6 +9458,9 @@ def err_nsreturns_retained_attribute_mismatch : Error< "overriding method has mismatched ns_returns_%select{not_retained|retained}0" " attributes">; +def err_nserrordomain_invalid_decl : Error< + "domain argument %0 does not refer to global constant">; + def warn_nsconsumed_attribute_mismatch : Warning< err_nsconsumed_attribute_mismatch.Text>, InGroup; def warn_nsreturns_retained_attribute_mismatch : Warning< 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 @@ -5322,6 +5322,35 @@ D->addAttr(::new (S.Context) ObjCRequiresSuperAttr(S.Context, Attrs)); } +static void handleNSErrorDomain(Sema &S, Decl *D, const ParsedAttr &AL) { + IdentifierLoc *IdentLoc = AL.isArgIdent(0) ? AL.getArgAsIdent(0) : nullptr; + if (!IdentLoc || !IdentLoc->Ident) { + // Try to locate the argument directly + SourceLocation Loc = AL.getLoc(); + if (const Expr *E = AL.getArgAsExpr(0)) + Loc = E->getBeginLoc(); + + S.Diag(Loc, diag::err_attribute_argument_n_type) + << AL << 1 << AANT_ArgumentIdentifier; + + return; + } + + // Verify that the identifier is a valid decl in the C decl namespace + LookupResult lookupResult(S, DeclarationName(IdentLoc->Ident), + SourceLocation(), + Sema::LookupNameKind::LookupOrdinaryName); + if (!S.LookupName(lookupResult, S.TUScope) || + !lookupResult.getAsSingle()) { + S.Diag(IdentLoc->Loc, diag::err_nserrordomain_invalid_decl) + << IdentLoc->Ident; + return; + } + + D->addAttr(::new (S.Context) + NSErrorDomainAttr(S.Context, AL, IdentLoc->Ident)); +} + static void handleObjCBridgeAttr(Sema &S, Decl *D, const ParsedAttr &AL) { IdentifierLoc *Parm = AL.isArgIdent(0) ? AL.getArgAsIdent(0) : nullptr; @@ -7093,6 +7122,9 @@ case ParsedAttr::AT_ObjCBoxable: handleObjCBoxable(S, D, AL); break; + case ParsedAttr::AT_NSErrorDomain: + handleNSErrorDomain(S, D, AL); + break; case ParsedAttr::AT_CFAuditedTransfer: handleSimpleAttributeWithExclusions(S, D, AL); 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 @@ -80,6 +80,7 @@ // CHECK-NEXT: MipsShortCall (SubjectMatchRule_function) // CHECK-NEXT: NSConsumed (SubjectMatchRule_variable_is_parameter) // CHECK-NEXT: NSConsumesSelf (SubjectMatchRule_objc_method) +// CHECK-NEXT: NSErrorDomain (SubjectMatchRule_enum) // CHECK-NEXT: Naked (SubjectMatchRule_function) // CHECK-NEXT: NoBuiltin (SubjectMatchRule_function) // CHECK-NEXT: NoCommon (SubjectMatchRule_variable) diff --git a/clang/test/Sema/ns_error_enum.m b/clang/test/Sema/ns_error_enum.m new file mode 100644 --- /dev/null +++ b/clang/test/Sema/ns_error_enum.m @@ -0,0 +1,54 @@ +// RUN: %clang_cc1 -verify %s -x objective-c +// RUN: %clang_cc1 -verify %s -x objective-c++ + + +#define CF_ENUM(_type, _name) enum _name : _type _name; enum _name : _type +#define NS_ENUM(_type, _name) CF_ENUM(_type, _name) + +#define NS_ERROR_ENUM(_type, _name, _domain) \ + enum _name : _type _name; enum __attribute__((ns_error_domain(_domain))) _name : _type + +typedef NS_ENUM(unsigned, MyEnum) { + MyFirst, + MySecond, +}; + +typedef NS_ENUM(invalidType, MyInvalidEnum) { +// expected-error@-1{{unknown type name 'invalidType'}} +// expected-error@-2{{unknown type name 'invalidType'}} + MyFirstInvalid, + MySecondInvalid, +}; + +@interface NSString +@end + +extern NSString *const MyErrorDomain; +typedef NS_ERROR_ENUM(unsigned char, MyErrorEnum, MyErrorDomain) { + MyErrFirst, + MyErrSecond, +}; +struct __attribute__((ns_error_domain(MyErrorDomain))) MyStructWithErrorDomain {}; +// expected-error@-1{{'ns_error_domain' attribute only applies to enums}} + +int __attribute__((ns_error_domain(MyErrorDomain))) NotTagDecl; + // expected-error@-1{{'ns_error_domain' attribute only applies to enums}} + +enum __attribute__((ns_error_domain())) NoArg { NoArgError }; +// expected-error@-1{{'ns_error_domain' attribute takes one argument}} + +enum __attribute__((ns_error_domain(MyErrorDomain, MyErrorDomain))) TwoArgs { TwoArgsError }; +// expected-error@-1{{'ns_error_domain' attribute takes one argument}} + +typedef NS_ERROR_ENUM(unsigned char, MyErrorEnumInvalid, InvalidDomain) { + // expected-error@-1{{domain argument 'InvalidDomain' does not refer to global constant}} + MyErrFirstInvalid, + MyErrSecondInvalid, +}; + +typedef NS_ERROR_ENUM(unsigned char, MyErrorEnumInvalid, "domain-string"); + // expected-error@-1{{'ns_error_domain' attribute requires parameter 1 to be an identifier}} + +void foo() {} +typedef NS_ERROR_ENUM(unsigned char, MyErrorEnumInvalidFunction, foo); + // expected-error@-1{{domain argument 'foo' does not refer to global constant}}