Merge branch 'dcreager/source-order-constraints' into dcreager/callable-return
* dcreager/source-order-constraints: (30 commits) clippy fix test expectations (again) include source_order in display_graph output place bounds/constraints first don't always bump only fold once document display source_order more comments remove now-unused items fix test expectation use source order in specialize_constrained too document overall approach more comment reuse self source_order sort specialize_constrained by source_order lots of renaming remove source_order_for simpler source_order_for doc restore TODOs ...
This commit is contained in:
@@ -201,7 +201,7 @@ python-version = "3.12"
|
||||
```py
|
||||
type IntOrStr = int | str
|
||||
|
||||
reveal_type(IntOrStr.__or__) # revealed: bound method typing.TypeAliasType.__or__(right: Any, /) -> _SpecialForm
|
||||
reveal_type(IntOrStr.__or__) # revealed: bound method TypeAliasType.__or__(right: Any, /) -> _SpecialForm
|
||||
```
|
||||
|
||||
## Method calls on types not disjoint from `None`
|
||||
|
||||
@@ -567,7 +567,7 @@ def f(x: int):
|
||||
super(x, x)
|
||||
|
||||
type IntAlias = int
|
||||
# error: [invalid-super-argument] "`typing.TypeAliasType` is not a valid class"
|
||||
# error: [invalid-super-argument] "`TypeAliasType` is not a valid class"
|
||||
super(IntAlias, 0)
|
||||
|
||||
# error: [invalid-super-argument] "`str` is not an instance or subclass of `<class 'int'>` in `super(<class 'int'>, str)` call"
|
||||
|
||||
@@ -521,6 +521,73 @@ frozen = MyFrozenChildClass()
|
||||
del frozen.x # TODO this should emit an [invalid-assignment]
|
||||
```
|
||||
|
||||
### frozen/non-frozen inheritance
|
||||
|
||||
If a non-frozen dataclass inherits from a frozen dataclass, an exception is raised at runtime. We
|
||||
catch this error:
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
`a.py`:
|
||||
|
||||
```py
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class FrozenBase:
|
||||
x: int
|
||||
|
||||
@dataclass
|
||||
# error: [invalid-frozen-dataclass-subclass] "Non-frozen dataclass `Child` cannot inherit from frozen dataclass `FrozenBase`"
|
||||
class Child(FrozenBase):
|
||||
y: int
|
||||
```
|
||||
|
||||
Frozen dataclasses inheriting from non-frozen dataclasses are also illegal:
|
||||
|
||||
`b.py`:
|
||||
|
||||
```py
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass
|
||||
class Base:
|
||||
x: int
|
||||
|
||||
@dataclass(frozen=True)
|
||||
# error: [invalid-frozen-dataclass-subclass] "Frozen dataclass `FrozenChild` cannot inherit from non-frozen dataclass `Base`"
|
||||
class FrozenChild(Base):
|
||||
y: int
|
||||
```
|
||||
|
||||
Example of diagnostics when there are multiple files involved:
|
||||
|
||||
`module.py`:
|
||||
|
||||
```py
|
||||
import dataclasses
|
||||
|
||||
@dataclasses.dataclass(frozen=False)
|
||||
class NotFrozenBase:
|
||||
x: int
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
from functools import total_ordering
|
||||
from typing import final
|
||||
from dataclasses import dataclass
|
||||
|
||||
from module import NotFrozenBase
|
||||
|
||||
@final
|
||||
@dataclass(frozen=True)
|
||||
@total_ordering
|
||||
class FrozenChild(NotFrozenBase): # error: [invalid-frozen-dataclass-subclass]
|
||||
y: str
|
||||
```
|
||||
|
||||
### `match_args`
|
||||
|
||||
If `match_args` is set to `True` (the default), the `__match_args__` attribute is a tuple created
|
||||
|
||||
@@ -9,7 +9,7 @@ from typing import ParamSpec
|
||||
|
||||
P = ParamSpec("P")
|
||||
reveal_type(type(P)) # revealed: <class 'ParamSpec'>
|
||||
reveal_type(P) # revealed: typing.ParamSpec
|
||||
reveal_type(P) # revealed: ParamSpec
|
||||
reveal_type(P.__name__) # revealed: Literal["P"]
|
||||
```
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ from typing import TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
reveal_type(type(T)) # revealed: <class 'TypeVar'>
|
||||
reveal_type(T) # revealed: typing.TypeVar
|
||||
reveal_type(T) # revealed: TypeVar
|
||||
reveal_type(T.__name__) # revealed: Literal["T"]
|
||||
```
|
||||
|
||||
@@ -146,7 +146,7 @@ from typing import TypeVar
|
||||
|
||||
T = TypeVar("T", default=int)
|
||||
reveal_type(type(T)) # revealed: <class 'TypeVar'>
|
||||
reveal_type(T) # revealed: typing.TypeVar
|
||||
reveal_type(T) # revealed: TypeVar
|
||||
reveal_type(T.__default__) # revealed: int
|
||||
reveal_type(T.__bound__) # revealed: None
|
||||
reveal_type(T.__constraints__) # revealed: tuple[()]
|
||||
@@ -187,7 +187,7 @@ from typing import TypeVar
|
||||
|
||||
T = TypeVar("T", bound=int)
|
||||
reveal_type(type(T)) # revealed: <class 'TypeVar'>
|
||||
reveal_type(T) # revealed: typing.TypeVar
|
||||
reveal_type(T) # revealed: TypeVar
|
||||
reveal_type(T.__bound__) # revealed: int
|
||||
reveal_type(T.__constraints__) # revealed: tuple[()]
|
||||
|
||||
@@ -211,7 +211,7 @@ from typing import TypeVar
|
||||
|
||||
T = TypeVar("T", int, str)
|
||||
reveal_type(type(T)) # revealed: <class 'TypeVar'>
|
||||
reveal_type(T) # revealed: typing.TypeVar
|
||||
reveal_type(T) # revealed: TypeVar
|
||||
reveal_type(T.__constraints__) # revealed: tuple[int, str]
|
||||
|
||||
S = TypeVar("S")
|
||||
|
||||
@@ -12,7 +12,7 @@ python-version = "3.13"
|
||||
|
||||
```py
|
||||
def foo1[**P]() -> None:
|
||||
reveal_type(P) # revealed: typing.ParamSpec
|
||||
reveal_type(P) # revealed: ParamSpec
|
||||
```
|
||||
|
||||
## Bounds and constraints
|
||||
@@ -45,14 +45,14 @@ The default value for a `ParamSpec` can be either a list of types, `...`, or ano
|
||||
|
||||
```py
|
||||
def foo2[**P = ...]() -> None:
|
||||
reveal_type(P) # revealed: typing.ParamSpec
|
||||
reveal_type(P) # revealed: ParamSpec
|
||||
|
||||
def foo3[**P = [int, str]]() -> None:
|
||||
reveal_type(P) # revealed: typing.ParamSpec
|
||||
reveal_type(P) # revealed: ParamSpec
|
||||
|
||||
def foo4[**P, **Q = P]():
|
||||
reveal_type(P) # revealed: typing.ParamSpec
|
||||
reveal_type(Q) # revealed: typing.ParamSpec
|
||||
reveal_type(P) # revealed: ParamSpec
|
||||
reveal_type(Q) # revealed: ParamSpec
|
||||
```
|
||||
|
||||
Other values are invalid.
|
||||
|
||||
@@ -17,7 +17,7 @@ instances of `typing.TypeVar`, just like legacy type variables.
|
||||
```py
|
||||
def f[T]():
|
||||
reveal_type(type(T)) # revealed: <class 'TypeVar'>
|
||||
reveal_type(T) # revealed: typing.TypeVar
|
||||
reveal_type(T) # revealed: TypeVar
|
||||
reveal_type(T.__name__) # revealed: Literal["T"]
|
||||
```
|
||||
|
||||
@@ -33,7 +33,7 @@ python-version = "3.13"
|
||||
```py
|
||||
def f[T = int]():
|
||||
reveal_type(type(T)) # revealed: <class 'TypeVar'>
|
||||
reveal_type(T) # revealed: typing.TypeVar
|
||||
reveal_type(T) # revealed: TypeVar
|
||||
reveal_type(T.__default__) # revealed: int
|
||||
reveal_type(T.__bound__) # revealed: None
|
||||
reveal_type(T.__constraints__) # revealed: tuple[()]
|
||||
@@ -66,7 +66,7 @@ class Invalid[S = T]: ...
|
||||
```py
|
||||
def f[T: int]():
|
||||
reveal_type(type(T)) # revealed: <class 'TypeVar'>
|
||||
reveal_type(T) # revealed: typing.TypeVar
|
||||
reveal_type(T) # revealed: TypeVar
|
||||
reveal_type(T.__bound__) # revealed: int
|
||||
reveal_type(T.__constraints__) # revealed: tuple[()]
|
||||
|
||||
@@ -79,7 +79,7 @@ def g[S]():
|
||||
```py
|
||||
def f[T: (int, str)]():
|
||||
reveal_type(type(T)) # revealed: <class 'TypeVar'>
|
||||
reveal_type(T) # revealed: typing.TypeVar
|
||||
reveal_type(T) # revealed: TypeVar
|
||||
reveal_type(T.__constraints__) # revealed: tuple[int, str]
|
||||
reveal_type(T.__bound__) # revealed: None
|
||||
|
||||
|
||||
@@ -400,7 +400,7 @@ reveal_type(ListOrTuple) # revealed: <types.UnionType special form 'list[T@List
|
||||
reveal_type(ListOrTupleLegacy)
|
||||
reveal_type(MyCallable) # revealed: <typing.Callable special form '(**P@MyCallable) -> T@MyCallable'>
|
||||
reveal_type(AnnotatedType) # revealed: <special form 'typing.Annotated[T@AnnotatedType, <metadata>]'>
|
||||
reveal_type(TransparentAlias) # revealed: typing.TypeVar
|
||||
reveal_type(TransparentAlias) # revealed: TypeVar
|
||||
reveal_type(MyOptional) # revealed: <types.UnionType special form 'T@MyOptional | None'>
|
||||
|
||||
def _(
|
||||
|
||||
@@ -12,7 +12,7 @@ python-version = "3.12"
|
||||
```py
|
||||
type IntOrStr = int | str
|
||||
|
||||
reveal_type(IntOrStr) # revealed: typing.TypeAliasType
|
||||
reveal_type(IntOrStr) # revealed: TypeAliasType
|
||||
reveal_type(IntOrStr.__name__) # revealed: Literal["IntOrStr"]
|
||||
|
||||
x: IntOrStr = 1
|
||||
@@ -205,7 +205,7 @@ from typing_extensions import TypeAliasType, Union
|
||||
|
||||
IntOrStr = TypeAliasType("IntOrStr", Union[int, str])
|
||||
|
||||
reveal_type(IntOrStr) # revealed: typing.TypeAliasType
|
||||
reveal_type(IntOrStr) # revealed: TypeAliasType
|
||||
|
||||
reveal_type(IntOrStr.__name__) # revealed: Literal["IntOrStr"]
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ diagnostic message for `invalid-exception-caught` expects to construct `typing.P
|
||||
def foo[**P]() -> None:
|
||||
try:
|
||||
pass
|
||||
# error: [invalid-exception-caught] "Invalid object caught in an exception handler: Object has type `typing.ParamSpec`"
|
||||
# error: [invalid-exception-caught] "Invalid object caught in an exception handler: Object has type `ParamSpec`"
|
||||
except P:
|
||||
pass
|
||||
```
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
# Implicit class body attributes
|
||||
|
||||
## Class body implicit attributes
|
||||
|
||||
Python makes certain names available implicitly inside class body scopes. These are `__qualname__`,
|
||||
`__module__`, and `__doc__`, as documented at
|
||||
<https://docs.python.org/3/reference/datamodel.html#creating-the-class-object>.
|
||||
|
||||
```py
|
||||
class Foo:
|
||||
reveal_type(__qualname__) # revealed: str
|
||||
reveal_type(__module__) # revealed: str
|
||||
reveal_type(__doc__) # revealed: str | None
|
||||
```
|
||||
|
||||
## `__firstlineno__` (Python 3.13+)
|
||||
|
||||
Python 3.13 added `__firstlineno__` to the class body namespace.
|
||||
|
||||
### Available in Python 3.13+
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.13"
|
||||
```
|
||||
|
||||
```py
|
||||
class Foo:
|
||||
reveal_type(__firstlineno__) # revealed: int
|
||||
```
|
||||
|
||||
### Not available in Python 3.12 and earlier
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
class Foo:
|
||||
# error: [unresolved-reference]
|
||||
__firstlineno__
|
||||
```
|
||||
|
||||
## Nested classes
|
||||
|
||||
These implicit attributes are also available in nested classes, and refer to the nested class:
|
||||
|
||||
```py
|
||||
class Outer:
|
||||
class Inner:
|
||||
reveal_type(__qualname__) # revealed: str
|
||||
reveal_type(__module__) # revealed: str
|
||||
```
|
||||
|
||||
## Class body implicit attributes have priority over globals
|
||||
|
||||
If a global variable with the same name exists, the class body implicit attribute takes priority
|
||||
within the class body:
|
||||
|
||||
```py
|
||||
__qualname__ = 42
|
||||
__module__ = 42
|
||||
|
||||
class Foo:
|
||||
# Inside the class body, these are the implicit class attributes
|
||||
reveal_type(__qualname__) # revealed: str
|
||||
reveal_type(__module__) # revealed: str
|
||||
|
||||
# Outside the class, the globals are visible
|
||||
reveal_type(__qualname__) # revealed: Literal[42]
|
||||
reveal_type(__module__) # revealed: Literal[42]
|
||||
```
|
||||
|
||||
## `__firstlineno__` has priority over globals (Python 3.13+)
|
||||
|
||||
The same applies to `__firstlineno__` on Python 3.13+:
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.13"
|
||||
```
|
||||
|
||||
```py
|
||||
__firstlineno__ = "not an int"
|
||||
|
||||
class Foo:
|
||||
reveal_type(__firstlineno__) # revealed: int
|
||||
|
||||
reveal_type(__firstlineno__) # revealed: Literal["not an int"]
|
||||
```
|
||||
|
||||
## Class body implicit attributes are not visible in methods
|
||||
|
||||
The implicit class body attributes are only available directly in the class body, not in nested
|
||||
function scopes (methods):
|
||||
|
||||
```py
|
||||
class Foo:
|
||||
# Available directly in the class body
|
||||
x = __qualname__
|
||||
reveal_type(x) # revealed: str
|
||||
|
||||
def method(self):
|
||||
# Not available in methods - falls back to builtins/globals
|
||||
# error: [unresolved-reference]
|
||||
__qualname__
|
||||
```
|
||||
|
||||
## Real-world use case: logging
|
||||
|
||||
A common use case is defining a logger with the class name:
|
||||
|
||||
```py
|
||||
import logging
|
||||
|
||||
class MyClass:
|
||||
logger = logging.getLogger(__qualname__)
|
||||
reveal_type(logger) # revealed: Logger
|
||||
```
|
||||
@@ -0,0 +1,154 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: dataclasses.md - Dataclasses - Other dataclass parameters - frozen/non-frozen inheritance
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/dataclasses/dataclasses.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## a.py
|
||||
|
||||
```
|
||||
1 | from dataclasses import dataclass
|
||||
2 |
|
||||
3 | @dataclass(frozen=True)
|
||||
4 | class FrozenBase:
|
||||
5 | x: int
|
||||
6 |
|
||||
7 | @dataclass
|
||||
8 | # error: [invalid-frozen-dataclass-subclass] "Non-frozen dataclass `Child` cannot inherit from frozen dataclass `FrozenBase`"
|
||||
9 | class Child(FrozenBase):
|
||||
10 | y: int
|
||||
```
|
||||
|
||||
## b.py
|
||||
|
||||
```
|
||||
1 | from dataclasses import dataclass
|
||||
2 |
|
||||
3 | @dataclass
|
||||
4 | class Base:
|
||||
5 | x: int
|
||||
6 |
|
||||
7 | @dataclass(frozen=True)
|
||||
8 | # error: [invalid-frozen-dataclass-subclass] "Frozen dataclass `FrozenChild` cannot inherit from non-frozen dataclass `Base`"
|
||||
9 | class FrozenChild(Base):
|
||||
10 | y: int
|
||||
```
|
||||
|
||||
## module.py
|
||||
|
||||
```
|
||||
1 | import dataclasses
|
||||
2 |
|
||||
3 | @dataclasses.dataclass(frozen=False)
|
||||
4 | class NotFrozenBase:
|
||||
5 | x: int
|
||||
```
|
||||
|
||||
## main.py
|
||||
|
||||
```
|
||||
1 | from functools import total_ordering
|
||||
2 | from typing import final
|
||||
3 | from dataclasses import dataclass
|
||||
4 |
|
||||
5 | from module import NotFrozenBase
|
||||
6 |
|
||||
7 | @final
|
||||
8 | @dataclass(frozen=True)
|
||||
9 | @total_ordering
|
||||
10 | class FrozenChild(NotFrozenBase): # error: [invalid-frozen-dataclass-subclass]
|
||||
11 | y: str
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-frozen-dataclass-subclass]: Non-frozen dataclass cannot inherit from frozen dataclass
|
||||
--> src/a.py:7:1
|
||||
|
|
||||
5 | x: int
|
||||
6 |
|
||||
7 | @dataclass
|
||||
| ---------- `Child` dataclass parameters
|
||||
8 | # error: [invalid-frozen-dataclass-subclass] "Non-frozen dataclass `Child` cannot inherit from frozen dataclass `FrozenBase`"
|
||||
9 | class Child(FrozenBase):
|
||||
| ^^^^^^----------^ Subclass `Child` is not frozen but base class `FrozenBase` is
|
||||
10 | y: int
|
||||
|
|
||||
info: This causes the class creation to fail
|
||||
info: Base class definition
|
||||
--> src/a.py:3:1
|
||||
|
|
||||
1 | from dataclasses import dataclass
|
||||
2 |
|
||||
3 | @dataclass(frozen=True)
|
||||
| ----------------------- `FrozenBase` dataclass parameters
|
||||
4 | class FrozenBase:
|
||||
| ^^^^^^^^^^ `FrozenBase` definition
|
||||
5 | x: int
|
||||
|
|
||||
info: rule `invalid-frozen-dataclass-subclass` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-frozen-dataclass-subclass]: Frozen dataclass cannot inherit from non-frozen dataclass
|
||||
--> src/b.py:7:1
|
||||
|
|
||||
5 | x: int
|
||||
6 |
|
||||
7 | @dataclass(frozen=True)
|
||||
| ----------------------- `FrozenChild` dataclass parameters
|
||||
8 | # error: [invalid-frozen-dataclass-subclass] "Frozen dataclass `FrozenChild` cannot inherit from non-frozen dataclass `Base`"
|
||||
9 | class FrozenChild(Base):
|
||||
| ^^^^^^^^^^^^----^ Subclass `FrozenChild` is frozen but base class `Base` is not
|
||||
10 | y: int
|
||||
|
|
||||
info: This causes the class creation to fail
|
||||
info: Base class definition
|
||||
--> src/b.py:3:1
|
||||
|
|
||||
1 | from dataclasses import dataclass
|
||||
2 |
|
||||
3 | @dataclass
|
||||
| ---------- `Base` dataclass parameters
|
||||
4 | class Base:
|
||||
| ^^^^ `Base` definition
|
||||
5 | x: int
|
||||
|
|
||||
info: rule `invalid-frozen-dataclass-subclass` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-frozen-dataclass-subclass]: Frozen dataclass cannot inherit from non-frozen dataclass
|
||||
--> src/main.py:8:1
|
||||
|
|
||||
7 | @final
|
||||
8 | @dataclass(frozen=True)
|
||||
| ----------------------- `FrozenChild` dataclass parameters
|
||||
9 | @total_ordering
|
||||
10 | class FrozenChild(NotFrozenBase): # error: [invalid-frozen-dataclass-subclass]
|
||||
| ^^^^^^^^^^^^-------------^ Subclass `FrozenChild` is frozen but base class `NotFrozenBase` is not
|
||||
11 | y: str
|
||||
|
|
||||
info: This causes the class creation to fail
|
||||
info: Base class definition
|
||||
--> src/module.py:3:1
|
||||
|
|
||||
1 | import dataclasses
|
||||
2 |
|
||||
3 | @dataclasses.dataclass(frozen=False)
|
||||
| ------------------------------------ `NotFrozenBase` dataclass parameters
|
||||
4 | class NotFrozenBase:
|
||||
| ^^^^^^^^^^^^^ `NotFrozenBase` definition
|
||||
5 | x: int
|
||||
|
|
||||
info: rule `invalid-frozen-dataclass-subclass` is enabled by default
|
||||
|
||||
```
|
||||
Reference in New Issue
Block a user