Use a tracked struct
This commit is contained in:
@@ -20,16 +20,13 @@ class Base: ...
|
||||
class Mixin: ...
|
||||
|
||||
# We synthesize a class type using the name argument
|
||||
Foo = type("Foo", (), {})
|
||||
reveal_type(Foo) # revealed: <class 'Foo'>
|
||||
reveal_type(type("Foo", (), {})) # revealed: <class 'Foo'>
|
||||
|
||||
# With a single base class
|
||||
Foo2 = type("Foo", (Base,), {"attr": 1})
|
||||
reveal_type(Foo2) # revealed: <class 'Foo'>
|
||||
reveal_type(type("Foo", (Base,), {"attr": 1})) # revealed: <class 'Foo'>
|
||||
|
||||
# With multiple base classes
|
||||
Foo3 = type("Foo", (Base, Mixin), {})
|
||||
reveal_type(Foo3) # revealed: <class 'Foo'>
|
||||
reveal_type(type("Foo", (Base, Mixin), {})) # revealed: <class 'Foo'>
|
||||
|
||||
# The inferred type is assignable to type[Base] since Foo inherits from Base
|
||||
tests: list[type[Base]] = []
|
||||
@@ -415,24 +412,22 @@ reveal_type(type("Bar", (int,), {}, weird_other_arg=42)) # revealed: Unknown
|
||||
reveal_type(type("Baz", (), {}, metaclass=type)) # revealed: Unknown
|
||||
```
|
||||
|
||||
The following calls are also invalid, due to incorrect argument types.
|
||||
|
||||
Inline calls (not assigned to a variable) fall back to regular `type` overload matching, which
|
||||
produces slightly different error messages than assigned dynamic class creation:
|
||||
The following calls are also invalid, due to incorrect argument types:
|
||||
|
||||
```py
|
||||
class Base: ...
|
||||
|
||||
# error: 6 [invalid-argument-type] "Argument to class `type` is incorrect: Expected `str`, found `Literal[b"Foo"]`"
|
||||
# error: [invalid-argument-type] "Invalid argument to parameter 1 (`name`) of `type()`: Expected `str`, found `Literal[b"Foo"]`"
|
||||
type(b"Foo", (), {})
|
||||
|
||||
# error: 13 [invalid-argument-type] "Argument to class `type` is incorrect: Expected `tuple[type, ...]`, found `<class 'Base'>`"
|
||||
# error: [invalid-argument-type] "Invalid argument to parameter 2 (`bases`) of `type()`: Expected `tuple[type, ...]`, found `<class 'Base'>`"
|
||||
type("Foo", Base, {})
|
||||
|
||||
# error: 13 [invalid-argument-type] "Argument to class `type` is incorrect: Expected `tuple[type, ...]`, found `tuple[Literal[1], Literal[2]]`"
|
||||
# error: 14 [invalid-base] "Invalid class base with type `Literal[1]`"
|
||||
# error: 17 [invalid-base] "Invalid class base with type `Literal[2]`"
|
||||
type("Foo", (1, 2), {})
|
||||
|
||||
# error: 22 [invalid-argument-type] "Argument to class `type` is incorrect: Expected `dict[str, Any]`, found `dict[str | bytes, Any]`"
|
||||
# error: [invalid-argument-type] "Invalid argument to parameter 3 (`namespace`) of `type()`: Expected `dict[str, Any]`, found `dict[Unknown | bytes, Unknown | int]`"
|
||||
type("Foo", (Base,), {b"attr": 1})
|
||||
```
|
||||
|
||||
@@ -656,20 +651,24 @@ def f(*args, **kwargs):
|
||||
|
||||
## Explicit type annotations
|
||||
|
||||
TODO: Annotated assignments with `type()` calls don't currently synthesize the specific class type.
|
||||
This will be fixed when we support all `type()` calls (including inline) via generic handling.
|
||||
When an explicit type annotation is provided, the inferred type is checked against it:
|
||||
|
||||
```py
|
||||
# The annotation `type` is compatible with the inferred class literal type
|
||||
T: type = type("T", (), {})
|
||||
reveal_type(T) # revealed: <class 'T'>
|
||||
|
||||
# The annotation `type[Base]` is compatible with the inferred type
|
||||
class Base: ...
|
||||
|
||||
# TODO: Should infer `<class 'T'>` instead of `type`
|
||||
T: type = type("T", (), {})
|
||||
reveal_type(T) # revealed: type
|
||||
|
||||
# TODO: Should infer `<class 'Derived'>` instead of `type[Base]}
|
||||
# error: [invalid-assignment] "Object of type `type` is not assignable to `type[Base]`"
|
||||
Derived: type[Base] = type("Derived", (Base,), {})
|
||||
reveal_type(Derived) # revealed: type[Base]
|
||||
reveal_type(Derived) # revealed: <class 'Derived'>
|
||||
|
||||
# Incompatible annotation produces an error
|
||||
class Unrelated: ...
|
||||
|
||||
# error: [invalid-assignment]
|
||||
Bad: type[Unrelated] = type("Bad", (Base,), {})
|
||||
```
|
||||
|
||||
## Special base classes
|
||||
|
||||
@@ -169,12 +169,15 @@ Narrowing does not occur in the same way if `type` is used to dynamically create
|
||||
|
||||
```py
|
||||
def _(x: str | int):
|
||||
# Inline type() calls fall back to regular type overload matching.
|
||||
# TODO: Once inline type() calls synthesize class types, this should narrow x to Never.
|
||||
# The following diagnostic is valid, since the three-argument form of `type`
|
||||
# can only be called with `str` as the first argument.
|
||||
#
|
||||
# error: 13 [invalid-argument-type] "Argument to class `type` is incorrect: Expected `str`, found `str | int`"
|
||||
# error: [invalid-argument-type] "Invalid argument to parameter 1 (`name`) of `type()`: Expected `str`, found `str | int`"
|
||||
if type(x, (), {}) is str:
|
||||
reveal_type(x) # revealed: str | int
|
||||
# But we synthesize a new class object as the result of a three-argument call to `type`,
|
||||
# and we know that this synthesized class object is not the same object as the `str` class object,
|
||||
# so here the type is narrowed to `Never`!
|
||||
reveal_type(x) # revealed: Never
|
||||
else:
|
||||
reveal_type(x) # revealed: str | int
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user