[red-knot] Three-argument type-calls take 'str' as the first argument (#17168)

## Summary

Similar to #17163, a minor fix in the signature of `type(…)`.

## Test Plan

New MD tests
This commit is contained in:
David Peter 2025-04-03 15:45:08 +02:00 committed by GitHub
parent d401a5440e
commit 3f00010a7a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 41 additions and 4 deletions

View file

@ -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

View file

@ -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:

View file

@ -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]))

View file

@ -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)),
),