From 130339f3d813e821ff59d929039095ac1933b4f6 Mon Sep 17 00:00:00 2001 From: David Peter Date: Thu, 3 Apr 2025 13:26:32 +0200 Subject: [PATCH] =?UTF-8?q?[red-knot]=20Fix=20`str(=E2=80=A6)`=20calls=20(?= =?UTF-8?q?#17163)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary The existing signature for `str` calls had various problems, one of which I noticed while looking at some ecosystem projects (`scrapy`, added as a project to mypy_primer in this PR). ## Test Plan - New tests for `str(…)` calls. - Observed reduction of false positives in ecosystem checks --- .github/workflows/mypy_primer.yaml | 2 +- .../resources/mdtest/call/builtins.md | 39 +++++++++++++++++++ crates/red_knot_python_semantic/src/types.rs | 30 +++++++++----- .../src/types/class.rs | 8 ++++ 4 files changed, 68 insertions(+), 11 deletions(-) diff --git a/.github/workflows/mypy_primer.yaml b/.github/workflows/mypy_primer.yaml index 0b44d43062..629ff605b6 100644 --- a/.github/workflows/mypy_primer.yaml +++ b/.github/workflows/mypy_primer.yaml @@ -68,7 +68,7 @@ jobs: --type-checker knot \ --old base_commit \ --new "$GITHUB_SHA" \ - --project-selector '/(mypy_primer|black|pyp|git-revise|zipp|arrow|isort|itsdangerous|rich|packaging|pybind11|pyinstrument|typeshed-stats)$' \ + --project-selector '/(mypy_primer|black|pyp|git-revise|zipp|arrow|isort|itsdangerous|rich|packaging|pybind11|pyinstrument|typeshed-stats|scrapy)$' \ --output concise \ --debug > mypy_primer.diff || [ $? -eq 1 ] diff --git a/crates/red_knot_python_semantic/resources/mdtest/call/builtins.md b/crates/red_knot_python_semantic/resources/mdtest/call/builtins.md index f8caef3a95..464358faa3 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/call/builtins.md +++ b/crates/red_knot_python_semantic/resources/mdtest/call/builtins.md @@ -38,3 +38,42 @@ type("Foo", ()) # error: [no-matching-overload] "No overload of class `type` matches arguments" type("Foo", (), {}, weird_other_arg=42) ``` + +## Calls to `str()` + +### Valid calls + +```py +str() +str("") +str(b"") +str(1) +str(object=1) + +str(b"M\xc3\xbcsli", "utf-8") +str(b"M\xc3\xbcsli", "utf-8", "replace") + +str(b"M\x00\xfc\x00s\x00l\x00i\x00", encoding="utf-16") +str(b"M\x00\xfc\x00s\x00l\x00i\x00", encoding="utf-16", errors="ignore") + +str(bytearray.fromhex("4d c3 bc 73 6c 69"), "utf-8") +str(bytearray(), "utf-8") + +str(encoding="utf-8", object=b"M\xc3\xbcsli") +str(b"", errors="replace") +str(encoding="utf-8") +str(errors="replace") +``` + +### Invalid calls + +```py +str(1, 2) # error: [no-matching-overload] +str(o=1) # error: [no-matching-overload] + +# First argument is not a bytes-like object: +str("Müsli", "utf-8") # error: [no-matching-overload] + +# Second argument is not a valid encoding: +str(b"M\xc3\xbcsli", b"utf-8") # error: [no-matching-overload] +``` diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 1a2017a618..beb7463eac 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -2850,21 +2850,31 @@ impl<'db> Type<'db> { self, [ Signature::new( - Parameters::new([Parameter::positional_only(Some( - Name::new_static("o"), - )) - .with_annotated_type(Type::any()) + Parameters::new([Parameter::positional_or_keyword( + Name::new_static("object"), + ) + .with_annotated_type(Type::object(db)) .with_default_type(Type::string_literal(db, ""))]), Some(KnownClass::Str.to_instance(db)), ), Signature::new( Parameters::new([ - Parameter::positional_only(Some(Name::new_static("o"))) - .with_annotated_type(Type::any()), // TODO: ReadableBuffer - Parameter::positional_only(Some(Name::new_static("encoding"))) - .with_annotated_type(KnownClass::Str.to_instance(db)), - Parameter::positional_only(Some(Name::new_static("errors"))) - .with_annotated_type(KnownClass::Str.to_instance(db)), + Parameter::positional_or_keyword(Name::new_static("object")) + // TODO: Should be `ReadableBuffer` instead of this union type: + .with_annotated_type(UnionType::from_elements( + db, + [ + KnownClass::Bytes.to_instance(db), + KnownClass::Bytearray.to_instance(db), + ], + )) + .with_default_type(Type::bytes_literal(db, b"")), + Parameter::positional_or_keyword(Name::new_static("encoding")) + .with_annotated_type(KnownClass::Str.to_instance(db)) + .with_default_type(Type::string_literal(db, "utf-8")), + Parameter::positional_or_keyword(Name::new_static("errors")) + .with_annotated_type(KnownClass::Str.to_instance(db)) + .with_default_type(Type::string_literal(db, "strict")), ]), Some(KnownClass::Str.to_instance(db)), ), diff --git a/crates/red_knot_python_semantic/src/types/class.rs b/crates/red_knot_python_semantic/src/types/class.rs index e337a4352c..51d95753ee 100644 --- a/crates/red_knot_python_semantic/src/types/class.rs +++ b/crates/red_knot_python_semantic/src/types/class.rs @@ -820,6 +820,7 @@ pub enum KnownClass { Bool, Object, Bytes, + Bytearray, Type, Int, Float, @@ -930,6 +931,7 @@ impl<'db> KnownClass { | Self::Int | Self::Type | Self::Bytes + | Self::Bytearray | Self::FrozenSet | Self::Range | Self::Property @@ -959,6 +961,7 @@ impl<'db> KnownClass { Self::Bool => "bool", Self::Object => "object", Self::Bytes => "bytes", + Self::Bytearray => "bytearray", Self::Tuple => "tuple", Self::Int => "int", Self::Float => "float", @@ -1136,6 +1139,7 @@ impl<'db> KnownClass { Self::Bool | Self::Object | Self::Bytes + | Self::Bytearray | Self::Type | Self::Int | Self::Float @@ -1218,6 +1222,7 @@ impl<'db> KnownClass { | Self::Bool | Self::Object | Self::Bytes + | Self::Bytearray | Self::Type | Self::Int | Self::Float @@ -1276,6 +1281,7 @@ impl<'db> KnownClass { | Self::Bool | Self::Object | Self::Bytes + | Self::Bytearray | Self::Tuple | Self::Int | Self::Float @@ -1330,6 +1336,7 @@ impl<'db> KnownClass { "bool" => Self::Bool, "object" => Self::Object, "bytes" => Self::Bytes, + "bytearray" => Self::Bytearray, "tuple" => Self::Tuple, "type" => Self::Type, "int" => Self::Int, @@ -1395,6 +1402,7 @@ impl<'db> KnownClass { | Self::Bool | Self::Object | Self::Bytes + | Self::Bytearray | Self::Type | Self::Int | Self::Float