[ty] List available members for a given type#18251
Conversation
|
b647bb3 to
eb7ffb1
Compare
|
|
||
| ### Unions | ||
|
|
||
| For unions, `all_members` will only return members that are available on all elements of the union. |
There was a problem hiding this comment.
This might be an interesting correctness-vs-UX tradeoff. When given a obj: str | None object, and tab-completing on obj.<tab>, it's technically correct to only show attributes that are available on str and None (so basically nothing, except for a few special methods on object that nobody wants to call directly).
But the more likely scenario is probably that the developer actually wants to access an attribute on str, and just forgot to eliminate the None possibility. So it might be more useful to show attributes that are available on str or None here instead? Not sure.
There was a problem hiding this comment.
I feel like this is a semantic/definition problem. all_members() is defined as:
The
ty_extensions.all_membersfunction allows access to a list of accessible members/attributes on a given object.
...and that is exactly what it should do, no special cases allowed.
Cases like str | None demand a different function that would list members that are available on at least one union member.
There was a problem hiding this comment.
Well I would change that docstring obviously, if we would decide to deliberate return members that are not available 😄 This function is only here to support that precise use case (providing completion results for the LPS). Making it available via ty_extensions.all_members was just a convenient way for me to test it. I'm not sure if this should really be exposed. Or if we should maybe move it to ty_extensions.internal or similar.
There was a problem hiding this comment.
I'm inclined to stick to what you have here for now (preferring correctness), and then loosen it up as use cases arise. Maybe, e.g., it makes sense to only loosen it in certain scenarios.
There was a problem hiding this comment.
I suspect that this will require some iteration and/or even configurations. E.g. r-a has an option that allows you to configure if r-a should hide members to which you don't have access to due to visibility constraint. Hiding them is probably the right default when working with third-party code, but for first-party code, I often want to see all members, and I'll then change the visibility of the member.
So I think we should move forward here as we can change this easily in the future
There was a problem hiding this comment.
Yeah I think this should be easy to change.
I can imagine wanting configuration for respecting __all__ too.
422e245 to
999c222
Compare
a5fda09 to
7f004ba
Compare
I think this seems like the main blocker here for getting this merged. I'd be in favor of moving it to |
d70d8e1 to
e8ef9e5
Compare
From what I can tell, it might be undesirable to respect However, we do want to respect it when there's a glob-star import, but ty already does that (and I've added tests for it). A relayed question here is whether we should respect
This I am not sure about. How do we go about deciding these sorts of questions? Otherwise, I think a review at this point would be helpful for me, so I'm going to take this out of draft. |
e8ef9e5 to
6d405d6
Compare
dhruvmanila
left a comment
There was a problem hiding this comment.
Overall, this looks good.
I've left a few comments stating the relevance of __all__ and the re-export conventions for imported symbols but that all can be done in a follow-up.
| # Returns a tuple of all members of the given object, similar to `dir(obj)` and | ||
| # `inspect.getmembers(obj)`, with at least the following differences: | ||
| # | ||
| # * `dir` and `inspect.getmembers` may use runtime mutable state to construct | ||
| # the list of attributes returned. In contrast, this routine is limited to | ||
| # static information only. | ||
| # * `dir` will respect an object's `__dir__` implementation, if present, but | ||
| # this method (currently) does not. | ||
| def all_members(obj: Any) -> tuple[str, ...]: ... |
There was a problem hiding this comment.
I'm sure you must've thought about this but in the future it would be useful to provide more information to the client like docstring, item kind, etc. and I'm not exactly sure how this API could help with that. I'm curious to hear your thoughts if any.
There was a problem hiding this comment.
We could just return a Vec<Type<'db>> or FxHashSet<Type<'db>> here but then there's also another question of whether to actually store information like docstring on the type itself or whether to store it somewhere else.
There was a problem hiding this comment.
I 100% agree that we will want more information.
My rough plan here is that completions will use the AllMembers API directly, and I'd like to augment it to return more than just names. I don't know exactly what it should return yet. A Type seems like a good start I think? But that all_members as a ty_extension could still just return a tuple of strings.
| 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)) |
There was a problem hiding this comment.
Can we add a test case which populates the negative set in intersection? Like, if not isinstance(...)
There was a problem hiding this comment.
Done! I added this test:
```py
from ty_extensions import all_members, static_assert
class A:
on_all: int = 1
only_on_a: str = "a"
only_on_ab: str = "a"
only_on_ac: str = "a"
class B:
on_all: int = 2
only_on_b: str = "b"
only_on_ab: str = "b"
only_on_bc: str = "b"
class C:
on_all: int = 3
only_on_c: str = "c"
only_on_ac: str = "c"
only_on_bc: str = "c"
def f(intersection: object):
if isinstance(intersection, A):
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))
```
This behavior exists and is valid at runtime so I'm not sure what do you mean by "only useful for this purpose" (cc @sharkdp) |
There's (hopefully) nothing wrong with the precise True/False type inference for these binary expressions. I was merely questioning if we should keep the extra code just for the purpose of writing these mdtests. For users of ty, there's probably not much value in inferring literal True/False over bool for these cases that are unlikely to occur in real world code. I guess @AlexWaygood could also use a this feature for some protocol tests (instead of always listing all members)? And maybe the all tests could also use a similar technique |
8bd7285 to
96fb3ab
Compare
96fb3ab to
e1c21b2
Compare
|
I'm going to bring this in, but happy to address any other feedback in a follow-up PR! |
|
And thank you @sharkdp for doing the lion's share of the work here!!! |
carljm
left a comment
There was a problem hiding this comment.
Looks great! Just a couple minor nits, not worth a separate PR but for consideration if a future PR touches these areas
| static_assert("foo" not in x) | ||
| ``` | ||
|
|
||
| ## Statically unknown results in a type error |
There was a problem hiding this comment.
I'm not sure what the "type error" being referred to here is -- the only error I see in this section is static-assert-error? I would expect this header to say something more like "statically unknown returns bool"
There was a problem hiding this comment.
Yeah, I was calling static-assert-error a type error, which I guess is not quite right? Can you say more about "statically unknown returns bool"? ty is still returning an error here when x in y is both unknown and used in a static context. What about, "Statically unknown results in error in static context" instead?
There was a problem hiding this comment.
It seems like you're more commenting on the behavior of static_assert here -- "static context" isn't really a thing otherwise. static_assert is really just a testing tool for us to easily make assertions about things being statically known. It asserts that some expression evaluates to Literal[True] and emits static-assert-error otherwise. But (outside of a different mdtest specifically for that purpose) I don't think mdtests should be commenting on the behavior of static_assert; the focus should be on the type system behavior under test (which in this case is <literal-str> in <tuple>). And the relevant behavior being tested here is that we infer the in expression as bool (not as Literal[True] or Literal[False]) if we can't statically determine the result of the in test.
On second look I think what this really underscores is that static_assert is just the wrong tool for this particular test; it would be clearer here to simply use reveal_type and assert that the revealed type of the expression is bool.
There was a problem hiding this comment.
Got it! Thank you for talking me through this. I really appreciate it!
I put up a new PR with these fixes: #18388
|
|
||
| <!-- snapshot-diagnostics --> | ||
|
|
||
| The `ty_extensions.all_members` function allows access to a list of accessible members/attributes on |
There was a problem hiding this comment.
this calls it a list, but it's a tuple
* main: [ty] support callability of bound/constrained typevars (#18389) [ty] Minor tweaks to "list all members" docs and tests (#18388) [ty] Fix broken property tests for disjointness (#18384) [ty] List available members for a given type (#18251) [`airflow`] Add unsafe fix for module moved cases (`AIR312`) (#18363) Add a `SourceFile` to `OldDiagnostic` (#18356) Update salsa past generational id change (#18362) [`airflow`] Add unsafe fix for module moved cases (`AIR311`) (#18366) [`airflow`] Add unsafe fix for module moved cases (`AIR301`) (#18367) [ty] Improve tests for `site-packages` discovery (#18374) [ty] _typeshed.Self is not a special form (#18377) [ty] Callable types are disjoint from non-callable `@final` nominal instance types (#18368) [ty] Add diagnosis for function with no return statement but with return type annotation (#18359) [`airflow`] Add unsafe fix module moved cases (`AIR302`) (#18093) Rename `ruff_linter::Diagnostic` to `OldDiagnostic` (#18355) [`refurb`] Add coverage of `set` and `frozenset` calls (`FURB171`) (#18035)

Summary
Provide a new
ide_support::all_membersfunction that lists all available attributes for a given type.To do:
__all__when accessing modules?ty_extensions.all_membersand the new<str_literal> in <tuple>inference support, that is probably only useful for this purpose.Test Plan
New Markdown tests