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..58bd273 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,6 +3969,16 @@ def CheckForNonStandardConstructs(filename, clean_lines, linenum, nesting_state, if explicit_constructor_match: is_marked_explicit = explicit_constructor_match.group(1) + constructor_suffix = _GetConstructorSuffix( + clean_lines, linenum, explicit_constructor_match.end() + ) + is_deleted = bool( + re.search( + r"=\s*delete\s*(?:\(.*\))?\s*;\s*$", + constructor_suffix, + re.DOTALL, + ) + ) if not explicit_constructor_match.group(2): constructor_args = [] @@ -3990,6 +4037,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..6f6e705 100755 --- a/cpplint_unittest.py +++ b/cpplint_unittest.py @@ -1639,6 +1639,68 @@ 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; + };""", + "", + ) + # 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) noexcept(noexcept(T{})) = delete; + };""", + "", + ) + self.TestMultiLineLint( + """ + class Foo { + template + Foo(T value) requires Integral = delete; + };""", + "", + ) + 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; + };""", + "", + ) + self.TestMultiLineLint( + """ + class Foo { + Foo(int f) = delete("use Bar instead"); + };""", + "", + ) + self.TestMultiLineLint( + """ + class Foo { + Foo(int f) + = delete; + };""", + "", + ) # missing explicit is bad, even with whitespace self.TestMultiLineLint( """