mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 05:44:56 +00:00
[ty] Remove special casing for string-literal-in-tuple __contains__
(#19642)
This commit is contained in:
parent
32c454bb56
commit
27b03a9d7b
6 changed files with 156 additions and 243 deletions
|
@ -1,49 +0,0 @@
|
|||
# Static binary operations using `in`
|
||||
|
||||
## Basic functionality
|
||||
|
||||
This demonstrates type inference support for `<str-literal> in <tuple>`:
|
||||
|
||||
```py
|
||||
from ty_extensions import static_assert
|
||||
|
||||
static_assert("foo" in ("quux", "foo", "baz"))
|
||||
static_assert("foo" not in ("quux", "bar", "baz"))
|
||||
```
|
||||
|
||||
## With variables
|
||||
|
||||
```py
|
||||
from ty_extensions import static_assert
|
||||
|
||||
x = ("quux", "foo", "baz")
|
||||
static_assert("foo" in x)
|
||||
|
||||
x = ("quux", "bar", "baz")
|
||||
static_assert("foo" not in x)
|
||||
```
|
||||
|
||||
## Statically unknown results in a `bool`
|
||||
|
||||
```py
|
||||
def _(a: str, b: str):
|
||||
reveal_type("foo" in (a, b)) # revealed: bool
|
||||
```
|
||||
|
||||
## Values being unknown doesn't mean the result is unknown
|
||||
|
||||
For example, when the types are completely disjoint:
|
||||
|
||||
```py
|
||||
from ty_extensions import static_assert
|
||||
|
||||
def _(a: int, b: int):
|
||||
static_assert("foo" not in (a, b))
|
||||
```
|
||||
|
||||
## Failure cases
|
||||
|
||||
```py
|
||||
# We don't support byte strings.
|
||||
reveal_type(b"foo" not in (b"quux", b"foo", b"baz")) # revealed: bool
|
||||
```
|
|
@ -1,60 +1,65 @@
|
|||
# List all members
|
||||
|
||||
This test suite acts as a set of unit tests for our `ide_support::all_members` routine, which lists
|
||||
all members available on a given type. This routine is used for autocomplete suggestions.
|
||||
|
||||
## Basic functionality
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
The `ty_extensions.all_members` function allows access to a tuple of accessible members/attributes
|
||||
on a given object. For example, all member functions of `str` are available on `"a"`:
|
||||
The `ty_extensions.all_members` and `ty_extensions.has_member` functions expose a Python-level API
|
||||
that can be used to query which attributes `ide_support::all_members` understands as being available
|
||||
on a given object. For example, all member functions of `str` are available on `"a"`. The Python API
|
||||
`all_members` returns a tuple of all available members; `has_member` returns `Literal[True]` if a
|
||||
given member is present in that tuple, and `Literal[False]` if not:
|
||||
|
||||
```py
|
||||
from ty_extensions import all_members, static_assert
|
||||
from ty_extensions import static_assert, has_member
|
||||
|
||||
members_of_str = all_members("a")
|
||||
|
||||
static_assert("replace" in members_of_str)
|
||||
static_assert("startswith" in members_of_str)
|
||||
static_assert("isupper" in members_of_str)
|
||||
static_assert(has_member("a", "replace"))
|
||||
static_assert(has_member("a", "startswith"))
|
||||
static_assert(has_member("a", "isupper"))
|
||||
```
|
||||
|
||||
Similarly, special members such as `__add__` are also available:
|
||||
|
||||
```py
|
||||
static_assert("__add__" in members_of_str)
|
||||
static_assert("__gt__" in members_of_str)
|
||||
static_assert(has_member("a", "__add__"))
|
||||
static_assert(has_member("a", "__gt__"))
|
||||
```
|
||||
|
||||
Members of base classes are also included (these dunder methods are defined on `object`):
|
||||
|
||||
```py
|
||||
static_assert("__doc__" in members_of_str)
|
||||
static_assert("__repr__" in members_of_str)
|
||||
static_assert(has_member("a", "__doc__"))
|
||||
static_assert(has_member("a", "__repr__"))
|
||||
```
|
||||
|
||||
Non-existent members are not included:
|
||||
|
||||
```py
|
||||
static_assert("non_existent" not in members_of_str)
|
||||
static_assert(not has_member("a", "non_existent"))
|
||||
```
|
||||
|
||||
Note: The full list of all members is relatively long, but `reveal_type` can theoretically be used
|
||||
to see them all:
|
||||
The full list of all members is relatively long, but `reveal_type` can be used in combination with
|
||||
`all_members` to see them all:
|
||||
|
||||
```py
|
||||
from typing_extensions import reveal_type
|
||||
from ty_extensions import all_members
|
||||
|
||||
reveal_type(members_of_str) # error: [revealed-type]
|
||||
reveal_type(all_members("a")) # error: [revealed-type]
|
||||
```
|
||||
|
||||
## Kinds of types
|
||||
|
||||
### Class instances
|
||||
|
||||
For instances of classes, `all_members` returns class members and implicit instance members of all
|
||||
classes in the MRO:
|
||||
For instances of classes, class members and implicit instance members of all superclasses are
|
||||
understood as being available:
|
||||
|
||||
```py
|
||||
from ty_extensions import all_members, static_assert
|
||||
from ty_extensions import has_member, static_assert
|
||||
|
||||
class Base:
|
||||
base_class_attr: int = 1
|
||||
|
@ -86,25 +91,23 @@ class C(Intermediate):
|
|||
def static_method() -> int:
|
||||
return 1
|
||||
|
||||
members_of_instance = all_members(C())
|
||||
static_assert(has_member(C(), "base_class_attr"))
|
||||
static_assert(has_member(C(), "intermediate_attr"))
|
||||
static_assert(has_member(C(), "class_attr"))
|
||||
|
||||
static_assert("base_class_attr" in members_of_instance)
|
||||
static_assert("intermediate_attr" in members_of_instance)
|
||||
static_assert("class_attr" in members_of_instance)
|
||||
static_assert(has_member(C(), "base_instance_attr"))
|
||||
static_assert(has_member(C(), "intermediate_instance_attr"))
|
||||
static_assert(has_member(C(), "instance_attr"))
|
||||
|
||||
static_assert("base_instance_attr" in members_of_instance)
|
||||
static_assert("intermediate_instance_attr" in members_of_instance)
|
||||
static_assert("instance_attr" in members_of_instance)
|
||||
static_assert(has_member(C(), "f_base"))
|
||||
static_assert(has_member(C(), "f_intermediate"))
|
||||
static_assert(has_member(C(), "f_c"))
|
||||
|
||||
static_assert("f_base" in members_of_instance)
|
||||
static_assert("f_intermediate" in members_of_instance)
|
||||
static_assert("f_c" in members_of_instance)
|
||||
static_assert(has_member(C(), "property_attr"))
|
||||
static_assert(has_member(C(), "class_method"))
|
||||
static_assert(has_member(C(), "static_method"))
|
||||
|
||||
static_assert("property_attr" in members_of_instance)
|
||||
static_assert("class_method" in members_of_instance)
|
||||
static_assert("static_method" in members_of_instance)
|
||||
|
||||
static_assert("non_existent" not in members_of_instance)
|
||||
static_assert(not has_member(C(), "non_existent"))
|
||||
```
|
||||
|
||||
### Class objects
|
||||
|
@ -112,7 +115,7 @@ static_assert("non_existent" not in members_of_instance)
|
|||
Class-level attributes can also be accessed through the class itself:
|
||||
|
||||
```py
|
||||
from ty_extensions import all_members, static_assert
|
||||
from ty_extensions import has_member, static_assert
|
||||
|
||||
class Base:
|
||||
base_attr: int = 1
|
||||
|
@ -123,18 +126,16 @@ class C(Base):
|
|||
def f(self):
|
||||
self.instance_attr = True
|
||||
|
||||
members_of_class = all_members(C)
|
||||
static_assert(has_member(C, "class_attr"))
|
||||
static_assert(has_member(C, "base_attr"))
|
||||
|
||||
static_assert("class_attr" in members_of_class)
|
||||
static_assert("base_attr" in members_of_class)
|
||||
|
||||
static_assert("non_existent" not in members_of_class)
|
||||
static_assert(not has_member(C, "non_existent"))
|
||||
```
|
||||
|
||||
But instance attributes can not be accessed this way:
|
||||
|
||||
```py
|
||||
static_assert("instance_attr" not in members_of_class)
|
||||
static_assert(not has_member(C, "instance_attr"))
|
||||
```
|
||||
|
||||
When a class has a metaclass, members of that metaclass (and bases of that metaclass) are also
|
||||
|
@ -150,16 +151,16 @@ class Meta(MetaBase):
|
|||
class D(Base, metaclass=Meta):
|
||||
class_attr = 3
|
||||
|
||||
static_assert("meta_base_attr" in all_members(D))
|
||||
static_assert("meta_attr" in all_members(D))
|
||||
static_assert("base_attr" in all_members(D))
|
||||
static_assert("class_attr" in all_members(D))
|
||||
static_assert(has_member(D, "meta_base_attr"))
|
||||
static_assert(has_member(D, "meta_attr"))
|
||||
static_assert(has_member(D, "base_attr"))
|
||||
static_assert(has_member(D, "class_attr"))
|
||||
```
|
||||
|
||||
### Generic classes
|
||||
|
||||
```py
|
||||
from ty_extensions import all_members, static_assert
|
||||
from ty_extensions import has_member, static_assert
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
@ -167,52 +168,53 @@ T = TypeVar("T")
|
|||
class C(Generic[T]):
|
||||
base_attr: T
|
||||
|
||||
static_assert("base_attr" in all_members(C[int]))
|
||||
static_assert("base_attr" in all_members(C[int]()))
|
||||
static_assert(has_member(C[int], "base_attr"))
|
||||
static_assert(has_member(C[int](), "base_attr"))
|
||||
```
|
||||
|
||||
### Other instance-like types
|
||||
|
||||
```py
|
||||
from ty_extensions import all_members, static_assert
|
||||
from ty_extensions import has_member, static_assert
|
||||
from typing_extensions import LiteralString
|
||||
|
||||
static_assert("__xor__" in all_members(True))
|
||||
static_assert("bit_length" in all_members(1))
|
||||
static_assert("startswith" in all_members("a"))
|
||||
static_assert("__buffer__" in all_members(b"a"))
|
||||
static_assert("is_integer" in all_members(3.14))
|
||||
static_assert(has_member(True, "__xor__"))
|
||||
static_assert(has_member(1, "bit_length"))
|
||||
static_assert(has_member("a", "startswith"))
|
||||
static_assert(has_member(b"a", "__buffer__"))
|
||||
static_assert(has_member(3.14, "is_integer"))
|
||||
|
||||
def _(literal_string: LiteralString):
|
||||
static_assert("startswith" in all_members(literal_string))
|
||||
static_assert(has_member(literal_string, "startswith"))
|
||||
|
||||
static_assert("count" in all_members(("some", "tuple", 1, 2)))
|
||||
static_assert(has_member(("some", "tuple", 1, 2), "count"))
|
||||
|
||||
static_assert("__doc__" in all_members(len))
|
||||
static_assert("__doc__" in all_members("a".startswith))
|
||||
static_assert(has_member(len, "__doc__"))
|
||||
static_assert(has_member("a".startswith, "__doc__"))
|
||||
```
|
||||
|
||||
### Enums
|
||||
|
||||
```py
|
||||
from ty_extensions import all_members, static_assert
|
||||
from ty_extensions import has_member, static_assert
|
||||
from enum import Enum
|
||||
|
||||
class Answer(Enum):
|
||||
NO = 0
|
||||
YES = 1
|
||||
|
||||
static_assert("NO" in all_members(Answer))
|
||||
static_assert("YES" in all_members(Answer))
|
||||
static_assert("__members__" in all_members(Answer))
|
||||
static_assert(has_member(Answer, "NO"))
|
||||
static_assert(has_member(Answer, "YES"))
|
||||
static_assert(has_member(Answer, "__members__"))
|
||||
```
|
||||
|
||||
### Unions
|
||||
|
||||
For unions, `all_members` will only return members that are available on all elements of the union.
|
||||
For unions, `ide_support::all_members` only returns members that are available on all elements of
|
||||
the union.
|
||||
|
||||
```py
|
||||
from ty_extensions import all_members, static_assert
|
||||
from ty_extensions import has_member, static_assert
|
||||
|
||||
class A:
|
||||
on_both: int = 1
|
||||
|
@ -223,20 +225,20 @@ class B:
|
|||
only_on_b: str = "b"
|
||||
|
||||
def f(union: A | B):
|
||||
static_assert("on_both" in all_members(union))
|
||||
static_assert("only_on_a" not in all_members(union))
|
||||
static_assert("only_on_b" not in all_members(union))
|
||||
static_assert(has_member(union, "on_both"))
|
||||
static_assert(not has_member(union, "only_on_a"))
|
||||
static_assert(not has_member(union, "only_on_b"))
|
||||
```
|
||||
|
||||
### Intersections
|
||||
|
||||
#### Only positive types
|
||||
|
||||
Conversely, for intersections, `all_members` will list members that are available on any of the
|
||||
elements:
|
||||
Conversely, for intersections, `ide_support::all_members` lists members that are available on any of
|
||||
the elements:
|
||||
|
||||
```py
|
||||
from ty_extensions import all_members, static_assert
|
||||
from ty_extensions import has_member, static_assert
|
||||
|
||||
class A:
|
||||
on_both: int = 1
|
||||
|
@ -249,9 +251,9 @@ class B:
|
|||
def f(intersection: object):
|
||||
if isinstance(intersection, A):
|
||||
if isinstance(intersection, B):
|
||||
static_assert("on_both" in all_members(intersection))
|
||||
static_assert("only_on_a" in all_members(intersection))
|
||||
static_assert("only_on_b" in all_members(intersection))
|
||||
static_assert(has_member(intersection, "on_both"))
|
||||
static_assert(has_member(intersection, "only_on_a"))
|
||||
static_assert(has_member(intersection, "only_on_b"))
|
||||
```
|
||||
|
||||
#### With negative types
|
||||
|
@ -259,7 +261,7 @@ def f(intersection: object):
|
|||
It also works when negative types are introduced:
|
||||
|
||||
```py
|
||||
from ty_extensions import all_members, static_assert
|
||||
from ty_extensions import has_member, static_assert
|
||||
|
||||
class A:
|
||||
on_all: int = 1
|
||||
|
@ -284,27 +286,27 @@ def f(intersection: object):
|
|||
if isinstance(intersection, B):
|
||||
if not isinstance(intersection, C):
|
||||
reveal_type(intersection) # revealed: A & B & ~C
|
||||
static_assert("on_all" in all_members(intersection))
|
||||
static_assert("only_on_a" in all_members(intersection))
|
||||
static_assert("only_on_b" in all_members(intersection))
|
||||
static_assert("only_on_c" not in all_members(intersection))
|
||||
static_assert("only_on_ab" in all_members(intersection))
|
||||
static_assert("only_on_ac" in all_members(intersection))
|
||||
static_assert("only_on_bc" in all_members(intersection))
|
||||
static_assert(has_member(intersection, "on_all"))
|
||||
static_assert(has_member(intersection, "only_on_a"))
|
||||
static_assert(has_member(intersection, "only_on_b"))
|
||||
static_assert(not has_member(intersection, "only_on_c"))
|
||||
static_assert(has_member(intersection, "only_on_ab"))
|
||||
static_assert(has_member(intersection, "only_on_ac"))
|
||||
static_assert(has_member(intersection, "only_on_bc"))
|
||||
```
|
||||
|
||||
## Modules
|
||||
|
||||
### Basic support with sub-modules
|
||||
|
||||
`all_members` can also list attributes on modules:
|
||||
`ide_support::all_members` can also list attributes on modules:
|
||||
|
||||
```py
|
||||
from ty_extensions import all_members, static_assert
|
||||
from ty_extensions import has_member, static_assert
|
||||
import math
|
||||
|
||||
static_assert("pi" in all_members(math))
|
||||
static_assert("cos" in all_members(math))
|
||||
static_assert(has_member(math, "pi"))
|
||||
static_assert(has_member(math, "cos"))
|
||||
```
|
||||
|
||||
This also works for submodules:
|
||||
|
@ -312,18 +314,18 @@ This also works for submodules:
|
|||
```py
|
||||
import os
|
||||
|
||||
static_assert("path" in all_members(os))
|
||||
static_assert(has_member(os, "path"))
|
||||
|
||||
import os.path
|
||||
|
||||
static_assert("join" in all_members(os.path))
|
||||
static_assert(has_member(os.path, "join"))
|
||||
```
|
||||
|
||||
Special members available on all modules are also included:
|
||||
|
||||
```py
|
||||
static_assert("__name__" in all_members(math))
|
||||
static_assert("__doc__" in all_members(math))
|
||||
static_assert(has_member(math, "__name__"))
|
||||
static_assert(has_member(math, "__doc__"))
|
||||
```
|
||||
|
||||
### `__all__` is not respected for direct module access
|
||||
|
@ -331,12 +333,12 @@ static_assert("__doc__" in all_members(math))
|
|||
`foo.py`:
|
||||
|
||||
```py
|
||||
from ty_extensions import all_members, static_assert
|
||||
from ty_extensions import has_member, static_assert
|
||||
|
||||
import bar
|
||||
|
||||
static_assert("lion" in all_members(bar))
|
||||
static_assert("tiger" in all_members(bar))
|
||||
static_assert(has_member(bar, "lion"))
|
||||
static_assert(has_member(bar, "tiger"))
|
||||
```
|
||||
|
||||
`bar.py`:
|
||||
|
@ -348,17 +350,17 @@ lion = 1
|
|||
tiger = 1
|
||||
```
|
||||
|
||||
### `__all__` is respected for glob imports
|
||||
### `__all__` is respected for `*` imports
|
||||
|
||||
`foo.py`:
|
||||
|
||||
```py
|
||||
from ty_extensions import all_members, static_assert
|
||||
from ty_extensions import has_member, static_assert
|
||||
|
||||
import bar
|
||||
|
||||
static_assert("lion" in all_members(bar))
|
||||
static_assert("tiger" not in all_members(bar))
|
||||
static_assert(has_member(bar, "lion"))
|
||||
static_assert(not has_member(bar, "tiger"))
|
||||
```
|
||||
|
||||
`bar.py`:
|
||||
|
@ -400,12 +402,12 @@ def evaluate(x: Optional[int] = None) -> int: ...
|
|||
`play.py`:
|
||||
|
||||
```py
|
||||
from ty_extensions import all_members, static_assert
|
||||
from ty_extensions import has_member, static_assert
|
||||
|
||||
import module
|
||||
|
||||
static_assert("evaluate" in all_members(module))
|
||||
static_assert("Optional" not in all_members(module))
|
||||
static_assert(has_member(module, "evaluate"))
|
||||
static_assert(not has_member(module, "Optional"))
|
||||
```
|
||||
|
||||
## Conditionally available members
|
||||
|
@ -421,9 +423,9 @@ python-version = "3.9"
|
|||
```
|
||||
|
||||
```py
|
||||
from ty_extensions import all_members, static_assert
|
||||
from ty_extensions import has_member, static_assert
|
||||
|
||||
static_assert("bit_count" not in all_members(42))
|
||||
static_assert(not has_member(42, "bit_count"))
|
||||
```
|
||||
|
||||
### 3.10
|
||||
|
@ -434,19 +436,19 @@ python-version = "3.10"
|
|||
```
|
||||
|
||||
```py
|
||||
from ty_extensions import all_members, static_assert
|
||||
from ty_extensions import has_member, static_assert
|
||||
|
||||
static_assert("bit_count" in all_members(42))
|
||||
static_assert(has_member(42, "bit_count"))
|
||||
```
|
||||
|
||||
## Failures cases
|
||||
## Failure cases
|
||||
|
||||
### Dynamically added members
|
||||
|
||||
Dynamically added members can not be accessed:
|
||||
Dynamically added members cannot be accessed:
|
||||
|
||||
```py
|
||||
from ty_extensions import all_members, static_assert
|
||||
from ty_extensions import has_member, static_assert
|
||||
|
||||
class C:
|
||||
static_attr = 1
|
||||
|
@ -460,8 +462,8 @@ class C:
|
|||
c = C()
|
||||
c.dynamic_attr = "a"
|
||||
|
||||
static_assert("static_attr" in all_members(c))
|
||||
static_assert("dynamic_attr" not in all_members(c))
|
||||
static_assert(has_member(c, "static_attr"))
|
||||
static_assert(not has_member(c, "dynamic_attr"))
|
||||
```
|
||||
|
||||
### Dataclasses
|
||||
|
@ -469,24 +471,24 @@ static_assert("dynamic_attr" not in all_members(c))
|
|||
So far, we do not include synthetic members of dataclasses.
|
||||
|
||||
```py
|
||||
from ty_extensions import all_members, static_assert
|
||||
from ty_extensions import has_member, static_assert
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass(order=True)
|
||||
class Person:
|
||||
name: str
|
||||
age: int
|
||||
name: str
|
||||
|
||||
static_assert("name" in all_members(Person))
|
||||
static_assert("age" in all_members(Person))
|
||||
static_assert(has_member(Person, "name"))
|
||||
static_assert(has_member(Person, "age"))
|
||||
|
||||
# These are always available, since they are also defined on `object`:
|
||||
static_assert("__init__" in all_members(Person))
|
||||
static_assert("__repr__" in all_members(Person))
|
||||
static_assert("__eq__" in all_members(Person))
|
||||
static_assert(has_member(Person, "__init__"))
|
||||
static_assert(has_member(Person, "__repr__"))
|
||||
static_assert(has_member(Person, "__eq__"))
|
||||
|
||||
# TODO: this should ideally be available:
|
||||
static_assert("__lt__" in all_members(Person)) # error: [static-assert-error]
|
||||
static_assert(has_member(Person, "__lt__")) # error: [static-assert-error]
|
||||
```
|
||||
|
||||
### Attributes not available at runtime
|
||||
|
@ -496,8 +498,8 @@ example, `__annotations__` does not exist on `int` at runtime, but it is availab
|
|||
on `object` in typeshed:
|
||||
|
||||
```py
|
||||
from ty_extensions import all_members, static_assert
|
||||
from ty_extensions import has_member, static_assert
|
||||
|
||||
# TODO: this should ideally not be available:
|
||||
static_assert("__annotations__" not in all_members(3)) # error: [static-assert-error]
|
||||
static_assert(not has_member(3, "__annotations__")) # error: [static-assert-error]
|
||||
```
|
||||
|
|
|
@ -12,33 +12,32 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/ide_support/all_members.
|
|||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from ty_extensions import all_members, static_assert
|
||||
1 | from ty_extensions import static_assert, has_member
|
||||
2 |
|
||||
3 | members_of_str = all_members("a")
|
||||
4 |
|
||||
5 | static_assert("replace" in members_of_str)
|
||||
6 | static_assert("startswith" in members_of_str)
|
||||
7 | static_assert("isupper" in members_of_str)
|
||||
8 | static_assert("__add__" in members_of_str)
|
||||
9 | static_assert("__gt__" in members_of_str)
|
||||
10 | static_assert("__doc__" in members_of_str)
|
||||
11 | static_assert("__repr__" in members_of_str)
|
||||
12 | static_assert("non_existent" not in members_of_str)
|
||||
13 | from typing_extensions import reveal_type
|
||||
14 |
|
||||
15 | reveal_type(members_of_str) # error: [revealed-type]
|
||||
3 | static_assert(has_member("a", "replace"))
|
||||
4 | static_assert(has_member("a", "startswith"))
|
||||
5 | static_assert(has_member("a", "isupper"))
|
||||
6 | static_assert(has_member("a", "__add__"))
|
||||
7 | static_assert(has_member("a", "__gt__"))
|
||||
8 | static_assert(has_member("a", "__doc__"))
|
||||
9 | static_assert(has_member("a", "__repr__"))
|
||||
10 | static_assert(not has_member("a", "non_existent"))
|
||||
11 | from typing_extensions import reveal_type
|
||||
12 | from ty_extensions import all_members
|
||||
13 |
|
||||
14 | reveal_type(all_members("a")) # error: [revealed-type]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:15:13
|
||||
--> src/mdtest_snippet.py:14:13
|
||||
|
|
||||
13 | from typing_extensions import reveal_type
|
||||
14 |
|
||||
15 | reveal_type(members_of_str) # error: [revealed-type]
|
||||
| ^^^^^^^^^^^^^^ `tuple[Literal["__add__"], Literal["__annotations__"], Literal["__class__"], Literal["__contains__"], Literal["__delattr__"], Literal["__dict__"], Literal["__dir__"], Literal["__doc__"], Literal["__eq__"], Literal["__format__"], Literal["__ge__"], Literal["__getattribute__"], Literal["__getitem__"], Literal["__getnewargs__"], Literal["__gt__"], Literal["__hash__"], Literal["__init__"], Literal["__init_subclass__"], Literal["__iter__"], Literal["__le__"], Literal["__len__"], Literal["__lt__"], Literal["__mod__"], Literal["__module__"], Literal["__mul__"], Literal["__ne__"], Literal["__new__"], Literal["__reduce__"], Literal["__reduce_ex__"], Literal["__repr__"], Literal["__reversed__"], Literal["__rmul__"], Literal["__setattr__"], Literal["__sizeof__"], Literal["__str__"], Literal["__subclasshook__"], Literal["capitalize"], Literal["casefold"], Literal["center"], Literal["count"], Literal["encode"], Literal["endswith"], Literal["expandtabs"], Literal["find"], Literal["format"], Literal["format_map"], Literal["index"], Literal["isalnum"], Literal["isalpha"], Literal["isascii"], Literal["isdecimal"], Literal["isdigit"], Literal["isidentifier"], Literal["islower"], Literal["isnumeric"], Literal["isprintable"], Literal["isspace"], Literal["istitle"], Literal["isupper"], Literal["join"], Literal["ljust"], Literal["lower"], Literal["lstrip"], Literal["maketrans"], Literal["partition"], Literal["removeprefix"], Literal["removesuffix"], Literal["replace"], Literal["rfind"], Literal["rindex"], Literal["rjust"], Literal["rpartition"], Literal["rsplit"], Literal["rstrip"], Literal["split"], Literal["splitlines"], Literal["startswith"], Literal["strip"], Literal["swapcase"], Literal["title"], Literal["translate"], Literal["upper"], Literal["zfill"]]`
|
||||
12 | from ty_extensions import all_members
|
||||
13 |
|
||||
14 | reveal_type(all_members("a")) # error: [revealed-type]
|
||||
| ^^^^^^^^^^^^^^^^ `tuple[Literal["__add__"], Literal["__annotations__"], Literal["__class__"], Literal["__contains__"], Literal["__delattr__"], Literal["__dict__"], Literal["__dir__"], Literal["__doc__"], Literal["__eq__"], Literal["__format__"], Literal["__ge__"], Literal["__getattribute__"], Literal["__getitem__"], Literal["__getnewargs__"], Literal["__gt__"], Literal["__hash__"], Literal["__init__"], Literal["__init_subclass__"], Literal["__iter__"], Literal["__le__"], Literal["__len__"], Literal["__lt__"], Literal["__mod__"], Literal["__module__"], Literal["__mul__"], Literal["__ne__"], Literal["__new__"], Literal["__reduce__"], Literal["__reduce_ex__"], Literal["__repr__"], Literal["__reversed__"], Literal["__rmul__"], Literal["__setattr__"], Literal["__sizeof__"], Literal["__str__"], Literal["__subclasshook__"], Literal["capitalize"], Literal["casefold"], Literal["center"], Literal["count"], Literal["encode"], Literal["endswith"], Literal["expandtabs"], Literal["find"], Literal["format"], Literal["format_map"], Literal["index"], Literal["isalnum"], Literal["isalpha"], Literal["isascii"], Literal["isdecimal"], Literal["isdigit"], Literal["isidentifier"], Literal["islower"], Literal["isnumeric"], Literal["isprintable"], Literal["isspace"], Literal["istitle"], Literal["isupper"], Literal["join"], Literal["ljust"], Literal["lower"], Literal["lstrip"], Literal["maketrans"], Literal["partition"], Literal["removeprefix"], Literal["removesuffix"], Literal["replace"], Literal["rfind"], Literal["rindex"], Literal["rjust"], Literal["rpartition"], Literal["rsplit"], Literal["rstrip"], Literal["split"], Literal["splitlines"], Literal["startswith"], Literal["strip"], Literal["swapcase"], Literal["title"], Literal["translate"], Literal["upper"], Literal["zfill"]]`
|
||||
|
|
||||
|
||||
```
|
||||
|
|
|
@ -78,7 +78,7 @@ use crate::types::visitor::any_over_type;
|
|||
use crate::types::{
|
||||
BoundMethodType, CallableType, ClassLiteral, ClassType, DeprecatedInstance, DynamicType,
|
||||
KnownClass, Truthiness, Type, TypeMapping, TypeRelation, TypeTransformer, TypeVarInstance,
|
||||
UnionBuilder, walk_type_mapping,
|
||||
UnionBuilder, all_members, walk_type_mapping,
|
||||
};
|
||||
use crate::{Db, FxOrderSet, ModuleName, resolve_module};
|
||||
|
||||
|
@ -1082,6 +1082,8 @@ pub enum KnownFunction {
|
|||
EnumMembers,
|
||||
/// `ty_extensions.all_members`
|
||||
AllMembers,
|
||||
/// `ty_extensions.has_member`
|
||||
HasMember,
|
||||
/// `ty_extensions.top_materialization`
|
||||
TopMaterialization,
|
||||
/// `ty_extensions.bottom_materialization`
|
||||
|
@ -1150,6 +1152,7 @@ impl KnownFunction {
|
|||
| Self::DunderAllNames
|
||||
| Self::EnumMembers
|
||||
| Self::StaticAssert
|
||||
| Self::HasMember
|
||||
| Self::AllMembers => module.is_ty_extensions(),
|
||||
Self::ImportModule => module.is_importlib(),
|
||||
}
|
||||
|
@ -1186,6 +1189,16 @@ impl KnownFunction {
|
|||
}
|
||||
}
|
||||
|
||||
KnownFunction::HasMember => {
|
||||
let [Some(ty), Some(Type::StringLiteral(member))] = parameter_types else {
|
||||
return;
|
||||
};
|
||||
let ty_members = all_members(db, *ty);
|
||||
overload.set_return_type(Type::BooleanLiteral(
|
||||
ty_members.iter().any(|m| m.name == member.value(db)),
|
||||
));
|
||||
}
|
||||
|
||||
KnownFunction::AssertType => {
|
||||
let [Some(actual_ty), Some(asserted_ty)] = parameter_types else {
|
||||
return;
|
||||
|
@ -1444,6 +1457,7 @@ pub(crate) mod tests {
|
|||
| KnownFunction::IsEquivalentTo
|
||||
| KnownFunction::TopMaterialization
|
||||
| KnownFunction::BottomMaterialization
|
||||
| KnownFunction::HasMember
|
||||
| KnownFunction::AllMembers => KnownModule::TyExtensions,
|
||||
|
||||
KnownFunction::ImportModule => KnownModule::ImportLib,
|
||||
|
|
|
@ -7691,38 +7691,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
right: Type<'db>,
|
||||
range: TextRange,
|
||||
) -> Result<Type<'db>, CompareUnsupportedError<'db>> {
|
||||
let is_str_literal_in_tuple = |literal: Type<'db>, tuple: TupleType<'db>| {
|
||||
// Protect against doing a lot of work for pathologically large
|
||||
// tuples.
|
||||
//
|
||||
// Ref: https://github.com/astral-sh/ruff/pull/18251#discussion_r2115909311
|
||||
let (minimum_length, _) = tuple.tuple(self.db()).len().size_hint();
|
||||
if minimum_length > 1 << 12 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut definitely_true = false;
|
||||
let mut definitely_false = true;
|
||||
for element in tuple.tuple(self.db()).all_elements().copied() {
|
||||
if element.is_string_literal() {
|
||||
if literal == element {
|
||||
definitely_true = true;
|
||||
definitely_false = false;
|
||||
}
|
||||
} else if !literal.is_disjoint_from(self.db(), element) {
|
||||
definitely_false = false;
|
||||
}
|
||||
}
|
||||
|
||||
if definitely_true {
|
||||
Some(true)
|
||||
} else if definitely_false {
|
||||
Some(false)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
// Note: identity (is, is not) for equal builtin types is unreliable and not part of the
|
||||
// language spec.
|
||||
// - `[ast::CompOp::Is]`: return `false` if unequal, `bool` if equal
|
||||
|
@ -7854,30 +7822,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
}
|
||||
}
|
||||
}
|
||||
(Type::StringLiteral(_), Type::Tuple(tuple)) if op == ast::CmpOp::In => {
|
||||
if let Some(answer) = is_str_literal_in_tuple(left, tuple) {
|
||||
return Ok(Type::BooleanLiteral(answer));
|
||||
}
|
||||
|
||||
self.infer_binary_type_comparison(
|
||||
KnownClass::Str.to_instance(self.db()),
|
||||
op,
|
||||
right,
|
||||
range,
|
||||
)
|
||||
}
|
||||
(Type::StringLiteral(_), Type::Tuple(tuple)) if op == ast::CmpOp::NotIn => {
|
||||
if let Some(answer) = is_str_literal_in_tuple(left, tuple) {
|
||||
return Ok(Type::BooleanLiteral(!answer));
|
||||
}
|
||||
|
||||
self.infer_binary_type_comparison(
|
||||
KnownClass::Str.to_instance(self.db()),
|
||||
op,
|
||||
right,
|
||||
range,
|
||||
)
|
||||
}
|
||||
(Type::StringLiteral(_), _) => self.infer_binary_type_comparison(
|
||||
KnownClass::Str.to_instance(self.db()),
|
||||
op,
|
||||
|
|
|
@ -61,3 +61,6 @@ def bottom_materialization(type: Any) -> Any: ...
|
|||
# * `dir` will respect an object's `__dir__` implementation, if present, but
|
||||
# this method (currently) does not.
|
||||
def all_members(obj: Any) -> tuple[str, ...]: ...
|
||||
|
||||
# Returns `True` if the given object has a member with the given name.
|
||||
def has_member(obj: Any, name: str) -> bool: ...
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue