diff --git a/crates/ty_python_semantic/resources/mdtest/binary/in.md b/crates/ty_python_semantic/resources/mdtest/binary/in.md deleted file mode 100644 index b85fef5f1d..0000000000 --- a/crates/ty_python_semantic/resources/mdtest/binary/in.md +++ /dev/null @@ -1,49 +0,0 @@ -# Static binary operations using `in` - -## Basic functionality - -This demonstrates type inference support for ` in `: - -```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 -``` diff --git a/crates/ty_python_semantic/resources/mdtest/ide_support/all_members.md b/crates/ty_python_semantic/resources/mdtest/ide_support/all_members.md index 79629c971c..5b628c7afa 100644 --- a/crates/ty_python_semantic/resources/mdtest/ide_support/all_members.md +++ b/crates/ty_python_semantic/resources/mdtest/ide_support/all_members.md @@ -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 -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] ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/all_members.md_-_List_all_members_-_Basic_functionality_(6b9531a70334bfad).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/all_members.md_-_List_all_members_-_Basic_functionality_(6b9531a70334bfad).snap index 55a377e0c3..fb31b51fe8 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/all_members.md_-_List_all_members_-_Basic_functionality_(6b9531a70334bfad).snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/all_members.md_-_List_all_members_-_Basic_functionality_(6b9531a70334bfad).snap @@ -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"]]` | ``` diff --git a/crates/ty_python_semantic/src/types/function.rs b/crates/ty_python_semantic/src/types/function.rs index 04e75976fc..2b284830c9 100644 --- a/crates/ty_python_semantic/src/types/function.rs +++ b/crates/ty_python_semantic/src/types/function.rs @@ -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, diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 5eed599ad5..c81019e722 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -7691,38 +7691,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { right: Type<'db>, range: TextRange, ) -> Result, 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, diff --git a/crates/ty_vendored/ty_extensions/ty_extensions.pyi b/crates/ty_vendored/ty_extensions/ty_extensions.pyi index f4a447d34c..e14b3b2c21 100644 --- a/crates/ty_vendored/ty_extensions/ty_extensions.pyi +++ b/crates/ty_vendored/ty_extensions/ty_extensions.pyi @@ -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: ...