Skip to content

fix: handle multiline function templates in namespaces - #447

Open
janmarsino98 wants to merge 1 commit into
cpplint:developfrom
janmarsino98:agent/fix-401-multiline-function-template
Open

fix: handle multiline function templates in namespaces#447
janmarsino98 wants to merge 1 commit into
cpplint:developfrom
janmarsino98:agent/fix-401-multiline-function-template

Conversation

@janmarsino98

@janmarsino98 janmarsino98 commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Fixes #401.

Root cause

The namespace-indentation regression made is_namespace_indent_item true whenever the current or previous nesting state was a namespace. That correctly catches indented functions, but also treats continuation lines inside a multiline template parameter list as namespace indentation.

Changes

  • Skip whitespace/indent_namespace only for lines inside a multiline template parameter list when the following statement is a function declaration.
  • Preserve errors on genuinely indented template declarations and function declarations.
  • Add positive and negative regression coverage.
  • Update the existing Boost sample expectation for the same valid function-template pattern.

This intentionally does not change assignments, using aliases, enums, or the separate work discussed in #376.

Validation

  • pytest -p no:cacheprovider: 227 passed, 89.96% coverage
  • pylint --persistent=no cpplint.py
  • mypy cpplint.py cpplint_unittest.py cpplint_clitest.py
  • pre-commit on all changed files

Summary by CodeRabbit

  • Bug Fixes

    • Fixed false-positive namespace indentation warnings for multiline function template declarations.
    • Correctly preserves warnings when continuation lines are genuinely over-indented.
  • Tests

    • Added coverage for properly and improperly indented multiline function templates.
  • Documentation

    • Updated the changelog with the linting fix.
  • Samples

    • Updated the Boost sample output to reflect the corrected error count.

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

whitespace/indent_namespace now skips continuation lines belonging to multiline function template declarations. Detection, regression tests, changelog text, and Boost sample expectations were updated.

Changes

Namespace indentation handling

Layer / File(s) Summary
Detect and exempt multiline function templates
cpplint.py
The namespace indentation check recognizes multiline function template declarations, including operators and destructors, while excluding class, enum, struct, and using declarations.
Validate corrected namespace results
cpplint_unittest.py, CHANGELOG.rst, samples/boost-sample/simple.def
Tests cover exempt and indented declarations; documentation and sample output reflect two fewer warnings.

Estimated code review effort: 3 (Moderate) | ~20 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR only fixes multiline function template declarations, but #401 also describes other namespace-indentation false positives that remain unchanged. Either expand the fix to cover the other reported whitespace/indent_namespace false positives or split the remaining cases into separate issues.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: handling multiline function templates in namespaces.
Out of Scope Changes check ✅ Passed The code, tests, and changelog updates all relate directly to the namespace-indentation fix and its regression coverage.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@janmarsino98
janmarsino98 marked this pull request as ready for review July 16, 2026 07:23

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
cpplint.py (1)

4164-4170: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Delay the multiline template check for better performance.

Currently, IsMultilineFunctionTemplateDeclaration runs for every line in the file. This results in unnecessary backward string scanning and regex evaluations for the vast majority of lines (e.g., inside function bodies or outside namespaces).

Moving this check inside the ShouldCheckNamespaceIndentation block ensures we only incur this cost for lines that are actually candidates for the namespace indentation warning.

⚡ Proposed refactor
-    if IsMultilineFunctionTemplateDeclaration(clean_lines, line):
-        return
-
     if ShouldCheckNamespaceIndentation(
         nesting_state, is_namespace_indent_item, clean_lines.elided, line
     ):
