[ty] Rework module resolution to be breadth-first instead of depth-first#22449
[ty] Rework module resolution to be breadth-first instead of depth-first#22449BurntSushi merged 7 commits intomainfrom
Conversation
Typing conformance resultsNo changes detected ✅ |
|
|
| Lint rule | Added | Removed | Changed |
|---|---|---|---|
invalid-await |
0 | 40 | 0 |
unresolved-attribute |
0 | 13 | 0 |
unresolved-import |
3 | 0 | 1 |
invalid-assignment |
0 | 1 | 0 |
invalid-return-type |
0 | 1 | 0 |
| Total | 3 | 55 | 1 |
Hey what the fuck? What on earth is happening here that we ever actually handled this properly. |
|
Hahaha |
|
I've dropped the reordering of |
|
I've tentatively introduced a rule that
Previously this worked because when resolving |
|
I need to confirm if this reflects runtime behaviour. Also this is a bit sad because it now means finding (unshadowable builtin modules also defacto short-circuit by filtering out all non-stdlib searchpaths) |
a08f5c1 to
af9615f
Compare
af9615f to
9a2b0f5
Compare
Memory usage reportSummary
Significant changesClick to expand detailed breakdownflake8
sphinx
trio
prefect
|
| from foo.bar.both import Both | ||
| from foo.bar.impl import Impl | ||
| from foo.bar.fake import Fake # error: "Cannot resolve" | ||
| from foo.bar.impl import Impl # error: [unresolved-import] |
There was a problem hiding this comment.
@Gankra Was this change intended? I think this test is trying to assert that foo.bar.impl is found becaue there is a partial py.typed in foo.bar overriding the full py.typed at the top-level of foo.
From what I can tell testing this out, this isn't how Python imports actually work unfortunately. It looks like as soon as you can find a module, it stops. Such that there isn't a search path independent preference for Here is my test setup: Now test that you get the print statement you expect when you only set one of the directories as a search path: And now set both directories: I believe that if |
|
Also, if I do So far so good. When we combine the search paths: So basically as soon as |
Testing this out this does indeed seem correct! Here's my test setup: Then I can verify I get the expected behavior when I use each of the search path directories on their own: So far so good. And now when I combine them in any order: If search path order allowed for imports on namespace packages to have higher precedence than regular packages, then we'd expect the second command to print |
| .with_python_version(PythonVersion::PY38) | ||
| .build(); | ||
|
|
||
| let existing_modules = create_module_names(&["asyncio", "functools", "xml.etree"]); |
There was a problem hiding this comment.
Hmmm, I think this PR can no longer resolve xml.etree since we call it a namespace package and put it in stdlib. And module resolution (as it kind of did before) specifically declines to support namespace packages in the stdlib since we know there aren't any in there. So that in turn I think makes this change (along with the others I think) probably fine?
9a2b0f5 to
5f2b2d5
Compare
|
All righty, I've updated this PR and I think it's ready for review. In particular, I believe this will fix astral-sh/ty#1749 (as @Gankra mentioned in the OP, but this PR now has a regression test for it) but it should also now fix astral-sh/ty#1967 (along with a regression test). I also unwound a couple of things in this PR that I think we don't want. See the commits following the first. @Gankra Would definitely appreciate a quick review on the commits I added here. I believe I left your initial commit alone, so you should be able to see the crimes I committed pretty easily by just looking at the subsequent commits. @AlexWaygood Would also appreciate review. My plan is to port the ideas in this PR (in a follow-up change) over to the |
5f2b2d5 to
f174673
Compare
In #22449, I added a check to our "did open" handler to effectively ignore notifications for text documents that we were sure weren't Python. This was meant to fix a case where we could return diagnostics for non-Python files, which was undesirable. However, it seems like that might have been too big of a hammer. It seems like we might still want to track non-Python text files in our index but not our project. Otherwise subsequent requests regarding that non-Python file result in log messages saying that ty doesn't know about the file. i.e., a state synchronization issue. Addresses #23121 (comment)
In #22449, I added a check to our "did open" handler to effectively ignore notifications for text documents that we were sure weren't Python. This was meant to fix a case where we could return diagnostics for non-Python files, which was undesirable. However, it seems like that might have been too big of a hammer. It seems like we might still want to track non-Python text files in our index but not our project. Otherwise subsequent requests regarding that non-Python file result in log messages saying that ty doesn't know about the file. i.e., a state synchronization issue. Addresses #23121 (comment)
In #22449, I added a check to our "did open" handler to effectively ignore notifications for text documents that we were sure weren't Python. This was meant to fix a case where we could return diagnostics for non-Python files, which was undesirable. However, it seems like that might have been too big of a hammer. It seems like we might still want to track non-Python text files in our index but not our project. Otherwise subsequent requests regarding that non-Python file result in log messages saying that ty doesn't know about the file. i.e., a state synchronization issue. Addresses #23121 (comment)
AlexWaygood
left a comment
There was a problem hiding this comment.
The tests here LGTM! One pathological edge case you could also add would be something like this:
```toml
extra-paths = ["/path-one", "/path-two"]
```
`/path-one/shapes/foo.py`:
```py
X = 42
```
`/path-two/shapes/bar.pyi`:
```pyi
```
`/path-two/shapes/py.typed`:
```
partial = true
```
`main.py`:
```py
from shapes.foo import X
reveal_type(X) # revealed: Literal[42]
```Because shapes-stubs is a stubs package, it must take priority over shapes in the first search path even though shapes-stubs appears in the second search path. But because shapes-stubs is a partial = true namespace package, when we fail to find the foo submodule in shapes-stubs, we must fallback to shapes/foo.py when resolving the module.
| According to [import resolution ordering], a `foo-stubs` stub package should have priority over a | ||
| `foo` package regardless of search path ordering. |
There was a problem hiding this comment.
this makes sense to me, but FWIW I think the spec is a bit vague here. This principle can be inferred from the language in the spec, but I don't think the "regardless of search path ordering" point is specifically addressed anywhere.
There was a problem hiding this comment.
Ah I had interpreted (4) as being about "regardless of search path ordering":
Stub packages - these packages SHOULD supersede any installed inline package. They can be found in directories named foopkg-stubs for package foopkg.
But fair that this is an interpretation. I'll loosen the wording a bit here.
f174673 to
2d72a11
Compare
…ull py.typed I *think* changing this test was wrong, because it seems intentional that we implement a basic inheritance scheme?
This test fails on current `main`. Ref astral-sh/ty#1749
…rch path ordering This includes a regression test that fails on current `main`. This also simplifies the "candidate retain" logic a bit. While I've been trying not to also make changes to the "list all modules" implementation (saving that for later), it was hard to avoid here because of our consistency check ensuring that listing modules and resolving a module always return the same thing. I tried to make the simplest change I could there. Fixes astral-sh/ty#1967
It exists at the intersection of namespace packages, partially typed packages and stubs.
2d72a11 to
56c38ab
Compare
* main: [ty] Split up `types/class.rs` (#23714) [ty] Rework module resolution to be breadth-first instead of depth-first (#22449) [ty] Move tests and type-alias-related code out of `types.rs` (#23711) [ty] Move TypeVar-related code to a `types::typevar` submodule (#23710) Fail CI on new linter ecosystem panics (#23597) [ty] Fix handling of non-Python text documents [ty] Move `CallableType`, and related methods/types, to a new `types::callable` submodule (#23707) [ty] Avoid stack overflow with recursive typevar (#23652) [ty] Add a diagnostic for an unused awaitable (#23650) [ty] Fix union `*args` binding for optional positional parameters (#23124) [`refurb`] Fix `FURB101` and `FURB103` false positives when I/O variable is used later (#23542) [ty] Fix type checking for multi-member enums within in a function block (#23683) [ty] Improve folding for decorators (#23543) [`airflow`] Extract common utilities for use in new rules (#23630)
Summary
By making the algorithm breadth-first/incremental, the logic has a coherent translation to computing all_modules. Thus this is ground-work for auto-complete and auto-import adding various missing import semantics (extremely optimistically, we could actually make it share the same code!).
In addition, this fixes two corner-case issues with our module resolution:
foo.pysimilarly to a legacy namespace package whereimport foowould find it butimport foo.barwould ignore it (and find any regular-package or namespace-packagefooon subsequent search-paths).We now consider all stub-packages to have higher priority than non-stub-packages, independent of search-path ordering. In most cases this means our behaviour will now be "search all paths for stubs, then search all paths for implementations" -- this isn't strictly true in this current implementation because two namespace packages could exist where one specifies a.pyi and the other specifies a.py and we still respect search-path order in that case (I think the impl is actually really close to fixing that but I finally got the tests passing and I didn't want to poke the bear).Test Plan
I need to write more tests to cover interesting cases I've thought of, for now I'm content with the existing tests passing (a few have been changed for now, I think all those changes are acceptable, but ymmv).