Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Apr 3, 2025
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions crates/red_knot_python_semantic/resources/mdtest/call/builtins.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]))
Expand Down
19 changes: 15 additions & 4 deletions crates/red_knot_python_semantic/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -950,6 +950,15 @@ impl<'db> Type<'db> {
)
}

// Any heterogeneous tuple type is assignable to `tuple`. This needs to be a special
// case because the left-hand side tuple might be a gradual type, so we can not rely
// on subtyping.
(Type::Tuple(_), Type::Instance(instance))
if instance.class.is_known(db, KnownClass::Tuple) =>
{
true
}

// `type[Any]` is assignable to any `type[...]` type, because `type[Any]` can
// materialize to any `type[...]` type.
(Type::SubclassOf(subclass_of_ty), Type::SubclassOf(_))
Expand Down Expand Up @@ -2903,12 +2912,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)),
),
Expand Down
Loading