[clang] 06340b8 - [ExtractAPI] Include nullability attributes (#209221)
via cfe-commits
cfe-commits at lists.llvm.org
Fri Jul 17 08:42:51 PDT 2026
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
+
+ at class NSString;
+ at protocol NSCopying
+ at end
+
+// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix RET_NULLABLE
+NSString *_Nullable returnsNullable(void);
+// RET_NULLABLE-LABEL: "!testLabel": "c:@F at 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 at 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 at 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 at 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 at 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 at 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 at 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
More information about the cfe-commits
mailing list