Compare commits
6 Commits
charlie/di
...
charlie/fu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
742c1b65d3 | ||
|
|
5220cf736f | ||
|
|
2c5e618f89 | ||
|
|
5bb91fa527 | ||
|
|
40a117cc43 | ||
|
|
0c810fe869 |
@@ -163,7 +163,7 @@ static PANDAS: Benchmark = Benchmark::new(
|
||||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY312,
|
||||
},
|
||||
4000,
|
||||
4057,
|
||||
);
|
||||
|
||||
static PYDANTIC: Benchmark = Benchmark::new(
|
||||
@@ -194,7 +194,7 @@ static SYMPY: Benchmark = Benchmark::new(
|
||||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY312,
|
||||
},
|
||||
13116,
|
||||
13200,
|
||||
);
|
||||
|
||||
static TANJUN: Benchmark = Benchmark::new(
|
||||
|
||||
@@ -3769,6 +3769,7 @@ quux.<CURSOR>
|
||||
__eq__ :: bound method Quux.__eq__(value: object, /) -> bool
|
||||
__format__ :: bound method Quux.__format__(format_spec: str, /) -> str
|
||||
__ge__ :: bound method Quux.__ge__(value: tuple[int | str, ...], /) -> bool
|
||||
__getattr__ :: bound method NamedTupleFallback.__getattr__(name: str, /) -> Any
|
||||
__getattribute__ :: bound method Quux.__getattribute__(name: str, /) -> Any
|
||||
__getitem__ :: Overload[(index: Literal[-2, 0], /) -> int, (index: Literal[-1, 1], /) -> str, (index: SupportsIndex, /) -> int | str, (index: slice[Any, Any, Any], /) -> tuple[int | str, ...]]
|
||||
__getstate__ :: bound method Quux.__getstate__() -> object
|
||||
@@ -3783,7 +3784,7 @@ quux.<CURSOR>
|
||||
__module__ :: str
|
||||
__mul__ :: bound method Quux.__mul__(value: SupportsIndex, /) -> tuple[int | str, ...]
|
||||
__ne__ :: bound method Quux.__ne__(value: object, /) -> bool
|
||||
__new__ :: (x: int, y: str) -> None
|
||||
__new__ :: (x: int, y: str) -> Quux
|
||||
__orig_bases__ :: tuple[Any, ...]
|
||||
__reduce__ :: bound method Quux.__reduce__() -> str | tuple[Any, ...]
|
||||
__reduce_ex__ :: bound method Quux.__reduce_ex__(protocol: SupportsIndex, /) -> str | tuple[Any, ...]
|
||||
|
||||
@@ -23,14 +23,259 @@ tests for the `__class__` attribute.)
|
||||
reveal_type(type(1)) # revealed: <class 'int'>
|
||||
```
|
||||
|
||||
But a three-argument call to type creates a dynamic instance of the `type` class:
|
||||
A three-argument call to `type()` creates a new class. We synthesize a class type using the name
|
||||
from the first argument:
|
||||
|
||||
```py
|
||||
class Base: ...
|
||||
class Mixin: ...
|
||||
|
||||
# We synthesize a class type using the name argument
|
||||
reveal_type(type("Foo", (), {})) # revealed: <class 'Foo'>
|
||||
|
||||
# With a single base class
|
||||
reveal_type(type("Foo", (Base,), {"attr": 1})) # revealed: <class 'Foo'>
|
||||
|
||||
# With multiple base classes
|
||||
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]] = []
|
||||
testCaseClass = type("Foo", (Base,), {})
|
||||
tests.append(testCaseClass) # No error - type[Foo] is assignable to type[Base]
|
||||
```
|
||||
|
||||
Instances of functional classes are typed with the synthesized class name. Attributes from all base
|
||||
classes are accessible:
|
||||
|
||||
```py
|
||||
class Base:
|
||||
base_attr: int = 1
|
||||
|
||||
def base_method(self) -> str:
|
||||
return "hello"
|
||||
|
||||
class Mixin:
|
||||
mixin_attr: str = "mixin"
|
||||
|
||||
Foo = type("Foo", (Base,), {})
|
||||
foo = Foo()
|
||||
|
||||
# Instance is typed with the synthesized class name
|
||||
reveal_type(foo) # revealed: Foo
|
||||
|
||||
# Inherited attributes are accessible
|
||||
reveal_type(foo.base_attr) # revealed: int
|
||||
reveal_type(foo.base_method()) # revealed: str
|
||||
|
||||
# Multiple inheritance: attributes from all bases are accessible
|
||||
Bar = type("Bar", (Base, Mixin), {})
|
||||
bar = Bar()
|
||||
reveal_type(bar.base_attr) # revealed: int
|
||||
reveal_type(bar.mixin_attr) # revealed: str
|
||||
```
|
||||
|
||||
Attributes from the namespace dict (third argument) are not tracked. Like Pyright, we error when
|
||||
attempting to access them:
|
||||
|
||||
```py
|
||||
class Base: ...
|
||||
|
||||
reveal_type(type("Foo", (), {})) # revealed: type
|
||||
Foo = type("Foo", (Base,), {"custom_attr": 42})
|
||||
foo = Foo()
|
||||
|
||||
reveal_type(type("Foo", (Base,), {"attr": 1})) # revealed: type
|
||||
# error: [unresolved-attribute] "Object of type `Foo` has no attribute `custom_attr`"
|
||||
reveal_type(foo.custom_attr) # revealed: Unknown
|
||||
```
|
||||
|
||||
Regular classes can inherit from functional classes:
|
||||
|
||||
```py
|
||||
class Base:
|
||||
base_attr: int = 1
|
||||
|
||||
FunctionalClass = type("FunctionalClass", (Base,), {})
|
||||
|
||||
class Child(FunctionalClass):
|
||||
child_attr: str = "child"
|
||||
|
||||
child = Child()
|
||||
|
||||
# Attributes from the functional class's base are accessible
|
||||
reveal_type(child.base_attr) # revealed: int
|
||||
|
||||
# The child class's own attributes are accessible
|
||||
reveal_type(child.child_attr) # revealed: str
|
||||
|
||||
# Child instances are subtypes of FunctionalClass instances
|
||||
def takes_functional(x: FunctionalClass) -> None: ...
|
||||
|
||||
takes_functional(child) # No error - Child is a subtype of FunctionalClass
|
||||
|
||||
# isinstance narrows to the functional class instance type
|
||||
def check_isinstance(x: object) -> None:
|
||||
if isinstance(x, FunctionalClass):
|
||||
reveal_type(x) # revealed: FunctionalClass
|
||||
```
|
||||
|
||||
Functional classes are correctly recognized as disjoint from unrelated types:
|
||||
|
||||
```py
|
||||
class Base: ...
|
||||
|
||||
Foo = type("Foo", (Base,), {})
|
||||
|
||||
def check_disjointness(x: Foo | int) -> None:
|
||||
if isinstance(x, int):
|
||||
reveal_type(x) # revealed: int
|
||||
else:
|
||||
# Foo and int are not considered disjoint because `class C(Foo, int)` could exist.
|
||||
reveal_type(x) # revealed: Foo & ~int
|
||||
|
||||
# Functional class inheriting from int is NOT disjoint from int
|
||||
IntSubclass = type("IntSubclass", (int,), {})
|
||||
|
||||
def check_int_subclass(x: IntSubclass | str) -> None:
|
||||
if isinstance(x, int):
|
||||
# IntSubclass inherits from int, so it's included in the narrowed type
|
||||
reveal_type(x) # revealed: IntSubclass
|
||||
else:
|
||||
reveal_type(x) # revealed: str
|
||||
```
|
||||
|
||||
Disjointness also works for `type[]` of functional classes:
|
||||
|
||||
```py
|
||||
from ty_extensions import is_disjoint_from, static_assert
|
||||
|
||||
# Functional classes with disjoint bases have disjoint type[] types.
|
||||
IntClass = type("IntClass", (int,), {})
|
||||
StrClass = type("StrClass", (str,), {})
|
||||
|
||||
static_assert(is_disjoint_from(type[IntClass], type[StrClass]))
|
||||
static_assert(is_disjoint_from(type[StrClass], type[IntClass]))
|
||||
|
||||
# Functional classes that share a common base are not disjoint.
|
||||
class Base: ...
|
||||
|
||||
Foo = type("Foo", (Base,), {})
|
||||
Bar = type("Bar", (Base,), {})
|
||||
|
||||
static_assert(not is_disjoint_from(type[Foo], type[Bar]))
|
||||
```
|
||||
|
||||
Functional classes can be used as pivot in `super()`:
|
||||
|
||||
```py
|
||||
class Base:
|
||||
def method(self) -> int:
|
||||
return 42
|
||||
|
||||
FunctionalChild = type("FunctionalChild", (Base,), {})
|
||||
|
||||
# Using functional class as pivot with functional class instance owner
|
||||
fc = FunctionalChild()
|
||||
reveal_type(super(FunctionalChild, fc)) # revealed: <super: FunctionalChild, FunctionalChild>
|
||||
reveal_type(super(FunctionalChild, fc).method()) # revealed: int
|
||||
|
||||
# Regular class inheriting from functional class
|
||||
class RegularChild(FunctionalChild):
|
||||
pass
|
||||
|
||||
rc = RegularChild()
|
||||
reveal_type(super(RegularChild, rc)) # revealed: <super: <class 'RegularChild'>, RegularChild>
|
||||
reveal_type(super(RegularChild, rc).method()) # revealed: int
|
||||
|
||||
# Using functional class as pivot with regular class instance owner
|
||||
reveal_type(super(FunctionalChild, rc)) # revealed: <super: FunctionalChild, RegularChild>
|
||||
reveal_type(super(FunctionalChild, rc).method()) # revealed: int
|
||||
```
|
||||
|
||||
Functional classes can inherit from other functional classes:
|
||||
|
||||
```py
|
||||
class Base:
|
||||
base_attr: int = 1
|
||||
|
||||
# Create a functional class that inherits from a regular class.
|
||||
Parent = type("Parent", (Base,), {})
|
||||
reveal_type(Parent) # revealed: <class 'Parent'>
|
||||
|
||||
# Create a functional class that inherits from another functional class.
|
||||
ChildCls = type("ChildCls", (Parent,), {})
|
||||
reveal_type(ChildCls) # revealed: <class 'ChildCls'>
|
||||
|
||||
# Child instances have access to attributes from the entire inheritance chain.
|
||||
child = ChildCls()
|
||||
reveal_type(child) # revealed: ChildCls
|
||||
reveal_type(child.base_attr) # revealed: int
|
||||
|
||||
# Child instances are subtypes of Parent instances.
|
||||
def takes_parent(x: Parent) -> None: ...
|
||||
|
||||
takes_parent(child) # No error - ChildCls is a subtype of Parent
|
||||
```
|
||||
|
||||
Functional classes with generic base classes:
|
||||
|
||||
```py
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
class Container(Generic[T]):
|
||||
value: T
|
||||
|
||||
# Functional class inheriting from a generic class specialization
|
||||
IntContainer = type("IntContainer", (Container[int],), {})
|
||||
reveal_type(IntContainer) # revealed: <class 'IntContainer'>
|
||||
|
||||
container = IntContainer()
|
||||
reveal_type(container) # revealed: IntContainer
|
||||
reveal_type(container.value) # revealed: int
|
||||
```
|
||||
|
||||
`type(instance)` returns the class of the functional instance:
|
||||
|
||||
```py
|
||||
class Base: ...
|
||||
|
||||
Foo = type("Foo", (Base,), {})
|
||||
foo = Foo()
|
||||
|
||||
# type() on an instance returns the class
|
||||
reveal_type(type(foo)) # revealed: type[Foo]
|
||||
```
|
||||
|
||||
`__class__` attribute access on functional instances:
|
||||
|
||||
```py
|
||||
class Base: ...
|
||||
|
||||
Foo = type("Foo", (Base,), {})
|
||||
foo = Foo()
|
||||
|
||||
# __class__ returns the class type
|
||||
reveal_type(foo.__class__) # revealed: type[Foo]
|
||||
```
|
||||
|
||||
Functional instances are subtypes of `object`:
|
||||
|
||||
```py
|
||||
class Base: ...
|
||||
|
||||
Foo = type("Foo", (Base,), {})
|
||||
foo = Foo()
|
||||
|
||||
# All functional instances are subtypes of object
|
||||
def takes_object(x: object) -> None: ...
|
||||
|
||||
takes_object(foo) # No error - Foo is a subtype of object
|
||||
|
||||
# Even functional classes with no explicit bases are subtypes of object
|
||||
EmptyBases = type("EmptyBases", (), {})
|
||||
empty = EmptyBases()
|
||||
takes_object(empty) # No error
|
||||
```
|
||||
|
||||
Other numbers of arguments are invalid
|
||||
@@ -61,6 +306,42 @@ type("Foo", (1, 2), {})
|
||||
type("Foo", (Base,), {b"attr": 1})
|
||||
```
|
||||
|
||||
MRO errors are detected and reported:
|
||||
|
||||
```py
|
||||
class A: ...
|
||||
|
||||
# Duplicate bases are detected
|
||||
# error: [duplicate-base] "Duplicate base class <class 'A'> in class `Dup`"
|
||||
Dup = type("Dup", (A, A), {})
|
||||
```
|
||||
|
||||
```py
|
||||
class A: ...
|
||||
class B(A): ...
|
||||
class C(A): ...
|
||||
|
||||
# This creates an inconsistent MRO because D would need B before C (from first base)
|
||||
# but also C before B (from second base inheritance through A)
|
||||
class X(B, C): ...
|
||||
class Y(C, B): ...
|
||||
|
||||
# error: [inconsistent-mro] "Cannot create a consistent method resolution order (MRO) for class `Conflict` with bases `[<class 'X'>, <class 'Y'>]`"
|
||||
Conflict = type("Conflict", (X, Y), {})
|
||||
```
|
||||
|
||||
Metaclass conflicts are detected and reported:
|
||||
|
||||
```py
|
||||
class Meta1(type): ...
|
||||
class Meta2(type): ...
|
||||
class A(metaclass=Meta1): ...
|
||||
class B(metaclass=Meta2): ...
|
||||
|
||||
# error: [conflicting-metaclass] "The metaclass of a derived class (`Bad`) must be a subclass of the metaclasses of all its bases, but `Meta1` (metaclass of base class `<class 'A'>`) and `Meta2` (metaclass of base class `<class 'B'>`) have no subclass relationship"
|
||||
Bad = type("Bad", (A, B), {})
|
||||
```
|
||||
|
||||
## Calls to `str()`
|
||||
|
||||
### Valid calls
|
||||
|
||||
@@ -1688,3 +1688,254 @@ reveal_type(ordered_foo()) # revealed: @Todo(Type::Intersection.call)
|
||||
# TODO: should be `Any`
|
||||
reveal_type(ordered_foo() < ordered_foo()) # revealed: @Todo(Type::Intersection.call)
|
||||
```
|
||||
|
||||
## `dataclasses.make_dataclass`
|
||||
|
||||
The `make_dataclass` function creates dataclasses dynamically.
|
||||
|
||||
### Basic usage
|
||||
|
||||
Using tuple syntax for fields (recommended for best type inference):
|
||||
|
||||
```py
|
||||
from dataclasses import make_dataclass
|
||||
|
||||
Point = make_dataclass("Point", (("x", int), ("y", int)))
|
||||
|
||||
reveal_type(Point) # revealed: <class 'Point'>
|
||||
|
||||
p = Point(1, 2)
|
||||
reveal_type(p) # revealed: Point
|
||||
reveal_type(p.x) # revealed: int
|
||||
reveal_type(p.y) # revealed: int
|
||||
|
||||
# The constructor accepts keyword arguments.
|
||||
p2 = Point(x=3, y=4)
|
||||
reveal_type(p2) # revealed: Point
|
||||
```
|
||||
|
||||
### Fields with types
|
||||
|
||||
Fields can be specified as `(name, type)` tuples:
|
||||
|
||||
```py
|
||||
from dataclasses import make_dataclass
|
||||
|
||||
Person = make_dataclass("Person", (("name", str), ("age", int)))
|
||||
|
||||
alice = Person("Alice", 30)
|
||||
reveal_type(alice.name) # revealed: str
|
||||
reveal_type(alice.age) # revealed: int
|
||||
|
||||
# error: [missing-argument] "No argument provided for required parameter `age`"
|
||||
Person("Bob")
|
||||
|
||||
# error: [too-many-positional-arguments]
|
||||
Person("Eve", 20, "extra")
|
||||
```
|
||||
|
||||
### List syntax
|
||||
|
||||
List syntax works the same as tuple syntax:
|
||||
|
||||
```py
|
||||
from dataclasses import make_dataclass
|
||||
|
||||
Point = make_dataclass("Point", [("x", int), ("y", int)])
|
||||
reveal_type(Point) # revealed: <class 'Point'>
|
||||
|
||||
p = Point(1, 2)
|
||||
reveal_type(p) # revealed: Point
|
||||
reveal_type(p.x) # revealed: int
|
||||
reveal_type(p.y) # revealed: int
|
||||
```
|
||||
|
||||
### Fields with defaults
|
||||
|
||||
Fields can have default values using the 3-tuple syntax `(name, type, default)`:
|
||||
|
||||
```py
|
||||
from dataclasses import make_dataclass
|
||||
|
||||
Person = make_dataclass("Person", [("name", str), ("age", int, 0)])
|
||||
|
||||
# With default, `age` is optional.
|
||||
bob = Person("Bob")
|
||||
reveal_type(bob) # revealed: Person
|
||||
reveal_type(bob.name) # revealed: str
|
||||
reveal_type(bob.age) # revealed: int
|
||||
|
||||
# Can still provide all arguments.
|
||||
alice = Person("Alice", 30)
|
||||
reveal_type(alice.age) # revealed: int
|
||||
|
||||
# Keyword arguments work too.
|
||||
charlie = Person(name="Charlie", age=25)
|
||||
reveal_type(charlie) # revealed: Person
|
||||
```
|
||||
|
||||
### String-only fields
|
||||
|
||||
Fields can be specified as just a string (field name only, type is `Any`):
|
||||
|
||||
```py
|
||||
from dataclasses import make_dataclass
|
||||
|
||||
# String-only fields have type Any.
|
||||
Flexible = make_dataclass("Flexible", ["x", "y"])
|
||||
|
||||
f = Flexible(1, "hello")
|
||||
reveal_type(f) # revealed: Flexible
|
||||
reveal_type(f.x) # revealed: Any
|
||||
reveal_type(f.y) # revealed: Any
|
||||
```
|
||||
|
||||
### Base classes
|
||||
|
||||
The `bases` keyword argument specifies base classes:
|
||||
|
||||
```py
|
||||
from dataclasses import make_dataclass
|
||||
|
||||
class Base:
|
||||
def greet(self) -> str:
|
||||
return "Hello"
|
||||
|
||||
Derived = make_dataclass("Derived", [("value", int)], bases=(Base,))
|
||||
|
||||
d = Derived(42)
|
||||
reveal_type(d) # revealed: Derived
|
||||
reveal_type(d.value) # revealed: int
|
||||
reveal_type(d.greet()) # revealed: str
|
||||
```
|
||||
|
||||
### The `order` parameter
|
||||
|
||||
When `order=True`, comparison methods are generated:
|
||||
|
||||
```py
|
||||
from dataclasses import make_dataclass
|
||||
|
||||
Ordered = make_dataclass("Ordered", [("x", int)], order=True)
|
||||
|
||||
a = Ordered(1)
|
||||
b = Ordered(2)
|
||||
|
||||
reveal_type(a < b) # revealed: bool
|
||||
reveal_type(a <= b) # revealed: bool
|
||||
reveal_type(a > b) # revealed: bool
|
||||
reveal_type(a >= b) # revealed: bool
|
||||
```
|
||||
|
||||
When `order=False` (the default), comparison methods are not generated:
|
||||
|
||||
```py
|
||||
from dataclasses import make_dataclass
|
||||
|
||||
Unordered = make_dataclass("Unordered", [("x", int)])
|
||||
|
||||
a = Unordered(1)
|
||||
b = Unordered(2)
|
||||
|
||||
# error: [unsupported-operator] "Operator `<` is not supported between two objects of type `Unordered`"
|
||||
a < b
|
||||
```
|
||||
|
||||
### The `frozen` parameter
|
||||
|
||||
When `frozen=True`, the dataclass is immutable and hashable:
|
||||
|
||||
```py
|
||||
from dataclasses import make_dataclass
|
||||
|
||||
Frozen = make_dataclass("Frozen", [("x", int)], frozen=True)
|
||||
|
||||
f = Frozen(1)
|
||||
reveal_type(hash(f)) # revealed: int
|
||||
```
|
||||
|
||||
### The `eq` parameter
|
||||
|
||||
When `eq=False`, the `__eq__` method is not generated, and `__hash__` falls back to
|
||||
`object.__hash__`:
|
||||
|
||||
```py
|
||||
from dataclasses import make_dataclass
|
||||
|
||||
NoEq = make_dataclass("NoEq", [("x", int)], eq=False)
|
||||
|
||||
a = NoEq(1)
|
||||
# __hash__ is inherited from object.
|
||||
reveal_type(hash(a)) # revealed: int
|
||||
```
|
||||
|
||||
### The `init` parameter
|
||||
|
||||
When `init=False`, no `__init__` is generated and calling the class with arguments fails:
|
||||
|
||||
```py
|
||||
from dataclasses import make_dataclass
|
||||
|
||||
NoInit = make_dataclass("NoInit", [("x", int)], init=False)
|
||||
|
||||
# error: [possibly-missing-implicit-call] "`__init__` method is missing on type `<class 'NoInit'>`. Make sure your `object` in typeshed has its definition."
|
||||
NoInit(1)
|
||||
```
|
||||
|
||||
### The `kw_only` parameter
|
||||
|
||||
When `kw_only=True`, all fields are keyword-only:
|
||||
|
||||
```py
|
||||
from dataclasses import make_dataclass
|
||||
|
||||
KwOnly = make_dataclass("KwOnly", [("x", int), ("y", str)], kw_only=True)
|
||||
|
||||
# Positional arguments are not allowed.
|
||||
# error: [missing-argument] "No arguments provided for required parameters `x`, `y`"
|
||||
# error: [too-many-positional-arguments]
|
||||
KwOnly(1, "hello")
|
||||
|
||||
# Keyword arguments work.
|
||||
k = KwOnly(x=1, y="hello")
|
||||
reveal_type(k) # revealed: KwOnly
|
||||
```
|
||||
|
||||
### The `unsafe_hash` parameter
|
||||
|
||||
When `unsafe_hash=True`, a `__hash__` method is generated even if `eq=True` and `frozen=False`:
|
||||
|
||||
```py
|
||||
from dataclasses import make_dataclass
|
||||
|
||||
UnsafeHash = make_dataclass("UnsafeHash", [("x", int)], unsafe_hash=True)
|
||||
|
||||
u = UnsafeHash(1)
|
||||
reveal_type(hash(u)) # revealed: int
|
||||
```
|
||||
|
||||
### Non-literal fields
|
||||
|
||||
When fields are passed as a variable rather than a literal, we cannot synthesize the dataclass
|
||||
fields. The return type is `type` instead of the specific dataclass class:
|
||||
|
||||
```py
|
||||
from dataclasses import make_dataclass
|
||||
|
||||
fields = [("x", int), ("y", str)]
|
||||
Dynamic = make_dataclass("Dynamic", fields)
|
||||
|
||||
# When fields is a variable, we cannot infer the class structure.
|
||||
reveal_type(Dynamic) # revealed: type
|
||||
```
|
||||
|
||||
### Invalid fields type
|
||||
|
||||
When an invalid type is passed as fields (not an iterable of field specs), we emit an error:
|
||||
|
||||
```py
|
||||
from dataclasses import make_dataclass
|
||||
|
||||
# error: [invalid-argument-type]
|
||||
Invalid = make_dataclass("Invalid", 42)
|
||||
```
|
||||
|
||||
@@ -90,11 +90,32 @@ Alternative functional syntax:
|
||||
Person2 = NamedTuple("Person", [("id", int), ("name", str)])
|
||||
alice2 = Person2(1, "Alice")
|
||||
|
||||
# TODO: should be an error
|
||||
# error: [missing-argument] "No argument provided for required parameter `name`"
|
||||
Person2(1)
|
||||
|
||||
reveal_type(alice2.id) # revealed: @Todo(functional `NamedTuple` syntax)
|
||||
reveal_type(alice2.name) # revealed: @Todo(functional `NamedTuple` syntax)
|
||||
reveal_type(alice2.id) # revealed: int
|
||||
reveal_type(alice2.name) # revealed: str
|
||||
```
|
||||
|
||||
### Functional syntax with variable fields
|
||||
|
||||
When fields are passed via a variable (not a literal), we fall back to `NamedTupleFallback` which
|
||||
allows any attribute access. This is a regression test for accessing `Self` attributes in methods of
|
||||
classes that inherit from namedtuples with dynamic fields:
|
||||
|
||||
```py
|
||||
from typing import NamedTuple
|
||||
from typing_extensions import Self
|
||||
|
||||
fields = [("host", str), ("port", int)]
|
||||
|
||||
class Url(NamedTuple("Url", fields)):
|
||||
def with_port(self, port: int) -> Self:
|
||||
# Attribute access on Self works via NamedTupleFallback.__getattr__.
|
||||
reveal_type(self.host) # revealed: Any
|
||||
reveal_type(self.port) # revealed: Any
|
||||
reveal_type(self.unknown) # revealed: Any
|
||||
return self._replace(port=port)
|
||||
```
|
||||
|
||||
### Definition
|
||||
@@ -305,10 +326,156 @@ reveal_type(IntBox(1)._replace(content=42)) # revealed: IntBox
|
||||
```py
|
||||
from collections import namedtuple
|
||||
|
||||
Person = namedtuple("Person", ["id", "name", "age"], defaults=[None])
|
||||
Person = namedtuple("Person", ["id", "name"])
|
||||
|
||||
alice = Person(1, "Alice", 42)
|
||||
bob = Person(2, "Bob")
|
||||
alice = Person(1, "Alice")
|
||||
|
||||
# Field access returns Any (no type information available).
|
||||
reveal_type(alice.id) # revealed: Any
|
||||
reveal_type(alice.name) # revealed: Any
|
||||
|
||||
# Alternative field specifications.
|
||||
Point1 = namedtuple("Point1", ["x", "y"])
|
||||
Point2 = namedtuple("Point2", "x y")
|
||||
Point3 = namedtuple("Point3", "x, y")
|
||||
|
||||
p1 = Point1(1, 2)
|
||||
p2 = Point2(3, 4)
|
||||
p3 = Point3(5, 6)
|
||||
|
||||
reveal_type(p1.x) # revealed: Any
|
||||
reveal_type(p2.x) # revealed: Any
|
||||
reveal_type(p3.x) # revealed: Any
|
||||
```
|
||||
|
||||
## `collections.namedtuple` with variable field names
|
||||
|
||||
When field names are passed via a variable (not a literal), we fall back to `NamedTupleFallback`
|
||||
which allows any attribute access. This is a regression test for accessing `Self` attributes in
|
||||
methods of classes that inherit from namedtuples with dynamic fields:
|
||||
|
||||
```py
|
||||
from collections import namedtuple
|
||||
from typing_extensions import Self
|
||||
|
||||
field_names = ["host", "port"]
|
||||
|
||||
class Url(namedtuple("Url", field_names)):
|
||||
def with_port(self, port: int) -> Self:
|
||||
# Attribute access on Self works via NamedTupleFallback.__getattr__.
|
||||
reveal_type(self.host) # revealed: Any
|
||||
reveal_type(self.port) # revealed: Any
|
||||
reveal_type(self.unknown) # revealed: Any
|
||||
return self._replace(port=port)
|
||||
```
|
||||
|
||||
## `collections.namedtuple` with defaults
|
||||
|
||||
The `defaults` parameter provides default values for the rightmost fields:
|
||||
|
||||
```py
|
||||
from collections import namedtuple
|
||||
|
||||
# Two fields, one default (applies to 'y').
|
||||
Point = namedtuple("Point", ["x", "y"], defaults=[0])
|
||||
|
||||
# Can be called with both arguments.
|
||||
p1 = Point(1, 2)
|
||||
reveal_type(p1) # revealed: Point
|
||||
|
||||
# Can be called with just the required argument.
|
||||
p2 = Point(1)
|
||||
reveal_type(p2) # revealed: Point
|
||||
|
||||
# error: [missing-argument] "No argument provided for required parameter `x`"
|
||||
Point()
|
||||
|
||||
# All fields have defaults.
|
||||
Point3D = namedtuple("Point3D", ["x", "y", "z"], defaults=[0, 0, 0])
|
||||
p3 = Point3D()
|
||||
reveal_type(p3) # revealed: Point3D
|
||||
|
||||
# Namedtuples with defaults are still compatible with tuple types.
|
||||
def takes_tuple(t: tuple[int, int]) -> None:
|
||||
pass
|
||||
|
||||
takes_tuple(Point(1, 2))
|
||||
```
|
||||
|
||||
## `collections.namedtuple` with rename
|
||||
|
||||
The `rename` parameter replaces invalid field names with positional names (`_0`, `_1`, etc.):
|
||||
|
||||
```py
|
||||
from collections import namedtuple
|
||||
|
||||
# Fields with Python keywords are renamed when rename=True.
|
||||
NT1 = namedtuple("NT1", ["abc", "def"], rename=True)
|
||||
nt1 = NT1(abc="x", _1="y")
|
||||
reveal_type(nt1) # revealed: NT1
|
||||
|
||||
# Fields starting with underscore are renamed when rename=True.
|
||||
NT2 = namedtuple("NT2", ["abc", "_d"], rename=True)
|
||||
nt2 = NT2(abc="x", _1="y")
|
||||
reveal_type(nt2) # revealed: NT2
|
||||
|
||||
# Duplicate field names are renamed when rename=True.
|
||||
NT3 = namedtuple("NT3", ["a", "a", "a"], rename=True)
|
||||
nt3 = NT3(a="x", _1="y", _2="z")
|
||||
reveal_type(nt3) # revealed: NT3
|
||||
|
||||
# Without rename=True, the original field names are used.
|
||||
NT4 = namedtuple("NT4", ["abc", "xyz"])
|
||||
nt4 = NT4(abc="x", xyz="y")
|
||||
reveal_type(nt4) # revealed: NT4
|
||||
```
|
||||
|
||||
## `collections.namedtuple` attributes
|
||||
|
||||
Functional namedtuples have synthesized attributes similar to class-based namedtuples:
|
||||
|
||||
```py
|
||||
from collections import namedtuple
|
||||
|
||||
Person = namedtuple("Person", ["name", "age"])
|
||||
|
||||
reveal_type(Person._fields) # revealed: tuple[Literal["name"], Literal["age"]]
|
||||
reveal_type(Person._field_defaults) # revealed: dict[str, Any]
|
||||
reveal_type(Person._make) # revealed: bound method <class 'Person'>._make(iterable: Iterable[Any]) -> Person
|
||||
reveal_type(Person._asdict) # revealed: def _asdict(self) -> dict[str, Any]
|
||||
reveal_type(Person._replace) # revealed: (self: Self, *, name: Any = ..., age: Any = ...) -> Self
|
||||
|
||||
# _make creates instances from an iterable.
|
||||
reveal_type(Person._make(["Alice", 30])) # revealed: Person
|
||||
|
||||
# _asdict converts to a dictionary.
|
||||
person = Person("Alice", 30)
|
||||
reveal_type(person._asdict()) # revealed: dict[str, Any]
|
||||
|
||||
# _replace creates a copy with replaced fields.
|
||||
reveal_type(person._replace(name="Bob")) # revealed: Person
|
||||
```
|
||||
|
||||
## `collections.namedtuple` tuple compatibility
|
||||
|
||||
Functional namedtuples inherit from tuple with `Any` element types since `collections.namedtuple`
|
||||
doesn't provide type information:
|
||||
|
||||
```py
|
||||
from collections import namedtuple
|
||||
from ty_extensions import static_assert, is_subtype_of
|
||||
|
||||
Person = namedtuple("Person", ["name", "age"])
|
||||
|
||||
# Functional namedtuples inherit from tuple[Any, Any, ...].
|
||||
static_assert(is_subtype_of(Person, tuple[object, object]))
|
||||
|
||||
def takes_tuple(t: tuple[str, int]) -> None:
|
||||
pass
|
||||
|
||||
# Instances are assignable to tuple types since fields are Any.
|
||||
p = Person("Alice", 30)
|
||||
takes_tuple(p)
|
||||
```
|
||||
|
||||
## The symbol `NamedTuple` itself
|
||||
|
||||
@@ -1576,7 +1576,10 @@ mod implicit_globals {
|
||||
else {
|
||||
return Place::Undefined.into();
|
||||
};
|
||||
let module_type_scope = module_type_class.body_scope(db);
|
||||
let Some(stmt_class) = module_type_class.as_stmt() else {
|
||||
return Place::Undefined.into();
|
||||
};
|
||||
let module_type_scope = stmt_class.body_scope(db);
|
||||
let place_table = place_table(db, module_type_scope);
|
||||
let Some(symbol_id) = place_table.symbol_id(name) else {
|
||||
return Place::Undefined.into();
|
||||
@@ -1704,7 +1707,9 @@ mod implicit_globals {
|
||||
return smallvec::SmallVec::default();
|
||||
};
|
||||
|
||||
let module_type_scope = module_type.body_scope(db);
|
||||
let Some(module_type_scope) = module_type.body_scope(db) else {
|
||||
return smallvec::SmallVec::default();
|
||||
};
|
||||
let module_type_symbol_table = place_table(db, module_type_scope);
|
||||
|
||||
module_type_symbol_table
|
||||
|
||||
@@ -24,6 +24,9 @@ use ty_module_resolver::{KnownModule, Module, ModuleName, resolve_module};
|
||||
use type_ordering::union_or_intersection_elements_ordering;
|
||||
|
||||
pub(crate) use self::builder::{IntersectionBuilder, UnionBuilder};
|
||||
pub(crate) use self::class::{
|
||||
FunctionalClassLiteral, FunctionalDataclassLiteral, FunctionalNamedTupleLiteral,
|
||||
};
|
||||
pub use self::cyclic::CycleDetector;
|
||||
pub(crate) use self::cyclic::{PairVisitor, TypeTransformer};
|
||||
pub(crate) use self::diagnostic::register_lints;
|
||||
@@ -78,7 +81,7 @@ use crate::types::visitor::any_over_type;
|
||||
use crate::unpack::EvaluationMode;
|
||||
use crate::{Db, FxOrderSet, Program};
|
||||
pub use class::KnownClass;
|
||||
pub(crate) use class::{ClassLiteral, ClassType, GenericAlias};
|
||||
pub(crate) use class::{ClassLiteral, ClassType, GenericAlias, StmtClassLiteral};
|
||||
use instance::Protocol;
|
||||
pub use instance::{NominalInstanceType, ProtocolInstanceType};
|
||||
pub use special_form::SpecialFormType;
|
||||
@@ -814,7 +817,7 @@ pub enum Type<'db> {
|
||||
Callable(CallableType<'db>),
|
||||
/// A specific module object
|
||||
ModuleLiteral(ModuleLiteralType<'db>),
|
||||
/// A specific class object
|
||||
/// A specific class object (either from a `class` statement or `type()` call)
|
||||
ClassLiteral(ClassLiteral<'db>),
|
||||
/// A specialization of a generic class
|
||||
GenericAlias(GenericAlias<'db>),
|
||||
@@ -1009,7 +1012,8 @@ impl<'db> Type<'db> {
|
||||
|
||||
fn is_enum(&self, db: &'db dyn Db) -> bool {
|
||||
self.as_nominal_instance()
|
||||
.and_then(|instance| crate::types::enums::enum_metadata(db, instance.class_literal(db)))
|
||||
.and_then(|instance| instance.class_literal(db))
|
||||
.and_then(|class| crate::types::enums::enum_metadata(db, class))
|
||||
.is_some()
|
||||
}
|
||||
|
||||
@@ -1129,25 +1133,21 @@ impl<'db> Type<'db> {
|
||||
pub(crate) fn specialization_of(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
expected_class: ClassLiteral<'_>,
|
||||
expected_class: StmtClassLiteral<'_>,
|
||||
) -> Option<Specialization<'db>> {
|
||||
self.class_and_specialization_of_optional(db, Some(expected_class))
|
||||
.map(|(_, specialization)| specialization)
|
||||
self.specialization_of_optional(db, Some(expected_class))
|
||||
}
|
||||
|
||||
/// If this type is a class instance, returns its class literal and specialization.
|
||||
pub(crate) fn class_specialization(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
) -> Option<(ClassLiteral<'db>, Specialization<'db>)> {
|
||||
self.class_and_specialization_of_optional(db, None)
|
||||
/// If this type is a class instance, returns its specialization.
|
||||
pub(crate) fn class_specialization(self, db: &'db dyn Db) -> Option<Specialization<'db>> {
|
||||
self.specialization_of_optional(db, None)
|
||||
}
|
||||
|
||||
fn class_and_specialization_of_optional(
|
||||
fn specialization_of_optional(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
expected_class: Option<ClassLiteral<'_>>,
|
||||
) -> Option<(ClassLiteral<'db>, Specialization<'db>)> {
|
||||
expected_class: Option<StmtClassLiteral<'_>>,
|
||||
) -> Option<Specialization<'db>> {
|
||||
let class_type = match self {
|
||||
Type::NominalInstance(instance) => instance,
|
||||
Type::ProtocolInstance(instance) => instance.to_nominal_instance()?,
|
||||
@@ -1156,12 +1156,12 @@ impl<'db> Type<'db> {
|
||||
}
|
||||
.class(db);
|
||||
|
||||
let (class_literal, specialization) = class_type.class_literal(db);
|
||||
let (class_literal, specialization) = class_type.stmt_class_literal(db)?;
|
||||
if expected_class.is_some_and(|expected_class| expected_class != class_literal) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some((class_literal, specialization?))
|
||||
specialization
|
||||
}
|
||||
|
||||
/// Returns the top materialization (or upper bound materialization) of this type, which is the
|
||||
@@ -1303,9 +1303,11 @@ impl<'db> Type<'db> {
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub(crate) const fn expect_class_literal(self) -> ClassLiteral<'db> {
|
||||
pub(crate) fn expect_class_literal(self) -> StmtClassLiteral<'db> {
|
||||
self.as_class_literal()
|
||||
.expect("Expected a Type::ClassLiteral variant")
|
||||
.as_stmt()
|
||||
.expect("Expected a statement-based class literal")
|
||||
}
|
||||
|
||||
pub const fn is_subclass_of(&self) -> bool {
|
||||
@@ -1852,7 +1854,8 @@ impl<'db> Type<'db> {
|
||||
| Type::TypeGuard(_)
|
||||
| Type::TypedDict(_)
|
||||
| Type::TypeAlias(_)
|
||||
| Type::NewTypeInstance(_) => false,
|
||||
| Type::NewTypeInstance(_)
|
||||
=> false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1898,16 +1901,17 @@ impl<'db> Type<'db> {
|
||||
}
|
||||
|
||||
// TODO: This is unsound so in future we can consider an opt-in option to disable it.
|
||||
Type::SubclassOf(subclass_of_ty) => match subclass_of_ty.subclass_of() {
|
||||
SubclassOfInner::Class(class) => Some(class.into_callable(db)),
|
||||
|
||||
SubclassOfInner::Dynamic(_) | SubclassOfInner::TypeVar(_) => {
|
||||
Type::SubclassOf(subclass_of_ty) => {
|
||||
let inner = subclass_of_ty.subclass_of();
|
||||
if let SubclassOfInner::Class(class) = inner {
|
||||
Some(class.into_callable(db))
|
||||
} else {
|
||||
Some(CallableTypes::one(CallableType::single(
|
||||
db,
|
||||
Signature::new(Parameters::unknown(), Some(Type::from(subclass_of_ty))),
|
||||
)))
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
Type::Union(union) => {
|
||||
let mut callables = SmallVec::new();
|
||||
@@ -2199,6 +2203,98 @@ impl<'db> Type<'db> {
|
||||
})
|
||||
}
|
||||
|
||||
(
|
||||
Type::KnownInstance(KnownInstanceType::TypingNamedTupleFieldsSchema(_)),
|
||||
Type::SpecialForm(SpecialFormType::TypingNamedTupleFieldsSchema),
|
||||
) if relation.is_assignability() => ConstraintSet::from(true),
|
||||
|
||||
// Iterable[tuple[str, type]] is assignable to TypingNamedTupleFieldsSchema.
|
||||
// This allows passing fields via variables:
|
||||
//
|
||||
// fields = [("x", int), ("y", int)]
|
||||
// NamedTuple("Point", fields)
|
||||
//
|
||||
// Without this, only literal lists would type-check.
|
||||
(_, Type::SpecialForm(SpecialFormType::TypingNamedTupleFieldsSchema))
|
||||
if relation.is_assignability() =>
|
||||
{
|
||||
// Expected: Iterable[tuple[str, type[Any]]]
|
||||
let field_tuple = Type::heterogeneous_tuple(
|
||||
db,
|
||||
[
|
||||
KnownClass::Str.to_instance(db),
|
||||
KnownClass::Type.to_specialized_instance(db, [Type::any()]),
|
||||
],
|
||||
);
|
||||
let expected = KnownClass::Iterable.to_specialized_instance(db, [field_tuple]);
|
||||
ConstraintSet::from(self.is_assignable_to(db, expected))
|
||||
}
|
||||
|
||||
(
|
||||
Type::KnownInstance(KnownInstanceType::CollectionsNamedTupleFieldsSchema(_)),
|
||||
Type::SpecialForm(SpecialFormType::CollectionsNamedTupleFieldsSchema),
|
||||
) if relation.is_assignability() => ConstraintSet::from(true),
|
||||
|
||||
// str | Sequence[str] is assignable to CollectionsNamedTupleFieldsSchema.
|
||||
// This allows passing field names via variables:
|
||||
//
|
||||
// field_names = ["x", "y"]
|
||||
// namedtuple("Point", field_names)
|
||||
//
|
||||
// Without this, only literal lists/strings would type-check.
|
||||
(_, Type::SpecialForm(SpecialFormType::CollectionsNamedTupleFieldsSchema))
|
||||
if relation.is_assignability() =>
|
||||
{
|
||||
let expected = UnionType::from_elements(
|
||||
db,
|
||||
[
|
||||
KnownClass::Str.to_instance(db),
|
||||
KnownClass::Sequence
|
||||
.to_specialized_instance(db, [KnownClass::Str.to_instance(db)]),
|
||||
],
|
||||
);
|
||||
ConstraintSet::from(self.is_assignable_to(db, expected))
|
||||
}
|
||||
|
||||
(
|
||||
Type::KnownInstance(KnownInstanceType::CollectionsNamedTupleDefaultsSchema(_)),
|
||||
Type::SpecialForm(SpecialFormType::CollectionsNamedTupleDefaultsSchema),
|
||||
) if relation.is_assignability() => ConstraintSet::from(true),
|
||||
|
||||
(
|
||||
Type::KnownInstance(KnownInstanceType::MakeDataclassFieldsSchema(_)),
|
||||
Type::SpecialForm(SpecialFormType::MakeDataclassFieldsSchema),
|
||||
) if relation.is_assignability() => ConstraintSet::from(true),
|
||||
|
||||
// Iterable[str | tuple[str, Any] | tuple[str, Any, Any]] is assignable to
|
||||
// MakeDataclassFieldsSchema. This allows passing fields via variables:
|
||||
//
|
||||
// fields = [("x", int), ("y", str)]
|
||||
// make_dataclass("Point", fields)
|
||||
//
|
||||
// Without this, only literal lists would type-check.
|
||||
(_, Type::SpecialForm(SpecialFormType::MakeDataclassFieldsSchema))
|
||||
if relation.is_assignability() =>
|
||||
{
|
||||
let expected_element = UnionType::from_elements(
|
||||
db,
|
||||
[
|
||||
KnownClass::Str.to_instance(db),
|
||||
Type::heterogeneous_tuple(
|
||||
db,
|
||||
[KnownClass::Str.to_instance(db), Type::any()],
|
||||
),
|
||||
Type::heterogeneous_tuple(
|
||||
db,
|
||||
[KnownClass::Str.to_instance(db), Type::any(), Type::any()],
|
||||
),
|
||||
],
|
||||
);
|
||||
let expected_iterable =
|
||||
KnownClass::Iterable.to_specialized_instance(db, [expected_element]);
|
||||
ConstraintSet::from(self.is_assignable_to(db, expected_iterable))
|
||||
}
|
||||
|
||||
// Dynamic is only a subtype of `object` and only a supertype of `Never`; both were
|
||||
// handled above. It's always assignable, though.
|
||||
//
|
||||
@@ -3236,7 +3332,11 @@ impl<'db> Type<'db> {
|
||||
if literal.enum_class_instance(db) != Type::NominalInstance(instance) {
|
||||
return ConstraintSet::from(false);
|
||||
}
|
||||
ConstraintSet::from(is_single_member_enum(db, instance.class_literal(db)))
|
||||
ConstraintSet::from(
|
||||
instance
|
||||
.class_literal(db)
|
||||
.is_some_and(|class| is_single_member_enum(db, class)),
|
||||
)
|
||||
}
|
||||
|
||||
(Type::PropertyInstance(left), Type::PropertyInstance(right)) => {
|
||||
@@ -3771,7 +3871,7 @@ impl<'db> Type<'db> {
|
||||
SubclassOfInner::Class(class_a) => ConstraintSet::from(
|
||||
!class_a.could_exist_in_mro_of(db, ClassType::NonGeneric(class_b)),
|
||||
),
|
||||
SubclassOfInner::TypeVar(_) => unreachable!(),
|
||||
SubclassOfInner::TypeVar(_) => ConstraintSet::from(false),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3782,7 +3882,7 @@ impl<'db> Type<'db> {
|
||||
SubclassOfInner::Class(class_a) => ConstraintSet::from(
|
||||
!class_a.could_exist_in_mro_of(db, ClassType::Generic(alias_b)),
|
||||
),
|
||||
SubclassOfInner::TypeVar(_) => unreachable!(),
|
||||
SubclassOfInner::TypeVar(_) => ConstraintSet::from(false),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3790,8 +3890,9 @@ impl<'db> Type<'db> {
|
||||
left.is_disjoint_from_impl(db, right, inferable, disjointness_visitor)
|
||||
}
|
||||
|
||||
// for `type[Any]`/`type[Unknown]`/`type[Todo]`, we know the type cannot be any larger than `type`,
|
||||
// so although the type is dynamic we can still determine disjointedness in some situations
|
||||
// For `type[Any]`/`type[Unknown]`/`type[Todo]`, we know the type cannot be any
|
||||
// larger than `type`, so although the type is dynamic we can still determine
|
||||
// disjointedness in some situations.
|
||||
(Type::SubclassOf(subclass_of_ty), other)
|
||||
| (other, Type::SubclassOf(subclass_of_ty)) => match subclass_of_ty.subclass_of() {
|
||||
SubclassOfInner::Dynamic(_) => {
|
||||
@@ -4166,7 +4267,9 @@ impl<'db> Type<'db> {
|
||||
|
||||
return;
|
||||
};
|
||||
let (class_literal, Some(specialization)) = instance.class(db).class_literal(db) else {
|
||||
|
||||
let Some((class_literal, Some(specialization))) = instance.class(db).stmt_class_literal(db)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let generic_context = specialization.generic_context(db);
|
||||
@@ -4250,7 +4353,7 @@ impl<'db> Type<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
// We eagerly transform `SubclassOf` to `ClassLiteral` for final types, so `SubclassOf` is never a singleton.
|
||||
// We eagerly transform `SubclassOf` to `StmtClassLiteral` for final types, so `SubclassOf` is never a singleton.
|
||||
Type::SubclassOf(..) => false,
|
||||
Type::BoundSuper(..) => false,
|
||||
Type::BooleanLiteral(_)
|
||||
@@ -5274,7 +5377,7 @@ impl<'db> Type<'db> {
|
||||
|
||||
Type::EnumLiteral(enum_literal)
|
||||
if matches!(name_str, "name" | "_name_")
|
||||
&& Type::ClassLiteral(enum_literal.enum_class(db))
|
||||
&& Type::ClassLiteral(ClassLiteral::Stmt(enum_literal.enum_class(db)))
|
||||
.is_subtype_of(db, KnownClass::Enum.to_subclass_of(db)) =>
|
||||
{
|
||||
Place::bound(Type::string_literal(db, enum_literal.name(db))).into()
|
||||
@@ -5282,7 +5385,7 @@ impl<'db> Type<'db> {
|
||||
|
||||
Type::EnumLiteral(enum_literal)
|
||||
if matches!(name_str, "value" | "_value_")
|
||||
&& Type::ClassLiteral(enum_literal.enum_class(db))
|
||||
&& Type::ClassLiteral(ClassLiteral::Stmt(enum_literal.enum_class(db)))
|
||||
.is_subtype_of(db, KnownClass::Enum.to_subclass_of(db)) =>
|
||||
{
|
||||
enum_metadata(db, enum_literal.enum_class(db))
|
||||
@@ -5306,9 +5409,14 @@ impl<'db> Type<'db> {
|
||||
|
||||
Type::NominalInstance(instance)
|
||||
if matches!(name_str, "value" | "_value_")
|
||||
&& is_single_member_enum(db, instance.class(db).class_literal(db).0) =>
|
||||
&& instance.class(db).stmt_class_literal(db).is_some_and(
|
||||
|(class_literal, _)| is_single_member_enum(db, class_literal),
|
||||
) =>
|
||||
{
|
||||
enum_metadata(db, instance.class(db).class_literal(db).0)
|
||||
instance
|
||||
.class(db)
|
||||
.stmt_class_literal(db)
|
||||
.and_then(|(class_literal, _)| enum_metadata(db, class_literal))
|
||||
.and_then(|metadata| metadata.members.get_index(0).map(|(_, v)| *v))
|
||||
.map_or(Place::Undefined, Place::bound)
|
||||
.into()
|
||||
@@ -5407,11 +5515,11 @@ impl<'db> Type<'db> {
|
||||
|
||||
Type::ClassLiteral(..) | Type::GenericAlias(..) | Type::SubclassOf(..) => {
|
||||
if let Some(enum_class) = match self {
|
||||
Type::ClassLiteral(literal) => Some(literal),
|
||||
Type::ClassLiteral(literal) => literal.as_stmt(),
|
||||
Type::SubclassOf(subclass_of) => subclass_of
|
||||
.subclass_of()
|
||||
.into_class(db)
|
||||
.map(|class| class.class_literal(db).0),
|
||||
.and_then(|class| class.stmt_class_literal(db).map(|(lit, _)| lit)),
|
||||
_ => None,
|
||||
} {
|
||||
if let Some(metadata) = enum_metadata(db, enum_class) {
|
||||
@@ -6051,6 +6159,98 @@ impl<'db> Type<'db> {
|
||||
.into()
|
||||
}
|
||||
|
||||
// collections.namedtuple(typename, field_names, ...)
|
||||
Some(KnownFunction::NamedTuple) => Binding::single(
|
||||
self,
|
||||
Signature::new(
|
||||
Parameters::new(
|
||||
db,
|
||||
[
|
||||
Parameter::positional_or_keyword(Name::new_static("typename"))
|
||||
.with_annotated_type(KnownClass::Str.to_instance(db)),
|
||||
Parameter::positional_or_keyword(Name::new_static("field_names"))
|
||||
.with_annotated_type(Type::SpecialForm(
|
||||
SpecialFormType::CollectionsNamedTupleFieldsSchema,
|
||||
)),
|
||||
// Additional optional parameters have defaults.
|
||||
Parameter::keyword_only(Name::new_static("rename"))
|
||||
.with_annotated_type(KnownClass::Bool.to_instance(db))
|
||||
.with_default_type(Type::BooleanLiteral(false)),
|
||||
Parameter::keyword_only(Name::new_static("defaults"))
|
||||
.with_annotated_type(Type::SpecialForm(
|
||||
SpecialFormType::CollectionsNamedTupleDefaultsSchema,
|
||||
))
|
||||
.with_default_type(Type::none(db)),
|
||||
Parameter::keyword_only(Name::new_static("module"))
|
||||
.with_default_type(Type::none(db)),
|
||||
],
|
||||
),
|
||||
Some(KnownClass::NamedTupleFallback.to_class_literal(db)),
|
||||
),
|
||||
)
|
||||
.into(),
|
||||
|
||||
// dataclasses.make_dataclass(cls_name, fields, ...)
|
||||
// The `fields` parameter accepts either:
|
||||
// - A list or tuple literal (inferred as MakeDataclassFieldsSchema)
|
||||
// - Any Iterable (fallback for variables containing fields)
|
||||
Some(KnownFunction::MakeDataclass) => Binding::single(
|
||||
self,
|
||||
Signature::new(
|
||||
Parameters::new(
|
||||
db,
|
||||
[
|
||||
Parameter::positional_or_keyword(Name::new_static("cls_name"))
|
||||
.with_annotated_type(KnownClass::Str.to_instance(db)),
|
||||
Parameter::positional_or_keyword(Name::new_static("fields"))
|
||||
.with_annotated_type(Type::SpecialForm(
|
||||
SpecialFormType::MakeDataclassFieldsSchema,
|
||||
)),
|
||||
// Additional optional parameters have defaults.
|
||||
Parameter::keyword_only(Name::new_static("bases"))
|
||||
.with_default_type(Type::empty_tuple(db)),
|
||||
Parameter::keyword_only(Name::new_static("namespace"))
|
||||
.with_default_type(Type::none(db)),
|
||||
Parameter::keyword_only(Name::new_static("init"))
|
||||
.with_annotated_type(KnownClass::Bool.to_instance(db))
|
||||
.with_default_type(Type::BooleanLiteral(true)),
|
||||
Parameter::keyword_only(Name::new_static("repr"))
|
||||
.with_annotated_type(KnownClass::Bool.to_instance(db))
|
||||
.with_default_type(Type::BooleanLiteral(true)),
|
||||
Parameter::keyword_only(Name::new_static("eq"))
|
||||
.with_annotated_type(KnownClass::Bool.to_instance(db))
|
||||
.with_default_type(Type::BooleanLiteral(true)),
|
||||
Parameter::keyword_only(Name::new_static("order"))
|
||||
.with_annotated_type(KnownClass::Bool.to_instance(db))
|
||||
.with_default_type(Type::BooleanLiteral(false)),
|
||||
Parameter::keyword_only(Name::new_static("unsafe_hash"))
|
||||
.with_annotated_type(KnownClass::Bool.to_instance(db))
|
||||
.with_default_type(Type::BooleanLiteral(false)),
|
||||
Parameter::keyword_only(Name::new_static("frozen"))
|
||||
.with_annotated_type(KnownClass::Bool.to_instance(db))
|
||||
.with_default_type(Type::BooleanLiteral(false)),
|
||||
Parameter::keyword_only(Name::new_static("match_args"))
|
||||
.with_annotated_type(KnownClass::Bool.to_instance(db))
|
||||
.with_default_type(Type::BooleanLiteral(true)),
|
||||
Parameter::keyword_only(Name::new_static("kw_only"))
|
||||
.with_annotated_type(KnownClass::Bool.to_instance(db))
|
||||
.with_default_type(Type::BooleanLiteral(false)),
|
||||
Parameter::keyword_only(Name::new_static("slots"))
|
||||
.with_annotated_type(KnownClass::Bool.to_instance(db))
|
||||
.with_default_type(Type::BooleanLiteral(false)),
|
||||
Parameter::keyword_only(Name::new_static("weakref_slot"))
|
||||
.with_annotated_type(KnownClass::Bool.to_instance(db))
|
||||
.with_default_type(Type::BooleanLiteral(false)),
|
||||
Parameter::keyword_only(Name::new_static("module"))
|
||||
.with_default_type(Type::none(db)),
|
||||
],
|
||||
),
|
||||
// Return type is `type` when we can't synthesize a specific dataclass.
|
||||
Some(KnownClass::Type.to_instance(db)),
|
||||
),
|
||||
)
|
||||
.into(),
|
||||
|
||||
_ => CallableBinding::from_overloads(
|
||||
self,
|
||||
function_type.signature(db).overloads.iter().cloned(),
|
||||
@@ -6444,6 +6644,31 @@ impl<'db> Type<'db> {
|
||||
.into()
|
||||
}
|
||||
|
||||
// Functional namedtuple: create a signature with the field types as parameters.
|
||||
None if class.as_functional_namedtuple().is_some() => {
|
||||
let namedtuple = class.as_functional_namedtuple().unwrap();
|
||||
let parameters: Vec<_> = namedtuple
|
||||
.fields(db)
|
||||
.iter()
|
||||
.map(|(name, ty, default_ty)| {
|
||||
let mut param = Parameter::positional_or_keyword(name.clone())
|
||||
.with_annotated_type(*ty);
|
||||
if let Some(default) = default_ty {
|
||||
param = param.with_default_type(*default);
|
||||
}
|
||||
param
|
||||
})
|
||||
.collect();
|
||||
Binding::single(
|
||||
self,
|
||||
Signature::new(
|
||||
Parameters::new(db, parameters),
|
||||
Some(namedtuple.to_instance(db)),
|
||||
),
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
||||
// Most class literal constructor calls are handled by `try_call_constructor` and
|
||||
// not via getting the signature here. This signature can still be used in some
|
||||
// cases (e.g. evaluating callable subtyping). TODO improve this definition
|
||||
@@ -6487,7 +6712,25 @@ impl<'db> Type<'db> {
|
||||
}
|
||||
|
||||
Type::SpecialForm(SpecialFormType::NamedTuple) => {
|
||||
Binding::single(self, Signature::todo("functional `NamedTuple` syntax")).into()
|
||||
// typing.NamedTuple(typename: str, fields: _TypingNamedTupleFieldsSchema)
|
||||
Binding::single(
|
||||
self,
|
||||
Signature::new(
|
||||
Parameters::new(
|
||||
db,
|
||||
[
|
||||
Parameter::positional_or_keyword(Name::new_static("typename"))
|
||||
.with_annotated_type(KnownClass::Str.to_instance(db)),
|
||||
Parameter::positional_or_keyword(Name::new_static("fields"))
|
||||
.with_annotated_type(Type::SpecialForm(
|
||||
SpecialFormType::TypingNamedTupleFieldsSchema,
|
||||
)),
|
||||
],
|
||||
),
|
||||
Some(KnownClass::NamedTupleFallback.to_class_literal(db)),
|
||||
),
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
||||
Type::GenericAlias(_) => {
|
||||
@@ -6500,23 +6743,29 @@ impl<'db> Type<'db> {
|
||||
.into()
|
||||
}
|
||||
|
||||
Type::SubclassOf(subclass_of_type) => match subclass_of_type.subclass_of() {
|
||||
SubclassOfInner::Dynamic(dynamic_type) => Type::Dynamic(dynamic_type).bindings(db),
|
||||
Type::SubclassOf(subclass_of_type) => {
|
||||
let inner = subclass_of_type.subclass_of();
|
||||
if let SubclassOfInner::Dynamic(dynamic_type) = inner {
|
||||
return Type::Dynamic(dynamic_type).bindings(db);
|
||||
}
|
||||
|
||||
// Most type[] constructor calls are handled by `try_call_constructor` and not via
|
||||
// getting the signature here. This signature can still be used in some cases (e.g.
|
||||
// evaluating callable subtyping). TODO improve this definition (intersection of
|
||||
// `__new__` and `__init__` signatures? and respect metaclass `__call__`).
|
||||
SubclassOfInner::Class(class) => Type::from(class).bindings(db),
|
||||
|
||||
// TODO annotated return type on `__new__` or metaclass `__call__`
|
||||
// TODO check call vs signatures of `__new__` and/or `__init__`
|
||||
SubclassOfInner::TypeVar(_) => Binding::single(
|
||||
self,
|
||||
Signature::new(Parameters::gradual_form(), self.to_instance(db)),
|
||||
)
|
||||
.into(),
|
||||
},
|
||||
// evaluating callable subtyping). TODO: improve this definition (intersection of
|
||||
// `__new__` and `__init__` signatures, and respect metaclass `__call__`).
|
||||
match inner {
|
||||
SubclassOfInner::Class(class) => Type::from(class).bindings(db),
|
||||
// TODO: annotated return type on `__new__` or metaclass `__call__`.
|
||||
// TODO: check call vs signatures of `__new__` and/or `__init__`.
|
||||
SubclassOfInner::TypeVar(_) => Binding::single(
|
||||
self,
|
||||
Signature::new(Parameters::gradual_form(), self.to_instance(db)),
|
||||
)
|
||||
.into(),
|
||||
// Dynamic handled by early return above.
|
||||
SubclassOfInner::Dynamic(_) => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
Type::NominalInstance(_) | Type::ProtocolInstance(_) | Type::NewTypeInstance(_) => {
|
||||
// Note that for objects that have a (possibly not callable!) `__call__` attribute,
|
||||
@@ -6850,7 +7099,8 @@ impl<'db> Type<'db> {
|
||||
| Type::BoundSuper(_)
|
||||
| Type::TypeIs(_)
|
||||
| Type::TypeGuard(_)
|
||||
| Type::TypedDict(_) => None
|
||||
| Type::TypedDict(_)
|
||||
=> None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7135,7 +7385,9 @@ impl<'db> Type<'db> {
|
||||
let from_class_base = |base: ClassBase<'db>| {
|
||||
let class = base.into_class()?;
|
||||
if class.is_known(db, KnownClass::Generator) {
|
||||
if let Some(specialization) = class.class_literal_specialized(db, None).1 {
|
||||
if let Some((_, Some(specialization))) =
|
||||
class.stmt_class_literal_specialized(db, None)
|
||||
{
|
||||
if let [_, _, return_ty] = specialization.types(db) {
|
||||
return Some(*return_ty);
|
||||
}
|
||||
@@ -7498,8 +7750,14 @@ impl<'db> Type<'db> {
|
||||
}
|
||||
Type::GenericAlias(alias) => Ok(Type::instance(db, ClassType::from(*alias))),
|
||||
|
||||
Type::SubclassOf(_)
|
||||
| Type::BooleanLiteral(_)
|
||||
Type::SubclassOf(_) => Err(InvalidTypeExpressionError {
|
||||
invalid_expressions: smallvec::smallvec_inline![
|
||||
InvalidTypeExpression::InvalidType(*self, scope_id)
|
||||
],
|
||||
fallback_type: Type::unknown(),
|
||||
}),
|
||||
|
||||
Type::BooleanLiteral(_)
|
||||
| Type::BytesLiteral(_)
|
||||
| Type::EnumLiteral(_)
|
||||
| Type::AlwaysTruthy
|
||||
@@ -7597,6 +7855,11 @@ impl<'db> Type<'db> {
|
||||
}
|
||||
KnownInstanceType::Callable(callable) => Ok(Type::Callable(*callable)),
|
||||
KnownInstanceType::LiteralStringAlias(ty) => Ok(ty.inner(db)),
|
||||
// Internal types, should never appear in user code.
|
||||
KnownInstanceType::TypingNamedTupleFieldsSchema(_)
|
||||
| KnownInstanceType::CollectionsNamedTupleFieldsSchema(_)
|
||||
| KnownInstanceType::CollectionsNamedTupleDefaultsSchema(_)
|
||||
| KnownInstanceType::MakeDataclassFieldsSchema(_) => Ok(Type::unknown()),
|
||||
},
|
||||
|
||||
Type::SpecialForm(special_form) => match special_form {
|
||||
@@ -7730,6 +7993,12 @@ impl<'db> Type<'db> {
|
||||
],
|
||||
fallback_type: Type::unknown(),
|
||||
}),
|
||||
|
||||
// Internal types, should never appear in user code.
|
||||
SpecialFormType::TypingNamedTupleFieldsSchema
|
||||
| SpecialFormType::CollectionsNamedTupleFieldsSchema
|
||||
| SpecialFormType::CollectionsNamedTupleDefaultsSchema
|
||||
| SpecialFormType::MakeDataclassFieldsSchema => Ok(Type::unknown()),
|
||||
},
|
||||
|
||||
Type::Union(union) => {
|
||||
@@ -7816,7 +8085,9 @@ impl<'db> Type<'db> {
|
||||
}
|
||||
Type::BytesLiteral(_) => KnownClass::Bytes.to_class_literal(db),
|
||||
Type::IntLiteral(_) => KnownClass::Int.to_class_literal(db),
|
||||
Type::EnumLiteral(enum_literal) => Type::ClassLiteral(enum_literal.enum_class(db)),
|
||||
Type::EnumLiteral(enum_literal) => {
|
||||
Type::ClassLiteral(ClassLiteral::Stmt(enum_literal.enum_class(db)))
|
||||
}
|
||||
Type::FunctionLiteral(_) => KnownClass::FunctionType.to_class_literal(db),
|
||||
Type::BoundMethod(_) => KnownClass::MethodType.to_class_literal(db),
|
||||
Type::KnownBoundMethod(method) => method.class().to_class_literal(db),
|
||||
@@ -8004,7 +8275,11 @@ impl<'db> Type<'db> {
|
||||
KnownInstanceType::Specialization(_) |
|
||||
KnownInstanceType::Literal(_) |
|
||||
KnownInstanceType::LiteralStringAlias(_) |
|
||||
KnownInstanceType::NewType(_) => {
|
||||
KnownInstanceType::NewType(_) |
|
||||
KnownInstanceType::TypingNamedTupleFieldsSchema(_) |
|
||||
KnownInstanceType::CollectionsNamedTupleFieldsSchema(_) |
|
||||
KnownInstanceType::CollectionsNamedTupleDefaultsSchema(_) |
|
||||
KnownInstanceType::MakeDataclassFieldsSchema(_) => {
|
||||
// TODO: For some of these, we may need to apply the type mapping to inner types.
|
||||
self
|
||||
},
|
||||
@@ -8400,7 +8675,11 @@ impl<'db> Type<'db> {
|
||||
| KnownInstanceType::Specialization(_)
|
||||
| KnownInstanceType::Literal(_)
|
||||
| KnownInstanceType::LiteralStringAlias(_)
|
||||
| KnownInstanceType::NewType(_) => {
|
||||
| KnownInstanceType::NewType(_)
|
||||
| KnownInstanceType::TypingNamedTupleFieldsSchema(_)
|
||||
| KnownInstanceType::CollectionsNamedTupleFieldsSchema(_)
|
||||
| KnownInstanceType::CollectionsNamedTupleDefaultsSchema(_)
|
||||
| KnownInstanceType::MakeDataclassFieldsSchema(_) => {
|
||||
// TODO: For some of these, we may need to try to find legacy typevars in inner types.
|
||||
}
|
||||
},
|
||||
@@ -8551,12 +8830,13 @@ impl<'db> Type<'db> {
|
||||
}
|
||||
Self::ModuleLiteral(module) => Some(TypeDefinition::Module(module.module(db))),
|
||||
Self::ClassLiteral(class_literal) => {
|
||||
Some(TypeDefinition::Class(class_literal.definition(db)))
|
||||
class_literal.definition(db).map(TypeDefinition::Class)
|
||||
}
|
||||
Self::GenericAlias(alias) => Some(TypeDefinition::Class(alias.definition(db))),
|
||||
Self::NominalInstance(instance) => {
|
||||
Some(TypeDefinition::Class(instance.class(db).definition(db)))
|
||||
}
|
||||
Self::NominalInstance(instance) => instance
|
||||
.class(db)
|
||||
.definition(db)
|
||||
.map(TypeDefinition::Class),
|
||||
Self::KnownInstance(instance) => match instance {
|
||||
KnownInstanceType::TypeVar(var) => {
|
||||
Some(TypeDefinition::TypeVar(var.definition(db)?))
|
||||
@@ -8569,9 +8849,11 @@ impl<'db> Type<'db> {
|
||||
},
|
||||
|
||||
Self::SubclassOf(subclass_of_type) => match subclass_of_type.subclass_of() {
|
||||
SubclassOfInner::Class(class) => Some(TypeDefinition::Class(class.definition(db))),
|
||||
SubclassOfInner::Dynamic(_) => None,
|
||||
SubclassOfInner::TypeVar(bound_typevar) => Some(TypeDefinition::TypeVar(bound_typevar.typevar(db).definition(db)?)),
|
||||
SubclassOfInner::Class(class) => class.definition(db).map(TypeDefinition::Class),
|
||||
SubclassOfInner::TypeVar(bound_typevar) => {
|
||||
Some(TypeDefinition::TypeVar(bound_typevar.typevar(db).definition(db)?))
|
||||
}
|
||||
},
|
||||
|
||||
Self::TypeAlias(alias) => alias.value_type(db).definition(db),
|
||||
@@ -8598,7 +8880,7 @@ impl<'db> Type<'db> {
|
||||
Self::TypeVar(bound_typevar) => Some(TypeDefinition::TypeVar(bound_typevar.typevar(db).definition(db)?)),
|
||||
|
||||
Self::ProtocolInstance(protocol) => match protocol.inner {
|
||||
Protocol::FromClass(class) => Some(TypeDefinition::Class(class.definition(db))),
|
||||
Protocol::FromClass(class) => class.definition(db).map(TypeDefinition::Class),
|
||||
Protocol::Synthesized(_) => None,
|
||||
},
|
||||
|
||||
@@ -8685,7 +8967,7 @@ impl<'db> Type<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn generic_origin(self, db: &'db dyn Db) -> Option<ClassLiteral<'db>> {
|
||||
pub(crate) fn generic_origin(self, db: &'db dyn Db) -> Option<StmtClassLiteral<'db>> {
|
||||
match self {
|
||||
Type::GenericAlias(generic) => Some(generic.origin(db)),
|
||||
Type::NominalInstance(instance) => {
|
||||
@@ -9067,6 +9349,20 @@ pub enum KnownInstanceType<'db> {
|
||||
/// An identity callable created with `typing.NewType(name, base)`, which behaves like a
|
||||
/// subtype of `base` in type expressions. See the `struct NewType` payload for an example.
|
||||
NewType(NewType<'db>),
|
||||
|
||||
/// Schema for `typing.NamedTuple` fields, extracted from a list or tuple literal.
|
||||
TypingNamedTupleFieldsSchema(TypingNamedTupleFieldsSchema<'db>),
|
||||
|
||||
/// Schema for `collections.namedtuple` field names, extracted from a list or tuple literal.
|
||||
CollectionsNamedTupleFieldsSchema(CollectionsNamedTupleFieldsSchema<'db>),
|
||||
|
||||
/// Schema for `collections.namedtuple` defaults count, extracted from a list or tuple literal.
|
||||
CollectionsNamedTupleDefaultsSchema(CollectionsNamedTupleDefaultsSchema<'db>),
|
||||
|
||||
/// Schema for `dataclasses.make_dataclass` fields.
|
||||
/// When a list or tuple literal is passed as the `fields` argument to `make_dataclass()`,
|
||||
/// the literal is inferred as this schema type.
|
||||
MakeDataclassFieldsSchema(MakeDataclassFieldsSchema<'db>),
|
||||
}
|
||||
|
||||
fn walk_known_instance_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
|
||||
@@ -9113,6 +9409,23 @@ fn walk_known_instance_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
|
||||
KnownInstanceType::NewType(newtype) => {
|
||||
visitor.visit_type(db, newtype.concrete_base_type(db));
|
||||
}
|
||||
KnownInstanceType::TypingNamedTupleFieldsSchema(schema) => {
|
||||
for (_, field_ty) in schema.fields(db) {
|
||||
visitor.visit_type(db, *field_ty);
|
||||
}
|
||||
}
|
||||
// CollectionsNamedTupleFieldsSchema only contains field names, no types to visit.
|
||||
KnownInstanceType::CollectionsNamedTupleFieldsSchema(_) => {}
|
||||
// CollectionsNamedTupleDefaultsSchema only contains a count, no types to visit.
|
||||
KnownInstanceType::CollectionsNamedTupleDefaultsSchema(_) => {}
|
||||
KnownInstanceType::MakeDataclassFieldsSchema(schema) => {
|
||||
for (_, field_ty, default_ty) in schema.fields(db) {
|
||||
visitor.visit_type(db, *field_ty);
|
||||
if let Some(default) = default_ty {
|
||||
visitor.visit_type(db, *default);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9160,6 +9473,18 @@ impl<'db> KnownInstanceType<'db> {
|
||||
// Nothing to normalize
|
||||
self
|
||||
}
|
||||
Self::TypingNamedTupleFieldsSchema(schema) => {
|
||||
Self::TypingNamedTupleFieldsSchema(schema.normalized_impl(db, visitor))
|
||||
}
|
||||
Self::CollectionsNamedTupleFieldsSchema(schema) => {
|
||||
Self::CollectionsNamedTupleFieldsSchema(schema.normalized_impl(db, visitor))
|
||||
}
|
||||
Self::CollectionsNamedTupleDefaultsSchema(schema) => {
|
||||
Self::CollectionsNamedTupleDefaultsSchema(schema.normalized_impl(db, visitor))
|
||||
}
|
||||
Self::MakeDataclassFieldsSchema(schema) => {
|
||||
Self::MakeDataclassFieldsSchema(schema.normalized_impl(db, visitor))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9209,6 +9534,52 @@ impl<'db> KnownInstanceType<'db> {
|
||||
Self::Specialization(specialization) => specialization
|
||||
.recursive_type_normalized_impl(db, div, true)
|
||||
.map(Self::Specialization),
|
||||
Self::TypingNamedTupleFieldsSchema(schema) => {
|
||||
// Normalize the field types.
|
||||
let normalized_fields: Option<Box<[_]>> = schema
|
||||
.fields(db)
|
||||
.iter()
|
||||
.map(|(name, ty)| {
|
||||
ty.recursive_type_normalized_impl(db, div, true)
|
||||
.map(|normalized_ty| (name.clone(), normalized_ty))
|
||||
})
|
||||
.collect();
|
||||
normalized_fields.map(|fields| {
|
||||
Self::TypingNamedTupleFieldsSchema(TypingNamedTupleFieldsSchema::new(
|
||||
db, fields,
|
||||
))
|
||||
})
|
||||
}
|
||||
// CollectionsNamedTupleFieldsSchema only contains field names, no types to normalize.
|
||||
Self::CollectionsNamedTupleFieldsSchema(schema) => {
|
||||
Some(Self::CollectionsNamedTupleFieldsSchema(schema))
|
||||
}
|
||||
// CollectionsNamedTupleDefaultsSchema only contains a count, no types to normalize.
|
||||
Self::CollectionsNamedTupleDefaultsSchema(schema) => {
|
||||
Some(Self::CollectionsNamedTupleDefaultsSchema(schema))
|
||||
}
|
||||
Self::MakeDataclassFieldsSchema(schema) => {
|
||||
// Normalize the field types.
|
||||
let normalized_fields: Option<Box<[_]>> = schema
|
||||
.fields(db)
|
||||
.iter()
|
||||
.map(|(name, ty, default)| {
|
||||
ty.recursive_type_normalized_impl(db, div, true)
|
||||
.and_then(|normalized_ty| {
|
||||
let normalized_default = match default {
|
||||
Some(d) => {
|
||||
Some(d.recursive_type_normalized_impl(db, div, true)?)
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
Some((name.clone(), normalized_ty, normalized_default))
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
normalized_fields.map(|fields| {
|
||||
Self::MakeDataclassFieldsSchema(MakeDataclassFieldsSchema::new(db, fields))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9235,6 +9606,11 @@ impl<'db> KnownInstanceType<'db> {
|
||||
| Self::Callable(_) => KnownClass::GenericAlias,
|
||||
Self::LiteralStringAlias(_) => KnownClass::Str,
|
||||
Self::NewType(_) => KnownClass::NewType,
|
||||
// Internal types, no corresponding known class.
|
||||
Self::TypingNamedTupleFieldsSchema(_)
|
||||
| Self::CollectionsNamedTupleFieldsSchema(_)
|
||||
| Self::CollectionsNamedTupleDefaultsSchema(_)
|
||||
| Self::MakeDataclassFieldsSchema(_) => KnownClass::Object,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9738,6 +10114,97 @@ impl<'db> FieldInstance<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Schema for `typing.NamedTuple` fields, extracted from a list or tuple literal.
|
||||
#[salsa::interned(debug, heap_size = ruff_memory_usage::heap_size)]
|
||||
#[derive(PartialOrd, Ord)]
|
||||
pub struct TypingNamedTupleFieldsSchema<'db> {
|
||||
#[returns(ref)]
|
||||
pub fields: Box<[(Name, Type<'db>)]>,
|
||||
}
|
||||
|
||||
// The Salsa heap is tracked separately.
|
||||
impl get_size2::GetSize for TypingNamedTupleFieldsSchema<'_> {}
|
||||
|
||||
impl<'db> TypingNamedTupleFieldsSchema<'db> {
|
||||
pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &NormalizedVisitor<'db>) -> Self {
|
||||
TypingNamedTupleFieldsSchema::new(
|
||||
db,
|
||||
self.fields(db)
|
||||
.iter()
|
||||
.map(|(name, ty)| (name.clone(), ty.normalized_impl(db, visitor)))
|
||||
.collect::<Box<[_]>>(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Schema for `collections.namedtuple` field names, extracted from a list or tuple literal.
|
||||
#[salsa::interned(debug, heap_size = ruff_memory_usage::heap_size)]
|
||||
#[derive(PartialOrd, Ord)]
|
||||
pub struct CollectionsNamedTupleFieldsSchema<'db> {
|
||||
#[returns(ref)]
|
||||
pub field_names: Box<[Name]>,
|
||||
}
|
||||
|
||||
// The Salsa heap is tracked separately.
|
||||
impl get_size2::GetSize for CollectionsNamedTupleFieldsSchema<'_> {}
|
||||
|
||||
impl CollectionsNamedTupleFieldsSchema<'_> {
|
||||
pub(crate) fn normalized_impl(self, _db: &dyn Db, _visitor: &NormalizedVisitor<'_>) -> Self {
|
||||
// Field names don't contain types, so no normalization needed.
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Schema for `collections.namedtuple` defaults count, extracted from a list or tuple literal.
|
||||
#[salsa::interned(debug, heap_size = ruff_memory_usage::heap_size)]
|
||||
#[derive(PartialOrd, Ord)]
|
||||
pub struct CollectionsNamedTupleDefaultsSchema<'db> {
|
||||
pub count: usize,
|
||||
}
|
||||
|
||||
// The Salsa heap is tracked separately.
|
||||
impl get_size2::GetSize for CollectionsNamedTupleDefaultsSchema<'_> {}
|
||||
|
||||
impl CollectionsNamedTupleDefaultsSchema<'_> {
|
||||
pub(crate) fn normalized_impl(self, _db: &dyn Db, _visitor: &NormalizedVisitor<'_>) -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Internal schema for the `fields` argument to `dataclasses.make_dataclass()`.
|
||||
///
|
||||
/// When a list or tuple literal is passed as the `fields` argument to `make_dataclass()`,
|
||||
/// the literal is inferred as this schema type instead of a regular list or tuple type.
|
||||
#[salsa::interned(debug, heap_size = ruff_memory_usage::heap_size)]
|
||||
#[derive(PartialOrd, Ord)]
|
||||
pub struct MakeDataclassFieldsSchema<'db> {
|
||||
/// The fields as (name, type, default) tuples extracted from the literal.
|
||||
/// The default is `None` if no default was provided.
|
||||
#[returns(ref)]
|
||||
pub fields: Box<[(Name, Type<'db>, Option<Type<'db>>)]>,
|
||||
}
|
||||
|
||||
// The Salsa heap is tracked separately.
|
||||
impl get_size2::GetSize for MakeDataclassFieldsSchema<'_> {}
|
||||
|
||||
impl<'db> MakeDataclassFieldsSchema<'db> {
|
||||
pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &NormalizedVisitor<'db>) -> Self {
|
||||
MakeDataclassFieldsSchema::new(
|
||||
db,
|
||||
self.fields(db)
|
||||
.iter()
|
||||
.map(|(name, ty, default)| {
|
||||
(
|
||||
name.clone(),
|
||||
ty.normalized_impl(db, visitor),
|
||||
default.map(|d| d.normalized_impl(db, visitor)),
|
||||
)
|
||||
})
|
||||
.collect::<Box<[_]>>(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether this typevar was created via the legacy `TypeVar` constructor, using PEP 695 syntax,
|
||||
/// or an implicit typevar like `Self` was used.
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, get_size2::GetSize)]
|
||||
@@ -11046,7 +11513,12 @@ impl<'db> UnionTypeInstance<'db> {
|
||||
) -> Result<impl Iterator<Item = Type<'db>> + 'db, InvalidTypeExpressionError<'db>> {
|
||||
let to_class_literal = |ty: Type<'db>| {
|
||||
ty.as_nominal_instance()
|
||||
.map(|instance| Type::ClassLiteral(instance.class(db).class_literal(db).0))
|
||||
.and_then(|instance| {
|
||||
instance
|
||||
.class(db)
|
||||
.stmt_class_literal(db)
|
||||
.map(|(lit, _)| Type::ClassLiteral(lit.into()))
|
||||
})
|
||||
.unwrap_or_else(Type::unknown)
|
||||
};
|
||||
|
||||
@@ -13961,7 +14433,7 @@ impl<'db> TypeAliasType<'db> {
|
||||
#[derive(Debug, Clone, PartialEq, Eq, salsa::Update, get_size2::GetSize)]
|
||||
pub(super) struct MetaclassCandidate<'db> {
|
||||
metaclass: ClassType<'db>,
|
||||
explicit_metaclass_of: ClassLiteral<'db>,
|
||||
explicit_metaclass_of: StmtClassLiteral<'db>,
|
||||
}
|
||||
|
||||
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
|
||||
@@ -14719,7 +15191,7 @@ impl<'db> BytesLiteralType<'db> {
|
||||
#[derive(PartialOrd, Ord)]
|
||||
pub struct EnumLiteralType<'db> {
|
||||
/// A reference to the enum class this literal belongs to
|
||||
enum_class: ClassLiteral<'db>,
|
||||
enum_class: StmtClassLiteral<'db>,
|
||||
/// The name of the enum member
|
||||
#[returns(ref)]
|
||||
name: Name,
|
||||
@@ -14947,7 +15419,7 @@ impl<'db> TypeGuardLike<'db> for TypeGuardType<'db> {
|
||||
/// being added to the given class.
|
||||
pub(super) fn determine_upper_bound<'db>(
|
||||
db: &'db dyn Db,
|
||||
class_literal: ClassLiteral<'db>,
|
||||
class_literal: StmtClassLiteral<'db>,
|
||||
specialization: Option<Specialization<'db>>,
|
||||
is_known_base: impl Fn(ClassBase<'db>) -> bool,
|
||||
) -> Type<'db> {
|
||||
|
||||
@@ -465,7 +465,8 @@ impl<'db> BoundSuperType<'db> {
|
||||
Type::ClassLiteral(class) => ClassBase::Class(ClassType::NonGeneric(class)),
|
||||
Type::SubclassOf(subclass_of) => match subclass_of.subclass_of() {
|
||||
SubclassOfInner::Dynamic(dynamic) => ClassBase::Dynamic(dynamic),
|
||||
_ => match subclass_of.subclass_of().into_class(db) {
|
||||
SubclassOfInner::Class(class) => ClassBase::Class(class),
|
||||
SubclassOfInner::TypeVar(_) => match subclass_of.subclass_of().into_class(db) {
|
||||
Some(class) => ClassBase::Class(class),
|
||||
None => {
|
||||
return Err(BoundSuperError::InvalidPivotClassType {
|
||||
@@ -485,21 +486,36 @@ impl<'db> BoundSuperType<'db> {
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(pivot_class) = pivot_class.into_class()
|
||||
&& let Some(owner_class) = owner.into_class(db)
|
||||
{
|
||||
let pivot_class = pivot_class.class_literal(db).0;
|
||||
if !owner_class.iter_mro(db).any(|superclass| match superclass {
|
||||
ClassBase::Dynamic(_) => true,
|
||||
ClassBase::Generic | ClassBase::Protocol | ClassBase::TypedDict => false,
|
||||
ClassBase::Class(superclass) => superclass.class_literal(db).0 == pivot_class,
|
||||
}) {
|
||||
return Err(BoundSuperError::FailingConditionCheck {
|
||||
pivot_class: pivot_class_type,
|
||||
owner: owner_type,
|
||||
typevar_context: None,
|
||||
});
|
||||
// Check that the owner's MRO contains the pivot class.
|
||||
let pivot_in_owner_mro = match pivot_class {
|
||||
ClassBase::Class(pivot_class_type) => {
|
||||
let pivot_literal = pivot_class_type.class_literal(db);
|
||||
match owner.into_class(db) {
|
||||
Some(owner_class) => {
|
||||
owner_class.iter_mro(db).any(|superclass| match superclass {
|
||||
ClassBase::Dynamic(_) => true,
|
||||
ClassBase::Generic | ClassBase::Protocol | ClassBase::TypedDict => {
|
||||
false
|
||||
}
|
||||
ClassBase::Class(superclass) => {
|
||||
superclass.class_literal(db) == pivot_literal
|
||||
}
|
||||
})
|
||||
}
|
||||
// Owner doesn't have a class - skip the check.
|
||||
None => true,
|
||||
}
|
||||
}
|
||||
ClassBase::Dynamic(_) => true,
|
||||
ClassBase::Generic | ClassBase::Protocol | ClassBase::TypedDict => true,
|
||||
};
|
||||
|
||||
if !pivot_in_owner_mro {
|
||||
return Err(BoundSuperError::FailingConditionCheck {
|
||||
pivot_class: pivot_class_type,
|
||||
owner: owner_type,
|
||||
typevar_context: None,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(Type::BoundSuper(BoundSuperType::new(
|
||||
@@ -518,16 +534,22 @@ impl<'db> BoundSuperType<'db> {
|
||||
db: &'db dyn Db,
|
||||
mro_iter: impl Iterator<Item = ClassBase<'db>>,
|
||||
) -> impl Iterator<Item = ClassBase<'db>> {
|
||||
let Some(pivot_class) = self.pivot_class(db).into_class() else {
|
||||
let pivot = self.pivot_class(db);
|
||||
|
||||
let Some(pivot_class) = pivot.into_class() else {
|
||||
return Either::Left(ClassBase::Dynamic(DynamicType::Unknown).mro(db, None));
|
||||
};
|
||||
|
||||
let pivot_literal = pivot_class.class_literal(db);
|
||||
let mut pivot_found = false;
|
||||
|
||||
Either::Right(mro_iter.skip_while(move |superclass| {
|
||||
if pivot_found {
|
||||
false
|
||||
} else if Some(pivot_class) == superclass.into_class() {
|
||||
} else if superclass
|
||||
.into_class()
|
||||
.is_some_and(|c| c.class_literal(db) == pivot_literal)
|
||||
{
|
||||
pivot_found = true;
|
||||
true
|
||||
} else {
|
||||
@@ -594,7 +616,8 @@ impl<'db> BoundSuperType<'db> {
|
||||
SuperOwnerKind::Instance(instance) => instance.class(db),
|
||||
};
|
||||
|
||||
let (class_literal, _) = class.class_literal(db);
|
||||
let class_literal = class.class_literal(db);
|
||||
|
||||
// TODO properly support super() with generic types
|
||||
// * requires a fix for https://github.com/astral-sh/ruff/issues/17432
|
||||
// * also requires understanding how we should handle cases like this:
|
||||
|
||||
@@ -751,7 +751,9 @@ impl<'db> IntersectionBuilder<'db> {
|
||||
self
|
||||
}
|
||||
Type::NominalInstance(instance)
|
||||
if enum_metadata(self.db, instance.class_literal(self.db)).is_some() =>
|
||||
if instance
|
||||
.class_literal(self.db)
|
||||
.is_some_and(|class| enum_metadata(self.db, class).is_some()) =>
|
||||
{
|
||||
let mut contains_enum_literal_as_negative_element = false;
|
||||
for intersection in &self.intersections {
|
||||
@@ -773,10 +775,13 @@ impl<'db> IntersectionBuilder<'db> {
|
||||
// `UnionBuilder` because we would simplify the union to just the enum instance
|
||||
// and end up in this branch again.
|
||||
let db = self.db;
|
||||
let class_literal = instance
|
||||
.class_literal(db)
|
||||
.expect("Already checked that class_literal is Some");
|
||||
self.add_positive_impl(
|
||||
Type::Union(UnionType::new(
|
||||
db,
|
||||
enum_member_literals(db, instance.class_literal(db), None)
|
||||
enum_member_literals(db, class_literal, None)
|
||||
.expect("Calling `enum_member_literals` on an enum class")
|
||||
.collect::<Box<[_]>>(),
|
||||
RecursivelyDefined::No,
|
||||
|
||||
@@ -352,7 +352,9 @@ pub(crate) fn is_expandable_type<'db>(db: &'db dyn Db, ty: Type<'db>) -> bool {
|
||||
.any(|element| is_expandable_type(db, element)),
|
||||
Tuple::Variable(_) => false,
|
||||
})
|
||||
|| enum_metadata(db, class.class_literal(db).0).is_some()
|
||||
|| class
|
||||
.stmt_class_literal(db)
|
||||
.is_some_and(|(lit, _)| enum_metadata(db, lit).is_some())
|
||||
}
|
||||
Type::Union(_) => true,
|
||||
_ => false,
|
||||
@@ -403,8 +405,10 @@ fn expand_type<'db>(db: &'db dyn Db, ty: Type<'db>) -> Option<Vec<Type<'db>>> {
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(enum_members) = enum_member_literals(db, class.class_literal(db).0, None) {
|
||||
return Some(enum_members.collect());
|
||||
if let Some((class_literal, _)) = class.stmt_class_literal(db) {
|
||||
if let Some(enum_members) = enum_member_literals(db, class_literal, None) {
|
||||
return Some(enum_members.collect());
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
|
||||
@@ -17,6 +17,8 @@ use std::fmt;
|
||||
use itertools::Itertools;
|
||||
use ruff_db::parsed::parsed_module;
|
||||
use ruff_python_ast::name::Name;
|
||||
use ruff_python_stdlib::identifiers::is_identifier;
|
||||
use ruff_python_stdlib::keyword::is_keyword;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use smallvec::{SmallVec, smallvec, smallvec_inline};
|
||||
|
||||
@@ -25,6 +27,7 @@ use crate::db::Db;
|
||||
use crate::dunder_all::dunder_all_names;
|
||||
use crate::place::{Definedness, Place, known_module_symbol};
|
||||
use crate::types::call::arguments::{Expansion, is_expandable_type};
|
||||
use crate::types::class_base::ClassBase;
|
||||
use crate::types::constraints::ConstraintSet;
|
||||
use crate::types::diagnostic::{
|
||||
CALL_NON_CALLABLE, CALL_TOP_CALLABLE, CONFLICTING_ARGUMENT_FORMS, INVALID_ARGUMENT_TYPE,
|
||||
@@ -40,14 +43,16 @@ use crate::types::generics::{
|
||||
InferableTypeVars, Specialization, SpecializationBuilder, SpecializationError,
|
||||
};
|
||||
use crate::types::signatures::{Parameter, ParameterForm, ParameterKind, Parameters};
|
||||
use crate::types::subclass_of::SubclassOfInner;
|
||||
use crate::types::tuple::{TupleLength, TupleSpec, TupleType};
|
||||
use crate::types::{
|
||||
BoundMethodType, BoundTypeVarIdentity, BoundTypeVarInstance, CallableSignature, CallableType,
|
||||
CallableTypeKind, ClassLiteral, DATACLASS_FLAGS, DataclassFlags, DataclassParams,
|
||||
FieldInstance, KnownBoundMethodType, KnownClass, KnownInstanceType, MemberLookupPolicy,
|
||||
NominalInstanceType, PropertyInstanceType, SpecialFormType, TrackedConstraintSet,
|
||||
TypeAliasType, TypeContext, TypeVarVariance, UnionBuilder, UnionType, WrapperDescriptorKind,
|
||||
enums, list_members, todo_type,
|
||||
CallableTypeKind, ClassLiteral, ClassType, DATACLASS_FLAGS, DataclassFlags, DataclassParams,
|
||||
FieldInstance, FunctionalClassLiteral, FunctionalDataclassLiteral, FunctionalNamedTupleLiteral,
|
||||
KnownBoundMethodType, KnownClass, KnownInstanceType, MemberLookupPolicy, NominalInstanceType,
|
||||
PropertyInstanceType, SpecialFormType, StmtClassLiteral, TrackedConstraintSet, TypeAliasType,
|
||||
TypeContext, TypeVarVariance, UnionBuilder, UnionType, WrapperDescriptorKind, enums,
|
||||
list_members, todo_type,
|
||||
};
|
||||
use crate::unpack::EvaluationMode;
|
||||
use crate::{DisplaySettings, Program};
|
||||
@@ -337,6 +342,7 @@ impl<'db> Bindings<'db> {
|
||||
|
||||
/// Evaluates the return type of certain known callables, where we have special-case logic to
|
||||
/// determine the return type in a way that isn't directly expressible in the type system.
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn evaluate_known_cases(
|
||||
&mut self,
|
||||
db: &'db dyn Db,
|
||||
@@ -904,7 +910,10 @@ impl<'db> Bindings<'db> {
|
||||
if let [Some(ty)] = overload.parameter_types() {
|
||||
let return_ty = match ty {
|
||||
Type::ClassLiteral(class) => {
|
||||
if let Some(metadata) = enums::enum_metadata(db, *class) {
|
||||
if let Some(metadata) = class
|
||||
.as_stmt()
|
||||
.and_then(|stmt| enums::enum_metadata(db, stmt))
|
||||
{
|
||||
Type::heterogeneous_tuple(
|
||||
db,
|
||||
metadata
|
||||
@@ -1109,11 +1118,11 @@ impl<'db> Bindings<'db> {
|
||||
}
|
||||
|
||||
// `dataclass` being used as a non-decorator
|
||||
if let [Some(Type::ClassLiteral(class_literal))] =
|
||||
if let [Some(Type::ClassLiteral(ClassLiteral::Stmt(class_literal)))] =
|
||||
overload.parameter_types()
|
||||
{
|
||||
let params = DataclassParams::default_params(db);
|
||||
overload.set_return_type(Type::from(ClassLiteral::new(
|
||||
overload.set_return_type(Type::from(StmtClassLiteral::new(
|
||||
db,
|
||||
class_literal.name(db),
|
||||
class_literal.body_scope(db),
|
||||
@@ -1169,9 +1178,271 @@ impl<'db> Bindings<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
// collections.namedtuple
|
||||
Some(KnownFunction::NamedTuple) => {
|
||||
overload
|
||||
.set_return_type(todo_type!("Support for functional `namedtuple`"));
|
||||
if let [Some(name_type), Some(field_names_type), ..] =
|
||||
overload.parameter_types()
|
||||
{
|
||||
let name = name_type
|
||||
.as_string_literal()
|
||||
.map(|s| Name::new(s.value(db)));
|
||||
|
||||
// Check if field_names_type is a CollectionsNamedTupleFieldsSchema.
|
||||
let field_names: Option<Box<[Name]>> = if let Type::KnownInstance(
|
||||
KnownInstanceType::CollectionsNamedTupleFieldsSchema(schema),
|
||||
) = field_names_type
|
||||
{
|
||||
Some(schema.field_names(db).clone())
|
||||
} else if let Some(string_literal) =
|
||||
field_names_type.as_string_literal()
|
||||
{
|
||||
// Handle space/comma-separated string.
|
||||
// Python's namedtuple replaces commas with spaces first,
|
||||
// then splits on whitespace.
|
||||
let field_str = string_literal.value(db);
|
||||
Some(
|
||||
field_str
|
||||
.replace(',', " ")
|
||||
.split_whitespace()
|
||||
.map(Name::new)
|
||||
.collect(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Check if `rename=True`.
|
||||
let rename = matches!(
|
||||
overload.parameter_type_by_name("rename", false),
|
||||
Ok(Some(Type::BooleanLiteral(true)))
|
||||
);
|
||||
|
||||
// Extract defaults count from the defaults parameter.
|
||||
// If it's a CollectionsNamedTupleDefaultsSchema, we can get the count directly.
|
||||
let num_defaults: usize = overload
|
||||
.parameter_type_by_name("defaults", false)
|
||||
.ok()
|
||||
.flatten()
|
||||
.and_then(|ty| {
|
||||
if let Type::KnownInstance(
|
||||
KnownInstanceType::CollectionsNamedTupleDefaultsSchema(
|
||||
schema,
|
||||
),
|
||||
) = ty
|
||||
{
|
||||
Some(schema.count(db))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap_or(0);
|
||||
|
||||
if let (Some(name), Some(mut field_names)) = (name, field_names) {
|
||||
// Apply rename logic if `rename=True`.
|
||||
if rename {
|
||||
let mut seen_names = FxHashSet::<&str>::default();
|
||||
for (i, field_name) in field_names.iter_mut().enumerate() {
|
||||
let name_str = field_name.as_str();
|
||||
// Rename if: starts with underscore, is a keyword,
|
||||
// is not a valid identifier, or is a duplicate.
|
||||
let needs_rename = name_str.starts_with('_')
|
||||
|| is_keyword(name_str)
|
||||
|| !is_identifier(name_str)
|
||||
|| seen_names.contains(name_str);
|
||||
if needs_rename {
|
||||
*field_name = Name::new(format!("_{i}"));
|
||||
}
|
||||
seen_names.insert(field_name.as_str());
|
||||
}
|
||||
}
|
||||
|
||||
// Build fields: All fields have type `Any` for collections.namedtuple.
|
||||
// Fields at the end get defaults from the `defaults` parameter.
|
||||
let num_fields = field_names.len();
|
||||
let fields: Box<[(Name, Type<'db>, Option<Type<'db>>)]> =
|
||||
field_names
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, field_name)| {
|
||||
// Defaults apply to the rightmost fields.
|
||||
// We use `Type::any()` for the default type since all fields are `Any`.
|
||||
let default = if num_defaults > 0
|
||||
&& i >= num_fields - num_defaults
|
||||
{
|
||||
Some(Type::any())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
(field_name.clone(), Type::any(), default)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let namedtuple =
|
||||
FunctionalNamedTupleLiteral::new(db, name, fields);
|
||||
overload.set_return_type(Type::ClassLiteral(
|
||||
ClassLiteral::FunctionalNamedTuple(namedtuple),
|
||||
));
|
||||
} else {
|
||||
overload.set_return_type(
|
||||
KnownClass::NamedTupleFallback.to_class_literal(db),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
overload.set_return_type(
|
||||
KnownClass::NamedTupleFallback.to_class_literal(db),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Some(KnownFunction::MakeDataclass) => {
|
||||
// Handle dataclasses.make_dataclass(cls_name, fields, ...)
|
||||
if let [Some(name_type), Some(fields_type), ..] =
|
||||
overload.parameter_types()
|
||||
{
|
||||
let name = name_type
|
||||
.as_string_literal()
|
||||
.map(|s| Name::new(s.value(db)));
|
||||
|
||||
// Extract fields from the schema type inferred from the literal.
|
||||
let fields: Option<Vec<(Name, Type<'db>, Option<Type<'db>>)>> =
|
||||
if let Type::KnownInstance(
|
||||
KnownInstanceType::MakeDataclassFieldsSchema(schema),
|
||||
) = fields_type
|
||||
{
|
||||
Some(
|
||||
schema
|
||||
.fields(db)
|
||||
.iter()
|
||||
.map(|(name, ty, default)| {
|
||||
(name.clone(), *ty, *default)
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Extract bases from keyword argument.
|
||||
let bases: Box<[ClassBase<'db>]> = overload
|
||||
.parameter_type_by_name("bases", false)
|
||||
.ok()
|
||||
.flatten()
|
||||
.and_then(|bases_type| {
|
||||
bases_type.exact_tuple_instance_spec(db).map(|tuple_spec| {
|
||||
tuple_spec
|
||||
.fixed_elements()
|
||||
.filter_map(|ty| match ty {
|
||||
Type::ClassLiteral(literal) => {
|
||||
Some(ClassBase::Class(
|
||||
literal.default_specialization(db),
|
||||
))
|
||||
}
|
||||
Type::GenericAlias(generic) => {
|
||||
Some(ClassBase::Class(ClassType::Generic(
|
||||
*generic,
|
||||
)))
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
// Extract dataclass flags from keyword arguments.
|
||||
let mut flags = DataclassFlags::empty();
|
||||
let init = overload
|
||||
.parameter_type_by_name("init", false)
|
||||
.ok()
|
||||
.flatten();
|
||||
if to_bool(&init, true) {
|
||||
flags |= DataclassFlags::INIT;
|
||||
}
|
||||
let repr = overload
|
||||
.parameter_type_by_name("repr", false)
|
||||
.ok()
|
||||
.flatten();
|
||||
if to_bool(&repr, true) {
|
||||
flags |= DataclassFlags::REPR;
|
||||
}
|
||||
let eq =
|
||||
overload.parameter_type_by_name("eq", false).ok().flatten();
|
||||
if to_bool(&eq, true) {
|
||||
flags |= DataclassFlags::EQ;
|
||||
}
|
||||
let order = overload
|
||||
.parameter_type_by_name("order", false)
|
||||
.ok()
|
||||
.flatten();
|
||||
if to_bool(&order, false) {
|
||||
flags |= DataclassFlags::ORDER;
|
||||
}
|
||||
let unsafe_hash = overload
|
||||
.parameter_type_by_name("unsafe_hash", false)
|
||||
.ok()
|
||||
.flatten();
|
||||
if to_bool(&unsafe_hash, false) {
|
||||
flags |= DataclassFlags::UNSAFE_HASH;
|
||||
}
|
||||
let frozen = overload
|
||||
.parameter_type_by_name("frozen", false)
|
||||
.ok()
|
||||
.flatten();
|
||||
if to_bool(&frozen, false) {
|
||||
flags |= DataclassFlags::FROZEN;
|
||||
}
|
||||
let match_args = overload
|
||||
.parameter_type_by_name("match_args", false)
|
||||
.ok()
|
||||
.flatten();
|
||||
if to_bool(&match_args, true) {
|
||||
if Program::get(db).python_version(db) >= PythonVersion::PY310 {
|
||||
flags |= DataclassFlags::MATCH_ARGS;
|
||||
}
|
||||
}
|
||||
let kw_only = overload
|
||||
.parameter_type_by_name("kw_only", false)
|
||||
.ok()
|
||||
.flatten();
|
||||
if to_bool(&kw_only, false) {
|
||||
if Program::get(db).python_version(db) >= PythonVersion::PY310 {
|
||||
flags |= DataclassFlags::KW_ONLY;
|
||||
}
|
||||
}
|
||||
let slots = overload
|
||||
.parameter_type_by_name("slots", false)
|
||||
.ok()
|
||||
.flatten();
|
||||
if to_bool(&slots, false) {
|
||||
if Program::get(db).python_version(db) >= PythonVersion::PY310 {
|
||||
flags |= DataclassFlags::SLOTS;
|
||||
}
|
||||
}
|
||||
let weakref_slot = overload
|
||||
.parameter_type_by_name("weakref_slot", false)
|
||||
.ok()
|
||||
.flatten();
|
||||
if to_bool(&weakref_slot, false) {
|
||||
if Program::get(db).python_version(db) >= PythonVersion::PY311 {
|
||||
flags |= DataclassFlags::WEAKREF_SLOT;
|
||||
}
|
||||
}
|
||||
|
||||
let params = DataclassParams::from_flags(db, flags);
|
||||
|
||||
if let (Some(name), Some(fields)) = (name, fields) {
|
||||
let dataclass = FunctionalDataclassLiteral::new(
|
||||
db,
|
||||
name,
|
||||
fields.into_boxed_slice(),
|
||||
bases,
|
||||
params,
|
||||
);
|
||||
overload.set_return_type(Type::ClassLiteral(
|
||||
ClassLiteral::FunctionalDataclass(dataclass),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ => {
|
||||
@@ -1373,6 +1644,54 @@ impl<'db> Bindings<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
Some(KnownClass::Type) if overload_index == 1 => {
|
||||
// Three-argument call: type(name, bases, dict)
|
||||
if let [Some(name_type), Some(bases), Some(_namespace), ..] =
|
||||
overload.parameter_types()
|
||||
{
|
||||
// Extract the name from the first argument (if it's a string literal).
|
||||
let name = name_type
|
||||
.as_string_literal()
|
||||
.map(|s| ruff_python_ast::name::Name::new(s.value(db)));
|
||||
|
||||
// Extract base classes from the bases tuple.
|
||||
let base_classes: Option<Box<[ClassBase<'db>]>> =
|
||||
bases.exact_tuple_instance_spec(db).and_then(|tuple_spec| {
|
||||
tuple_spec
|
||||
.fixed_elements()
|
||||
.map(|base| match base {
|
||||
Type::ClassLiteral(class) => {
|
||||
Some(ClassBase::Class(
|
||||
class.default_specialization(db),
|
||||
))
|
||||
}
|
||||
Type::GenericAlias(alias) => Some(
|
||||
ClassBase::Class(ClassType::Generic(*alias)),
|
||||
),
|
||||
Type::SubclassOf(subclass_of) => {
|
||||
match subclass_of.subclass_of() {
|
||||
SubclassOfInner::Class(class) => {
|
||||
Some(ClassBase::Class(class))
|
||||
}
|
||||
SubclassOfInner::Dynamic(_)
|
||||
| SubclassOfInner::TypeVar(_) => None,
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect::<Option<Box<[_]>>>()
|
||||
});
|
||||
|
||||
if let (Some(name), Some(bases)) = (name, base_classes) {
|
||||
let functional_class =
|
||||
FunctionalClassLiteral::new(db, name, bases);
|
||||
overload.set_return_type(Type::ClassLiteral(
|
||||
ClassLiteral::Functional(functional_class),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(KnownClass::Property) => {
|
||||
if let [getter, setter, ..] = overload.parameter_types() {
|
||||
overload.set_return_type(Type::PropertyInstance(
|
||||
@@ -1409,6 +1728,94 @@ impl<'db> Bindings<'db> {
|
||||
overload.set_return_type(todo_type!("Support for functional `TypedDict`"));
|
||||
}
|
||||
|
||||
Type::SpecialForm(SpecialFormType::NamedTuple) => {
|
||||
if let [Some(name_type), Some(fields_type), ..] = overload.parameter_types()
|
||||
{
|
||||
let name = name_type
|
||||
.as_string_literal()
|
||||
.map(|s| Name::new(s.value(db)));
|
||||
|
||||
// Check if fields_type is a TypingNamedTupleFieldsSchema (from literal inference).
|
||||
let fields: Option<Box<[(Name, Type<'db>, Option<Type<'db>>)]>> =
|
||||
if let Type::KnownInstance(
|
||||
KnownInstanceType::TypingNamedTupleFieldsSchema(schema),
|
||||
) = fields_type
|
||||
{
|
||||
// Extract fields from the schema.
|
||||
Some(
|
||||
schema
|
||||
.fields(db)
|
||||
.iter()
|
||||
.map(|(name, ty)| (name.clone(), *ty, None))
|
||||
.collect(),
|
||||
)
|
||||
} else {
|
||||
// Fall back to extracting from a tuple type for the variable case:
|
||||
// fields = (("x", int), ("y", str))
|
||||
// NamedTuple("Foo", fields)
|
||||
let extract_field = |field_tuple: &Type<'db>| -> Option<(
|
||||
Name,
|
||||
Type<'db>,
|
||||
Option<Type<'db>>,
|
||||
)> {
|
||||
let field_spec =
|
||||
field_tuple.exact_tuple_instance_spec(db)?;
|
||||
let elements: Vec<_> =
|
||||
field_spec.fixed_elements().collect();
|
||||
if elements.len() != 2 {
|
||||
return None;
|
||||
}
|
||||
let field_name = elements[0]
|
||||
.as_string_literal()
|
||||
.map(|s| Name::new(s.value(db)))?;
|
||||
let field_ty = elements[1];
|
||||
let resolved_ty = match field_ty {
|
||||
Type::ClassLiteral(class) => {
|
||||
class.to_non_generic_instance(db)
|
||||
}
|
||||
Type::GenericAlias(alias) => {
|
||||
Type::instance(db, ClassType::Generic(*alias))
|
||||
}
|
||||
Type::SubclassOf(subclass_of) => {
|
||||
match subclass_of.subclass_of() {
|
||||
SubclassOfInner::Class(class) => {
|
||||
Type::instance(db, class)
|
||||
}
|
||||
_ => *field_ty,
|
||||
}
|
||||
}
|
||||
ty => *ty,
|
||||
};
|
||||
Some((field_name, resolved_ty, None))
|
||||
};
|
||||
|
||||
fields_type.exact_tuple_instance_spec(db).and_then(
|
||||
|tuple_spec| {
|
||||
tuple_spec
|
||||
.fixed_elements()
|
||||
.map(extract_field)
|
||||
.collect::<Option<Box<[_]>>>()
|
||||
},
|
||||
)
|
||||
};
|
||||
|
||||
if let (Some(name), Some(fields)) = (name, fields) {
|
||||
let namedtuple = FunctionalNamedTupleLiteral::new(db, name, fields);
|
||||
overload.set_return_type(Type::ClassLiteral(
|
||||
ClassLiteral::FunctionalNamedTuple(namedtuple),
|
||||
));
|
||||
} else {
|
||||
overload.set_return_type(
|
||||
KnownClass::NamedTupleFallback.to_class_literal(db),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
overload.set_return_type(
|
||||
KnownClass::NamedTupleFallback.to_class_literal(db),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Not a special case
|
||||
_ => {}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,11 @@
|
||||
use crate::Db;
|
||||
use crate::types::class::CodeGeneratorKind;
|
||||
use crate::types::generics::Specialization;
|
||||
use crate::types::mro::MroIterator;
|
||||
use crate::types::tuple::TupleType;
|
||||
use crate::types::{
|
||||
ApplyTypeMappingVisitor, ClassLiteral, ClassType, DynamicType, KnownClass, KnownInstanceType,
|
||||
MaterializationKind, MroError, MroIterator, NormalizedVisitor, SpecialFormType, Type,
|
||||
ApplyTypeMappingVisitor, ClassType, DynamicType, KnownClass, KnownInstanceType,
|
||||
MaterializationKind, MroError, NormalizedVisitor, SpecialFormType, StmtClassLiteral, Type,
|
||||
TypeContext, TypeMapping, todo_type,
|
||||
};
|
||||
|
||||
@@ -91,7 +92,7 @@ impl<'db> ClassBase<'db> {
|
||||
pub(super) fn try_from_type(
|
||||
db: &'db dyn Db,
|
||||
ty: Type<'db>,
|
||||
subclass: ClassLiteral<'db>,
|
||||
subclass: StmtClassLiteral<'db>,
|
||||
) -> Option<Self> {
|
||||
match ty {
|
||||
Type::Dynamic(dynamic) => Some(Self::Dynamic(dynamic)),
|
||||
@@ -102,10 +103,7 @@ impl<'db> ClassBase<'db> {
|
||||
{
|
||||
Self::try_from_type(db, todo_type!("GenericAlias instance"), subclass)
|
||||
}
|
||||
Type::SubclassOf(subclass_of) => subclass_of
|
||||
.subclass_of()
|
||||
.into_dynamic()
|
||||
.map(ClassBase::Dynamic),
|
||||
Type::SubclassOf(subclass_of) => subclass_of.subclass_of().to_class_base(),
|
||||
Type::Intersection(inter) => {
|
||||
let valid_element = inter
|
||||
.positive(db)
|
||||
@@ -197,7 +195,12 @@ impl<'db> ClassBase<'db> {
|
||||
// A class inheriting from a newtype would make intuitive sense, but newtype
|
||||
// wrappers are just identity callables at runtime, so this sort of inheritance
|
||||
// doesn't work and isn't allowed.
|
||||
| KnownInstanceType::NewType(_) => None,
|
||||
| KnownInstanceType::NewType(_)
|
||||
// Internal types, should never be a base.
|
||||
| KnownInstanceType::TypingNamedTupleFieldsSchema(_)
|
||||
| KnownInstanceType::CollectionsNamedTupleFieldsSchema(_)
|
||||
| KnownInstanceType::CollectionsNamedTupleDefaultsSchema(_)
|
||||
| KnownInstanceType::MakeDataclassFieldsSchema(_) => None,
|
||||
KnownInstanceType::TypeGenericAlias(_) => {
|
||||
Self::try_from_type(db, KnownClass::Type.to_class_literal(db), subclass)
|
||||
}
|
||||
@@ -239,7 +242,11 @@ impl<'db> ClassBase<'db> {
|
||||
| SpecialFormType::TypeOf
|
||||
| SpecialFormType::CallableTypeOf
|
||||
| SpecialFormType::AlwaysTruthy
|
||||
| SpecialFormType::AlwaysFalsy => None,
|
||||
| SpecialFormType::AlwaysFalsy
|
||||
| SpecialFormType::TypingNamedTupleFieldsSchema
|
||||
| SpecialFormType::CollectionsNamedTupleFieldsSchema
|
||||
| SpecialFormType::CollectionsNamedTupleDefaultsSchema
|
||||
| SpecialFormType::MakeDataclassFieldsSchema => None,
|
||||
|
||||
SpecialFormType::Any => Some(Self::Dynamic(DynamicType::Any)),
|
||||
SpecialFormType::Unknown => Some(Self::unknown()),
|
||||
@@ -312,6 +319,15 @@ impl<'db> ClassBase<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the metaclass of this class base.
|
||||
pub(crate) fn metaclass(self, db: &'db dyn Db) -> Type<'db> {
|
||||
match self {
|
||||
Self::Class(class) => class.metaclass(db),
|
||||
Self::Dynamic(dynamic) => Type::Dynamic(dynamic),
|
||||
Self::Protocol | Self::Generic | Self::TypedDict => KnownClass::Type.to_instance(db),
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_type_mapping_impl<'a>(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
@@ -360,7 +376,11 @@ impl<'db> ClassBase<'db> {
|
||||
pub(super) fn has_cyclic_mro(self, db: &'db dyn Db) -> bool {
|
||||
match self {
|
||||
ClassBase::Class(class) => {
|
||||
let (class_literal, specialization) = class.class_literal(db);
|
||||
let Some((class_literal, specialization)) = class.stmt_class_literal(db) else {
|
||||
// Functional classes can't have cyclic MRO since their bases must
|
||||
// already exist at creation time.
|
||||
return false;
|
||||
};
|
||||
class_literal
|
||||
.try_mro(db, specialization)
|
||||
.is_err_and(MroError::is_cycle)
|
||||
|
||||
@@ -2,7 +2,7 @@ use super::call::CallErrorKind;
|
||||
use super::context::InferContext;
|
||||
use super::mro::DuplicateBaseError;
|
||||
use super::{
|
||||
CallArguments, CallDunderError, ClassBase, ClassLiteral, KnownClass,
|
||||
CallArguments, CallDunderError, ClassBase, ClassLiteral, KnownClass, StmtClassLiteral,
|
||||
add_inferred_python_version_hint_to_diagnostic,
|
||||
};
|
||||
use crate::diagnostic::did_you_mean;
|
||||
@@ -2733,12 +2733,12 @@ pub(super) fn report_implicit_return_type(
|
||||
"Only classes that directly inherit from `typing.Protocol` \
|
||||
or `typing_extensions.Protocol` are considered protocol classes",
|
||||
);
|
||||
sub_diagnostic.annotate(
|
||||
Annotation::primary(class.header_span(db)).message(format_args!(
|
||||
if let Some(span) = class.header_span(db) {
|
||||
sub_diagnostic.annotate(Annotation::primary(span).message(format_args!(
|
||||
"`Protocol` not present in `{class}`'s immediate bases",
|
||||
class = class.name(db)
|
||||
)),
|
||||
);
|
||||
)));
|
||||
}
|
||||
diagnostic.sub(sub_diagnostic);
|
||||
|
||||
diagnostic.info("See https://typing.python.org/en/latest/spec/protocol.html#");
|
||||
@@ -2907,7 +2907,7 @@ pub(crate) fn report_invalid_exception_cause(context: &InferContext, node: &ast:
|
||||
|
||||
pub(crate) fn report_instance_layout_conflict(
|
||||
context: &InferContext,
|
||||
class: ClassLiteral,
|
||||
class: StmtClassLiteral,
|
||||
node: &ast::StmtClassDef,
|
||||
disjoint_bases: &IncompatibleBases,
|
||||
) {
|
||||
@@ -2942,7 +2942,7 @@ pub(crate) fn report_instance_layout_conflict(
|
||||
|
||||
let span = context.span(&node.bases()[*node_index]);
|
||||
let mut annotation = Annotation::secondary(span.clone());
|
||||
if disjoint_base.class == *originating_base {
|
||||
if originating_base.as_stmt() == Some(disjoint_base.class) {
|
||||
match disjoint_base.kind {
|
||||
DisjointBaseKind::DefinesSlots => {
|
||||
annotation = annotation.message(format_args!(
|
||||
@@ -3112,7 +3112,7 @@ pub(crate) fn report_invalid_argument_number_to_special_form(
|
||||
pub(crate) fn report_bad_argument_to_get_protocol_members(
|
||||
context: &InferContext,
|
||||
call: &ast::ExprCall,
|
||||
class: ClassLiteral,
|
||||
class: StmtClassLiteral,
|
||||
) {
|
||||
let Some(builder) = context.report_lint(&INVALID_ARGUMENT_TYPE, call) else {
|
||||
return;
|
||||
@@ -3165,9 +3165,9 @@ pub(crate) fn report_bad_argument_to_protocol_interface(
|
||||
class.name(db)
|
||||
),
|
||||
);
|
||||
class_def_diagnostic.annotate(Annotation::primary(
|
||||
class.class_literal(db).0.header_span(db),
|
||||
));
|
||||
if let Some((class_literal, _)) = class.stmt_class_literal(db) {
|
||||
class_def_diagnostic.annotate(Annotation::primary(class_literal.header_span(db)));
|
||||
}
|
||||
diagnostic.sub(class_def_diagnostic);
|
||||
}
|
||||
|
||||
@@ -3225,10 +3225,11 @@ pub(crate) fn report_runtime_check_against_non_runtime_checkable_protocol(
|
||||
but it is not declared as runtime-checkable"
|
||||
),
|
||||
);
|
||||
class_def_diagnostic.annotate(
|
||||
Annotation::primary(protocol.header_span(db))
|
||||
.message(format_args!("`{class_name}` declared here")),
|
||||
);
|
||||
if let Some(span) = protocol.header_span(db) {
|
||||
class_def_diagnostic.annotate(
|
||||
Annotation::primary(span).message(format_args!("`{class_name}` declared here")),
|
||||
);
|
||||
}
|
||||
diagnostic.sub(class_def_diagnostic);
|
||||
|
||||
diagnostic.info(format_args!(
|
||||
@@ -3256,10 +3257,12 @@ pub(crate) fn report_attempted_protocol_instantiation(
|
||||
SubDiagnosticSeverity::Info,
|
||||
format_args!("Protocol classes cannot be instantiated"),
|
||||
);
|
||||
class_def_diagnostic.annotate(
|
||||
Annotation::primary(protocol.header_span(db))
|
||||
.message(format_args!("`{class_name}` declared as a protocol here")),
|
||||
);
|
||||
if let Some(span) = protocol.header_span(db) {
|
||||
class_def_diagnostic.annotate(
|
||||
Annotation::primary(span)
|
||||
.message(format_args!("`{class_name}` declared as a protocol here")),
|
||||
);
|
||||
}
|
||||
diagnostic.sub(class_def_diagnostic);
|
||||
}
|
||||
|
||||
@@ -3344,10 +3347,12 @@ pub(crate) fn report_undeclared_protocol_member(
|
||||
"Assigning to an undeclared variable in a protocol class \
|
||||
leads to an ambiguous interface",
|
||||
);
|
||||
class_def_diagnostic.annotate(
|
||||
Annotation::primary(protocol_class.header_span(db))
|
||||
.message(format_args!("`{class_name}` declared as a protocol here",)),
|
||||
);
|
||||
if let Some(span) = protocol_class.header_span(db) {
|
||||
class_def_diagnostic.annotate(
|
||||
Annotation::primary(span)
|
||||
.message(format_args!("`{class_name}` declared as a protocol here",)),
|
||||
);
|
||||
}
|
||||
diagnostic.sub(class_def_diagnostic);
|
||||
|
||||
diagnostic.info(format_args!(
|
||||
@@ -3358,7 +3363,7 @@ pub(crate) fn report_undeclared_protocol_member(
|
||||
|
||||
pub(crate) fn report_duplicate_bases(
|
||||
context: &InferContext,
|
||||
class: ClassLiteral,
|
||||
class: StmtClassLiteral,
|
||||
duplicate_base_error: &DuplicateBaseError,
|
||||
bases_list: &[ast::Expr],
|
||||
) {
|
||||
@@ -3405,7 +3410,7 @@ pub(crate) fn report_invalid_or_unsupported_base(
|
||||
context: &InferContext,
|
||||
base_node: &ast::Expr,
|
||||
base_type: Type,
|
||||
class: ClassLiteral,
|
||||
class: StmtClassLiteral,
|
||||
) {
|
||||
let db = context.db();
|
||||
let instance_of_type = KnownClass::Type.to_instance(db);
|
||||
@@ -3515,7 +3520,7 @@ fn report_unsupported_base(
|
||||
context: &InferContext,
|
||||
base_node: &ast::Expr,
|
||||
base_type: Type,
|
||||
class: ClassLiteral,
|
||||
class: StmtClassLiteral,
|
||||
) {
|
||||
let Some(builder) = context.report_lint(&UNSUPPORTED_BASE, base_node) else {
|
||||
return;
|
||||
@@ -3538,7 +3543,7 @@ fn report_invalid_base<'ctx, 'db>(
|
||||
context: &'ctx InferContext<'db, '_>,
|
||||
base_node: &ast::Expr,
|
||||
base_type: Type<'db>,
|
||||
class: ClassLiteral<'db>,
|
||||
class: StmtClassLiteral<'db>,
|
||||
) -> Option<LintDiagnosticGuard<'ctx, 'db>> {
|
||||
let builder = context.report_lint(&INVALID_BASE, base_node)?;
|
||||
let mut diagnostic = builder.into_diagnostic(format_args!(
|
||||
@@ -3634,7 +3639,7 @@ pub(crate) fn report_invalid_key_on_typed_dict<'db>(
|
||||
|
||||
pub(super) fn report_namedtuple_field_without_default_after_field_with_default<'db>(
|
||||
context: &InferContext<'db, '_>,
|
||||
class: ClassLiteral<'db>,
|
||||
class: StmtClassLiteral<'db>,
|
||||
(field, field_def): (&str, Option<Definition<'db>>),
|
||||
(field_with_default, field_with_default_def): &(Name, Option<Definition<'db>>),
|
||||
) {
|
||||
@@ -3683,7 +3688,7 @@ pub(super) fn report_namedtuple_field_without_default_after_field_with_default<'
|
||||
|
||||
pub(super) fn report_named_tuple_field_with_leading_underscore<'db>(
|
||||
context: &InferContext<'db, '_>,
|
||||
class: ClassLiteral<'db>,
|
||||
class: StmtClassLiteral<'db>,
|
||||
field_name: &str,
|
||||
field_definition: Option<Definition<'db>>,
|
||||
) {
|
||||
@@ -3807,7 +3812,7 @@ pub(crate) fn report_cannot_delete_typed_dict_key<'db>(
|
||||
|
||||
pub(crate) fn report_invalid_type_param_order<'db>(
|
||||
context: &InferContext<'db, '_>,
|
||||
class: ClassLiteral<'db>,
|
||||
class: StmtClassLiteral<'db>,
|
||||
node: &ast::StmtClassDef,
|
||||
typevar_with_default: TypeVarInstance<'db>,
|
||||
invalid_later_typevars: &[TypeVarInstance<'db>],
|
||||
@@ -3892,7 +3897,7 @@ pub(crate) fn report_invalid_type_param_order<'db>(
|
||||
pub(crate) fn report_rebound_typevar<'db>(
|
||||
context: &InferContext<'db, '_>,
|
||||
typevar_name: &ast::name::Name,
|
||||
class: ClassLiteral<'db>,
|
||||
class: StmtClassLiteral<'db>,
|
||||
class_node: &ast::StmtClassDef,
|
||||
other_typevar: BoundTypeVarInstance<'db>,
|
||||
) {
|
||||
@@ -3911,7 +3916,7 @@ pub(crate) fn report_rebound_typevar<'db>(
|
||||
return;
|
||||
};
|
||||
let span = match binding_type(db, other_definition) {
|
||||
Type::ClassLiteral(class) => Some(class.header_span(db)),
|
||||
Type::ClassLiteral(class) => class.as_stmt().map(|stmt| stmt.header_span(db)),
|
||||
Type::FunctionLiteral(function) => function.spans(db).map(|spans| spans.signature),
|
||||
_ => return,
|
||||
};
|
||||
@@ -3967,10 +3972,11 @@ pub(super) fn report_invalid_method_override<'db>(
|
||||
let superclass_name = superclass.name(db);
|
||||
|
||||
let overridden_method = if class_name == superclass_name {
|
||||
format!(
|
||||
"{superclass}.{member}",
|
||||
superclass = superclass.qualified_name(db),
|
||||
)
|
||||
if let Some(qualified_name) = superclass.qualified_name(db) {
|
||||
format!("{qualified_name}.{member}")
|
||||
} else {
|
||||
format!("{superclass_name}.{member}")
|
||||
}
|
||||
} else {
|
||||
format!("{superclass_name}.{member}")
|
||||
};
|
||||
@@ -4019,7 +4025,10 @@ pub(super) fn report_invalid_method_override<'db>(
|
||||
);
|
||||
}
|
||||
|
||||
let superclass_scope = superclass.class_literal(db).0.body_scope(db);
|
||||
let Some((superclass_literal, _)) = superclass.stmt_class_literal(db) else {
|
||||
return;
|
||||
};
|
||||
let superclass_scope = superclass_literal.body_scope(db);
|
||||
|
||||
match superclass_method_kind {
|
||||
MethodKind::NotSynthesized => {
|
||||
@@ -4085,10 +4094,12 @@ pub(super) fn report_invalid_method_override<'db>(
|
||||
)),
|
||||
};
|
||||
|
||||
sub.annotate(
|
||||
Annotation::primary(superclass.header_span(db))
|
||||
.message(format_args!("Definition of `{superclass_name}`")),
|
||||
);
|
||||
if let Some(span) = superclass.header_span(db) {
|
||||
sub.annotate(
|
||||
Annotation::primary(span)
|
||||
.message(format_args!("Definition of `{superclass_name}`")),
|
||||
);
|
||||
}
|
||||
diagnostic.sub(sub);
|
||||
}
|
||||
}
|
||||
@@ -4150,7 +4161,10 @@ pub(super) fn report_overridden_final_method<'db>(
|
||||
};
|
||||
|
||||
let superclass_name = if superclass.name(db) == subclass.name(db) {
|
||||
superclass.qualified_name(db).to_string()
|
||||
superclass
|
||||
.qualified_name(db)
|
||||
.map(|name| name.to_string())
|
||||
.unwrap_or_else(|| superclass.name(db).to_string())
|
||||
} else {
|
||||
superclass.name(db).to_string()
|
||||
};
|
||||
@@ -4206,9 +4220,10 @@ pub(super) fn report_overridden_final_method<'db>(
|
||||
// but you'd want to delete the `@my_property.deleter` as well as the getter and the deleter,
|
||||
// and we don't model property deleters at all right now.
|
||||
if let Type::FunctionLiteral(function) = subclass_type {
|
||||
let class_node = subclass
|
||||
.class_literal(db)
|
||||
.0
|
||||
let Some((subclass_literal, _)) = subclass.stmt_class_literal(db) else {
|
||||
return;
|
||||
};
|
||||
let class_node = subclass_literal
|
||||
.body_scope(db)
|
||||
.node(db)
|
||||
.expect_class()
|
||||
@@ -4506,9 +4521,9 @@ fn report_unsupported_binary_operation_impl<'a>(
|
||||
|
||||
pub(super) fn report_bad_frozen_dataclass_inheritance<'db>(
|
||||
context: &InferContext<'db, '_>,
|
||||
class: ClassLiteral<'db>,
|
||||
class: StmtClassLiteral<'db>,
|
||||
class_node: &ast::StmtClassDef,
|
||||
base_class: ClassLiteral<'db>,
|
||||
base_class: StmtClassLiteral<'db>,
|
||||
base_class_node: &ast::Expr,
|
||||
base_class_params: DataclassFlags,
|
||||
) {
|
||||
|
||||
@@ -16,7 +16,8 @@ use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use crate::Db;
|
||||
use crate::place::Place;
|
||||
use crate::semantic_index::definition::Definition;
|
||||
use crate::types::class::{ClassLiteral, ClassType, GenericAlias};
|
||||
use crate::types::class::{ClassLiteral, ClassType, GenericAlias, StmtClassLiteral};
|
||||
use crate::types::class_base::ClassBase;
|
||||
use crate::types::function::{FunctionType, OverloadLiteral};
|
||||
use crate::types::generics::{GenericContext, Specialization};
|
||||
use crate::types::signatures::{
|
||||
@@ -366,6 +367,9 @@ struct AmbiguousClassCollector<'db> {
|
||||
|
||||
impl<'db> AmbiguousClassCollector<'db> {
|
||||
fn record_class(&self, db: &'db dyn Db, class: ClassLiteral<'db>) {
|
||||
let ClassLiteral::Stmt(class) = class else {
|
||||
return;
|
||||
};
|
||||
match self.class_names.borrow_mut().entry(class.name(db)) {
|
||||
Entry::Vacant(entry) => {
|
||||
entry.insert(AmbiguityState::Unambiguous(class));
|
||||
@@ -413,10 +417,10 @@ impl<'db> AmbiguousClassCollector<'db> {
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
enum AmbiguityState<'db> {
|
||||
/// The class can be displayed unambiguously using its unqualified name
|
||||
Unambiguous(ClassLiteral<'db>),
|
||||
Unambiguous(StmtClassLiteral<'db>),
|
||||
/// The class must be displayed using its fully qualified name to avoid ambiguity.
|
||||
RequiresFullyQualifiedName {
|
||||
class: ClassLiteral<'db>,
|
||||
class: StmtClassLiteral<'db>,
|
||||
qualified_name_components: Vec<String>,
|
||||
},
|
||||
/// Even the class's fully qualified name is not sufficient;
|
||||
@@ -432,8 +436,12 @@ impl<'db> TypeVisitor<'db> for AmbiguousClassCollector<'db> {
|
||||
fn visit_type(&self, db: &'db dyn Db, ty: Type<'db>) {
|
||||
match ty {
|
||||
Type::ClassLiteral(class) => self.record_class(db, class),
|
||||
Type::EnumLiteral(literal) => self.record_class(db, literal.enum_class(db)),
|
||||
Type::GenericAlias(alias) => self.record_class(db, alias.origin(db)),
|
||||
Type::EnumLiteral(literal) => {
|
||||
self.record_class(db, ClassLiteral::Stmt(literal.enum_class(db)));
|
||||
}
|
||||
Type::GenericAlias(alias) => {
|
||||
self.record_class(db, ClassLiteral::Stmt(alias.origin(db)));
|
||||
}
|
||||
// Visit the class (as if it were a nominal-instance type)
|
||||
// rather than the protocol members, if it is a class-based protocol.
|
||||
// (For the purposes of displaying the type, we'll use the class name.)
|
||||
@@ -536,7 +544,7 @@ impl fmt::Debug for DisplayType<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> ClassLiteral<'db> {
|
||||
impl<'db> StmtClassLiteral<'db> {
|
||||
fn display_with(self, db: &'db dyn Db, settings: DisplaySettings<'db>) -> ClassDisplay<'db> {
|
||||
ClassDisplay {
|
||||
db,
|
||||
@@ -548,7 +556,7 @@ impl<'db> ClassLiteral<'db> {
|
||||
|
||||
struct ClassDisplay<'db> {
|
||||
db: &'db dyn Db,
|
||||
class: ClassLiteral<'db>,
|
||||
class: StmtClassLiteral<'db>,
|
||||
settings: DisplaySettings<'db>,
|
||||
}
|
||||
|
||||
@@ -556,7 +564,7 @@ impl<'db> FmtDetailed<'db> for ClassDisplay<'db> {
|
||||
fn fmt_detailed(&self, f: &mut TypeWriter<'_, '_, 'db>) -> fmt::Result {
|
||||
let qualification_level = self.settings.qualified.get(&**self.class.name(self.db));
|
||||
|
||||
let ty = Type::ClassLiteral(self.class);
|
||||
let ty = Type::ClassLiteral(ClassLiteral::Stmt(self.class));
|
||||
if qualification_level.is_some() {
|
||||
write!(f.with_type(ty), "{}", self.class.qualified_name(self.db))?;
|
||||
} else {
|
||||
@@ -649,17 +657,35 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
|
||||
.expect("Specialization::tuple() should always return `Some()` for `KnownClass::Tuple`")
|
||||
.display_with(self.db, self.settings.clone())
|
||||
.fmt_detailed(f),
|
||||
(ClassType::NonGeneric(class), _) => {
|
||||
(ClassType::NonGeneric(ClassLiteral::Stmt(class)), _) => {
|
||||
class.display_with(self.db, self.settings.clone()).fmt_detailed(f)
|
||||
},
|
||||
}
|
||||
(ClassType::NonGeneric(ClassLiteral::Functional(functional)), _) => {
|
||||
f.with_type(self.ty).write_str(functional.name(self.db).as_str())
|
||||
}
|
||||
(ClassType::NonGeneric(ClassLiteral::FunctionalNamedTuple(namedtuple)), _) => {
|
||||
f.with_type(self.ty).write_str(namedtuple.name(self.db).as_str())
|
||||
}
|
||||
(ClassType::NonGeneric(ClassLiteral::FunctionalDataclass(dataclass)), _) => {
|
||||
f.with_type(self.ty).write_str(dataclass.name(self.db).as_str())
|
||||
}
|
||||
(ClassType::Generic(alias), _) => alias.display_with(self.db, self.settings.clone()).fmt_detailed(f),
|
||||
}
|
||||
}
|
||||
Type::ProtocolInstance(protocol) => match protocol.inner {
|
||||
Protocol::FromClass(class) => match *class {
|
||||
ClassType::NonGeneric(class) => class
|
||||
ClassType::NonGeneric(ClassLiteral::Stmt(class)) => class
|
||||
.display_with(self.db, self.settings.clone())
|
||||
.fmt_detailed(f),
|
||||
ClassType::NonGeneric(ClassLiteral::Functional(functional)) => f
|
||||
.with_type(self.ty)
|
||||
.write_str(functional.name(self.db).as_str()),
|
||||
ClassType::NonGeneric(ClassLiteral::FunctionalNamedTuple(namedtuple)) => f
|
||||
.with_type(self.ty)
|
||||
.write_str(namedtuple.name(self.db).as_str()),
|
||||
ClassType::NonGeneric(ClassLiteral::FunctionalDataclass(dataclass)) => f
|
||||
.with_type(self.ty)
|
||||
.write_str(dataclass.name(self.db).as_str()),
|
||||
ClassType::Generic(alias) => alias
|
||||
.display_with(self.db, self.settings.clone())
|
||||
.fmt_detailed(f),
|
||||
@@ -698,9 +724,25 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
|
||||
f.set_invalid_type_annotation();
|
||||
let mut f = f.with_type(self.ty);
|
||||
f.write_str("<class '")?;
|
||||
class
|
||||
.display_with(self.db, self.settings.clone())
|
||||
.fmt_detailed(&mut f)?;
|
||||
match class {
|
||||
ClassLiteral::Stmt(stmt_class) => {
|
||||
stmt_class
|
||||
.display_with(self.db, self.settings.clone())
|
||||
.fmt_detailed(&mut f)?;
|
||||
}
|
||||
ClassLiteral::Functional(func_class) => {
|
||||
// Functional classes don't have qualified names; just use the name.
|
||||
f.write_str(func_class.name(self.db))?;
|
||||
}
|
||||
ClassLiteral::FunctionalNamedTuple(namedtuple) => {
|
||||
// Functional namedtuples don't have qualified names; just use the name.
|
||||
f.write_str(namedtuple.name(self.db))?;
|
||||
}
|
||||
ClassLiteral::FunctionalDataclass(dataclass) => {
|
||||
// Functional dataclasses don't have qualified names; just use the name.
|
||||
f.write_str(dataclass.name(self.db))?;
|
||||
}
|
||||
}
|
||||
f.write_str("'>")
|
||||
}
|
||||
Type::GenericAlias(generic) => {
|
||||
@@ -713,7 +755,7 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
|
||||
f.write_str("'>")
|
||||
}
|
||||
Type::SubclassOf(subclass_of_ty) => match subclass_of_ty.subclass_of() {
|
||||
SubclassOfInner::Class(ClassType::NonGeneric(class)) => {
|
||||
SubclassOfInner::Class(ClassType::NonGeneric(ClassLiteral::Stmt(class))) => {
|
||||
f.with_type(KnownClass::Type.to_class_literal(self.db))
|
||||
.write_str("type")?;
|
||||
f.write_char('[')?;
|
||||
@@ -722,6 +764,33 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
|
||||
.fmt_detailed(f)?;
|
||||
f.write_char(']')
|
||||
}
|
||||
SubclassOfInner::Class(ClassType::NonGeneric(ClassLiteral::Functional(
|
||||
functional,
|
||||
))) => {
|
||||
f.with_type(KnownClass::Type.to_class_literal(self.db))
|
||||
.write_str("type")?;
|
||||
f.write_char('[')?;
|
||||
f.write_str(functional.name(self.db).as_str())?;
|
||||
f.write_char(']')
|
||||
}
|
||||
SubclassOfInner::Class(ClassType::NonGeneric(
|
||||
ClassLiteral::FunctionalNamedTuple(namedtuple),
|
||||
)) => {
|
||||
f.with_type(KnownClass::Type.to_class_literal(self.db))
|
||||
.write_str("type")?;
|
||||
f.write_char('[')?;
|
||||
f.write_str(namedtuple.name(self.db).as_str())?;
|
||||
f.write_char(']')
|
||||
}
|
||||
SubclassOfInner::Class(ClassType::NonGeneric(
|
||||
ClassLiteral::FunctionalDataclass(dataclass),
|
||||
)) => {
|
||||
f.with_type(KnownClass::Type.to_class_literal(self.db))
|
||||
.write_str("type")?;
|
||||
f.write_char('[')?;
|
||||
f.write_str(dataclass.name(self.db).as_str())?;
|
||||
f.write_char(']')
|
||||
}
|
||||
SubclassOfInner::Class(ClassType::Generic(alias)) => {
|
||||
f.with_type(KnownClass::Type.to_class_literal(self.db))
|
||||
.write_str("type")?;
|
||||
@@ -984,9 +1053,22 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
|
||||
Type::BoundSuper(bound_super) => {
|
||||
f.set_invalid_type_annotation();
|
||||
f.write_str("<super: ")?;
|
||||
Type::from(bound_super.pivot_class(self.db))
|
||||
.display_with(self.db, self.settings.singleline())
|
||||
.fmt_detailed(f)?;
|
||||
// Display functional class pivots as just their name (not type[...]).
|
||||
match bound_super.pivot_class(self.db) {
|
||||
ClassBase::Class(ClassType::NonGeneric(ClassLiteral::Functional(fc))) => {
|
||||
f.with_type(self.ty).write_str(fc.name(self.db))?;
|
||||
}
|
||||
ClassBase::Class(ClassType::NonGeneric(ClassLiteral::FunctionalDataclass(
|
||||
dc,
|
||||
))) => {
|
||||
f.with_type(self.ty).write_str(dc.name(self.db))?;
|
||||
}
|
||||
pivot => {
|
||||
Type::from(pivot)
|
||||
.display_with(self.db, self.settings.singleline())
|
||||
.fmt_detailed(f)?;
|
||||
}
|
||||
}
|
||||
f.write_str(", ")?;
|
||||
Type::from(bound_super.owner(self.db))
|
||||
.display_with(self.db, self.settings.singleline())
|
||||
@@ -998,9 +1080,18 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
|
||||
fmt_type_guard_like(self.db, type_guard, &self.settings, f)
|
||||
}
|
||||
Type::TypedDict(TypedDictType::Class(defining_class)) => match defining_class {
|
||||
ClassType::NonGeneric(class) => class
|
||||
ClassType::NonGeneric(ClassLiteral::Stmt(class)) => class
|
||||
.display_with(self.db, self.settings.clone())
|
||||
.fmt_detailed(f),
|
||||
ClassType::NonGeneric(ClassLiteral::Functional(functional)) => f
|
||||
.with_type(self.ty)
|
||||
.write_str(functional.name(self.db).as_str()),
|
||||
ClassType::NonGeneric(ClassLiteral::FunctionalNamedTuple(namedtuple)) => f
|
||||
.with_type(self.ty)
|
||||
.write_str(namedtuple.name(self.db).as_str()),
|
||||
ClassType::NonGeneric(ClassLiteral::FunctionalDataclass(dataclass)) => f
|
||||
.with_type(self.ty)
|
||||
.write_str(dataclass.name(self.db).as_str()),
|
||||
ClassType::Generic(alias) => alias
|
||||
.display_with(self.db, self.settings.clone())
|
||||
.fmt_detailed(f),
|
||||
@@ -1294,7 +1385,7 @@ impl<'db> GenericAlias<'db> {
|
||||
}
|
||||
|
||||
pub(crate) struct DisplayGenericAlias<'db> {
|
||||
origin: ClassLiteral<'db>,
|
||||
origin: StmtClassLiteral<'db>,
|
||||
specialization: Specialization<'db>,
|
||||
db: &'db dyn Db,
|
||||
settings: DisplaySettings<'db>,
|
||||
@@ -1571,7 +1662,7 @@ impl TupleSpecialization {
|
||||
matches!(self, Self::Yes)
|
||||
}
|
||||
|
||||
fn from_class(db: &dyn Db, class: ClassLiteral) -> Self {
|
||||
fn from_class(db: &dyn Db, class: StmtClassLiteral) -> Self {
|
||||
if class.is_tuple(db) {
|
||||
Self::Yes
|
||||
} else {
|
||||
@@ -2550,6 +2641,64 @@ impl<'db> FmtDetailed<'db> for DisplayKnownInstanceRepr<'db> {
|
||||
f.with_type(ty).write_str(declaration.name(self.db))?;
|
||||
f.write_str("'>")
|
||||
}
|
||||
KnownInstanceType::TypingNamedTupleFieldsSchema(schema) => {
|
||||
f.set_invalid_type_annotation();
|
||||
f.write_str("<namedtuple-fields-schema [")?;
|
||||
let mut first = true;
|
||||
for (name, field_ty) in schema.fields(self.db) {
|
||||
if first {
|
||||
first = false;
|
||||
} else {
|
||||
f.write_str(", ")?;
|
||||
}
|
||||
write!(
|
||||
f.with_type(*field_ty),
|
||||
"({name}, {})",
|
||||
field_ty.display(self.db)
|
||||
)?;
|
||||
}
|
||||
f.write_str("]>")
|
||||
}
|
||||
KnownInstanceType::CollectionsNamedTupleFieldsSchema(schema) => {
|
||||
f.set_invalid_type_annotation();
|
||||
f.write_str("<namedtuple-field-names-schema [")?;
|
||||
let mut first = true;
|
||||
for name in schema.field_names(self.db) {
|
||||
if first {
|
||||
first = false;
|
||||
} else {
|
||||
f.write_str(", ")?;
|
||||
}
|
||||
write!(f, "{name}")?;
|
||||
}
|
||||
f.write_str("]>")
|
||||
}
|
||||
KnownInstanceType::CollectionsNamedTupleDefaultsSchema(schema) => {
|
||||
f.set_invalid_type_annotation();
|
||||
write!(
|
||||
f,
|
||||
"<namedtuple-defaults-schema count={}>",
|
||||
schema.count(self.db)
|
||||
)
|
||||
}
|
||||
KnownInstanceType::MakeDataclassFieldsSchema(schema) => {
|
||||
f.set_invalid_type_annotation();
|
||||
f.write_str("<make-dataclass-fields-schema [")?;
|
||||
let mut first = true;
|
||||
for (name, field_ty, _) in schema.fields(self.db) {
|
||||
if first {
|
||||
first = false;
|
||||
} else {
|
||||
f.write_str(", ")?;
|
||||
}
|
||||
write!(
|
||||
f.with_type(*field_ty),
|
||||
"({name}, {})",
|
||||
field_ty.display(self.db)
|
||||
)?;
|
||||
}
|
||||
f.write_str("]>")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::{
|
||||
semantic_index::{place_table, use_def_map},
|
||||
types::{
|
||||
ClassBase, ClassLiteral, DynamicType, EnumLiteralType, KnownClass, MemberLookupPolicy,
|
||||
Type, TypeQualifiers,
|
||||
StmtClassLiteral, Type, TypeQualifiers,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -40,7 +40,7 @@ impl EnumMetadata<'_> {
|
||||
fn enum_metadata_cycle_initial<'db>(
|
||||
_db: &'db dyn Db,
|
||||
_id: salsa::Id,
|
||||
_class: ClassLiteral<'db>,
|
||||
_class: StmtClassLiteral<'db>,
|
||||
) -> Option<EnumMetadata<'db>> {
|
||||
Some(EnumMetadata::empty())
|
||||
}
|
||||
@@ -50,7 +50,7 @@ fn enum_metadata_cycle_initial<'db>(
|
||||
#[salsa::tracked(returns(as_ref), cycle_initial=enum_metadata_cycle_initial, heap_size=ruff_memory_usage::heap_size)]
|
||||
pub(crate) fn enum_metadata<'db>(
|
||||
db: &'db dyn Db,
|
||||
class: ClassLiteral<'db>,
|
||||
class: StmtClassLiteral<'db>,
|
||||
) -> Option<EnumMetadata<'db>> {
|
||||
// This is a fast path to avoid traversing the MRO of known classes
|
||||
if class
|
||||
@@ -136,7 +136,7 @@ pub(crate) fn enum_metadata<'db>(
|
||||
auto_counter += 1;
|
||||
|
||||
// `StrEnum`s have different `auto()` behaviour to enums inheriting from `(str, Enum)`
|
||||
let auto_value_ty = if Type::ClassLiteral(class)
|
||||
let auto_value_ty = if Type::ClassLiteral(ClassLiteral::Stmt(class))
|
||||
.is_subtype_of(db, KnownClass::StrEnum.to_subclass_of(db))
|
||||
{
|
||||
Type::string_literal(db, &name.to_lowercase())
|
||||
@@ -265,7 +265,7 @@ pub(crate) fn enum_metadata<'db>(
|
||||
|
||||
pub(crate) fn enum_member_literals<'a, 'db: 'a>(
|
||||
db: &'db dyn Db,
|
||||
class: ClassLiteral<'db>,
|
||||
class: StmtClassLiteral<'db>,
|
||||
exclude_member: Option<&'a Name>,
|
||||
) -> Option<impl Iterator<Item = Type<'a>> + 'a> {
|
||||
enum_metadata(db, class).map(|metadata| {
|
||||
@@ -277,13 +277,15 @@ pub(crate) fn enum_member_literals<'a, 'db: 'a>(
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn is_single_member_enum<'db>(db: &'db dyn Db, class: ClassLiteral<'db>) -> bool {
|
||||
pub(crate) fn is_single_member_enum<'db>(db: &'db dyn Db, class: StmtClassLiteral<'db>) -> bool {
|
||||
enum_metadata(db, class).is_some_and(|metadata| metadata.members.len() == 1)
|
||||
}
|
||||
|
||||
pub(crate) fn is_enum_class<'db>(db: &'db dyn Db, ty: Type<'db>) -> bool {
|
||||
match ty {
|
||||
Type::ClassLiteral(class_literal) => enum_metadata(db, class_literal).is_some(),
|
||||
Type::ClassLiteral(class_literal) => class_literal
|
||||
.as_stmt()
|
||||
.is_some_and(|stmt| enum_metadata(db, stmt).is_some()),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@@ -293,8 +295,12 @@ pub(crate) fn is_enum_class<'db>(db: &'db dyn Db, ty: Type<'db>) -> bool {
|
||||
///
|
||||
/// This is a lighter-weight check than `enum_metadata`, which additionally
|
||||
/// verifies that the class has members.
|
||||
pub(crate) fn is_enum_class_by_inheritance<'db>(db: &'db dyn Db, class: ClassLiteral<'db>) -> bool {
|
||||
Type::ClassLiteral(class).is_subtype_of(db, KnownClass::Enum.to_subclass_of(db))
|
||||
pub(crate) fn is_enum_class_by_inheritance<'db>(
|
||||
db: &'db dyn Db,
|
||||
class: StmtClassLiteral<'db>,
|
||||
) -> bool {
|
||||
Type::ClassLiteral(ClassLiteral::Stmt(class))
|
||||
.is_subtype_of(db, KnownClass::Enum.to_subclass_of(db))
|
||||
|| class
|
||||
.metaclass(db)
|
||||
.is_subtype_of(db, KnownClass::EnumType.to_subclass_of(db))
|
||||
|
||||
@@ -1221,10 +1221,7 @@ fn is_instance_truthiness<'db>(
|
||||
.class(db)
|
||||
.iter_mro(db)
|
||||
.filter_map(ClassBase::into_class)
|
||||
.any(|c| match c {
|
||||
ClassType::Generic(c) => c.origin(db) == class,
|
||||
ClassType::NonGeneric(c) => c == class,
|
||||
})
|
||||
.any(|c| c.class_literal(db) == class)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -1410,6 +1407,9 @@ pub enum KnownFunction {
|
||||
Dataclass,
|
||||
/// `dataclasses.field`
|
||||
Field,
|
||||
/// `dataclasses.make_dataclass`
|
||||
#[strum(serialize = "make_dataclass")]
|
||||
MakeDataclass,
|
||||
|
||||
/// `inspect.getattr_static`
|
||||
GetattrStatic,
|
||||
@@ -1496,7 +1496,7 @@ impl KnownFunction {
|
||||
Self::AsyncContextManager => {
|
||||
matches!(module, KnownModule::Contextlib)
|
||||
}
|
||||
Self::Dataclass | Self::Field => {
|
||||
Self::Dataclass | Self::Field | Self::MakeDataclass => {
|
||||
matches!(module, KnownModule::Dataclasses)
|
||||
}
|
||||
Self::GetattrStatic => module.is_inspect(),
|
||||
@@ -1729,7 +1729,13 @@ impl KnownFunction {
|
||||
if class.is_protocol(db) {
|
||||
return;
|
||||
}
|
||||
report_bad_argument_to_get_protocol_members(context, call_expression, *class);
|
||||
if let Some(stmt_class) = class.as_stmt() {
|
||||
report_bad_argument_to_get_protocol_members(
|
||||
context,
|
||||
call_expression,
|
||||
stmt_class,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
KnownFunction::RevealProtocolInterface => {
|
||||
@@ -1857,9 +1863,16 @@ impl KnownFunction {
|
||||
}
|
||||
|
||||
if self == KnownFunction::IsInstance {
|
||||
overload.set_return_type(
|
||||
is_instance_truthiness(db, *first_arg, *class).into_type(db),
|
||||
);
|
||||
if let Some(stmt_class) = class.as_stmt() {
|
||||
overload.set_return_type(
|
||||
is_instance_truthiness(
|
||||
db,
|
||||
*first_arg,
|
||||
ClassLiteral::Stmt(stmt_class),
|
||||
)
|
||||
.into_type(db),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
// The special-casing here is necessary because we recognise the symbol `typing.Any` as an
|
||||
@@ -2028,7 +2041,9 @@ pub(crate) mod tests {
|
||||
|
||||
KnownFunction::AsyncContextManager => KnownModule::Contextlib,
|
||||
|
||||
KnownFunction::Dataclass | KnownFunction::Field => KnownModule::Dataclasses,
|
||||
KnownFunction::Dataclass | KnownFunction::Field | KnownFunction::MakeDataclass => {
|
||||
KnownModule::Dataclasses
|
||||
}
|
||||
|
||||
KnownFunction::GetattrStatic => KnownModule::Inspect,
|
||||
|
||||
|
||||
@@ -20,9 +20,9 @@ use crate::types::variance::VarianceInferable;
|
||||
use crate::types::visitor::{TypeCollector, TypeVisitor, walk_type_with_recursion_guard};
|
||||
use crate::types::{
|
||||
ApplyTypeMappingVisitor, BindingContext, BoundTypeVarIdentity, BoundTypeVarInstance,
|
||||
ClassLiteral, FindLegacyTypeVarsVisitor, HasRelationToVisitor, IntersectionType,
|
||||
IsDisjointVisitor, IsEquivalentVisitor, KnownClass, KnownInstanceType, MaterializationKind,
|
||||
NormalizedVisitor, Type, TypeContext, TypeMapping, TypeRelation, TypeVarBoundOrConstraints,
|
||||
FindLegacyTypeVarsVisitor, HasRelationToVisitor, IntersectionType, IsDisjointVisitor,
|
||||
IsEquivalentVisitor, KnownClass, KnownInstanceType, MaterializationKind, NormalizedVisitor,
|
||||
StmtClassLiteral, Type, TypeContext, TypeMapping, TypeRelation, TypeVarBoundOrConstraints,
|
||||
TypeVarIdentity, TypeVarInstance, TypeVarKind, TypeVarVariance, UnionType, declaration_type,
|
||||
walk_type_var_bounds,
|
||||
};
|
||||
@@ -86,7 +86,7 @@ pub(crate) fn typing_self<'db>(
|
||||
db: &'db dyn Db,
|
||||
function_scope_id: ScopeId,
|
||||
typevar_binding_context: Option<Definition<'db>>,
|
||||
class: ClassLiteral<'db>,
|
||||
class: StmtClassLiteral<'db>,
|
||||
) -> Option<BoundTypeVarInstance<'db>> {
|
||||
let index = semantic_index(db, function_scope_id.file(db));
|
||||
|
||||
|
||||
@@ -8,7 +8,8 @@ use crate::semantic_index::{attribute_scopes, global_scope, semantic_index, use_
|
||||
use crate::types::call::{CallArguments, MatchedArgument};
|
||||
use crate::types::signatures::{ParameterKind, Signature};
|
||||
use crate::types::{
|
||||
CallDunderError, CallableTypes, ClassBase, KnownUnion, Type, TypeContext, UnionType,
|
||||
CallDunderError, CallableTypes, ClassBase, ClassLiteral, ClassType, KnownUnion, Type,
|
||||
TypeContext, UnionType,
|
||||
};
|
||||
use crate::{Db, DisplaySettings, HasType, SemanticModel};
|
||||
use ruff_db::files::FileRange;
|
||||
@@ -168,9 +169,9 @@ pub fn definitions_for_name<'db>(
|
||||
// instead of `int` (hover only shows the docstring of the first definition).
|
||||
.rev()
|
||||
.filter_map(|ty| ty.as_nominal_instance())
|
||||
.map(|instance| {
|
||||
let definition = instance.class_literal(db).definition(db);
|
||||
ResolvedDefinition::Definition(definition)
|
||||
.filter_map(|instance| {
|
||||
let definition = instance.class_literal(db)?.definition(db);
|
||||
Some(ResolvedDefinition::Definition(definition))
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
@@ -266,7 +267,10 @@ pub fn definitions_for_attribute<'db>(
|
||||
let class_literal = match meta_type {
|
||||
Type::ClassLiteral(class_literal) => class_literal,
|
||||
Type::SubclassOf(subclass) => match subclass.subclass_of().into_class(db) {
|
||||
Some(cls) => cls.class_literal(db).0,
|
||||
Some(cls) => match cls.stmt_class_literal(db) {
|
||||
Some((lit, _)) => ClassLiteral::Stmt(lit),
|
||||
None => continue,
|
||||
},
|
||||
None => continue,
|
||||
},
|
||||
_ => continue,
|
||||
@@ -274,9 +278,9 @@ pub fn definitions_for_attribute<'db>(
|
||||
|
||||
// Walk the MRO: include class and its ancestors, but stop when we find a match
|
||||
'scopes: for ancestor in class_literal
|
||||
.iter_mro(db, None)
|
||||
.iter_mro(db)
|
||||
.filter_map(ClassBase::into_class)
|
||||
.map(|cls| cls.class_literal(db).0)
|
||||
.filter_map(|cls: ClassType<'db>| cls.stmt_class_literal(db).map(|(lit, _)| lit))
|
||||
{
|
||||
let class_scope = ancestor.body_scope(db);
|
||||
let class_place_table = crate::semantic_index::place_table(db, class_scope);
|
||||
|
||||
@@ -53,7 +53,8 @@ use crate::types::function::FunctionType;
|
||||
use crate::types::generics::Specialization;
|
||||
use crate::types::unpacker::{UnpackResult, Unpacker};
|
||||
use crate::types::{
|
||||
ClassLiteral, KnownClass, Truthiness, Type, TypeAndQualifiers, declaration_type,
|
||||
ClassLiteral, KnownClass, StmtClassLiteral, Truthiness, Type, TypeAndQualifiers,
|
||||
declaration_type,
|
||||
};
|
||||
use crate::unpack::Unpack;
|
||||
use builder::TypeInferenceBuilder;
|
||||
@@ -465,7 +466,7 @@ pub(crate) fn nearest_enclosing_class<'db>(
|
||||
db: &'db dyn Db,
|
||||
semantic: &SemanticIndex<'db>,
|
||||
scope: ScopeId,
|
||||
) -> Option<ClassLiteral<'db>> {
|
||||
) -> Option<StmtClassLiteral<'db>> {
|
||||
semantic
|
||||
.ancestor_scopes(scope.file_scope_id(db))
|
||||
.find_map(|(_, ancestor_scope)| {
|
||||
@@ -474,6 +475,7 @@ pub(crate) fn nearest_enclosing_class<'db>(
|
||||
declaration_type(db, definition)
|
||||
.inner_type()
|
||||
.as_class_literal()
|
||||
.and_then(ClassLiteral::as_stmt)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,9 @@ use crate::semantic_index::{
|
||||
use crate::subscript::{PyIndex, PySlice};
|
||||
use crate::types::call::bind::{CallableDescription, MatchingOverloadIndex};
|
||||
use crate::types::call::{Binding, Bindings, CallArguments, CallError, CallErrorKind};
|
||||
use crate::types::class::{CodeGeneratorKind, FieldKind, MetaclassErrorKind, MethodDecorator};
|
||||
use crate::types::class::{
|
||||
ClassLiteral, CodeGeneratorKind, FieldKind, MetaclassErrorKind, MethodDecorator,
|
||||
};
|
||||
use crate::types::context::{InNoTypeCheck, InferContext};
|
||||
use crate::types::cyclic::CycleDetector;
|
||||
use crate::types::diagnostic::{
|
||||
@@ -106,15 +108,16 @@ use crate::types::typed_dict::{
|
||||
use crate::types::visitor::any_over_type;
|
||||
use crate::types::{
|
||||
BoundTypeVarIdentity, BoundTypeVarInstance, CallDunderError, CallableBinding, CallableType,
|
||||
CallableTypeKind, ClassLiteral, ClassType, DataclassParams, DynamicType, InternedType,
|
||||
CallableTypeKind, ClassType, CollectionsNamedTupleDefaultsSchema,
|
||||
CollectionsNamedTupleFieldsSchema, DataclassParams, DynamicType, InternedType,
|
||||
IntersectionBuilder, IntersectionType, KnownClass, KnownInstanceType, KnownUnion,
|
||||
LintDiagnosticGuard, MemberLookupPolicy, MetaclassCandidate, PEP695TypeAliasType,
|
||||
ParamSpecAttrKind, Parameter, ParameterForm, Parameters, Signature, SpecialFormType,
|
||||
SubclassOfType, TrackedConstraintSet, Truthiness, Type, TypeAliasType, TypeAndQualifiers,
|
||||
TypeContext, TypeQualifiers, TypeVarBoundOrConstraints, TypeVarBoundOrConstraintsEvaluation,
|
||||
TypeVarDefaultEvaluation, TypeVarIdentity, TypeVarInstance, TypeVarKind, TypeVarVariance,
|
||||
TypedDictType, UnionBuilder, UnionType, UnionTypeInstance, binding_type, infer_scope_types,
|
||||
todo_type,
|
||||
LintDiagnosticGuard, MakeDataclassFieldsSchema, MemberLookupPolicy, MetaclassCandidate,
|
||||
PEP695TypeAliasType, ParamSpecAttrKind, Parameter, ParameterForm, Parameters, Signature,
|
||||
SpecialFormType, StmtClassLiteral, SubclassOfType, TrackedConstraintSet, Truthiness, Type,
|
||||
TypeAliasType, TypeAndQualifiers, TypeContext, TypeQualifiers, TypeVarBoundOrConstraints,
|
||||
TypeVarBoundOrConstraintsEvaluation, TypeVarDefaultEvaluation, TypeVarIdentity,
|
||||
TypeVarInstance, TypeVarKind, TypeVarVariance, TypedDictType, TypingNamedTupleFieldsSchema,
|
||||
UnionBuilder, UnionType, UnionTypeInstance, binding_type, infer_scope_types, todo_type,
|
||||
};
|
||||
use crate::types::{CallableTypes, overrides};
|
||||
use crate::types::{ClassBase, add_inferred_python_version_hint_to_diagnostic};
|
||||
@@ -598,6 +601,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
if let DefinitionKind::Class(class) = definition.kind(self.db()) {
|
||||
ty.inner_type()
|
||||
.as_class_literal()
|
||||
.and_then(ClassLiteral::as_stmt)
|
||||
.map(|class_literal| (class_literal, class.node(self.module())))
|
||||
} else {
|
||||
None
|
||||
@@ -728,7 +732,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
};
|
||||
|
||||
if let Some(disjoint_base) = base_class.nearest_disjoint_base(self.db()) {
|
||||
disjoint_bases.insert(disjoint_base, i, base_class.class_literal(self.db()).0);
|
||||
disjoint_bases.insert(disjoint_base, i, base_class.class_literal(self.db()));
|
||||
}
|
||||
|
||||
if is_protocol
|
||||
@@ -759,24 +763,24 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
}
|
||||
}
|
||||
|
||||
let (base_class_literal, _) = base_class.class_literal(self.db());
|
||||
if let Some((base_class_literal, _)) = base_class.stmt_class_literal(self.db()) {
|
||||
if let (Some(base_params), Some(class_params)) = (
|
||||
base_class_literal.dataclass_params(self.db()),
|
||||
class.dataclass_params(self.db()),
|
||||
) {
|
||||
let base_params = base_params.flags(self.db());
|
||||
let class_is_frozen = class_params.flags(self.db()).is_frozen();
|
||||
|
||||
if let (Some(base_params), Some(class_params)) = (
|
||||
base_class_literal.dataclass_params(self.db()),
|
||||
class.dataclass_params(self.db()),
|
||||
) {
|
||||
let base_params = base_params.flags(self.db());
|
||||
let class_is_frozen = class_params.flags(self.db()).is_frozen();
|
||||
|
||||
if base_params.is_frozen() != class_is_frozen {
|
||||
report_bad_frozen_dataclass_inheritance(
|
||||
&self.context,
|
||||
class,
|
||||
class_node,
|
||||
base_class_literal,
|
||||
&class_node.bases()[i],
|
||||
base_params,
|
||||
);
|
||||
if base_params.is_frozen() != class_is_frozen {
|
||||
report_bad_frozen_dataclass_inheritance(
|
||||
&self.context,
|
||||
class,
|
||||
class_node,
|
||||
base_class_literal,
|
||||
&class_node.bases()[i],
|
||||
base_params,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1167,7 +1171,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
.expect_class_literal();
|
||||
|
||||
if class.is_protocol(self.db())
|
||||
|| (Type::ClassLiteral(class)
|
||||
|| (Type::ClassLiteral(ClassLiteral::Stmt(class))
|
||||
.is_subtype_of(self.db(), KnownClass::ABCMeta.to_instance(self.db()))
|
||||
&& overloads.iter().all(|overload| {
|
||||
overload.has_known_decorator(
|
||||
@@ -2696,7 +2700,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
let class_literal = infer_definition_types(db, class_definition)
|
||||
.declaration_type(class_definition)
|
||||
.inner_type()
|
||||
.as_class_literal()?;
|
||||
.as_class_literal()?
|
||||
.as_stmt()?;
|
||||
|
||||
let typing_self = typing_self(db, self.scope(), Some(method_definition), class_literal);
|
||||
if is_classmethod {
|
||||
@@ -2873,7 +2878,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
Type::SpecialForm(SpecialFormType::NamedTuple)
|
||||
}
|
||||
(None, "Any") if in_typing_module() => Type::SpecialForm(SpecialFormType::Any),
|
||||
_ => Type::from(ClassLiteral::new(
|
||||
_ => Type::from(StmtClassLiteral::new(
|
||||
self.db(),
|
||||
name.id.clone(),
|
||||
body_scope,
|
||||
@@ -4168,11 +4173,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
// If it's a user-defined class, suggest adding a `__setitem__` method.
|
||||
if object_ty
|
||||
.as_nominal_instance()
|
||||
.and_then(|instance| {
|
||||
file_to_module(
|
||||
db,
|
||||
instance.class(db).class_literal(db).0.file(db),
|
||||
)
|
||||
.and_then(|instance| instance.class(db).stmt_class_literal(db))
|
||||
.and_then(|(class_literal, _)| {
|
||||
file_to_module(db, class_literal.file(db))
|
||||
})
|
||||
.and_then(|module| module.search_path(db))
|
||||
.is_some_and(ty_module_resolver::SearchPath::is_first_party)
|
||||
@@ -4509,8 +4512,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
}
|
||||
|
||||
// Check if class-level attribute already has a value
|
||||
{
|
||||
let class_definition = class_ty.class_literal(db).0;
|
||||
if let Some((class_definition, _)) = class_ty.stmt_class_literal(db) {
|
||||
let class_scope_id = class_definition.body_scope(db).file_scope_id(db);
|
||||
let place_table = builder.index.place_table(class_scope_id);
|
||||
|
||||
@@ -5785,6 +5787,122 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
)))
|
||||
}
|
||||
|
||||
/// Extract fields from a list or tuple literal for `typing.NamedTuple`.
|
||||
fn infer_typing_namedtuple_fields_schema(
|
||||
&mut self,
|
||||
field_elts: &[ast::Expr],
|
||||
) -> Option<TypingNamedTupleFieldsSchema<'db>> {
|
||||
let db = self.db();
|
||||
let mut fields: Vec<(ast::name::Name, Type<'db>)> = Vec::with_capacity(field_elts.len());
|
||||
|
||||
for elt in field_elts {
|
||||
// Each element should be a tuple like ("field_name", type).
|
||||
let ast::Expr::Tuple(tuple_expr) = elt else {
|
||||
return None;
|
||||
};
|
||||
|
||||
if tuple_expr.elts.len() != 2 {
|
||||
return None;
|
||||
}
|
||||
|
||||
// First element: field name (string literal).
|
||||
let field_name_expr = &tuple_expr.elts[0];
|
||||
let field_name_ty = self.infer_expression(field_name_expr, TypeContext::default());
|
||||
let field_name_lit = field_name_ty.as_string_literal()?;
|
||||
let field_name = ast::name::Name::new(field_name_lit.value(db));
|
||||
|
||||
// Second element: field type.
|
||||
let field_type_expr = &tuple_expr.elts[1];
|
||||
let field_type_ty = self.infer_type_expression(field_type_expr);
|
||||
|
||||
fields.push((field_name, field_type_ty));
|
||||
}
|
||||
|
||||
Some(TypingNamedTupleFieldsSchema::new(
|
||||
db,
|
||||
fields.into_boxed_slice(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Extract field names from a list or tuple literal for `collections.namedtuple`.
|
||||
fn infer_collections_namedtuple_fields_schema(
|
||||
&mut self,
|
||||
field_elts: &[ast::Expr],
|
||||
) -> Option<CollectionsNamedTupleFieldsSchema<'db>> {
|
||||
let db = self.db();
|
||||
let mut field_names: Vec<ast::name::Name> = Vec::with_capacity(field_elts.len());
|
||||
|
||||
for elt in field_elts {
|
||||
// Each element should be a string literal (field name).
|
||||
let field_ty = self.infer_expression(elt, TypeContext::default());
|
||||
let field_lit = field_ty.as_string_literal()?;
|
||||
field_names.push(ast::name::Name::new(field_lit.value(db)));
|
||||
}
|
||||
|
||||
Some(CollectionsNamedTupleFieldsSchema::new(
|
||||
db,
|
||||
field_names.into_boxed_slice(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Extract fields from a list or tuple literal for `dataclasses.make_dataclass`.
|
||||
///
|
||||
/// Each element can be:
|
||||
/// - A string literal (field name only, type is Any)
|
||||
/// - A 2-tuple of (name, type)
|
||||
/// - A 3-tuple of (name, type, field)
|
||||
fn infer_make_dataclass_fields_schema(
|
||||
&mut self,
|
||||
field_elts: &[ast::Expr],
|
||||
) -> Option<MakeDataclassFieldsSchema<'db>> {
|
||||
let db = self.db();
|
||||
let mut fields: Vec<(ast::name::Name, Type<'db>, Option<Type<'db>>)> =
|
||||
Vec::with_capacity(field_elts.len());
|
||||
|
||||
for elt in field_elts {
|
||||
// Handle string literal (field name only, type is Any).
|
||||
if let ast::Expr::StringLiteral(string_lit) = elt {
|
||||
let field_name = ast::name::Name::new(string_lit.value.to_str());
|
||||
fields.push((field_name, Type::any(), None));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle tuple: (name, type) or (name, type, field).
|
||||
let ast::Expr::Tuple(tuple_expr) = elt else {
|
||||
return None;
|
||||
};
|
||||
|
||||
if tuple_expr.elts.len() < 2 {
|
||||
return None;
|
||||
}
|
||||
|
||||
// First element: field name (string literal).
|
||||
let field_name_expr = &tuple_expr.elts[0];
|
||||
let field_name_ty = self.infer_expression(field_name_expr, TypeContext::default());
|
||||
let field_name_lit = field_name_ty.as_string_literal()?;
|
||||
let field_name = ast::name::Name::new(field_name_lit.value(db));
|
||||
|
||||
// Second element: field type annotation.
|
||||
let field_type_expr = &tuple_expr.elts[1];
|
||||
let field_type_ty = self.infer_type_expression(field_type_expr);
|
||||
|
||||
// Third element (optional): default value or field() specification.
|
||||
let default_ty = if tuple_expr.elts.len() >= 3 {
|
||||
let default_expr = &tuple_expr.elts[2];
|
||||
Some(self.infer_expression(default_expr, TypeContext::default()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
fields.push((field_name, field_type_ty, default_ty));
|
||||
}
|
||||
|
||||
Some(MakeDataclassFieldsSchema::new(
|
||||
db,
|
||||
fields.into_boxed_slice(),
|
||||
))
|
||||
}
|
||||
|
||||
fn infer_assignment_deferred(&mut self, value: &ast::Expr) {
|
||||
// Infer deferred bounds/constraints/defaults of a legacy TypeVar / ParamSpec / NewType.
|
||||
let ast::Expr::Call(ast::ExprCall {
|
||||
@@ -5999,7 +6117,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
let class_literal = infer_definition_types(db, class_definition)
|
||||
.declaration_type(class_definition)
|
||||
.inner_type()
|
||||
.as_class_literal()?;
|
||||
.as_class_literal()?
|
||||
.as_stmt()?;
|
||||
|
||||
class_literal
|
||||
.dataclass_params(db)
|
||||
@@ -7786,6 +7905,47 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
parenthesized: _,
|
||||
} = tuple;
|
||||
|
||||
// Extract fields for typing.NamedTuple.
|
||||
if let Some(Type::SpecialForm(SpecialFormType::TypingNamedTupleFieldsSchema)) =
|
||||
tcx.annotation
|
||||
{
|
||||
if let Some(schema) = self.infer_typing_namedtuple_fields_schema(elts) {
|
||||
return Type::KnownInstance(KnownInstanceType::TypingNamedTupleFieldsSchema(
|
||||
schema,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(Type::SpecialForm(SpecialFormType::CollectionsNamedTupleFieldsSchema)) =
|
||||
tcx.annotation
|
||||
{
|
||||
if let Some(schema) = self.infer_collections_namedtuple_fields_schema(elts) {
|
||||
return Type::KnownInstance(KnownInstanceType::CollectionsNamedTupleFieldsSchema(
|
||||
schema,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Extract defaults count for collections.namedtuple.
|
||||
if let Some(Type::SpecialForm(SpecialFormType::CollectionsNamedTupleDefaultsSchema)) =
|
||||
tcx.annotation
|
||||
{
|
||||
for elt in elts {
|
||||
self.infer_expression(elt, TypeContext::default());
|
||||
}
|
||||
let schema = CollectionsNamedTupleDefaultsSchema::new(self.db(), elts.len());
|
||||
return Type::KnownInstance(KnownInstanceType::CollectionsNamedTupleDefaultsSchema(
|
||||
schema,
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(Type::SpecialForm(SpecialFormType::MakeDataclassFieldsSchema)) = tcx.annotation
|
||||
{
|
||||
if let Some(schema) = self.infer_make_dataclass_fields_schema(elts) {
|
||||
return Type::KnownInstance(KnownInstanceType::MakeDataclassFieldsSchema(schema));
|
||||
}
|
||||
}
|
||||
|
||||
// Remove any union elements of that are unrelated to the tuple type.
|
||||
let tcx = tcx.map(|annotation| {
|
||||
let inferable = KnownClass::Tuple
|
||||
@@ -7834,6 +7994,47 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
ctx: _,
|
||||
} = list;
|
||||
|
||||
// Extract fields for typing.NamedTuple.
|
||||
if let Some(Type::SpecialForm(SpecialFormType::TypingNamedTupleFieldsSchema)) =
|
||||
tcx.annotation
|
||||
{
|
||||
if let Some(schema) = self.infer_typing_namedtuple_fields_schema(elts) {
|
||||
return Type::KnownInstance(KnownInstanceType::TypingNamedTupleFieldsSchema(
|
||||
schema,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(Type::SpecialForm(SpecialFormType::CollectionsNamedTupleFieldsSchema)) =
|
||||
tcx.annotation
|
||||
{
|
||||
if let Some(schema) = self.infer_collections_namedtuple_fields_schema(elts) {
|
||||
return Type::KnownInstance(KnownInstanceType::CollectionsNamedTupleFieldsSchema(
|
||||
schema,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Extract defaults count for collections.namedtuple.
|
||||
if let Some(Type::SpecialForm(SpecialFormType::CollectionsNamedTupleDefaultsSchema)) =
|
||||
tcx.annotation
|
||||
{
|
||||
for elt in elts {
|
||||
self.infer_expression(elt, TypeContext::default());
|
||||
}
|
||||
let schema = CollectionsNamedTupleDefaultsSchema::new(self.db(), elts.len());
|
||||
return Type::KnownInstance(KnownInstanceType::CollectionsNamedTupleDefaultsSchema(
|
||||
schema,
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(Type::SpecialForm(SpecialFormType::MakeDataclassFieldsSchema)) = tcx.annotation
|
||||
{
|
||||
if let Some(schema) = self.infer_make_dataclass_fields_schema(elts) {
|
||||
return Type::KnownInstance(KnownInstanceType::MakeDataclassFieldsSchema(schema));
|
||||
}
|
||||
}
|
||||
|
||||
let elts = elts.iter().map(|elt| [Some(elt)]);
|
||||
let infer_elt_ty = |builder: &mut Self, elt, tcx| builder.infer_expression(elt, tcx);
|
||||
self.infer_collection_literal(elts, tcx, infer_elt_ty, KnownClass::List)
|
||||
@@ -8808,11 +9009,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
// are handled by the default constructor-call logic (we synthesize a `__new__` method for them
|
||||
// in `ClassType::own_class_member()`).
|
||||
class.is_known(self.db(), KnownClass::Tuple) && !class.is_generic()
|
||||
) || CodeGeneratorKind::TypedDict.matches(
|
||||
self.db(),
|
||||
class.class_literal(self.db()).0,
|
||||
class.class_literal(self.db()).1,
|
||||
);
|
||||
) || class
|
||||
.stmt_class_literal(self.db())
|
||||
.is_some_and(|(class_literal, specialization)| {
|
||||
CodeGeneratorKind::TypedDict.matches(self.db(), class_literal, specialization)
|
||||
});
|
||||
|
||||
// temporary special-casing for all subclasses of `enum.Enum`
|
||||
// until we support the functional syntax for creating enum classes
|
||||
@@ -11652,12 +11853,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
}
|
||||
|
||||
if let Some(generic_context) = class.generic_context(self.db()) {
|
||||
return self.infer_explicit_class_specialization(
|
||||
subscript,
|
||||
value_ty,
|
||||
class,
|
||||
generic_context,
|
||||
);
|
||||
if let Some(stmt_class) = class.as_stmt() {
|
||||
return self.infer_explicit_class_specialization(
|
||||
subscript,
|
||||
value_ty,
|
||||
stmt_class,
|
||||
generic_context,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Type::KnownInstance(KnownInstanceType::TypeAliasType(TypeAliasType::ManualPEP695(
|
||||
@@ -11988,7 +12191,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
&mut self,
|
||||
subscript: &ast::ExprSubscript,
|
||||
value_ty: Type<'db>,
|
||||
generic_class: ClassLiteral<'db>,
|
||||
generic_class: StmtClassLiteral<'db>,
|
||||
generic_context: GenericContext<'db>,
|
||||
) -> Type<'db> {
|
||||
let db = self.db();
|
||||
@@ -12702,7 +12905,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
// TODO: properly handle old-style generics; get rid of this temporary hack
|
||||
if !value_ty
|
||||
.as_class_literal()
|
||||
.is_some_and(|class| class.iter_mro(db, None).contains(&ClassBase::Generic))
|
||||
.is_some_and(|class| class.iter_mro(db).contains(&ClassBase::Generic))
|
||||
{
|
||||
report_not_subscriptable(context, subscript, value_ty, "__class_getitem__");
|
||||
}
|
||||
|
||||
@@ -1033,6 +1033,14 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
||||
}
|
||||
Type::unknown()
|
||||
}
|
||||
// Internal types, should never appear in user code.
|
||||
KnownInstanceType::TypingNamedTupleFieldsSchema(_)
|
||||
| KnownInstanceType::CollectionsNamedTupleFieldsSchema(_)
|
||||
| KnownInstanceType::CollectionsNamedTupleDefaultsSchema(_)
|
||||
| KnownInstanceType::MakeDataclassFieldsSchema(_) => {
|
||||
self.infer_type_expression(&subscript.slice);
|
||||
Type::unknown()
|
||||
}
|
||||
},
|
||||
Type::Dynamic(DynamicType::UnknownGeneric(_)) => {
|
||||
self.infer_explicit_type_alias_specialization(subscript, value_ty, true)
|
||||
@@ -1045,12 +1053,12 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
||||
value_ty
|
||||
}
|
||||
Type::ClassLiteral(class) => {
|
||||
match class.generic_context(self.db()) {
|
||||
Some(generic_context) => {
|
||||
match (class.generic_context(self.db()), class.as_stmt()) {
|
||||
(Some(generic_context), Some(stmt_class)) => {
|
||||
let specialized_class = self.infer_explicit_class_specialization(
|
||||
subscript,
|
||||
value_ty,
|
||||
class,
|
||||
stmt_class,
|
||||
generic_context,
|
||||
);
|
||||
|
||||
@@ -1062,7 +1070,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
||||
)
|
||||
.unwrap_or(Type::unknown())
|
||||
}
|
||||
None => {
|
||||
_ => {
|
||||
// TODO: emit a diagnostic if you try to specialize a non-generic class.
|
||||
self.infer_type_expression(slice);
|
||||
todo_type!("specialized non-generic class")
|
||||
@@ -1588,7 +1596,11 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
||||
| SpecialFormType::TypedDict
|
||||
| SpecialFormType::Unknown
|
||||
| SpecialFormType::Any
|
||||
| SpecialFormType::NamedTuple => {
|
||||
| SpecialFormType::NamedTuple
|
||||
| SpecialFormType::TypingNamedTupleFieldsSchema
|
||||
| SpecialFormType::CollectionsNamedTupleFieldsSchema
|
||||
| SpecialFormType::CollectionsNamedTupleDefaultsSchema
|
||||
| SpecialFormType::MakeDataclassFieldsSchema => {
|
||||
self.infer_type_expression(arguments_slice);
|
||||
|
||||
if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) {
|
||||
|
||||
@@ -13,8 +13,8 @@ use crate::types::generics::{InferableTypeVars, walk_specialization};
|
||||
use crate::types::protocol_class::{ProtocolClass, walk_protocol_interface};
|
||||
use crate::types::tuple::{TupleSpec, TupleType, walk_tuple_type};
|
||||
use crate::types::{
|
||||
ApplyTypeMappingVisitor, ClassBase, ClassLiteral, FindLegacyTypeVarsVisitor,
|
||||
HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, NormalizedVisitor, TypeContext,
|
||||
ApplyTypeMappingVisitor, ClassBase, FindLegacyTypeVarsVisitor, HasRelationToVisitor,
|
||||
IsDisjointVisitor, IsEquivalentVisitor, NormalizedVisitor, StmtClassLiteral, TypeContext,
|
||||
TypeMapping, TypeRelation, VarianceInferable,
|
||||
};
|
||||
use crate::{Db, FxOrderSet};
|
||||
@@ -34,7 +34,12 @@ impl<'db> Type<'db> {
|
||||
}
|
||||
|
||||
pub(crate) fn instance(db: &'db dyn Db, class: ClassType<'db>) -> Self {
|
||||
let (class_literal, specialization) = class.class_literal(db);
|
||||
let Some((class_literal, specialization)) = class.stmt_class_literal(db) else {
|
||||
// Functional classes don't have a class literal; just return a nominal instance.
|
||||
return Type::NominalInstance(NominalInstanceType(NominalInstanceInner::NonTuple(
|
||||
class,
|
||||
)));
|
||||
};
|
||||
match class_literal.known(db) {
|
||||
Some(KnownClass::Tuple) => Type::tuple(TupleType::new(
|
||||
db,
|
||||
@@ -227,18 +232,21 @@ impl<'db> NominalInstanceType<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn class_literal(&self, db: &'db dyn Db) -> ClassLiteral<'db> {
|
||||
/// Returns the statement-defined class literal for this instance, if there is one.
|
||||
pub(super) fn class_literal(&self, db: &'db dyn Db) -> Option<StmtClassLiteral<'db>> {
|
||||
let class = match self.0 {
|
||||
NominalInstanceInner::ExactTuple(tuple) => tuple.to_class_type(db),
|
||||
NominalInstanceInner::NonTuple(class) => class,
|
||||
NominalInstanceInner::Object => {
|
||||
return KnownClass::Object
|
||||
.try_to_class_literal(db)
|
||||
.expect("Typeshed should always have a `object` class in `builtins.pyi`");
|
||||
return Some(
|
||||
KnownClass::Object
|
||||
.try_to_class_literal(db)
|
||||
.expect("Typeshed should always have a `object` class in `builtins.pyi`"),
|
||||
);
|
||||
}
|
||||
};
|
||||
let (class_literal, _) = class.class_literal(db);
|
||||
class_literal
|
||||
let (class_literal, _) = class.stmt_class_literal(db)?;
|
||||
Some(class_literal)
|
||||
}
|
||||
|
||||
/// Returns the [`KnownClass`] that this is a nominal instance of, or `None` if it is not an
|
||||
@@ -277,7 +285,7 @@ impl<'db> NominalInstanceType<'db> {
|
||||
.find_map(|class| match class.known(db)? {
|
||||
// N.B. this is a pure optimisation: iterating through the MRO would give us
|
||||
// the correct tuple spec for `sys._version_info`, since we special-case the class
|
||||
// in `ClassLiteral::explicit_bases()` so that it is inferred as inheriting from
|
||||
// in `StmtClassLiteral::explicit_bases()` so that it is inferred as inheriting from
|
||||
// a tuple type with the correct spec for the user's configured Python version and platform.
|
||||
KnownClass::VersionInfo => {
|
||||
Some(Cow::Owned(TupleSpec::version_info_spec(db)))
|
||||
@@ -339,10 +347,9 @@ impl<'db> NominalInstanceType<'db> {
|
||||
NominalInstanceInner::ExactTuple(_) | NominalInstanceInner::Object => return None,
|
||||
NominalInstanceInner::NonTuple(class) => class,
|
||||
};
|
||||
let (class, Some(specialization)) = class.class_literal(db) else {
|
||||
return None;
|
||||
};
|
||||
if !class.is_known(db, KnownClass::Slice) {
|
||||
let (class_literal, specialization) = class.stmt_class_literal(db)?;
|
||||
let specialization = specialization?;
|
||||
if !class_literal.is_known(db, KnownClass::Slice) {
|
||||
return None;
|
||||
}
|
||||
let [start, stop, step] = specialization.types(db) else {
|
||||
@@ -482,8 +489,13 @@ impl<'db> NominalInstanceType<'db> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.or(db, || {
|
||||
ConstraintSet::from(!(self.class(db)).could_coexist_in_mro_with(db, other.class(db)))
|
||||
ConstraintSet::from(
|
||||
!self
|
||||
.class(db)
|
||||
.could_coexist_in_mro_with(db, other.class(db)),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -498,7 +510,11 @@ impl<'db> NominalInstanceType<'db> {
|
||||
NominalInstanceInner::NonTuple(class) => class
|
||||
.known(db)
|
||||
.map(KnownClass::is_singleton)
|
||||
.unwrap_or_else(|| is_single_member_enum(db, class.class_literal(db).0)),
|
||||
.unwrap_or_else(|| {
|
||||
class
|
||||
.stmt_class_literal(db)
|
||||
.is_some_and(|(lit, _)| is_single_member_enum(db, lit))
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -510,7 +526,11 @@ impl<'db> NominalInstanceType<'db> {
|
||||
.known(db)
|
||||
.and_then(KnownClass::is_single_valued)
|
||||
.or_else(|| Some(self.tuple_spec(db)?.is_single_valued(db)))
|
||||
.unwrap_or_else(|| is_single_member_enum(db, class.class_literal(db).0)),
|
||||
.unwrap_or_else(|| {
|
||||
class
|
||||
.stmt_class_literal(db)
|
||||
.is_some_and(|(lit, _)| is_single_member_enum(db, lit))
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -623,7 +643,7 @@ pub(super) fn walk_protocol_instance_type<'db, V: super::visitor::TypeVisitor<'d
|
||||
} else {
|
||||
match protocol.inner {
|
||||
Protocol::FromClass(class) => {
|
||||
if let Some(specialization) = class.class_literal(db).1 {
|
||||
if let Some((_, Some(specialization))) = class.stmt_class_literal(db) {
|
||||
walk_specialization(db, specialization, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,8 +20,8 @@ use crate::{
|
||||
semantic_index, use_def_map,
|
||||
},
|
||||
types::{
|
||||
ClassBase, ClassLiteral, KnownClass, KnownInstanceType, SubclassOfInner, Type,
|
||||
TypeVarBoundOrConstraints, class::CodeGeneratorKind, generics::Specialization,
|
||||
ClassBase, ClassLiteral, KnownClass, KnownInstanceType, StmtClassLiteral, SubclassOfInner,
|
||||
Type, TypeVarBoundOrConstraints, class::CodeGeneratorKind, generics::Specialization,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -181,9 +181,16 @@ impl<'db> AllMembers<'db> {
|
||||
),
|
||||
|
||||
Type::NominalInstance(instance) => {
|
||||
let (class_literal, specialization) = instance.class(db).class_literal(db);
|
||||
self.extend_with_instance_members(db, ty, class_literal);
|
||||
self.extend_with_synthetic_members(db, ty, class_literal, specialization);
|
||||
let class = instance.class(db);
|
||||
if let Some((class_literal, specialization)) = class.stmt_class_literal(db) {
|
||||
self.extend_with_instance_members(db, ty, class_literal);
|
||||
self.extend_with_synthetic_members(
|
||||
db,
|
||||
ty,
|
||||
ClassLiteral::Stmt(class_literal),
|
||||
specialization,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Type::NewTypeInstance(newtype) => {
|
||||
@@ -212,8 +219,8 @@ impl<'db> AllMembers<'db> {
|
||||
|
||||
Type::GenericAlias(generic_alias) => {
|
||||
let class_literal = generic_alias.origin(db);
|
||||
self.extend_with_class_members(db, ty, class_literal);
|
||||
self.extend_with_synthetic_members(db, ty, class_literal, None);
|
||||
self.extend_with_class_members(db, ty, ClassLiteral::Stmt(class_literal));
|
||||
self.extend_with_synthetic_members(db, ty, ClassLiteral::Stmt(class_literal), None);
|
||||
if let Type::ClassLiteral(metaclass) = class_literal.metaclass(db) {
|
||||
self.extend_with_class_members(db, ty, metaclass);
|
||||
}
|
||||
@@ -225,11 +232,23 @@ impl<'db> AllMembers<'db> {
|
||||
}
|
||||
_ => {
|
||||
if let Some(class_type) = subclass_of_type.subclass_of().into_class(db) {
|
||||
let (class_literal, specialization) = class_type.class_literal(db);
|
||||
self.extend_with_class_members(db, ty, class_literal);
|
||||
self.extend_with_synthetic_members(db, ty, class_literal, specialization);
|
||||
if let Type::ClassLiteral(metaclass) = class_literal.metaclass(db) {
|
||||
self.extend_with_class_members(db, ty, metaclass);
|
||||
if let Some((class_literal, specialization)) =
|
||||
class_type.stmt_class_literal(db)
|
||||
{
|
||||
self.extend_with_class_members(
|
||||
db,
|
||||
ty,
|
||||
ClassLiteral::Stmt(class_literal),
|
||||
);
|
||||
self.extend_with_synthetic_members(
|
||||
db,
|
||||
ty,
|
||||
ClassLiteral::Stmt(class_literal),
|
||||
specialization,
|
||||
);
|
||||
if let Type::ClassLiteral(metaclass) = class_literal.metaclass(db) {
|
||||
self.extend_with_class_members(db, ty, metaclass);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -289,12 +308,18 @@ impl<'db> AllMembers<'db> {
|
||||
}
|
||||
Type::SubclassOf(subclass_of) => {
|
||||
if let Some(class) = subclass_of.subclass_of().into_class(db) {
|
||||
self.extend_with_class_members(db, ty, class.class_literal(db).0);
|
||||
if let Some((class_literal, _)) = class.stmt_class_literal(db) {
|
||||
self.extend_with_class_members(
|
||||
db,
|
||||
ty,
|
||||
ClassLiteral::Stmt(class_literal),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Type::GenericAlias(generic_alias) => {
|
||||
let class_literal = generic_alias.origin(db);
|
||||
self.extend_with_class_members(db, ty, class_literal);
|
||||
self.extend_with_class_members(db, ty, ClassLiteral::Stmt(class_literal));
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
@@ -304,7 +329,7 @@ impl<'db> AllMembers<'db> {
|
||||
self.extend_with_class_members(db, ty, class_literal);
|
||||
}
|
||||
|
||||
if let Type::ClassLiteral(class) =
|
||||
if let Type::ClassLiteral(ClassLiteral::Stmt(class)) =
|
||||
KnownClass::TypedDictFallback.to_class_literal(db)
|
||||
{
|
||||
self.extend_with_instance_members(db, ty, class);
|
||||
@@ -410,9 +435,9 @@ impl<'db> AllMembers<'db> {
|
||||
class_literal: ClassLiteral<'db>,
|
||||
) {
|
||||
for parent in class_literal
|
||||
.iter_mro(db, None)
|
||||
.iter_mro(db)
|
||||
.filter_map(ClassBase::into_class)
|
||||
.map(|class| class.class_literal(db).0)
|
||||
.filter_map(|class| class.stmt_class_literal(db).map(|(lit, _)| lit))
|
||||
{
|
||||
let parent_scope = parent.body_scope(db);
|
||||
for memberdef in all_end_of_scope_members(db, parent_scope) {
|
||||
@@ -428,52 +453,64 @@ impl<'db> AllMembers<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
fn extend_with_instance_members(
|
||||
/// Extend with instance members from a single class (not its MRO).
|
||||
fn extend_with_instance_members_for_class(
|
||||
&mut self,
|
||||
db: &'db dyn Db,
|
||||
ty: Type<'db>,
|
||||
class_literal: ClassLiteral<'db>,
|
||||
class_literal: StmtClassLiteral<'db>,
|
||||
) {
|
||||
for parent in class_literal
|
||||
.iter_mro(db, None)
|
||||
.filter_map(ClassBase::into_class)
|
||||
.map(|class| class.class_literal(db).0)
|
||||
{
|
||||
let class_body_scope = parent.body_scope(db);
|
||||
let file = class_body_scope.file(db);
|
||||
let index = semantic_index(db, file);
|
||||
for function_scope_id in attribute_scopes(db, class_body_scope) {
|
||||
for place_expr in index.place_table(function_scope_id).members() {
|
||||
let Some(name) = place_expr.as_instance_attribute() else {
|
||||
continue;
|
||||
};
|
||||
let result = ty.member(db, name);
|
||||
let Some(ty) = result.place.ignore_possibly_undefined() else {
|
||||
continue;
|
||||
};
|
||||
self.members.insert(Member {
|
||||
name: Name::new(name),
|
||||
ty,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// This is very similar to `extend_with_class_members`,
|
||||
// but uses the type of the class instance to query the
|
||||
// class member. This gets us the right type for each
|
||||
// member, e.g., `SomeClass.__delattr__` is not a bound
|
||||
// method, but `instance_of_SomeClass.__delattr__` is.
|
||||
for memberdef in all_end_of_scope_members(db, class_body_scope) {
|
||||
let result = ty.member(db, memberdef.member.name.as_str());
|
||||
let class_body_scope = class_literal.body_scope(db);
|
||||
let file = class_body_scope.file(db);
|
||||
let index = semantic_index(db, file);
|
||||
for function_scope_id in attribute_scopes(db, class_body_scope) {
|
||||
for place_expr in index.place_table(function_scope_id).members() {
|
||||
let Some(name) = place_expr.as_instance_attribute() else {
|
||||
continue;
|
||||
};
|
||||
let result = ty.member(db, name);
|
||||
let Some(ty) = result.place.ignore_possibly_undefined() else {
|
||||
continue;
|
||||
};
|
||||
self.members.insert(Member {
|
||||
name: memberdef.member.name,
|
||||
name: Name::new(name),
|
||||
ty,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// This is very similar to `extend_with_class_members`,
|
||||
// but uses the type of the class instance to query the
|
||||
// class member. This gets us the right type for each
|
||||
// member, e.g., `SomeClass.__delattr__` is not a bound
|
||||
// method, but `instance_of_SomeClass.__delattr__` is.
|
||||
for memberdef in all_end_of_scope_members(db, class_body_scope) {
|
||||
let result = ty.member(db, memberdef.member.name.as_str());
|
||||
let Some(ty) = result.place.ignore_possibly_undefined() else {
|
||||
continue;
|
||||
};
|
||||
self.members.insert(Member {
|
||||
name: memberdef.member.name,
|
||||
ty,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Extend with instance members from a class and all classes in its MRO.
|
||||
fn extend_with_instance_members(
|
||||
&mut self,
|
||||
db: &'db dyn Db,
|
||||
ty: Type<'db>,
|
||||
class_literal: StmtClassLiteral<'db>,
|
||||
) {
|
||||
for class in class_literal
|
||||
.iter_mro(db, None)
|
||||
.filter_map(ClassBase::into_class)
|
||||
{
|
||||
if let Some((class_literal, _)) = class.stmt_class_literal(db) {
|
||||
self.extend_with_instance_members_for_class(db, ty, class_literal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn extend_with_synthetic_members(
|
||||
@@ -483,7 +520,10 @@ impl<'db> AllMembers<'db> {
|
||||
class_literal: ClassLiteral<'db>,
|
||||
specialization: Option<Specialization<'db>>,
|
||||
) {
|
||||
match CodeGeneratorKind::from_class(db, class_literal, specialization) {
|
||||
let Some(stmt_class) = class_literal.as_stmt() else {
|
||||
return;
|
||||
};
|
||||
match CodeGeneratorKind::from_class(db, stmt_class, specialization) {
|
||||
Some(CodeGeneratorKind::NamedTuple) => {
|
||||
if ty.is_nominal_instance() {
|
||||
self.extend_with_type(db, KnownClass::NamedTupleFallback.to_instance(db));
|
||||
|
||||
@@ -2,17 +2,20 @@ use std::collections::VecDeque;
|
||||
use std::ops::Deref;
|
||||
|
||||
use indexmap::IndexMap;
|
||||
use rustc_hash::FxBuildHasher;
|
||||
use rustc_hash::{FxBuildHasher, FxHashSet};
|
||||
|
||||
use crate::Db;
|
||||
use crate::types::class_base::ClassBase;
|
||||
use crate::types::generics::Specialization;
|
||||
use crate::types::{ClassLiteral, ClassType, KnownClass, KnownInstanceType, SpecialFormType, Type};
|
||||
use crate::types::{
|
||||
ClassLiteral, ClassType, FunctionalClassLiteral, KnownClass, KnownInstanceType,
|
||||
SpecialFormType, StmtClassLiteral, Type,
|
||||
};
|
||||
|
||||
/// The inferred method resolution order of a given class.
|
||||
///
|
||||
/// An MRO cannot contain non-specialized generic classes. (This is why [`ClassBase`] contains a
|
||||
/// [`ClassType`], not a [`ClassLiteral`].) Any generic classes in a base class list are always
|
||||
/// [`ClassType`], not a [`StmtClassLiteral`].) Any generic classes in a base class list are always
|
||||
/// specialized — either because the class is explicitly specialized if there is a subscript
|
||||
/// expression, or because we create the default specialization if there isn't.
|
||||
///
|
||||
@@ -29,12 +32,12 @@ use crate::types::{ClassLiteral, ClassType, KnownClass, KnownInstanceType, Speci
|
||||
///
|
||||
/// See [`ClassType::iter_mro`] for more details.
|
||||
#[derive(PartialEq, Eq, Clone, Debug, salsa::Update, get_size2::GetSize)]
|
||||
pub(super) struct Mro<'db>(Box<[ClassBase<'db>]>);
|
||||
pub(crate) struct Mro<'db>(Box<[ClassBase<'db>]>);
|
||||
|
||||
impl<'db> Mro<'db> {
|
||||
/// Attempt to resolve the MRO of a given class. Because we derive the MRO from the list of
|
||||
/// base classes in the class definition, this operation is performed on a [class
|
||||
/// literal][ClassLiteral], not a [class type][ClassType]. (You can _also_ get the MRO of a
|
||||
/// literal][StmtClassLiteral], not a [class type][ClassType]. (You can _also_ get the MRO of a
|
||||
/// class type, but this is done by first getting the MRO of the underlying class literal, and
|
||||
/// specializing each base class as needed if the class type is a generic alias.)
|
||||
///
|
||||
@@ -46,9 +49,9 @@ impl<'db> Mro<'db> {
|
||||
///
|
||||
/// (We emit a diagnostic warning about the runtime `TypeError` in
|
||||
/// [`super::infer::infer_scope_types`].)
|
||||
pub(super) fn of_class(
|
||||
pub(super) fn of_stmt_class(
|
||||
db: &'db dyn Db,
|
||||
class_literal: ClassLiteral<'db>,
|
||||
class_literal: StmtClassLiteral<'db>,
|
||||
specialization: Option<Specialization<'db>>,
|
||||
) -> Result<Self, MroError<'db>> {
|
||||
let class = class_literal.apply_optional_specialization(db, specialization);
|
||||
@@ -69,6 +72,81 @@ impl<'db> Mro<'db> {
|
||||
])
|
||||
}
|
||||
|
||||
/// Attempt to resolve the MRO of a functional class (created via `type(name, bases, dict)`).
|
||||
///
|
||||
/// Uses C3 linearization when possible, returning an error if the MRO cannot be resolved.
|
||||
pub(super) fn of_functional_class(
|
||||
db: &'db dyn Db,
|
||||
functional: FunctionalClassLiteral<'db>,
|
||||
) -> Result<Self, FunctionalMroError<'db>> {
|
||||
let bases = functional.bases(db);
|
||||
|
||||
// Check for duplicate bases first.
|
||||
let mut seen = FxHashSet::default();
|
||||
let mut duplicates = Vec::new();
|
||||
for base in bases {
|
||||
if !seen.insert(*base) {
|
||||
duplicates.push(*base);
|
||||
}
|
||||
}
|
||||
if !duplicates.is_empty() {
|
||||
return Err(FunctionalMroError::DuplicateBases(
|
||||
duplicates.into_boxed_slice(),
|
||||
));
|
||||
}
|
||||
|
||||
// Compute MRO using C3 linearization.
|
||||
let mro_bases = if bases.is_empty() {
|
||||
// Empty bases: MRO is just `object`.
|
||||
vec![ClassBase::object(db)]
|
||||
} else if bases.len() == 1 {
|
||||
// Single base: MRO is just that base's MRO.
|
||||
bases[0].mro(db, None).collect()
|
||||
} else {
|
||||
// Multiple bases: use C3 merge algorithm.
|
||||
let mut seqs: Vec<VecDeque<ClassBase<'db>>> = Vec::with_capacity(bases.len() + 1);
|
||||
|
||||
// Add each base's MRO.
|
||||
for base in bases {
|
||||
seqs.push(base.mro(db, None).collect());
|
||||
}
|
||||
|
||||
// Add the list of bases in order.
|
||||
seqs.push(bases.iter().copied().collect());
|
||||
|
||||
c3_merge(seqs)
|
||||
.map(|mro| mro.iter().copied().collect())
|
||||
.ok_or(FunctionalMroError::UnresolvableMro)?
|
||||
};
|
||||
|
||||
let mut result = vec![ClassBase::Class(ClassType::NonGeneric(functional.into()))];
|
||||
result.extend(mro_bases);
|
||||
Ok(Self::from(result))
|
||||
}
|
||||
|
||||
/// Compute a fallback MRO for a functional class when `of_functional_class` fails.
|
||||
///
|
||||
/// Iterates over base MROs sequentially with deduplication.
|
||||
pub(super) fn functional_fallback(
|
||||
db: &'db dyn Db,
|
||||
functional: FunctionalClassLiteral<'db>,
|
||||
) -> Self {
|
||||
let self_base = ClassBase::Class(ClassType::NonGeneric(functional.into()));
|
||||
let mut result = vec![self_base];
|
||||
let mut seen = FxHashSet::default();
|
||||
seen.insert(self_base);
|
||||
|
||||
for base in functional.bases(db) {
|
||||
for item in base.mro(db, None) {
|
||||
if seen.insert(item) {
|
||||
result.push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Self::from(result)
|
||||
}
|
||||
|
||||
fn of_class_impl(
|
||||
db: &'db dyn Db,
|
||||
class: ClassType<'db>,
|
||||
@@ -156,7 +234,10 @@ impl<'db> Mro<'db> {
|
||||
)
|
||||
) =>
|
||||
{
|
||||
ClassBase::try_from_type(db, *single_base, class.class_literal(db).0).map_or_else(
|
||||
let Some((class_literal, _)) = class.stmt_class_literal(db) else {
|
||||
return Err(MroErrorKind::InvalidBases(Box::from([(0, *single_base)])));
|
||||
};
|
||||
ClassBase::try_from_type(db, *single_base, class_literal).map_or_else(
|
||||
|| Err(MroErrorKind::InvalidBases(Box::from([(0, *single_base)]))),
|
||||
|single_base| {
|
||||
if single_base.has_cyclic_mro(db) {
|
||||
@@ -176,6 +257,11 @@ impl<'db> Mro<'db> {
|
||||
// what MRO Python will give this class at runtime
|
||||
// (if an MRO is indeed resolvable at all!)
|
||||
_ => {
|
||||
let Some((class_literal, _)) = class.stmt_class_literal(db) else {
|
||||
// Functional classes don't have explicit bases to resolve.
|
||||
return Ok(std::iter::once(ClassBase::Class(class)).collect());
|
||||
};
|
||||
|
||||
let mut resolved_bases = vec![];
|
||||
let mut invalid_bases = vec![];
|
||||
|
||||
@@ -191,7 +277,7 @@ impl<'db> Mro<'db> {
|
||||
&original_bases[i + 1..],
|
||||
);
|
||||
} else {
|
||||
match ClassBase::try_from_type(db, *base, class.class_literal(db).0) {
|
||||
match ClassBase::try_from_type(db, *base, class_literal) {
|
||||
Some(valid_base) => resolved_bases.push(valid_base),
|
||||
None => invalid_bases.push((i, *base)),
|
||||
}
|
||||
@@ -258,9 +344,7 @@ impl<'db> Mro<'db> {
|
||||
// `inconsistent-mro` diagnostic (which would be accurate -- but not nearly as
|
||||
// precise!).
|
||||
for (index, base) in original_bases.iter().enumerate() {
|
||||
let Some(base) =
|
||||
ClassBase::try_from_type(db, *base, class.class_literal(db).0)
|
||||
else {
|
||||
let Some(base) = ClassBase::try_from_type(db, *base, class_literal) else {
|
||||
continue;
|
||||
};
|
||||
base_to_indices.entry(base).or_default().push(index);
|
||||
@@ -354,8 +438,8 @@ impl<'db> FromIterator<ClassBase<'db>> for Mro<'db> {
|
||||
///
|
||||
/// Even for first-party code, where we will have to resolve the MRO for every class we encounter,
|
||||
/// loading the cached MRO comes with a certain amount of overhead, so it's best to avoid calling the
|
||||
/// Salsa-tracked [`ClassLiteral::try_mro`] method unless it's absolutely necessary.
|
||||
pub(super) struct MroIterator<'db> {
|
||||
/// Salsa-tracked [`StmtClassLiteral::try_mro`] method unless it's absolutely necessary.
|
||||
pub(crate) struct MroIterator<'db> {
|
||||
db: &'db dyn Db,
|
||||
|
||||
/// The class whose MRO we're iterating over
|
||||
@@ -372,9 +456,33 @@ pub(super) struct MroIterator<'db> {
|
||||
/// The full MRO is expensive to materialize, so this field is `None`
|
||||
/// unless we actually *need* to iterate past the first element of the MRO,
|
||||
/// at which point it is lazily materialized.
|
||||
subsequent_elements: Option<std::slice::Iter<'db, ClassBase<'db>>>,
|
||||
subsequent_elements: Option<SubsequentMroElements<'db>>,
|
||||
}
|
||||
|
||||
/// The subsequent elements of an MRO (everything after the first element).
|
||||
///
|
||||
/// For statement-defined classes, we borrow from the cached MRO via `returns(as_ref)`.
|
||||
/// For functional classes, we clone the MRO since the cache returns an owned value.
|
||||
enum SubsequentMroElements<'db> {
|
||||
/// Iterator over a borrowed MRO slice (for statement-defined classes).
|
||||
Borrowed(std::slice::Iter<'db, ClassBase<'db>>),
|
||||
/// Iterator over an owned MRO (for functional classes).
|
||||
Owned(std::vec::IntoIter<ClassBase<'db>>),
|
||||
}
|
||||
|
||||
impl<'db> Iterator for SubsequentMroElements<'db> {
|
||||
type Item = ClassBase<'db>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self {
|
||||
Self::Borrowed(iter) => iter.next().copied(),
|
||||
Self::Owned(iter) => iter.next(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::iter::FusedIterator for SubsequentMroElements<'_> {}
|
||||
|
||||
impl<'db> MroIterator<'db> {
|
||||
pub(super) fn new(
|
||||
db: &'db dyn Db,
|
||||
@@ -390,19 +498,70 @@ impl<'db> MroIterator<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
fn first_element(&self) -> ClassBase<'db> {
|
||||
match self.class {
|
||||
ClassLiteral::Stmt(stmt) => {
|
||||
ClassBase::Class(stmt.apply_optional_specialization(self.db, self.specialization))
|
||||
}
|
||||
ClassLiteral::Functional(functional) => {
|
||||
ClassBase::Class(ClassType::NonGeneric(functional.into()))
|
||||
}
|
||||
ClassLiteral::FunctionalNamedTuple(namedtuple) => {
|
||||
ClassBase::Class(ClassType::NonGeneric(namedtuple.into()))
|
||||
}
|
||||
ClassLiteral::FunctionalDataclass(dataclass) => {
|
||||
ClassBase::Class(ClassType::NonGeneric(dataclass.into()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Materialize the full MRO of the class.
|
||||
/// Return an iterator over that MRO which skips the first element of the MRO.
|
||||
fn full_mro_except_first_element(&mut self) -> impl Iterator<Item = ClassBase<'db>> + '_ {
|
||||
fn full_mro_except_first_element(&mut self) -> &mut SubsequentMroElements<'db> {
|
||||
self.subsequent_elements
|
||||
.get_or_insert_with(|| {
|
||||
let mut full_mro_iter = match self.class.try_mro(self.db, self.specialization) {
|
||||
Ok(mro) => mro.iter(),
|
||||
Err(error) => error.fallback_mro().iter(),
|
||||
};
|
||||
full_mro_iter.next();
|
||||
full_mro_iter
|
||||
.get_or_insert_with(|| match self.class {
|
||||
ClassLiteral::Stmt(stmt) => {
|
||||
let mut full_mro_iter = match stmt.try_mro(self.db, self.specialization) {
|
||||
Ok(mro) => mro.iter(),
|
||||
Err(error) => error.fallback_mro().iter(),
|
||||
};
|
||||
full_mro_iter.next();
|
||||
SubsequentMroElements::Borrowed(full_mro_iter)
|
||||
}
|
||||
ClassLiteral::Functional(functional) => {
|
||||
let mro = functional.mro(self.db);
|
||||
let elements: Vec<_> = mro.iter().skip(1).copied().collect();
|
||||
SubsequentMroElements::Owned(elements.into_iter())
|
||||
}
|
||||
ClassLiteral::FunctionalNamedTuple(namedtuple) => {
|
||||
// Functional namedtuples inherit from their tuple base type.
|
||||
// For example, `NamedTuple("Point", [("x", int), ("y", str)])` has the MRO:
|
||||
// `[Point, tuple[int, str], tuple, object]`.
|
||||
let tuple_base = namedtuple.tuple_base_type(self.db);
|
||||
let elements: Vec<_> = tuple_base.iter_mro(self.db).collect();
|
||||
SubsequentMroElements::Owned(elements.into_iter())
|
||||
}
|
||||
ClassLiteral::FunctionalDataclass(dataclass) => {
|
||||
// Dataclasses inherit from their base classes plus object.
|
||||
// For simplicity, we just use object's MRO if no bases.
|
||||
let bases = dataclass.bases(self.db);
|
||||
if bases.is_empty() {
|
||||
let object_class = KnownClass::Object
|
||||
.to_class_literal(self.db)
|
||||
.as_class_literal()
|
||||
.expect("Object should be a class literal");
|
||||
let elements: Vec<_> = object_class.iter_mro(self.db).collect();
|
||||
SubsequentMroElements::Owned(elements.into_iter())
|
||||
} else {
|
||||
// Use the first base's MRO.
|
||||
let elements: Vec<_> = match bases[0] {
|
||||
ClassBase::Class(class) => class.iter_mro(self.db).collect(),
|
||||
_ => vec![ClassBase::Class(ClassType::object(self.db))],
|
||||
};
|
||||
SubsequentMroElements::Owned(elements.into_iter())
|
||||
}
|
||||
}
|
||||
})
|
||||
.copied()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -412,10 +571,7 @@ impl<'db> Iterator for MroIterator<'db> {
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if !self.first_element_yielded {
|
||||
self.first_element_yielded = true;
|
||||
return Some(ClassBase::Class(
|
||||
self.class
|
||||
.apply_optional_specialization(self.db, self.specialization),
|
||||
));
|
||||
return Some(self.first_element());
|
||||
}
|
||||
self.full_mro_except_first_element().next()
|
||||
}
|
||||
@@ -544,3 +700,15 @@ fn c3_merge(mut sequences: Vec<VecDeque<ClassBase>>) -> Option<Mro> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Error kinds for functional class MRO computation.
|
||||
///
|
||||
/// These mirror the relevant variants from `MroErrorKind` for regular classes.
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) enum FunctionalMroError<'db> {
|
||||
/// The class has duplicate bases in its bases tuple.
|
||||
DuplicateBases(Box<[ClassBase<'db>]>),
|
||||
|
||||
/// The MRO is unresolvable through the C3-merge algorithm.
|
||||
UnresolvableMro,
|
||||
}
|
||||
|
||||
@@ -155,7 +155,7 @@ impl ClassInfoConstraintFunction {
|
||||
/// The `classinfo` argument can be a class literal, a tuple of (tuples of) class literals. PEP 604
|
||||
/// union types are not yet supported. Returns `None` if the `classinfo` argument has a wrong type.
|
||||
fn generate_constraint<'db>(self, db: &'db dyn Db, classinfo: Type<'db>) -> Option<Type<'db>> {
|
||||
let constraint_fn = |class: ClassLiteral<'db>| match self {
|
||||
let constraint_from_class_literal = |class: ClassLiteral<'db>| match self {
|
||||
ClassInfoConstraintFunction::IsInstance => {
|
||||
Type::instance(db, class.top_materialization(db))
|
||||
}
|
||||
@@ -166,9 +166,11 @@ impl ClassInfoConstraintFunction {
|
||||
|
||||
match classinfo {
|
||||
Type::TypeAlias(alias) => self.generate_constraint(db, alias.value_type(db)),
|
||||
Type::ClassLiteral(class_literal) => Some(constraint_fn(class_literal)),
|
||||
Type::ClassLiteral(class_literal) => Some(constraint_from_class_literal(class_literal)),
|
||||
Type::SubclassOf(subclass_of_ty) => match subclass_of_ty.subclass_of() {
|
||||
SubclassOfInner::Class(ClassType::NonGeneric(class)) => Some(constraint_fn(class)),
|
||||
SubclassOfInner::Class(ClassType::NonGeneric(class_literal)) => {
|
||||
Some(constraint_from_class_literal(class_literal))
|
||||
}
|
||||
// It's not valid to use a generic alias as the second argument to `isinstance()` or `issubclass()`,
|
||||
// e.g. `isinstance(x, list[int])` fails at runtime.
|
||||
SubclassOfInner::Class(ClassType::Generic(_)) => None,
|
||||
@@ -794,11 +796,16 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
|
||||
}
|
||||
// Treat enums as a union of their members.
|
||||
Type::NominalInstance(instance)
|
||||
if enum_metadata(db, instance.class_literal(db)).is_some() =>
|
||||
if instance
|
||||
.class_literal(db)
|
||||
.is_some_and(|class| enum_metadata(db, class).is_some()) =>
|
||||
{
|
||||
let class_literal = instance
|
||||
.class_literal(db)
|
||||
.expect("Already checked that class_literal is Some");
|
||||
UnionType::from_elements(
|
||||
db,
|
||||
enum_member_literals(db, instance.class_literal(db), None)
|
||||
enum_member_literals(db, class_literal, None)
|
||||
.expect("Calling `enum_member_literals` on an enum class")
|
||||
.map(|ty| filter_to_cannot_be_equal(db, ty, rhs_ty)),
|
||||
)
|
||||
|
||||
@@ -19,7 +19,7 @@ use ruff_python_ast as ast;
|
||||
/// ```
|
||||
///
|
||||
/// The revealed types there are:
|
||||
/// - `typing.NewType`: `Type::ClassLiteral(ClassLiteral)` with `KnownClass::NewType`.
|
||||
/// - `typing.NewType`: `Type::ClassLiteral(StmtClassLiteral)` with `KnownClass::NewType`.
|
||||
/// - `Foo`: `Type::KnownInstance(KnownInstanceType::NewType(NewType { .. }))`
|
||||
/// - `x`: `Type::NewTypeInstance(NewType { .. })`
|
||||
///
|
||||
|
||||
@@ -16,7 +16,7 @@ use crate::{
|
||||
symbol::ScopedSymbolId, use_def_map,
|
||||
},
|
||||
types::{
|
||||
ClassBase, ClassLiteral, ClassType, KnownClass, Type,
|
||||
ClassBase, ClassType, KnownClass, StmtClassLiteral, Type,
|
||||
class::CodeGeneratorKind,
|
||||
context::InferContext,
|
||||
diagnostic::{
|
||||
@@ -45,7 +45,7 @@ const PROHIBITED_NAMEDTUPLE_ATTRS: &[&str] = &[
|
||||
"_source",
|
||||
];
|
||||
|
||||
pub(super) fn check_class<'db>(context: &InferContext<'db, '_>, class: ClassLiteral<'db>) {
|
||||
pub(super) fn check_class<'db>(context: &InferContext<'db, '_>, class: StmtClassLiteral<'db>) {
|
||||
let db = context.db();
|
||||
let configuration = OverrideRulesConfig::from(context);
|
||||
if configuration.no_rules_enabled() {
|
||||
@@ -116,7 +116,10 @@ fn check_class_declaration<'db>(
|
||||
return;
|
||||
};
|
||||
|
||||
let (literal, specialization) = class.class_literal(db);
|
||||
let Some((literal, specialization)) = class.stmt_class_literal(db) else {
|
||||
// Functional classes don't have class literals.
|
||||
return;
|
||||
};
|
||||
let class_kind = CodeGeneratorKind::from_class(db, literal, specialization);
|
||||
|
||||
// Check for prohibited `NamedTuple` attribute overrides.
|
||||
@@ -164,7 +167,12 @@ fn check_class_declaration<'db>(
|
||||
ClassBase::Class(class) => class,
|
||||
};
|
||||
|
||||
let (superclass_literal, superclass_specialization) = superclass.class_literal(db);
|
||||
let Some((superclass_literal, superclass_specialization)) =
|
||||
superclass.stmt_class_literal(db)
|
||||
else {
|
||||
// Functional classes in the MRO don't have class literals.
|
||||
continue;
|
||||
};
|
||||
let superclass_scope = superclass_literal.body_scope(db);
|
||||
let superclass_symbol_table = place_table(db, superclass_scope);
|
||||
let superclass_symbol_id = superclass_symbol_table.symbol_id(&member.name);
|
||||
|
||||
@@ -159,7 +159,7 @@ impl Ty {
|
||||
.place
|
||||
.expect_type();
|
||||
debug_assert!(
|
||||
matches!(ty, Type::NominalInstance(instance) if is_single_member_enum(db, instance.class_literal(db)))
|
||||
matches!(ty, Type::NominalInstance(instance) if instance.class_literal(db).is_some_and(|class| is_single_member_enum(db, class)))
|
||||
);
|
||||
ty
|
||||
}
|
||||
|
||||
@@ -12,11 +12,11 @@ use crate::{
|
||||
place::{Definedness, Place, PlaceAndQualifiers, place_from_bindings, place_from_declarations},
|
||||
semantic_index::{definition::Definition, place::ScopedPlaceId, place_table, use_def_map},
|
||||
types::{
|
||||
ApplyTypeMappingVisitor, BoundTypeVarInstance, CallableType, ClassBase, ClassLiteral,
|
||||
ClassType, FindLegacyTypeVarsVisitor, HasRelationToVisitor,
|
||||
InstanceFallbackShadowsNonDataDescriptor, IsDisjointVisitor, KnownFunction,
|
||||
MemberLookupPolicy, NormalizedVisitor, PropertyInstanceType, Signature, Type, TypeMapping,
|
||||
TypeQualifiers, TypeRelation, TypeVarVariance, VarianceInferable,
|
||||
ApplyTypeMappingVisitor, BoundTypeVarInstance, CallableType, ClassBase, ClassType,
|
||||
FindLegacyTypeVarsVisitor, HasRelationToVisitor, InstanceFallbackShadowsNonDataDescriptor,
|
||||
IsDisjointVisitor, KnownFunction, MemberLookupPolicy, NormalizedVisitor,
|
||||
PropertyInstanceType, Signature, StmtClassLiteral, Type, TypeMapping, TypeQualifiers,
|
||||
TypeRelation, TypeVarVariance, VarianceInferable,
|
||||
constraints::{ConstraintSet, IteratorConstraintsExtension, OptionConstraintsExtension},
|
||||
context::InferContext,
|
||||
diagnostic::report_undeclared_protocol_member,
|
||||
@@ -26,11 +26,11 @@ use crate::{
|
||||
},
|
||||
};
|
||||
|
||||
impl<'db> ClassLiteral<'db> {
|
||||
impl<'db> StmtClassLiteral<'db> {
|
||||
/// Returns `Some` if this is a protocol class, `None` otherwise.
|
||||
pub(super) fn into_protocol_class(self, db: &'db dyn Db) -> Option<ProtocolClass<'db>> {
|
||||
self.is_protocol(db)
|
||||
.then_some(ProtocolClass(ClassType::NonGeneric(self)))
|
||||
.then_some(ProtocolClass(ClassType::NonGeneric(self.into())))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,10 +73,17 @@ impl<'db> ProtocolClass<'db> {
|
||||
}
|
||||
|
||||
pub(super) fn is_runtime_checkable(self, db: &'db dyn Db) -> bool {
|
||||
self.class_literal(db)
|
||||
.0
|
||||
.known_function_decorators(db)
|
||||
.contains(&KnownFunction::RuntimeCheckable)
|
||||
// Check if this class or any ancestor protocol is decorated with @runtime_checkable.
|
||||
// Per PEP 544, @runtime_checkable propagates to subclasses.
|
||||
self.0.iter_mro(db).any(|base| {
|
||||
base.into_class()
|
||||
.and_then(|class| class.stmt_class_literal(db))
|
||||
.is_some_and(|(class_literal, _)| {
|
||||
class_literal
|
||||
.known_function_decorators(db)
|
||||
.contains(&KnownFunction::RuntimeCheckable)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Iterate through the body of the protocol class. Check that all definitions
|
||||
@@ -85,7 +92,10 @@ impl<'db> ProtocolClass<'db> {
|
||||
pub(super) fn validate_members(self, context: &InferContext) {
|
||||
let db = context.db();
|
||||
let interface = self.interface(db);
|
||||
let body_scope = self.class_literal(db).0.body_scope(db);
|
||||
let Some((class_literal, _)) = self.stmt_class_literal(db) else {
|
||||
return;
|
||||
};
|
||||
let body_scope = class_literal.body_scope(db);
|
||||
let class_place_table = place_table(db, body_scope);
|
||||
|
||||
for (symbol_id, mut bindings_iterator) in
|
||||
@@ -101,7 +111,11 @@ impl<'db> ProtocolClass<'db> {
|
||||
self.iter_mro(db)
|
||||
.filter_map(ClassBase::into_class)
|
||||
.any(|superclass| {
|
||||
let superclass_scope = superclass.class_literal(db).0.body_scope(db);
|
||||
let Some((superclass_literal, _)) = superclass.stmt_class_literal(db)
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
let superclass_scope = superclass_literal.body_scope(db);
|
||||
let Some(scoped_symbol_id) =
|
||||
place_table(db, superclass_scope).symbol_id(symbol_name)
|
||||
else {
|
||||
@@ -866,7 +880,7 @@ impl BoundOnClass {
|
||||
}
|
||||
}
|
||||
|
||||
/// Inner Salsa query for [`ProtocolClassLiteral::interface`].
|
||||
/// Inner Salsa query for [`ProtocolClass::interface`].
|
||||
#[salsa::tracked(cycle_initial=proto_interface_cycle_initial, heap_size=ruff_memory_usage::heap_size)]
|
||||
fn cached_protocol_interface<'db>(
|
||||
db: &'db dyn Db,
|
||||
@@ -874,15 +888,16 @@ fn cached_protocol_interface<'db>(
|
||||
) -> ProtocolInterface<'db> {
|
||||
let mut members = BTreeMap::default();
|
||||
|
||||
for (parent_protocol, specialization) in class
|
||||
for (parent_scope, specialization) in class
|
||||
.iter_mro(db)
|
||||
.filter_map(ClassBase::into_class)
|
||||
.filter_map(|class| {
|
||||
let (class, specialization) = class.class_literal(db);
|
||||
Some((class.into_protocol_class(db)?, specialization))
|
||||
let (class_literal, specialization) = class.stmt_class_literal(db)?;
|
||||
let protocol_class = class_literal.into_protocol_class(db)?;
|
||||
let parent_scope = protocol_class.stmt_class_literal(db)?.0.body_scope(db);
|
||||
Some((parent_scope, specialization))
|
||||
})
|
||||
{
|
||||
let parent_scope = parent_protocol.class_literal(db).0.body_scope(db);
|
||||
let use_def_map = use_def_map(db, parent_scope);
|
||||
let place_table = place_table(db, parent_scope);
|
||||
let mut direct_members = FxHashMap::default();
|
||||
|
||||
@@ -130,6 +130,21 @@ pub enum SpecialFormType {
|
||||
/// Typeshed defines this symbol as a class, but this isn't accurate: it's actually a factory function
|
||||
/// at runtime. We therefore represent it as a special form internally.
|
||||
NamedTuple,
|
||||
|
||||
/// Internal annotation for `typing.NamedTuple` fields parameter.
|
||||
TypingNamedTupleFieldsSchema,
|
||||
|
||||
/// Internal annotation for `collections.namedtuple` field names parameter.
|
||||
CollectionsNamedTupleFieldsSchema,
|
||||
|
||||
/// Internal annotation for `collections.namedtuple` defaults parameter.
|
||||
CollectionsNamedTupleDefaultsSchema,
|
||||
|
||||
/// An internal type representing the fields argument to `dataclasses.make_dataclass`.
|
||||
/// When a list or tuple literal is passed as the `fields` argument, it gets inferred as a
|
||||
/// `KnownInstanceType::MakeDataclassFieldsSchema` instead of a regular list or tuple type.
|
||||
/// This allows bind.rs to extract field information without AST access.
|
||||
MakeDataclassFieldsSchema,
|
||||
}
|
||||
|
||||
impl SpecialFormType {
|
||||
@@ -182,7 +197,13 @@ impl SpecialFormType {
|
||||
| Self::ChainMap
|
||||
| Self::OrderedDict => KnownClass::StdlibAlias,
|
||||
|
||||
Self::Unknown | Self::AlwaysTruthy | Self::AlwaysFalsy => KnownClass::Object,
|
||||
Self::Unknown
|
||||
| Self::AlwaysTruthy
|
||||
| Self::AlwaysFalsy
|
||||
| Self::TypingNamedTupleFieldsSchema
|
||||
| Self::CollectionsNamedTupleFieldsSchema
|
||||
| Self::CollectionsNamedTupleDefaultsSchema
|
||||
| Self::MakeDataclassFieldsSchema => KnownClass::Object,
|
||||
|
||||
Self::NamedTuple => KnownClass::FunctionType,
|
||||
}
|
||||
@@ -266,7 +287,11 @@ impl SpecialFormType {
|
||||
| Self::Bottom
|
||||
| Self::Intersection
|
||||
| Self::TypeOf
|
||||
| Self::CallableTypeOf => module.is_ty_extensions(),
|
||||
| Self::CallableTypeOf
|
||||
| Self::TypingNamedTupleFieldsSchema
|
||||
| Self::CollectionsNamedTupleFieldsSchema
|
||||
| Self::CollectionsNamedTupleDefaultsSchema
|
||||
| Self::MakeDataclassFieldsSchema => module.is_ty_extensions(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -327,7 +352,11 @@ impl SpecialFormType {
|
||||
| Self::ReadOnly
|
||||
| Self::Protocol
|
||||
| Self::Any
|
||||
| Self::Generic => false,
|
||||
| Self::Generic
|
||||
| Self::TypingNamedTupleFieldsSchema
|
||||
| Self::CollectionsNamedTupleFieldsSchema
|
||||
| Self::CollectionsNamedTupleDefaultsSchema
|
||||
| Self::MakeDataclassFieldsSchema => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,7 +411,11 @@ impl SpecialFormType {
|
||||
| Self::Callable
|
||||
| Self::Protocol
|
||||
| Self::Generic
|
||||
| Self::Unpack => None,
|
||||
| Self::Unpack
|
||||
| Self::TypingNamedTupleFieldsSchema
|
||||
| Self::CollectionsNamedTupleFieldsSchema
|
||||
| Self::CollectionsNamedTupleDefaultsSchema
|
||||
| Self::MakeDataclassFieldsSchema => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -434,7 +467,11 @@ impl SpecialFormType {
|
||||
| Self::Unknown
|
||||
| Self::TypeOf
|
||||
| Self::Any // can be used in `issubclass()` but not `isinstance()`.
|
||||
| Self::Unpack => false,
|
||||
| Self::Unpack
|
||||
| Self::TypingNamedTupleFieldsSchema
|
||||
| Self::CollectionsNamedTupleFieldsSchema
|
||||
| Self::CollectionsNamedTupleDefaultsSchema
|
||||
| Self::MakeDataclassFieldsSchema => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -485,6 +522,14 @@ impl SpecialFormType {
|
||||
SpecialFormType::Protocol => "Protocol",
|
||||
SpecialFormType::Generic => "Generic",
|
||||
SpecialFormType::NamedTuple => "NamedTuple",
|
||||
SpecialFormType::TypingNamedTupleFieldsSchema => "_TypingNamedTupleFieldsSchema",
|
||||
SpecialFormType::CollectionsNamedTupleFieldsSchema => {
|
||||
"_CollectionsNamedTupleFieldsSchema"
|
||||
}
|
||||
SpecialFormType::CollectionsNamedTupleDefaultsSchema => {
|
||||
"_CollectionsNamedTupleDefaultsSchema"
|
||||
}
|
||||
SpecialFormType::MakeDataclassFieldsSchema => "_MakeDataclassFieldsSchema",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -535,7 +580,11 @@ impl SpecialFormType {
|
||||
| SpecialFormType::TypeOf
|
||||
| SpecialFormType::CallableTypeOf
|
||||
| SpecialFormType::Top
|
||||
| SpecialFormType::Bottom => &[KnownModule::TyExtensions],
|
||||
| SpecialFormType::Bottom
|
||||
| SpecialFormType::TypingNamedTupleFieldsSchema
|
||||
| SpecialFormType::CollectionsNamedTupleFieldsSchema
|
||||
| SpecialFormType::CollectionsNamedTupleDefaultsSchema
|
||||
| SpecialFormType::MakeDataclassFieldsSchema => &[KnownModule::TyExtensions],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
use crate::place::PlaceAndQualifiers;
|
||||
use crate::semantic_index::definition::Definition;
|
||||
use crate::types::class_base::ClassBase;
|
||||
use crate::types::constraints::ConstraintSet;
|
||||
use crate::types::generics::InferableTypeVars;
|
||||
use crate::types::protocol_class::ProtocolClass;
|
||||
use crate::types::variance::VarianceInferable;
|
||||
use crate::types::{
|
||||
ApplyTypeMappingVisitor, BoundTypeVarInstance, ClassType, DynamicType,
|
||||
FindLegacyTypeVarsVisitor, HasRelationToVisitor, IsDisjointVisitor, KnownClass,
|
||||
MaterializationKind, MemberLookupPolicy, NormalizedVisitor, SpecialFormType, Type, TypeContext,
|
||||
TypeMapping, TypeRelation, TypeVarBoundOrConstraints, TypedDictType, UnionType, todo_type,
|
||||
ApplyTypeMappingVisitor, BoundTypeVarInstance, ClassLiteral, ClassType, DynamicType,
|
||||
FindLegacyTypeVarsVisitor, FunctionalClassLiteral, FunctionalNamedTupleLiteral,
|
||||
HasRelationToVisitor, IsDisjointVisitor, KnownClass, MaterializationKind, MemberLookupPolicy,
|
||||
NormalizedVisitor, SpecialFormType, Type, TypeContext, TypeMapping, TypeRelation,
|
||||
TypeVarBoundOrConstraints, TypedDictType, UnionType, todo_type,
|
||||
};
|
||||
use crate::{Db, FxOrderSet};
|
||||
|
||||
@@ -263,15 +265,15 @@ impl<'db> SubclassOfType<'db> {
|
||||
_visitor: &IsDisjointVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
match (self.subclass_of, other.subclass_of) {
|
||||
// Dynamic types have unknown structure, so we can't prove disjointness.
|
||||
(SubclassOfInner::Dynamic(_), _) | (_, SubclassOfInner::Dynamic(_)) => {
|
||||
ConstraintSet::from(false)
|
||||
}
|
||||
(SubclassOfInner::Class(self_class), SubclassOfInner::Class(other_class)) => {
|
||||
ConstraintSet::from(!self_class.could_coexist_in_mro_with(db, other_class))
|
||||
}
|
||||
(SubclassOfInner::TypeVar(_), _) | (_, SubclassOfInner::TypeVar(_)) => {
|
||||
unreachable!()
|
||||
}
|
||||
// TypeVar should have been handled before calling this method.
|
||||
(SubclassOfInner::TypeVar(_), _) | (_, SubclassOfInner::TypeVar(_)) => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,9 +334,11 @@ impl<'db> SubclassOfType<'db> {
|
||||
}
|
||||
|
||||
pub(crate) fn is_typed_dict(self, db: &'db dyn Db) -> bool {
|
||||
self.subclass_of
|
||||
.into_class(db)
|
||||
.is_some_and(|class| class.class_literal(db).0.is_typed_dict(db))
|
||||
self.subclass_of.into_class(db).is_some_and(|class| {
|
||||
class
|
||||
.stmt_class_literal(db)
|
||||
.is_some_and(|(lit, _)| lit.is_typed_dict(db))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -405,13 +409,6 @@ impl<'db> SubclassOfInner<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) const fn into_dynamic(self) -> Option<DynamicType<'db>> {
|
||||
match self {
|
||||
Self::Class(_) | Self::TypeVar(_) => None,
|
||||
Self::Dynamic(dynamic) => Some(dynamic),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) const fn into_type_var(self) -> Option<BoundTypeVarInstance<'db>> {
|
||||
match self {
|
||||
Self::Class(_) | Self::Dynamic(_) => None,
|
||||
@@ -419,6 +416,17 @@ impl<'db> SubclassOfInner<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert to a `ClassBase` if this is a class-like type.
|
||||
///
|
||||
/// Returns `None` for `TypeVar` since type variables require special handling.
|
||||
pub(crate) const fn to_class_base(self) -> Option<ClassBase<'db>> {
|
||||
match self {
|
||||
Self::Class(class) => Some(ClassBase::Class(class)),
|
||||
Self::Dynamic(dynamic) => Some(ClassBase::Dynamic(dynamic)),
|
||||
Self::TypeVar(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn try_from_instance(db: &'db dyn Db, ty: Type<'db>) -> Option<Self> {
|
||||
Some(match ty {
|
||||
Type::NominalInstance(instance) => SubclassOfInner::Class(instance.class(db)),
|
||||
@@ -534,3 +542,17 @@ impl<'db> From<SubclassOfType<'db>> for Type<'db> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> From<FunctionalClassLiteral<'db>> for SubclassOfInner<'db> {
|
||||
fn from(value: FunctionalClassLiteral<'db>) -> Self {
|
||||
SubclassOfInner::Class(ClassType::NonGeneric(ClassLiteral::Functional(value)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> From<FunctionalNamedTupleLiteral<'db>> for SubclassOfInner<'db> {
|
||||
fn from(value: FunctionalNamedTupleLiteral<'db>) -> Self {
|
||||
SubclassOfInner::Class(ClassType::NonGeneric(ClassLiteral::FunctionalNamedTuple(
|
||||
value,
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +74,9 @@ impl<'db> TypedDictType<'db> {
|
||||
pub(crate) fn items(self, db: &'db dyn Db) -> &'db TypedDictSchema<'db> {
|
||||
#[salsa::tracked(returns(ref), heap_size=ruff_memory_usage::heap_size)]
|
||||
fn class_based_items<'db>(db: &'db dyn Db, class: ClassType<'db>) -> TypedDictSchema<'db> {
|
||||
let (class_literal, specialization) = class.class_literal(db);
|
||||
let Some((class_literal, specialization)) = class.stmt_class_literal(db) else {
|
||||
return TypedDictSchema::default();
|
||||
};
|
||||
class_literal
|
||||
.fields(db, specialization, CodeGeneratorKind::TypedDict)
|
||||
.into_iter()
|
||||
@@ -294,7 +296,7 @@ impl<'db> TypedDictType<'db> {
|
||||
|
||||
pub fn definition(self, db: &'db dyn Db) -> Option<Definition<'db>> {
|
||||
match self {
|
||||
TypedDictType::Class(defining_class) => Some(defining_class.definition(db)),
|
||||
TypedDictType::Class(defining_class) => defining_class.definition(db),
|
||||
TypedDictType::Synthesized(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,18 +55,16 @@ class TypedDictFallback(Mapping[str, object], metaclass=ABCMeta):
|
||||
class NamedTupleFallback(tuple[Any, ...]):
|
||||
_field_defaults: ClassVar[dict[str, Any]]
|
||||
_fields: ClassVar[tuple[str, ...]]
|
||||
# Allow any attribute access since we don't know the actual fields.
|
||||
def __getattr__(self, name: str, /) -> Any: ...
|
||||
# __orig_bases__ sometimes exists on <3.12, but not consistently
|
||||
# So we only add it to the stub on 3.12+.
|
||||
if sys.version_info >= (3, 12):
|
||||
__orig_bases__: ClassVar[tuple[Any, ...]]
|
||||
|
||||
@overload
|
||||
def __init__(self, typename: str, fields: Iterable[tuple[str, Any]], /) -> None: ...
|
||||
@overload
|
||||
@typing_extensions.deprecated(
|
||||
"Creating a typing.NamedTuple using keyword arguments is deprecated and support will be removed in Python 3.15"
|
||||
)
|
||||
def __init__(self, typename: str, fields: None = None, /, **kwargs: Any) -> None: ...
|
||||
# For instance construction when field names are unknown: Point(1, 2).
|
||||
def __new__(cls, *args: Any, **kwargs: Any) -> typing_extensions.Self: ...
|
||||
def __init__(self, *args: Any, **kwargs: Any) -> None: ...
|
||||
@classmethod
|
||||
def _make(cls, iterable: Iterable[Any]) -> typing_extensions.Self: ...
|
||||
def _asdict(self) -> dict[str, Any]: ...
|
||||
|
||||
Reference in New Issue
Block a user