+        if IsMultilineFunctionTemplateDeclaration(clean_lines, line):
+            return
         CheckItemIndentationInNamespace(filename, clean_lines.elided, line, error)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cpplint.py` around lines 4164 - 4170, Move the
IsMultilineFunctionTemplateDeclaration check inside the
ShouldCheckNamespaceIndentation conditional, after confirming the line is a
namespace-indentation candidate and before calling
CheckItemIndentationInNamespace; remove the unconditional early return so the
expensive scan is skipped for unrelated lines.
cpplint_unittest.py (1)

326-357: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a test case for function template definitions.

The tests added here only cover function template declarations. To ensure that function template definitions (which include a { block) are also correctly exempted from false positive warnings and don't regress in the future, consider adding a test case for them. This will also verify the bug fix proposed in cpplint.py.

🧪 Proposed test addition
         ]
         assert self.GetNamespaceResults(lines) == ""
 
+        lines = [
+            "namespace Test {",
+            "template <typename Type1,",
+            "          typename Type2>",
+            "void TestFunc(const Type1 &var1, Type2 &var2) {",
+            "}",
+            "}  // namespace Test",
+        ]
+        assert self.GetNamespaceResults(lines) == ""
+
     def testNamespaceIndentationIndentedMultilineFunctionTemplateDeclaration(self):
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cpplint_unittest.py` around lines 326 - 357, Add coverage in the namespace
indentation tests for multiline function template definitions, including a body
block with `{`, and verify correctly aligned definitions produce no warnings
while indented definitions report the expected namespace-indentation violations.
Extend the existing test methods near
testNamespaceIndentationMultilineFunctionTemplateDeclaration, reusing
GetNamespaceResults and the established declaration expectations.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cpplint.py`:
- Around line 7408-7415: Update the declaration handling around the existing
function-template detection to isolate the signature by splitting on either “;”
or “{” before checking for braces and matching the function name. Preserve the
existing exemptions for empty declarations, class/enum/struct/using
declarations, and assignment operators while allowing function template
definitions to return the same result as declarations.

---

Nitpick comments:
In `@cpplint_unittest.py`:
- Around line 326-357: Add coverage in the namespace indentation tests for
multiline function template definitions, including a body block with `{`, and
verify correctly aligned definitions produce no warnings while indented
definitions report the expected namespace-indentation violations. Extend the
existing test methods near
testNamespaceIndentationMultilineFunctionTemplateDeclaration, reusing
GetNamespaceResults and the established declaration expectations.

In `@cpplint.py`:
- Around line 4164-4170: Move the IsMultilineFunctionTemplateDeclaration check
inside the ShouldCheckNamespaceIndentation conditional, after confirming the
line is a namespace-indentation candidate and before calling
CheckItemIndentationInNamespace; remove the unconditional early return so the
expensive scan is skipped for unrelated lines.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 484524ee-ff9f-4aef-8dc6-575e23645c1c

📥 Commits

Reviewing files that changed from the base of the PR and between 6be492a and cd67aa7.

📒 Files selected for processing (4)
  • CHANGELOG.rst
  • cpplint.py
  • cpplint_unittest.py
  • samples/boost-sample/simple.def

Comment thread cpplint.py
Comment on lines +7408 to +7415
declaration = declaration.strip()
if not declaration or "{" in declaration.split(";", 1)[0]:
return False
if re.match(r"(?:class|enum|struct|using)\b", declaration):
return False

function = re.search(r"\b(?:operator\s*\S+|~?[A-Za-z_]\w*)\s*\(", declaration)
return function is not None and "=" not in declaration[: function.start()]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Fix false positives for function template definitions.

The current logic correctly exempts function template declarations but fails for function template definitions (which have a { before any ;). Because "{" in declaration.split(";", 1)[0] evaluates to True for definitions, the function returns False, causing the continuation lines of a function template definition's parameters to still trigger false positive indentation warnings.

To support both declarations and definitions, we can split on both ; and { to isolate the signature, and then run the existing checks on it.

🐛 Proposed fix
             declaration = declaration.strip()
-            if not declaration or "{" in declaration.split(";", 1)[0]:
+            if not declaration:
                 return False
-            if re.match(r"(?:class|enum|struct|using)\b", declaration):
+
+            decl_head = re.split(r"[;{]", declaration, 1)[0].strip()
+            if re.match(r"(?:class|enum|struct|using)\b", decl_head):
                 return False
 
-            function = re.search(r"\b(?:operator\s*\S+|~?[A-Za-z_]\w*)\s*\(", declaration)
-            return function is not None and "=" not in declaration[: function.start()]
+            function = re.search(r"\b(?:operator\s*\S+|~?[A-Za-z_]\w*)\s*\(", decl_head)
+            return function is not None and "=" not in decl_head[: function.start()]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
declaration = declaration.strip()
if not declaration or "{" in declaration.split(";", 1)[0]:
return False
if re.match(r"(?:class|enum|struct|using)\b", declaration):
return False
function = re.search(r"\b(?:operator\s*\S+|~?[A-Za-z_]\w*)\s*\(", declaration)
return function is not None and "=" not in declaration[: function.start()]
declaration = declaration.strip()
if not declaration:
return False
decl_head = re.split(r"[;{]", declaration, 1)[0].strip()
if re.match(r"(?:class|enum|struct|using)\b", decl_head):
return False
function = re.search(r"\b(?:operator\s*\S+|~?[A-Za-z_]\w*)\s*\(", decl_head)
return function is not None and "=" not in decl_head[: function.start()]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cpplint.py` around lines 7408 - 7415, Update the declaration handling around
the existing function-template detection to isolate the signature by splitting
on either “;” or “{” before checking for braces and matching the function name.
Preserve the existing exemptions for empty declarations, class/enum/struct/using
declarations, and assignment operators while allowing function template
definitions to return the same result as declarations.

@androvonx95

Copy link
Copy Markdown

I reproduced this on the PR head:

  • Declaration form from Several false positives for whitespace/indent_namespace #401: fixed (no whitespace/indent_namespace on the template continuation).

  • Same shape as a definition still false-positives on the continuation line, because of:

    if not declaration or "{" in declaration.split(";", 1)[0]:
    return False

Please treat the signature as the text before either ; or { (so declarations and definitions both qualify), and add a regression test for a multiline function template definition inside a namespace. Happy to re-review after that.

@breidenbach0 breidenbach0 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have reviewed it locally: "pytest cpplint_unittest.py -k NamespaceIndentation" passes
(6 passed), and a manual repro confirms the behaviour, a
"template <...>\nvoid F(...)" continuation at column 0 no longer triggers
"whitespace/indent_namespace", while the same template indented is still
flagged twice (matches the negative test). The boost-sample delta
(2900->2898, octonion.hpp:675-676) lines up with the intended removal.

The fix is well-targeted: IsMultilineFunctionTemplateDeclaration only
suppresses lines that fall within a multiline "template<...>" span whose
post-">" declaration resolves to a function, and it correctly bails for
class/struct/enum/using so template type declarations stay checked.

One non-blocking suggestion: the class/struct/enum/using exclusion branch
isn't directly covered — adding a case (e.g. a multiline
"template<...>\n class Foo;" indented) asserting it's still flagged would
lock that distinction in against future regressions.

@aaronliu0130 aaronliu0130 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, I'm not sure why we should bail if it's not a function. Was the F+ triggerable with template usage in non-functions too?

Comment thread cpplint_unittest.py
results = self.GetNamespaceResults(lines)
assert results == ""

def testNamespaceIndentationMultilineFunctionTemplateDeclaration(self):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably have a test for definition too.

Comment thread cpplint_unittest.py
]
assert self.GetNamespaceResults(lines) == ""

def testNamespaceIndentationIndentedMultilineFunctionTemplateDeclaration(self):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method split seems unnecessary.

include/boost/math/octonion.hpp:673: Line ends in whitespace. Consider deleting these extra spaces. [whitespace/end_of_line] [4]
include/boost/math/octonion.hpp:674: Do not indent within a namespace. [whitespace/indent_namespace] [4]
include/boost/math/octonion.hpp:675: Do not indent within a namespace. [whitespace/indent_namespace] [4]
include/boost/math/octonion.hpp:676: Do not indent within a namespace. [whitespace/indent_namespace] [4]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

False negative.

Comment thread cpplint.py
"""Checks whether a line continues a function template declaration."""
for start_line in range(linenum - 1, -1, -1):
line = clean_lines.elided[start_line]
if re.search(r"[;{}]", line):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not

Suggested change
if re.search(r"[;{}]", line):
if any((c in set(";{}")) for c in line):

Comment thread cpplint.py
or (isinstance(nesting_state.previous_stack_top, _NamespaceInfo))
)

if IsMultilineFunctionTemplateDeclaration(clean_lines, line):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be part of ShouldCheckNamespaceIndentation()

Comment thread cpplint.py
declaration = clean_lines.elided[end_line][end_pos:]
for next_line in range(end_line + 1, clean_lines.NumLines()):
declaration += " " + clean_lines.elided[next_line].strip()
if re.search(r"[;{}]", declaration):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread cpplint.py
if re.search(r"[;{}]", line):
return False

if template := re.search(r"\btemplate\s*<", line):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems more pythonic to have an early exit instead of an implicit else to me

Comment thread cpplint.py
if not start_line < linenum <= end_line or end_pos < 0:
return False

declaration = clean_lines.elided[end_line][end_pos:]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably strip here instead of at 7408.

Comment thread cpplint.py
break

declaration = declaration.strip()
if not declaration or "{" in declaration.split(";", 1)[0]:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What kind of false positive are we avoiding here? Wouldn't this stop functions with both template and definition from being exempted?

Comment thread cpplint_unittest.py
Comment on lines +346 to +356
lines = [
"namespace Test {",
" template <typename Type1,",
" typename Type2>",
" void TestFunc(const Type1 &var1, Type2 &var2);",
"} // namespace Test",
]
assert self.GetNamespaceResults(lines) == [
"Do not indent within a namespace. [whitespace/indent_namespace] [4]",
"Do not indent within a namespace. [whitespace/indent_namespace] [4]",
]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this does what you think it does. If it was erroring for truly indented, we would see three errors instead of two. It is a compelling design decision (performance for a pretty niche case, and we'd have errored on the starting line already) whether we even want to check for this kind of false negative (see also the Boost sample's F- above), but this makes codereaders thought it still emits on the F- when it does not.

@breidenbach0

Copy link
Copy Markdown

@aaronliu0130, tested it, and you're right: the F+ fires for non-function
templates too. On the current head, continuation lines of a multiline
template<...> still trigger whitespace/indent_namespace for:

 - class Foo {};        -> flagged
 - struct Bar {};       -> flagged
 - using Map = ...;     -> flagged
 - a function *definition* with {  -> flagged (the
   "{" in declaration.split(";", 1)[0] guard suppresses the exemption)

Only the function-declaration case is cleaned up, so the function-only
exemption is the gap, exempting the continuation lines of any multiline
template<...> regardless of what follows would be the consistent fix.

@yangfan-yf-yf yangfan-yf-yf left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even within the function-declaration scope of this change, the template-span scan still misses valid continuation lines when an earlier template parameter contains braces or a semicolon. For example:

namespace Test {
template <typename T,
          bool Valid = requires { typename T::value_type; },
          typename U = void>
void Func();
}  // namespace Test

On cd67aa7, the typename U = void line still emits whitespace/indent_namespace. IsMultilineFunctionTemplateDeclaration() walks backward and returns at [;{}] before finding the enclosing template <, even though those tokens belong to the requires-expression inside the template parameter list.

Please identify the enclosing template-parameter span before treating statement delimiters as boundaries, and add a regression with a later continuation line after a requires-expression or braced default argument. The full suite passes (227 passed), so this boundary currently has no focused coverage.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Several false positives for whitespace/indent_namespace

5 participants