[clang] [clang-tools-extra] Reland "[clang] Don't add documentation comments to the AST if not requested (#206363)" (PR #221605)
via cfe-commits
cfe-commits at lists.llvm.org
Sun Sep 13 04:05:14 PDT 2026
https://github.com/AnonMiraj updated https://github.com/llvm/llvm-project/pull/221605
>From fb786eda3d6b7276a822899f8612587349dc9bcc Mon Sep 17 00:00:00 2001
From: Anonmiraj <ezzibrahimx at gmail.com>
Date: Thu, 3 Sep 2026 07:29:27 +0300
Subject: [PATCH 1/5] Reland "[clang] Don't add documentation comments to the
AST if not requested (#206363)"
This reverts commit 26fc63e1ab6d05bdf708e5311e683f158a7204bd, restoring
e046dce4a4c80610b49d67bc02c85f86b1a6353d unchanged.
The original was reverted for a compile-time regression: +7.6% instructions
at -O0 and +2.8% at -O3 on the compile-time tracker geomean, up to +14.9% on
individual benchmarks. The next commit fixes that; this one is a verbatim
reland so the delta is reviewable on its own.
Don't collect documentation comments unless they are requested
(-Wdocumentation, -fparse-all-comments, code completion, PCH serialization,
or libclang)
closes #165515
---------
Co-authored-by: Erich Keane <ekeane at nvidia.com>
---
.../clang-doc/tool/ClangDocMain.cpp | 3 +-
clang-tools-extra/clangd/Compiler.cpp | 3 +-
.../clangd/index/IndexAction.cpp | 3 +-
clang/docs/ReleaseNotes.md | 6 +
clang/include/clang/Basic/CommentOptions.h | 8 +
clang/include/clang/Basic/Diagnostic.h | 10 +
clang/include/clang/Basic/DiagnosticIDs.h | 6 +
clang/include/clang/Basic/LangOptions.def | 2 -
clang/include/clang/Options/Options.td | 8 +-
clang/include/clang/Sema/Sema.h | 5 +
clang/lib/AST/ASTContext.cpp | 2 +-
clang/lib/Basic/DiagnosticIDs.cpp | 196 ++++++++++--------
clang/lib/Driver/ToolChains/Clang.cpp | 2 +
clang/lib/ExtractAPI/ExtractAPIConsumer.cpp | 3 +
clang/lib/Frontend/ASTUnit.cpp | 6 +
clang/lib/Frontend/CompilerInvocation.cpp | 1 +
clang/lib/Frontend/FrontendActions.cpp | 6 +
clang/lib/Sema/Sema.cpp | 33 ++-
clang/lib/Sema/SemaDecl.cpp | 7 +-
clang/test/AST/ast-dump-comment-retention.cpp | 28 +++
.../warn-documentation-comment-retention.cpp | 38 ++++
21 files changed, 278 insertions(+), 98 deletions(-)
create mode 100644 clang/test/AST/ast-dump-comment-retention.cpp
create mode 100644 clang/test/Sema/warn-documentation-comment-retention.cpp
diff --git a/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp b/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp
index 00290d7cdc74b..0b81a3cb20351 100644
--- a/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp
+++ b/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp
@@ -285,7 +285,8 @@ Example usage for a project using a compile commands database:
llvm::outs() << "Emiting docs in " << Format << " format.\n";
auto G = ExitOnErr(doc::findGeneratorByName(Format));
- ArgumentsAdjuster ArgAdjuster;
+ ArgumentsAdjuster ArgAdjuster = getInsertArgumentAdjuster(
+ "-fretain-comments", tooling::ArgumentInsertPosition::END);
if (!DoxygenOnly)
ArgAdjuster = combineAdjusters(
getInsertArgumentAdjuster("-fparse-all-comments",
diff --git a/clang-tools-extra/clangd/Compiler.cpp b/clang-tools-extra/clangd/Compiler.cpp
index 4644cd75c0833..aaeaab30b96db 100644
--- a/clang-tools-extra/clangd/Compiler.cpp
+++ b/clang-tools-extra/clangd/Compiler.cpp
@@ -121,7 +121,8 @@ buildCompilerInvocation(const ParseInputs &Inputs, clang::DiagnosticConsumer &D,
// createInvocationFromCommandLine sets DisableFree.
CI->getFrontendOpts().DisableFree = false;
CI->getLangOpts().CommentOpts.ParseAllComments = true;
- CI->getLangOpts().RetainCommentsFromSystemHeaders = true;
+ CI->getLangOpts().CommentOpts.RetainComments = true;
+ CI->getLangOpts().CommentOpts.RetainCommentsFromSystemHeaders = true;
disableUnsupportedOptions(*CI);
return CI;
diff --git a/clang-tools-extra/clangd/index/IndexAction.cpp b/clang-tools-extra/clangd/index/IndexAction.cpp
index 489c61f1ff424..21e055b82d722 100644
--- a/clang-tools-extra/clangd/index/IndexAction.cpp
+++ b/clang-tools-extra/clangd/index/IndexAction.cpp
@@ -167,7 +167,8 @@ class IndexAction : public ASTFrontendAction {
bool BeginInvocation(CompilerInstance &CI) override {
// We want all comments, not just the doxygen ones.
CI.getLangOpts().CommentOpts.ParseAllComments = true;
- CI.getLangOpts().RetainCommentsFromSystemHeaders = true;
+ CI.getLangOpts().CommentOpts.RetainComments = true;
+ CI.getLangOpts().CommentOpts.RetainCommentsFromSystemHeaders = true;
// Index the whole file even if there are warnings and -Werror is set.
// Avoids some analyses too. Set in two places as we're late to the party.
CI.getDiagnosticOpts().IgnoreWarnings = true;
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index bf295981710ac..9142fcddb363a 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -433,6 +433,12 @@ features cannot lower the translation-unit ABI level;
- Improved how Unicode characters are displayed in diagnostic messages.
+- Clang no longer retains source comments in the AST when nothing will read them
+ back. Comments are now collected only when they may be consumed (e.g. with
+ ``-fparse-all-comments``, when ``-Wdocumentation`` is enabled, when emitting a
+ PCH/module, or during code completion), reducing memory overhead for typical
+ compilations.
+
- `-Wtautological-pointer-compare` and `-Wpointer-bool-conversion` now
diagnose a reference to a function (e.g. of type `void (&)()`) compared
against or converted to a null pointer, the same as a bare function name.
diff --git a/clang/include/clang/Basic/CommentOptions.h b/clang/include/clang/Basic/CommentOptions.h
index 7d142fc32f511..73e7cba91cca8 100644
--- a/clang/include/clang/Basic/CommentOptions.h
+++ b/clang/include/clang/Basic/CommentOptions.h
@@ -30,6 +30,14 @@ struct CommentOptions {
/// Treat ordinary comments as documentation comments.
bool ParseAllComments = false;
+ /// Force the front end to retain all documentation comments in the AST, even
+ /// when no comment consuming diagnostic or language option is enabled. Tools
+ /// that query comments after parsing set this.
+ bool RetainComments = false;
+
+ /// Retain documentation comments from system headers in the AST.
+ bool RetainCommentsFromSystemHeaders = false;
+
CommentOptions() = default;
};
diff --git a/clang/include/clang/Basic/Diagnostic.h b/clang/include/clang/Basic/Diagnostic.h
index 834f026aff62d..699cd89791619 100644
--- a/clang/include/clang/Basic/Diagnostic.h
+++ b/clang/include/clang/Basic/Diagnostic.h
@@ -974,6 +974,16 @@ class DiagnosticsEngine : public RefCountedBase<DiagnosticsEngine> {
diag::Severity::Ignored;
}
+ bool areAllIgnored(StringRef Group, SourceLocation Loc) const {
+ llvm::SmallVector<diag::kind> diagsInGroup;
+ bool Failed = Diags->getDiagnosticsInGroup(diag::Flavor::WarningOrError,
+ Group, diagsInGroup);
+ assert(!Failed && "Incorrect group name?");
+ (void)Failed;
+ return Diags->getDiagnosticListHighestSeverity(diagsInGroup, Loc, *this) ==
+ diag::Severity::Ignored;
+ }
+
/// Based on the way the client configured the DiagnosticsEngine
/// object, classify the specified diagnostic ID into a Level, consumable by
/// the DiagnosticConsumer.
diff --git a/clang/include/clang/Basic/DiagnosticIDs.h b/clang/include/clang/Basic/DiagnosticIDs.h
index 148d772a9e593..1bb0529c3ff26 100644
--- a/clang/include/clang/Basic/DiagnosticIDs.h
+++ b/clang/include/clang/Basic/DiagnosticIDs.h
@@ -516,6 +516,12 @@ class DiagnosticIDs : public RefCountedBase<DiagnosticIDs> {
getDiagnosticSeverity(unsigned DiagID, SourceLocation Loc,
const DiagnosticsEngine &Diag) const LLVM_READONLY;
+ /// Given a collection of diagnostic IDs, get the 'highest' severity of them
+ /// at the provided location for this DiagnosticsEngine.
+ diag::Severity getDiagnosticListHighestSeverity(
+ llvm::ArrayRef<diag::kind> DiagIDs, SourceLocation Loc,
+ const DiagnosticsEngine &Diag) const LLVM_READONLY;
+
Class getDiagClass(unsigned DiagID) const;
/// Whether the diagnostic may leave the AST in a state where some
diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def
index ad993ce7e5d95..1422125b77741 100644
--- a/clang/include/clang/Basic/LangOptions.def
+++ b/clang/include/clang/Basic/LangOptions.def
@@ -405,8 +405,6 @@ LANGOPT(ApplePragmaPack, 1, 0, NotCompatible, "Apple gcc-compatible #pragma pack
LANGOPT(XLPragmaPack, 1, 0, NotCompatible, "IBM XL #pragma pack handling")
-LANGOPT(RetainCommentsFromSystemHeaders, 1, 0, Compatible, "retain documentation comments from system headers in the AST")
-
LANGOPT(APINotes, 1, 0, NotCompatible, "use external API notes")
LANGOPT(APINotesModules, 1, 0, NotCompatible, "use module-based external API notes")
LANGOPT(SwiftVersionIndependentAPINotes, 1, 0, NotCompatible, "use external API notes capturing all versions")
diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td
index 9ef012dfe1d03..f97a2a54e665d 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -2221,6 +2221,12 @@ defm define_target_os_macros : OptInCC1FFlag<"define-target-os-macros",
def fparse_all_comments : Flag<["-"], "fparse-all-comments">, Group<f_clang_Group>,
Visibility<[ClangOption, CC1Option]>,
MarshallingInfoFlag<LangOpts<"CommentOpts.ParseAllComments">>;
+def fretain_comments : Flag<["-"], "fretain-comments">, Group<f_clang_Group>,
+ Visibility<[ClangOption, CC1Option]>,
+ HelpText<"Retain documentation comments in the AST even when no diagnostic or "
+ "language option would otherwise require them (e.g. for tools that "
+ "query comments after parsing)">,
+ MarshallingInfoFlag<LangOpts<"CommentOpts.RetainComments">>;
def frecord_command_line : Flag<["-"], "frecord-command-line">,
DocBrief<[{Generate a section named ".GCC.command.line" containing the
driver command-line. After linking, the section may contain multiple command
@@ -3942,7 +3948,7 @@ defm implicit_modules : BoolFOption<"implicit-modules",
[NoXarchOption], [ClangOption, CLOption]>>;
def fretain_comments_from_system_headers : Flag<["-"], "fretain-comments-from-system-headers">, Group<f_Group>,
Visibility<[ClangOption, CC1Option]>,
- MarshallingInfoFlag<LangOpts<"RetainCommentsFromSystemHeaders">>;
+ MarshallingInfoFlag<LangOpts<"CommentOpts.RetainCommentsFromSystemHeaders">>;
def fmodule_header : Flag <["-"], "fmodule-header">, Group<f_Group>,
Visibility<[ClangOption, CLOption]>,
HelpText<"Build a C++20 Header Unit from a header">;
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 9b07b591b8c07..36fc50dcd426b 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -1126,6 +1126,11 @@ class Sema final : public SemaBase {
void ActOnComment(SourceRange Comment);
+ /// Returns true if a comment at \p Loc should be retained in the AST
+ /// (some consumer such as -Wdocumentation, -fparse-all-comments, code
+ /// completion, or AST-file serialization may read it back).
+ bool shouldRetainCommentsInAST(SourceLocation Loc) const;
+
/// Retrieve the parser's current scope.
///
/// This routine must only be used when it is certain that semantic analysis
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index b7e771595e86e..cf1c68d0d4443 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -357,7 +357,7 @@ RawComment *ASTContext::getRawCommentNoCache(RawCommentLookupKey Key) const {
}
void ASTContext::addComment(const RawComment &RC) {
- assert(LangOpts.RetainCommentsFromSystemHeaders ||
+ assert(LangOpts.CommentOpts.RetainCommentsFromSystemHeaders ||
!SourceMgr.isInSystemHeader(RC.getSourceRange().getBegin()));
Comments.addComment(RC, LangOpts.CommentOpts, BumpAlloc);
}
diff --git a/clang/lib/Basic/DiagnosticIDs.cpp b/clang/lib/Basic/DiagnosticIDs.cpp
index 3709528e497d2..ef9db935ca752 100644
--- a/clang/lib/Basic/DiagnosticIDs.cpp
+++ b/clang/lib/Basic/DiagnosticIDs.cpp
@@ -541,103 +541,131 @@ DiagnosticIDs::getDiagnosticLevel(unsigned DiagID, SourceLocation Loc,
diag::Severity
DiagnosticIDs::getDiagnosticSeverity(unsigned DiagID, SourceLocation Loc,
const DiagnosticsEngine &Diag) const {
- bool IsCustomDiag = DiagnosticIDs::IsCustomDiag(DiagID);
- assert(getDiagClass(DiagID) != CLASS_NOTE);
-
- // Specific non-error diagnostics may be mapped to various levels from ignored
- // to error. Errors can only be mapped to fatal.
- diag::Severity Result = diag::Severity::Fatal;
+ return getDiagnosticListHighestSeverity({DiagID}, Loc, Diag);
+}
- // Get the mapping information, or compute it lazily.
+diag::Severity DiagnosticIDs::getDiagnosticListHighestSeverity(
+ llvm::ArrayRef<diag::kind> DiagIDs, SourceLocation Loc,
+ const DiagnosticsEngine &Diag) const {
DiagnosticsEngine::DiagState *State = Diag.GetDiagStateForLoc(Loc);
- DiagnosticMapping Mapping = State->getOrAddMapping((diag::kind)DiagID);
-
- // TODO: Can a null severity really get here?
- if (Mapping.getSeverity() != diag::Severity())
- Result = Mapping.getSeverity();
-
- // Upgrade ignored diagnostics if -Weverything is enabled.
- if (State->EnableAllWarnings && Result == diag::Severity::Ignored &&
- !Mapping.isUser() &&
- (IsCustomDiag || getDiagClass(DiagID) != CLASS_REMARK))
- Result = diag::Severity::Warning;
-
- // Ignore -pedantic diagnostics inside __extension__ blocks.
- // (The diagnostics controlled by -pedantic are the extension diagnostics
- // that are not enabled by default.)
- bool EnabledByDefault = false;
- bool IsExtensionDiag = isExtensionDiag(DiagID, EnabledByDefault);
- if (Diag.AllExtensionsSilenced && IsExtensionDiag && !EnabledByDefault)
- return diag::Severity::Ignored;
-
- // For extension diagnostics that haven't been explicitly mapped, check if we
- // should upgrade the diagnostic. Skip if the user explicitly suppressed it
- // (e.g. -Wno-foo).
- if (IsExtensionDiag &&
- !(Mapping.isUser() && Result == diag::Severity::Ignored)) {
- if (Mapping.hasNoWarningAsError())
- Result = std::max(Result,
- std::min(State->ExtBehavior, diag::Severity::Warning));
- else
- Result = std::max(Result, State->ExtBehavior);
- }
-
- // At this point, ignored errors can no longer be upgraded.
- if (Result == diag::Severity::Ignored)
- return Result;
- // Honor -w: this disables all messages which are not Error/Fatal by
- // default (disregarding attempts to upgrade severity from Warning to Error),
- // as well as disabling all messages which are currently mapped to Warning
- // (whether by default or downgraded from Error via e.g. -Wno-error or #pragma
- // diagnostic.)
- // FIXME: Should -w be ignored for custom warnings without a group?
- if (State->IgnoreAllWarnings) {
- if ((!IsCustomDiag || CustomDiagInfo->getDescription(DiagID).GetGroup()) &&
- (Result == diag::Severity::Warning ||
- (Result >= diag::Severity::Error &&
- !isDefaultMappingAsError((diag::kind)DiagID))))
+ auto checkSingleDiag = [&](diag::kind DiagID) -> diag::Severity {
+ bool IsCustomDiag = DiagnosticIDs::IsCustomDiag(DiagID);
+ assert(getDiagClass(DiagID) != CLASS_NOTE);
+
+ // Specific non-error diagnostics may be mapped to various levels from
+ // ignored to error. Errors can only be mapped to fatal.
+ diag::Severity Result = diag::Severity::Fatal;
+
+ // Get the mapping information, or compute it lazily.
+ DiagnosticMapping Mapping = State->getOrAddMapping((diag::kind)DiagID);
+
+ // TODO: Can a null severity really get here?
+ if (Mapping.getSeverity() != diag::Severity())
+ Result = Mapping.getSeverity();
+
+ // Upgrade ignored diagnostics if -Weverything is enabled.
+ if (State->EnableAllWarnings && Result == diag::Severity::Ignored &&
+ !Mapping.isUser() &&
+ (IsCustomDiag || getDiagClass(DiagID) != CLASS_REMARK))
+ Result = diag::Severity::Warning;
+
+ // Ignore -pedantic diagnostics inside __extension__ blocks.
+ // (The diagnostics controlled by -pedantic are the extension diagnostics
+ // that are not enabled by default.)
+ bool EnabledByDefault = false;
+ bool IsExtensionDiag = isExtensionDiag(DiagID, EnabledByDefault);
+ if (Diag.AllExtensionsSilenced && IsExtensionDiag && !EnabledByDefault)
return diag::Severity::Ignored;
- }
- // If -Werror is enabled, map warnings to errors unless explicitly disabled.
- if (Result == diag::Severity::Warning) {
- if (State->WarningsAsErrors && !Mapping.hasNoWarningAsError())
+ // For extension diagnostics that haven't been explicitly mapped, check if
+ // we should upgrade the diagnostic. Skip if the user explicitly
+ // suppressed it (e.g. -Wno-foo).
+ if (IsExtensionDiag &&
+ !(Mapping.isUser() && Result == diag::Severity::Ignored)) {
+ if (Mapping.hasNoWarningAsError())
+ Result = std::max(
+ Result, std::min(State->ExtBehavior, diag::Severity::Warning));
+ else
+ Result = std::max(Result, State->ExtBehavior);
+ }
+
+ // At this point, ignored errors can no longer be upgraded.
+ if (Result == diag::Severity::Ignored)
+ return Result;
+
+ // Honor -w: this disables all messages which are not Error/Fatal by
+ // default (disregarding attempts to upgrade severity from Warning to
+ // Error), as well as disabling all messages which are currently mapped to
+ // Warning (whether by default or downgraded from Error via e.g.
+ // -Wno-error or #pragma diagnostic.)
+ // FIXME: Should -w be ignored for custom warnings without a group?
+ if (State->IgnoreAllWarnings) {
+ if ((!IsCustomDiag ||
+ CustomDiagInfo->getDescription(DiagID).GetGroup()) &&
+ (Result == diag::Severity::Warning ||
+ (Result >= diag::Severity::Error &&
+ !isDefaultMappingAsError((diag::kind)DiagID))))
+ return diag::Severity::Ignored;
+ }
+
+ // If -Werror is enabled, map warnings to errors unless explicitly
+ // disabled.
+ if (Result == diag::Severity::Warning) {
+ if (State->WarningsAsErrors && !Mapping.hasNoWarningAsError())
+ Result = diag::Severity::Error;
+ }
+
+ // If -Wfatal-errors is enabled, map errors to fatal unless explicitly
+ // disabled.
+ if (Result == diag::Severity::Error) {
+ if (State->ErrorsAsFatal && !Mapping.hasNoErrorAsFatal())
+ Result = diag::Severity::Fatal;
+ }
+
+ // If explicitly requested, map fatal errors to errors.
+ if (Result == diag::Severity::Fatal &&
+ DiagID != diag::fatal_too_many_errors && Diag.FatalsAsError)
Result = diag::Severity::Error;
- }
- // If -Wfatal-errors is enabled, map errors to fatal unless explicitly
- // disabled.
- if (Result == diag::Severity::Error) {
- if (State->ErrorsAsFatal && !Mapping.hasNoErrorAsFatal())
- Result = diag::Severity::Fatal;
- }
+ // Rest of the mappings are only applicable for diagnostics associated
+ // with a SourceLocation, bail out early for others.
+ if (!Diag.hasSourceManager())
+ return Result;
+
+ // We check both the location-specific state and the ForceSystemWarnings
+ // override. In some cases (like template instantiations from system
+ // modules), the location-specific state might have suppression enabled,
+ // but the engine might have an override (e.g.
+ // AllowWarningInSystemHeaders) to show the warning.
+ if (State->SuppressSystemWarnings && !Diag.getForceSystemWarnings() &&
+ shouldSuppressAsSystemWarning(DiagID, Loc, Diag)) {
+ return diag::Severity::Ignored;
+ }
- // If explicitly requested, map fatal errors to errors.
- if (Result == diag::Severity::Fatal &&
- DiagID != diag::fatal_too_many_errors && Diag.FatalsAsError)
- Result = diag::Severity::Error;
+ // Clang-diagnostics pragmas always take precedence over suppression
+ // mapping.
+ if (!Mapping.isPragma() && Diag.isSuppressedViaMapping(DiagID, Loc))
+ return diag::Severity::Ignored;
- // Rest of the mappings are only applicable for diagnostics associated with a
- // SourceLocation, bail out early for others.
- if (!Diag.hasSourceManager())
return Result;
+ };
- // We check both the location-specific state and the ForceSystemWarnings
- // override. In some cases (like template instantiations from system modules),
- // the location-specific state might have suppression enabled, but the
- // engine might have an override (e.g. AllowWarningInSystemHeaders) to show
- // the warning.
- if (State->SuppressSystemWarnings && !Diag.getForceSystemWarnings() &&
- shouldSuppressAsSystemWarning(DiagID, Loc, Diag)) {
- return diag::Severity::Ignored;
+ diag::Severity CompositeResult = diag::Severity::Ignored;
+ for (diag::kind DiagID : DiagIDs) {
+ CompositeResult = std::max(CompositeResult, checkSingleDiag(DiagID));
+
+ // If we already hit 'fatal', we can't get any higher! So just return that.
+ // We could potentially short-cut this by taking a parameter for "return
+ // first greater than", but since our uses of this are fairly small, and
+ // that only optimizes for the "we are about to do something expensive
+ // anyway" variant (that is, when everything is NOT ignored), it doesn't
+ // seem particularly valuable.
+ if (CompositeResult == diag::Severity::Fatal)
+ break;
}
- // Clang-diagnostics pragmas always take precedence over suppression mapping.
- if (!Mapping.isPragma() && Diag.isSuppressedViaMapping(DiagID, Loc))
- return diag::Severity::Ignored;
-
- return Result;
+ return CompositeResult;
}
bool DiagnosticIDs::shouldSuppressAsSystemWarning(
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index b3923c0c1e981..26978796a7e80 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -8165,6 +8165,8 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
Args.AddAllArgs(CmdArgs, options::OPT_fcomment_block_commands);
// Forward -fparse-all-comments to -cc1.
Args.AddAllArgs(CmdArgs, options::OPT_fparse_all_comments);
+ // Forward -fretain-comments to -cc1.
+ Args.AddAllArgs(CmdArgs, options::OPT_fretain_comments);
// Turn -fplugin=name.so into -load name.so
for (const Arg *A : Args.filtered(options::OPT_fplugin_EQ)) {
diff --git a/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp b/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp
index c81d76764643b..4e3c5466e577f 100644
--- a/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp
+++ b/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp
@@ -455,6 +455,9 @@ ExtractAPIAction::CreateASTConsumer(CompilerInstance &CI, StringRef InFile) {
}
bool ExtractAPIAction::PrepareToExecuteAction(CompilerInstance &CI) {
+ // ExtractAPI reads documentation comments off the AST.
+ CI.getLangOpts().CommentOpts.RetainComments = true;
+
// Public API can never be inside function bodies, so skip parsing them.
CI.getFrontendOpts().SkipFunctionBodies = true;
diff --git a/clang/lib/Frontend/ASTUnit.cpp b/clang/lib/Frontend/ASTUnit.cpp
index 75e4f7772f47c..738fe99cef46b 100644
--- a/clang/lib/Frontend/ASTUnit.cpp
+++ b/clang/lib/Frontend/ASTUnit.cpp
@@ -1533,6 +1533,9 @@ ASTUnit *ASTUnit::LoadFromCompilerInvocationAction(
// We'll manage file buffers ourselves.
CI->getPreprocessorOpts().RetainRemappedFileBuffers = true;
+ // libclang and other ASTUnit clients query documentation comments after
+ // parsing, so keep them in the AST.
+ CI->getLangOpts().CommentOpts.RetainComments = true;
CI->getFrontendOpts().DisableFree = false;
ProcessWarningOptions(AST->getDiagnostics(), CI->getDiagnosticOpts(),
AST->getFileManager().getVirtualFileSystem());
@@ -1641,6 +1644,9 @@ bool ASTUnit::LoadFromCompilerInvocation(
// We'll manage file buffers ourselves.
Invocation->getPreprocessorOpts().RetainRemappedFileBuffers = true;
+ // libclang and other ASTUnit clients query documentation comments after
+ // parsing, so keep them in the AST.
+ Invocation->getLangOpts().CommentOpts.RetainComments = true;
Invocation->getFrontendOpts().DisableFree = false;
getDiagnostics().Reset();
ProcessWarningOptions(getDiagnostics(), Invocation->getDiagnosticOpts(),
diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp
index ea8368908879a..a09976f0d441c 100644
--- a/clang/lib/Frontend/CompilerInvocation.cpp
+++ b/clang/lib/Frontend/CompilerInvocation.cpp
@@ -5298,6 +5298,7 @@ std::string CompilerInvocation::computeContextHash() const {
HBuilder.add(getLangOpts().ObjCRuntime);
HBuilder.addRange(getLangOpts().CommentOpts.BlockCommandNames);
+ HBuilder.add(getLangOpts().CommentOpts.RetainCommentsFromSystemHeaders);
// Extend the signature with the target options.
HBuilder.add(getTargetOpts().Triple, getTargetOpts().CPU,
diff --git a/clang/lib/Frontend/FrontendActions.cpp b/clang/lib/Frontend/FrontendActions.cpp
index e7b05740b8376..aaaf0b6401502 100644
--- a/clang/lib/Frontend/FrontendActions.cpp
+++ b/clang/lib/Frontend/FrontendActions.cpp
@@ -86,6 +86,8 @@ ASTPrintAction::CreateASTConsumer(CompilerInstance &CI, StringRef InFile) {
std::unique_ptr<ASTConsumer>
ASTDumpAction::CreateASTConsumer(CompilerInstance &CI, StringRef InFile) {
+ // Dumping the AST shows documentation comments.
+ CI.getLangOpts().CommentOpts.RetainComments = true;
const FrontendOptions &Opts = CI.getFrontendOpts();
return CreateASTDumper(nullptr /*Dump to stdout.*/, Opts.ASTDumpFilter,
Opts.ASTDumpDecls, Opts.ASTDumpAll,
@@ -250,6 +252,10 @@ GenerateModuleFromModuleMapAction::CreateOutputFile(CompilerInstance &CI,
bool GenerateModuleInterfaceAction::PrepareToExecuteAction(
CompilerInstance &CI) {
+ // Documentation comments must still be serialized into the BMI
+ // so importers can query them.
+ CI.getLangOpts().CommentOpts.RetainComments = true;
+
for (const auto &FIF : CI.getFrontendOpts().Inputs) {
if (const auto InputFormat = FIF.getKind().getFormat();
InputFormat != InputKind::Format::Source) {
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 21f71d7f8b40e..f933444c22adf 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -2749,10 +2749,39 @@ LambdaScopeInfo *Sema::getCurGenericLambda() {
return nullptr;
}
+bool Sema::shouldRetainCommentsInAST(SourceLocation Loc) const {
+ if (!LangOpts.CommentOpts.RetainCommentsFromSystemHeaders &&
+ SourceMgr.isInSystemHeader(Loc))
+ return false;
+
+ if (LangOpts.CommentOpts.ParseAllComments)
+ return true;
+
+ if (LangOpts.CommentOpts.RetainComments)
+ return true;
+
+ // When building a PCH the comments are serialized into the AST file
+ // so downstream consumers like clangd) can retrieve documentation, and the
+ // incremental/REPL front end may query them interactively.
+ if (TUKind != TU_Complete)
+ return true;
+
+ if (PP.isCodeCompletionEnabled())
+ return true;
+
+ // Keep the comment if any of the -Wdocumentation warnings is enabled at
+ // its location (checking the location handles warnings turned on by
+ // `#pragma clang diagnostic`). -Wdocumentation-pedantic is checked
+ // separately because it is not a subgroup of -Wdocumentation.
+ if (!Diags.areAllIgnored("documentation", Loc) ||
+ !Diags.areAllIgnored("documentation-pedantic", Loc))
+ return true;
+
+ return false;
+}
void Sema::ActOnComment(SourceRange Comment) {
- if (!LangOpts.RetainCommentsFromSystemHeaders &&
- SourceMgr.isInSystemHeader(Comment.getBegin()))
+ if (!shouldRetainCommentsInAST(Comment.getBegin()))
return;
RawComment RC(SourceMgr, Comment, LangOpts.CommentOpts, false);
if (RC.isAlmostTrailingComment() || RC.hasUnsupportedSplice(SourceMgr)) {
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index d6114ccbae7fe..8c29b30d7a8ac 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -30,7 +30,6 @@
#include "clang/AST/StmtCXX.h"
#include "clang/AST/Type.h"
#include "clang/Basic/Builtins.h"
-#include "clang/Basic/DiagnosticComment.h"
#include "clang/Basic/HLSLRuntime.h"
#include "clang/Basic/PartialDiagnostic.h"
#include "clang/Basic/SourceManager.h"
@@ -15731,10 +15730,8 @@ void Sema::ActOnDocumentableDecls(ArrayRef<Decl *> Group) {
if (Group.empty() || !Group[0])
return;
- if (Diags.isIgnored(diag::warn_doc_param_not_found,
- Group[0]->getLocation()) &&
- Diags.isIgnored(diag::warn_unknown_comment_command_name,
- Group[0]->getLocation()))
+ if (Diags.areAllIgnored("documentation", Group[0]->getLocation()) &&
+ Diags.areAllIgnored("documentation-pedantic", Group[0]->getLocation()))
return;
if (Group.size() >= 2) {
diff --git a/clang/test/AST/ast-dump-comment-retention.cpp b/clang/test/AST/ast-dump-comment-retention.cpp
new file mode 100644
index 0000000000000..e2a68e2f3221d
--- /dev/null
+++ b/clang/test/AST/ast-dump-comment-retention.cpp
@@ -0,0 +1,28 @@
+// Comments are only collected into the AST when a consumer may read them back
+// (see Sema::shouldRetainCommentsInAST). -ast-dump is such a consumer: it
+// force-enables comment retention, so documentation comments remain visible in
+// its output even without -Wdocumentation. An ordinary comment is only turned
+// into an AST comment node when -fparse-all-comments is passed.
+
+// RUN: %clang_cc1 -ast-dump -ast-dump-filter Test %s \
+// RUN: | FileCheck -strict-whitespace %s --check-prefixes=CHECK,DEFAULT
+// RUN: %clang_cc1 -fparse-all-comments -ast-dump -ast-dump-filter Test %s \
+// RUN: | FileCheck -strict-whitespace %s --check-prefixes=CHECK,ALL
+
+/// Doc
+int Test_DocComment;
+// A documentation comment is retained for -ast-dump in both modes.
+// CHECK: VarDecl{{.*}}Test_DocComment
+// CHECK-NEXT: FullComment
+// CHECK-NEXT: ParagraphComment
+// CHECK-NEXT: TextComment{{.*}} Text=" Doc"
+
+// Ordinary
+int Test_OrdinaryComment;
+// An ordinary comment becomes an AST comment node only with
+// -fparse-all-comments; by default it is dropped.
+// CHECK: VarDecl{{.*}}Test_OrdinaryComment
+// ALL-NEXT: FullComment
+// ALL-NEXT: ParagraphComment
+// ALL-NEXT: TextComment{{.*}} Text=" Ordinary"
+// DEFAULT-NOT: FullComment
diff --git a/clang/test/Sema/warn-documentation-comment-retention.cpp b/clang/test/Sema/warn-documentation-comment-retention.cpp
new file mode 100644
index 0000000000000..5d7a50b0bb970
--- /dev/null
+++ b/clang/test/Sema/warn-documentation-comment-retention.cpp
@@ -0,0 +1,38 @@
+// RUN: %clang_cc1 -fsyntax-only -verify %s
+
+// The comment-retention optimization (Sema::shouldRetainCommentsInAST) must
+// still parse a documentation comment when -Wdocumentation is enabled at the
+// comment's location -- including when it is turned on by a #pragma clang
+// diagnostic rather than on the command line. The check is done at the
+// comment's location precisely so pragma regions are honored.
+
+/// \returns Aaa
+void outside();
+// -Wdocumentation is off at this location, so the comment is not checked and
+// no diagnostic is produced. -verify fails on any unexpected diagnostic, so
+// this line asserts the comment is *not* diagnosed here.
+
+#pragma clang diagnostic push
+#pragma clang diagnostic warning "-Wdocumentation"
+/// \returns Aaa
+void inside();
+// expected-warning at -2 {{'\returns' command used in a comment that is attached to a function returning void}}
+#pragma clang diagnostic pop
+
+// Any warning in the -Wdocumentation group must keep the comment, not just a
+// hard-coded subset: -Wdocumentation-html is a subgroup of -Wdocumentation.
+#pragma clang diagnostic push
+#pragma clang diagnostic warning "-Wdocumentation-html"
+/// Aaa <br></br>
+void html_inside();
+// expected-warning at -2 {{HTML end tag 'br' is forbidden}}
+#pragma clang diagnostic pop
+
+// -Wdocumentation-unknown-command is under -Wdocumentation-pedantic, which is
+// not a subgroup of -Wdocumentation and must be checked separately.
+#pragma clang diagnostic push
+#pragma clang diagnostic warning "-Wdocumentation-unknown-command"
+/// \unknowncommand Aaa
+void unknown_inside();
+// expected-warning at -2 {{unknown command tag name}}
+#pragma clang diagnostic pop
>From 1f1eb5f99dcb69f4c0eec129b7a6415dbab26602 Mon Sep 17 00:00:00 2001
From: Anonmiraj <ezzibrahimx at gmail.com>
Date: Sat, 5 Sep 2026 09:15:58 +0300
Subject: [PATCH 2/5] [clang][NFC] Share the ForceSystemWarnings RAII and the
system classification
---
clang/include/clang/Basic/Diagnostic.h | 23 +++++++++++++++++++++++
clang/lib/Basic/Diagnostic.cpp | 7 +++++++
clang/lib/Sema/AnalysisBasedWarnings.cpp | 4 +---
clang/lib/Sema/SemaAvailability.cpp | 16 ++--------------
4 files changed, 33 insertions(+), 17 deletions(-)
diff --git a/clang/include/clang/Basic/Diagnostic.h b/clang/include/clang/Basic/Diagnostic.h
index 699cd89791619..2d3f48f602bed 100644
--- a/clang/include/clang/Basic/Diagnostic.h
+++ b/clang/include/clang/Basic/Diagnostic.h
@@ -589,6 +589,12 @@ class DiagnosticsEngine : public RefCountedBase<DiagnosticsEngine> {
return GetDiagStateForLoc(Loc);
}
+ /// Returns whether \p Loc is in a system header and/or a system macro, as a
+ /// value in [0, 4). Severity depends on this through
+ /// DiagnosticIDs::shouldSuppressAsSystemWarning(), so a cache keyed on
+ /// getDiagStateKeyForLoc() must take it into account as well.
+ unsigned getDiagStateSystemClassForLoc(SourceLocation Loc) const;
+
/// True if an active diagnostic suppression mapping makes severity dependent
/// on the file path.
bool hasDiagSuppressionMapping() const {
@@ -1150,6 +1156,23 @@ class IgnoreAllWarningDiagRAII {
~IgnoreAllWarningDiagRAII() { Diag.setIgnoreAllWarnings(OldValue); }
};
+/// RAII class that temporarily forces warnings in system headers and system
+/// macros to be shown on a DiagnosticsEngine and restores the previous state on
+/// destruction. Use it to ask what a diagnostic's severity would be if the
+/// location were not in a system header.
+class ForceSystemWarningsRAII {
+ DiagnosticsEngine &Diag;
+ bool OldValue;
+
+public:
+ explicit ForceSystemWarningsRAII(DiagnosticsEngine &Diag, bool Force = true)
+ : Diag(Diag), OldValue(Diag.getForceSystemWarnings()) {
+ if (Force)
+ Diag.setForceSystemWarnings(true);
+ }
+ ~ForceSystemWarningsRAII() { Diag.setForceSystemWarnings(OldValue); }
+};
+
/// The streaming interface shared between DiagnosticBuilder and
/// PartialDiagnostic. This class is not intended to be constructed directly
/// but only as base class of DiagnosticBuilder and PartialDiagnostic builder.
diff --git a/clang/lib/Basic/Diagnostic.cpp b/clang/lib/Basic/Diagnostic.cpp
index 48dd9559ab8e6..995fd6dff2254 100644
--- a/clang/lib/Basic/Diagnostic.cpp
+++ b/clang/lib/Basic/Diagnostic.cpp
@@ -596,6 +596,13 @@ bool WarningsSpecialCaseList::isDiagSuppressed(diag::kind DiagId,
return LastSup > LastEmit;
}
+unsigned
+DiagnosticsEngine::getDiagStateSystemClassForLoc(SourceLocation Loc) const {
+ const SourceManager &SM = getSourceManager();
+ return (SM.isInSystemHeader(SM.getExpansionLoc(Loc)) ? 2u : 0u) |
+ (SM.isInSystemMacro(Loc) ? 1u : 0u);
+}
+
bool DiagnosticsEngine::isSuppressedViaMapping(diag::kind DiagId,
SourceLocation DiagLoc) const {
if (!hasSourceManager() || !DiagSuppressionMapping)
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index d0500a6defd64..fb7ead718a4ac 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2802,9 +2802,7 @@ sema::AnalysisBasedWarnings::getPolicyInEffectAt(SourceLocation Loc) {
unsigned SysIdx = 0;
if (Cacheable) {
StateKey = D.getDiagStateKeyForLoc(Loc);
- const SourceManager &SM = D.getSourceManager();
- SysIdx = (SM.isInSystemHeader(SM.getExpansionLoc(Loc)) ? 2u : 0u) |
- (SM.isInSystemMacro(Loc) ? 1u : 0u);
+ SysIdx = D.getDiagStateSystemClassForLoc(Loc);
auto It = PolicyCache[SysIdx].find(StateKey);
if (It != PolicyCache[SysIdx].end()) {
Policy P = It->second;
diff --git a/clang/lib/Sema/SemaAvailability.cpp b/clang/lib/Sema/SemaAvailability.cpp
index 636168d13d5b8..25f0cb5f1af8f 100644
--- a/clang/lib/Sema/SemaAvailability.cpp
+++ b/clang/lib/Sema/SemaAvailability.cpp
@@ -669,20 +669,8 @@ static void DoEmitAvailabilityWarning(Sema &S, AvailabilityResult K,
bool ShouldAllowWarningInSystemHeader =
InstantiationLoc != Loc &&
!S.getSourceManager().isInSystemHeader(InstantiationLoc);
- struct AllowWarningInSystemHeaders {
- AllowWarningInSystemHeaders(DiagnosticsEngine &E,
- bool AllowWarningInSystemHeaders)
- : Engine(E), Prev(E.getForceSystemWarnings()) {
- if (AllowWarningInSystemHeaders)
- Engine.setForceSystemWarnings(true);
- }
- ~AllowWarningInSystemHeaders() { Engine.setForceSystemWarnings(Prev); }
-
- private:
- DiagnosticsEngine &Engine;
- bool Prev;
- } SystemWarningOverrideRAII(S.getDiagnostics(),
- ShouldAllowWarningInSystemHeader);
+ ForceSystemWarningsRAII SystemWarningOverrideRAII(
+ S.getDiagnostics(), ShouldAllowWarningInSystemHeader);
if (!Message.empty()) {
S.Diag(Loc, diag_message) << ReferringDecl << Message << FixIts;
>From e8f7f96262a312739a990d1747a332ba6f1859b3 Mon Sep 17 00:00:00 2001
From: Anonmiraj <ezzibrahimx at gmail.com>
Date: Sun, 6 Sep 2026 22:53:39 +0300
Subject: [PATCH 3/5] [clang] Cache whether the documentation warnings are
enabled
---
clang/include/clang/Sema/Sema.h | 26 +++++++-
clang/lib/Sema/Sema.cpp | 61 ++++++++++++++++---
clang/lib/Sema/SemaDecl.cpp | 3 +-
.../Inputs/documentation-system-header-doc.h | 2 +
.../Sema/Inputs/documentation-system-header.h | 2 +
...n-documentation-system-header-retained.cpp | 11 ++++
.../Sema/warn-documentation-system-header.cpp | 14 +++++
7 files changed, 106 insertions(+), 13 deletions(-)
create mode 100644 clang/test/Sema/Inputs/documentation-system-header-doc.h
create mode 100644 clang/test/Sema/Inputs/documentation-system-header.h
create mode 100644 clang/test/Sema/warn-documentation-system-header-retained.cpp
create mode 100644 clang/test/Sema/warn-documentation-system-header.cpp
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 36fc50dcd426b..ced5a30b5c377 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -1126,10 +1126,34 @@ class Sema final : public SemaBase {
void ActOnComment(SourceRange Comment);
+ /// Returns true if any of the documentation warnings is enabled at \p Loc.
+ bool areDocumentationDiagsEnabled(SourceLocation Loc);
+
+ /// Discard the areDocumentationDiagsEnabled() cache, for when a
+ /// `#pragma clang diagnostic` has changed diagnostic severities.
+ void clearDocumentationDiagsCache();
+
+private:
+ /// The uncached answer for both documentation groups at \p Loc.
+ bool computeDocumentationDiagsAt(SourceLocation Loc) const;
+
+ /// Caches results for areDocumentationDiagsEnabled().
+ /// Flushed whenever a diagnostic pragma changes severities.
+ /// Level one is keyed on the diagnostic state alone.
+ const void *DocDiagsStateKey = nullptr;
+ bool DocDiagsEnabledIgnoringSystem = false;
+
+ /// Level two, for when the location does matter. Bit i of each mask is a
+ /// getDiagStateSystemClassForLoc() value; bit 0 is unused.
+ uint8_t DocDiagsExactComputed = 0;
+ uint8_t DocDiagsExactEnabled = 0;
+
+public:
+
/// Returns true if a comment at \p Loc should be retained in the AST
/// (some consumer such as -Wdocumentation, -fparse-all-comments, code
/// completion, or AST-file serialization may read it back).
- bool shouldRetainCommentsInAST(SourceLocation Loc) const;
+ bool shouldRetainCommentsInAST(SourceLocation Loc);
/// Retrieve the parser's current scope.
///
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index f933444c22adf..586b7acd1ba10 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -227,9 +227,10 @@ class SemaPPCallbacks : public PPCallbacks {
}
void PragmaDiagnostic(SourceLocation Loc, StringRef Namespace,
diag::Severity Mapping, StringRef Str) override {
- // The pragma changed diagnostic severities; drop any cached analysis
- // warning policies derived from the previous state.
+ // The pragma changed diagnostic severities; drop any cached state
+ // derived from the previous one.
S->AnalysisWarnings.clearPolicyCache();
+ S->clearDocumentationDiagsCache();
// If one of the analysis-based diagnostics was enabled while processing
// a function, we want to note it in the analysis-based warnings so they
@@ -2749,7 +2750,7 @@ LambdaScopeInfo *Sema::getCurGenericLambda() {
return nullptr;
}
-bool Sema::shouldRetainCommentsInAST(SourceLocation Loc) const {
+bool Sema::shouldRetainCommentsInAST(SourceLocation Loc) {
if (!LangOpts.CommentOpts.RetainCommentsFromSystemHeaders &&
SourceMgr.isInSystemHeader(Loc))
return false;
@@ -2769,15 +2770,55 @@ bool Sema::shouldRetainCommentsInAST(SourceLocation Loc) const {
if (PP.isCodeCompletionEnabled())
return true;
- // Keep the comment if any of the -Wdocumentation warnings is enabled at
- // its location (checking the location handles warnings turned on by
- // `#pragma clang diagnostic`). -Wdocumentation-pedantic is checked
- // separately because it is not a subgroup of -Wdocumentation.
- if (!Diags.areAllIgnored("documentation", Loc) ||
- !Diags.areAllIgnored("documentation-pedantic", Loc))
+ // Keep the comment if a documentation warning is enabled at its location.
+ // Checking the location, rather than globally, is what makes a warning
+ // turned on by a `#pragma clang diagnostic` take effect.
+ return areDocumentationDiagsEnabled(Loc);
+}
+
+bool Sema::computeDocumentationDiagsAt(SourceLocation Loc) const {
+ return !Diags.areAllIgnored("documentation", Loc) ||
+ !Diags.areAllIgnored("documentation-pedantic", Loc);
+}
+
+void Sema::clearDocumentationDiagsCache() {
+ DocDiagsStateKey = nullptr;
+ DocDiagsExactComputed = 0;
+}
+
+bool Sema::areDocumentationDiagsEnabled(SourceLocation Loc) {
+ // Under a suppression mapping the severity depends on the file path rather
+ // than the diagnostic state, so there is nothing stable to key a cache on.
+ if (Loc.isInvalid() || Diags.hasDiagSuppressionMapping())
+ return computeDocumentationDiagsAt(Loc);
+
+ const void *StateKey = Diags.getDiagStateKeyForLoc(Loc);
+ if (StateKey != DocDiagsStateKey) {
+ DocDiagsStateKey = StateKey;
+ {
+ // Answer as if Loc were not in a system header.
+ ForceSystemWarningsRAII ShowSystemWarnings(Diags);
+ DocDiagsEnabledIgnoringSystem = computeDocumentationDiagsAt(Loc);
+ }
+ DocDiagsExactComputed = 0;
+ }
+
+ if (!DocDiagsEnabledIgnoringSystem)
+ return false;
+
+ unsigned SysIdx = Diags.getDiagStateSystemClassForLoc(Loc);
+ if (SysIdx == 0)
return true;
- return false;
+ const unsigned Bit = 1u << SysIdx;
+ if (!(DocDiagsExactComputed & Bit)) {
+ DocDiagsExactComputed |= Bit;
+ if (computeDocumentationDiagsAt(Loc))
+ DocDiagsExactEnabled |= Bit;
+ else
+ DocDiagsExactEnabled &= ~Bit;
+ }
+ return (DocDiagsExactEnabled & Bit) != 0;
}
void Sema::ActOnComment(SourceRange Comment) {
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 8c29b30d7a8ac..d9512b82a198a 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -15730,8 +15730,7 @@ void Sema::ActOnDocumentableDecls(ArrayRef<Decl *> Group) {
if (Group.empty() || !Group[0])
return;
- if (Diags.areAllIgnored("documentation", Group[0]->getLocation()) &&
- Diags.areAllIgnored("documentation-pedantic", Group[0]->getLocation()))
+ if (!areDocumentationDiagsEnabled(Group[0]->getLocation()))
return;
if (Group.size() >= 2) {
diff --git a/clang/test/Sema/Inputs/documentation-system-header-doc.h b/clang/test/Sema/Inputs/documentation-system-header-doc.h
new file mode 100644
index 0000000000000..9cbb4493ede07
--- /dev/null
+++ b/clang/test/Sema/Inputs/documentation-system-header-doc.h
@@ -0,0 +1,2 @@
+/// \returns Aaa
+void system_documented();
diff --git a/clang/test/Sema/Inputs/documentation-system-header.h b/clang/test/Sema/Inputs/documentation-system-header.h
new file mode 100644
index 0000000000000..b41a2c37bcb48
--- /dev/null
+++ b/clang/test/Sema/Inputs/documentation-system-header.h
@@ -0,0 +1,2 @@
+void system_decl_one();
+void system_decl_two();
diff --git a/clang/test/Sema/warn-documentation-system-header-retained.cpp b/clang/test/Sema/warn-documentation-system-header-retained.cpp
new file mode 100644
index 0000000000000..93b84f978d8bb
--- /dev/null
+++ b/clang/test/Sema/warn-documentation-system-header-retained.cpp
@@ -0,0 +1,11 @@
+// RUN: %clang_cc1 -fsyntax-only -Wdocumentation -Wsystem-headers \
+// RUN: -fretain-comments-from-system-headers -isystem %S/Inputs \
+// RUN: -include documentation-system-header-doc.h %s 2>&1 | FileCheck %s
+
+// Comments in a system header normally go unchecked, because the warnings are
+// off there. -Wsystem-headers turns them back on, and then the comment does
+// have to be checked -- so the answer cannot be hardcoded for system headers.
+
+// CHECK: documentation-system-header-doc.h:1:6: warning: '\returns' command used in a comment that is attached to a function returning void
+
+void user_fn();
diff --git a/clang/test/Sema/warn-documentation-system-header.cpp b/clang/test/Sema/warn-documentation-system-header.cpp
new file mode 100644
index 0000000000000..3a5b7fd43c9e4
--- /dev/null
+++ b/clang/test/Sema/warn-documentation-system-header.cpp
@@ -0,0 +1,14 @@
+// RUN: %clang_cc1 -fsyntax-only -Wdocumentation -isystem %S/Inputs \
+// RUN: -include documentation-system-header.h -verify %s
+
+// Clang only collects a documentation comment if a warning would read it, and
+// it remembers that answer. Inside a system header the answer is 'no', so a
+// declaration from one must not leave that 'no' behind for the user's own code
+// -- if it does, the comment below is dropped and nothing warns about it.
+//
+// The header is force-included so its declarations are seen first. A comment
+// read from this file first would record the right answer and hide the bug.
+
+/// \returns Aaa
+void user_fn();
+// expected-warning at -2 {{'\returns' command used in a comment that is attached to a function returning void}}
>From 9f506c7e7400c9bcb5db23f3d36adebd6a866915 Mon Sep 17 00:00:00 2001
From: Anonmiraj <ezzibrahimx at gmail.com>
Date: Sun, 6 Sep 2026 23:11:33 +0300
Subject: [PATCH 4/5] fix format
---
clang/include/clang/Sema/Sema.h | 1 -
1 file changed, 1 deletion(-)
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index ced5a30b5c377..928dbcd3ac589 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -1149,7 +1149,6 @@ class Sema final : public SemaBase {
uint8_t DocDiagsExactEnabled = 0;
public:
-
/// Returns true if a comment at \p Loc should be retained in the AST
/// (some consumer such as -Wdocumentation, -fparse-all-comments, code
/// completion, or AST-file serialization may read it back).
>From ea669a715217f64f310a2113eb63f5d7455ddf62 Mon Sep 17 00:00:00 2001
From: Anonmiraj <ezzibrahimx at gmail.com>
Date: Sun, 13 Sep 2026 13:56:06 +0300
Subject: [PATCH 5/5] use enum for diagstatesystemclass
---
clang/include/clang/Basic/Diagnostic.h | 15 ++++++++++++---
clang/include/clang/Sema/AnalysisBasedWarnings.h | 3 ++-
clang/include/clang/Sema/Sema.h | 2 +-
clang/lib/Basic/Diagnostic.cpp | 10 +++++++---
clang/lib/Sema/AnalysisBasedWarnings.cpp | 2 +-
clang/lib/Sema/Sema.cpp | 6 +++---
6 files changed, 26 insertions(+), 12 deletions(-)
diff --git a/clang/include/clang/Basic/Diagnostic.h b/clang/include/clang/Basic/Diagnostic.h
index 2d3f48f602bed..01d497ca8294f 100644
--- a/clang/include/clang/Basic/Diagnostic.h
+++ b/clang/include/clang/Basic/Diagnostic.h
@@ -225,6 +225,15 @@ class DiagStorageAllocator {
}
};
+/// Whether a source location is in a system header and/or a system macro.
+enum class DiagStateSystemClass : unsigned {
+ UserCode = 0,
+ SystemMacro = 1 << 0,
+ SystemHeader = 1 << 1,
+ SystemHeaderAndMacro = SystemHeader | SystemMacro,
+ NUM_CLASSES
+};
+
/// Concrete class used by the front-end to report problems and issues.
///
/// This massages the diagnostics (e.g. handling things like "report warnings
@@ -589,11 +598,11 @@ class DiagnosticsEngine : public RefCountedBase<DiagnosticsEngine> {
return GetDiagStateForLoc(Loc);
}
- /// Returns whether \p Loc is in a system header and/or a system macro, as a
- /// value in [0, 4). Severity depends on this through
+ /// Returns whether \p Loc is in a system header and/or a system macro.
+ /// Severity depends on this through
/// DiagnosticIDs::shouldSuppressAsSystemWarning(), so a cache keyed on
/// getDiagStateKeyForLoc() must take it into account as well.
- unsigned getDiagStateSystemClassForLoc(SourceLocation Loc) const;
+ DiagStateSystemClass getDiagStateSystemClassForLoc(SourceLocation Loc) const;
/// True if an active diagnostic suppression mapping makes severity dependent
/// on the file path.
diff --git a/clang/include/clang/Sema/AnalysisBasedWarnings.h b/clang/include/clang/Sema/AnalysisBasedWarnings.h
index f8bd867062b47..ea54f7e5ee8aa 100644
--- a/clang/include/clang/Sema/AnalysisBasedWarnings.h
+++ b/clang/include/clang/Sema/AnalysisBasedWarnings.h
@@ -68,7 +68,8 @@ class AnalysisBasedWarnings {
/// Caches results for getPolicyInEffectAt().
/// Flushed whenever a diagnostic pragma changes severities.
- llvm::DenseMap<const void *, Policy> PolicyCache[4];
+ llvm::DenseMap<const void *, Policy>
+ PolicyCache[static_cast<unsigned>(DiagStateSystemClass::NUM_CLASSES)];
/// \name Statistics
/// @{
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 574784a8052be..d5fc911922059 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -1144,7 +1144,7 @@ class Sema final : public SemaBase {
bool DocDiagsEnabledIgnoringSystem = false;
/// Level two, for when the location does matter. Bit i of each mask is a
- /// getDiagStateSystemClassForLoc() value; bit 0 is unused.
+ /// DiagStateSystemClass value; bit 0 is unused.
uint8_t DocDiagsExactComputed = 0;
uint8_t DocDiagsExactEnabled = 0;
diff --git a/clang/lib/Basic/Diagnostic.cpp b/clang/lib/Basic/Diagnostic.cpp
index 995fd6dff2254..e32e8776c1b88 100644
--- a/clang/lib/Basic/Diagnostic.cpp
+++ b/clang/lib/Basic/Diagnostic.cpp
@@ -596,11 +596,15 @@ bool WarningsSpecialCaseList::isDiagSuppressed(diag::kind DiagId,
return LastSup > LastEmit;
}
-unsigned
+DiagStateSystemClass
DiagnosticsEngine::getDiagStateSystemClassForLoc(SourceLocation Loc) const {
const SourceManager &SM = getSourceManager();
- return (SM.isInSystemHeader(SM.getExpansionLoc(Loc)) ? 2u : 0u) |
- (SM.isInSystemMacro(Loc) ? 1u : 0u);
+ unsigned Class = 0;
+ if (SM.isInSystemHeader(SM.getExpansionLoc(Loc)))
+ Class |= static_cast<unsigned>(DiagStateSystemClass::SystemHeader);
+ if (SM.isInSystemMacro(Loc))
+ Class |= static_cast<unsigned>(DiagStateSystemClass::SystemMacro);
+ return static_cast<DiagStateSystemClass>(Class);
}
bool DiagnosticsEngine::isSuppressedViaMapping(diag::kind DiagId,
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index fb7ead718a4ac..99424c0e68b84 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2802,7 +2802,7 @@ sema::AnalysisBasedWarnings::getPolicyInEffectAt(SourceLocation Loc) {
unsigned SysIdx = 0;
if (Cacheable) {
StateKey = D.getDiagStateKeyForLoc(Loc);
- SysIdx = D.getDiagStateSystemClassForLoc(Loc);
+ SysIdx = static_cast<unsigned>(D.getDiagStateSystemClassForLoc(Loc));
auto It = PolicyCache[SysIdx].find(StateKey);
if (It != PolicyCache[SysIdx].end()) {
Policy P = It->second;
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 586b7acd1ba10..29158cfff6231 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -2806,11 +2806,11 @@ bool Sema::areDocumentationDiagsEnabled(SourceLocation Loc) {
if (!DocDiagsEnabledIgnoringSystem)
return false;
- unsigned SysIdx = Diags.getDiagStateSystemClassForLoc(Loc);
- if (SysIdx == 0)
+ DiagStateSystemClass SysClass = Diags.getDiagStateSystemClassForLoc(Loc);
+ if (SysClass == DiagStateSystemClass::UserCode)
return true;
- const unsigned Bit = 1u << SysIdx;
+ const unsigned Bit = 1u << static_cast<unsigned>(SysClass);
if (!(DocDiagsExactComputed & Bit)) {
DocDiagsExactComputed |= Bit;
if (computeDocumentationDiagsAt(Loc))
More information about the cfe-commits
mailing list