[clang] [clang-format] Allow one indent level before explicit access labels (PR #227704)

via cfe-commits cfe-commits at lists.llvm.org
Fri Oct 2 15:03:43 PDT 2026


https://github.com/noambouillet updated https://github.com/llvm/llvm-project/pull/227704

>From f55190042b1ba98c2946eb94e468cc4899718a38 Mon Sep 17 00:00:00 2001
From: Noam Bouillet <noam.bouillet at epitech.eu>
Date: Wed, 30 Sep 2026 14:07:44 +0200
Subject: [PATCH 1/5] feat(format): make implicit access indent optional

Preserve existing output by default while allowing records without an explicit access label to use one indentation level.

Refs #61631
---
 clang/docs/ClangFormatStyleOptions.md      | 17 +++++--
 clang/docs/ReleaseNotes.md                 |  4 ++
 clang/include/clang/Format/Format.h        | 16 +++++--
 clang/lib/Format/Format.cpp                |  3 ++
 clang/lib/Format/UnwrappedLineParser.cpp   | 47 ++++++++++++++-----
 clang/lib/Format/UnwrappedLineParser.h     |  6 ++-
 clang/unittests/Format/ConfigParseTest.cpp |  1 +
 clang/unittests/Format/FormatTest.cpp      | 53 ++++++++++++++++++++++
 clang/unittests/Format/FormatTestJava.cpp  | 12 +++++
 9 files changed, 140 insertions(+), 19 deletions(-)

diff --git a/clang/docs/ClangFormatStyleOptions.md b/clang/docs/ClangFormatStyleOptions.md
index 81984ff185e53..d831bcac07c9c 100644
--- a/clang/docs/ClangFormatStyleOptions.md
+++ b/clang/docs/ClangFormatStyleOptions.md
@@ -4752,9 +4752,10 @@ the configuration (without a prefix: `Auto`).
   the record members, respecting the `AccessModifierOffset`. Record
   members are indented one level below the record.
   When `true`, access modifiers get their own indentation level. As a
-  consequence, record members are always indented 2 levels below the record,
-  regardless of the access modifier presence. Value of the
-  `AccessModifierOffset` is ignored.
+  consequence, record members are by default indented 2 levels below the
+  record, regardless of the access modifier presence. Value of the
+  `AccessModifierOffset` is ignored. `IndentImplicitAccessModifiers` can
+  change the indentation before the first explicit access modifier.
 
   ```c++
   false:                                 true:
@@ -4952,6 +4953,16 @@ the configuration (without a prefix: `Auto`).
 
 
 
+(indentimplicitaccessmodifiers)=
+
+**IndentImplicitAccessModifiers** (`Boolean`) {versionbadge}`clang-format 24` {ref}`¶ <IndentImplicitAccessModifiers>`
+
+: When `IndentAccessModifiers` is `true`, indent members before the first
+  explicit access modifier by two levels. Set this option to `false` to
+  indent those members by one level. Members after an explicit access
+  modifier still use two levels. This option has no effect if
+  `IndentAccessModifiers` is false.
+
 (indentppdirectives)=
 
 **IndentPPDirectives** (`PPDirectiveIndentStyle`) {versionbadge}`clang-format 6` {ref}`¶ <IndentPPDirectives>`
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index c778703e8cc6f..a2090bb98df30 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -944,6 +944,10 @@ features cannot lower the translation-unit ABI level;
 
 ### clang-format
 
+- Add `IndentImplicitAccessModifiers` to allow members before the first
+  explicit access modifier to use one indentation level when
+  `IndentAccessModifiers` is enabled. The default preserves existing formatting.
+
 - Add `SpacesInBlockComments` option to control spacing after `/*` and
   before `*/` in ordinary block comments.
 - Add `AfterRequiresExpression` sub-option of `BraceWrapping` to wrap the
diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h
index 6d4fa6e8ee2a7..40ae52a0148e8 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -3187,9 +3187,10 @@ struct FormatStyle {
   /// the record members, respecting the `AccessModifierOffset`. Record
   /// members are indented one level below the record.
   /// When `true`, access modifiers get their own indentation level. As a
-  /// consequence, record members are always indented 2 levels below the record,
-  /// regardless of the access modifier presence. Value of the
-  /// `AccessModifierOffset` is ignored.
+  /// consequence, record members are by default indented 2 levels below the
+  /// record, regardless of the access modifier presence. Value of the
+  /// `AccessModifierOffset` is ignored. `IndentImplicitAccessModifiers` can
+  /// change the indentation before the first explicit access modifier.
   /// \code
   ///    false:                                 true:
   ///    class C {                      vs.     class C {
@@ -3208,6 +3209,14 @@ struct FormatStyle {
   /// \version 13
   bool IndentAccessModifiers;
 
+  /// When `IndentAccessModifiers` is `true`, indent members before the first
+  /// explicit access modifier by two levels. Set this option to `false` to
+  /// indent those members by one level. Members after an explicit access
+  /// modifier still use two levels. This option has no effect if
+  /// `IndentAccessModifiers` is false.
+  /// \version 24
+  bool IndentImplicitAccessModifiers;
+
   /// Indent case label blocks one level from the case label.
   ///
   /// When `false`, the block following the case label uses the same
@@ -6242,6 +6251,7 @@ struct FormatStyle {
                R.IncludeStyle.IncludeIsMainSourceRegex &&
            IncludeStyle.MainIncludeChar == R.IncludeStyle.MainIncludeChar &&
            IndentAccessModifiers == R.IndentAccessModifiers &&
+           IndentImplicitAccessModifiers == R.IndentImplicitAccessModifiers &&
            IndentCaseBlocks == R.IndentCaseBlocks &&
            IndentCaseLabels == R.IndentCaseLabels &&
            IndentExportBlock == R.IndentExportBlock &&
diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp
index 4c78c1dbe9f80..2f6d70d2bf4e2 100644
--- a/clang/lib/Format/Format.cpp
+++ b/clang/lib/Format/Format.cpp
@@ -1399,6 +1399,8 @@ template <> struct MappingTraits<FormatStyle> {
     IO.mapOptional("IncludeIsMainSourceRegex",
                    Style.IncludeStyle.IncludeIsMainSourceRegex);
     IO.mapOptional("IndentAccessModifiers", Style.IndentAccessModifiers);
+    IO.mapOptional("IndentImplicitAccessModifiers",
+                   Style.IndentImplicitAccessModifiers);
     IO.mapOptional("IndentCaseBlocks", Style.IndentCaseBlocks);
     IO.mapOptional("IndentCaseLabels", Style.IndentCaseLabels);
     IO.mapOptional("IndentExportBlock", Style.IndentExportBlock);
@@ -1977,6 +1979,7 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) {
   LLVMStyle.IncludeStyle.IncludeIsMainRegex = "(Test)?$";
   LLVMStyle.IncludeStyle.MainIncludeChar = tooling::IncludeStyle::MICD_Quote;
   LLVMStyle.IndentAccessModifiers = false;
+  LLVMStyle.IndentImplicitAccessModifiers = true;
   LLVMStyle.IndentCaseBlocks = false;
   LLVMStyle.IndentCaseLabels = false;
   LLVMStyle.IndentExportBlock = true;
diff --git a/clang/lib/Format/UnwrappedLineParser.cpp b/clang/lib/Format/UnwrappedLineParser.cpp
index 4825e825af1fa..0e0825112b513 100644
--- a/clang/lib/Format/UnwrappedLineParser.cpp
+++ b/clang/lib/Format/UnwrappedLineParser.cpp
@@ -352,7 +352,8 @@ bool UnwrappedLineParser::precededByCommentOrPPDirective() const {
 /// (A simple block has a single statement.)
 bool UnwrappedLineParser::parseLevel(const FormatToken *OpeningBrace,
                                      IfStmtKind *IfKind,
-                                     FormatToken **IfLeftBrace) {
+                                     FormatToken **IfLeftBrace,
+                                     bool *SeenExplicitAccessModifier) {
   const bool InRequiresExpression =
       OpeningBrace && OpeningBrace->is(TT_RequiresExpressionLBrace);
   const bool IsPrecededByCommentOrPPDirective =
@@ -377,7 +378,18 @@ bool UnwrappedLineParser::parseLevel(const FormatToken *OpeningBrace,
       Kind = tok::r_brace;
 
     auto ParseDefault = [this, OpeningBrace, IfKind, &IfLBrace, &HasDoWhile,
-                         &HasLabel, &StatementCount] {
+                         &HasLabel, &StatementCount,
+                         SeenExplicitAccessModifier] {
+      const bool IsQtAccessLabel =
+          SeenExplicitAccessModifier && !*SeenExplicitAccessModifier &&
+          FormatTok->isOneOf(Keywords.kw_signals, Keywords.kw_qsignals,
+                             Keywords.kw_slots, Keywords.kw_qslots) &&
+          Tokens->peekNextToken(/*SkipComment=*/true)->is(tok::colon);
+      if (SeenExplicitAccessModifier && !*SeenExplicitAccessModifier &&
+          (FormatTok->isAccessSpecifierKeyword() || IsQtAccessLabel)) {
+        ++Line->Level;
+        *SeenExplicitAccessModifier = true;
+      }
       parseStructuralElement(OpeningBrace, IfKind, &IfLBrace,
                              HasDoWhile ? nullptr : &HasDoWhile,
                              HasLabel ? nullptr : &HasLabel);
@@ -744,11 +756,10 @@ bool UnwrappedLineParser::mightFitOnOneLine(
   return Line.Level * Style.IndentWidth + Length <= ColumnLimit;
 }
 
-FormatToken *UnwrappedLineParser::parseBlock(bool MustBeDeclaration,
-                                             unsigned AddLevels, bool MunchSemi,
-                                             bool KeepBraces,
-                                             IfStmtKind *IfKind,
-                                             bool UnindentWhitesmithsBraces) {
+FormatToken *UnwrappedLineParser::parseBlock(
+    bool MustBeDeclaration, unsigned AddLevels, bool MunchSemi, bool KeepBraces,
+    IfStmtKind *IfKind, bool UnindentWhitesmithsBraces,
+    bool IndentAfterExplicitAccessModifier) {
   auto HandleVerilogBlockLabel = [this]() {
     // ":" name
     if (Style.isVerilog() && FormatTok->is(tok::colon)) {
@@ -820,7 +831,11 @@ FormatToken *UnwrappedLineParser::parseBlock(bool MustBeDeclaration,
     Line->Level += AddLevels - (IsWhitesmiths ? 1 : 0);
 
   FormatToken *IfLBrace = nullptr;
-  const bool SimpleBlock = parseLevel(Tok, IfKind, &IfLBrace);
+  bool SeenExplicitAccessModifier = false;
+  const bool SimpleBlock =
+      parseLevel(Tok, IfKind, &IfLBrace,
+                 IndentAfterExplicitAccessModifier ? &SeenExplicitAccessModifier
+                                                   : nullptr);
 
   if (eof())
     return IfLBrace;
@@ -879,7 +894,8 @@ FormatToken *UnwrappedLineParser::parseBlock(bool MustBeDeclaration,
   size_t PPEndHash = computePPHash();
 
   // Munch the closing brace.
-  nextToken(/*LevelDifference=*/-AddLevels);
+  nextToken(/*LevelDifference=*/
+            -static_cast<int>(AddLevels + SeenExplicitAccessModifier));
 
   // When this is a function block and there is an unnecessary semicolon
   // afterwards then mark it as optional (so the RemoveSemi pass can get rid of
@@ -4298,8 +4314,17 @@ void UnwrappedLineParser::parseRecord(bool ParseAsExpr, bool IsJavaRecord) {
         addUnwrappedLine();
       }
 
-      unsigned AddLevels = Style.IndentAccessModifiers ? 2u : 1u;
-      parseBlock(/*MustBeDeclaration=*/true, AddLevels, /*MunchSemi=*/false);
+      const bool IndentAfterExplicitAccessModifier =
+          Style.isCpp() && Style.IndentAccessModifiers &&
+          !Style.IndentImplicitAccessModifiers;
+      unsigned AddLevels =
+          Style.IndentAccessModifiers && !IndentAfterExplicitAccessModifier
+              ? 2u
+              : 1u;
+      parseBlock(/*MustBeDeclaration=*/true, AddLevels, /*MunchSemi=*/false,
+                 /*KeepBraces=*/true, /*IfKind=*/nullptr,
+                 /*UnindentWhitesmithsBraces=*/false,
+                 IndentAfterExplicitAccessModifier);
     }
     setPreviousRBraceType(ClosingBraceType);
   }
diff --git a/clang/lib/Format/UnwrappedLineParser.h b/clang/lib/Format/UnwrappedLineParser.h
index 5b93c8f346d75..2e1755ad3b2c6 100644
--- a/clang/lib/Format/UnwrappedLineParser.h
+++ b/clang/lib/Format/UnwrappedLineParser.h
@@ -126,13 +126,15 @@ class UnwrappedLineParser {
   bool precededByCommentOrPPDirective() const;
   bool parseLevel(const FormatToken *OpeningBrace = nullptr,
                   IfStmtKind *IfKind = nullptr,
-                  FormatToken **IfLeftBrace = nullptr);
+                  FormatToken **IfLeftBrace = nullptr,
+                  bool *SeenExplicitAccessModifier = nullptr);
   bool mightFitOnOneLine(UnwrappedLine &Line,
                          const FormatToken *OpeningBrace = nullptr) const;
   FormatToken *parseBlock(bool MustBeDeclaration = false,
                           unsigned AddLevels = 1u, bool MunchSemi = true,
                           bool KeepBraces = true, IfStmtKind *IfKind = nullptr,
-                          bool UnindentWhitesmithsBraces = false);
+                          bool UnindentWhitesmithsBraces = false,
+                          bool IndentAfterExplicitAccessModifier = false);
   void parseChildBlock();
   void parsePPDirective();
   void parsePPDefine();
diff --git a/clang/unittests/Format/ConfigParseTest.cpp b/clang/unittests/Format/ConfigParseTest.cpp
index 86511edb9d40d..ccbc346901460 100644
--- a/clang/unittests/Format/ConfigParseTest.cpp
+++ b/clang/unittests/Format/ConfigParseTest.cpp
@@ -190,6 +190,7 @@ TEST(ConfigParseTest, ParsesConfigurationBools) {
   CHECK_PARSE_BOOL_FIELD(DerivePointerAlignment, "DerivePointerBinding");
   CHECK_PARSE_BOOL(DisableFormat);
   CHECK_PARSE_BOOL(IndentAccessModifiers);
+  CHECK_PARSE_BOOL(IndentImplicitAccessModifiers);
   CHECK_PARSE_BOOL(IndentCaseBlocks);
   CHECK_PARSE_BOOL(IndentCaseLabels);
   CHECK_PARSE_BOOL(IndentExportBlock);
diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp
index bb630da34d7d9..656a352ecb005 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -24920,6 +24920,59 @@ TEST_F(FormatTest, IndentAccessModifiers) {
                Style);
 }
 
+TEST_F(FormatTest, IndentImplicitAccessModifiers) {
+  FormatStyle Style = getLLVMStyle();
+  Style.IndentAccessModifiers = true;
+  Style.IndentImplicitAccessModifiers = false;
+  Style.IndentWidth = 4;
+  Style.EmptyLineBeforeAccessModifier = FormatStyle::ELBAMS_Never;
+  Style.BreakBeforeBraces = FormatStyle::BS_Allman;
+
+  verifyFormat("class Outer\n"
+               "{\n"
+               "    public:\n"
+               "        struct Inner\n"
+               "        {\n"
+               "            bool first;\n"
+               "            bool second;\n"
+               "        };\n"
+               "};",
+               Style);
+  verifyFormat("struct S\n"
+               "{\n"
+               "    int before;\n"
+               "    private:\n"
+               "        int after;\n"
+               "};",
+               Style);
+  verifyFormat("union U\n"
+               "{\n"
+               "    int first;\n"
+               "    class Inner\n"
+               "    {\n"
+               "        public:\n"
+               "            int member;\n"
+               "    };\n"
+               "    int last;\n"
+               "};",
+               Style);
+  verifyFormat("class QtObject\n"
+               "{\n"
+               "    signals:\n"
+               "        void changed();\n"
+               "};",
+               Style);
+
+  Style.BreakBeforeBraces = FormatStyle::BS_Whitesmiths;
+  verifyFormat("struct S\n"
+               "    {\n"
+               "    int before;\n"
+               "    public:\n"
+               "        int after;\n"
+               "    };",
+               Style);
+}
+
 TEST_F(FormatTest, LimitlessStringsAndComments) {
   auto Style = getLLVMStyleWithColumns(0);
   constexpr StringRef Code(
diff --git a/clang/unittests/Format/FormatTestJava.cpp b/clang/unittests/Format/FormatTestJava.cpp
index a11fce963d820..86e4b2fc361b2 100644
--- a/clang/unittests/Format/FormatTestJava.cpp
+++ b/clang/unittests/Format/FormatTestJava.cpp
@@ -28,6 +28,18 @@ class FormatTestJava : public test::FormatTestBase {
   }
 };
 
+TEST_F(FormatTestJava, IndentImplicitAccessModifiersDoesNotAffectJava) {
+  FormatStyle Style = getDefaultStyle();
+  Style.IndentWidth = 4;
+  Style.IndentAccessModifiers = true;
+  Style.IndentImplicitAccessModifiers = false;
+  verifyFormat("class C {\n"
+               "        int before;\n"
+               "        public int after;\n"
+               "}",
+               Style);
+}
+
 TEST_F(FormatTestJava, NoAlternativeOperatorNames) {
   verifyFormat("someObject.and();");
 }

>From beb4dc8d614e0d71afcd45298dbbbe7dd1857e63 Mon Sep 17 00:00:00 2001
From: Noam Bouillet <noam.bouillet at epitech.eu>
Date: Thu, 1 Oct 2026 18:15:19 +0200
Subject: [PATCH 2/5] refactor(format): use enum for access indent

Preserve existing YAML true/false values while replacing the added boolean with a three-mode enum. C++ FormatStyle callers must use enum values instead of bool assignments.

Refs #61631
---
 clang/docs/ClangFormatStyleOptions.md       | 69 ++++++++++++++-------
 clang/docs/ReleaseNotes.md                  |  7 +--
 clang/include/clang/Format/Format.h         | 61 +++++++++++-------
 clang/lib/Format/Format.cpp                 | 18 ++++--
 clang/lib/Format/UnwrappedLineFormatter.cpp |  5 +-
 clang/lib/Format/UnwrappedLineParser.cpp    | 43 ++++++++-----
 clang/unittests/Format/ConfigParseTest.cpp  | 15 ++++-
 clang/unittests/Format/FormatTest.cpp       | 46 ++++++++++++--
 clang/unittests/Format/FormatTestCSharp.cpp | 11 ++++
 clang/unittests/Format/FormatTestJava.cpp   |  5 +-
 10 files changed, 200 insertions(+), 80 deletions(-)

diff --git a/clang/docs/ClangFormatStyleOptions.md b/clang/docs/ClangFormatStyleOptions.md
index d831bcac07c9c..5aabda184da0f 100644
--- a/clang/docs/ClangFormatStyleOptions.md
+++ b/clang/docs/ClangFormatStyleOptions.md
@@ -4744,21 +4744,24 @@ the configuration (without a prefix: `Auto`).
 
 (indentaccessmodifiers)=
 
-**IndentAccessModifiers** (`Boolean`) {versionbadge}`clang-format 13` {ref}`¶ <IndentAccessModifiers>`
-
-: Specify whether access modifiers should have their own indentation level.
-
-  When `false`, access modifiers are indented (or outdented) relative to
-  the record members, respecting the `AccessModifierOffset`. Record
-  members are indented one level below the record.
-  When `true`, access modifiers get their own indentation level. As a
-  consequence, record members are by default indented 2 levels below the
-  record, regardless of the access modifier presence. Value of the
-  `AccessModifierOffset` is ignored. `IndentImplicitAccessModifiers` can
-  change the indentation before the first explicit access modifier.
+**IndentAccessModifiers** (`IndentAccessModifierStyle`) {versionbadge}`clang-format 13` {ref}`¶ <IndentAccessModifiers>`
+
+: Specify how access modifiers and record members are indented.
+
+  `Never` (also `false` in configuration) uses `AccessModifierOffset` for
+  access modifiers and indents members one level below the record. `Always`
+  (also `true` in configuration) gives access modifiers their own
+  indentation level and indents all members two levels below the record,
+  even if there is no explicit access modifier.
+  `AfterFirstAccessModifier` gives members one level before the first
+  explicit access modifier and two levels afterwards. If there is no
+  explicit access modifier, members stay at one level. This behavior applies
+  to C, C++, and Objective-C; other languages treat it like `Always`.
+  `AccessModifierOffset` is ignored by `Always` and
+  `AfterFirstAccessModifier`.
 
   ```c++
-  false:                                 true:
+  Never (false):                         Always (true):
   class C {                      vs.     class C {
     class D {                                class D {
       void bar();                                void bar();
@@ -4773,6 +4776,36 @@ the configuration (without a prefix: `Auto`).
   }                                      }
   ```
 
+  ```yaml
+  IndentAccessModifiers: AfterFirstAccessModifier
+  ```
+
+  ```c++
+  struct S {
+    int before;
+
+    public:
+      int after;
+  };
+  ```
+
+  Possible values:
+
+  - `IAMS_Never` (in configuration: `Never`)
+    Use `AccessModifierOffset` for access modifiers and indent members one
+    level below the record.
+
+  - `IAMS_Always` (in configuration: `Always`)
+    Give access modifiers their own indentation level and indent all
+    members two levels below the record.
+
+  - `IAMS_AfterFirstAccessModifier` (in configuration: `AfterFirstAccessModifier`)
+    In C, C++, and Objective-C, indent members one level until the first
+    explicit access modifier, then two levels. Other languages use the
+    `Always` behavior. Available since clang-format 24.
+
+
+
 (indentcaseblocks)=
 
 **IndentCaseBlocks** (`Boolean`) {versionbadge}`clang-format 11` {ref}`¶ <IndentCaseBlocks>`
@@ -4953,16 +4986,6 @@ the configuration (without a prefix: `Auto`).
 
 
 
-(indentimplicitaccessmodifiers)=
-
-**IndentImplicitAccessModifiers** (`Boolean`) {versionbadge}`clang-format 24` {ref}`¶ <IndentImplicitAccessModifiers>`
-
-: When `IndentAccessModifiers` is `true`, indent members before the first
-  explicit access modifier by two levels. Set this option to `false` to
-  indent those members by one level. Members after an explicit access
-  modifier still use two levels. This option has no effect if
-  `IndentAccessModifiers` is false.
-
 (indentppdirectives)=
 
 **IndentPPDirectives** (`PPDirectiveIndentStyle`) {versionbadge}`clang-format 6` {ref}`¶ <IndentPPDirectives>`
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index a2090bb98df30..edbdf4f5dd31b 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -944,10 +944,6 @@ features cannot lower the translation-unit ABI level;
 
 ### clang-format
 
-- Add `IndentImplicitAccessModifiers` to allow members before the first
-  explicit access modifier to use one indentation level when
-  `IndentAccessModifiers` is enabled. The default preserves existing formatting.
-
 - Add `SpacesInBlockComments` option to control spacing after `/*` and
   before `*/` in ordinary block comments.
 - Add `AfterRequiresExpression` sub-option of `BraceWrapping` to wrap the
@@ -957,6 +953,9 @@ features cannot lower the translation-unit ABI level;
 - `QualifierOrder` now supports `typedef`, `consteval`, `constinit`,
   `thread_local`, `extern`, `mutable`, `signed`, `unsigned`, `long`, `short`,
   and `explicit` declaration specifiers.
+- Extend `IndentAccessModifiers` with `AfterFirstAccessModifier` to indent
+  members before the first explicit access modifier by one level. Existing
+  configuration values `true` and `false` remain supported.
 
 ### libclang
 
diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h
index 40ae52a0148e8..e6c8aa1f90604 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -3181,18 +3181,35 @@ struct FormatStyle {
   /// \version 13
   std::vector<std::string> IfMacros;
 
-  /// Specify whether access modifiers should have their own indentation level.
-  ///
-  /// When `false`, access modifiers are indented (or outdented) relative to
-  /// the record members, respecting the `AccessModifierOffset`. Record
-  /// members are indented one level below the record.
-  /// When `true`, access modifiers get their own indentation level. As a
-  /// consequence, record members are by default indented 2 levels below the
-  /// record, regardless of the access modifier presence. Value of the
-  /// `AccessModifierOffset` is ignored. `IndentImplicitAccessModifiers` can
-  /// change the indentation before the first explicit access modifier.
+  /// Modes for indenting access modifiers and record members.
+  enum IndentAccessModifierStyle : int8_t {
+    /// Use `AccessModifierOffset` for access modifiers and indent members one
+    /// level below the record.
+    IAMS_Never,
+    /// Give access modifiers their own indentation level and indent all
+    /// members two levels below the record.
+    IAMS_Always,
+    /// In C, C++, and Objective-C, indent members one level until the first
+    /// explicit access modifier, then two levels. Other languages use the
+    /// `Always` behavior. Available since clang-format 24.
+    IAMS_AfterFirstAccessModifier,
+  };
+
+  /// Specify how access modifiers and record members are indented.
+  ///
+  /// `Never` (also `false` in configuration) uses `AccessModifierOffset` for
+  /// access modifiers and indents members one level below the record. `Always`
+  /// (also `true` in configuration) gives access modifiers their own
+  /// indentation level and indents all members two levels below the record,
+  /// even if there is no explicit access modifier.
+  /// `AfterFirstAccessModifier` gives members one level before the first
+  /// explicit access modifier and two levels afterwards. If there is no
+  /// explicit access modifier, members stay at one level. This behavior applies
+  /// to C, C++, and Objective-C; other languages treat it like `Always`.
+  /// `AccessModifierOffset` is ignored by `Always` and
+  /// `AfterFirstAccessModifier`.
   /// \code
-  ///    false:                                 true:
+  ///    Never (false):                         Always (true):
   ///    class C {                      vs.     class C {
   ///      class D {                                class D {
   ///        void bar();                                void bar();
@@ -3206,16 +3223,19 @@ struct FormatStyle {
   ///      return 1;                              return 1;
   ///    }                                      }
   /// \endcode
+  /// \code{.yaml}
+  ///   IndentAccessModifiers: AfterFirstAccessModifier
+  /// \endcode
+  /// \code
+  ///    struct S {
+  ///      int before;
+  ///
+  ///      public:
+  ///        int after;
+  ///    };
+  /// \endcode
   /// \version 13
-  bool IndentAccessModifiers;
-
-  /// When `IndentAccessModifiers` is `true`, indent members before the first
-  /// explicit access modifier by two levels. Set this option to `false` to
-  /// indent those members by one level. Members after an explicit access
-  /// modifier still use two levels. This option has no effect if
-  /// `IndentAccessModifiers` is false.
-  /// \version 24
-  bool IndentImplicitAccessModifiers;
+  IndentAccessModifierStyle IndentAccessModifiers;
 
   /// Indent case label blocks one level from the case label.
   ///
@@ -6251,7 +6271,6 @@ struct FormatStyle {
                R.IncludeStyle.IncludeIsMainSourceRegex &&
            IncludeStyle.MainIncludeChar == R.IncludeStyle.MainIncludeChar &&
            IndentAccessModifiers == R.IndentAccessModifiers &&
-           IndentImplicitAccessModifiers == R.IndentImplicitAccessModifiers &&
            IndentCaseBlocks == R.IndentCaseBlocks &&
            IndentCaseLabels == R.IndentCaseLabels &&
            IndentExportBlock == R.IndentExportBlock &&
diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp
index 2f6d70d2bf4e2..bdb15326a1603 100644
--- a/clang/lib/Format/Format.cpp
+++ b/clang/lib/Format/Format.cpp
@@ -477,6 +477,19 @@ struct ScalarEnumerationTraits<FormatStyle::EnumTrailingCommaStyle> {
   }
 };
 
+template <>
+struct ScalarEnumerationTraits<FormatStyle::IndentAccessModifierStyle> {
+  static void enumeration(IO &IO,
+                          FormatStyle::IndentAccessModifierStyle &Value) {
+    IO.enumCase(Value, "Never", FormatStyle::IAMS_Never);
+    IO.enumCase(Value, "Always", FormatStyle::IAMS_Always);
+    IO.enumCase(Value, "AfterFirstAccessModifier",
+                FormatStyle::IAMS_AfterFirstAccessModifier);
+    IO.enumCase(Value, "false", FormatStyle::IAMS_Never);
+    IO.enumCase(Value, "true", FormatStyle::IAMS_Always);
+  }
+};
+
 template <>
 struct ScalarEnumerationTraits<FormatStyle::IndentExternBlockStyle> {
   static void enumeration(IO &IO, FormatStyle::IndentExternBlockStyle &Value) {
@@ -1399,8 +1412,6 @@ template <> struct MappingTraits<FormatStyle> {
     IO.mapOptional("IncludeIsMainSourceRegex",
                    Style.IncludeStyle.IncludeIsMainSourceRegex);
     IO.mapOptional("IndentAccessModifiers", Style.IndentAccessModifiers);
-    IO.mapOptional("IndentImplicitAccessModifiers",
-                   Style.IndentImplicitAccessModifiers);
     IO.mapOptional("IndentCaseBlocks", Style.IndentCaseBlocks);
     IO.mapOptional("IndentCaseLabels", Style.IndentCaseLabels);
     IO.mapOptional("IndentExportBlock", Style.IndentExportBlock);
@@ -1978,8 +1989,7 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) {
       {".*", 1, 0, false}};
   LLVMStyle.IncludeStyle.IncludeIsMainRegex = "(Test)?$";
   LLVMStyle.IncludeStyle.MainIncludeChar = tooling::IncludeStyle::MICD_Quote;
-  LLVMStyle.IndentAccessModifiers = false;
-  LLVMStyle.IndentImplicitAccessModifiers = true;
+  LLVMStyle.IndentAccessModifiers = FormatStyle::IAMS_Never;
   LLVMStyle.IndentCaseBlocks = false;
   LLVMStyle.IndentCaseLabels = false;
   LLVMStyle.IndentExportBlock = true;
diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp
index f005f228328ce..4d27ddde33ff0 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -136,8 +136,9 @@ class LevelIndentTracker {
       // The AccessModifierOffset may be overridden by IndentAccessModifiers,
       // in which case we take a negative value of the IndentWidth to simulate
       // the upper indent level.
-      return Style.IndentAccessModifiers ? -Style.IndentWidth
-                                         : Style.AccessModifierOffset;
+      return Style.IndentAccessModifiers != FormatStyle::IAMS_Never
+                 ? -Style.IndentWidth
+                 : Style.AccessModifierOffset;
     }
     return 0;
   }
diff --git a/clang/lib/Format/UnwrappedLineParser.cpp b/clang/lib/Format/UnwrappedLineParser.cpp
index 0e0825112b513..11c70db50435e 100644
--- a/clang/lib/Format/UnwrappedLineParser.cpp
+++ b/clang/lib/Format/UnwrappedLineParser.cpp
@@ -380,15 +380,15 @@ bool UnwrappedLineParser::parseLevel(const FormatToken *OpeningBrace,
     auto ParseDefault = [this, OpeningBrace, IfKind, &IfLBrace, &HasDoWhile,
                          &HasLabel, &StatementCount,
                          SeenExplicitAccessModifier] {
-      const bool IsQtAccessLabel =
-          SeenExplicitAccessModifier && !*SeenExplicitAccessModifier &&
-          FormatTok->isOneOf(Keywords.kw_signals, Keywords.kw_qsignals,
-                             Keywords.kw_slots, Keywords.kw_qslots) &&
-          Tokens->peekNextToken(/*SkipComment=*/true)->is(tok::colon);
-      if (SeenExplicitAccessModifier && !*SeenExplicitAccessModifier &&
-          (FormatTok->isAccessSpecifierKeyword() || IsQtAccessLabel)) {
-        ++Line->Level;
-        *SeenExplicitAccessModifier = true;
+      if (SeenExplicitAccessModifier && !*SeenExplicitAccessModifier) {
+        const bool IsQtAccessLabel =
+            FormatTok->isOneOf(Keywords.kw_signals, Keywords.kw_qsignals,
+                               Keywords.kw_slots, Keywords.kw_qslots) &&
+            Tokens->peekNextToken(/*SkipComment=*/true)->is(tok::colon);
+        if (FormatTok->isAccessSpecifierKeyword() || IsQtAccessLabel) {
+          ++Line->Level;
+          *SeenExplicitAccessModifier = true;
+        }
       }
       parseStructuralElement(OpeningBrace, IfKind, &IfLBrace,
                              HasDoWhile ? nullptr : &HasDoWhile,
@@ -894,8 +894,9 @@ FormatToken *UnwrappedLineParser::parseBlock(
   size_t PPEndHash = computePPHash();
 
   // Munch the closing brace.
-  nextToken(/*LevelDifference=*/
-            -static_cast<int>(AddLevels + SeenExplicitAccessModifier));
+  if (SeenExplicitAccessModifier)
+    ++AddLevels;
+  nextToken(/*LevelDifference=*/-static_cast<int>(AddLevels));
 
   // When this is a function block and there is an unnecessary semicolon
   // afterwards then mark it as optional (so the RemoveSemi pass can get rid of
@@ -4315,12 +4316,20 @@ void UnwrappedLineParser::parseRecord(bool ParseAsExpr, bool IsJavaRecord) {
       }
 
       const bool IndentAfterExplicitAccessModifier =
-          Style.isCpp() && Style.IndentAccessModifiers &&
-          !Style.IndentImplicitAccessModifiers;
-      unsigned AddLevels =
-          Style.IndentAccessModifiers && !IndentAfterExplicitAccessModifier
-              ? 2u
-              : 1u;
+          Style.isCpp() && Style.IndentAccessModifiers ==
+                               FormatStyle::IAMS_AfterFirstAccessModifier;
+      unsigned AddLevels = 1u;
+      switch (Style.IndentAccessModifiers) {
+      case FormatStyle::IAMS_Never:
+        break;
+      case FormatStyle::IAMS_Always:
+        AddLevels = 2u;
+        break;
+      case FormatStyle::IAMS_AfterFirstAccessModifier:
+        // Other languages keep the indentation of the old true setting.
+        AddLevels = IndentAfterExplicitAccessModifier ? 1u : 2u;
+        break;
+      }
       parseBlock(/*MustBeDeclaration=*/true, AddLevels, /*MunchSemi=*/false,
                  /*KeepBraces=*/true, /*IfKind=*/nullptr,
                  /*UnindentWhitesmithsBraces=*/false,
diff --git a/clang/unittests/Format/ConfigParseTest.cpp b/clang/unittests/Format/ConfigParseTest.cpp
index ccbc346901460..c0afbfab9ede2 100644
--- a/clang/unittests/Format/ConfigParseTest.cpp
+++ b/clang/unittests/Format/ConfigParseTest.cpp
@@ -189,8 +189,6 @@ TEST(ConfigParseTest, ParsesConfigurationBools) {
   CHECK_PARSE_BOOL(DerivePointerAlignment);
   CHECK_PARSE_BOOL_FIELD(DerivePointerAlignment, "DerivePointerBinding");
   CHECK_PARSE_BOOL(DisableFormat);
-  CHECK_PARSE_BOOL(IndentAccessModifiers);
-  CHECK_PARSE_BOOL(IndentImplicitAccessModifiers);
   CHECK_PARSE_BOOL(IndentCaseBlocks);
   CHECK_PARSE_BOOL(IndentCaseLabels);
   CHECK_PARSE_BOOL(IndentExportBlock);
@@ -1030,6 +1028,19 @@ TEST(ConfigParseTest, ParsesConfiguration) {
               AllowShortIfStatementsOnASingleLine,
               FormatStyle::SIS_WithoutElse);
 
+  Style.IndentAccessModifiers = FormatStyle::IAMS_Always;
+  CHECK_PARSE("IndentAccessModifiers: Never", IndentAccessModifiers,
+              FormatStyle::IAMS_Never);
+  CHECK_PARSE("IndentAccessModifiers: Always", IndentAccessModifiers,
+              FormatStyle::IAMS_Always);
+  CHECK_PARSE("IndentAccessModifiers: AfterFirstAccessModifier",
+              IndentAccessModifiers,
+              FormatStyle::IAMS_AfterFirstAccessModifier);
+  CHECK_PARSE("IndentAccessModifiers: false", IndentAccessModifiers,
+              FormatStyle::IAMS_Never);
+  CHECK_PARSE("IndentAccessModifiers: true", IndentAccessModifiers,
+              FormatStyle::IAMS_Always);
+
   Style.IndentExternBlock = FormatStyle::IEBS_NoIndent;
   CHECK_PARSE("IndentExternBlock: AfterExternBlock", IndentExternBlock,
               FormatStyle::IEBS_AfterExternBlock);
diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp
index 656a352ecb005..e2bb45a7e67d4 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -24839,7 +24839,7 @@ TEST_F(FormatTest, StatementAttributeLikeMacros) {
 
 TEST_F(FormatTest, IndentAccessModifiers) {
   FormatStyle Style = getLLVMStyle();
-  Style.IndentAccessModifiers = true;
+  Style.IndentAccessModifiers = FormatStyle::IAMS_Always;
   // Members are *two* levels below the record;
   // Style.IndentWidth == 2, thus yielding a 4 spaces wide indentation.
   verifyFormat("class C {\n"
@@ -24920,10 +24920,48 @@ TEST_F(FormatTest, IndentAccessModifiers) {
                Style);
 }
 
-TEST_F(FormatTest, IndentImplicitAccessModifiers) {
+TEST_F(FormatTest, IndentAccessModifiersAfterFirst) {
   FormatStyle Style = getLLVMStyle();
-  Style.IndentAccessModifiers = true;
-  Style.IndentImplicitAccessModifiers = false;
+  verifyFormat("struct S {\n"
+               "  int before;\n"
+               "\n"
+               "public:\n"
+               "  int after;\n"
+               "};",
+               Style);
+
+  Style.IndentAccessModifiers = FormatStyle::IAMS_Always;
+  verifyFormat("struct S {\n"
+               "    int before;\n"
+               "\n"
+               "  public:\n"
+               "    int after;\n"
+               "};",
+               Style);
+  Style.IndentAccessModifiers = FormatStyle::IAMS_AfterFirstAccessModifier;
+  verifyFormat("struct S {\n"
+               "  int member;\n"
+               "};",
+               Style);
+  verifyFormat("struct S {\n"
+               "  int before;\n"
+               "\n"
+               "  public:\n"
+               "    int after;\n"
+               "};",
+               Style);
+  verifyFormat("struct S {\n"
+               "  int before;\n"
+               "\n"
+               "  public:\n"
+               "    int after;\n"
+               "\n"
+               "  private:\n"
+               "    int last;\n"
+               "};",
+               Style);
+
+  // Also cover the Allman layout from issue #61631.
   Style.IndentWidth = 4;
   Style.EmptyLineBeforeAccessModifier = FormatStyle::ELBAMS_Never;
   Style.BreakBeforeBraces = FormatStyle::BS_Allman;
diff --git a/clang/unittests/Format/FormatTestCSharp.cpp b/clang/unittests/Format/FormatTestCSharp.cpp
index 805fe6f9bd3c2..be98501db166f 100644
--- a/clang/unittests/Format/FormatTestCSharp.cpp
+++ b/clang/unittests/Format/FormatTestCSharp.cpp
@@ -124,6 +124,17 @@ TEST_F(FormatTestCSharp, AccessModifiers) {
                "}");
 }
 
+TEST_F(FormatTestCSharp, IndentAccessModifiersAfterFirstUsesAlways) {
+  FormatStyle Style = getDefaultStyle();
+  Style.IndentAccessModifiers = FormatStyle::IAMS_AfterFirstAccessModifier;
+  verifyFormat("class C\n"
+               "{\n"
+               "        int Before;\n"
+               "        public int After;\n"
+               "}",
+               Style);
+}
+
 TEST_F(FormatTestCSharp, NoStringLiteralBreaks) {
   // Breaking of interpolated strings is not implemented.
   auto Style = getDefaultStyle();
diff --git a/clang/unittests/Format/FormatTestJava.cpp b/clang/unittests/Format/FormatTestJava.cpp
index 86e4b2fc361b2..53168ef6a19e1 100644
--- a/clang/unittests/Format/FormatTestJava.cpp
+++ b/clang/unittests/Format/FormatTestJava.cpp
@@ -28,11 +28,10 @@ class FormatTestJava : public test::FormatTestBase {
   }
 };
 
-TEST_F(FormatTestJava, IndentImplicitAccessModifiersDoesNotAffectJava) {
+TEST_F(FormatTestJava, IndentAccessModifiersAfterFirstDoesNotAffectJava) {
   FormatStyle Style = getDefaultStyle();
   Style.IndentWidth = 4;
-  Style.IndentAccessModifiers = true;
-  Style.IndentImplicitAccessModifiers = false;
+  Style.IndentAccessModifiers = FormatStyle::IAMS_AfterFirstAccessModifier;
   verifyFormat("class C {\n"
                "        int before;\n"
                "        public int after;\n"

>From b27ce0fd6f1dc72cfc0474155ba3de3f1af3cd72 Mon Sep 17 00:00:00 2001
From: Noam Bouillet <noam.bouillet at epitech.eu>
Date: Thu, 1 Oct 2026 18:28:18 +0200
Subject: [PATCH 3/5] test(format): isolate access indent style cases

Keep the plain LLVM style checks focused on the new option. Test the Allman reproduction and Whitesmiths behavior separately.
---
 clang/unittests/Format/FormatTest.cpp | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp
index e2bb45a7e67d4..358fb03bf4141 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -24960,8 +24960,11 @@ TEST_F(FormatTest, IndentAccessModifiersAfterFirst) {
                "    int last;\n"
                "};",
                Style);
+}
 
-  // Also cover the Allman layout from issue #61631.
+TEST_F(FormatTest, IndentAccessModifiersAfterFirstAllman) {
+  FormatStyle Style = getLLVMStyle();
+  Style.IndentAccessModifiers = FormatStyle::IAMS_AfterFirstAccessModifier;
   Style.IndentWidth = 4;
   Style.EmptyLineBeforeAccessModifier = FormatStyle::ELBAMS_Never;
   Style.BreakBeforeBraces = FormatStyle::BS_Allman;
@@ -25000,7 +25003,13 @@ TEST_F(FormatTest, IndentAccessModifiersAfterFirst) {
                "        void changed();\n"
                "};",
                Style);
+}
 
+TEST_F(FormatTest, IndentAccessModifiersAfterFirstWhitesmiths) {
+  FormatStyle Style = getLLVMStyle();
+  Style.IndentAccessModifiers = FormatStyle::IAMS_AfterFirstAccessModifier;
+  Style.IndentWidth = 4;
+  Style.EmptyLineBeforeAccessModifier = FormatStyle::ELBAMS_Never;
   Style.BreakBeforeBraces = FormatStyle::BS_Whitesmiths;
   verifyFormat("struct S\n"
                "    {\n"

>From 2175d322497f3bf201af6b25b7d3855eac4555f7 Mon Sep 17 00:00:00 2001
From: Noam Bouillet <noam.bouillet at epitech.eu>
Date: Fri, 2 Oct 2026 00:30:26 +0200
Subject: [PATCH 4/5] refactor(format): address access indent review

---
 clang/docs/ClangFormatStyleOptions.md    | 72 ++++++++++--------------
 clang/include/clang/Format/Format.h      | 66 +++++++++-------------
 clang/lib/Format/Format.cpp              |  2 +
 clang/lib/Format/UnwrappedLineParser.cpp | 14 +++--
 4 files changed, 65 insertions(+), 89 deletions(-)

diff --git a/clang/docs/ClangFormatStyleOptions.md b/clang/docs/ClangFormatStyleOptions.md
index 5aabda184da0f..e982c26929ccc 100644
--- a/clang/docs/ClangFormatStyleOptions.md
+++ b/clang/docs/ClangFormatStyleOptions.md
@@ -4748,61 +4748,47 @@ the configuration (without a prefix: `Auto`).
 
 : Specify how access modifiers and record members are indented.
 
-  `Never` (also `false` in configuration) uses `AccessModifierOffset` for
-  access modifiers and indents members one level below the record. `Always`
-  (also `true` in configuration) gives access modifiers their own
-  indentation level and indents all members two levels below the record,
-  even if there is no explicit access modifier.
-  `AfterFirstAccessModifier` gives members one level before the first
-  explicit access modifier and two levels afterwards. If there is no
-  explicit access modifier, members stay at one level. This behavior applies
-  to C, C++, and Objective-C; other languages treat it like `Always`.
-  `AccessModifierOffset` is ignored by `Always` and
-  `AfterFirstAccessModifier`.
-
-  ```c++
-  Never (false):                         Always (true):
-  class C {                      vs.     class C {
-    class D {                                class D {
-      void bar();                                void bar();
-    protected:                                 protected:
-      D();                                       D();
-    };                                       };
-  public:                                  public:
-    C();                                     C();
-  };                                     };
-  void foo() {                           void foo() {
-    return 1;                              return 1;
-  }                                      }
-  ```
-
-  ```yaml
-  IndentAccessModifiers: AfterFirstAccessModifier
-  ```
-
-  ```c++
-  struct S {
-    int before;
-
-    public:
-      int after;
-  };
-  ```
-
   Possible values:
 
   - `IAMS_Never` (in configuration: `Never`)
     Use `AccessModifierOffset` for access modifiers and indent members one
     level below the record.
 
+    ```c++
+    struct S {
+      int before;
+
+    public:
+      int after;
+    };
+    ```
+
   - `IAMS_Always` (in configuration: `Always`)
     Give access modifiers their own indentation level and indent all
-    members two levels below the record.
+    members two levels below the record. Ignore `AccessModifierOffset`.
+
+    ```c++
+    struct S {
+        int before;
+
+      public:
+        int after;
+    };
+    ```
 
   - `IAMS_AfterFirstAccessModifier` (in configuration: `AfterFirstAccessModifier`)
     In C, C++, and Objective-C, indent members one level until the first
     explicit access modifier, then two levels. Other languages use the
-    `Always` behavior. Available since clang-format 24.
+    `Always` behavior. Ignore `AccessModifierOffset`.
+
+    ```c++
+    struct S {
+      int before;
+
+      public:
+        int after;
+    };
+    ```
 
 
 
diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h
index e6c8aa1f90604..a67a8596e661c 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -3185,55 +3185,41 @@ struct FormatStyle {
   enum IndentAccessModifierStyle : int8_t {
     /// Use `AccessModifierOffset` for access modifiers and indent members one
     /// level below the record.
+    /// \code
+    ///   struct S {
+    ///     int before;
+    ///
+    ///   public:
+    ///     int after;
+    ///   };
+    /// \endcode
     IAMS_Never,
     /// Give access modifiers their own indentation level and indent all
-    /// members two levels below the record.
+    /// members two levels below the record. Ignore `AccessModifierOffset`.
+    /// \code
+    ///   struct S {
+    ///       int before;
+    ///
+    ///     public:
+    ///       int after;
+    ///   };
+    /// \endcode
     IAMS_Always,
     /// In C, C++, and Objective-C, indent members one level until the first
     /// explicit access modifier, then two levels. Other languages use the
-    /// `Always` behavior. Available since clang-format 24.
+    /// `Always` behavior. Ignore `AccessModifierOffset`.
+    /// \code
+    ///   struct S {
+    ///     int before;
+    ///
+    ///     public:
+    ///       int after;
+    ///   };
+    /// \endcode
     IAMS_AfterFirstAccessModifier,
   };
 
   /// Specify how access modifiers and record members are indented.
-  ///
-  /// `Never` (also `false` in configuration) uses `AccessModifierOffset` for
-  /// access modifiers and indents members one level below the record. `Always`
-  /// (also `true` in configuration) gives access modifiers their own
-  /// indentation level and indents all members two levels below the record,
-  /// even if there is no explicit access modifier.
-  /// `AfterFirstAccessModifier` gives members one level before the first
-  /// explicit access modifier and two levels afterwards. If there is no
-  /// explicit access modifier, members stay at one level. This behavior applies
-  /// to C, C++, and Objective-C; other languages treat it like `Always`.
-  /// `AccessModifierOffset` is ignored by `Always` and
-  /// `AfterFirstAccessModifier`.
-  /// \code
-  ///    Never (false):                         Always (true):
-  ///    class C {                      vs.     class C {
-  ///      class D {                                class D {
-  ///        void bar();                                void bar();
-  ///      protected:                                 protected:
-  ///        D();                                       D();
-  ///      };                                       };
-  ///    public:                                  public:
-  ///      C();                                     C();
-  ///    };                                     };
-  ///    void foo() {                           void foo() {
-  ///      return 1;                              return 1;
-  ///    }                                      }
-  /// \endcode
-  /// \code{.yaml}
-  ///   IndentAccessModifiers: AfterFirstAccessModifier
-  /// \endcode
-  /// \code
-  ///    struct S {
-  ///      int before;
-  ///
-  ///      public:
-  ///        int after;
-  ///    };
-  /// \endcode
   /// \version 13
   IndentAccessModifierStyle IndentAccessModifiers;
 
diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp
index bdb15326a1603..e1b7f7804b5c9 100644
--- a/clang/lib/Format/Format.cpp
+++ b/clang/lib/Format/Format.cpp
@@ -485,6 +485,8 @@ struct ScalarEnumerationTraits<FormatStyle::IndentAccessModifierStyle> {
     IO.enumCase(Value, "Always", FormatStyle::IAMS_Always);
     IO.enumCase(Value, "AfterFirstAccessModifier",
                 FormatStyle::IAMS_AfterFirstAccessModifier);
+
+    // For backward compatibility.
     IO.enumCase(Value, "false", FormatStyle::IAMS_Never);
     IO.enumCase(Value, "true", FormatStyle::IAMS_Always);
   }
diff --git a/clang/lib/Format/UnwrappedLineParser.cpp b/clang/lib/Format/UnwrappedLineParser.cpp
index 11c70db50435e..9e81efc286a5e 100644
--- a/clang/lib/Format/UnwrappedLineParser.cpp
+++ b/clang/lib/Format/UnwrappedLineParser.cpp
@@ -896,7 +896,7 @@ FormatToken *UnwrappedLineParser::parseBlock(
   // Munch the closing brace.
   if (SeenExplicitAccessModifier)
     ++AddLevels;
-  nextToken(/*LevelDifference=*/-static_cast<int>(AddLevels));
+  nextToken(/*LevelDifference=*/-AddLevels);
 
   // When this is a function block and there is an unnecessary semicolon
   // afterwards then mark it as optional (so the RemoveSemi pass can get rid of
@@ -4315,9 +4315,7 @@ void UnwrappedLineParser::parseRecord(bool ParseAsExpr, bool IsJavaRecord) {
         addUnwrappedLine();
       }
 
-      const bool IndentAfterExplicitAccessModifier =
-          Style.isCpp() && Style.IndentAccessModifiers ==
-                               FormatStyle::IAMS_AfterFirstAccessModifier;
+      bool IndentAfterExplicitAccessModifier = false;
       unsigned AddLevels = 1u;
       switch (Style.IndentAccessModifiers) {
       case FormatStyle::IAMS_Never:
@@ -4326,8 +4324,12 @@ void UnwrappedLineParser::parseRecord(bool ParseAsExpr, bool IsJavaRecord) {
         AddLevels = 2u;
         break;
       case FormatStyle::IAMS_AfterFirstAccessModifier:
-        // Other languages keep the indentation of the old true setting.
-        AddLevels = IndentAfterExplicitAccessModifier ? 1u : 2u;
+        if (Style.isCpp()) {
+          IndentAfterExplicitAccessModifier = true;
+        } else {
+          // Other languages use the same indentation as IAMS_Always.
+          AddLevels = 2u;
+        }
         break;
       }
       parseBlock(/*MustBeDeclaration=*/true, AddLevels, /*MunchSemi=*/false,

>From 58173480d3e9c4bb13d007e06057e33bef2b3d1c Mon Sep 17 00:00:00 2001
From: Noam Bouillet <noam.bouillet at epitech.eu>
Date: Sat, 3 Oct 2026 00:02:41 +0200
Subject: [PATCH 5/5] refactor(format): simplify access indent handling

---
 clang/docs/ClangFormatStyleOptions.md    |  5 +++--
 clang/include/clang/Format/Format.h      |  5 +++--
 clang/lib/Format/UnwrappedLineParser.cpp | 13 ++++++-------
 3 files changed, 12 insertions(+), 11 deletions(-)

diff --git a/clang/docs/ClangFormatStyleOptions.md b/clang/docs/ClangFormatStyleOptions.md
index e982c26929ccc..6ff29082c8a7a 100644
--- a/clang/docs/ClangFormatStyleOptions.md
+++ b/clang/docs/ClangFormatStyleOptions.md
@@ -4765,7 +4765,8 @@ the configuration (without a prefix: `Auto`).
 
   - `IAMS_Always` (in configuration: `Always`)
     Give access modifiers their own indentation level and indent all
-    members two levels below the record. Ignore `AccessModifierOffset`.
+    members two levels below the record. Value of the `AccessModifierOffset`
+    is ignored.
 
     ```c++
     struct S {
@@ -4779,7 +4780,7 @@ the configuration (without a prefix: `Auto`).
   - `IAMS_AfterFirstAccessModifier` (in configuration: `AfterFirstAccessModifier`)
     In C, C++, and Objective-C, indent members one level until the first
     explicit access modifier, then two levels. Other languages use the
-    `Always` behavior. Ignore `AccessModifierOffset`.
+    `Always` behavior. Value of the `AccessModifierOffset` is ignored.
 
     ```c++
     struct S {
diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h
index a67a8596e661c..036353752e831 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -3195,7 +3195,8 @@ struct FormatStyle {
     /// \endcode
     IAMS_Never,
     /// Give access modifiers their own indentation level and indent all
-    /// members two levels below the record. Ignore `AccessModifierOffset`.
+    /// members two levels below the record. Value of the `AccessModifierOffset`
+    /// is ignored.
     /// \code
     ///   struct S {
     ///       int before;
@@ -3207,7 +3208,7 @@ struct FormatStyle {
     IAMS_Always,
     /// In C, C++, and Objective-C, indent members one level until the first
     /// explicit access modifier, then two levels. Other languages use the
-    /// `Always` behavior. Ignore `AccessModifierOffset`.
+    /// `Always` behavior. Value of the `AccessModifierOffset` is ignored.
     /// \code
     ///   struct S {
     ///     int before;
diff --git a/clang/lib/Format/UnwrappedLineParser.cpp b/clang/lib/Format/UnwrappedLineParser.cpp
index 9e81efc286a5e..901335950b9fa 100644
--- a/clang/lib/Format/UnwrappedLineParser.cpp
+++ b/clang/lib/Format/UnwrappedLineParser.cpp
@@ -893,9 +893,9 @@ FormatToken *UnwrappedLineParser::parseBlock(
 
   size_t PPEndHash = computePPHash();
 
-  // Munch the closing brace.
   if (SeenExplicitAccessModifier)
     ++AddLevels;
+  // Munch the closing brace.
   nextToken(/*LevelDifference=*/-AddLevels);
 
   // When this is a function block and there is an unnecessary semicolon
@@ -4320,16 +4320,15 @@ void UnwrappedLineParser::parseRecord(bool ParseAsExpr, bool IsJavaRecord) {
       switch (Style.IndentAccessModifiers) {
       case FormatStyle::IAMS_Never:
         break;
-      case FormatStyle::IAMS_Always:
-        AddLevels = 2u;
-        break;
       case FormatStyle::IAMS_AfterFirstAccessModifier:
         if (Style.isCpp()) {
           IndentAfterExplicitAccessModifier = true;
-        } else {
-          // Other languages use the same indentation as IAMS_Always.
-          AddLevels = 2u;
+          break;
         }
+        // Other languages use the same indentation as IAMS_Always.
+        [[fallthrough]];
+      case FormatStyle::IAMS_Always:
+        AddLevels = 2u;
         break;
       }
       parseBlock(/*MustBeDeclaration=*/true, AddLevels, /*MunchSemi=*/false,



More information about the cfe-commits mailing list