Author: Prajwal Nadig Date: 2026-07-17T16:42:46+01:00 New Revision: 06340b81573a6824c0ddd54fcba5ae8ffacacd6f
URL: https://github.com/llvm/llvm-project/commit/06340b81573a6824c0ddd54fcba5ae8ffacacd6f DIFF: https://github.com/llvm/llvm-project/commit/06340b81573a6824c0ddd54fcba5ae8ffacacd6f.diff LOG: [ExtractAPI] Include nullability attributes (#209221) Nullability attributes (nullable, nonnull, null_unspecified, null_resettable) add valuable context about the type, and would be useful when viewing declarations. This patch updates ExtractAPI to include these attributes in the symbol graph. rdar://139097216 Added: clang/test/ExtractAPI/objc_nullability.m Modified: clang/include/clang/ExtractAPI/DeclarationFragments.h clang/lib/ExtractAPI/DeclarationFragments.cpp Removed: ################################################################################ diff --git a/clang/include/clang/ExtractAPI/DeclarationFragments.h b/clang/include/clang/ExtractAPI/DeclarationFragments.h index 4859225ef4ced..c8228ffb275bd 100644 --- a/clang/include/clang/ExtractAPI/DeclarationFragments.h +++ b/clang/include/clang/ExtractAPI/DeclarationFragments.h @@ -204,6 +204,10 @@ class DeclarationFragments { return *this; } + bool endsWithKeyword() const { + return !Fragments.empty() && Fragments.back().Kind == FragmentKind::Keyword; + } + /// Append a text Fragment of a space character. /// /// \returns a reference to the DeclarationFragments object itself after diff --git a/clang/lib/ExtractAPI/DeclarationFragments.cpp b/clang/lib/ExtractAPI/DeclarationFragments.cpp index 4eb6f79cb4d85..1ebaf2e7e2456 100644 --- a/clang/lib/ExtractAPI/DeclarationFragments.cpp +++ b/clang/lib/ExtractAPI/DeclarationFragments.cpp @@ -261,9 +261,16 @@ DeclarationFragments DeclarationFragmentsBuilder::getFragmentsForType( } if (const AttributedType *AT = dyn_cast<AttributedType>(T)) { - // FIXME: Serialize Attributes correctly Fragments.append( getFragmentsForType(AT->getModifiedType(), Context, After)); + + // Render explicit nullability annotations after the modified type. + // FIXME: Other AttributedType kinds are not rendered. + if (auto Nullability = AT->getImmediateNullability()) + Fragments.appendSpace().append( + getNullabilitySpelling(*Nullability, /*isContextSensitive=*/false), + DeclarationFragments::FragmentKind::Keyword); + return Fragments; } @@ -632,11 +639,15 @@ DeclarationFragmentsBuilder::getFragmentsForParam(const ParmVarDecl *Param) { .append(Param->getName(), DeclarationFragments::FragmentKind::InternalParam); } else { + // Pointer types should typically not have a space between the * and + // the parameter name. However, if a keyword sits in between, then + // a space must be inserted to avoid joining the keyword and the name. + bool TrailingKeyword = TypeFragments.endsWithKeyword(); Fragments.append(std::move(TypeFragments)); // If the type is a type alias, append the space // even if the underlying type is a pointer type. if (T->isTypedefNameType() || - (!T->isAnyPointerType() && !T->isBlockPointerType())) + (!T->isAnyPointerType() && !T->isBlockPointerType()) || TrailingKeyword) Fragments.appendSpace(); Fragments .append(Param->getName(), @@ -720,8 +731,12 @@ DeclarationFragmentsBuilder::getFragmentsForFunction(const FunctionDecl *Func) { ReturnValueFragment.begin()->Spelling.swap(ProperArgName); } + // Pointer types should typically not have a space between the * and + // the function name. However, if a keyword sits in between, then + // a space must be inserted to avoid joining the keyword and the name. + bool ReturnTrailingKeyword = ReturnValueFragment.endsWithKeyword(); Fragments.append(std::move(ReturnValueFragment)); - if (!ReturnType->isAnyPointerType()) + if (!ReturnType->isAnyPointerType() || ReturnTrailingKeyword) Fragments.appendSpace(); Fragments.append(Func->getNameAsString(), DeclarationFragments::FragmentKind::Identifier); diff --git a/clang/test/ExtractAPI/objc_nullability.m b/clang/test/ExtractAPI/objc_nullability.m new file mode 100644 index 0000000000000..93b5322892123 --- /dev/null +++ b/clang/test/ExtractAPI/objc_nullability.m @@ -0,0 +1,142 @@ +// RUN: rm -rf %t +// RUN: %clang_cc1 -extract-api --pretty-sgf --emit-sgf-symbol-labels-for-testing \ +// RUN: -x objective-c-header -triple arm64-apple-macosx %s -o %t/output.symbols.json -verify + +@class NSString; +@protocol NSCopying +@end + +// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix RET_NULLABLE +NSString *_Nullable returnsNullable(void); +// RET_NULLABLE-LABEL: "!testLabel": "c:@F@returnsNullable" +// RET_NULLABLE: "declarationFragments": [ +// RET_NULLABLE-NEXT: { +// RET_NULLABLE-NEXT: "kind": "typeIdentifier", +// RET_NULLABLE-NEXT: "preciseIdentifier": "c:objc(cs)NSString", +// RET_NULLABLE-NEXT: "spelling": "NSString" +// RET_NULLABLE-NEXT: }, +// RET_NULLABLE-NEXT: { +// RET_NULLABLE-NEXT: "kind": "text", +// RET_NULLABLE-NEXT: "spelling": " * " +// RET_NULLABLE-NEXT: }, +// RET_NULLABLE-NEXT: { +// RET_NULLABLE-NEXT: "kind": "keyword", +// RET_NULLABLE-NEXT: "spelling": "_Nullable" +// RET_NULLABLE-NEXT: }, +// RET_NULLABLE-NEXT: { +// RET_NULLABLE-NEXT: "kind": "text", +// RET_NULLABLE-NEXT: "spelling": " " +// RET_NULLABLE-NEXT: }, +// RET_NULLABLE-NEXT: { +// RET_NULLABLE-NEXT: "kind": "identifier", +// RET_NULLABLE-NEXT: "spelling": "returnsNullable" +// RET_NULLABLE-NEXT: }, + +// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix PARAM_NONNULL +void takesNonnull(NSString *_Nonnull s); +// PARAM_NONNULL-LABEL: "!testLabel": "c:@F@takesNonnull" +// PARAM_NONNULL: "declarationFragments": [ +// PARAM_NONNULL: { +// PARAM_NONNULL: "kind": "typeIdentifier", +// PARAM_NONNULL: "preciseIdentifier": "c:objc(cs)NSString", +// PARAM_NONNULL: "spelling": "NSString" +// PARAM_NONNULL-NEXT: }, +// PARAM_NONNULL-NEXT: { +// PARAM_NONNULL-NEXT: "kind": "text", +// PARAM_NONNULL-NEXT: "spelling": " * " +// PARAM_NONNULL-NEXT: }, +// PARAM_NONNULL-NEXT: { +// PARAM_NONNULL-NEXT: "kind": "keyword", +// PARAM_NONNULL-NEXT: "spelling": "_Nonnull" +// PARAM_NONNULL-NEXT: }, +// PARAM_NONNULL-NEXT: { +// PARAM_NONNULL-NEXT: "kind": "text", +// PARAM_NONNULL-NEXT: "spelling": " " +// PARAM_NONNULL-NEXT: }, +// PARAM_NONNULL-NEXT: { +// PARAM_NONNULL-NEXT: "kind": "internalParam", +// PARAM_NONNULL-NEXT: "spelling": "s" +// PARAM_NONNULL-NEXT: }, + +// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix PLAIN_C +int *_Nullable plainCPointer(int *_Nonnull p); +// PLAIN_C-LABEL: "!testLabel": "c:@F@plainCPointer" +// PLAIN_C: "declarationFragments": [ +// PLAIN_C: { +// PLAIN_C: "kind": "typeIdentifier", +// PLAIN_C: "spelling": "int" +// PLAIN_C-NEXT: }, +// PLAIN_C-NEXT: { +// PLAIN_C-NEXT: "kind": "text", +// PLAIN_C-NEXT: "spelling": " * " +// PLAIN_C-NEXT: }, +// PLAIN_C-NEXT: { +// PLAIN_C-NEXT: "kind": "keyword", +// PLAIN_C-NEXT: "spelling": "_Nullable" +// PLAIN_C-NEXT: }, + +// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix NULL_UNSPEC +void nullUnspec(NSString *_Null_unspecified s); +// NULL_UNSPEC-LABEL: "!testLabel": "c:@F@nullUnspec" +// NULL_UNSPEC: "declarationFragments": [ +// NULL_UNSPEC: { +// NULL_UNSPEC: "kind": "keyword", +// NULL_UNSPEC: "spelling": "_Null_unspecified" +// NULL_UNSPEC-NEXT: }, + +// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix ID_NULLABLE +void takesIdNullable(id _Nullable obj); +// ID_NULLABLE-LABEL: "!testLabel": "c:@F@takesIdNullable" +// ID_NULLABLE: "declarationFragments": [ +// ID_NULLABLE: { +// ID_NULLABLE: "kind": "keyword", +// ID_NULLABLE: "spelling": "id" +// ID_NULLABLE-NEXT: }, +// ID_NULLABLE-NEXT: { +// ID_NULLABLE-NEXT: "kind": "text", +// ID_NULLABLE-NEXT: "spelling": " " +// ID_NULLABLE-NEXT: }, +// ID_NULLABLE-NEXT: { +// ID_NULLABLE-NEXT: "kind": "keyword", +// ID_NULLABLE-NEXT: "spelling": "_Nullable" +// ID_NULLABLE-NEXT: }, + +// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix ID_PROTO +void takesIdProto(id<NSCopying> _Nullable obj); +// ID_PROTO-LABEL: "!testLabel": "c:@F@takesIdProto" +// ID_PROTO: "declarationFragments": [ +// ID_PROTO: { +// ID_PROTO: "kind": "typeIdentifier", +// ID_PROTO: "preciseIdentifier": "c:Qoobjc(pl)NSCopying", +// ID_PROTO: "spelling": "id<NSCopying>" +// ID_PROTO-NEXT: }, +// ID_PROTO-NEXT: { +// ID_PROTO-NEXT: "kind": "text", +// ID_PROTO-NEXT: "spelling": " " +// ID_PROTO-NEXT: }, +// ID_PROTO-NEXT: { +// ID_PROTO-NEXT: "kind": "keyword", +// ID_PROTO-NEXT: "spelling": "_Nullable" +// ID_PROTO-NEXT: }, + +// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix ASSUME_NN +_Pragma("clang assume_nonnull begin") +NSString *implicitlyNonnull(NSString *s); +_Pragma("clang assume_nonnull end") +// ASSUME_NN-LABEL: "!testLabel": "c:@F@implicitlyNonnull" +// ASSUME_NN: "declarationFragments": [ +// ASSUME_NN: { +// ASSUME_NN: "kind": "typeIdentifier", +// ASSUME_NN: "preciseIdentifier": "c:objc(cs)NSString", +// ASSUME_NN: "spelling": "NSString" +// ASSUME_NN-NEXT: }, +// ASSUME_NN-NEXT: { +// ASSUME_NN-NEXT: "kind": "text", +// ASSUME_NN-NEXT: "spelling": " * " +// ASSUME_NN-NEXT: }, +// ASSUME_NN-NEXT: { +// ASSUME_NN-NEXT: "kind": "keyword", +// ASSUME_NN-NEXT: "spelling": "_Nonnull" +// ASSUME_NN-NEXT: }, + +// expected-no-diagnostics _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
