From 00a4e14ce28186f7907878d5a2db4cc2b67f1acd Mon Sep 17 00:00:00 2001 From: yangfan-yf-yf Date: Sat, 25 Jul 2026 14:30:57 +0800 Subject: [PATCH 1/5] fix(runtime/explicit): skip deleted constructors Fixes #386 --- CHANGELOG.rst | 1 + cpplint.py | 4 +++- cpplint_unittest.py | 8 ++++++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c8429e9..2e516df 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,7 @@ TBA * Fixed a whitespace/newline false positive for control conditions containing lambdas. (#410) * We now error on relative include paths (``./``, ``../``). (#432) * This makes ``#include "./foo.h"`` produce two separate errors: that foo.cpp should include foo.h and that relative paths are not allowed. +* Deleted single-argument constructors no longer trigger ``runtime/explicit``. (#386) 2.0.2 (2025-04-08) =========== diff --git a/cpplint.py b/cpplint.py index 41c3bb0..991b15c 100755 --- a/cpplint.py +++ b/cpplint.py @@ -3926,12 +3926,13 @@ def CheckForNonStandardConstructs(filename, clean_lines, linenum, nesting_state, explicit_constructor_match = re.match( r"\s+(?:(?:inline|constexpr)\s+)*(explicit\s+)?" rf"(?:(?:inline|constexpr)\s+)*{re.escape(base_classname)}\s*" - r"\(((?:[^()]|\([^()]*\))*)\)", + r"\(((?:[^()]|\([^()]*\))*)\)\s*(=\s*delete\s*;)?", line, ) if explicit_constructor_match: is_marked_explicit = explicit_constructor_match.group(1) + is_deleted = bool(explicit_constructor_match.group(3)) if not explicit_constructor_match.group(2): constructor_args = [] @@ -3990,6 +3991,7 @@ def CheckForNonStandardConstructs(filename, clean_lines, linenum, nesting_state, if ( not is_marked_explicit + and not is_deleted and onearg_constructor and not initializer_list_constructor and not copy_constructor diff --git a/cpplint_unittest.py b/cpplint_unittest.py index 032ecc5..ff5c0b3 100755 --- a/cpplint_unittest.py +++ b/cpplint_unittest.py @@ -1639,6 +1639,14 @@ class Foo { };""", "Single-parameter constructors should be marked explicit. [runtime/explicit] [4]", ) + # Deleted constructors cannot be called implicitly. + self.TestMultiLineLint( + """ + class Foo { + Foo(int f) = delete; + };""", + "", + ) # missing explicit is bad, even with whitespace self.TestMultiLineLint( """ From fb92da6836a4fe4c9b5cc3aa610c06f0c3df0520 Mon Sep 17 00:00:00 2001 From: yangfan-yf-yf Date: Sat, 25 Jul 2026 16:35:34 +0800 Subject: [PATCH 2/5] fix: detect deleted constructor suffixes --- cpplint.py | 18 ++++++++++++++++-- cpplint_unittest.py | 17 +++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/cpplint.py b/cpplint.py index 991b15c..42c1a08 100755 --- a/cpplint.py +++ b/cpplint.py @@ -3926,13 +3926,27 @@ def CheckForNonStandardConstructs(filename, clean_lines, linenum, nesting_state, explicit_constructor_match = re.match( r"\s+(?:(?:inline|constexpr)\s+)*(explicit\s+)?" rf"(?:(?:inline|constexpr)\s+)*{re.escape(base_classname)}\s*" - r"\(((?:[^()]|\([^()]*\))*)\)\s*(=\s*delete\s*;)?", + r"\(((?:[^()]|\([^()]*\))*)\)", line, ) if explicit_constructor_match: is_marked_explicit = explicit_constructor_match.group(1) - is_deleted = bool(explicit_constructor_match.group(3)) + constructor_suffix = line[explicit_constructor_match.end() :] + next_line = linenum + 1 + while ( + ";" not in constructor_suffix + and "{" not in constructor_suffix + and next_line < clean_lines.NumLines() + ): + constructor_suffix += clean_lines.lines[next_line] + next_line += 1 + is_deleted = bool( + re.match( + r"\s*(?:noexcept(?:\s*\([^;{}]*\))?\s*)?=\s*delete\s*;", + constructor_suffix, + ) + ) if not explicit_constructor_match.group(2): constructor_args = [] diff --git a/cpplint_unittest.py b/cpplint_unittest.py index ff5c0b3..9134f99 100755 --- a/cpplint_unittest.py +++ b/cpplint_unittest.py @@ -1644,6 +1644,23 @@ class Foo { """ class Foo { Foo(int f) = delete; + };""", + "", + ) + # Deleted constructors may include a noexcept suffix or split the + # deleted marker across lines. + self.TestMultiLineLint( + """ + class Foo { + Foo(int f) noexcept = delete; + };""", + "", + ) + self.TestMultiLineLint( + """ + class Foo { + Foo(int f) + = delete; };""", "", ) From 01b808d7a9e773d118a24b8532672372257e894d Mon Sep 17 00:00:00 2001 From: yangfan-yf-yf Date: Sat, 25 Jul 2026 16:41:37 +0800 Subject: [PATCH 3/5] fix: handle nested deleted constructor suffixes --- cpplint.py | 52 ++++++++++++++++++++++++++++++++++++--------- cpplint_unittest.py | 7 ++++++ 2 files changed, 49 insertions(+), 10 deletions(-) diff --git a/cpplint.py b/cpplint.py index 42c1a08..f596124 100755 --- a/cpplint.py +++ b/cpplint.py @@ -3786,6 +3786,43 @@ def InnermostClass(self): return None +def _GetConstructorSuffix(clean_lines, linenum, match_end): + """Returns the constructor declaration suffix up to its top-level terminator.""" + constructor_suffix = [] + paren_depth = 0 + bracket_depth = 0 + brace_depth = 0 + suffix_line = clean_lines.elided[linenum][match_end:] + next_line = linenum + 1 + + while True: + for char in suffix_line: + if char == ";" and not paren_depth and not bracket_depth and not brace_depth: + constructor_suffix.append(char) + return "".join(constructor_suffix) + if char == "{" and not paren_depth and not bracket_depth and not brace_depth: + return "".join(constructor_suffix) + + constructor_suffix.append(char) + if char == "(": + paren_depth += 1 + elif char == ")": + paren_depth -= 1 + elif char == "[": + bracket_depth += 1 + elif char == "]": + bracket_depth -= 1 + elif char == "{": + brace_depth += 1 + elif char == "}": + brace_depth -= 1 + + if next_line >= clean_lines.NumLines(): + return "".join(constructor_suffix) + suffix_line = "\n" + clean_lines.elided[next_line] + next_line += 1 + + def CheckForNonStandardConstructs(filename, clean_lines, linenum, nesting_state, error): r"""Logs an error if we see certain non-ANSI constructs ignored by gcc-2. @@ -3932,19 +3969,14 @@ def CheckForNonStandardConstructs(filename, clean_lines, linenum, nesting_state, if explicit_constructor_match: is_marked_explicit = explicit_constructor_match.group(1) - constructor_suffix = line[explicit_constructor_match.end() :] - next_line = linenum + 1 - while ( - ";" not in constructor_suffix - and "{" not in constructor_suffix - and next_line < clean_lines.NumLines() - ): - constructor_suffix += clean_lines.lines[next_line] - next_line += 1 + constructor_suffix = _GetConstructorSuffix( + clean_lines, linenum, explicit_constructor_match.end() + ) is_deleted = bool( re.match( - r"\s*(?:noexcept(?:\s*\([^;{}]*\))?\s*)?=\s*delete\s*;", + r"\s*(?:noexcept(?:\s*\(.*\))?\s*)?=\s*delete\s*;", constructor_suffix, + re.DOTALL, ) ) diff --git a/cpplint_unittest.py b/cpplint_unittest.py index 9134f99..5bba895 100755 --- a/cpplint_unittest.py +++ b/cpplint_unittest.py @@ -1658,6 +1658,13 @@ class Foo { ) self.TestMultiLineLint( """ + class Foo { + Foo(int f) noexcept(noexcept(T{})) = delete; + };""", + "", + ) + self.TestMultiLineLint( + """ class Foo { Foo(int f) = delete; From 453f31e58eae7326f3270767b1aef756f4fd30a4 Mon Sep 17 00:00:00 2001 From: yangfan-yf-yf Date: Sun, 26 Jul 2026 07:44:11 +0800 Subject: [PATCH 4/5] fix: recognize additional deleted constructor suffixes --- cpplint.py | 4 ++-- cpplint_unittest.py | 22 ++++++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/cpplint.py b/cpplint.py index f596124..58bd273 100755 --- a/cpplint.py +++ b/cpplint.py @@ -3973,8 +3973,8 @@ def CheckForNonStandardConstructs(filename, clean_lines, linenum, nesting_state, clean_lines, linenum, explicit_constructor_match.end() ) is_deleted = bool( - re.match( - r"\s*(?:noexcept(?:\s*\(.*\))?\s*)?=\s*delete\s*;", + re.search( + r"=\s*delete\s*(?:\(.*\))?\s*;\s*$", constructor_suffix, re.DOTALL, ) diff --git a/cpplint_unittest.py b/cpplint_unittest.py index 5bba895..65e17f5 100755 --- a/cpplint_unittest.py +++ b/cpplint_unittest.py @@ -1665,6 +1665,28 @@ class Foo { ) self.TestMultiLineLint( """ + class Foo { + template + Foo(T value) requires Integral = delete; + };""", + "", + ) + self.TestMultiLineLint( + """ + class Foo { + Foo(int f) throw() = delete; + };""", + "", + ) + self.TestMultiLineLint( + """ + class Foo { + Foo(int f) = delete("use Bar instead"); + };""", + "", + ) + self.TestMultiLineLint( + """ class Foo { Foo(int f) = delete; From c7d895cf156750c127aa58dc05d195eb5a215683 Mon Sep 17 00:00:00 2001 From: yangfan-yf-yf Date: Tue, 4 Aug 2026 21:16:46 +0800 Subject: [PATCH 5/5] test: cover non-deleted requires constructor --- cpplint_unittest.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cpplint_unittest.py b/cpplint_unittest.py index 65e17f5..6f6e705 100755 --- a/cpplint_unittest.py +++ b/cpplint_unittest.py @@ -1673,6 +1673,14 @@ class Foo { ) self.TestMultiLineLint( """ + class Foo { + template + Foo(T value) requires Integral; + };""", + "Single-parameter constructors should be marked explicit. [runtime/explicit] [4]", + ) + self.TestMultiLineLint( + """ class Foo { Foo(int f) throw() = delete; };""",