-
Notifications
You must be signed in to change notification settings - Fork 1.6k
[ty] No union with Unknown for module-global symbols
#20643
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Contributor
Diagnostic diff on typing conformance testsChanges were detected when running ty on typing conformance tests--- old-output.txt 2025-10-01 08:03:41.657168848 +0000
+++ new-output.txt 2025-10-01 08:03:44.982161744 +0000
@@ -373,7 +373,7 @@
generics_defaults_specialization.py:30:1: error[non-subscriptable] Cannot subscript object of type `<class 'SomethingWithNoDefaults[int, typing.TypeVar]'>` with no `__class_getitem__` method
generics_defaults_specialization.py:45:1: error[type-assertion-failure] Argument does not have asserted type `@Todo(unsupported nested subscript in type[X])`
generics_paramspec_basic.py:27:38: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `int`
-generics_paramspec_components.py:49:20: error[invalid-argument-type] Argument expression after ** must be a mapping type: Found `tuple[Unknown, ...]`
+generics_paramspec_components.py:49:20: error[invalid-argument-type] Argument expression after ** must be a mapping type: Found `tuple[@Todo(Support for `typing.ParamSpec`), ...]`
generics_paramspec_components.py:83:18: error[parameter-already-assigned] Multiple values provided for parameter 1 (`x`) of function `foo`
generics_paramspec_semantics.py:13:56: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `(...) -> str`
generics_paramspec_semantics.py:17:40: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `int` |
Contributor
|
Contributor
|
| Lint rule | Added | Removed | Changed |
|---|---|---|---|
unsupported-operator |
15 | 12 | 2,605 |
possibly-missing-attribute |
3 | 1,343 | 148 |
unresolved-attribute |
1,453 | 11 | 24 |
no-matching-overload |
849 | 4 | 0 |
invalid-argument-type |
169 | 55 | 483 |
type-assertion-failure |
12 | 666 | 9 |
unsupported-base |
0 | 0 | 191 |
invalid-assignment |
35 | 63 | 73 |
unused-ignore-comment |
17 | 84 | 0 |
invalid-return-type |
30 | 5 | 31 |
invalid-context-manager |
0 | 0 | 65 |
conflicting-argument-forms |
0 | 36 | 0 |
non-subscriptable |
0 | 0 | 20 |
possibly-unresolved-reference |
0 | 17 | 0 |
invalid-type-form |
7 | 0 | 8 |
invalid-exception-caught |
0 | 0 | 11 |
deprecated |
7 | 0 | 0 |
possibly-missing-implicit-call |
0 | 1 | 4 |
call-non-callable |
0 | 4 | 0 |
not-iterable |
1 | 1 | 2 |
invalid-parameter-default |
0 | 0 | 3 |
redundant-cast |
3 | 0 | 0 |
unresolved-reference |
0 | 3 | 0 |
index-out-of-bounds |
0 | 0 | 2 |
invalid-key |
2 | 0 | 0 |
invalid-super-argument |
0 | 0 | 2 |
invalid-syntax-in-forward-annotation |
0 | 2 | 0 |
invalid-attribute-access |
1 | 0 | 0 |
invalid-raise |
1 | 0 | 0 |
missing-argument |
1 | 0 | 0 |
too-many-positional-arguments |
1 | 0 | 0 |
unknown-argument |
1 | 0 | 0 |
unresolved-import |
0 | 1 | 0 |
unsupported-bool-conversion |
0 | 1 | 0 |
| Total | 2,608 | 2,309 | 3,681 |
572a18f to
00c67c9
Compare
00c67c9 to
9452f2c
Compare
Contributor
Author
|
Let's do this without literal promotion first, and then potentially add literal promotion in a second step to evaluate it separately. |
sharkdp
added a commit
that referenced
this pull request
Oct 1, 2025
## Summary Quoting from the newly added comment: Module-level globals can be mutated externally. A `MY_CONSTANT = 1` global might be changed to `"some string"` from code outside of the module that we're looking at, and so from a gradual-guarantee perspective, it makes sense to infer a type of `Literal[1] | Unknown` for global symbols. This allows the code that does the mutation to type check correctly, and for code that uses the global, it accurately reflects the lack of knowledge about the type. External modifications (or modifications through `global` statements) that would require a wider type are relatively rare. From a practical perspective, we can therefore achieve a better user experience by trusting the inferred type. Users who need the external mutation to work can always annotate the global with the wider type. And everyone else benefits from more precise type inference. I initially implemented this by applying literal promotion to the type of the unannotated module globals (as suggested in astral-sh/ty#1069), but the ecosystem impact showed a lot of problems (#20643). I fixed/patched some of these problems, but this PR seems like a good first step, and it seems sensible to apply the literal promotion change in a second step that can be evaluated separately. closes astral-sh/ty#1069 ## Ecosystem impact This seems like an (unexpectedly large) net positive with 650 fewer diagnostics overall.. even though this change will certainly catch more true positives. * There are 666 removed `type-assertion-failure` diagnostics, where we were previously used the correct type already, but removing the `Unknown` now leads to an "exact" match. * 1464 of the 1805 total new diagnostics are `unresolved-attribute` errors, most (1365) of which were previously `possibly-missing-attribute` errors. So they could also be counted as "changed" diagnostics. * For code that uses constants like ```py IS_PYTHON_AT_LEAST_3_10 = sys.version_info >= (3, 10) ``` where we would have previously inferred a type of `Literal[True/False] | Unknown`, removing the `Unknown` now allows us to do reachability analysis on branches that use these constants, and so we get a lot of favorable ecosystem changes because of that. * There is code like the following, where we previously emitted `conflicting-argument-forms` diagnostics on calls to the aliased `assert_type`, because its type was `Unknown | def …` (and the call to `Unknown` "used" the type form argument in a non type-form way): ```py if sys.version_info >= (3, 11): import typing assert_type = typing.assert_type else: import typing_extensions assert_type = typing_extensions.assert_type ``` * ~100 new `invalid-argument-type` false positives, due to missing `**kwargs` support (astral-sh/ty#247) ## Typing conformance ```diff +protocols_modules.py:25:1: error[invalid-assignment] Object of type `<module '_protocols_modules1'>` is not assignable to `Options1` ``` This diagnostic should apparently not be there, but it looks like we also fail other tests in that file, so it seems to be a limitation that was previously hidden by `Unknown` somehow. ## Test Plan Updated tests and relatively thorough ecosystem analysis.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
not ready for review
Summary
astral-sh/ty#1069
Test Plan