mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-04 10:48:32 +00:00
[flake8-use-pathlib
] Fix PTH123
false positive when open
is passed a file descriptor from a function call (#17705)
## Summary Includes minor changes to the semantic type inference to help detect the return type of function call. Fixes #17691 ## Test Plan Snapshot tests
This commit is contained in:
parent
93d6a3567b
commit
8c68d30c3a
3 changed files with 58 additions and 5 deletions
|
@ -55,3 +55,16 @@ x = 2
|
|||
open(x)
|
||||
def foo(y: int):
|
||||
open(y)
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/17691
|
||||
def f() -> int:
|
||||
return 1
|
||||
open(f())
|
||||
|
||||
open(b"foo")
|
||||
byte_str = b"bar"
|
||||
open(byte_str)
|
||||
|
||||
def bytes_str_func() -> bytes:
|
||||
return b"foo"
|
||||
open(bytes_str_func())
|
||||
|
|
|
@ -126,10 +126,9 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) {
|
|||
.arguments
|
||||
.find_argument_value("opener", 7)
|
||||
.is_some_and(|expr| !expr.is_none_literal_expr())
|
||||
|| call
|
||||
.arguments
|
||||
.find_positional(0)
|
||||
.is_some_and(|expr| is_file_descriptor(expr, checker.semantic()))
|
||||
|| call.arguments.find_positional(0).is_some_and(|expr| {
|
||||
is_file_descriptor_or_bytes_str(expr, checker.semantic())
|
||||
})
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
@ -168,6 +167,10 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) {
|
|||
}
|
||||
}
|
||||
|
||||
fn is_file_descriptor_or_bytes_str(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||
is_file_descriptor(expr, semantic) || is_bytes_string(expr, semantic)
|
||||
}
|
||||
|
||||
/// Returns `true` if the given expression looks like a file descriptor, i.e., if it is an integer.
|
||||
fn is_file_descriptor(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||
if matches!(
|
||||
|
@ -180,7 +183,7 @@ fn is_file_descriptor(expr: &Expr, semantic: &SemanticModel) -> bool {
|
|||
return true;
|
||||
}
|
||||
|
||||
let Some(name) = expr.as_name_expr() else {
|
||||
let Some(name) = get_name_expr(expr) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
|
@ -190,3 +193,28 @@ fn is_file_descriptor(expr: &Expr, semantic: &SemanticModel) -> bool {
|
|||
|
||||
typing::is_int(binding, semantic)
|
||||
}
|
||||
|
||||
/// Returns `true` if the given expression is a bytes string.
|
||||
fn is_bytes_string(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||
if matches!(expr, Expr::BytesLiteral(_)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let Some(name) = get_name_expr(expr) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let Some(binding) = semantic.only_binding(name).map(|id| semantic.binding(id)) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
typing::is_bytes(binding, semantic)
|
||||
}
|
||||
|
||||
fn get_name_expr(expr: &Expr) -> Option<&ast::ExprName> {
|
||||
match expr {
|
||||
Expr::Name(name) => Some(name),
|
||||
Expr::Call(ast::ExprCall { func, .. }) => get_name_expr(func),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -639,6 +639,18 @@ pub fn check_type<T: TypeChecker>(binding: &Binding, semantic: &SemanticModel) -
|
|||
_ => false,
|
||||
},
|
||||
|
||||
BindingKind::FunctionDefinition(_) => match binding.statement(semantic) {
|
||||
// ```python
|
||||
// def foo() -> int:
|
||||
// ...
|
||||
// ```
|
||||
Some(Stmt::FunctionDef(ast::StmtFunctionDef { returns, .. })) => returns
|
||||
.as_ref()
|
||||
.is_some_and(|return_ann| T::match_annotation(return_ann, semantic)),
|
||||
|
||||
_ => false,
|
||||
},
|
||||
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue