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 9a7e1f0efa..070df96a03 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("B", Z) 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 6104e1f5c3..1b06d83baa 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 ed7dd8e7be..7bc7aeaa45 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 11a1c6b3f1..45540232b7 100644 --- a/crates/ruff_python_semantic/src/analyze/type_inference.rs +++ b/crates/ruff_python_semantic/src/analyze/type_inference.rs @@ -234,12 +234,39 @@ 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)), + ) => { + return ResolvedPythonType::Atom(PythonType::Number( + 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), + ) => 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)), @@ -476,6 +503,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))