From ad30daeba05f17e8fa7be4c7645a3cf7cb9a5f36 Mon Sep 17 00:00:00 2001 From: Thibaut Decombe Date: Sun, 8 Dec 2024 18:46:44 +0100 Subject: [PATCH 1/2] Fix false positive for strings created via "a" * 12 --- .../fixtures/pylint/invalid_envvar_default.py | 2 ++ ...ts__PLW1508_invalid_envvar_default.py.snap | 1 + ...ew__PLW1508_invalid_envvar_default.py.snap | 3 +++ .../src/analyze/type_inference.rs | 19 +++++++++++++++++++ 4 files changed, 25 insertions(+) diff --git a/crates/ruff_linter/resources/test/fixtures/pylint/invalid_envvar_default.py b/crates/ruff_linter/resources/test/fixtures/pylint/invalid_envvar_default.py index 9a7e1f0efafcb4..070df96a03cbe7 100644 --- a/crates/ruff_linter/resources/test/fixtures/pylint/invalid_envvar_default.py +++ b/crates/ruff_linter/resources/test/fixtures/pylint/invalid_envvar_default.py @@ -13,3 +13,5 @@ os.getenv("AA", "GOOD" if Z else "BAR") os.getenv("AA", 1 if Z else "BAR") # [invalid-envvar-default] os.environ.get("TEST", 12) # [invalid-envvar-default] +os.environ.get("TEST", "AA" * 12) +os.environ.get("TEST", 13 * "AA") diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW1508_invalid_envvar_default.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW1508_invalid_envvar_default.py.snap index 6104e1f5c37be4..1b06d83baa9bb1 100644 --- a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW1508_invalid_envvar_default.py.snap +++ b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW1508_invalid_envvar_default.py.snap @@ -49,4 +49,5 @@ invalid_envvar_default.py:14:17: PLW1508 Invalid type for environment variable d 14 | os.getenv("AA", 1 if Z else "BAR") # [invalid-envvar-default] | ^^^^^^^^^^^^^^^^^ PLW1508 15 | os.environ.get("TEST", 12) # [invalid-envvar-default] +16 | os.environ.get("TEST", "AA" * 12) | diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__preview__PLW1508_invalid_envvar_default.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__preview__PLW1508_invalid_envvar_default.py.snap index ed7dd8e7bedfb9..7bc7aeaa456929 100644 --- a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__preview__PLW1508_invalid_envvar_default.py.snap +++ b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__preview__PLW1508_invalid_envvar_default.py.snap @@ -49,6 +49,7 @@ invalid_envvar_default.py:14:17: PLW1508 Invalid type for environment variable d 14 | os.getenv("AA", 1 if Z else "BAR") # [invalid-envvar-default] | ^^^^^^^^^^^^^^^^^ PLW1508 15 | os.environ.get("TEST", 12) # [invalid-envvar-default] +16 | os.environ.get("TEST", "AA" * 12) | invalid_envvar_default.py:15:24: PLW1508 Invalid type for environment variable default; expected `str` or `None` @@ -57,4 +58,6 @@ invalid_envvar_default.py:15:24: PLW1508 Invalid type for environment variable d 14 | os.getenv("AA", 1 if Z else "BAR") # [invalid-envvar-default] 15 | os.environ.get("TEST", 12) # [invalid-envvar-default] | ^^ PLW1508 +16 | os.environ.get("TEST", "AA" * 12) +17 | os.environ.get("TEST", 13 * "AA") | diff --git a/crates/ruff_python_semantic/src/analyze/type_inference.rs b/crates/ruff_python_semantic/src/analyze/type_inference.rs index 11a1c6b3f16039..351602909d2828 100644 --- a/crates/ruff_python_semantic/src/analyze/type_inference.rs +++ b/crates/ruff_python_semantic/src/analyze/type_inference.rs @@ -248,6 +248,17 @@ impl From<&Expr> for ResolvedPythonType { left.coerce(right), )); } + // Ex) `"1" * 2` or `2 * "1"` + ( + ResolvedPythonType::Atom(PythonType::String), + ResolvedPythonType::Atom(PythonType::Number(NumberLike::Integer)), + ) + | ( + ResolvedPythonType::Atom(PythonType::Number(NumberLike::Integer)), + ResolvedPythonType::Atom(PythonType::String), + ) if matches!(op, Operator::Mult) => { + return ResolvedPythonType::Atom(PythonType::String); + } (ResolvedPythonType::Atom(_), ResolvedPythonType::Atom(_)) => { return ResolvedPythonType::TypeError; } @@ -476,6 +487,14 @@ mod tests { ResolvedPythonType::from(parse("1.0 * 2j").expr()), ResolvedPythonType::Atom(PythonType::Number(NumberLike::Complex)) ); + assert_eq!( + ResolvedPythonType::from(parse("'AA' * 2").expr()), + ResolvedPythonType::Atom(PythonType::String) + ); + assert_eq!( + ResolvedPythonType::from(parse("4 * 'AA'").expr()), + ResolvedPythonType::Atom(PythonType::String) + ); assert_eq!( ResolvedPythonType::from(parse("1 / True").expr()), ResolvedPythonType::Atom(PythonType::Number(NumberLike::Float)) From a81143e51d145dcce4cb1e1238a1ab50b927d4b5 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Sun, 8 Dec 2024 18:21:02 +0000 Subject: [PATCH 2/2] split into its own branch --- .../src/analyze/type_inference.rs | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/crates/ruff_python_semantic/src/analyze/type_inference.rs b/crates/ruff_python_semantic/src/analyze/type_inference.rs index 351602909d2828..45540232b71181 100644 --- a/crates/ruff_python_semantic/src/analyze/type_inference.rs +++ b/crates/ruff_python_semantic/src/analyze/type_inference.rs @@ -234,12 +234,11 @@ impl From<&Expr> for ResolvedPythonType { } _ => {} }, - // Standard arithmetic operators, which coerce to the "highest" number type. - Operator::Mult | Operator::FloorDiv | Operator::Pow => match ( + Operator::Mult => match ( ResolvedPythonType::from(left.as_ref()), ResolvedPythonType::from(right.as_ref()), ) { - // Ex) `1 - 2` + // Ex) `2 * 4` ( ResolvedPythonType::Atom(PythonType::Number(left)), ResolvedPythonType::Atom(PythonType::Number(right)), @@ -256,8 +255,25 @@ impl From<&Expr> for ResolvedPythonType { | ( ResolvedPythonType::Atom(PythonType::Number(NumberLike::Integer)), ResolvedPythonType::Atom(PythonType::String), - ) if matches!(op, Operator::Mult) => { - return ResolvedPythonType::Atom(PythonType::String); + ) => return ResolvedPythonType::Atom(PythonType::String), + (ResolvedPythonType::Atom(_), ResolvedPythonType::Atom(_)) => { + return ResolvedPythonType::TypeError; + } + _ => {} + }, + // Standard arithmetic operators, which coerce to the "highest" number type. + Operator::FloorDiv | Operator::Pow => match ( + ResolvedPythonType::from(left.as_ref()), + ResolvedPythonType::from(right.as_ref()), + ) { + // Ex) `2 ** 4` + ( + ResolvedPythonType::Atom(PythonType::Number(left)), + ResolvedPythonType::Atom(PythonType::Number(right)), + ) => { + return ResolvedPythonType::Atom(PythonType::Number( + left.coerce(right), + )); } (ResolvedPythonType::Atom(_), ResolvedPythonType::Atom(_)) => { return ResolvedPythonType::TypeError;