From bce7f599d86ef0e4d0faf8aa74a6254ad8b32240 Mon Sep 17 00:00:00 2001 From: Tushar Malpani Date: Wed, 29 Jul 2026 18:05:35 +0000 Subject: [PATCH] fix: handle templated lambdas in semicolon check --- CHANGELOG.rst | 1 + cpplint.py | 75 ++++++++++++++++++++++++++++++++++++++++----- cpplint_unittest.py | 41 +++++++++++++++++++++++++ 3 files changed, 109 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4127d4e..ea94140 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,7 @@ TBA === * Fixed a whitespace/newline false positive for control conditions containing lambdas. (#410) +* Fixed a readability/braces false positive for C++20 templated lambdas. (#385) 2.0.2 (2025-04-08) =========== diff --git a/cpplint.py b/cpplint.py index 07687d2..33f7566 100755 --- a/cpplint.py +++ b/cpplint.py @@ -5078,6 +5078,50 @@ def CheckBraces(filename, clean_lines, linenum, error): ) +def MatchLambdaCapture(clean_lines, linenum, line_prefix): + """Matches a lambda capture before an optional template parameter list.""" + capture = re.match(r"^(.*\])\s*$", line_prefix) + if capture or not re.search(r">\s*$", line_prefix): + return capture + + template_end = line_prefix.rfind(">") + template_start = ReverseCloseExpression(clean_lines, linenum, template_end) + if template_start[2] < 0: + return None + + capture_prefix = template_start[0][0 : template_start[2]] + if not capture_prefix.strip() and template_start[1] > 0: + capture_prefix = GetPreviousNonBlankLine(clean_lines, template_start[1])[0] + return re.match(r"^(.*\])\s*$", capture_prefix) + + +def MatchLambdaRequiresClause(clean_lines, linenum): + """Matches the lambda capture preceding a multiline requires-clause.""" + while linenum >= 0: + line = clean_lines.elided[linenum] + if requires := re.search(r"\brequires\b", line): + capture = MatchLambdaCapture( + clean_lines, + linenum, + line[0 : requires.start()], + ) + if capture: + return capture + template_line, template_linenum = GetPreviousNonBlankLine( + clean_lines, + linenum, + ) + return MatchLambdaCapture( + clean_lines, + template_linenum, + template_line, + ) + if re.search(r"[;{}]\s*$", line): + return None + _, linenum = GetPreviousNonBlankLine(clean_lines, linenum) + return None + + def CheckTrailingSemicolon(filename, clean_lines, linenum, error): """Looks for redundant trailing semicolon. @@ -5168,7 +5212,11 @@ def CheckTrailingSemicolon(filename, clean_lines, linenum, error): if opening_parenthesis[2] > -1: line_prefix = opening_parenthesis[0][0 : opening_parenthesis[2]] macro = re.search(r"\b([A-Z_][A-Z0-9_]*)\s*$", line_prefix) - func = re.match(r"^(.*\])\s*$", line_prefix) + func = MatchLambdaCapture( + clean_lines, + opening_parenthesis[1], + line_prefix, + ) if ( ( macro @@ -5192,13 +5240,24 @@ def CheckTrailingSemicolon(filename, clean_lines, linenum, error): or re.search(r"\s+=\s*$", line_prefix) ): match = None - if ( - match - and opening_parenthesis[1] > 1 - and re.search(r"\]\s*$", clean_lines.elided[opening_parenthesis[1] - 1]) - ): - # Multi-line lambda-expression - match = None + if match and opening_parenthesis[1] > 0: + previous_line, previous_linenum = GetPreviousNonBlankLine( + clean_lines, + opening_parenthesis[1], + ) + func = MatchLambdaCapture( + clean_lines, + previous_linenum, + previous_line, + ) + if not func: + func = MatchLambdaRequiresClause( + clean_lines, + previous_linenum, + ) + if func and not re.search(r"\boperator\s*\[\s*\]", func.group(1)): + # Multi-line lambda-expression + match = None else: # Try matching cases 2-3. diff --git a/cpplint_unittest.py b/cpplint_unittest.py index d568251..3a797d3 100755 --- a/cpplint_unittest.py +++ b/cpplint_unittest.py @@ -3166,9 +3166,50 @@ def testLambda(self): self.TestLint("auto x = []() {};", "") self.TestLint("return []() {};", "") self.TestMultiLineLint("auto x = []() {\n};\n", "") + self.TestMultiLineLint( + "int main() {\n auto identity = [](T&& t) {\n return t;\n };\n}\n", + "", + ) + self.TestMultiLineLint( + "auto identity = []<\n typename T\n>(T&& t) {\n return t;\n};\n", + "", + ) + self.TestMultiLineLint( + "auto identity = []\n(T&& t) {\n return t;\n};\n", + "", + ) + self.TestMultiLineLint( + "auto identity = []\n\n(T&& t) {\n return t;\n};\n", + "", + ) + self.TestMultiLineLint( + "auto identity = []\n" + " requires std::integral\n" + "(T&& t) {\n" + " return t;\n" + "};\n", + "", + ) + self.TestMultiLineLint( + "auto identity = []\n" + " requires std::integral &&\n" + " std::copyable\n" + "(T&& t) {\n" + " return t;\n" + "};\n", + "", + ) self.TestLint( "int operator[](int x) {};", "You don't need a ; after a } [readability/braces] [4]" ) + self.TestMultiLineLint( + "int operator[]\n(int x) {};", + "You don't need a ; after a } [readability/braces] [4]", + ) + self.TestMultiLineLint( + "template \n requires C\nvoid Function(T value) {};", + "You don't need a ; after a } [readability/braces] [4]", + ) self.TestMultiLineLint("auto x = [&a,\nb]() {};", "") self.TestMultiLineLint("auto x = [&a,\nb]\n() {};", "")