mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-09 05:08:21 +00:00
Support IfExp
with dual string arms in invalid-envvar-value
(#6538)
## Summary Closes https://github.com/astral-sh/ruff/issues/6537. We need to improve the `PythonType` algorithm, so this also documents some of its limitations as TODOs.
This commit is contained in:
parent
8660e5057c
commit
446ceed1ad
6 changed files with 52 additions and 78 deletions
|
@ -1,7 +1,7 @@
|
|||
//! Analysis rules to perform basic type inference on individual expressions.
|
||||
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::{Constant, Expr};
|
||||
use ruff_python_ast::{Constant, Expr, Operator};
|
||||
|
||||
/// An extremely simple type inference system for individual expressions.
|
||||
///
|
||||
|
@ -9,7 +9,7 @@ use ruff_python_ast::{Constant, Expr};
|
|||
/// such as strings, integers, floats, and containers. It cannot infer the
|
||||
/// types of variables or expressions that are not statically known from
|
||||
/// individual AST nodes alone.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, is_macro::Is)]
|
||||
pub enum PythonType {
|
||||
/// A string literal, such as `"hello"`.
|
||||
String,
|
||||
|
@ -55,27 +55,43 @@ impl From<&Expr> for PythonType {
|
|||
Expr::Tuple(_) => PythonType::Tuple,
|
||||
Expr::GeneratorExp(_) => PythonType::Generator,
|
||||
Expr::FString(_) => PythonType::String,
|
||||
Expr::BinOp(ast::ExprBinOp { left, op, .. }) => {
|
||||
// Ex) "a" % "b"
|
||||
if op.is_mod() {
|
||||
if matches!(
|
||||
left.as_ref(),
|
||||
Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(..),
|
||||
..
|
||||
})
|
||||
) {
|
||||
return PythonType::String;
|
||||
}
|
||||
if matches!(
|
||||
left.as_ref(),
|
||||
Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Bytes(..),
|
||||
..
|
||||
})
|
||||
) {
|
||||
return PythonType::Bytes;
|
||||
Expr::IfExp(ast::ExprIfExp { body, orelse, .. }) => {
|
||||
let body = PythonType::from(body.as_ref());
|
||||
let orelse = PythonType::from(orelse.as_ref());
|
||||
// TODO(charlie): If we have two known types, we should return a union. As-is,
|
||||
// callers that ignore the `Unknown` type will allow invalid expressions (e.g.,
|
||||
// if you're testing for strings, you may accept `String` or `Unknown`, and you'd
|
||||
// now accept, e.g., `1 if True else "a"`, which resolves to `Unknown`).
|
||||
if body == orelse {
|
||||
body
|
||||
} else {
|
||||
PythonType::Unknown
|
||||
}
|
||||
}
|
||||
Expr::BinOp(ast::ExprBinOp {
|
||||
left, op, right, ..
|
||||
}) => {
|
||||
match op {
|
||||
// Ex) "a" + "b"
|
||||
Operator::Add => {
|
||||
match (
|
||||
PythonType::from(left.as_ref()),
|
||||
PythonType::from(right.as_ref()),
|
||||
) {
|
||||
(PythonType::String, PythonType::String) => return PythonType::String,
|
||||
(PythonType::Bytes, PythonType::Bytes) => return PythonType::Bytes,
|
||||
// TODO(charlie): If we have two known types, they may be incompatible.
|
||||
// Return an error (e.g., for `1 + "a"`).
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
// Ex) "a" % "b"
|
||||
Operator::Mod => match PythonType::from(left.as_ref()) {
|
||||
PythonType::String => return PythonType::String,
|
||||
PythonType::Bytes => return PythonType::Bytes,
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
PythonType::Unknown
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue