[flake8-use-pathlib] Mark PTH101 fix as unsafe when first argument is a class attribute annotated as int#25086
Conversation
… an attribute access When `os.chmod` is called with an attribute access (e.g., `self.fd`), the linter cannot determine statically whether the attribute is a file descriptor or a path. Apply the fix as unsafe in this case to avoid converting working code into code that raises `TypeError` at runtime. Refs astral-sh#17699
|
I think we could change if let Expr::Attribute(_) = expr
&& let Some(binding_id) = semantic.lookup_attribute(expr)
{
return typing::is_int(semantic.binding(binding_id), semantic);
}But we would still not support class C:
def __init__(self, fd: int):
self.fd = fd
def f(self):
os.chmod(self.fd, 0o644)I'm also not sure if it's worth making all fixes where the type isn't known as import os
fd = compute_path()
os.chmod(fd, 0o644)even though the type of |
ntBre
left a comment
There was a problem hiding this comment.
Thanks! I left one comment on the tests, but I'll otherwise mark this as a draft until Micha's feedback is addressed. Feel free to open it for review again when it's ready for another look.
| # https://github.com/astral-sh/ruff/issues/17699 | ||
| # Attribute access in the first argument: the type cannot be statically determined, | ||
| # so the fix should be marked as unsafe (the attribute may be a file descriptor). | ||
| class _AttrHolder: | ||
| fd: int = 0 | ||
| name: str = "" | ||
|
|
||
| _holder = _AttrHolder() | ||
| os.chmod(_holder.fd, 0o644) | ||
| os.chmod(_holder.name, 0o644) | ||
|
|
There was a problem hiding this comment.
Could you move any additions to the file to the bottom of the file (or a new one)? This avoids shifting all of the other snapshots down and increasing the size of the diff.
…kup_attribute` Extend `is_file_descriptor` to resolve attribute expressions that `SemanticModel::lookup_attribute` maps to a binding — concretely, class attributes accessed via the class itself (e.g., `MyClass.fd`). When the resolved binding is `int`, the PTH rule bails out, mirroring the behavior for `Name` expressions with `int` annotations. Test additions are placed at the bottom of `full_name.py` to keep the snapshot diff focused on the new cases. Refs astral-sh#17699
|
Hi! Updated:
Let me know if you need anything! |
|
PTH101 fix as unsafe when first argument is a class attribute annotated as int
PTH101 fix as unsafe when first argument is a class attribute annotated as intflake8-use-pathlib] Mark PTH101 fix as unsafe when first argument is a class attribute annotated as int`
flake8-use-pathlib] Mark PTH101 fix as unsafe when first argument is a class attribute annotated as int`flake8-use-pathlib] Mark PTH101 fix as unsafe when first argument is a class attribute annotated as int
…t is a class attribute annotated as `int` (astral-sh#25086)
…t is a class attribute annotated as `int` (astral-sh#25086)
Summary
Closes #24895 (closed as duplicate of #17699).
The autofix for
PTH101(os.chmod→Path.chmod) currently producesruntime errors when the first argument is an attribute access whose underlying
type is an
int(file descriptor). For example:The cause is in
is_file_descriptor(inflake8_use_pathlib/helpers.rs),which extracts a
Namefrom the argument viaget_name_exprto look up itsbinding type.
get_name_expronly handlesExpr::NameandExpr::Call—it returns
NoneforExpr::Attribute, so the helper cannot tell thatself.fdmight be anint, and the fix is applied asSafe.This PR marks the fix asApplicability::Unsafewhenever the first argumentis an
Expr::Attribute. The diagnostic is still emitted, but applying theautofix now requires
--unsafe-fixes, preventing the silent regression.The same demotion applies to attribute accesses that look like strings(e.g.,
os.chmod(self.name, 0o644)). This is intentional: the heuristiccannot statically distinguish
intattributes fromstr/PathLikeones,so applying it uniformly is the honest behavior.
This PR marks the fix as
Applicable::Unsafeif the first argument is annotated with aninttypeScope
The fix is intentionally local to
os_chmod.rs:helpers.rsis unchanged. Extendingget_name_exprto handle attributeswould require attribute type resolution, which is closer to
ty's domainthan Ruff's.
is_file_descriptor(os.stat,os.rename,os.unlink, etc.) are likely affected by the same pattern,but those are out of scope here. Happy to open a tracking issue or follow
up if useful.
This narrow scope also aligns with @ntBre's note in #17699 about the broader
stabilization of these fixes.
Test plan
crates/ruff_linter/resources/test/fixtures/flake8_use_pathlib/full_name.pycovering attribute access (both
intandstrattributes).full_name.py.snapandpreview_full_name.py.snap. The newpreview diagnostics carry the
note: This is an unsafe fix and may change runtime behaviormarker. Existing diagnostics are unchanged in content;only their reported line numbers shift due to the inserted fixture lines.
cargo test -p ruff_linter→ 2759 passed, 0 failed.cargo clippy --workspace --all-targets --all-features -- -D warnings→ clean.uvx prek run -a→ all hooks passed.Refs #17699