From 38bfc0668f9fb04d1436934528b0fb451ce38e02 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Thu, 6 Aug 2026 22:04:06 +0200 Subject: [PATCH 1/6] Flag useless string expressions in preview --- .../flake8-bugbear/useless-expression.md | 98 +++++++++++++++++++ crates/ruff_linter/src/preview.rs | 5 + .../rules/useless_expression.rs | 23 ++++- 3 files changed, 121 insertions(+), 5 deletions(-) create mode 100644 crates/ruff_linter/resources/mdtest/flake8-bugbear/useless-expression.md diff --git a/crates/ruff_linter/resources/mdtest/flake8-bugbear/useless-expression.md b/crates/ruff_linter/resources/mdtest/flake8-bugbear/useless-expression.md new file mode 100644 index 00000000000000..8570a874113f3c --- /dev/null +++ b/crates/ruff_linter/resources/mdtest/flake8-bugbear/useless-expression.md @@ -0,0 +1,98 @@ +# `useless-expression` (`B018`) + +```toml +lint.preview = true +lint.select = ["B018"] +``` + +## String literals + +In preview, string literals and f-strings are useless expressions unless Ruff +recognizes them as docstrings. + +```py +"""Module docstring.""" + +"Standalone module string." # error: [useless-expression] + +value = 1 +f"Useless module f-string: {value}" # error: [useless-expression] + + +class Class: + """Class docstring.""" + + "Standalone class string." # error: [useless-expression] + f"Useless class f-string: {value}" # error: [useless-expression] + + def method(self): + """Method docstring.""" + + local = 1 + "Standalone function string." # error: [useless-expression] + f"Useless function f-string: {local}" # error: [useless-expression] +``` + +## Strings after overloads + +A string after an overload is not a docstring for the preceding function. + +```py +from typing import overload + + +@overload +def f(value: None) -> str: ... + + +"None overload documentation." # error: [useless-expression] + + +@overload +def f(value: list[str]) -> int: ... + + +"List overload documentation." # error: [useless-expression] + + +def f(value): + return value +``` + +## Section separators + +A standalone string used as a visual separator is still a useless expression. + +```py +def main(): + pass + + +"""MAIN""" # error: [useless-expression] + +if __name__ == "__main__": + main() +``` + +## Attribute docstrings + +Ruff recognizes strings immediately following simple assignments and annotated +assignments at module or class scope as attribute docstrings. A second string +is not part of the attribute docstring. + +```py +module_attribute = 1 +"Module attribute docstring." + +annotated_module_attribute: int +"Annotated module attribute docstring." + + +class Class: + attribute = 1 + "Class attribute docstring." + + annotated_attribute: int + "Annotated class attribute docstring." + "Not an attribute docstring." # error: [useless-expression] +``` diff --git a/crates/ruff_linter/src/preview.rs b/crates/ruff_linter/src/preview.rs index 853063ec23dfee..844eb77270a16d 100644 --- a/crates/ruff_linter/src/preview.rs +++ b/crates/ruff_linter/src/preview.rs @@ -26,6 +26,11 @@ pub(crate) const fn is_s103_extended_dangerous_bits_enabled(settings: &LinterSet settings.preview.is_enabled() } +// https://github.com/astral-sh/ruff/issues/11292 +pub(crate) const fn is_useless_string_expression_enabled(settings: &LinterSettings) -> bool { + settings.preview.is_enabled() +} + // https://github.com/astral-sh/ruff/pull/21382 pub(crate) const fn is_custom_exception_checking_enabled(settings: &LinterSettings) -> bool { settings.preview.is_enabled() diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/useless_expression.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/useless_expression.rs index 1bcd6f638b819f..0a3a1bdbbec364 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/useless_expression.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/useless_expression.rs @@ -5,6 +5,7 @@ use ruff_text_size::Ranged; use crate::Violation; use crate::checkers::ast::Checker; +use crate::preview::is_useless_string_expression_enabled; use crate::rules::flake8_bugbear::helpers::at_last_top_level_expression_in_cell; @@ -26,6 +27,12 @@ use crate::rules::flake8_bugbear::helpers::at_last_top_level_expression_in_cell; /// foo = 1 + 1 /// ``` /// +/// ## Preview +/// When [preview] is enabled, this rule also flags standalone string literals +/// and f-strings that are not docstrings or attribute docstrings. +/// +/// [preview]: https://docs.astral.sh/ruff/preview/ +/// /// ## Notebook behavior /// For Jupyter Notebooks, this rule is not applied to the last top-level expression in a cell. /// This is because it's common to have a notebook cell that ends with an expression, @@ -78,11 +85,17 @@ pub(crate) fn useless_expression(checker: &Checker, value: &Expr) { return; } - // Ignore strings, to avoid false positives with docstrings. - if matches!( - value, - Expr::FString(_) | Expr::StringLiteral(_) | Expr::EllipsisLiteral(_) - ) { + // In stable mode, ignore strings to avoid false positives with docstrings. In preview mode, + // only ignore strings that Ruff recognizes as PEP 257 or attribute docstrings. + if matches!(value, Expr::FString(_) | Expr::StringLiteral(_)) + && (!is_useless_string_expression_enabled(checker.settings()) + || checker.semantic().in_pep_257_docstring() + || checker.semantic().in_attribute_docstring()) + { + return; + } + + if value.is_ellipsis_literal_expr() { return; } From 8401e877716194feacbafab54aa7be2c7ff474fd Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Thu, 6 Aug 2026 22:38:51 +0200 Subject: [PATCH 2/6] Recognize __init__ attribute docstrings --- .../flake8-bugbear/useless-expression.md | 27 ++++++++ crates/ruff_linter/src/checkers/ast/mod.rs | 67 ++++++++++++++++--- 2 files changed, 83 insertions(+), 11 deletions(-) diff --git a/crates/ruff_linter/resources/mdtest/flake8-bugbear/useless-expression.md b/crates/ruff_linter/resources/mdtest/flake8-bugbear/useless-expression.md index 8570a874113f3c..2c9adbee3fb76e 100644 --- a/crates/ruff_linter/resources/mdtest/flake8-bugbear/useless-expression.md +++ b/crates/ruff_linter/resources/mdtest/flake8-bugbear/useless-expression.md @@ -96,3 +96,30 @@ class Class: "Annotated class attribute docstring." "Not an attribute docstring." # error: [useless-expression] ``` + +## Instance attribute docstrings + +Strings following instance attribute assignments directly in `__init__` are +attribute docstrings. Local assignments, nested assignments, and assignments in +other methods do not introduce attribute docstrings. + +```py +class Class: + def __init__(this): + this.attribute = 1 + "Instance attribute docstring." + + this.annotated_attribute: int + "Annotated instance attribute docstring." + + local = 1 + "Not an instance attribute docstring." # error: [useless-expression] + + if local: + this.nested_attribute = 1 + "Not a top-level assignment." # error: [useless-expression] + + def method(self): + self.attribute = 1 + "Not in an `__init__` method." # error: [useless-expression] +``` diff --git a/crates/ruff_linter/src/checkers/ast/mod.rs b/crates/ruff_linter/src/checkers/ast/mod.rs index 029ac85d08f1b8..b82ddd653eaee0 100644 --- a/crates/ruff_linter/src/checkers/ast/mod.rs +++ b/crates/ruff_linter/src/checkers/ast/mod.rs @@ -53,7 +53,7 @@ use ruff_python_parser::semantic_errors::{ use ruff_python_parser::typing::{AnnotationKind, ParsedAnnotation, parse_type_annotation}; use ruff_python_parser::{ParseError, Parsed}; use ruff_python_semantic::all::{DunderAllDefinition, DunderAllFlags}; -use ruff_python_semantic::analyze::{imports, typing}; +use ruff_python_semantic::analyze::{imports, typing, visibility}; use ruff_python_semantic::{ BindingFlags, BindingId, BindingKind, Exceptions, Export, FromImport, GeneratorKind, Globals, Import, Module, ModuleKind, ModuleSource, NodeId, ScopeId, ScopeKind, SemanticModel, @@ -178,6 +178,10 @@ pub(crate) enum ExpectedDocstringKind { /// class Foo: /// b = 1 /// """This is the docstring for `Foo.b` class variable.""" + /// + /// def __init__(self) -> None: + /// self.c = 1 + /// """This is the docstring for the `c` instance variable.""" /// ``` Attribute, } @@ -666,6 +670,34 @@ impl<'a> Checker<'a> { self.context } + /// Return the instance parameter when visiting a statement directly in an `__init__` method. + fn init_instance_parameter(&self) -> Option<&'a str> { + let semantic = self.semantic(); + let scope = semantic.current_scope(); + let ScopeKind::Function(function) = scope.kind else { + return None; + }; + + if !visibility::is_init(&function.name) + || !semantic + .first_non_type_parent_scope(scope) + .is_some_and(|parent| parent.kind.is_class()) + || !matches!( + semantic.current_statement_parent(), + Some(Stmt::FunctionDef(parent)) if std::ptr::eq(parent, function) + ) + { + return None; + } + + function + .parameters + .posonlyargs + .first() + .or_else(|| function.parameters.args.first()) + .map(|parameter| parameter.name().as_str()) + } + /// Return the current [`DocstringState`]. pub(crate) fn docstring_state(&self) -> DocstringState { self.docstring_state @@ -1636,20 +1668,33 @@ impl<'a> Visitor<'a> for Checker<'a> { _ => visitor::walk_stmt(self, stmt), } - if self.semantic().at_top_level() || self.semantic().current_scope().kind.is_class() { - match stmt { - Stmt::Assign(ast::StmtAssign { targets, .. }) => { - if let [Expr::Name(_)] = targets.as_slice() { - self.docstring_state = - DocstringState::Expected(ExpectedDocstringKind::Attribute); - } - } - Stmt::AnnAssign(ast::StmtAnnAssign { target, .. }) if target.is_name_expr() => { + let in_module_or_class = + self.semantic().at_top_level() || self.semantic().current_scope().kind.is_class(); + let init_instance_parameter = self.init_instance_parameter(); + let is_attribute_target = |target: &Expr| { + (in_module_or_class && target.is_name_expr()) + || init_instance_parameter.is_some_and(|instance| { + matches!( + target, + Expr::Attribute(attribute) + if attribute.value.as_name_expr().is_some_and(|name| name.id == instance) + ) + }) + }; + + match stmt { + Stmt::Assign(ast::StmtAssign { targets, .. }) => { + if let [target] = targets.as_slice() + && is_attribute_target(target) + { self.docstring_state = DocstringState::Expected(ExpectedDocstringKind::Attribute); } - _ => {} } + Stmt::AnnAssign(ast::StmtAnnAssign { target, .. }) if is_attribute_target(target) => { + self.docstring_state = DocstringState::Expected(ExpectedDocstringKind::Attribute); + } + _ => {} } // Step 3: Clean-up From e32fdfcf849327f0563dc5b7f0a74c9351da6a23 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Thu, 6 Aug 2026 22:46:27 +0200 Subject: [PATCH 3/6] Narrow __init__ docstring detection --- crates/ruff_linter/src/checkers/ast/mod.rs | 67 +++++++++++++--------- 1 file changed, 39 insertions(+), 28 deletions(-) diff --git a/crates/ruff_linter/src/checkers/ast/mod.rs b/crates/ruff_linter/src/checkers/ast/mod.rs index b82ddd653eaee0..43fc33f9a0cac9 100644 --- a/crates/ruff_linter/src/checkers/ast/mod.rs +++ b/crates/ruff_linter/src/checkers/ast/mod.rs @@ -670,12 +670,13 @@ impl<'a> Checker<'a> { self.context } - /// Return the instance parameter when visiting a statement directly in an `__init__` method. - fn init_instance_parameter(&self) -> Option<&'a str> { + /// Return `true` if the statement assigns an instance attribute directly in an `__init__` + /// method. + fn is_init_instance_attribute_assignment(&self, stmt: &Stmt) -> bool { let semantic = self.semantic(); let scope = semantic.current_scope(); let ScopeKind::Function(function) = scope.kind else { - return None; + return false; }; if !visibility::is_init(&function.name) @@ -687,15 +688,34 @@ impl<'a> Checker<'a> { Some(Stmt::FunctionDef(parent)) if std::ptr::eq(parent, function) ) { - return None; + return false; } - function + let Some(instance) = function .parameters .posonlyargs .first() .or_else(|| function.parameters.args.first()) .map(|parameter| parameter.name().as_str()) + else { + return false; + }; + + let is_instance_attribute = |target: &Expr| { + matches!( + target, + Expr::Attribute(attribute) + if attribute.value.as_name_expr().is_some_and(|name| name.id == instance) + ) + }; + + match stmt { + Stmt::Assign(ast::StmtAssign { targets, .. }) => { + matches!(targets.as_slice(), [target] if is_instance_attribute(target)) + } + Stmt::AnnAssign(ast::StmtAnnAssign { target, .. }) => is_instance_attribute(target), + _ => false, + } } /// Return the current [`DocstringState`]. @@ -1668,33 +1688,24 @@ impl<'a> Visitor<'a> for Checker<'a> { _ => visitor::walk_stmt(self, stmt), } - let in_module_or_class = - self.semantic().at_top_level() || self.semantic().current_scope().kind.is_class(); - let init_instance_parameter = self.init_instance_parameter(); - let is_attribute_target = |target: &Expr| { - (in_module_or_class && target.is_name_expr()) - || init_instance_parameter.is_some_and(|instance| { - matches!( - target, - Expr::Attribute(attribute) - if attribute.value.as_name_expr().is_some_and(|name| name.id == instance) - ) - }) - }; - - match stmt { - Stmt::Assign(ast::StmtAssign { targets, .. }) => { - if let [target] = targets.as_slice() - && is_attribute_target(target) - { + if self.semantic().at_top_level() || self.semantic().current_scope().kind.is_class() { + match stmt { + Stmt::Assign(ast::StmtAssign { targets, .. }) => { + if let [Expr::Name(_)] = targets.as_slice() { + self.docstring_state = + DocstringState::Expected(ExpectedDocstringKind::Attribute); + } + } + Stmt::AnnAssign(ast::StmtAnnAssign { target, .. }) if target.is_name_expr() => { self.docstring_state = DocstringState::Expected(ExpectedDocstringKind::Attribute); } + _ => {} } - Stmt::AnnAssign(ast::StmtAnnAssign { target, .. }) if is_attribute_target(target) => { - self.docstring_state = DocstringState::Expected(ExpectedDocstringKind::Attribute); - } - _ => {} + } + + if self.is_init_instance_attribute_assignment(stmt) { + self.docstring_state = DocstringState::Expected(ExpectedDocstringKind::Attribute); } // Step 3: Clean-up From cc819a02b5700f685f609d78aad8e6a365c1944f Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Thu, 6 Aug 2026 22:53:38 +0200 Subject: [PATCH 4/6] Resolve __init__ receiver bindings --- .../mdtest/flake8-bugbear/useless-expression.md | 4 ++++ crates/ruff_linter/src/checkers/ast/mod.rs | 17 +++++++++++------ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/crates/ruff_linter/resources/mdtest/flake8-bugbear/useless-expression.md b/crates/ruff_linter/resources/mdtest/flake8-bugbear/useless-expression.md index 2c9adbee3fb76e..e7b1f08ffbd0c7 100644 --- a/crates/ruff_linter/resources/mdtest/flake8-bugbear/useless-expression.md +++ b/crates/ruff_linter/resources/mdtest/flake8-bugbear/useless-expression.md @@ -119,6 +119,10 @@ class Class: this.nested_attribute = 1 "Not a top-level assignment." # error: [useless-expression] + this = object() + this.rebound_attribute = 1 + "Not an instance attribute docstring." # error: [useless-expression] + def method(self): self.attribute = 1 "Not in an `__init__` method." # error: [useless-expression] diff --git a/crates/ruff_linter/src/checkers/ast/mod.rs b/crates/ruff_linter/src/checkers/ast/mod.rs index 43fc33f9a0cac9..4da040b2f529ce 100644 --- a/crates/ruff_linter/src/checkers/ast/mod.rs +++ b/crates/ruff_linter/src/checkers/ast/mod.rs @@ -696,17 +696,22 @@ impl<'a> Checker<'a> { .posonlyargs .first() .or_else(|| function.parameters.args.first()) - .map(|parameter| parameter.name().as_str()) else { return false; }; let is_instance_attribute = |target: &Expr| { - matches!( - target, - Expr::Attribute(attribute) - if attribute.value.as_name_expr().is_some_and(|name| name.id == instance) - ) + let Some(name) = target + .as_attribute_expr() + .and_then(|attribute| attribute.value.as_name_expr()) + else { + return false; + }; + let Some(binding_id) = semantic.resolve_name(name) else { + return false; + }; + let binding = semantic.binding(binding_id); + binding.kind.is_argument() && binding.range == instance.parameter.name.range() }; match stmt { From 4965c26a7c6d4e7e3209723054e6d06b5551332d Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Thu, 6 Aug 2026 23:02:57 +0200 Subject: [PATCH 5/6] Recognize PEP 695 type alias docstrings --- .../flake8-bugbear/useless-expression.md | 18 +++++++++++++++--- crates/ruff_linter/src/checkers/ast/mod.rs | 6 ++++++ .../flake8_bugbear/rules/useless_expression.rs | 2 ++ 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/crates/ruff_linter/resources/mdtest/flake8-bugbear/useless-expression.md b/crates/ruff_linter/resources/mdtest/flake8-bugbear/useless-expression.md index e7b1f08ffbd0c7..7663fff9bae85f 100644 --- a/crates/ruff_linter/resources/mdtest/flake8-bugbear/useless-expression.md +++ b/crates/ruff_linter/resources/mdtest/flake8-bugbear/useless-expression.md @@ -76,9 +76,9 @@ if __name__ == "__main__": ## Attribute docstrings -Ruff recognizes strings immediately following simple assignments and annotated -assignments at module or class scope as attribute docstrings. A second string -is not part of the attribute docstring. +Ruff recognizes strings immediately following simple assignments, annotated +assignments, and `type` statements at module or class scope as attribute +docstrings. A second string is not part of the attribute docstring. ```py module_attribute = 1 @@ -87,6 +87,9 @@ module_attribute = 1 annotated_module_attribute: int "Annotated module attribute docstring." +type ModuleAlias = int +"Module type alias docstring." + class Class: attribute = 1 @@ -94,6 +97,15 @@ class Class: annotated_attribute: int "Annotated class attribute docstring." + + type ClassAlias = str + "Class type alias docstring." + + "Not an attribute docstring." # error: [useless-expression] + + +def function(): + type LocalAlias = bytes "Not an attribute docstring." # error: [useless-expression] ``` diff --git a/crates/ruff_linter/src/checkers/ast/mod.rs b/crates/ruff_linter/src/checkers/ast/mod.rs index 4da040b2f529ce..04cb6efa323731 100644 --- a/crates/ruff_linter/src/checkers/ast/mod.rs +++ b/crates/ruff_linter/src/checkers/ast/mod.rs @@ -1705,6 +1705,12 @@ impl<'a> Visitor<'a> for Checker<'a> { self.docstring_state = DocstringState::Expected(ExpectedDocstringKind::Attribute); } + // Follow Pyright's convention for documenting PEP 695 type aliases: + // https://discuss.python.org/t/docstrings-for-new-type-aliases-as-defined-in-pep-695/39816 + Stmt::TypeAlias(_) => { + self.docstring_state = + DocstringState::Expected(ExpectedDocstringKind::Attribute); + } _ => {} } } diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/useless_expression.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/useless_expression.rs index 0a3a1bdbbec364..53d3904bc78da4 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/useless_expression.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/useless_expression.rs @@ -31,6 +31,8 @@ use crate::rules::flake8_bugbear::helpers::at_last_top_level_expression_in_cell; /// When [preview] is enabled, this rule also flags standalone string literals /// and f-strings that are not docstrings or attribute docstrings. /// +/// Strings following PEP 695 `type` statements are also treated as attribute docstrings. +/// /// [preview]: https://docs.astral.sh/ruff/preview/ /// /// ## Notebook behavior From 01f7418bd8ca2028051e025e58411daee1361f2b Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Fri, 7 Aug 2026 07:07:07 +0000 Subject: [PATCH 6/6] Preserve side-effectful f-string expressions --- .../flake8-bugbear/useless-expression.md | 31 ++++++++++++++----- .../rules/useless_expression.rs | 9 +++--- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/crates/ruff_linter/resources/mdtest/flake8-bugbear/useless-expression.md b/crates/ruff_linter/resources/mdtest/flake8-bugbear/useless-expression.md index 7663fff9bae85f..a91803a705fab5 100644 --- a/crates/ruff_linter/resources/mdtest/flake8-bugbear/useless-expression.md +++ b/crates/ruff_linter/resources/mdtest/flake8-bugbear/useless-expression.md @@ -7,30 +7,47 @@ lint.select = ["B018"] ## String literals -In preview, string literals and f-strings are useless expressions unless Ruff -recognizes them as docstrings. +In preview, string literals and side-effect-free f-strings are useless +expressions unless Ruff recognizes them as docstrings. ```py """Module docstring.""" "Standalone module string." # error: [useless-expression] -value = 1 -f"Useless module f-string: {value}" # error: [useless-expression] +f"Useless module f-string: {1}" # error: [useless-expression] class Class: """Class docstring.""" "Standalone class string." # error: [useless-expression] - f"Useless class f-string: {value}" # error: [useless-expression] + f"Useless class f-string: {1}" # error: [useless-expression] def method(self): """Method docstring.""" - local = 1 "Standalone function string." # error: [useless-expression] - f"Useless function f-string: {local}" # error: [useless-expression] + f"Useless function f-string: {1}" # error: [useless-expression] +``` + +## F-string formatting side effects + +Interpolating a value can call a user-defined `__format__` method, including +when the value appears inside another interpolation's format specification. +These f-strings are not useless expressions because formatting the value has +an observable side effect. + +```py +class Formatted: + def __format__(self, spec: str) -> str: + print("formatted") + return "1" + + +value = Formatted() +f"{value}" +f"{1:{value}}" ``` ## Strings after overloads diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/useless_expression.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/useless_expression.rs index 53d3904bc78da4..49ae591fb0cb0b 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/useless_expression.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/useless_expression.rs @@ -1,6 +1,6 @@ use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::Expr; -use ruff_python_ast::helpers::contains_effect; +use ruff_python_ast::helpers::side_effect; use ruff_text_size::Ranged; use crate::Violation; @@ -29,7 +29,7 @@ use crate::rules::flake8_bugbear::helpers::at_last_top_level_expression_in_cell; /// /// ## Preview /// When [preview] is enabled, this rule also flags standalone string literals -/// and f-strings that are not docstrings or attribute docstrings. +/// and side-effect-free f-strings that are not docstrings or attribute docstrings. /// /// Strings following PEP 695 `type` statements are also treated as attribute docstrings. /// @@ -111,8 +111,9 @@ pub(crate) fn useless_expression(checker: &Checker, value: &Expr) { return; } - // Ignore statements that have side effects. - if contains_effect(value, |id| checker.semantic().has_builtin_binding(id)) { + // Formatting an interpolated value can invoke user-defined `__format__` or `__str__` methods. + let effect = side_effect(value, |id| checker.semantic().has_builtin_binding(id)); + if effect.is_present() || (value.is_f_string_expr() && !effect.is_absent()) { // Flag attributes as useless expressions, even if they're attached to calls or other // expressions. if value.is_attribute_expr() {