Compare commits
2 Commits
dcreager/s
...
alex/let-c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0787d8f543 | ||
|
|
3e0299488e |
@@ -4025,7 +4025,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, ...]
|
||||
|
||||
@@ -1708,6 +1708,44 @@ class Foo(type("Ba<CURSOR>r", (), {})):
|
||||
assert_snapshot!(test.goto_definition(), @"No goto target found");
|
||||
}
|
||||
|
||||
/// goto-definition on a dynamic namedtuple class literal (created via `collections.namedtuple()`)
|
||||
#[test]
|
||||
fn goto_definition_dynamic_namedtuple_literal() {
|
||||
let test = CursorTest::builder()
|
||||
.source(
|
||||
"main.py",
|
||||
r#"
|
||||
from collections import namedtuple
|
||||
|
||||
Point = namedtuple("Point", ["x", "y"])
|
||||
|
||||
p = Poi<CURSOR>nt(1, 2)
|
||||
"#,
|
||||
)
|
||||
.build();
|
||||
|
||||
assert_snapshot!(test.goto_definition(), @r#"
|
||||
info[goto-definition]: Go to definition
|
||||
--> main.py:6:5
|
||||
|
|
||||
4 | Point = namedtuple("Point", ["x", "y"])
|
||||
5 |
|
||||
6 | p = Point(1, 2)
|
||||
| ^^^^^ Clicking here
|
||||
|
|
||||
info: Found 1 definition
|
||||
--> main.py:4:1
|
||||
|
|
||||
2 | from collections import namedtuple
|
||||
3 |
|
||||
4 | Point = namedtuple("Point", ["x", "y"])
|
||||
| -----
|
||||
5 |
|
||||
6 | p = Point(1, 2)
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
// TODO: Should only list `a: int`
|
||||
#[test]
|
||||
fn redeclarations() {
|
||||
|
||||
@@ -367,7 +367,7 @@ def f_wrong(c: Callable[[], None]):
|
||||
# error: [unresolved-attribute] "Object of type `() -> None` has no attribute `__qualname__`"
|
||||
c.__qualname__
|
||||
|
||||
# error: [unresolved-attribute] "Unresolved attribute `__qualname__` on type `() -> None`."
|
||||
# error: [unresolved-attribute] "Unresolved attribute `__qualname__` on type `() -> None`"
|
||||
c.__qualname__ = "my_callable"
|
||||
```
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Unsupported special types
|
||||
|
||||
We do not understand the functional syntax for creating `NamedTuple`s, `TypedDict`s or `Enum`s yet.
|
||||
But we also do not emit false positives when these are used in type expressions.
|
||||
We do not understand the functional syntax for creating `TypedDict`s or `Enum`s yet. But we also do
|
||||
not emit false positives when these are used in type expressions.
|
||||
|
||||
```py
|
||||
import collections
|
||||
@@ -11,8 +11,6 @@ import typing
|
||||
MyEnum = enum.Enum("MyEnum", ["foo", "bar", "baz"])
|
||||
MyIntEnum = enum.IntEnum("MyIntEnum", ["foo", "bar", "baz"])
|
||||
MyTypedDict = typing.TypedDict("MyTypedDict", {"foo": int})
|
||||
MyNamedTuple1 = typing.NamedTuple("MyNamedTuple1", [("foo", int)])
|
||||
MyNamedTuple2 = collections.namedtuple("MyNamedTuple2", ["foo"])
|
||||
|
||||
def f(a: MyEnum, b: MyTypedDict, c: MyNamedTuple1, d: MyNamedTuple2): ...
|
||||
def f(a: MyEnum, b: MyTypedDict): ...
|
||||
```
|
||||
|
||||
@@ -84,17 +84,489 @@ alice.id = 42
|
||||
bob.age = None
|
||||
```
|
||||
|
||||
Alternative functional syntax:
|
||||
Alternative functional syntax with a list of tuples:
|
||||
|
||||
```py
|
||||
Person2 = NamedTuple("Person", [("id", int), ("name", str)])
|
||||
alice2 = Person2(1, "Alice")
|
||||
|
||||
# TODO: should be an error
|
||||
# error: [missing-argument]
|
||||
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 a tuple of tuples:
|
||||
|
||||
```py
|
||||
Person3 = NamedTuple("Person", (("id", int), ("name", str)))
|
||||
alice3 = Person3(1, "Alice")
|
||||
|
||||
reveal_type(alice3.id) # revealed: int
|
||||
reveal_type(alice3.name) # revealed: str
|
||||
```
|
||||
|
||||
Functional syntax with a tuple of lists:
|
||||
|
||||
```py
|
||||
Person4 = NamedTuple("Person", (["id", int], ["name", str]))
|
||||
alice4 = Person4(1, "Alice")
|
||||
|
||||
reveal_type(alice4.id) # revealed: int
|
||||
reveal_type(alice4.name) # revealed: str
|
||||
```
|
||||
|
||||
Functional syntax with a list of lists:
|
||||
|
||||
```py
|
||||
Person5 = NamedTuple("Person", [["id", int], ["name", str]])
|
||||
alice5 = Person5(1, "Alice")
|
||||
|
||||
reveal_type(alice5.id) # revealed: int
|
||||
reveal_type(alice5.name) # revealed: str
|
||||
```
|
||||
|
||||
### Functional syntax with variable name
|
||||
|
||||
When the typename is passed via a variable, we can extract it from the inferred literal string type:
|
||||
|
||||
```py
|
||||
from typing import NamedTuple
|
||||
|
||||
name = "Person"
|
||||
Person = NamedTuple(name, [("id", int), ("name", str)])
|
||||
|
||||
p = Person(1, "Alice")
|
||||
reveal_type(p.id) # revealed: int
|
||||
reveal_type(p.name) # revealed: str
|
||||
```
|
||||
|
||||
### Functional syntax with tuple variable fields
|
||||
|
||||
When fields are passed via a tuple variable, we can extract the literal field names and types from
|
||||
the inferred tuple type:
|
||||
|
||||
```py
|
||||
from typing import NamedTuple
|
||||
from ty_extensions import static_assert, is_subtype_of, reveal_mro
|
||||
|
||||
fields = (("host", str), ("port", int))
|
||||
Url = NamedTuple("Url", fields)
|
||||
|
||||
url = Url("localhost", 8080)
|
||||
reveal_type(url.host) # revealed: str
|
||||
reveal_type(url.port) # revealed: int
|
||||
|
||||
# Generic types are also correctly converted to instance types.
|
||||
generic_fields = (("items", list[int]), ("mapping", dict[str, bool]))
|
||||
Container = NamedTuple("Container", generic_fields)
|
||||
container = Container([1, 2, 3], {"a": True})
|
||||
reveal_type(container.items) # revealed: list[int]
|
||||
reveal_type(container.mapping) # revealed: dict[str, bool]
|
||||
|
||||
# MRO includes the properly specialized tuple type.
|
||||
# revealed: (<class 'Url'>, <class 'tuple[str, int]'>, <class 'object'>)
|
||||
reveal_mro(Url)
|
||||
|
||||
static_assert(is_subtype_of(Url, tuple[str, int]))
|
||||
|
||||
# Invalid type expressions in fields produce a diagnostic.
|
||||
invalid_fields = (("x", 42),) # 42 is not a valid type
|
||||
# error: [invalid-type-form] "Invalid type `Literal[42]` in `NamedTuple` field type"
|
||||
InvalidNT = NamedTuple("InvalidNT", invalid_fields)
|
||||
reveal_type(InvalidNT) # revealed: <class 'InvalidNT'>
|
||||
|
||||
# Unpacking works correctly with the field types.
|
||||
host, port = url
|
||||
reveal_type(host) # revealed: str
|
||||
reveal_type(port) # revealed: int
|
||||
|
||||
# error: [invalid-assignment] "Too many values to unpack: Expected 1"
|
||||
(only_one,) = url
|
||||
|
||||
# error: [invalid-assignment] "Not enough values to unpack: Expected 3"
|
||||
a, b, c = url
|
||||
|
||||
# Indexing works correctly.
|
||||
reveal_type(url[0]) # revealed: str
|
||||
reveal_type(url[1]) # revealed: int
|
||||
|
||||
# error: [index-out-of-bounds]
|
||||
url[2]
|
||||
```
|
||||
|
||||
### Functional syntax with variadic tuple fields
|
||||
|
||||
When fields are passed as a variadic tuple (e.g., `tuple[..., *tuple[T, ...]]`), we cannot determine
|
||||
the exact field count statically. In this case, we fall back to unknown fields:
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.11"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing import NamedTuple
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
# Variadic tuple - we can't determine the exact fields statically.
|
||||
def get_fields() -> tuple[tuple[str, type[int]], *tuple[tuple[str, type[str]], ...]]:
|
||||
return (("x", int), ("y", str))
|
||||
|
||||
fields = get_fields()
|
||||
NT = NamedTuple("NT", fields)
|
||||
|
||||
# Fields are unknown, so attribute access returns Any and MRO has Unknown tuple.
|
||||
reveal_type(NT) # revealed: <class 'NT'>
|
||||
reveal_mro(NT) # revealed: (<class 'NT'>, <class 'tuple[Unknown, ...]'>, <class 'object'>)
|
||||
reveal_type(NT(1, "a").x) # revealed: Any
|
||||
```
|
||||
|
||||
Similarly for `collections.namedtuple`:
|
||||
|
||||
```py
|
||||
import collections
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
def get_field_names() -> tuple[str, *tuple[str, ...]]:
|
||||
return ("x", "y")
|
||||
|
||||
field_names = get_field_names()
|
||||
NT = collections.namedtuple("NT", field_names)
|
||||
|
||||
# Fields are unknown, so attribute access returns Any and MRO has Unknown tuple.
|
||||
reveal_type(NT) # revealed: <class 'NT'>
|
||||
reveal_mro(NT) # revealed: (<class 'NT'>, <class 'tuple[Unknown, ...]'>, <class 'object'>)
|
||||
reveal_type(NT(1, 2).x) # revealed: Any
|
||||
```
|
||||
|
||||
### Class inheriting from functional NamedTuple
|
||||
|
||||
Classes can inherit from functional namedtuples. The constructor parameters and field types are
|
||||
properly inherited:
|
||||
|
||||
```py
|
||||
from typing import NamedTuple
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
class Url(NamedTuple("Url", [("host", str), ("path", str)])):
|
||||
pass
|
||||
|
||||
reveal_type(Url) # revealed: <class 'Url'>
|
||||
# revealed: (<class 'mdtest_snippet.Url @ src/mdtest_snippet.py:4:7'>, <class 'mdtest_snippet.Url @ src/mdtest_snippet.py:4:11'>, <class 'tuple[str, str]'>, <class 'object'>)
|
||||
reveal_mro(Url)
|
||||
reveal_type(Url.__new__) # revealed: (cls: type, host: str, path: str) -> Url
|
||||
|
||||
# Constructor works with the inherited fields.
|
||||
url = Url("example.com", "/path")
|
||||
reveal_type(url) # revealed: Url
|
||||
reveal_type(url.host) # revealed: str
|
||||
reveal_type(url.path) # revealed: str
|
||||
|
||||
# Error handling works correctly.
|
||||
# error: [missing-argument]
|
||||
Url("example.com")
|
||||
|
||||
# error: [too-many-positional-arguments]
|
||||
Url("example.com", "/path", "extra")
|
||||
```
|
||||
|
||||
Subclasses can add methods that use inherited fields:
|
||||
|
||||
```py
|
||||
from typing import NamedTuple
|
||||
from typing_extensions import Self
|
||||
|
||||
class Url(NamedTuple("Url", [("host", str), ("port", int)])):
|
||||
def with_port(self, port: int) -> Self:
|
||||
reveal_type(self.host) # revealed: str
|
||||
reveal_type(self.port) # revealed: int
|
||||
return self._replace(port=port)
|
||||
|
||||
url = Url("localhost", 8080)
|
||||
reveal_type(url.with_port(9000)) # revealed: Url
|
||||
```
|
||||
|
||||
For `class Foo(namedtuple("Foo", ...)): ...`, the inner call creates a namedtuple class, but the
|
||||
outer class is just a regular class inheriting from it. This is equivalent to:
|
||||
|
||||
```py
|
||||
class _Foo(NamedTuple): ...
|
||||
|
||||
class Foo(_Foo): # Regular class, not a namedtuple
|
||||
...
|
||||
```
|
||||
|
||||
Because the outer class is not itself a namedtuple, it can use `super()` and override `__new__`:
|
||||
|
||||
```py
|
||||
from collections import namedtuple
|
||||
from typing import NamedTuple
|
||||
|
||||
class ExtType(namedtuple("ExtType", "code data")):
|
||||
"""Override __new__ to add validation."""
|
||||
|
||||
def __new__(cls, code, data):
|
||||
if not isinstance(code, int):
|
||||
raise TypeError("code must be int")
|
||||
return super().__new__(cls, code, data)
|
||||
|
||||
class Url(NamedTuple("Url", [("host", str), ("path", str)])):
|
||||
"""Override __new__ to normalize the path."""
|
||||
|
||||
def __new__(cls, host, path):
|
||||
if path and not path.startswith("/"):
|
||||
path = "/" + path
|
||||
return super().__new__(cls, host, path)
|
||||
|
||||
# Both work correctly.
|
||||
ext = ExtType(42, b"hello")
|
||||
reveal_type(ext) # revealed: ExtType
|
||||
|
||||
url = Url("example.com", "path")
|
||||
reveal_type(url) # revealed: Url
|
||||
```
|
||||
|
||||
### Functional syntax with list variable fields
|
||||
|
||||
When fields are passed via a list variable (not a literal), the field names cannot be determined
|
||||
statically. Attribute access returns `Any` and the constructor accepts any arguments:
|
||||
|
||||
```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:
|
||||
# Fields are unknown, so attribute access returns Any.
|
||||
reveal_type(self.host) # revealed: Any
|
||||
reveal_type(self.port) # revealed: Any
|
||||
reveal_type(self.unknown) # revealed: Any
|
||||
return self._replace(port=port)
|
||||
```
|
||||
|
||||
When constructing a namedtuple directly with dynamically-defined fields, keyword arguments are
|
||||
accepted because the constructor uses a gradual signature:
|
||||
|
||||
```py
|
||||
import collections
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
CheckerConfig = ["duration", "video_fps", "audio_sample_rate"]
|
||||
GroundTruth = collections.namedtuple("GroundTruth", " ".join(CheckerConfig))
|
||||
|
||||
# No error - fields are unknown, so any keyword arguments are accepted
|
||||
config = GroundTruth(duration=0, video_fps=30, audio_sample_rate=44100)
|
||||
reveal_type(config) # revealed: GroundTruth
|
||||
reveal_type(config.duration) # revealed: Any
|
||||
|
||||
# Namedtuples with unknown fields inherit from tuple[Unknown, ...] to avoid false positives.
|
||||
# revealed: (<class 'GroundTruth'>, <class 'tuple[Unknown, ...]'>, <class 'object'>)
|
||||
reveal_mro(GroundTruth)
|
||||
|
||||
# No index-out-of-bounds error since the tuple length is unknown.
|
||||
reveal_type(config[0]) # revealed: Unknown
|
||||
reveal_type(config[100]) # revealed: Unknown
|
||||
```
|
||||
|
||||
### Functional syntax signature validation
|
||||
|
||||
The `collections.namedtuple` function accepts `str | Iterable[str]` for `field_names`:
|
||||
|
||||
```py
|
||||
import collections
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
# String field names (space-separated)
|
||||
Point1 = collections.namedtuple("Point", "x y")
|
||||
reveal_type(Point1) # revealed: <class 'Point'>
|
||||
reveal_mro(Point1) # revealed: (<class 'Point'>, <class 'tuple[Any, Any]'>, <class 'object'>)
|
||||
|
||||
# String field names with multiple spaces
|
||||
Point1a = collections.namedtuple("Point", "x y")
|
||||
reveal_type(Point1a) # revealed: <class 'Point'>
|
||||
reveal_mro(Point1a) # revealed: (<class 'Point'>, <class 'tuple[Any, Any]'>, <class 'object'>)
|
||||
|
||||
# String field names (comma-separated also works at runtime)
|
||||
Point2 = collections.namedtuple("Point", "x, y")
|
||||
reveal_type(Point2) # revealed: <class 'Point'>
|
||||
reveal_mro(Point2) # revealed: (<class 'Point'>, <class 'tuple[Any, Any]'>, <class 'object'>)
|
||||
|
||||
# List of strings
|
||||
Point3 = collections.namedtuple("Point", ["x", "y"])
|
||||
reveal_type(Point3) # revealed: <class 'Point'>
|
||||
reveal_mro(Point3) # revealed: (<class 'Point'>, <class 'tuple[Any, Any]'>, <class 'object'>)
|
||||
|
||||
# Tuple of strings
|
||||
Point4 = collections.namedtuple("Point", ("x", "y"))
|
||||
reveal_type(Point4) # revealed: <class 'Point'>
|
||||
reveal_mro(Point4) # revealed: (<class 'Point'>, <class 'tuple[Any, Any]'>, <class 'object'>)
|
||||
|
||||
# Invalid: integer is not a valid typename
|
||||
# error: [invalid-argument-type]
|
||||
Invalid = collections.namedtuple(123, ["x", "y"])
|
||||
reveal_type(Invalid) # revealed: <class '<unknown>'>
|
||||
reveal_mro(Invalid) # revealed: (<class '<unknown>'>, <class 'tuple[Any, Any]'>, <class 'object'>)
|
||||
|
||||
# Invalid: too many positional arguments
|
||||
# error: [too-many-positional-arguments] "Too many positional arguments to function `namedtuple`: expected 2, got 4"
|
||||
TooMany = collections.namedtuple("TooMany", "x", "y", "z")
|
||||
reveal_type(TooMany) # revealed: <class 'TooMany'>
|
||||
```
|
||||
|
||||
The `typing.NamedTuple` function accepts `Iterable[tuple[str, Any]]` for `fields`:
|
||||
|
||||
```py
|
||||
from typing import NamedTuple
|
||||
|
||||
# List of tuples
|
||||
Person1 = NamedTuple("Person", [("name", str), ("age", int)])
|
||||
reveal_type(Person1) # revealed: <class 'Person'>
|
||||
|
||||
# Tuple of tuples
|
||||
Person2 = NamedTuple("Person", (("name", str), ("age", int)))
|
||||
reveal_type(Person2) # revealed: <class 'Person'>
|
||||
|
||||
# Invalid: integer is not a valid typename
|
||||
# error: [invalid-argument-type]
|
||||
NamedTuple(123, [("name", str)])
|
||||
|
||||
# Invalid: too many positional arguments
|
||||
# error: [too-many-positional-arguments] "Too many positional arguments to function `NamedTuple`: expected 2, got 4"
|
||||
TooMany = NamedTuple("TooMany", [("x", int)], "extra", "args")
|
||||
reveal_type(TooMany) # revealed: <class 'TooMany'>
|
||||
```
|
||||
|
||||
### Keyword arguments for `collections.namedtuple`
|
||||
|
||||
The `collections.namedtuple` function accepts `rename`, `defaults`, and `module` keyword arguments:
|
||||
|
||||
```py
|
||||
import collections
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
# `rename=True` replaces invalid identifiers with positional names
|
||||
Point = collections.namedtuple("Point", ["x", "class", "_y", "z", "z"], rename=True)
|
||||
reveal_type(Point) # revealed: <class 'Point'>
|
||||
reveal_type(Point.__new__) # revealed: (cls: type, x: Any, _1: Any, _2: Any, z: Any, _4: Any) -> Point
|
||||
reveal_mro(Point) # revealed: (<class 'Point'>, <class 'tuple[Any, Any, Any, Any, Any]'>, <class 'object'>)
|
||||
p = Point(1, 2, 3, 4, 5)
|
||||
reveal_type(p.x) # revealed: Any
|
||||
reveal_type(p._1) # revealed: Any
|
||||
reveal_type(p._2) # revealed: Any
|
||||
reveal_type(p.z) # revealed: Any
|
||||
reveal_type(p._4) # revealed: Any
|
||||
|
||||
# Truthy non-bool values for `rename` are also handled, but emit a diagnostic
|
||||
# error: [invalid-argument-type] "Invalid argument to parameter `rename` of `namedtuple()`"
|
||||
Point2 = collections.namedtuple("Point2", ["_x", "class"], rename=1)
|
||||
reveal_type(Point2) # revealed: <class 'Point2'>
|
||||
reveal_type(Point2.__new__) # revealed: (cls: type, _0: Any, _1: Any) -> Point2
|
||||
|
||||
# `defaults` provides default values for the rightmost fields
|
||||
Person = collections.namedtuple("Person", ["name", "age", "city"], defaults=["Unknown"])
|
||||
reveal_type(Person) # revealed: <class 'Person'>
|
||||
reveal_type(Person.__new__) # revealed: (cls: type, name: Any, age: Any, city: Any = ...) -> Person
|
||||
reveal_mro(Person) # revealed: (<class 'Person'>, <class 'tuple[Any, Any, Any]'>, <class 'object'>)
|
||||
# Can create with all fields
|
||||
person1 = Person("Alice", 30, "NYC")
|
||||
# Can omit the field with default
|
||||
person2 = Person("Bob", 25)
|
||||
reveal_type(person1.city) # revealed: Any
|
||||
reveal_type(person2.city) # revealed: Any
|
||||
|
||||
# `module` is valid but doesn't affect type checking
|
||||
Config = collections.namedtuple("Config", ["host", "port"], module="myapp")
|
||||
reveal_type(Config) # revealed: <class 'Config'>
|
||||
|
||||
# When more defaults are provided than fields, we treat all fields as having defaults.
|
||||
# TODO: This should emit a diagnostic since it would fail at runtime.
|
||||
TooManyDefaults = collections.namedtuple("TooManyDefaults", ["x", "y"], defaults=("a", "b", "c"))
|
||||
reveal_type(TooManyDefaults) # revealed: <class 'TooManyDefaults'>
|
||||
reveal_type(TooManyDefaults.__new__) # revealed: (cls: type, x: Any = ..., y: Any = ...) -> TooManyDefaults
|
||||
|
||||
# Unknown keyword arguments produce an error
|
||||
# error: [unknown-argument]
|
||||
Bad1 = collections.namedtuple("Bad1", ["x", "y"], foobarbaz=42)
|
||||
reveal_type(Bad1) # revealed: <class 'Bad1'>
|
||||
reveal_mro(Bad1) # revealed: (<class 'Bad1'>, <class 'tuple[Any, Any]'>, <class 'object'>)
|
||||
|
||||
# Multiple unknown keyword arguments
|
||||
# error: [unknown-argument]
|
||||
# error: [unknown-argument]
|
||||
Bad2 = collections.namedtuple("Bad2", ["x"], invalid1=True, invalid2=False)
|
||||
reveal_type(Bad2) # revealed: <class 'Bad2'>
|
||||
reveal_mro(Bad2) # revealed: (<class 'Bad2'>, <class 'tuple[Any]'>, <class 'object'>)
|
||||
|
||||
# Invalid type for `defaults` (not Iterable[Any] | None)
|
||||
# error: [invalid-argument-type] "Invalid argument to parameter `defaults` of `namedtuple()`"
|
||||
Bad3 = collections.namedtuple("Bad3", ["x"], defaults=123)
|
||||
reveal_type(Bad3) # revealed: <class 'Bad3'>
|
||||
|
||||
# Invalid type for `module` (not str | None)
|
||||
# error: [invalid-argument-type] "Invalid argument to parameter `module` of `namedtuple()`"
|
||||
Bad4 = collections.namedtuple("Bad4", ["x"], module=456)
|
||||
reveal_type(Bad4) # revealed: <class 'Bad4'>
|
||||
|
||||
# Invalid type for `field_names` (not str | Iterable[str])
|
||||
# error: [invalid-argument-type] "Invalid argument to parameter `field_names` of `namedtuple()`"
|
||||
Bad5 = collections.namedtuple("Bad5", 12345)
|
||||
reveal_type(Bad5) # revealed: <class 'Bad5'>
|
||||
```
|
||||
|
||||
### Keyword arguments for `typing.NamedTuple`
|
||||
|
||||
The `typing.NamedTuple` function does not accept any keyword arguments:
|
||||
|
||||
```py
|
||||
from typing import NamedTuple
|
||||
|
||||
# error: [unknown-argument]
|
||||
Bad3 = NamedTuple("Bad3", [("x", int)], rename=True)
|
||||
|
||||
# error: [unknown-argument]
|
||||
Bad4 = NamedTuple("Bad4", [("x", int)], defaults=[0])
|
||||
|
||||
# error: [unknown-argument]
|
||||
Bad5 = NamedTuple("Bad5", [("x", int)], foobarbaz=42)
|
||||
|
||||
# Invalid type for `fields` (not an iterable)
|
||||
# error: [invalid-argument-type] "Invalid argument to parameter `fields` of `NamedTuple()`"
|
||||
Bad6 = NamedTuple("Bad6", 12345)
|
||||
reveal_type(Bad6) # revealed: <class 'Bad6'>
|
||||
```
|
||||
|
||||
### Starred and double-starred arguments
|
||||
|
||||
When starred (`*args`) or double-starred (`**kwargs`) arguments are used, we fall back to normal
|
||||
call binding since we can't statically determine the arguments. This results in `NamedTupleFallback`
|
||||
being returned:
|
||||
|
||||
```py
|
||||
import collections
|
||||
from typing import NamedTuple
|
||||
|
||||
args = ("Point", ["x", "y"])
|
||||
kwargs = {"rename": True}
|
||||
|
||||
# Starred positional arguments - falls back to NamedTupleFallback
|
||||
Point1 = collections.namedtuple(*args)
|
||||
reveal_type(Point1) # revealed: type[NamedTupleFallback]
|
||||
|
||||
# Ideally we'd catch this false negative,
|
||||
# but it's unclear if it's worth the added complexity
|
||||
Point2 = NamedTuple(*args)
|
||||
reveal_type(Point2) # revealed: type[NamedTupleFallback]
|
||||
|
||||
# Double-starred keyword arguments - falls back to NamedTupleFallback
|
||||
Point3 = collections.namedtuple("Point", ["x", "y"], **kwargs)
|
||||
reveal_type(Point3) # revealed: type[NamedTupleFallback]
|
||||
|
||||
Point4 = NamedTuple("Point", [("x", int), ("y", int)], **kwargs)
|
||||
reveal_type(Point4) # revealed: type[NamedTupleFallback]
|
||||
```
|
||||
|
||||
### Definition
|
||||
@@ -154,6 +626,84 @@ class D(
|
||||
class E(NamedTuple, Protocol): ...
|
||||
```
|
||||
|
||||
However, as explained above, for `class Foo(namedtuple("Foo", ...)): ...` the outer class is not
|
||||
itself a namedtuple—it just inherits from one. So it can use multiple inheritance freely:
|
||||
|
||||
```py
|
||||
from abc import ABC
|
||||
from collections import namedtuple
|
||||
from typing import NamedTuple
|
||||
|
||||
class Point(namedtuple("Point", ["x", "y"]), ABC):
|
||||
"""No error - functional namedtuple inheritance allows multiple inheritance."""
|
||||
|
||||
class Url(NamedTuple("Url", [("host", str), ("port", int)]), ABC):
|
||||
"""No error - typing.NamedTuple functional syntax also allows multiple inheritance."""
|
||||
|
||||
p = Point(1, 2)
|
||||
reveal_type(p.x) # revealed: Any
|
||||
reveal_type(p.y) # revealed: Any
|
||||
|
||||
u = Url("localhost", 8080)
|
||||
reveal_type(u.host) # revealed: str
|
||||
reveal_type(u.port) # revealed: int
|
||||
```
|
||||
|
||||
### Inherited tuple methods
|
||||
|
||||
Namedtuples inherit methods from their tuple base class, including `count`, `index`, and comparison
|
||||
methods (`__lt__`, `__le__`, `__gt__`, `__ge__`).
|
||||
|
||||
```py
|
||||
from collections import namedtuple
|
||||
from typing import NamedTuple
|
||||
|
||||
# typing.NamedTuple inherits tuple methods
|
||||
class Point(NamedTuple):
|
||||
x: int
|
||||
y: int
|
||||
|
||||
p = Point(1, 2)
|
||||
reveal_type(p.count(1)) # revealed: int
|
||||
reveal_type(p.index(2)) # revealed: int
|
||||
|
||||
# collections.namedtuple also inherits tuple methods
|
||||
Person = namedtuple("Person", ["name", "age"])
|
||||
alice = Person("Alice", 30)
|
||||
reveal_type(alice.count("Alice")) # revealed: int
|
||||
reveal_type(alice.index(30)) # revealed: int
|
||||
```
|
||||
|
||||
The `@total_ordering` decorator should not emit a diagnostic, since the required `__lt__` method is
|
||||
already present:
|
||||
|
||||
```py
|
||||
from collections import namedtuple
|
||||
from functools import total_ordering
|
||||
from typing import NamedTuple
|
||||
|
||||
# No error - __lt__ is inherited from the tuple base class
|
||||
@total_ordering
|
||||
class Point(namedtuple("Point", "x y")): ...
|
||||
|
||||
p1 = Point(1, 2)
|
||||
p2 = Point(3, 4)
|
||||
# TODO: should be `bool`, not `Any | Literal[False]`
|
||||
reveal_type(p1 < p2) # revealed: Any | Literal[False]
|
||||
reveal_type(p1 <= p2) # revealed: Any | Literal[True]
|
||||
|
||||
# Same for typing.NamedTuple - no error
|
||||
@total_ordering
|
||||
class Person(NamedTuple):
|
||||
name: str
|
||||
age: int
|
||||
|
||||
alice = Person("Alice", 30)
|
||||
bob = Person("Bob", 25)
|
||||
reveal_type(alice < bob) # revealed: bool
|
||||
reveal_type(alice >= bob) # revealed: bool
|
||||
```
|
||||
|
||||
### Inheriting from a `NamedTuple`
|
||||
|
||||
Inheriting from a `NamedTuple` is supported, but new fields on the subclass will not be part of the
|
||||
@@ -254,6 +804,34 @@ reveal_type(LegacyProperty[str].value.fget) # revealed: (self, /) -> str
|
||||
reveal_type(LegacyProperty("height", 3.4).value) # revealed: int | float
|
||||
```
|
||||
|
||||
Generic namedtuples can also be defined using the functional syntax with type variables in the field
|
||||
types. We don't currently support this, but mypy does:
|
||||
|
||||
```py
|
||||
from typing import NamedTuple, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
# TODO: ideally this would create a generic namedtuple class
|
||||
Pair = NamedTuple("Pair", [("first", T), ("second", T)])
|
||||
|
||||
# For now, the TypeVar is not specialized, so the field types remain as `T@Pair` and argument type
|
||||
# errors are emitted when calling the constructor.
|
||||
reveal_type(Pair) # revealed: <class 'Pair'>
|
||||
|
||||
# error: [invalid-argument-type]
|
||||
# error: [invalid-argument-type]
|
||||
reveal_type(Pair(1, 2)) # revealed: Pair
|
||||
|
||||
# error: [invalid-argument-type]
|
||||
# error: [invalid-argument-type]
|
||||
reveal_type(Pair(1, 2).first) # revealed: T@Pair
|
||||
|
||||
# error: [invalid-argument-type]
|
||||
# error: [invalid-argument-type]
|
||||
reveal_type(Pair(1, 2).second) # revealed: T@Pair
|
||||
```
|
||||
|
||||
## Attributes on `NamedTuple`
|
||||
|
||||
The following attributes are available on `NamedTuple` classes / instances:
|
||||
@@ -311,6 +889,73 @@ alice = Person(1, "Alice", 42)
|
||||
bob = Person(2, "Bob")
|
||||
```
|
||||
|
||||
## `collections.namedtuple` with tuple variable field names
|
||||
|
||||
When field names are passed via a tuple variable, we can extract the literal field names from the
|
||||
inferred tuple type. The class is properly synthesized (not a fallback), but field types are `Any`
|
||||
since `collections.namedtuple` doesn't include type annotations:
|
||||
|
||||
```py
|
||||
from collections import namedtuple
|
||||
|
||||
field_names = ("name", "age")
|
||||
Person = namedtuple("Person", field_names)
|
||||
|
||||
reveal_type(Person) # revealed: <class 'Person'>
|
||||
|
||||
alice = Person("Alice", 42)
|
||||
reveal_type(alice) # revealed: Person
|
||||
reveal_type(alice.name) # revealed: Any
|
||||
reveal_type(alice.age) # revealed: Any
|
||||
```
|
||||
|
||||
## `collections.namedtuple` with list variable field names
|
||||
|
||||
When field names are passed via a list 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:
|
||||
# Fields are unknown, so attribute access returns `Any`.
|
||||
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` 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
|
||||
```
|
||||
|
||||
## The symbol `NamedTuple` itself
|
||||
|
||||
At runtime, `NamedTuple` is a function, and we understand this:
|
||||
|
||||
@@ -39,7 +39,7 @@ info: rule `unresolved-attribute` is enabled by default
|
||||
```
|
||||
|
||||
```
|
||||
error[unresolved-attribute]: Unresolved attribute `non_existent` on type `C`.
|
||||
error[unresolved-attribute]: Unresolved attribute `non_existent` on type `C`
|
||||
--> src/mdtest_snippet.py:6:1
|
||||
|
|
||||
5 | instance = C()
|
||||
|
||||
@@ -30,6 +30,23 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/named_tuple.md
|
||||
15 |
|
||||
16 | # error: [invalid-named-tuple]
|
||||
17 | class E(NamedTuple, Protocol): ...
|
||||
18 | from abc import ABC
|
||||
19 | from collections import namedtuple
|
||||
20 | from typing import NamedTuple
|
||||
21 |
|
||||
22 | class Point(namedtuple("Point", ["x", "y"]), ABC):
|
||||
23 | """No error - functional namedtuple inheritance allows multiple inheritance."""
|
||||
24 |
|
||||
25 | class Url(NamedTuple("Url", [("host", str), ("port", int)]), ABC):
|
||||
26 | """No error - typing.NamedTuple functional syntax also allows multiple inheritance."""
|
||||
27 |
|
||||
28 | p = Point(1, 2)
|
||||
29 | reveal_type(p.x) # revealed: Any
|
||||
30 | reveal_type(p.y) # revealed: Any
|
||||
31 |
|
||||
32 | u = Url("localhost", 8080)
|
||||
33 | reveal_type(u.host) # revealed: str
|
||||
34 | reveal_type(u.port) # revealed: int
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
@@ -68,6 +85,8 @@ error[invalid-named-tuple]: NamedTuple class `E` cannot use multiple inheritance
|
||||
16 | # error: [invalid-named-tuple]
|
||||
17 | class E(NamedTuple, Protocol): ...
|
||||
| ^^^^^^^^
|
||||
18 | from abc import ABC
|
||||
19 | from collections import namedtuple
|
||||
|
|
||||
info: rule `invalid-named-tuple` is enabled by default
|
||||
|
||||
|
||||
@@ -485,7 +485,6 @@ macro_rules! todo_type {
|
||||
pub use crate::types::definition::TypeDefinition;
|
||||
use crate::types::relation::{
|
||||
HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, TypeRelation,
|
||||
default_equivalent_visitor,
|
||||
};
|
||||
pub(crate) use todo_type;
|
||||
|
||||
@@ -588,7 +587,7 @@ impl<'db> PropertyInstanceType<'db> {
|
||||
other: Self,
|
||||
inferable: InferableTypeVars<'_, 'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
self.is_equivalent_to_impl(db, other, inferable, &default_equivalent_visitor(db))
|
||||
self.is_equivalent_to_impl(db, other, inferable, &IsEquivalentVisitor::default())
|
||||
}
|
||||
|
||||
fn is_equivalent_to_impl(
|
||||
@@ -600,27 +599,27 @@ impl<'db> PropertyInstanceType<'db> {
|
||||
) -> ConstraintSet<'db> {
|
||||
let getter_equivalence = if let Some(getter) = self.getter(db) {
|
||||
let Some(other_getter) = other.getter(db) else {
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
return ConstraintSet::from(false);
|
||||
};
|
||||
getter.is_equivalent_to_impl(db, other_getter, inferable, visitor)
|
||||
} else {
|
||||
if other.getter(db).is_some() {
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
return ConstraintSet::from(false);
|
||||
}
|
||||
ConstraintSet::from_bool(db, true)
|
||||
ConstraintSet::from(true)
|
||||
};
|
||||
|
||||
let setter_equivalence = || {
|
||||
if let Some(setter) = self.setter(db) {
|
||||
let Some(other_setter) = other.setter(db) else {
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
return ConstraintSet::from(false);
|
||||
};
|
||||
setter.is_equivalent_to_impl(db, other_setter, inferable, visitor)
|
||||
} else {
|
||||
if other.setter(db).is_some() {
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
return ConstraintSet::from(false);
|
||||
}
|
||||
ConstraintSet::from_bool(db, true)
|
||||
ConstraintSet::from(true)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -3385,17 +3384,16 @@ impl<'db> Type<'db> {
|
||||
.map(|class| class.class_literal(db)),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(enum_class) = enum_class {
|
||||
if let Some(metadata) = enum_metadata(db, enum_class) {
|
||||
if let Some(resolved_name) = metadata.resolve_member(&name) {
|
||||
return Place::bound(Type::EnumLiteral(EnumLiteralType::new(
|
||||
db,
|
||||
enum_class,
|
||||
resolved_name,
|
||||
)))
|
||||
.into();
|
||||
}
|
||||
}
|
||||
if let Some(enum_class) = enum_class
|
||||
&& let Some(metadata) = enum_metadata(db, enum_class)
|
||||
&& let Some(resolved_name) = metadata.resolve_member(&name)
|
||||
{
|
||||
return Place::bound(Type::EnumLiteral(EnumLiteralType::new(
|
||||
db,
|
||||
enum_class,
|
||||
resolved_name,
|
||||
)))
|
||||
.into();
|
||||
}
|
||||
|
||||
let class_attr_plain = self.find_name_in_mro_with_policy(db, name_str, policy).expect(
|
||||
@@ -4403,10 +4401,6 @@ impl<'db> Type<'db> {
|
||||
.into()
|
||||
}
|
||||
|
||||
Type::SpecialForm(SpecialFormType::NamedTuple) => {
|
||||
Binding::single(self, Signature::todo("functional `NamedTuple` syntax")).into()
|
||||
}
|
||||
|
||||
Type::GenericAlias(_) => {
|
||||
// TODO annotated return type on `__new__` or metaclass `__call__`
|
||||
// TODO check call vs signatures of `__new__` and/or `__init__`
|
||||
@@ -5068,16 +5062,15 @@ 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((_, Some(specialization))) =
|
||||
if class.is_known(db, KnownClass::Generator)
|
||||
&& let Some((_, Some(specialization))) =
|
||||
class.static_class_literal_specialized(db, None)
|
||||
{
|
||||
if let [_, _, return_ty] = specialization.types(db) {
|
||||
return Some(*return_ty);
|
||||
}
|
||||
}
|
||||
&& let [_, _, return_ty] = specialization.types(db)
|
||||
{
|
||||
Some(*return_ty)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
None
|
||||
};
|
||||
|
||||
match self {
|
||||
@@ -8059,15 +8052,10 @@ impl<'db> TypeVarInstance<'db> {
|
||||
let typevar_node = typevar.node(&module);
|
||||
let bound =
|
||||
definition_expression_type(db, definition, typevar_node.bound.as_ref()?);
|
||||
let constraints = if let Some(tuple) = bound
|
||||
.as_nominal_instance()
|
||||
.and_then(|instance| instance.tuple_spec(db))
|
||||
let constraints = if let Some(tuple) = bound.tuple_instance_spec(db)
|
||||
&& let Tuple::Fixed(tuple) = tuple.into_owned()
|
||||
{
|
||||
if let Tuple::Fixed(tuple) = tuple.into_owned() {
|
||||
tuple.owned_elements()
|
||||
} else {
|
||||
vec![Type::unknown()].into_boxed_slice()
|
||||
}
|
||||
tuple.owned_elements()
|
||||
} else {
|
||||
vec![Type::unknown()].into_boxed_slice()
|
||||
};
|
||||
@@ -9145,13 +9133,13 @@ impl<'db> AwaitError<'db> {
|
||||
""
|
||||
};
|
||||
diag.info(format_args!("`__await__` is{possibly} not callable"));
|
||||
if let Some(definition) = bindings.callable_type().definition(db) {
|
||||
if let Some(definition_range) = definition.focus_range(db) {
|
||||
diag.annotate(
|
||||
Annotation::secondary(definition_range.into())
|
||||
.message("attribute defined here"),
|
||||
);
|
||||
}
|
||||
if let Some(definition) = bindings.callable_type().definition(db)
|
||||
&& let Some(definition_range) = definition.focus_range(db)
|
||||
{
|
||||
diag.annotate(
|
||||
Annotation::secondary(definition_range.into())
|
||||
.message("attribute defined here"),
|
||||
);
|
||||
}
|
||||
}
|
||||
Self::Call(CallDunderError::PossiblyUnbound(bindings)) => {
|
||||
@@ -9165,13 +9153,12 @@ impl<'db> AwaitError<'db> {
|
||||
}
|
||||
Self::Call(CallDunderError::MethodNotAvailable) => {
|
||||
diag.info("`__await__` is missing");
|
||||
if let Some(type_definition) = context_expression_type.definition(db) {
|
||||
if let Some(definition_range) = type_definition.focus_range(db) {
|
||||
diag.annotate(
|
||||
Annotation::secondary(definition_range.into())
|
||||
.message("type defined here"),
|
||||
);
|
||||
}
|
||||
if let Some(type_definition) = context_expression_type.definition(db)
|
||||
&& let Some(definition_range) = type_definition.focus_range(db)
|
||||
{
|
||||
diag.annotate(
|
||||
Annotation::secondary(definition_range.into()).message("type defined here"),
|
||||
);
|
||||
}
|
||||
}
|
||||
Self::InvalidReturnType(return_type, bindings) => {
|
||||
@@ -10507,7 +10494,7 @@ impl<'db> CallableType<'db> {
|
||||
disjointness_visitor: &IsDisjointVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
if other.is_function_like(db) && !self.is_function_like(db) {
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
return ConstraintSet::from(false);
|
||||
}
|
||||
|
||||
self.signatures(db).has_relation_to_impl(
|
||||
@@ -10531,20 +10518,13 @@ impl<'db> CallableType<'db> {
|
||||
visitor: &IsEquivalentVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
if self == other {
|
||||
return ConstraintSet::from_bool(db, true);
|
||||
return ConstraintSet::from(true);
|
||||
}
|
||||
|
||||
ConstraintSet::from_bool(db, self.is_function_like(db) == other.is_function_like(db)).and(
|
||||
db,
|
||||
|| {
|
||||
self.signatures(db).is_equivalent_to_impl(
|
||||
db,
|
||||
other.signatures(db),
|
||||
inferable,
|
||||
visitor,
|
||||
)
|
||||
},
|
||||
)
|
||||
ConstraintSet::from(self.is_function_like(db) == other.is_function_like(db)).and(db, || {
|
||||
self.signatures(db)
|
||||
.is_equivalent_to_impl(db, other.signatures(db), inferable, visitor)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10729,7 +10709,7 @@ impl<'db> KnownBoundMethodType<'db> {
|
||||
) => self_property.when_equivalent_to(db, other_property, inferable),
|
||||
|
||||
(KnownBoundMethodType::StrStartswith(_), KnownBoundMethodType::StrStartswith(_)) => {
|
||||
ConstraintSet::from_bool(db, self == other)
|
||||
ConstraintSet::from(self == other)
|
||||
}
|
||||
|
||||
(
|
||||
@@ -10759,7 +10739,7 @@ impl<'db> KnownBoundMethodType<'db> {
|
||||
| (
|
||||
KnownBoundMethodType::GenericContextSpecializeConstrained(_),
|
||||
KnownBoundMethodType::GenericContextSpecializeConstrained(_),
|
||||
) => ConstraintSet::from_bool(db, true),
|
||||
) => ConstraintSet::from(true),
|
||||
|
||||
(
|
||||
KnownBoundMethodType::FunctionTypeDunderGet(_)
|
||||
@@ -10786,7 +10766,7 @@ impl<'db> KnownBoundMethodType<'db> {
|
||||
| KnownBoundMethodType::ConstraintSetSatisfies(_)
|
||||
| KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(_)
|
||||
| KnownBoundMethodType::GenericContextSpecializeConstrained(_),
|
||||
) => ConstraintSet::from_bool(db, false),
|
||||
) => ConstraintSet::from(false),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10818,7 +10798,7 @@ impl<'db> KnownBoundMethodType<'db> {
|
||||
) => self_property.is_equivalent_to_impl(db, other_property, inferable, visitor),
|
||||
|
||||
(KnownBoundMethodType::StrStartswith(_), KnownBoundMethodType::StrStartswith(_)) => {
|
||||
ConstraintSet::from_bool(db, self == other)
|
||||
ConstraintSet::from(self == other)
|
||||
}
|
||||
|
||||
(
|
||||
@@ -10832,7 +10812,7 @@ impl<'db> KnownBoundMethodType<'db> {
|
||||
| (
|
||||
KnownBoundMethodType::ConstraintSetNever,
|
||||
KnownBoundMethodType::ConstraintSetNever,
|
||||
) => ConstraintSet::from_bool(db, true),
|
||||
) => ConstraintSet::from(true),
|
||||
|
||||
(
|
||||
KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(left_constraints),
|
||||
@@ -10852,7 +10832,7 @@ impl<'db> KnownBoundMethodType<'db> {
|
||||
(
|
||||
KnownBoundMethodType::GenericContextSpecializeConstrained(left_generic_context),
|
||||
KnownBoundMethodType::GenericContextSpecializeConstrained(right_generic_context),
|
||||
) => ConstraintSet::from_bool(db, left_generic_context == right_generic_context),
|
||||
) => ConstraintSet::from(left_generic_context == right_generic_context),
|
||||
|
||||
(
|
||||
KnownBoundMethodType::FunctionTypeDunderGet(_)
|
||||
@@ -10879,7 +10859,7 @@ impl<'db> KnownBoundMethodType<'db> {
|
||||
| KnownBoundMethodType::ConstraintSetSatisfies(_)
|
||||
| KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(_)
|
||||
| KnownBoundMethodType::GenericContextSpecializeConstrained(_),
|
||||
) => ConstraintSet::from_bool(db, false),
|
||||
) => ConstraintSet::from(false),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11364,20 +11344,20 @@ impl<'db> ModuleLiteralType<'db> {
|
||||
// if it exists. First, we need to look up the `__getattr__` function in the module's scope.
|
||||
if let Some(file) = self.module(db).file(db) {
|
||||
let getattr_symbol = imported_symbol(db, file, "__getattr__", None);
|
||||
if let Place::Defined(place) = getattr_symbol.place {
|
||||
// If we found a __getattr__ function, try to call it with the name argument
|
||||
if let Ok(outcome) = place.ty.try_call(
|
||||
// If we found a __getattr__ function, try to call it with the name argument
|
||||
if let Place::Defined(place) = getattr_symbol.place
|
||||
&& let Ok(outcome) = place.ty.try_call(
|
||||
db,
|
||||
&CallArguments::positional([Type::string_literal(db, name)]),
|
||||
) {
|
||||
return PlaceAndQualifiers {
|
||||
place: Place::Defined(DefinedPlace {
|
||||
ty: outcome.return_type(db),
|
||||
..place
|
||||
}),
|
||||
qualifiers: TypeQualifiers::FROM_MODULE_GETATTR,
|
||||
};
|
||||
}
|
||||
)
|
||||
{
|
||||
return PlaceAndQualifiers {
|
||||
place: Place::Defined(DefinedPlace {
|
||||
ty: outcome.return_type(db),
|
||||
..place
|
||||
}),
|
||||
qualifiers: TypeQualifiers::FROM_MODULE_GETATTR,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11403,10 +11383,10 @@ impl<'db> ModuleLiteralType<'db> {
|
||||
// the parent module's `__init__.py` file being evaluated. That said, we have
|
||||
// chosen to always have the submodule take priority. (This matches pyright's
|
||||
// current behavior, but is the opposite of mypy's current behavior.)
|
||||
if self.available_submodule_attributes(db).contains(name) {
|
||||
if let Some(submodule) = self.resolve_submodule(db, name) {
|
||||
return Place::bound(submodule).into();
|
||||
}
|
||||
if self.available_submodule_attributes(db).contains(name)
|
||||
&& let Some(submodule) = self.resolve_submodule(db, name)
|
||||
{
|
||||
return Place::bound(submodule).into();
|
||||
}
|
||||
|
||||
let place_and_qualifiers = self
|
||||
@@ -12079,23 +12059,23 @@ impl<'db> UnionType<'db> {
|
||||
_visitor: &IsEquivalentVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
if self == other {
|
||||
return ConstraintSet::from_bool(db, true);
|
||||
return ConstraintSet::from(true);
|
||||
}
|
||||
|
||||
let self_elements = self.elements(db);
|
||||
let other_elements = other.elements(db);
|
||||
|
||||
if self_elements.len() != other_elements.len() {
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
return ConstraintSet::from(false);
|
||||
}
|
||||
|
||||
let sorted_self = self.normalized(db);
|
||||
|
||||
if sorted_self == Type::Union(other) {
|
||||
return ConstraintSet::from_bool(db, true);
|
||||
return ConstraintSet::from(true);
|
||||
}
|
||||
|
||||
ConstraintSet::from_bool(db, sorted_self == other.normalized(db))
|
||||
ConstraintSet::from(sorted_self == other.normalized(db))
|
||||
}
|
||||
|
||||
/// Identify some specific unions of known classes, currently the ones that `float` and
|
||||
@@ -12105,17 +12085,11 @@ impl<'db> UnionType<'db> {
|
||||
let mut has_float = false;
|
||||
let mut has_complex = false;
|
||||
for element in self.elements(db) {
|
||||
if let Type::NominalInstance(nominal) = element
|
||||
&& let Some(known) = nominal.known_class(db)
|
||||
{
|
||||
match known {
|
||||
KnownClass::Int => has_int = true,
|
||||
KnownClass::Float => has_float = true,
|
||||
KnownClass::Complex => has_complex = true,
|
||||
_ => return None,
|
||||
}
|
||||
} else {
|
||||
return None;
|
||||
match element.as_nominal_instance()?.known_class(db)? {
|
||||
KnownClass::Int => has_int = true,
|
||||
KnownClass::Float => has_float = true,
|
||||
KnownClass::Complex => has_complex = true,
|
||||
_ => return None,
|
||||
}
|
||||
}
|
||||
match (has_int, has_float, has_complex) {
|
||||
@@ -12489,30 +12463,30 @@ impl<'db> IntersectionType<'db> {
|
||||
_visitor: &IsEquivalentVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
if self == other {
|
||||
return ConstraintSet::from_bool(db, true);
|
||||
return ConstraintSet::from(true);
|
||||
}
|
||||
|
||||
let self_positive = self.positive(db);
|
||||
let other_positive = other.positive(db);
|
||||
|
||||
if self_positive.len() != other_positive.len() {
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
return ConstraintSet::from(false);
|
||||
}
|
||||
|
||||
let self_negative = self.negative(db);
|
||||
let other_negative = other.negative(db);
|
||||
|
||||
if self_negative.len() != other_negative.len() {
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
return ConstraintSet::from(false);
|
||||
}
|
||||
|
||||
let sorted_self = self.normalized(db);
|
||||
|
||||
if sorted_self == other {
|
||||
return ConstraintSet::from_bool(db, true);
|
||||
return ConstraintSet::from(true);
|
||||
}
|
||||
|
||||
ConstraintSet::from_bool(db, sorted_self == other.normalized(db))
|
||||
ConstraintSet::from(sorted_self == other.normalized(db))
|
||||
}
|
||||
|
||||
/// Returns an iterator over the positive elements of the intersection. If
|
||||
|
||||
@@ -1187,11 +1187,6 @@ impl<'db> Bindings<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
Some(KnownFunction::NamedTuple) => {
|
||||
overload
|
||||
.set_return_type(todo_type!("Support for functional `namedtuple`"));
|
||||
}
|
||||
|
||||
_ => {
|
||||
// Ideally, either the implementation, or exactly one of the overloads
|
||||
// of the function can have the dataclass_transform decorator applied.
|
||||
@@ -1313,7 +1308,7 @@ impl<'db> Bindings<'db> {
|
||||
if !overload.parameter_types().is_empty() {
|
||||
return;
|
||||
}
|
||||
let constraints = ConstraintSet::from_bool(db, true);
|
||||
let constraints = ConstraintSet::from(true);
|
||||
let tracked = TrackedConstraintSet::new(db, constraints);
|
||||
overload.set_return_type(Type::KnownInstance(
|
||||
KnownInstanceType::ConstraintSet(tracked),
|
||||
@@ -1324,7 +1319,7 @@ impl<'db> Bindings<'db> {
|
||||
if !overload.parameter_types().is_empty() {
|
||||
return;
|
||||
}
|
||||
let constraints = ConstraintSet::from_bool(db, false);
|
||||
let constraints = ConstraintSet::from(false);
|
||||
let tracked = TrackedConstraintSet::new(db, constraints);
|
||||
overload.set_return_type(Type::KnownInstance(
|
||||
KnownInstanceType::ConstraintSet(tracked),
|
||||
|
||||
@@ -37,7 +37,6 @@ use crate::types::member::{Member, class_member};
|
||||
use crate::types::mro::{DynamicMroError, DynamicMroErrorKind};
|
||||
use crate::types::relation::{
|
||||
HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, TypeRelation,
|
||||
default_disjoint_visitor, default_relation_visitor,
|
||||
};
|
||||
use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signature};
|
||||
use crate::types::tuple::{TupleSpec, TupleType};
|
||||
@@ -195,6 +194,7 @@ impl<'db> CodeGeneratorKind<'db> {
|
||||
Self::from_static_class(db, static_class, specialization)
|
||||
}
|
||||
ClassLiteral::Dynamic(dynamic_class) => Self::from_dynamic_class(db, dynamic_class),
|
||||
ClassLiteral::DynamicNamedTuple(_) => Some(Self::NamedTuple),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -464,6 +464,8 @@ pub enum ClassLiteral<'db> {
|
||||
Static(StaticClassLiteral<'db>),
|
||||
/// A class created dynamically via `type(name, bases, dict)`.
|
||||
Dynamic(DynamicClassLiteral<'db>),
|
||||
/// A class created via `collections.namedtuple()` or `typing.NamedTuple()`.
|
||||
DynamicNamedTuple(DynamicNamedTupleLiteral<'db>),
|
||||
}
|
||||
|
||||
impl<'db> ClassLiteral<'db> {
|
||||
@@ -472,6 +474,7 @@ impl<'db> ClassLiteral<'db> {
|
||||
match self {
|
||||
Self::Static(class) => class.name(db),
|
||||
Self::Dynamic(class) => class.name(db),
|
||||
Self::DynamicNamedTuple(namedtuple) => namedtuple.name(db),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -496,6 +499,7 @@ impl<'db> ClassLiteral<'db> {
|
||||
match self {
|
||||
Self::Static(class) => class.metaclass(db),
|
||||
Self::Dynamic(class) => class.metaclass(db),
|
||||
Self::DynamicNamedTuple(namedtuple) => namedtuple.metaclass(db),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -509,6 +513,7 @@ impl<'db> ClassLiteral<'db> {
|
||||
match self {
|
||||
Self::Static(class) => class.class_member(db, name, policy),
|
||||
Self::Dynamic(class) => class.class_member(db, name, policy),
|
||||
Self::DynamicNamedTuple(namedtuple) => namedtuple.class_member(db, name, policy),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -524,7 +529,7 @@ impl<'db> ClassLiteral<'db> {
|
||||
) -> PlaceAndQualifiers<'db> {
|
||||
match self {
|
||||
Self::Static(class) => class.class_member_from_mro(db, name, policy, mro_iter),
|
||||
Self::Dynamic(_) => {
|
||||
Self::Dynamic(_) | Self::DynamicNamedTuple(_) => {
|
||||
// Dynamic classes don't have inherited generic context and are never `object`.
|
||||
let result = MroLookup::new(db, mro_iter).class_member(name, policy, None, false);
|
||||
match result {
|
||||
@@ -550,7 +555,7 @@ impl<'db> ClassLiteral<'db> {
|
||||
pub(crate) fn default_specialization(self, db: &'db dyn Db) -> ClassType<'db> {
|
||||
match self {
|
||||
Self::Static(class) => class.default_specialization(db),
|
||||
Self::Dynamic(_) => ClassType::NonGeneric(self),
|
||||
Self::Dynamic(_) | Self::DynamicNamedTuple(_) => ClassType::NonGeneric(self),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -558,7 +563,7 @@ impl<'db> ClassLiteral<'db> {
|
||||
pub(crate) fn identity_specialization(self, db: &'db dyn Db) -> ClassType<'db> {
|
||||
match self {
|
||||
Self::Static(class) => class.identity_specialization(db),
|
||||
Self::Dynamic(_) => ClassType::NonGeneric(self),
|
||||
Self::Dynamic(_) | Self::DynamicNamedTuple(_) => ClassType::NonGeneric(self),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -576,7 +581,7 @@ impl<'db> ClassLiteral<'db> {
|
||||
pub fn is_typed_dict(self, db: &'db dyn Db) -> bool {
|
||||
match self {
|
||||
Self::Static(class) => class.is_typed_dict(db),
|
||||
Self::Dynamic(_) => false,
|
||||
Self::Dynamic(_) | Self::DynamicNamedTuple(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -584,7 +589,7 @@ impl<'db> ClassLiteral<'db> {
|
||||
pub(crate) fn is_tuple(self, db: &'db dyn Db) -> bool {
|
||||
match self {
|
||||
Self::Static(class) => class.is_tuple(db),
|
||||
Self::Dynamic(_) => false,
|
||||
Self::Dynamic(_) | Self::DynamicNamedTuple(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -606,6 +611,7 @@ impl<'db> ClassLiteral<'db> {
|
||||
match self {
|
||||
Self::Static(class) => class.file(db),
|
||||
Self::Dynamic(class) => class.scope(db).file(db),
|
||||
Self::DynamicNamedTuple(class) => class.scope(db).file(db),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -617,6 +623,7 @@ impl<'db> ClassLiteral<'db> {
|
||||
match self {
|
||||
Self::Static(class) => class.header_range(db),
|
||||
Self::Dynamic(class) => class.header_range(db),
|
||||
Self::DynamicNamedTuple(class) => class.header_range(db),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -629,8 +636,10 @@ impl<'db> ClassLiteral<'db> {
|
||||
pub(crate) fn is_final(self, db: &'db dyn Db) -> bool {
|
||||
match self {
|
||||
Self::Static(class) => class.is_final(db),
|
||||
// Dynamic classes created via `type()` cannot be marked as final.
|
||||
// Dynamic classes created via `type()`, `collections.namedtuple()`, etc. cannot be
|
||||
// marked as final.
|
||||
Self::Dynamic(_) => false,
|
||||
Self::DynamicNamedTuple(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -647,6 +656,7 @@ impl<'db> ClassLiteral<'db> {
|
||||
match self {
|
||||
Self::Static(class) => class.has_own_ordering_method(db),
|
||||
Self::Dynamic(class) => class.has_own_ordering_method(db),
|
||||
Self::DynamicNamedTuple(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -654,7 +664,7 @@ impl<'db> ClassLiteral<'db> {
|
||||
pub(crate) fn as_static(self) -> Option<StaticClassLiteral<'db>> {
|
||||
match self {
|
||||
Self::Static(class) => Some(class),
|
||||
Self::Dynamic(_) => None,
|
||||
Self::Dynamic(_) | Self::DynamicNamedTuple(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -663,6 +673,7 @@ impl<'db> ClassLiteral<'db> {
|
||||
match self {
|
||||
Self::Static(class) => Some(class.definition(db)),
|
||||
Self::Dynamic(class) => class.definition(db),
|
||||
Self::DynamicNamedTuple(namedtuple) => namedtuple.definition(db),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -674,6 +685,9 @@ impl<'db> ClassLiteral<'db> {
|
||||
match self {
|
||||
Self::Static(class) => Some(TypeDefinition::StaticClass(class.definition(db))),
|
||||
Self::Dynamic(class) => class.definition(db).map(TypeDefinition::DynamicClass),
|
||||
Self::DynamicNamedTuple(namedtuple) => {
|
||||
namedtuple.definition(db).map(TypeDefinition::DynamicClass)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -690,6 +704,7 @@ impl<'db> ClassLiteral<'db> {
|
||||
match self {
|
||||
Self::Static(class) => class.header_span(db),
|
||||
Self::Dynamic(class) => class.header_span(db),
|
||||
Self::DynamicNamedTuple(namedtuple) => namedtuple.header_span(db),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -714,6 +729,9 @@ impl<'db> ClassLiteral<'db> {
|
||||
match self {
|
||||
Self::Static(class) => class.as_disjoint_base(db),
|
||||
Self::Dynamic(class) => class.as_disjoint_base(db),
|
||||
// Dynamic namedtuples define `__slots__ = ()`, but `__slots__` must be
|
||||
// non-empty for a class to be a disjoint base.
|
||||
Self::DynamicNamedTuple(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -721,7 +739,9 @@ impl<'db> ClassLiteral<'db> {
|
||||
pub(crate) fn to_non_generic_instance(self, db: &'db dyn Db) -> Type<'db> {
|
||||
match self {
|
||||
Self::Static(class) => class.to_non_generic_instance(db),
|
||||
Self::Dynamic(_) => Type::instance(db, ClassType::NonGeneric(self)),
|
||||
Self::Dynamic(_) | Self::DynamicNamedTuple(_) => {
|
||||
Type::instance(db, ClassType::NonGeneric(self))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -742,7 +762,7 @@ impl<'db> ClassLiteral<'db> {
|
||||
) -> ClassType<'db> {
|
||||
match self {
|
||||
Self::Static(class) => class.apply_specialization(db, f),
|
||||
Self::Dynamic(_) => ClassType::NonGeneric(self),
|
||||
Self::Dynamic(_) | Self::DynamicNamedTuple(_) => ClassType::NonGeneric(self),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -756,6 +776,7 @@ impl<'db> ClassLiteral<'db> {
|
||||
match self {
|
||||
Self::Static(class) => class.instance_member(db, specialization, name),
|
||||
Self::Dynamic(class) => class.instance_member(db, name),
|
||||
Self::DynamicNamedTuple(namedtuple) => namedtuple.instance_member(db, name),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -763,7 +784,7 @@ impl<'db> ClassLiteral<'db> {
|
||||
pub(crate) fn top_materialization(self, db: &'db dyn Db) -> ClassType<'db> {
|
||||
match self {
|
||||
Self::Static(class) => class.top_materialization(db),
|
||||
Self::Dynamic(_) => ClassType::NonGeneric(self),
|
||||
Self::Dynamic(_) | Self::DynamicNamedTuple(_) => ClassType::NonGeneric(self),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -777,11 +798,16 @@ impl<'db> ClassLiteral<'db> {
|
||||
) -> PlaceAndQualifiers<'db> {
|
||||
match self {
|
||||
Self::Static(class) => class.typed_dict_member(db, specialization, name, policy),
|
||||
Self::Dynamic(_) => Place::Undefined.into(),
|
||||
Self::Dynamic(_) | Self::DynamicNamedTuple(_) => Place::Undefined.into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a new `ClassLiteral` with the given dataclass params, preserving all other fields.
|
||||
///
|
||||
/// TODO: Applying `@dataclasses.dataclass` to a `NamedTuple` subclass doesn't fail at runtime
|
||||
/// (e.g., `@dataclasses.dataclass class Foo(NamedTuple): ...`), and neither does
|
||||
/// `dataclasses.dataclass(collections.namedtuple("A", ()))`. We should either infer these
|
||||
/// accurately or emit a diagnostic on them.
|
||||
pub(crate) fn with_dataclass_params(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
@@ -792,6 +818,7 @@ impl<'db> ClassLiteral<'db> {
|
||||
Self::Dynamic(class) => {
|
||||
Self::Dynamic(class.with_dataclass_params(db, dataclass_params))
|
||||
}
|
||||
Self::DynamicNamedTuple(_) => self,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -808,6 +835,12 @@ impl<'db> From<DynamicClassLiteral<'db>> for ClassLiteral<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> From<DynamicNamedTupleLiteral<'db>> for ClassLiteral<'db> {
|
||||
fn from(literal: DynamicNamedTupleLiteral<'db>) -> Self {
|
||||
ClassLiteral::DynamicNamedTuple(literal)
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a class type, which might be a non-generic class, or a specialization of a generic
|
||||
/// class.
|
||||
#[derive(
|
||||
@@ -900,7 +933,7 @@ impl<'db> ClassType<'db> {
|
||||
) -> Option<(StaticClassLiteral<'db>, Option<Specialization<'db>>)> {
|
||||
match self {
|
||||
Self::NonGeneric(ClassLiteral::Static(class)) => Some((class, None)),
|
||||
Self::NonGeneric(ClassLiteral::Dynamic(_)) => None,
|
||||
Self::NonGeneric(ClassLiteral::Dynamic(_) | ClassLiteral::DynamicNamedTuple(_)) => None,
|
||||
Self::Generic(generic) => Some((generic.origin(db), Some(generic.specialization(db)))),
|
||||
}
|
||||
}
|
||||
@@ -914,7 +947,7 @@ impl<'db> ClassType<'db> {
|
||||
) -> Option<(StaticClassLiteral<'db>, Option<Specialization<'db>>)> {
|
||||
match self {
|
||||
Self::NonGeneric(ClassLiteral::Static(class)) => Some((class, None)),
|
||||
Self::NonGeneric(ClassLiteral::Dynamic(_)) => None,
|
||||
Self::NonGeneric(ClassLiteral::Dynamic(_) | ClassLiteral::DynamicNamedTuple(_)) => None,
|
||||
Self::Generic(generic) => Some((
|
||||
generic.origin(db),
|
||||
Some(
|
||||
@@ -1064,8 +1097,8 @@ impl<'db> ClassType<'db> {
|
||||
other,
|
||||
inferable,
|
||||
TypeRelation::Subtyping,
|
||||
&default_relation_visitor(db),
|
||||
&default_disjoint_visitor(db),
|
||||
&HasRelationToVisitor::default(),
|
||||
&IsDisjointVisitor::default(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1084,45 +1117,42 @@ impl<'db> ClassType<'db> {
|
||||
TypeRelation::Subtyping
|
||||
| TypeRelation::Redundancy
|
||||
| TypeRelation::SubtypingAssuming(_) => {
|
||||
ConstraintSet::from_bool(db, other.is_object(db))
|
||||
ConstraintSet::from(other.is_object(db))
|
||||
}
|
||||
TypeRelation::Assignability | TypeRelation::ConstraintSetAssignability => {
|
||||
ConstraintSet::from_bool(db, !other.is_final(db))
|
||||
ConstraintSet::from(!other.is_final(db))
|
||||
}
|
||||
},
|
||||
|
||||
// Protocol, Generic, and TypedDict are special bases that don't match ClassType.
|
||||
ClassBase::Protocol | ClassBase::Generic | ClassBase::TypedDict => {
|
||||
ConstraintSet::from_bool(db, false)
|
||||
ConstraintSet::from(false)
|
||||
}
|
||||
|
||||
ClassBase::Class(base) => match (base, other) {
|
||||
// Two non-generic classes match if they have the same class literal.
|
||||
(ClassType::NonGeneric(base_literal), ClassType::NonGeneric(other_literal)) => {
|
||||
ConstraintSet::from_bool(db, base_literal == other_literal)
|
||||
ConstraintSet::from(base_literal == other_literal)
|
||||
}
|
||||
|
||||
// Two generic classes match if they have the same origin and compatible specializations.
|
||||
(ClassType::Generic(base), ClassType::Generic(other)) => {
|
||||
ConstraintSet::from_bool(db, base.origin(db) == other.origin(db)).and(
|
||||
db,
|
||||
|| {
|
||||
base.specialization(db).has_relation_to_impl(
|
||||
db,
|
||||
other.specialization(db),
|
||||
inferable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
},
|
||||
)
|
||||
ConstraintSet::from(base.origin(db) == other.origin(db)).and(db, || {
|
||||
base.specialization(db).has_relation_to_impl(
|
||||
db,
|
||||
other.specialization(db),
|
||||
inferable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
// Generic and non-generic classes don't match.
|
||||
(ClassType::Generic(_), ClassType::NonGeneric(_))
|
||||
| (ClassType::NonGeneric(_), ClassType::Generic(_)) => {
|
||||
ConstraintSet::from_bool(db, false)
|
||||
ConstraintSet::from(false)
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -1137,18 +1167,18 @@ impl<'db> ClassType<'db> {
|
||||
visitor: &IsEquivalentVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
if self == other {
|
||||
return ConstraintSet::from_bool(db, true);
|
||||
return ConstraintSet::from(true);
|
||||
}
|
||||
|
||||
match (self, other) {
|
||||
// Two non-generic classes are only equivalent if they are equal (handled above).
|
||||
// A non-generic class is never equivalent to a generic class.
|
||||
(ClassType::NonGeneric(_), _) | (_, ClassType::NonGeneric(_)) => {
|
||||
ConstraintSet::from_bool(db, false)
|
||||
ConstraintSet::from(false)
|
||||
}
|
||||
|
||||
(ClassType::Generic(this), ClassType::Generic(other)) => {
|
||||
ConstraintSet::from_bool(db, this.origin(db) == other.origin(db)).and(db, || {
|
||||
ConstraintSet::from(this.origin(db) == other.origin(db)).and(db, || {
|
||||
this.specialization(db).is_equivalent_to_impl(
|
||||
db,
|
||||
other.specialization(db),
|
||||
@@ -1327,6 +1357,9 @@ impl<'db> ClassType<'db> {
|
||||
Self::NonGeneric(ClassLiteral::Dynamic(dynamic)) => {
|
||||
return dynamic.own_class_member(db, name);
|
||||
}
|
||||
Self::NonGeneric(ClassLiteral::DynamicNamedTuple(namedtuple)) => {
|
||||
return namedtuple.own_class_member(db, name);
|
||||
}
|
||||
Self::NonGeneric(ClassLiteral::Static(class)) => (class, None),
|
||||
Self::Generic(generic) => (generic.origin(db), Some(generic.specialization(db))),
|
||||
};
|
||||
@@ -1617,6 +1650,9 @@ impl<'db> ClassType<'db> {
|
||||
pub(super) fn instance_member(self, db: &'db dyn Db, name: &str) -> PlaceAndQualifiers<'db> {
|
||||
match self {
|
||||
Self::NonGeneric(ClassLiteral::Dynamic(class)) => class.instance_member(db, name),
|
||||
Self::NonGeneric(ClassLiteral::DynamicNamedTuple(namedtuple)) => {
|
||||
namedtuple.instance_member(db, name)
|
||||
}
|
||||
Self::NonGeneric(ClassLiteral::Static(class)) => {
|
||||
if class.is_typed_dict(db) {
|
||||
return Place::Undefined.into();
|
||||
@@ -1645,6 +1681,9 @@ impl<'db> ClassType<'db> {
|
||||
Self::NonGeneric(ClassLiteral::Dynamic(dynamic)) => {
|
||||
dynamic.own_instance_member(db, name)
|
||||
}
|
||||
Self::NonGeneric(ClassLiteral::DynamicNamedTuple(namedtuple)) => {
|
||||
namedtuple.own_instance_member(db, name)
|
||||
}
|
||||
Self::NonGeneric(ClassLiteral::Static(class_literal)) => {
|
||||
class_literal.own_instance_member(db, name)
|
||||
}
|
||||
@@ -1907,7 +1946,9 @@ impl<'db> VarianceInferable<'db> for ClassType<'db> {
|
||||
fn variance_of(self, db: &'db dyn Db, typevar: BoundTypeVarInstance<'db>) -> TypeVarVariance {
|
||||
match self {
|
||||
Self::NonGeneric(ClassLiteral::Static(class)) => class.variance_of(db, typevar),
|
||||
Self::NonGeneric(ClassLiteral::Dynamic(_)) => TypeVarVariance::Bivariant,
|
||||
Self::NonGeneric(ClassLiteral::Dynamic(_) | ClassLiteral::DynamicNamedTuple(_)) => {
|
||||
TypeVarVariance::Bivariant
|
||||
}
|
||||
Self::Generic(generic) => generic.variance_of(db, typevar),
|
||||
}
|
||||
}
|
||||
@@ -2064,6 +2105,20 @@ impl<'db> StaticClassLiteral<'db> {
|
||||
self.is_known(db, KnownClass::Tuple)
|
||||
}
|
||||
|
||||
/// Returns `true` if this class inherits from a functional namedtuple
|
||||
/// (`DynamicNamedTupleLiteral`) that has unknown fields.
|
||||
///
|
||||
/// When the base namedtuple's fields were determined dynamically (e.g., from a variable),
|
||||
/// we can't synthesize precise method signatures and should fall back to `NamedTupleFallback`.
|
||||
pub(crate) fn namedtuple_base_has_unknown_fields(self, db: &'db dyn Db) -> bool {
|
||||
self.explicit_bases(db).iter().any(|base| match base {
|
||||
Type::ClassLiteral(ClassLiteral::DynamicNamedTuple(namedtuple)) => {
|
||||
!namedtuple.has_known_fields(db)
|
||||
}
|
||||
_ => false,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns a new [`StaticClassLiteral`] with the given dataclass params, preserving all other fields.
|
||||
pub(crate) fn with_dataclass_params(
|
||||
self,
|
||||
@@ -2145,6 +2200,8 @@ impl<'db> StaticClassLiteral<'db> {
|
||||
return Some(ty);
|
||||
}
|
||||
}
|
||||
// Dynamic namedtuples don't define their own ordering methods.
|
||||
ClassLiteral::DynamicNamedTuple(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3165,37 +3222,44 @@ impl<'db> StaticClassLiteral<'db> {
|
||||
.with_annotated_type(instance_ty);
|
||||
signature_from_fields(vec![self_parameter], Type::none(db))
|
||||
}
|
||||
(CodeGeneratorKind::NamedTuple, "__new__") => {
|
||||
let cls_parameter = Parameter::positional_or_keyword(Name::new_static("cls"))
|
||||
.with_annotated_type(KnownClass::Type.to_instance(db));
|
||||
signature_from_fields(vec![cls_parameter], Type::none(db))
|
||||
(
|
||||
CodeGeneratorKind::NamedTuple,
|
||||
"__new__" | "__init__" | "_replace" | "__replace__" | "_fields",
|
||||
) if self.namedtuple_base_has_unknown_fields(db) => {
|
||||
// When the namedtuple base has unknown fields, fall back to NamedTupleFallback
|
||||
// which has generic signatures that accept any arguments.
|
||||
KnownClass::NamedTupleFallback
|
||||
.to_class_literal(db)
|
||||
.as_class_literal()?
|
||||
.as_static()?
|
||||
.own_class_member(db, inherited_generic_context, None, name)
|
||||
.ignore_possibly_undefined()
|
||||
.map(|ty| {
|
||||
ty.apply_type_mapping(
|
||||
db,
|
||||
&TypeMapping::ReplaceSelf {
|
||||
new_upper_bound: instance_ty,
|
||||
},
|
||||
TypeContext::default(),
|
||||
)
|
||||
})
|
||||
}
|
||||
(CodeGeneratorKind::NamedTuple, "_replace" | "__replace__") => {
|
||||
if name == "__replace__"
|
||||
&& Program::get(db).python_version(db) < PythonVersion::PY313
|
||||
{
|
||||
return None;
|
||||
}
|
||||
// Use `Self` type variable as return type so that subclasses get the correct
|
||||
// return type when calling `_replace`. For example, if `IntBox` inherits from
|
||||
// `Box[int]` (a NamedTuple), then `IntBox(1)._replace(content=42)` should return
|
||||
// `IntBox`, not `Box[int]`.
|
||||
let self_ty = Type::TypeVar(BoundTypeVarInstance::synthetic_self(
|
||||
db,
|
||||
instance_ty,
|
||||
BindingContext::Synthetic,
|
||||
));
|
||||
let self_parameter = Parameter::positional_or_keyword(Name::new_static("self"))
|
||||
.with_annotated_type(self_ty);
|
||||
signature_from_fields(vec![self_parameter], self_ty)
|
||||
}
|
||||
(CodeGeneratorKind::NamedTuple, "_fields") => {
|
||||
// Synthesize a precise tuple type for _fields using literal string types.
|
||||
// For example, a NamedTuple with `name` and `age` fields gets
|
||||
// `tuple[Literal["name"], Literal["age"]]`.
|
||||
(CodeGeneratorKind::NamedTuple, "__new__" | "_replace" | "__replace__" | "_fields") => {
|
||||
let fields = self.fields(db, specialization, field_policy);
|
||||
let field_types = fields.keys().map(|name| Type::string_literal(db, name));
|
||||
Some(Type::heterogeneous_tuple(db, field_types))
|
||||
let fields_iter = fields.iter().map(|(name, field)| {
|
||||
let default_ty = match &field.kind {
|
||||
FieldKind::NamedTuple { default_ty } => *default_ty,
|
||||
_ => None,
|
||||
};
|
||||
(name.clone(), field.declared_ty, default_ty)
|
||||
});
|
||||
synthesize_namedtuple_class_member(
|
||||
db,
|
||||
name,
|
||||
instance_ty,
|
||||
fields_iter,
|
||||
specialization.map(|s| s.generic_context(db)),
|
||||
)
|
||||
}
|
||||
(CodeGeneratorKind::DataclassLike(_), "__lt__" | "__le__" | "__gt__" | "__ge__") => {
|
||||
if !has_dataclass_param(DataclassFlags::ORDER) {
|
||||
@@ -4680,7 +4744,7 @@ impl<'db> VarianceInferable<'db> for ClassLiteral<'db> {
|
||||
fn variance_of(self, db: &'db dyn Db, typevar: BoundTypeVarInstance<'db>) -> TypeVarVariance {
|
||||
match self {
|
||||
Self::Static(class) => class.variance_of(db, typevar),
|
||||
Self::Dynamic(_) => TypeVarVariance::Bivariant,
|
||||
Self::Dynamic(_) | Self::DynamicNamedTuple(_) => TypeVarVariance::Bivariant,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5090,6 +5154,404 @@ pub(crate) struct DynamicMetaclassConflict<'db> {
|
||||
pub(crate) base2: ClassBase<'db>,
|
||||
}
|
||||
|
||||
/// Create a property type for a namedtuple field.
|
||||
fn create_field_property<'db>(db: &'db dyn Db, field_ty: Type<'db>) -> Type<'db> {
|
||||
let property_getter_signature = Signature::new(
|
||||
Parameters::new(
|
||||
db,
|
||||
[Parameter::positional_only(Some(Name::new_static("self")))],
|
||||
),
|
||||
field_ty,
|
||||
);
|
||||
let property_getter = Type::single_callable(db, property_getter_signature);
|
||||
let property = PropertyInstanceType::new(db, Some(property_getter), None);
|
||||
Type::PropertyInstance(property)
|
||||
}
|
||||
|
||||
/// Synthesize a namedtuple class member given the field information.
|
||||
///
|
||||
/// This is used by both `DynamicNamedTupleLiteral` and `StaticClassLiteral` (for declarative
|
||||
/// namedtuples) to avoid duplicating the synthesis logic.
|
||||
///
|
||||
/// The `inherited_generic_context` parameter is used for declarative namedtuples to preserve
|
||||
/// generic context in the synthesized `__new__` signature.
|
||||
fn synthesize_namedtuple_class_member<'db>(
|
||||
db: &'db dyn Db,
|
||||
name: &str,
|
||||
instance_ty: Type<'db>,
|
||||
fields: impl Iterator<Item = (Name, Type<'db>, Option<Type<'db>>)>,
|
||||
inherited_generic_context: Option<GenericContext<'db>>,
|
||||
) -> Option<Type<'db>> {
|
||||
match name {
|
||||
"__new__" => {
|
||||
// __new__(cls, field1, field2, ...) -> Self
|
||||
let mut parameters = vec![
|
||||
Parameter::positional_or_keyword(Name::new_static("cls"))
|
||||
.with_annotated_type(KnownClass::Type.to_instance(db)),
|
||||
];
|
||||
|
||||
for (field_name, field_ty, default_ty) in fields {
|
||||
let mut param =
|
||||
Parameter::positional_or_keyword(field_name).with_annotated_type(field_ty);
|
||||
if let Some(default) = default_ty {
|
||||
param = param.with_default_type(default);
|
||||
}
|
||||
parameters.push(param);
|
||||
}
|
||||
|
||||
let signature = Signature::new_generic(
|
||||
inherited_generic_context,
|
||||
Parameters::new(db, parameters),
|
||||
instance_ty,
|
||||
);
|
||||
Some(Type::function_like_callable(db, signature))
|
||||
}
|
||||
"_fields" => {
|
||||
// _fields: tuple[Literal["field1"], Literal["field2"], ...]
|
||||
let field_types =
|
||||
fields.map(|(field_name, _, _)| Type::string_literal(db, &field_name));
|
||||
Some(Type::heterogeneous_tuple(db, field_types))
|
||||
}
|
||||
"_replace" | "__replace__" => {
|
||||
if name == "__replace__" && Program::get(db).python_version(db) < PythonVersion::PY313 {
|
||||
return None;
|
||||
}
|
||||
|
||||
// _replace(self, *, field1=..., field2=...) -> Self
|
||||
let self_ty = Type::TypeVar(BoundTypeVarInstance::synthetic_self(
|
||||
db,
|
||||
instance_ty,
|
||||
BindingContext::Synthetic,
|
||||
));
|
||||
|
||||
let mut parameters = vec![
|
||||
Parameter::positional_or_keyword(Name::new_static("self"))
|
||||
.with_annotated_type(self_ty),
|
||||
];
|
||||
|
||||
for (field_name, field_ty, _) in fields {
|
||||
parameters.push(
|
||||
Parameter::keyword_only(field_name)
|
||||
.with_annotated_type(field_ty)
|
||||
.with_default_type(field_ty),
|
||||
);
|
||||
}
|
||||
|
||||
let signature = Signature::new(Parameters::new(db, parameters), self_ty);
|
||||
Some(Type::function_like_callable(db, signature))
|
||||
}
|
||||
"__init__" => {
|
||||
// Namedtuples don't have a custom __init__. All construction happens in __new__.
|
||||
None
|
||||
}
|
||||
_ => {
|
||||
// Fall back to NamedTupleFallback for other synthesized methods.
|
||||
KnownClass::NamedTupleFallback
|
||||
.to_class_literal(db)
|
||||
.as_class_literal()?
|
||||
.as_static()?
|
||||
.own_class_member(db, inherited_generic_context, None, name)
|
||||
.ignore_possibly_undefined()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A namedtuple created via the functional form `namedtuple(name, fields)` or
|
||||
/// `NamedTuple(name, fields)`.
|
||||
///
|
||||
/// For example:
|
||||
/// ```python
|
||||
/// from collections import namedtuple
|
||||
/// Point = namedtuple("Point", ["x", "y"])
|
||||
///
|
||||
/// from typing import NamedTuple
|
||||
/// Person = NamedTuple("Person", [("name", str), ("age", int)])
|
||||
/// ```
|
||||
///
|
||||
/// The type of `Point` would be `type[Point]` where `Point` is a `DynamicNamedTupleLiteral`.
|
||||
#[salsa::interned(debug, heap_size = ruff_memory_usage::heap_size)]
|
||||
#[derive(PartialOrd, Ord)]
|
||||
pub struct DynamicNamedTupleLiteral<'db> {
|
||||
/// The name of the namedtuple (from the first argument).
|
||||
#[returns(ref)]
|
||||
pub name: Name,
|
||||
|
||||
/// The fields as (name, type, default) tuples.
|
||||
/// For `collections.namedtuple`, all types are `Any`.
|
||||
/// For `typing.NamedTuple`, types come from the field definitions.
|
||||
/// The third element is the default type, if any.
|
||||
#[returns(ref)]
|
||||
pub fields: Box<[(Name, Type<'db>, Option<Type<'db>>)]>,
|
||||
|
||||
/// Whether the fields are known statically.
|
||||
///
|
||||
/// When `true`, the fields were determined from a literal (list or tuple).
|
||||
/// When `false`, the fields argument was dynamic (e.g., a variable),
|
||||
/// and attribute lookups should return `Any` instead of failing.
|
||||
pub has_known_fields: bool,
|
||||
|
||||
/// The anchor for this dynamic namedtuple, providing stable identity.
|
||||
///
|
||||
/// - `Definition`: The call is assigned to a variable. The definition
|
||||
/// uniquely identifies this namedtuple and can be used to find the call.
|
||||
/// - `ScopeOffset`: The call is "dangling" (not assigned). The offset
|
||||
/// is relative to the enclosing scope's anchor node index.
|
||||
pub anchor: DynamicClassAnchor<'db>,
|
||||
}
|
||||
|
||||
impl get_size2::GetSize for DynamicNamedTupleLiteral<'_> {}
|
||||
|
||||
#[salsa::tracked]
|
||||
impl<'db> DynamicNamedTupleLiteral<'db> {
|
||||
/// Returns the definition where this namedtuple is created, if it was assigned to a variable.
|
||||
pub(crate) fn definition(self, db: &'db dyn Db) -> Option<Definition<'db>> {
|
||||
match self.anchor(db) {
|
||||
DynamicClassAnchor::Definition(definition) => Some(definition),
|
||||
DynamicClassAnchor::ScopeOffset { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the scope in which this dynamic class was created.
|
||||
pub(crate) fn scope(self, db: &'db dyn Db) -> ScopeId<'db> {
|
||||
match self.anchor(db) {
|
||||
DynamicClassAnchor::Definition(definition) => definition.scope(db),
|
||||
DynamicClassAnchor::ScopeOffset { scope, .. } => scope,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an instance type for this dynamic namedtuple.
|
||||
pub(crate) fn to_instance(self, db: &'db dyn Db) -> Type<'db> {
|
||||
Type::instance(db, ClassType::NonGeneric(self.into()))
|
||||
}
|
||||
|
||||
/// Returns the range of the namedtuple call expression.
|
||||
pub(crate) fn header_range(self, db: &'db dyn Db) -> TextRange {
|
||||
let scope = self.scope(db);
|
||||
let file = scope.file(db);
|
||||
let module = parsed_module(db, file).load(db);
|
||||
|
||||
match self.anchor(db) {
|
||||
DynamicClassAnchor::Definition(definition) => {
|
||||
// For definitions, get the range from the definition's value.
|
||||
// The namedtuple call is the value of the assignment.
|
||||
definition
|
||||
.kind(db)
|
||||
.value(&module)
|
||||
.expect("DynamicClassAnchor::Definition should only be used for assignments")
|
||||
.range()
|
||||
}
|
||||
DynamicClassAnchor::ScopeOffset { offset, .. } => {
|
||||
// For dangling calls, compute the absolute index from the offset.
|
||||
let scope_anchor = scope.node(db).node_index().unwrap_or(NodeIndex::from(0));
|
||||
let anchor_u32 = scope_anchor
|
||||
.as_u32()
|
||||
.expect("anchor should not be NodeIndex::NONE");
|
||||
let absolute_index = NodeIndex::from(anchor_u32 + offset);
|
||||
|
||||
// Get the node and return its range.
|
||||
let node: &ast::ExprCall = module
|
||||
.get_by_index(absolute_index)
|
||||
.try_into()
|
||||
.expect("scope offset should point to ExprCall");
|
||||
node.range()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a [`Span`] pointing to the namedtuple call expression.
|
||||
pub(super) fn header_span(self, db: &'db dyn Db) -> Span {
|
||||
Span::from(self.scope(db).file(db)).with_range(self.header_range(db))
|
||||
}
|
||||
|
||||
/// Compute the MRO for this namedtuple.
|
||||
///
|
||||
/// The MRO is `[self, tuple[field_types...], object]`.
|
||||
/// For example, `namedtuple("Point", [("x", int), ("y", int)])` has MRO
|
||||
/// `[Point, tuple[int, int], object]`.
|
||||
#[salsa::tracked(returns(ref), heap_size = ruff_memory_usage::heap_size)]
|
||||
pub(crate) fn mro(self, db: &'db dyn Db) -> Mro<'db> {
|
||||
let self_base = ClassBase::Class(ClassType::NonGeneric(self.into()));
|
||||
let tuple_class = self.tuple_base_class(db);
|
||||
let object_class = KnownClass::Object
|
||||
.to_class_literal(db)
|
||||
.as_class_literal()
|
||||
.expect("object should be a class literal")
|
||||
.default_specialization(db);
|
||||
Mro::from([
|
||||
self_base,
|
||||
ClassBase::Class(tuple_class),
|
||||
ClassBase::Class(object_class),
|
||||
])
|
||||
}
|
||||
|
||||
/// Get the metaclass of this dynamic namedtuple.
|
||||
///
|
||||
/// Namedtuples always have `type` as their metaclass.
|
||||
pub(crate) fn metaclass(self, db: &'db dyn Db) -> Type<'db> {
|
||||
let _ = self;
|
||||
KnownClass::Type.to_class_literal(db)
|
||||
}
|
||||
|
||||
/// Compute the specialized tuple class that this namedtuple inherits from.
|
||||
///
|
||||
/// For example, `namedtuple("Point", [("x", int), ("y", int)])` inherits from `tuple[int, int]`.
|
||||
pub(crate) fn tuple_base_class(self, db: &'db dyn Db) -> ClassType<'db> {
|
||||
// If fields are unknown, return `tuple[Unknown, ...]` to avoid false positives
|
||||
// like index-out-of-bounds errors.
|
||||
if !self.has_known_fields(db) {
|
||||
return TupleType::homogeneous(db, Type::unknown()).to_class_type(db);
|
||||
}
|
||||
|
||||
let field_types = self.fields(db).iter().map(|(_, ty, _)| *ty);
|
||||
TupleType::heterogeneous(db, field_types)
|
||||
.map(|t| t.to_class_type(db))
|
||||
.unwrap_or_else(|| {
|
||||
KnownClass::Tuple
|
||||
.to_class_literal(db)
|
||||
.as_class_literal()
|
||||
.expect("tuple should be a class literal")
|
||||
.default_specialization(db)
|
||||
})
|
||||
}
|
||||
|
||||
/// Look up an instance member defined directly on this class (not inherited).
|
||||
///
|
||||
/// For dynamic namedtuples, instance members are the field names.
|
||||
/// If fields are unknown (dynamic), returns `Any` for any attribute.
|
||||
pub(super) fn own_instance_member(self, db: &'db dyn Db, name: &str) -> Member<'db> {
|
||||
for (field_name, field_ty, _) in self.fields(db).as_ref() {
|
||||
if field_name.as_str() == name {
|
||||
return Member::definitely_declared(*field_ty);
|
||||
}
|
||||
}
|
||||
|
||||
if !self.has_known_fields(db) {
|
||||
return Member::definitely_declared(Type::any());
|
||||
}
|
||||
|
||||
Member::unbound()
|
||||
}
|
||||
|
||||
/// Look up an instance member by name (including superclasses).
|
||||
pub(crate) fn instance_member(self, db: &'db dyn Db, name: &str) -> PlaceAndQualifiers<'db> {
|
||||
// First check own instance members.
|
||||
let result = self.own_instance_member(db, name);
|
||||
if !result.is_undefined() {
|
||||
return result.inner;
|
||||
}
|
||||
|
||||
// Fall back to the tuple base type for other attributes.
|
||||
Type::instance(db, self.tuple_base_class(db)).instance_member(db, name)
|
||||
}
|
||||
|
||||
/// Look up a class-level member by name.
|
||||
pub(crate) fn class_member(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
name: &str,
|
||||
policy: MemberLookupPolicy,
|
||||
) -> PlaceAndQualifiers<'db> {
|
||||
// First check synthesized members and fields.
|
||||
let member = self.own_class_member(db, name);
|
||||
if !member.is_undefined() {
|
||||
return member.inner;
|
||||
}
|
||||
|
||||
// Fall back to tuple class members.
|
||||
let result = self
|
||||
.tuple_base_class(db)
|
||||
.class_literal(db)
|
||||
.class_member(db, name, policy);
|
||||
|
||||
// If fields are unknown (dynamic) and the attribute wasn't found,
|
||||
// return `Any` instead of failing.
|
||||
if !self.has_known_fields(db) && result.place.is_undefined() {
|
||||
return Place::bound(Type::any()).into();
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Look up a class-level member defined directly on this class (not inherited).
|
||||
///
|
||||
/// This only checks synthesized members and field properties, without falling
|
||||
/// back to tuple or other base classes.
|
||||
pub(super) fn own_class_member(self, db: &'db dyn Db, name: &str) -> Member<'db> {
|
||||
// Handle synthesized namedtuple attributes.
|
||||
if let Some(ty) = self.synthesized_class_member(db, name) {
|
||||
return Member::definitely_declared(ty);
|
||||
}
|
||||
|
||||
// Check if it's a field name (returns a property descriptor).
|
||||
for (field_name, field_ty, _) in self.fields(db).as_ref() {
|
||||
if field_name.as_str() == name {
|
||||
return Member::definitely_declared(create_field_property(db, *field_ty));
|
||||
}
|
||||
}
|
||||
|
||||
Member::default()
|
||||
}
|
||||
|
||||
/// Generate synthesized class members for namedtuples.
|
||||
fn synthesized_class_member(self, db: &'db dyn Db, name: &str) -> Option<Type<'db>> {
|
||||
let instance_ty = self.to_instance(db);
|
||||
|
||||
// When fields are unknown, handle constructor and field-specific methods specially.
|
||||
if !self.has_known_fields(db) {
|
||||
match name {
|
||||
// For constructors, return a gradual signature that accepts any arguments.
|
||||
"__new__" | "__init__" => {
|
||||
let signature = Signature::new(Parameters::gradual_form(), instance_ty);
|
||||
return Some(Type::function_like_callable(db, signature));
|
||||
}
|
||||
// For other field-specific methods, fall through to NamedTupleFallback.
|
||||
"_fields" | "_replace" | "__replace__" => {
|
||||
return KnownClass::NamedTupleFallback
|
||||
.to_class_literal(db)
|
||||
.as_class_literal()?
|
||||
.as_static()?
|
||||
.own_class_member(db, None, None, name)
|
||||
.ignore_possibly_undefined()
|
||||
.map(|ty| {
|
||||
ty.apply_type_mapping(
|
||||
db,
|
||||
&TypeMapping::ReplaceSelf {
|
||||
new_upper_bound: instance_ty,
|
||||
},
|
||||
TypeContext::default(),
|
||||
)
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
let result = synthesize_namedtuple_class_member(
|
||||
db,
|
||||
name,
|
||||
instance_ty,
|
||||
self.fields(db).iter().cloned(),
|
||||
None,
|
||||
);
|
||||
// For fallback members from NamedTupleFallback, apply type mapping to handle
|
||||
// `Self` types. The explicitly synthesized members (__new__, _fields, _replace,
|
||||
// __replace__) don't need this mapping.
|
||||
if matches!(name, "__new__" | "_fields" | "_replace" | "__replace__") {
|
||||
result
|
||||
} else {
|
||||
result.map(|ty| {
|
||||
ty.apply_type_mapping(
|
||||
db,
|
||||
&TypeMapping::ReplaceSelf {
|
||||
new_upper_bound: instance_ty,
|
||||
},
|
||||
TypeContext::default(),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs member lookups over an MRO (Method Resolution Order).
|
||||
///
|
||||
/// This struct encapsulates the shared logic for looking up class and instance
|
||||
@@ -5366,6 +5828,11 @@ impl<'db> QualifiedClassName<'db> {
|
||||
let scope = class.scope(self.db);
|
||||
(scope.file(self.db), scope.file_scope_id(self.db), 0)
|
||||
}
|
||||
ClassLiteral::DynamicNamedTuple(namedtuple) => {
|
||||
// Dynamic namedtuples don't have a body scope; start from the enclosing scope.
|
||||
let scope = namedtuple.scope(self.db);
|
||||
(scope.file(self.db), scope.file_scope_id(self.db), 0)
|
||||
}
|
||||
};
|
||||
|
||||
let module_ast = parsed_module(self.db, file).load(self.db);
|
||||
@@ -6527,7 +6994,7 @@ impl KnownClass {
|
||||
db: &'db dyn Db,
|
||||
other: ClassType<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
ConstraintSet::from_bool(db, self.is_subclass_of(db, other))
|
||||
ConstraintSet::from(self.is_subclass_of(db, other))
|
||||
}
|
||||
|
||||
/// Return the module in which we should look up the definition for this class
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -69,6 +69,7 @@ pub(crate) fn enum_metadata<'db>(
|
||||
// ```
|
||||
return None;
|
||||
}
|
||||
ClassLiteral::DynamicNamedTuple(..) => return None,
|
||||
};
|
||||
|
||||
// This is a fast path to avoid traversing the MRO of known classes
|
||||
|
||||
@@ -1123,7 +1123,7 @@ impl<'db> FunctionType<'db> {
|
||||
disjointness_visitor: &IsDisjointVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
if self.literal(db) != other.literal(db) {
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
return ConstraintSet::from(false);
|
||||
}
|
||||
|
||||
let self_signature = self.signature(db);
|
||||
@@ -1146,10 +1146,10 @@ impl<'db> FunctionType<'db> {
|
||||
visitor: &IsEquivalentVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
if self.normalized(db) == other.normalized(db) {
|
||||
return ConstraintSet::from_bool(db, true);
|
||||
return ConstraintSet::from(true);
|
||||
}
|
||||
if self.literal(db) != other.literal(db) {
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
return ConstraintSet::from(false);
|
||||
}
|
||||
let self_signature = self.signature(db);
|
||||
let other_signature = other.signature(db);
|
||||
|
||||
@@ -17,7 +17,6 @@ use crate::types::constraints::{ConstraintSet, IteratorConstraintsExtension};
|
||||
use crate::types::instance::{Protocol, ProtocolInstanceType};
|
||||
use crate::types::relation::{
|
||||
HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, TypeRelation,
|
||||
default_disjoint_visitor, default_relation_visitor,
|
||||
};
|
||||
use crate::types::signatures::Parameters;
|
||||
use crate::types::tuple::{TupleSpec, TupleType, walk_tuple_type};
|
||||
@@ -794,12 +793,12 @@ fn is_subtype_in_invariant_position<'db>(
|
||||
// This should be removed and properly handled in the respective
|
||||
// `(Type::TypeVar(_), _) | (_, Type::TypeVar(_))` branch of
|
||||
// `Type::has_relation_to_impl`. Right now, we cannot generally
|
||||
// return `ConstraintSet::from_bool(db, true)` from that branch, as that
|
||||
// return `ConstraintSet::from(true)` from that branch, as that
|
||||
// leads to union simplification, which means that we lose track
|
||||
// of type variables without recording the constraints under which
|
||||
// the relation holds.
|
||||
if matches!(base, Type::TypeVar(_)) || matches!(derived, Type::TypeVar(_)) {
|
||||
return ConstraintSet::from_bool(db, true);
|
||||
return ConstraintSet::from(true);
|
||||
}
|
||||
|
||||
derived.has_relation_to_impl(
|
||||
@@ -1273,7 +1272,7 @@ impl<'db> Specialization<'db> {
|
||||
) -> ConstraintSet<'db> {
|
||||
let generic_context = self.generic_context(db);
|
||||
if generic_context != other.generic_context(db) {
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
return ConstraintSet::from(false);
|
||||
}
|
||||
|
||||
if let (Some(self_tuple), Some(other_tuple)) = (self.tuple_inner(db), other.tuple_inner(db))
|
||||
@@ -1332,7 +1331,7 @@ impl<'db> Specialization<'db> {
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
),
|
||||
TypeVarVariance::Bivariant => ConstraintSet::from_bool(db, true),
|
||||
TypeVarVariance::Bivariant => ConstraintSet::from(true),
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1347,8 +1346,8 @@ impl<'db> Specialization<'db> {
|
||||
db,
|
||||
other,
|
||||
inferable,
|
||||
&default_disjoint_visitor(db),
|
||||
&default_relation_visitor(db),
|
||||
&IsDisjointVisitor::default(),
|
||||
&HasRelationToVisitor::default(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1362,7 +1361,7 @@ impl<'db> Specialization<'db> {
|
||||
) -> ConstraintSet<'db> {
|
||||
let generic_context = self.generic_context(db);
|
||||
if generic_context != other.generic_context(db) {
|
||||
return ConstraintSet::from_bool(db, true);
|
||||
return ConstraintSet::from(true);
|
||||
}
|
||||
|
||||
if let (Some(self_tuple), Some(other_tuple)) = (self.tuple_inner(db), other.tuple_inner(db))
|
||||
@@ -1400,13 +1399,13 @@ impl<'db> Specialization<'db> {
|
||||
),
|
||||
|
||||
// If `Foo[T]` is covariant in `T`, `Foo[Never]` is a subtype of `Foo[A]` and `Foo[B]`
|
||||
TypeVarVariance::Covariant => ConstraintSet::from_bool(db, false),
|
||||
TypeVarVariance::Covariant => ConstraintSet::from(false),
|
||||
|
||||
// If `Foo[T]` is contravariant in `T`, `Foo[A | B]` is a subtype of `Foo[A]` and `Foo[B]`
|
||||
TypeVarVariance::Contravariant => ConstraintSet::from_bool(db, false),
|
||||
TypeVarVariance::Contravariant => ConstraintSet::from(false),
|
||||
|
||||
// If `Foo[T]` is bivariant in `T`, `Foo[A]` and `Foo[B]` are mutual subtypes.
|
||||
TypeVarVariance::Bivariant => ConstraintSet::from_bool(db, false),
|
||||
TypeVarVariance::Bivariant => ConstraintSet::from(false),
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -1419,14 +1418,14 @@ impl<'db> Specialization<'db> {
|
||||
visitor: &IsEquivalentVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
if self.materialization_kind(db) != other.materialization_kind(db) {
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
return ConstraintSet::from(false);
|
||||
}
|
||||
let generic_context = self.generic_context(db);
|
||||
if generic_context != other.generic_context(db) {
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
return ConstraintSet::from(false);
|
||||
}
|
||||
|
||||
let mut result = ConstraintSet::from_bool(db, true);
|
||||
let mut result = ConstraintSet::from(true);
|
||||
for ((bound_typevar, self_type), other_type) in generic_context
|
||||
.variables(db)
|
||||
.zip(self.types(db))
|
||||
@@ -1444,7 +1443,7 @@ impl<'db> Specialization<'db> {
|
||||
| TypeVarVariance::Contravariant => {
|
||||
self_type.is_equivalent_to_impl(db, *other_type, inferable, visitor)
|
||||
}
|
||||
TypeVarVariance::Bivariant => ConstraintSet::from_bool(db, true),
|
||||
TypeVarVariance::Bivariant => ConstraintSet::from(true),
|
||||
};
|
||||
if result.intersect(db, compatible).is_never_satisfied(db) {
|
||||
return result;
|
||||
@@ -1452,7 +1451,7 @@ impl<'db> Specialization<'db> {
|
||||
}
|
||||
|
||||
match (self.tuple_inner(db), other.tuple_inner(db)) {
|
||||
(Some(_), None) | (None, Some(_)) => return ConstraintSet::from_bool(db, false),
|
||||
(Some(_), None) | (None, Some(_)) => return ConstraintSet::from(false),
|
||||
(None, None) => {}
|
||||
(Some(self_tuple), Some(other_tuple)) => {
|
||||
let compatible =
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -13,7 +13,6 @@ use crate::types::generics::{InferableTypeVars, walk_specialization};
|
||||
use crate::types::protocol_class::{ProtocolClass, walk_protocol_interface};
|
||||
use crate::types::relation::{
|
||||
HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, TypeRelation,
|
||||
default_disjoint_visitor, default_relation_visitor,
|
||||
};
|
||||
use crate::types::tuple::{TupleSpec, TupleType, walk_tuple_type};
|
||||
use crate::types::{
|
||||
@@ -43,6 +42,9 @@ impl<'db> Type<'db> {
|
||||
ClassLiteral::Dynamic(_) => {
|
||||
Type::NominalInstance(NominalInstanceType(NominalInstanceInner::NonTuple(class)))
|
||||
}
|
||||
ClassLiteral::DynamicNamedTuple(_) => {
|
||||
Type::NominalInstance(NominalInstanceType(NominalInstanceInner::NonTuple(class)))
|
||||
}
|
||||
ClassLiteral::Static(class_literal) => {
|
||||
let specialization = class.into_generic_alias().map(|g| g.specialization(db));
|
||||
match class_literal.known(db) {
|
||||
@@ -180,7 +182,7 @@ impl<'db> Type<'db> {
|
||||
// recognise `str` as a subtype of `Container[str]`.
|
||||
structurally_satisfied.or(db, || {
|
||||
let Some(nominal_instance) = protocol.to_nominal_instance() else {
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
return ConstraintSet::from(false);
|
||||
};
|
||||
|
||||
// if `self` and `other` are *both* protocols, we also need to treat `self` as if it
|
||||
@@ -412,7 +414,7 @@ impl<'db> NominalInstanceType<'db> {
|
||||
disjointness_visitor: &IsDisjointVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
match (self.0, other.0) {
|
||||
(_, NominalInstanceInner::Object) => ConstraintSet::from_bool(db, true),
|
||||
(_, NominalInstanceInner::Object) => ConstraintSet::from(true),
|
||||
(
|
||||
NominalInstanceInner::ExactTuple(tuple1),
|
||||
NominalInstanceInner::ExactTuple(tuple2),
|
||||
@@ -448,12 +450,12 @@ impl<'db> NominalInstanceType<'db> {
|
||||
NominalInstanceInner::ExactTuple(tuple2),
|
||||
) => tuple1.is_equivalent_to_impl(db, tuple2, inferable, visitor),
|
||||
(NominalInstanceInner::Object, NominalInstanceInner::Object) => {
|
||||
ConstraintSet::from_bool(db, true)
|
||||
ConstraintSet::from(true)
|
||||
}
|
||||
(NominalInstanceInner::NonTuple(class1), NominalInstanceInner::NonTuple(class2)) => {
|
||||
class1.is_equivalent_to_impl(db, class2, inferable, visitor)
|
||||
}
|
||||
_ => ConstraintSet::from_bool(db, false),
|
||||
_ => ConstraintSet::from(false),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -466,9 +468,9 @@ impl<'db> NominalInstanceType<'db> {
|
||||
relation_visitor: &HasRelationToVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
if self.is_object() || other.is_object() {
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
return ConstraintSet::from(false);
|
||||
}
|
||||
let mut result = ConstraintSet::from_bool(db, false);
|
||||
let mut result = ConstraintSet::from(false);
|
||||
if let Some(self_spec) = self.tuple_spec(db) {
|
||||
if let Some(other_spec) = other.tuple_spec(db) {
|
||||
let compatible = self_spec.is_disjoint_from_impl(
|
||||
@@ -485,8 +487,7 @@ impl<'db> NominalInstanceType<'db> {
|
||||
}
|
||||
|
||||
result.or(db, || {
|
||||
ConstraintSet::from_bool(
|
||||
db,
|
||||
ConstraintSet::from(
|
||||
!self
|
||||
.class(db)
|
||||
.could_coexist_in_mro_with(db, other.class(db)),
|
||||
@@ -715,8 +716,8 @@ impl<'db> ProtocolInstanceType<'db> {
|
||||
protocol,
|
||||
InferableTypeVars::None,
|
||||
TypeRelation::Subtyping,
|
||||
&default_relation_visitor(db),
|
||||
&default_disjoint_visitor(db),
|
||||
&HasRelationToVisitor::default(),
|
||||
&IsDisjointVisitor::default(),
|
||||
)
|
||||
.is_always_satisfied(db)
|
||||
}
|
||||
@@ -782,13 +783,13 @@ impl<'db> ProtocolInstanceType<'db> {
|
||||
_visitor: &IsEquivalentVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
if self == other {
|
||||
return ConstraintSet::from_bool(db, true);
|
||||
return ConstraintSet::from(true);
|
||||
}
|
||||
let self_normalized = self.normalized(db);
|
||||
if self_normalized == Type::ProtocolInstance(other) {
|
||||
return ConstraintSet::from_bool(db, true);
|
||||
return ConstraintSet::from(true);
|
||||
}
|
||||
ConstraintSet::from_bool(db, self_normalized == other.normalized(db))
|
||||
ConstraintSet::from(self_normalized == other.normalized(db))
|
||||
}
|
||||
|
||||
/// Return `true` if this protocol type is disjoint from the protocol `other`.
|
||||
@@ -798,12 +799,12 @@ impl<'db> ProtocolInstanceType<'db> {
|
||||
#[expect(clippy::unused_self)]
|
||||
pub(super) fn is_disjoint_from_impl(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
_db: &'db dyn Db,
|
||||
_other: Self,
|
||||
_inferable: InferableTypeVars<'_, 'db>,
|
||||
_visitor: &IsDisjointVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
ConstraintSet::from_bool(db, false)
|
||||
ConstraintSet::from(false)
|
||||
}
|
||||
|
||||
pub(crate) fn instance_member(self, db: &'db dyn Db, name: &str) -> PlaceAndQualifiers<'db> {
|
||||
|
||||
@@ -500,6 +500,9 @@ impl<'db> MroIterator<'db> {
|
||||
ClassLiteral::Dynamic(literal) => {
|
||||
ClassBase::Class(ClassType::NonGeneric(literal.into()))
|
||||
}
|
||||
ClassLiteral::DynamicNamedTuple(literal) => {
|
||||
ClassBase::Class(ClassType::NonGeneric(literal.into()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -524,6 +527,11 @@ impl<'db> MroIterator<'db> {
|
||||
full_mro_iter.next();
|
||||
full_mro_iter
|
||||
}
|
||||
ClassLiteral::DynamicNamedTuple(literal) => {
|
||||
let mut full_mro_iter = literal.mro(self.db).iter();
|
||||
full_mro_iter.next();
|
||||
full_mro_iter
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,16 +125,16 @@ impl<'db> NewType<'db> {
|
||||
// base class, we don't have to keep looking.
|
||||
pub(crate) fn has_relation_to_impl(self, db: &'db dyn Db, other: Self) -> ConstraintSet<'db> {
|
||||
if self.is_equivalent_to_impl(db, other) {
|
||||
return ConstraintSet::from_bool(db, true);
|
||||
return ConstraintSet::from(true);
|
||||
}
|
||||
for base in self.iter_bases(db) {
|
||||
if let NewTypeBase::NewType(base_newtype) = base {
|
||||
if base_newtype.is_equivalent_to_impl(db, other) {
|
||||
return ConstraintSet::from_bool(db, true);
|
||||
return ConstraintSet::from(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
ConstraintSet::from_bool(db, false)
|
||||
ConstraintSet::from(false)
|
||||
}
|
||||
|
||||
pub(crate) fn is_disjoint_from_impl(self, db: &'db dyn Db, other: Self) -> ConstraintSet<'db> {
|
||||
|
||||
@@ -292,44 +292,76 @@ impl<'db> ProtocolInterface<'db> {
|
||||
) -> ConstraintSet<'db> {
|
||||
other.members(db).when_all(db, |other_member| {
|
||||
self.member_by_name(db, other_member.name)
|
||||
.when_some_and(db, |our_member| {
|
||||
match (our_member.kind, other_member.kind) {
|
||||
// Method members are always immutable;
|
||||
// they can never be subtypes of/assignable to mutable attribute members.
|
||||
(ProtocolMemberKind::Method(_), ProtocolMemberKind::Other(_)) => {
|
||||
ConstraintSet::from_bool(db, false)
|
||||
}
|
||||
.when_some_and(|our_member| match (our_member.kind, other_member.kind) {
|
||||
// Method members are always immutable;
|
||||
// they can never be subtypes of/assignable to mutable attribute members.
|
||||
(ProtocolMemberKind::Method(_), ProtocolMemberKind::Other(_)) => {
|
||||
ConstraintSet::from(false)
|
||||
}
|
||||
|
||||
// A property member can only be a subtype of an attribute member
|
||||
// if the property is readable *and* writable.
|
||||
//
|
||||
// TODO: this should also consider the types of the members on both sides.
|
||||
(ProtocolMemberKind::Property(property), ProtocolMemberKind::Other(_)) => {
|
||||
ConstraintSet::from_bool(
|
||||
db,
|
||||
property.getter(db).is_some() && property.setter(db).is_some(),
|
||||
)
|
||||
}
|
||||
// A property member can only be a subtype of an attribute member
|
||||
// if the property is readable *and* writable.
|
||||
//
|
||||
// TODO: this should also consider the types of the members on both sides.
|
||||
(ProtocolMemberKind::Property(property), ProtocolMemberKind::Other(_)) => {
|
||||
ConstraintSet::from(
|
||||
property.getter(db).is_some() && property.setter(db).is_some(),
|
||||
)
|
||||
}
|
||||
|
||||
// A `@property` member can never be a subtype of a method member, as it is not necessarily
|
||||
// accessible on the meta-type, whereas a method member must be.
|
||||
(ProtocolMemberKind::Property(_), ProtocolMemberKind::Method(_)) => {
|
||||
ConstraintSet::from_bool(db, false)
|
||||
}
|
||||
// A `@property` member can never be a subtype of a method member, as it is not necessarily
|
||||
// accessible on the meta-type, whereas a method member must be.
|
||||
(ProtocolMemberKind::Property(_), ProtocolMemberKind::Method(_)) => {
|
||||
ConstraintSet::from(false)
|
||||
}
|
||||
|
||||
// But an attribute member *can* be a subtype of a method member,
|
||||
// providing it is marked `ClassVar`
|
||||
(
|
||||
ProtocolMemberKind::Other(our_type),
|
||||
ProtocolMemberKind::Method(other_type),
|
||||
) => ConstraintSet::from_bool(
|
||||
// But an attribute member *can* be a subtype of a method member,
|
||||
// providing it is marked `ClassVar`
|
||||
(
|
||||
ProtocolMemberKind::Other(our_type),
|
||||
ProtocolMemberKind::Method(other_type),
|
||||
) => ConstraintSet::from(
|
||||
our_member.qualifiers.contains(TypeQualifiers::CLASS_VAR),
|
||||
)
|
||||
.and(db, || {
|
||||
our_type.has_relation_to_impl(
|
||||
db,
|
||||
our_member.qualifiers.contains(TypeQualifiers::CLASS_VAR),
|
||||
Type::Callable(protocol_bind_self(db, other_type, None)),
|
||||
inferable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
}),
|
||||
|
||||
(
|
||||
ProtocolMemberKind::Method(our_method),
|
||||
ProtocolMemberKind::Method(other_method),
|
||||
) => our_method.bind_self(db, None).has_relation_to_impl(
|
||||
db,
|
||||
protocol_bind_self(db, other_method, None),
|
||||
inferable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
),
|
||||
|
||||
(
|
||||
ProtocolMemberKind::Other(our_type),
|
||||
ProtocolMemberKind::Other(other_type),
|
||||
) => our_type
|
||||
.has_relation_to_impl(
|
||||
db,
|
||||
other_type,
|
||||
inferable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
.and(db, || {
|
||||
our_type.has_relation_to_impl(
|
||||
other_type.has_relation_to_impl(
|
||||
db,
|
||||
Type::Callable(protocol_bind_self(db, other_type, None)),
|
||||
our_type,
|
||||
inferable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
@@ -337,50 +369,14 @@ impl<'db> ProtocolInterface<'db> {
|
||||
)
|
||||
}),
|
||||
|
||||
(
|
||||
ProtocolMemberKind::Method(our_method),
|
||||
ProtocolMemberKind::Method(other_method),
|
||||
) => our_method.bind_self(db, None).has_relation_to_impl(
|
||||
db,
|
||||
protocol_bind_self(db, other_method, None),
|
||||
inferable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
),
|
||||
|
||||
(
|
||||
ProtocolMemberKind::Other(our_type),
|
||||
ProtocolMemberKind::Other(other_type),
|
||||
) => our_type
|
||||
.has_relation_to_impl(
|
||||
db,
|
||||
other_type,
|
||||
inferable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
.and(db, || {
|
||||
other_type.has_relation_to_impl(
|
||||
db,
|
||||
our_type,
|
||||
inferable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
}),
|
||||
|
||||
// TODO: finish assignability/subtyping between two `@property` members,
|
||||
// and between a `@property` member and a member of a different kind.
|
||||
(
|
||||
ProtocolMemberKind::Property(_)
|
||||
| ProtocolMemberKind::Method(_)
|
||||
| ProtocolMemberKind::Other(_),
|
||||
ProtocolMemberKind::Property(_),
|
||||
) => ConstraintSet::from_bool(db, true),
|
||||
}
|
||||
// TODO: finish assignability/subtyping between two `@property` members,
|
||||
// and between a `@property` member and a member of a different kind.
|
||||
(
|
||||
ProtocolMemberKind::Property(_)
|
||||
| ProtocolMemberKind::Method(_)
|
||||
| ProtocolMemberKind::Other(_),
|
||||
ProtocolMemberKind::Property(_),
|
||||
) => ConstraintSet::from(true),
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -717,7 +713,7 @@ impl<'a, 'db> ProtocolMember<'a, 'db> {
|
||||
match &self.kind {
|
||||
// TODO: implement disjointness for property/method members as well as attribute members
|
||||
ProtocolMemberKind::Property(_) | ProtocolMemberKind::Method(_) => {
|
||||
ConstraintSet::from_bool(db, false)
|
||||
ConstraintSet::from(false)
|
||||
}
|
||||
ProtocolMemberKind::Other(ty) => ty.is_disjoint_from_impl(
|
||||
db,
|
||||
@@ -768,7 +764,7 @@ impl<'a, 'db> ProtocolMember<'a, 'db> {
|
||||
)
|
||||
.place
|
||||
else {
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
return ConstraintSet::from(false);
|
||||
};
|
||||
attribute_type
|
||||
};
|
||||
@@ -787,7 +783,7 @@ impl<'a, 'db> ProtocolMember<'a, 'db> {
|
||||
let fallback_other = other.literal_fallback_instance(db).unwrap_or(other);
|
||||
attribute_type
|
||||
.try_upcast_to_callable(db)
|
||||
.when_some_and(db, |callables| {
|
||||
.when_some_and(|callables| {
|
||||
callables
|
||||
.map(|callable| callable.apply_self(db, fallback_other))
|
||||
.has_relation_to_impl(
|
||||
@@ -801,16 +797,13 @@ impl<'a, 'db> ProtocolMember<'a, 'db> {
|
||||
})
|
||||
}
|
||||
// TODO: consider the types of the attribute on `other` for property members
|
||||
ProtocolMemberKind::Property(_) => ConstraintSet::from_bool(
|
||||
db,
|
||||
matches!(
|
||||
other.member(db, self.name).place,
|
||||
Place::Defined(DefinedPlace {
|
||||
definedness: Definedness::AlwaysDefined,
|
||||
..
|
||||
})
|
||||
),
|
||||
),
|
||||
ProtocolMemberKind::Property(_) => ConstraintSet::from(matches!(
|
||||
other.member(db, self.name).place,
|
||||
Place::Defined(DefinedPlace {
|
||||
definedness: Definedness::AlwaysDefined,
|
||||
..
|
||||
})
|
||||
)),
|
||||
ProtocolMemberKind::Other(member_type) => {
|
||||
let Place::Defined(DefinedPlace {
|
||||
ty: attribute_type,
|
||||
@@ -818,7 +811,7 @@ impl<'a, 'db> ProtocolMember<'a, 'db> {
|
||||
..
|
||||
}) = other.member(db, self.name).place
|
||||
else {
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
return ConstraintSet::from(false);
|
||||
};
|
||||
member_type
|
||||
.has_relation_to_impl(
|
||||
|
||||
@@ -310,8 +310,8 @@ impl<'db> Type<'db> {
|
||||
target,
|
||||
inferable,
|
||||
relation,
|
||||
&default_relation_visitor(db),
|
||||
&default_disjoint_visitor(db),
|
||||
&HasRelationToVisitor::default(),
|
||||
&IsDisjointVisitor::default(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -330,7 +330,7 @@ impl<'db> Type<'db> {
|
||||
// Note that we could do a full equivalence check here, but that would be both expensive
|
||||
// and unnecessary. This early return is only an optimisation.
|
||||
if (!relation.is_subtyping() || self.subtyping_is_always_reflexive()) && self == target {
|
||||
return ConstraintSet::from_bool(db, true);
|
||||
return ConstraintSet::from(true);
|
||||
}
|
||||
|
||||
// Handle constraint implication first. If either `self` or `target` is a typevar, check
|
||||
@@ -359,22 +359,22 @@ impl<'db> Type<'db> {
|
||||
match (self, target) {
|
||||
// Everything is a subtype of `object`.
|
||||
(_, Type::NominalInstance(instance)) if instance.is_object() => {
|
||||
ConstraintSet::from_bool(db, true)
|
||||
ConstraintSet::from(true)
|
||||
}
|
||||
(_, Type::ProtocolInstance(target)) if target.is_equivalent_to_object(db) => {
|
||||
ConstraintSet::from_bool(db, true)
|
||||
ConstraintSet::from(true)
|
||||
}
|
||||
|
||||
// `Never` is the bottom type, the empty set.
|
||||
// It is a subtype of all other types.
|
||||
(Type::Never, _) => ConstraintSet::from_bool(db, true),
|
||||
(Type::Never, _) => ConstraintSet::from(true),
|
||||
|
||||
// In some specific situations, `Any`/`Unknown`/`@Todo` can be simplified out of unions and intersections,
|
||||
// but this is not true for divergent types (and moving this case any lower down appears to cause
|
||||
// "too many cycle iterations" panics).
|
||||
(Type::Dynamic(DynamicType::Divergent(_)), _)
|
||||
| (_, Type::Dynamic(DynamicType::Divergent(_))) => {
|
||||
ConstraintSet::from_bool(db, relation.is_assignability())
|
||||
ConstraintSet::from(relation.is_assignability())
|
||||
}
|
||||
|
||||
(Type::TypeAlias(self_alias), _) => {
|
||||
@@ -409,7 +409,7 @@ impl<'db> Type<'db> {
|
||||
(Type::KnownInstance(KnownInstanceType::Field(field)), right)
|
||||
if relation.is_assignability() =>
|
||||
{
|
||||
field.default_type(db).when_none_or(db, |default_type| {
|
||||
field.default_type(db).when_none_or(|default_type| {
|
||||
default_type.has_relation_to_impl(
|
||||
db,
|
||||
right,
|
||||
@@ -434,39 +434,31 @@ impl<'db> Type<'db> {
|
||||
!matches!(dynamic, DynamicType::Divergent(_)),
|
||||
"DynamicType::Divergent should have been handled in an earlier branch"
|
||||
);
|
||||
ConstraintSet::from_bool(
|
||||
db,
|
||||
match relation {
|
||||
TypeRelation::Subtyping | TypeRelation::SubtypingAssuming(_) => false,
|
||||
TypeRelation::Assignability | TypeRelation::ConstraintSetAssignability => {
|
||||
true
|
||||
}
|
||||
TypeRelation::Redundancy => match target {
|
||||
Type::Dynamic(_) => true,
|
||||
Type::Union(union) => union.elements(db).iter().any(Type::is_dynamic),
|
||||
_ => false,
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
(_, Type::Dynamic(_)) => ConstraintSet::from_bool(
|
||||
db,
|
||||
match relation {
|
||||
ConstraintSet::from(match relation {
|
||||
TypeRelation::Subtyping | TypeRelation::SubtypingAssuming(_) => false,
|
||||
TypeRelation::Assignability | TypeRelation::ConstraintSetAssignability => true,
|
||||
TypeRelation::Redundancy => match self {
|
||||
TypeRelation::Redundancy => match target {
|
||||
Type::Dynamic(_) => true,
|
||||
Type::Intersection(intersection) => {
|
||||
// If a `Divergent` type is involved, it must not be eliminated.
|
||||
intersection
|
||||
.positive(db)
|
||||
.iter()
|
||||
.any(Type::is_non_divergent_dynamic)
|
||||
}
|
||||
Type::Union(union) => union.elements(db).iter().any(Type::is_dynamic),
|
||||
_ => false,
|
||||
},
|
||||
})
|
||||
}
|
||||
(_, Type::Dynamic(_)) => ConstraintSet::from(match relation {
|
||||
TypeRelation::Subtyping | TypeRelation::SubtypingAssuming(_) => false,
|
||||
TypeRelation::Assignability | TypeRelation::ConstraintSetAssignability => true,
|
||||
TypeRelation::Redundancy => match self {
|
||||
Type::Dynamic(_) => true,
|
||||
Type::Intersection(intersection) => {
|
||||
// If a `Divergent` type is involved, it must not be eliminated.
|
||||
intersection
|
||||
.positive(db)
|
||||
.iter()
|
||||
.any(Type::is_non_divergent_dynamic)
|
||||
}
|
||||
_ => false,
|
||||
},
|
||||
),
|
||||
}),
|
||||
|
||||
// In general, a TypeVar `T` is not a subtype of a type `S` unless one of the two conditions is satisfied:
|
||||
// 1. `T` is a bound TypeVar and `T`'s upper bound is a subtype of `S`.
|
||||
@@ -479,7 +471,7 @@ impl<'db> Type<'db> {
|
||||
if !bound_typevar.is_inferable(db, inferable)
|
||||
&& union.elements(db).contains(&self) =>
|
||||
{
|
||||
ConstraintSet::from_bool(db, true)
|
||||
ConstraintSet::from(true)
|
||||
}
|
||||
|
||||
// A similar rule applies in reverse to intersection types.
|
||||
@@ -487,13 +479,13 @@ impl<'db> Type<'db> {
|
||||
if !bound_typevar.is_inferable(db, inferable)
|
||||
&& intersection.positive(db).contains(&target) =>
|
||||
{
|
||||
ConstraintSet::from_bool(db, true)
|
||||
ConstraintSet::from(true)
|
||||
}
|
||||
(Type::Intersection(intersection), Type::TypeVar(bound_typevar))
|
||||
if !bound_typevar.is_inferable(db, inferable)
|
||||
&& intersection.negative(db).contains(&target) =>
|
||||
{
|
||||
ConstraintSet::from_bool(db, false)
|
||||
ConstraintSet::from(false)
|
||||
}
|
||||
|
||||
// Two identical typevars must always solve to the same type, so they are always
|
||||
@@ -505,7 +497,7 @@ impl<'db> Type<'db> {
|
||||
if !lhs_bound_typevar.is_inferable(db, inferable)
|
||||
&& lhs_bound_typevar.is_same_typevar_as(db, rhs_bound_typevar) =>
|
||||
{
|
||||
ConstraintSet::from_bool(db, true)
|
||||
ConstraintSet::from(true)
|
||||
}
|
||||
|
||||
// `type[T]` is a subtype of the class object `A` if every instance of `T` is a subtype of an instance
|
||||
@@ -514,7 +506,7 @@ impl<'db> Type<'db> {
|
||||
if !subclass_of
|
||||
.into_type_var()
|
||||
.zip(target.to_instance(db))
|
||||
.when_some_and(db, |(this_instance, other_instance)| {
|
||||
.when_some_and(|(this_instance, other_instance)| {
|
||||
Type::TypeVar(this_instance).has_relation_to_impl(
|
||||
db,
|
||||
other_instance,
|
||||
@@ -530,7 +522,7 @@ impl<'db> Type<'db> {
|
||||
subclass_of
|
||||
.into_type_var()
|
||||
.zip(target.to_instance(db))
|
||||
.when_some_and(db, |(this_instance, other_instance)| {
|
||||
.when_some_and(|(this_instance, other_instance)| {
|
||||
Type::TypeVar(this_instance).has_relation_to_impl(
|
||||
db,
|
||||
other_instance,
|
||||
@@ -546,7 +538,7 @@ impl<'db> Type<'db> {
|
||||
if !subclass_of
|
||||
.into_type_var()
|
||||
.zip(self.to_instance(db))
|
||||
.when_some_and(db, |(other_instance, this_instance)| {
|
||||
.when_some_and(|(other_instance, this_instance)| {
|
||||
this_instance.has_relation_to_impl(
|
||||
db,
|
||||
Type::TypeVar(other_instance),
|
||||
@@ -562,7 +554,7 @@ impl<'db> Type<'db> {
|
||||
subclass_of
|
||||
.into_type_var()
|
||||
.zip(self.to_instance(db))
|
||||
.when_some_and(db, |(other_instance, this_instance)| {
|
||||
.when_some_and(|(other_instance, this_instance)| {
|
||||
this_instance.has_relation_to_impl(
|
||||
db,
|
||||
Type::TypeVar(other_instance),
|
||||
@@ -615,7 +607,7 @@ impl<'db> Type<'db> {
|
||||
&& !bound_typevar
|
||||
.typevar(db)
|
||||
.constraints(db)
|
||||
.when_some_and(db, |constraints| {
|
||||
.when_some_and(|constraints| {
|
||||
constraints.iter().when_all(db, |constraint| {
|
||||
self.has_relation_to_impl(
|
||||
db,
|
||||
@@ -636,7 +628,7 @@ impl<'db> Type<'db> {
|
||||
bound_typevar
|
||||
.typevar(db)
|
||||
.constraints(db)
|
||||
.when_some_and(db, |constraints| {
|
||||
.when_some_and(|constraints| {
|
||||
constraints.iter().when_all(db, |constraint| {
|
||||
self.has_relation_to_impl(
|
||||
db,
|
||||
@@ -656,11 +648,11 @@ impl<'db> Type<'db> {
|
||||
|
||||
// TODO: record the unification constraints
|
||||
|
||||
ConstraintSet::from_bool(db, true)
|
||||
ConstraintSet::from(true)
|
||||
}
|
||||
|
||||
// `Never` is the bottom type, the empty set.
|
||||
(_, Type::Never) => ConstraintSet::from_bool(db, false),
|
||||
(_, Type::Never) => ConstraintSet::from(false),
|
||||
|
||||
(Type::NewTypeInstance(self_newtype), Type::NewTypeInstance(target_newtype)) => {
|
||||
self_newtype.has_relation_to_impl(db, target_newtype)
|
||||
@@ -719,7 +711,7 @@ impl<'db> Type<'db> {
|
||||
disjointness_visitor,
|
||||
)
|
||||
} else {
|
||||
ConstraintSet::from_bool(db, false)
|
||||
ConstraintSet::from(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -823,7 +815,7 @@ impl<'db> Type<'db> {
|
||||
// bound. This is true even if the bound is a final class, since the typevar can still
|
||||
// be specialized to `Never`.)
|
||||
(_, Type::TypeVar(bound_typevar)) if !bound_typevar.is_inferable(db, inferable) => {
|
||||
ConstraintSet::from_bool(db, false)
|
||||
ConstraintSet::from(false)
|
||||
}
|
||||
|
||||
(_, Type::TypeVar(typevar))
|
||||
@@ -844,29 +836,26 @@ impl<'db> Type<'db> {
|
||||
{
|
||||
// TODO: record the unification constraints
|
||||
|
||||
typevar
|
||||
.typevar(db)
|
||||
.upper_bound(db)
|
||||
.when_none_or(db, |bound| {
|
||||
self.has_relation_to_impl(
|
||||
db,
|
||||
bound,
|
||||
inferable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
})
|
||||
typevar.typevar(db).upper_bound(db).when_none_or(|bound| {
|
||||
self.has_relation_to_impl(
|
||||
db,
|
||||
bound,
|
||||
inferable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: Infer specializations here
|
||||
(_, Type::TypeVar(bound_typevar)) if bound_typevar.is_inferable(db, inferable) => {
|
||||
ConstraintSet::from_bool(db, false)
|
||||
ConstraintSet::from(false)
|
||||
}
|
||||
(Type::TypeVar(bound_typevar), _) => {
|
||||
// All inferable cases should have been handled above
|
||||
assert!(!bound_typevar.is_inferable(db, inferable));
|
||||
ConstraintSet::from_bool(db, false)
|
||||
ConstraintSet::from(false)
|
||||
}
|
||||
|
||||
// All other `NewType` assignments fall back to the concrete base type.
|
||||
@@ -886,12 +875,8 @@ impl<'db> Type<'db> {
|
||||
|
||||
// Note that the definition of `Type::AlwaysFalsy` depends on the return value of `__bool__`.
|
||||
// If `__bool__` always returns True or False, it can be treated as a subtype of `AlwaysTruthy` or `AlwaysFalsy`, respectively.
|
||||
(left, Type::AlwaysFalsy) => {
|
||||
ConstraintSet::from_bool(db, left.bool(db).is_always_false())
|
||||
}
|
||||
(left, Type::AlwaysTruthy) => {
|
||||
ConstraintSet::from_bool(db, left.bool(db).is_always_true())
|
||||
}
|
||||
(left, Type::AlwaysFalsy) => ConstraintSet::from(left.bool(db).is_always_false()),
|
||||
(left, Type::AlwaysTruthy) => ConstraintSet::from(left.bool(db).is_always_true()),
|
||||
// Currently, the only supertype of `AlwaysFalsy` and `AlwaysTruthy` is the universal set (object instance).
|
||||
(Type::AlwaysFalsy | Type::AlwaysTruthy, _) => {
|
||||
target.when_equivalent_to(db, Type::object(), inferable)
|
||||
@@ -951,7 +936,7 @@ impl<'db> Type<'db> {
|
||||
| Type::FunctionLiteral(_)
|
||||
| Type::ModuleLiteral(_)
|
||||
| Type::EnumLiteral(_),
|
||||
) => ConstraintSet::from_bool(db, false),
|
||||
) => ConstraintSet::from(false),
|
||||
|
||||
(Type::Callable(self_callable), Type::Callable(other_callable)) => relation_visitor
|
||||
.visit((self, target, relation), || {
|
||||
@@ -967,17 +952,16 @@ impl<'db> Type<'db> {
|
||||
|
||||
(_, Type::Callable(other_callable)) => {
|
||||
relation_visitor.visit((self, target, relation), || {
|
||||
self.try_upcast_to_callable(db)
|
||||
.when_some_and(db, |callables| {
|
||||
callables.has_relation_to_impl(
|
||||
db,
|
||||
other_callable,
|
||||
inferable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
})
|
||||
self.try_upcast_to_callable(db).when_some_and(|callables| {
|
||||
callables.has_relation_to_impl(
|
||||
db,
|
||||
other_callable,
|
||||
inferable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1015,7 +999,7 @@ impl<'db> Type<'db> {
|
||||
}
|
||||
|
||||
// A protocol instance can never be a subtype of a nominal type, with the *sole* exception of `object`.
|
||||
(Type::ProtocolInstance(_), _) => ConstraintSet::from_bool(db, false),
|
||||
(Type::ProtocolInstance(_), _) => ConstraintSet::from(false),
|
||||
|
||||
(Type::TypedDict(self_typeddict), Type::TypedDict(other_typeddict)) => relation_visitor
|
||||
.visit((self, target, relation), || {
|
||||
@@ -1047,22 +1031,22 @@ impl<'db> Type<'db> {
|
||||
}),
|
||||
|
||||
// A non-`TypedDict` cannot subtype a `TypedDict`
|
||||
(_, Type::TypedDict(_)) => ConstraintSet::from_bool(db, false),
|
||||
(_, Type::TypedDict(_)) => ConstraintSet::from(false),
|
||||
|
||||
// All `StringLiteral` types are a subtype of `LiteralString`.
|
||||
(Type::StringLiteral(_), Type::LiteralString) => ConstraintSet::from_bool(db, true),
|
||||
(Type::StringLiteral(_), Type::LiteralString) => ConstraintSet::from(true),
|
||||
|
||||
// An instance is a subtype of an enum literal, if it is an instance of the enum class
|
||||
// and the enum has only one member.
|
||||
(Type::NominalInstance(_), Type::EnumLiteral(target_enum_literal)) => {
|
||||
if target_enum_literal.enum_class_instance(db) != self {
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
return ConstraintSet::from(false);
|
||||
}
|
||||
|
||||
ConstraintSet::from_bool(
|
||||
ConstraintSet::from(is_single_member_enum(
|
||||
db,
|
||||
is_single_member_enum(db, target_enum_literal.enum_class(db)),
|
||||
)
|
||||
target_enum_literal.enum_class(db),
|
||||
))
|
||||
}
|
||||
|
||||
// Except for the special `LiteralString` case above,
|
||||
@@ -1078,7 +1062,7 @@ impl<'db> Type<'db> {
|
||||
| Type::EnumLiteral(_)
|
||||
| Type::FunctionLiteral(_),
|
||||
_,
|
||||
) => (self.literal_fallback_instance(db)).when_some_and(db, |instance| {
|
||||
) => (self.literal_fallback_instance(db)).when_some_and(|instance| {
|
||||
instance.has_relation_to_impl(
|
||||
db,
|
||||
target,
|
||||
@@ -1123,7 +1107,7 @@ impl<'db> Type<'db> {
|
||||
|
||||
(Type::DataclassDecorator(_) | Type::DataclassTransformer(_), _) => {
|
||||
// TODO: Implement subtyping using an equivalent `Callable` type.
|
||||
ConstraintSet::from_bool(db, false)
|
||||
ConstraintSet::from(false)
|
||||
}
|
||||
|
||||
// `TypeIs` is invariant.
|
||||
@@ -1186,7 +1170,7 @@ impl<'db> Type<'db> {
|
||||
)
|
||||
}
|
||||
|
||||
(Type::Callable(_), _) => ConstraintSet::from_bool(db, false),
|
||||
(Type::Callable(_), _) => ConstraintSet::from(false),
|
||||
|
||||
(Type::BoundSuper(_), Type::BoundSuper(_)) => {
|
||||
self.when_equivalent_to(db, target, inferable)
|
||||
@@ -1203,7 +1187,7 @@ impl<'db> Type<'db> {
|
||||
(Type::SubclassOf(subclass_of), _) | (_, Type::SubclassOf(subclass_of))
|
||||
if subclass_of.is_type_var() =>
|
||||
{
|
||||
ConstraintSet::from_bool(db, false)
|
||||
ConstraintSet::from(false)
|
||||
}
|
||||
|
||||
// `Literal[<class 'C'>]` is a subtype of `type[B]` if `C` is a subclass of `B`,
|
||||
@@ -1221,7 +1205,7 @@ impl<'db> Type<'db> {
|
||||
disjointness_visitor,
|
||||
)
|
||||
})
|
||||
.unwrap_or_else(|| ConstraintSet::from_bool(db, relation.is_assignability())),
|
||||
.unwrap_or_else(|| ConstraintSet::from(relation.is_assignability())),
|
||||
|
||||
// Similarly, `<class 'C'>` is assignable to `<class 'C[...]'>` (a generic-alias type)
|
||||
// if the default specialization of `C` is assignable to `C[...]`. This scenario occurs
|
||||
@@ -1263,7 +1247,7 @@ impl<'db> Type<'db> {
|
||||
disjointness_visitor,
|
||||
)
|
||||
})
|
||||
.unwrap_or_else(|| ConstraintSet::from_bool(db, relation.is_assignability())),
|
||||
.unwrap_or_else(|| ConstraintSet::from(relation.is_assignability())),
|
||||
|
||||
// This branch asks: given two types `type[T]` and `type[S]`, is `type[T]` a subtype of `type[S]`?
|
||||
(Type::SubclassOf(self_subclass_ty), Type::SubclassOf(target_subclass_ty)) => {
|
||||
@@ -1314,7 +1298,7 @@ impl<'db> Type<'db> {
|
||||
disjointness_visitor,
|
||||
)
|
||||
.or(db, || {
|
||||
ConstraintSet::from_bool(db, relation.is_assignability()).and(db, || {
|
||||
ConstraintSet::from(relation.is_assignability()).and(db, || {
|
||||
other.has_relation_to_impl(
|
||||
db,
|
||||
KnownClass::Type.to_instance(db),
|
||||
@@ -1419,7 +1403,7 @@ impl<'db> Type<'db> {
|
||||
|
||||
// Other than the special cases enumerated above, nominal-instance types are never
|
||||
// subtypes of any other variants
|
||||
(Type::NominalInstance(_), _) => ConstraintSet::from_bool(db, false),
|
||||
(Type::NominalInstance(_), _) => ConstraintSet::from(false),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1446,7 +1430,7 @@ impl<'db> Type<'db> {
|
||||
other: Type<'db>,
|
||||
inferable: InferableTypeVars<'_, 'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
self.is_equivalent_to_impl(db, other, inferable, &default_equivalent_visitor(db))
|
||||
self.is_equivalent_to_impl(db, other, inferable, &IsEquivalentVisitor::default())
|
||||
}
|
||||
|
||||
pub(crate) fn is_equivalent_to_impl(
|
||||
@@ -1457,7 +1441,7 @@ impl<'db> Type<'db> {
|
||||
visitor: &IsEquivalentVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
if self == other {
|
||||
return ConstraintSet::from_bool(db, true);
|
||||
return ConstraintSet::from(true);
|
||||
}
|
||||
|
||||
match (self, other) {
|
||||
@@ -1465,17 +1449,17 @@ impl<'db> Type<'db> {
|
||||
// which prevents `Divergent` from being eliminated during union reduction.
|
||||
(Type::Dynamic(_), Type::Dynamic(DynamicType::Divergent(_)))
|
||||
| (Type::Dynamic(DynamicType::Divergent(_)), Type::Dynamic(_)) => {
|
||||
ConstraintSet::from_bool(db, false)
|
||||
ConstraintSet::from(false)
|
||||
}
|
||||
(Type::Dynamic(_), Type::Dynamic(_)) => ConstraintSet::from_bool(db, true),
|
||||
(Type::Dynamic(_), Type::Dynamic(_)) => ConstraintSet::from(true),
|
||||
|
||||
(Type::SubclassOf(first), Type::SubclassOf(second)) => {
|
||||
match (first.subclass_of(), second.subclass_of()) {
|
||||
(first, second) if first == second => ConstraintSet::from_bool(db, true),
|
||||
(first, second) if first == second => ConstraintSet::from(true),
|
||||
(SubclassOfInner::Dynamic(_), SubclassOfInner::Dynamic(_)) => {
|
||||
ConstraintSet::from_bool(db, true)
|
||||
ConstraintSet::from(true)
|
||||
}
|
||||
_ => ConstraintSet::from_bool(db, false),
|
||||
_ => ConstraintSet::from(false),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1494,7 +1478,7 @@ impl<'db> Type<'db> {
|
||||
}
|
||||
|
||||
(Type::NewTypeInstance(self_newtype), Type::NewTypeInstance(other_newtype)) => {
|
||||
ConstraintSet::from_bool(db, self_newtype.is_equivalent_to_impl(db, other_newtype))
|
||||
ConstraintSet::from(self_newtype.is_equivalent_to_impl(db, other_newtype))
|
||||
}
|
||||
|
||||
(Type::NominalInstance(first), Type::NominalInstance(second)) => {
|
||||
@@ -1527,16 +1511,16 @@ impl<'db> Type<'db> {
|
||||
}
|
||||
(Type::ProtocolInstance(protocol), nominal @ Type::NominalInstance(n))
|
||||
| (nominal @ Type::NominalInstance(n), Type::ProtocolInstance(protocol)) => {
|
||||
ConstraintSet::from_bool(db, n.is_object() && protocol.normalized(db) == nominal)
|
||||
ConstraintSet::from(n.is_object() && protocol.normalized(db) == nominal)
|
||||
}
|
||||
// An instance of an enum class is equivalent to an enum literal of that class,
|
||||
// if that enum has only has one member.
|
||||
(Type::NominalInstance(instance), Type::EnumLiteral(literal))
|
||||
| (Type::EnumLiteral(literal), Type::NominalInstance(instance)) => {
|
||||
if literal.enum_class_instance(db) != Type::NominalInstance(instance) {
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
return ConstraintSet::from(false);
|
||||
}
|
||||
ConstraintSet::from_bool(db, is_single_member_enum(db, instance.class_literal(db)))
|
||||
ConstraintSet::from(is_single_member_enum(db, instance.class_literal(db)))
|
||||
}
|
||||
|
||||
(Type::PropertyInstance(left), Type::PropertyInstance(right)) => {
|
||||
@@ -1547,7 +1531,7 @@ impl<'db> Type<'db> {
|
||||
left.is_equivalent_to_impl(db, right, inferable, visitor)
|
||||
}),
|
||||
|
||||
_ => ConstraintSet::from_bool(db, false),
|
||||
_ => ConstraintSet::from(false),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1581,8 +1565,8 @@ impl<'db> Type<'db> {
|
||||
db,
|
||||
other,
|
||||
inferable,
|
||||
&default_disjoint_visitor(db),
|
||||
&default_relation_visitor(db),
|
||||
&IsDisjointVisitor::default(),
|
||||
&HasRelationToVisitor::default(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1607,7 +1591,7 @@ impl<'db> Type<'db> {
|
||||
.member(db, member.name())
|
||||
.place
|
||||
.ignore_possibly_undefined()
|
||||
.when_none_or(db, |attribute_type| {
|
||||
.when_none_or(|attribute_type| {
|
||||
member.has_disjoint_type_from(
|
||||
db,
|
||||
attribute_type,
|
||||
@@ -1620,9 +1604,9 @@ impl<'db> Type<'db> {
|
||||
}
|
||||
|
||||
match (self, other) {
|
||||
(Type::Never, _) | (_, Type::Never) => ConstraintSet::from_bool(db, true),
|
||||
(Type::Never, _) | (_, Type::Never) => ConstraintSet::from(true),
|
||||
|
||||
(Type::Dynamic(_), _) | (_, Type::Dynamic(_)) => ConstraintSet::from_bool(db, false),
|
||||
(Type::Dynamic(_), _) | (_, Type::Dynamic(_)) => ConstraintSet::from(false),
|
||||
|
||||
(Type::TypeAlias(alias), _) => {
|
||||
let self_alias_ty = alias.value_type(db);
|
||||
@@ -1675,7 +1659,7 @@ impl<'db> Type<'db> {
|
||||
if !subclass_of
|
||||
.into_type_var()
|
||||
.zip(other.to_instance(db))
|
||||
.when_none_or(db, |(this_instance, other_instance)| {
|
||||
.when_none_or(|(this_instance, other_instance)| {
|
||||
Type::TypeVar(this_instance).is_disjoint_from_impl(
|
||||
db,
|
||||
other_instance,
|
||||
@@ -1690,7 +1674,7 @@ impl<'db> Type<'db> {
|
||||
subclass_of
|
||||
.into_type_var()
|
||||
.zip(other.to_instance(db))
|
||||
.when_none_or(db, |(this_instance, other_instance)| {
|
||||
.when_none_or(|(this_instance, other_instance)| {
|
||||
Type::TypeVar(this_instance).is_disjoint_from_impl(
|
||||
db,
|
||||
other_instance,
|
||||
@@ -1709,7 +1693,7 @@ impl<'db> Type<'db> {
|
||||
if !self_bound_typevar.is_inferable(db, inferable)
|
||||
&& self_bound_typevar.is_same_typevar_as(db, other_bound_typevar) =>
|
||||
{
|
||||
ConstraintSet::from_bool(db, false)
|
||||
ConstraintSet::from(false)
|
||||
}
|
||||
|
||||
(tvar @ Type::TypeVar(bound_typevar), Type::Intersection(intersection))
|
||||
@@ -1717,7 +1701,7 @@ impl<'db> Type<'db> {
|
||||
if !bound_typevar.is_inferable(db, inferable)
|
||||
&& intersection.negative(db).contains(&tvar) =>
|
||||
{
|
||||
ConstraintSet::from_bool(db, true)
|
||||
ConstraintSet::from(true)
|
||||
}
|
||||
|
||||
// An unbounded typevar is never disjoint from any other type, since it might be
|
||||
@@ -1728,7 +1712,7 @@ impl<'db> Type<'db> {
|
||||
if !bound_typevar.is_inferable(db, inferable) =>
|
||||
{
|
||||
match bound_typevar.typevar(db).bound_or_constraints(db) {
|
||||
None => ConstraintSet::from_bool(db, false),
|
||||
None => ConstraintSet::from(false),
|
||||
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => bound
|
||||
.is_disjoint_from_impl(
|
||||
db,
|
||||
@@ -1752,7 +1736,7 @@ impl<'db> Type<'db> {
|
||||
}
|
||||
|
||||
// TODO: Infer specializations here
|
||||
(Type::TypeVar(_), _) | (_, Type::TypeVar(_)) => ConstraintSet::from_bool(db, false),
|
||||
(Type::TypeVar(_), _) | (_, Type::TypeVar(_)) => ConstraintSet::from(false),
|
||||
|
||||
(Type::Union(union), other) | (other, Type::Union(union)) => {
|
||||
union.elements(db).iter().when_all(db, |e| {
|
||||
@@ -1857,7 +1841,7 @@ impl<'db> Type<'db> {
|
||||
| Type::ClassLiteral(..)
|
||||
| Type::SpecialForm(..)
|
||||
| Type::KnownInstance(..)),
|
||||
) => ConstraintSet::from_bool(db, left != right),
|
||||
) => ConstraintSet::from(left != right),
|
||||
|
||||
(
|
||||
Type::SubclassOf(_),
|
||||
@@ -1886,16 +1870,16 @@ impl<'db> Type<'db> {
|
||||
| Type::WrapperDescriptor(..)
|
||||
| Type::ModuleLiteral(..),
|
||||
Type::SubclassOf(_),
|
||||
) => ConstraintSet::from_bool(db, true),
|
||||
) => ConstraintSet::from(true),
|
||||
|
||||
(Type::AlwaysTruthy, ty) | (ty, Type::AlwaysTruthy) => {
|
||||
// `Truthiness::Ambiguous` may include `AlwaysTrue` as a subset, so it's not guaranteed to be disjoint.
|
||||
// Thus, they are only disjoint if `ty.bool() == AlwaysFalse`.
|
||||
ConstraintSet::from_bool(db, ty.bool(db).is_always_false())
|
||||
ConstraintSet::from(ty.bool(db).is_always_false())
|
||||
}
|
||||
(Type::AlwaysFalsy, ty) | (ty, Type::AlwaysFalsy) => {
|
||||
// Similarly, they are only disjoint if `ty.bool() == AlwaysTrue`.
|
||||
ConstraintSet::from_bool(db, ty.bool(db).is_always_true())
|
||||
ConstraintSet::from(ty.bool(db).is_always_true())
|
||||
}
|
||||
|
||||
(Type::ProtocolInstance(left), Type::ProtocolInstance(right)) => disjointness_visitor
|
||||
@@ -2027,7 +2011,7 @@ impl<'db> Type<'db> {
|
||||
disjointness_visitor,
|
||||
relation_visitor,
|
||||
),
|
||||
Place::Undefined => ConstraintSet::from_bool(db, false),
|
||||
Place::Undefined => ConstraintSet::from(false),
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -2036,29 +2020,26 @@ impl<'db> Type<'db> {
|
||||
(Type::SubclassOf(subclass_of_ty), _) | (_, Type::SubclassOf(subclass_of_ty))
|
||||
if subclass_of_ty.is_type_var() =>
|
||||
{
|
||||
ConstraintSet::from_bool(db, true)
|
||||
ConstraintSet::from(true)
|
||||
}
|
||||
|
||||
(Type::GenericAlias(left_alias), Type::GenericAlias(right_alias)) => {
|
||||
ConstraintSet::from_bool(db, left_alias.origin(db) != right_alias.origin(db)).or(
|
||||
db,
|
||||
|| {
|
||||
left_alias.specialization(db).is_disjoint_from_impl(
|
||||
db,
|
||||
right_alias.specialization(db),
|
||||
inferable,
|
||||
disjointness_visitor,
|
||||
relation_visitor,
|
||||
)
|
||||
},
|
||||
)
|
||||
ConstraintSet::from(left_alias.origin(db) != right_alias.origin(db)).or(db, || {
|
||||
left_alias.specialization(db).is_disjoint_from_impl(
|
||||
db,
|
||||
right_alias.specialization(db),
|
||||
inferable,
|
||||
disjointness_visitor,
|
||||
relation_visitor,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
(Type::ClassLiteral(class_literal), other @ Type::GenericAlias(_))
|
||||
| (other @ Type::GenericAlias(_), Type::ClassLiteral(class_literal)) => class_literal
|
||||
.default_specialization(db)
|
||||
.into_generic_alias()
|
||||
.when_none_or(db, |alias| {
|
||||
.when_none_or(|alias| {
|
||||
other.is_disjoint_from_impl(
|
||||
db,
|
||||
Type::GenericAlias(alias),
|
||||
@@ -2071,9 +2052,8 @@ impl<'db> Type<'db> {
|
||||
(Type::SubclassOf(subclass_of_ty), Type::ClassLiteral(class_b))
|
||||
| (Type::ClassLiteral(class_b), Type::SubclassOf(subclass_of_ty)) => {
|
||||
match subclass_of_ty.subclass_of() {
|
||||
SubclassOfInner::Dynamic(_) => ConstraintSet::from_bool(db, false),
|
||||
SubclassOfInner::Class(class_a) => ConstraintSet::from_bool(
|
||||
db,
|
||||
SubclassOfInner::Dynamic(_) => ConstraintSet::from(false),
|
||||
SubclassOfInner::Class(class_a) => ConstraintSet::from(
|
||||
!class_a.could_exist_in_mro_of(db, ClassType::NonGeneric(class_b)),
|
||||
),
|
||||
SubclassOfInner::TypeVar(_) => unreachable!(),
|
||||
@@ -2083,9 +2063,8 @@ impl<'db> Type<'db> {
|
||||
(Type::SubclassOf(subclass_of_ty), Type::GenericAlias(alias_b))
|
||||
| (Type::GenericAlias(alias_b), Type::SubclassOf(subclass_of_ty)) => {
|
||||
match subclass_of_ty.subclass_of() {
|
||||
SubclassOfInner::Dynamic(_) => ConstraintSet::from_bool(db, false),
|
||||
SubclassOfInner::Class(class_a) => ConstraintSet::from_bool(
|
||||
db,
|
||||
SubclassOfInner::Dynamic(_) => ConstraintSet::from(false),
|
||||
SubclassOfInner::Class(class_a) => ConstraintSet::from(
|
||||
!class_a.could_exist_in_mro_of(db, ClassType::Generic(alias_b)),
|
||||
),
|
||||
SubclassOfInner::TypeVar(_) => unreachable!(),
|
||||
@@ -2123,12 +2102,12 @@ impl<'db> Type<'db> {
|
||||
|
||||
(Type::SpecialForm(special_form), Type::NominalInstance(instance))
|
||||
| (Type::NominalInstance(instance), Type::SpecialForm(special_form)) => {
|
||||
ConstraintSet::from_bool(db, !special_form.is_instance_of(db, instance.class(db)))
|
||||
ConstraintSet::from(!special_form.is_instance_of(db, instance.class(db)))
|
||||
}
|
||||
|
||||
(Type::KnownInstance(known_instance), Type::NominalInstance(instance))
|
||||
| (Type::NominalInstance(instance), Type::KnownInstance(known_instance)) => {
|
||||
ConstraintSet::from_bool(db, !known_instance.is_instance_of(db, instance.class(db)))
|
||||
ConstraintSet::from(!known_instance.is_instance_of(db, instance.class(db)))
|
||||
}
|
||||
|
||||
(
|
||||
@@ -2148,7 +2127,7 @@ impl<'db> Type<'db> {
|
||||
|
||||
(Type::BooleanLiteral(..) | Type::TypeIs(_) | Type::TypeGuard(_), _)
|
||||
| (_, Type::BooleanLiteral(..) | Type::TypeIs(_) | Type::TypeGuard(_)) => {
|
||||
ConstraintSet::from_bool(db, true)
|
||||
ConstraintSet::from(true)
|
||||
}
|
||||
|
||||
(Type::IntLiteral(..), Type::NominalInstance(instance))
|
||||
@@ -2160,12 +2139,10 @@ impl<'db> Type<'db> {
|
||||
.negate(db)
|
||||
}
|
||||
|
||||
(Type::IntLiteral(..), _) | (_, Type::IntLiteral(..)) => {
|
||||
ConstraintSet::from_bool(db, true)
|
||||
}
|
||||
(Type::IntLiteral(..), _) | (_, Type::IntLiteral(..)) => ConstraintSet::from(true),
|
||||
|
||||
(Type::StringLiteral(..), Type::LiteralString)
|
||||
| (Type::LiteralString, Type::StringLiteral(..)) => ConstraintSet::from_bool(db, false),
|
||||
| (Type::LiteralString, Type::StringLiteral(..)) => ConstraintSet::from(false),
|
||||
|
||||
(Type::StringLiteral(..) | Type::LiteralString, Type::NominalInstance(instance))
|
||||
| (Type::NominalInstance(instance), Type::StringLiteral(..) | Type::LiteralString) => {
|
||||
@@ -2176,10 +2153,8 @@ impl<'db> Type<'db> {
|
||||
.negate(db)
|
||||
}
|
||||
|
||||
(Type::LiteralString, Type::LiteralString) => ConstraintSet::from_bool(db, false),
|
||||
(Type::LiteralString, _) | (_, Type::LiteralString) => {
|
||||
ConstraintSet::from_bool(db, true)
|
||||
}
|
||||
(Type::LiteralString, Type::LiteralString) => ConstraintSet::from(false),
|
||||
(Type::LiteralString, _) | (_, Type::LiteralString) => ConstraintSet::from(true),
|
||||
|
||||
(Type::BytesLiteral(..), Type::NominalInstance(instance))
|
||||
| (Type::NominalInstance(instance), Type::BytesLiteral(..)) => {
|
||||
@@ -2204,9 +2179,7 @@ impl<'db> Type<'db> {
|
||||
)
|
||||
.negate(db)
|
||||
}
|
||||
(Type::EnumLiteral(..), _) | (_, Type::EnumLiteral(..)) => {
|
||||
ConstraintSet::from_bool(db, true)
|
||||
}
|
||||
(Type::EnumLiteral(..), _) | (_, Type::EnumLiteral(..)) => ConstraintSet::from(true),
|
||||
|
||||
// A class-literal type `X` is always disjoint from an instance type `Y`,
|
||||
// unless the type expressing "all instances of `Z`" is a subtype of of `Y`,
|
||||
@@ -2277,7 +2250,7 @@ impl<'db> Type<'db> {
|
||||
// No two callable types are ever disjoint because
|
||||
// `(*args: object, **kwargs: object) -> Never` is a subtype of all fully static
|
||||
// callable types.
|
||||
ConstraintSet::from_bool(db, false)
|
||||
ConstraintSet::from(false)
|
||||
}
|
||||
|
||||
(Type::Callable(_), Type::StringLiteral(_) | Type::BytesLiteral(_))
|
||||
@@ -2285,7 +2258,7 @@ impl<'db> Type<'db> {
|
||||
// A callable type is disjoint from other literal types. For example,
|
||||
// `Type::StringLiteral` must be an instance of exactly `str`, not a subclass
|
||||
// of `str`, and `str` is not callable. The same applies to other literal types.
|
||||
ConstraintSet::from_bool(db, true)
|
||||
ConstraintSet::from(true)
|
||||
}
|
||||
|
||||
(Type::Callable(_), Type::SpecialForm(special_form))
|
||||
@@ -2294,7 +2267,7 @@ impl<'db> Type<'db> {
|
||||
// that are callable (like TypedDict and collection constructors).
|
||||
// Most special forms are type constructors/annotations (like `typing.Literal`,
|
||||
// `typing.Union`, etc.) that are subscripted, not called.
|
||||
ConstraintSet::from_bool(db, !special_form.is_callable())
|
||||
ConstraintSet::from(!special_form.is_callable())
|
||||
}
|
||||
|
||||
(
|
||||
@@ -2312,7 +2285,7 @@ impl<'db> Type<'db> {
|
||||
)
|
||||
.place
|
||||
.ignore_possibly_undefined()
|
||||
.when_none_or(db, |dunder_call| {
|
||||
.when_none_or(|dunder_call| {
|
||||
dunder_call
|
||||
.has_relation_to_impl(
|
||||
db,
|
||||
@@ -2334,7 +2307,7 @@ impl<'db> Type<'db> {
|
||||
Type::Callable(_) | Type::DataclassDecorator(_) | Type::DataclassTransformer(_),
|
||||
) => {
|
||||
// TODO: Implement disjointness for general callable type with other types
|
||||
ConstraintSet::from_bool(db, false)
|
||||
ConstraintSet::from(false)
|
||||
}
|
||||
|
||||
(Type::ModuleLiteral(..), other @ Type::NominalInstance(..))
|
||||
@@ -2396,9 +2369,7 @@ impl<'db> Type<'db> {
|
||||
)
|
||||
}
|
||||
|
||||
(Type::GenericAlias(_), _) | (_, Type::GenericAlias(_)) => {
|
||||
ConstraintSet::from_bool(db, true)
|
||||
}
|
||||
(Type::GenericAlias(_), _) | (_, Type::GenericAlias(_)) => ConstraintSet::from(true),
|
||||
|
||||
(Type::TypedDict(self_typeddict), Type::TypedDict(other_typeddict)) => {
|
||||
disjointness_visitor.visit((self, other), || {
|
||||
@@ -2445,8 +2416,10 @@ fn is_redundant_with_cycle_initial<'db>(
|
||||
pub(crate) type HasRelationToVisitor<'db> =
|
||||
CycleDetector<TypeRelation<'db>, (Type<'db>, Type<'db>, TypeRelation<'db>), ConstraintSet<'db>>;
|
||||
|
||||
pub(crate) fn default_relation_visitor(db: &dyn Db) -> HasRelationToVisitor<'_> {
|
||||
HasRelationToVisitor::new(ConstraintSet::from_bool(db, true))
|
||||
impl Default for HasRelationToVisitor<'_> {
|
||||
fn default() -> Self {
|
||||
HasRelationToVisitor::new(ConstraintSet::from(true))
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`PairVisitor`] that is used in `is_disjoint_from` methods.
|
||||
@@ -2455,8 +2428,10 @@ pub(crate) type IsDisjointVisitor<'db> = PairVisitor<'db, IsDisjoint, Constraint
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct IsDisjoint;
|
||||
|
||||
pub(crate) fn default_disjoint_visitor(db: &dyn Db) -> IsDisjointVisitor<'_> {
|
||||
IsDisjointVisitor::new(ConstraintSet::from_bool(db, false))
|
||||
impl Default for IsDisjointVisitor<'_> {
|
||||
fn default() -> Self {
|
||||
IsDisjointVisitor::new(ConstraintSet::from(false))
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`PairVisitor`] that is used in `is_equivalent` methods.
|
||||
@@ -2465,6 +2440,8 @@ pub(crate) type IsEquivalentVisitor<'db> = PairVisitor<'db, IsEquivalent, Constr
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct IsEquivalent;
|
||||
|
||||
pub(crate) fn default_equivalent_visitor(db: &dyn Db) -> IsEquivalentVisitor<'_> {
|
||||
IsEquivalentVisitor::new(ConstraintSet::from_bool(db, true))
|
||||
impl Default for IsEquivalentVisitor<'_> {
|
||||
fn default() -> Self {
|
||||
IsEquivalentVisitor::new(ConstraintSet::from(true))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ use crate::types::generics::{GenericContext, InferableTypeVars, walk_generic_con
|
||||
use crate::types::infer::{infer_deferred_types, infer_scope_types};
|
||||
use crate::types::relation::{
|
||||
HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, TypeRelation,
|
||||
default_disjoint_visitor, default_relation_visitor,
|
||||
};
|
||||
use crate::types::{
|
||||
ApplyTypeMappingVisitor, BindingContext, BoundTypeVarInstance, CallableType, CallableTypeKind,
|
||||
@@ -307,8 +306,8 @@ impl<'db> CallableSignature<'db> {
|
||||
other,
|
||||
inferable,
|
||||
TypeRelation::Subtyping,
|
||||
&default_relation_visitor(db),
|
||||
&default_disjoint_visitor(db),
|
||||
&HasRelationToVisitor::default(),
|
||||
&IsDisjointVisitor::default(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -363,8 +362,8 @@ impl<'db> CallableSignature<'db> {
|
||||
other,
|
||||
inferable,
|
||||
TypeRelation::ConstraintSetAssignability,
|
||||
&default_relation_visitor(db),
|
||||
&default_disjoint_visitor(db),
|
||||
&HasRelationToVisitor::default(),
|
||||
&IsDisjointVisitor::default(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -550,7 +549,7 @@ impl<'db> CallableSignature<'db> {
|
||||
}
|
||||
(_, _) => {
|
||||
if self == other {
|
||||
return ConstraintSet::from_bool(db, true);
|
||||
return ConstraintSet::from(true);
|
||||
}
|
||||
self.is_subtype_of_impl(db, other, inferable)
|
||||
.and(db, || other.is_subtype_of_impl(db, self, inferable))
|
||||
@@ -1006,14 +1005,14 @@ impl<'db> Signature<'db> {
|
||||
inferable: InferableTypeVars<'_, 'db>,
|
||||
visitor: &IsEquivalentVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
let mut result = ConstraintSet::from_bool(db, true);
|
||||
let mut result = ConstraintSet::from(true);
|
||||
|
||||
if self.parameters.is_gradual() != other.parameters.is_gradual() {
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
return ConstraintSet::from(false);
|
||||
}
|
||||
|
||||
if self.parameters.len() != other.parameters.len() {
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
return ConstraintSet::from(false);
|
||||
}
|
||||
|
||||
let mut check_types = |self_type: Type<'db>, other_type: Type<'db>| {
|
||||
@@ -1070,7 +1069,7 @@ impl<'db> Signature<'db> {
|
||||
|
||||
(ParameterKind::KeywordVariadic { .. }, ParameterKind::KeywordVariadic { .. }) => {}
|
||||
|
||||
_ => return ConstraintSet::from_bool(db, false),
|
||||
_ => return ConstraintSet::from(false),
|
||||
}
|
||||
|
||||
if !check_types(
|
||||
@@ -1133,8 +1132,8 @@ impl<'db> Signature<'db> {
|
||||
other,
|
||||
inferable,
|
||||
TypeRelation::ConstraintSetAssignability,
|
||||
&default_relation_visitor(db),
|
||||
&default_disjoint_visitor(db),
|
||||
&HasRelationToVisitor::default(),
|
||||
&IsDisjointVisitor::default(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1252,7 +1251,7 @@ impl<'db> Signature<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
let mut result = ConstraintSet::from_bool(db, true);
|
||||
let mut result = ConstraintSet::from(true);
|
||||
|
||||
let mut check_types = |type1: Type<'db>, type2: Type<'db>| {
|
||||
match (type1, type2) {
|
||||
@@ -1309,15 +1308,15 @@ impl<'db> Signature<'db> {
|
||||
.keyword_variadic()
|
||||
.is_some_and(|(_, param)| param.annotated_type().is_object())
|
||||
{
|
||||
return ConstraintSet::from_bool(db, true);
|
||||
return ConstraintSet::from(true);
|
||||
}
|
||||
|
||||
// The top signature is supertype of (and assignable from) all other signatures. It is a
|
||||
// subtype of no signature except itself, and assignable only to the gradual signature.
|
||||
if other.parameters.is_top() {
|
||||
return ConstraintSet::from_bool(db, true);
|
||||
return ConstraintSet::from(true);
|
||||
} else if self.parameters.is_top() && !other.parameters.is_gradual() {
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
return ConstraintSet::from(false);
|
||||
}
|
||||
|
||||
// If either of the parameter lists is gradual (`...`), then it is assignable to and from
|
||||
@@ -1325,8 +1324,7 @@ impl<'db> Signature<'db> {
|
||||
if self.parameters.is_gradual() || other.parameters.is_gradual() {
|
||||
result.intersect(
|
||||
db,
|
||||
ConstraintSet::from_bool(
|
||||
db,
|
||||
ConstraintSet::from(
|
||||
relation.is_assignability() || relation.is_constraint_set_assignability(),
|
||||
),
|
||||
);
|
||||
@@ -1428,7 +1426,7 @@ impl<'db> Signature<'db> {
|
||||
// `other`, then the non-variadic parameters in `self` must have a default
|
||||
// value.
|
||||
if default_type.is_none() {
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
return ConstraintSet::from(false);
|
||||
}
|
||||
}
|
||||
ParameterKind::Variadic { .. } | ParameterKind::KeywordVariadic { .. } => {
|
||||
@@ -1440,7 +1438,7 @@ impl<'db> Signature<'db> {
|
||||
EitherOrBoth::Right(_) => {
|
||||
// If there are more parameters in `other` than in `self`, then `self` is not a
|
||||
// subtype of `other`.
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
return ConstraintSet::from(false);
|
||||
}
|
||||
|
||||
EitherOrBoth::Both(self_parameter, other_parameter) => {
|
||||
@@ -1460,7 +1458,7 @@ impl<'db> Signature<'db> {
|
||||
},
|
||||
) => {
|
||||
if self_default.is_none() && other_default.is_some() {
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
return ConstraintSet::from(false);
|
||||
}
|
||||
if !check_types(
|
||||
other_parameter.annotated_type(),
|
||||
@@ -1481,11 +1479,11 @@ impl<'db> Signature<'db> {
|
||||
},
|
||||
) => {
|
||||
if self_name != other_name {
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
return ConstraintSet::from(false);
|
||||
}
|
||||
// The following checks are the same as positional-only parameters.
|
||||
if self_default.is_none() && other_default.is_some() {
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
return ConstraintSet::from(false);
|
||||
}
|
||||
if !check_types(
|
||||
other_parameter.annotated_type(),
|
||||
@@ -1570,7 +1568,7 @@ impl<'db> Signature<'db> {
|
||||
break;
|
||||
}
|
||||
|
||||
_ => return ConstraintSet::from_bool(db, false),
|
||||
_ => return ConstraintSet::from(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1603,7 +1601,7 @@ impl<'db> Signature<'db> {
|
||||
// previous loop. They cannot be matched against any parameter in `other` which
|
||||
// only contains keyword-only and keyword-variadic parameters so the subtype
|
||||
// relation is invalid.
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
return ConstraintSet::from(false);
|
||||
}
|
||||
ParameterKind::Variadic { .. } => {}
|
||||
}
|
||||
@@ -1630,7 +1628,7 @@ impl<'db> Signature<'db> {
|
||||
..
|
||||
} => {
|
||||
if self_default.is_none() && other_default.is_some() {
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
return ConstraintSet::from(false);
|
||||
}
|
||||
if !check_types(
|
||||
other_parameter.annotated_type(),
|
||||
@@ -1651,14 +1649,14 @@ impl<'db> Signature<'db> {
|
||||
return result;
|
||||
}
|
||||
} else {
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
return ConstraintSet::from(false);
|
||||
}
|
||||
}
|
||||
ParameterKind::KeywordVariadic { .. } => {
|
||||
let Some(self_keyword_variadic_type) = self_keyword_variadic else {
|
||||
// For a `self <: other` relationship, if `other` has a keyword variadic
|
||||
// parameter, `self` must also have a keyword variadic parameter.
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
return ConstraintSet::from(false);
|
||||
};
|
||||
if !check_types(other_parameter.annotated_type(), self_keyword_variadic_type) {
|
||||
return result;
|
||||
@@ -1666,7 +1664,7 @@ impl<'db> Signature<'db> {
|
||||
}
|
||||
_ => {
|
||||
// This can only occur in case of a syntax error.
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
return ConstraintSet::from(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1675,7 +1673,7 @@ impl<'db> Signature<'db> {
|
||||
// optional otherwise the subtype relation is invalid.
|
||||
for (_, self_parameter) in self_keywords {
|
||||
if self_parameter.default_type().is_none() {
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
return ConstraintSet::from(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -224,16 +224,13 @@ impl<'db> SubclassOfType<'db> {
|
||||
) -> ConstraintSet<'db> {
|
||||
match (self.subclass_of, other.subclass_of) {
|
||||
(SubclassOfInner::Dynamic(_), SubclassOfInner::Dynamic(_)) => {
|
||||
ConstraintSet::from_bool(db, !relation.is_subtyping())
|
||||
ConstraintSet::from(!relation.is_subtyping())
|
||||
}
|
||||
(SubclassOfInner::Dynamic(_), SubclassOfInner::Class(other_class)) => {
|
||||
ConstraintSet::from_bool(
|
||||
db,
|
||||
other_class.is_object(db) || relation.is_assignability(),
|
||||
)
|
||||
ConstraintSet::from(other_class.is_object(db) || relation.is_assignability())
|
||||
}
|
||||
(SubclassOfInner::Class(_), SubclassOfInner::Dynamic(_)) => {
|
||||
ConstraintSet::from_bool(db, relation.is_assignability())
|
||||
ConstraintSet::from(relation.is_assignability())
|
||||
}
|
||||
|
||||
// For example, `type[bool]` describes all possible runtime subclasses of the class `bool`,
|
||||
@@ -267,10 +264,10 @@ impl<'db> SubclassOfType<'db> {
|
||||
) -> ConstraintSet<'db> {
|
||||
match (self.subclass_of, other.subclass_of) {
|
||||
(SubclassOfInner::Dynamic(_), _) | (_, SubclassOfInner::Dynamic(_)) => {
|
||||
ConstraintSet::from_bool(db, false)
|
||||
ConstraintSet::from(false)
|
||||
}
|
||||
(SubclassOfInner::Class(self_class), SubclassOfInner::Class(other_class)) => {
|
||||
ConstraintSet::from_bool(db, !self_class.could_coexist_in_mro_with(db, other_class))
|
||||
ConstraintSet::from(!self_class.could_coexist_in_mro_with(db, other_class))
|
||||
}
|
||||
(SubclassOfInner::TypeVar(_), _) | (_, SubclassOfInner::TypeVar(_)) => {
|
||||
unreachable!()
|
||||
|
||||
@@ -516,7 +516,7 @@ impl<'db> FixedLengthTuple<Type<'db>> {
|
||||
) -> ConstraintSet<'db> {
|
||||
match other {
|
||||
Tuple::Fixed(other) => {
|
||||
ConstraintSet::from_bool(db, self.0.len() == other.0.len()).and(db, || {
|
||||
ConstraintSet::from(self.0.len() == other.0.len()).and(db, || {
|
||||
(self.0.iter().zip(&other.0)).when_all(db, |(self_ty, other_ty)| {
|
||||
self_ty.has_relation_to_impl(
|
||||
db,
|
||||
@@ -533,11 +533,11 @@ impl<'db> FixedLengthTuple<Type<'db>> {
|
||||
Tuple::Variable(other) => {
|
||||
// This tuple must have enough elements to match up with the other tuple's prefix
|
||||
// and suffix, and each of those elements must pairwise satisfy the relation.
|
||||
let mut result = ConstraintSet::from_bool(db, true);
|
||||
let mut result = ConstraintSet::from(true);
|
||||
let mut self_iter = self.0.iter();
|
||||
for other_ty in other.prefix_elements() {
|
||||
let Some(self_ty) = self_iter.next() else {
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
return ConstraintSet::from(false);
|
||||
};
|
||||
let element_constraints = self_ty.has_relation_to_impl(
|
||||
db,
|
||||
@@ -556,7 +556,7 @@ impl<'db> FixedLengthTuple<Type<'db>> {
|
||||
}
|
||||
for other_ty in other.iter_suffix_elements().rev() {
|
||||
let Some(self_ty) = self_iter.next_back() else {
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
return ConstraintSet::from(false);
|
||||
};
|
||||
let element_constraints = self_ty.has_relation_to_impl(
|
||||
db,
|
||||
@@ -599,7 +599,7 @@ impl<'db> FixedLengthTuple<Type<'db>> {
|
||||
inferable: InferableTypeVars<'_, 'db>,
|
||||
visitor: &IsEquivalentVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
ConstraintSet::from_bool(db, self.0.len() == other.0.len()).and(db, || {
|
||||
ConstraintSet::from(self.0.len() == other.0.len()).and(db, || {
|
||||
(self.0.iter())
|
||||
.zip(&other.0)
|
||||
.when_all(db, |(self_ty, other_ty)| {
|
||||
@@ -1031,17 +1031,17 @@ impl<'db> VariableLengthTuple<Type<'db>> {
|
||||
// possible lengths. This means that `tuple[Any, ...]` can match any tuple of any
|
||||
// length.
|
||||
if !relation.is_assignability() || !self.variable().is_dynamic() {
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
return ConstraintSet::from(false);
|
||||
}
|
||||
|
||||
// In addition, the other tuple must have enough elements to match up with this
|
||||
// tuple's prefix and suffix, and each of those elements must pairwise satisfy the
|
||||
// relation.
|
||||
let mut result = ConstraintSet::from_bool(db, true);
|
||||
let mut result = ConstraintSet::from(true);
|
||||
let mut other_iter = other.iter_all_elements();
|
||||
for self_ty in self.prenormalized_prefix_elements(db, None) {
|
||||
let Some(other_ty) = other_iter.next() else {
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
return ConstraintSet::from(false);
|
||||
};
|
||||
let element_constraints = self_ty.has_relation_to_impl(
|
||||
db,
|
||||
@@ -1061,7 +1061,7 @@ impl<'db> VariableLengthTuple<Type<'db>> {
|
||||
let suffix: Vec<_> = self.prenormalized_suffix_elements(db, None).collect();
|
||||
for self_ty in suffix.iter().rev() {
|
||||
let Some(other_ty) = other_iter.next_back() else {
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
return ConstraintSet::from(false);
|
||||
};
|
||||
let element_constraints = self_ty.has_relation_to_impl(
|
||||
db,
|
||||
@@ -1097,7 +1097,7 @@ impl<'db> VariableLengthTuple<Type<'db>> {
|
||||
// The overlapping parts of the prefixes and suffixes must satisfy the relation.
|
||||
// Any remaining parts must satisfy the relation with the other tuple's
|
||||
// variable-length part.
|
||||
let mut result = ConstraintSet::from_bool(db, true);
|
||||
let mut result = ConstraintSet::from(true);
|
||||
let pairwise = self
|
||||
.prenormalized_prefix_elements(db, self_prenormalize_variable)
|
||||
.zip_longest(
|
||||
@@ -1127,7 +1127,7 @@ impl<'db> VariableLengthTuple<Type<'db>> {
|
||||
// that can materialize to provide it (for assignability only),
|
||||
// as in `tuple[Any, ...]` matching `tuple[int, int]`.
|
||||
if !relation.is_assignability() || !self.variable().is_dynamic() {
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
return ConstraintSet::from(false);
|
||||
}
|
||||
self.variable().has_relation_to_impl(
|
||||
db,
|
||||
@@ -1181,7 +1181,7 @@ impl<'db> VariableLengthTuple<Type<'db>> {
|
||||
// that can materialize to provide it (for assignability only),
|
||||
// as in `tuple[Any, ...]` matching `tuple[int, int]`.
|
||||
if !relation.is_assignability() || !self.variable().is_dynamic() {
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
return ConstraintSet::from(false);
|
||||
}
|
||||
self.variable().has_relation_to_impl(
|
||||
db,
|
||||
@@ -1233,7 +1233,7 @@ impl<'db> VariableLengthTuple<Type<'db>> {
|
||||
self_ty.is_equivalent_to_impl(db, other_ty, inferable, visitor)
|
||||
}
|
||||
EitherOrBoth::Left(_) | EitherOrBoth::Right(_) => {
|
||||
ConstraintSet::from_bool(db, false)
|
||||
ConstraintSet::from(false)
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -1245,7 +1245,7 @@ impl<'db> VariableLengthTuple<Type<'db>> {
|
||||
self_ty.is_equivalent_to_impl(db, other_ty, inferable, visitor)
|
||||
}
|
||||
EitherOrBoth::Left(_) | EitherOrBoth::Right(_) => {
|
||||
ConstraintSet::from_bool(db, false)
|
||||
ConstraintSet::from(false)
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -1513,7 +1513,7 @@ impl<'db> Tuple<Type<'db>> {
|
||||
self_tuple.is_equivalent_to_impl(db, other_tuple, inferable, visitor)
|
||||
}
|
||||
(Tuple::Fixed(_), Tuple::Variable(_)) | (Tuple::Variable(_), Tuple::Fixed(_)) => {
|
||||
ConstraintSet::from_bool(db, false)
|
||||
ConstraintSet::from(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1530,10 +1530,10 @@ impl<'db> Tuple<Type<'db>> {
|
||||
let (self_min, self_max) = self.len().size_hint();
|
||||
let (other_min, other_max) = other.len().size_hint();
|
||||
if self_max.is_some_and(|max| max < other_min) {
|
||||
return ConstraintSet::from_bool(db, true);
|
||||
return ConstraintSet::from(true);
|
||||
}
|
||||
if other_max.is_some_and(|max| max < self_min) {
|
||||
return ConstraintSet::from_bool(db, true);
|
||||
return ConstraintSet::from(true);
|
||||
}
|
||||
|
||||
// If any of the required elements are pairwise disjoint, the tuples are disjoint as well.
|
||||
|
||||
@@ -151,24 +151,24 @@ impl<'db> TypedDictType<'db> {
|
||||
&& let Some(target_defining_class) = target.defining_class()
|
||||
&& defining_class.is_subclass_of(db, target_defining_class)
|
||||
{
|
||||
return ConstraintSet::from_bool(db, true);
|
||||
return ConstraintSet::from(true);
|
||||
}
|
||||
|
||||
let self_items = self.items(db);
|
||||
let target_items = target.items(db);
|
||||
// Many rules violations short-circuit with "never", but asking whether one field is
|
||||
// [relation] to/of another can produce more complicated constraints, and we collect those.
|
||||
let mut constraints = ConstraintSet::from_bool(db, true);
|
||||
let mut constraints = ConstraintSet::from(true);
|
||||
for (target_item_name, target_item_field) in target_items {
|
||||
let field_constraints = if target_item_field.is_required() {
|
||||
// required target fields
|
||||
let Some(self_item_field) = self_items.get(target_item_name) else {
|
||||
// Self is missing a required field.
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
return ConstraintSet::from(false);
|
||||
};
|
||||
if !self_item_field.is_required() {
|
||||
// A required field is not required in self.
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
return ConstraintSet::from(false);
|
||||
}
|
||||
if target_item_field.is_read_only() {
|
||||
// For `ReadOnly[]` fields in the target, the corresponding fields in
|
||||
@@ -186,7 +186,7 @@ impl<'db> TypedDictType<'db> {
|
||||
} else {
|
||||
if self_item_field.is_read_only() {
|
||||
// A read-only field can't be assigned to a mutable target.
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
return ConstraintSet::from(false);
|
||||
}
|
||||
// For mutable fields in the target, the relation needs to apply both
|
||||
// ways, or else mutating the target could violate the structural
|
||||
@@ -252,12 +252,12 @@ impl<'db> TypedDictType<'db> {
|
||||
if let Some(self_item_field) = self_items.get(target_item_name) {
|
||||
if self_item_field.is_read_only() {
|
||||
// A read-only field can't be assigned to a mutable target.
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
return ConstraintSet::from(false);
|
||||
}
|
||||
if self_item_field.is_required() {
|
||||
// A required field can't be assigned to a not-required, mutable field
|
||||
// in the target, because `del` is allowed on the target field.
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
return ConstraintSet::from(false);
|
||||
}
|
||||
|
||||
// As above, for mutable fields in the target, the relation needs
|
||||
@@ -289,7 +289,7 @@ impl<'db> TypedDictType<'db> {
|
||||
// interaction between two structural assignability rules prevents
|
||||
// unsoundness" in `typed_dict.md`.
|
||||
// TODO: `closed` and `extra_items` support will go here.
|
||||
ConstraintSet::from_bool(db, false)
|
||||
ConstraintSet::from(false)
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -341,13 +341,13 @@ impl<'db> TypedDictType<'db> {
|
||||
// sorted order instead of paying for a lookup for each field, as long as their lengths are
|
||||
// the same.
|
||||
if self.items(db).len() != other.items(db).len() {
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
return ConstraintSet::from(false);
|
||||
}
|
||||
self.items(db).iter().zip(other.items(db)).when_all(
|
||||
db,
|
||||
|((name, field), (other_name, other_field))| {
|
||||
if name != other_name || field.flags != other_field.flags {
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
return ConstraintSet::from(false);
|
||||
}
|
||||
field.declared_ty.is_equivalent_to_impl(
|
||||
db,
|
||||
@@ -435,7 +435,7 @@ impl<'db> TypedDictType<'db> {
|
||||
{
|
||||
// One side demands a `Required` source field, while the other side demands a
|
||||
// `NotRequired` one. They must be disjoint.
|
||||
return ConstraintSet::from_bool(db, true);
|
||||
return ConstraintSet::from(true);
|
||||
}
|
||||
}
|
||||
if !self_field.is_read_only() && !other_field.is_read_only() {
|
||||
|
||||
Reference in New Issue
Block a user