From 3f00010a7a195ed2cac6a52bd30bc4d0019f90f7 Mon Sep 17 00:00:00 2001 From: David Peter Date: Thu, 3 Apr 2025 15:45:08 +0200 Subject: [PATCH] [red-knot] Three-argument type-calls take 'str' as the first argument (#17168) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Similar to #17163, a minor fix in the signature of `type(…)`. ## Test Plan New MD tests --- .../resources/mdtest/call/builtins.md | 22 +++++++++++++++++++ .../resources/mdtest/narrow/type.md | 3 +++ .../type_properties/is_assignable_to.md | 3 +++ crates/red_knot_python_semantic/src/types.rs | 17 ++++++++++---- 4 files changed, 41 insertions(+), 4 deletions(-) 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 464358faa3..a7b9935ee6 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/call/builtins.md +++ b/crates/red_knot_python_semantic/resources/mdtest/call/builtins.md @@ -26,7 +26,11 @@ reveal_type(type(1)) # revealed: Literal[int] But a three-argument call to type creates a dynamic instance of the `type` class: ```py +class Base: ... + reveal_type(type("Foo", (), {})) # revealed: type + +reveal_type(type("Foo", (Base,), {"attr": 1})) # revealed: type ``` Other numbers of arguments are invalid @@ -39,6 +43,24 @@ type("Foo", ()) type("Foo", (), {}, weird_other_arg=42) ``` +The following calls are also invalid, due to incorrect argument types: + +```py +class Base: ... + +# error: [no-matching-overload] "No overload of class `type` matches arguments" +type(b"Foo", (), {}) + +# error: [no-matching-overload] "No overload of class `type` matches arguments" +type("Foo", Base, {}) + +# TODO: this should be an error +type("Foo", (1, 2), {}) + +# TODO: this should be an error +type("Foo", (Base,), {b"attr": 1}) +``` + ## Calls to `str()` ### Valid calls diff --git a/crates/red_knot_python_semantic/resources/mdtest/narrow/type.md b/crates/red_knot_python_semantic/resources/mdtest/narrow/type.md index 1e3d5f2f11..0b7f0f49b6 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/narrow/type.md +++ b/crates/red_knot_python_semantic/resources/mdtest/narrow/type.md @@ -76,6 +76,9 @@ No narrowing should occur if `type` is used to dynamically create a class: ```py def _(x: str | int): + # The following diagnostic is valid, since the three-argument form of `type` + # can only be called with `str` as the first argument. + # error: [no-matching-overload] "No overload of class `type` matches arguments" if type(x, (), {}) is str: reveal_type(x) # revealed: str | int else: diff --git a/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_assignable_to.md b/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_assignable_to.md index 6f9cf78028..bfc76cbdbd 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_assignable_to.md +++ b/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_assignable_to.md @@ -194,6 +194,9 @@ static_assert(is_assignable_to(tuple[int, str], tuple[int, str])) static_assert(is_assignable_to(tuple[Literal[1], Literal[2]], tuple[int, int])) static_assert(is_assignable_to(tuple[Any, Literal[2]], tuple[int, int])) static_assert(is_assignable_to(tuple[Literal[1], Any], tuple[int, int])) +static_assert(is_assignable_to(tuple[()], tuple)) +static_assert(is_assignable_to(tuple[int, str], tuple)) +static_assert(is_assignable_to(tuple[Any], tuple)) static_assert(not is_assignable_to(tuple[()], tuple[int])) static_assert(not is_assignable_to(tuple[int], tuple[str])) diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index beb7463eac..d15802b946 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -950,6 +950,13 @@ impl<'db> Type<'db> { ) } + // This special case is required because the left-hand side tuple might be a + // gradual type, so we can not rely on subtyping. This allows us to assign e.g. + // `tuple[Any, int]` to `tuple`. + (Type::Tuple(_), _) => KnownClass::Tuple + .to_instance(db) + .is_assignable_to(db, target), + // `type[Any]` is assignable to any `type[...]` type, because `type[Any]` can // materialize to any `type[...]` type. (Type::SubclassOf(subclass_of_ty), Type::SubclassOf(_)) @@ -2903,12 +2910,14 @@ impl<'db> Type<'db> { ), Signature::new( Parameters::new([ - Parameter::positional_only(Some(Name::new_static("o"))) - .with_annotated_type(Type::any()), + Parameter::positional_only(Some(Name::new_static("name"))) + .with_annotated_type(KnownClass::Str.to_instance(db)), Parameter::positional_only(Some(Name::new_static("bases"))) - .with_annotated_type(Type::any()), + // TODO: Should be tuple[type, ...] once we have support for homogenous tuples + .with_annotated_type(KnownClass::Tuple.to_instance(db)), Parameter::positional_only(Some(Name::new_static("dict"))) - .with_annotated_type(Type::any()), + // TODO: Should be `dict[str, Any]` once we have support for generics + .with_annotated_type(KnownClass::Dict.to_instance(db)), ]), Some(KnownClass::Type.to_instance(db)), ),