@@ -148,6 +148,201 @@ reveal_type(X) # revealed: Unknown
148148reveal_type(Y) # revealed: Unknown
149149```
150150
151+ ### Global-scope symbols defined in many other ways
152+
153+ ` a.py ` :
154+
155+ ``` py
156+ import typing
157+ from collections import OrderedDict
158+ from collections import OrderedDict as Foo
159+
160+ A, B = 1 , (C := 2 )
161+ D: (E := 4 ) = (F := 5 ) # error: [invalid-type-form]
162+
163+ for G in [1 ]:
164+ ...
165+
166+ for (H := 4 ).whatever in [2 ]: # error: [unresolved-attribute]
167+ ...
168+
169+ class I : ...
170+
171+ def J (): ...
172+
173+ type K = int
174+
175+ with () as L: # error: [invalid-context-manager]
176+ ...
177+
178+ match 42 :
179+ case {" something" : M}:
180+ ...
181+ case [* N]:
182+ ...
183+ case [O]:
184+ ...
185+ case P | Q:
186+ ...
187+ case object (foo = R):
188+ ...
189+ case object (S):
190+ ...
191+ case T:
192+ ...
193+ ```
194+
195+ ` b.py ` :
196+
197+ ``` py
198+ from a import *
199+
200+ # fmt: off
201+
202+ print ((
203+ # TODO : false positive
204+ A, # error: [unresolved-reference]
205+ # TODO : false positive
206+ B, # error: [unresolved-reference]
207+ # TODO : false positive
208+ C, # error: [unresolved-reference]
209+ # TODO : false positive
210+ D, # error: [unresolved-reference]
211+ # TODO : false positive
212+ E, # error: [unresolved-reference]
213+ # TODO : false positive
214+ F, # error: [unresolved-reference]
215+ # TODO : could emit diagnostic about being possibly unbound, but this is a false positive
216+ G, # error: [unresolved-reference] "Name `G` used when not defined"
217+ # TODO : false positive
218+ H, # error: [unresolved-reference]
219+ # TODO : false positive
220+ I, # error: [unresolved-reference]
221+ # TODO : false positive
222+ J, # error: [unresolved-reference]
223+ # TODO : false positive
224+ K, # error: [unresolved-reference]
225+ # TODO : false positive
226+ L, # error: [unresolved-reference]
227+ # TODO : could emit diagnostic about being possibly unbound, but this is a false positive
228+ M, # error: [unresolved-reference] "Name `M` used when not defined"
229+ # TODO : could emit diagnostic about being possibly unbound, but this is a false positive
230+ N, # error: [unresolved-reference] "Name `N` used when not defined"
231+ # TODO : could emit diagnostic about being possibly unbound, but this is a false positive
232+ O, # error: [unresolved-reference] "Name `O` used when not defined"
233+ # TODO : could emit diagnostic about being possibly unbound, but this is a false positive
234+ P, # error: [unresolved-reference] "Name `P` used when not defined"
235+ # TODO : could emit diagnostic about being possibly unbound, but this is a false positive
236+ Q, # error: [unresolved-reference] "Name `Q` used when not defined"
237+ # TODO : could emit diagnostic about being possibly unbound, but this is a false positive
238+ R, # error: [unresolved-reference] "Name `R` used when not defined"
239+ # TODO : could emit diagnostic about being possibly unbound, but this is a false positive
240+ S, # error: [unresolved-reference] "Name `S` used when not defined"
241+ # TODO : could emit diagnostic about being possibly unbound, but this is a false positive
242+ T, # error: [unresolved-reference] "Name `T` used when not defined"
243+ # TODO : false positive
244+ typing, # error: [unresolved-reference]
245+ # TODO : false positive
246+ OrderedDict, # error: [unresolved-reference]
247+ # TODO : false positive
248+ Foo, # error: [unresolved-reference]
249+ ))
250+ ```
251+
252+ ### Definitions in function-like scopes are not global definitions
253+
254+ Except for some cases involving walrus expressions inside comprehension scopes.
255+
256+ ` a.py ` :
257+
258+ ``` py
259+ class Iterator :
260+ def __next__ (self ) -> int :
261+ return 42
262+
263+ class Iterable :
264+ def __iter__ (self ) -> Iterator:
265+ return Iterator()
266+
267+ [a for a in Iterable()]
268+ {b for b in Iterable()}
269+ {c: c for c in Iterable()}
270+ (d for d in Iterable())
271+ lambda e : (f := 42 )
272+
273+ # Definitions created by walruses in a comprehension scope are unique;
274+ # they "leak out" of the scope and are stored in the surrounding scope
275+ [(g := h * 2 ) for h in Iterable()]
276+ [i for j in Iterable() if (i := j - 10 ) > 0 ]
277+ {(k := l * 2 ): (m := l * 3 ) for l in Iterable()}
278+ ```
279+
280+ ` b.py ` :
281+
282+ ``` py
283+ from a import *
284+
285+ # error: [unresolved-reference]
286+ reveal_type(a) # revealed: Unknown
287+ # error: [unresolved-reference]
288+ reveal_type(b) # revealed: Unknown
289+ # error: [unresolved-reference]
290+ reveal_type(c) # revealed: Unknown
291+ # error: [unresolved-reference]
292+ reveal_type(d) # revealed: Unknown
293+ # error: [unresolved-reference]
294+ reveal_type(e) # revealed: Unknown
295+ # error: [unresolved-reference]
296+ reveal_type(f) # revealed: Unknown
297+ # error: [unresolved-reference]
298+ reveal_type(h) # revealed: Unknown
299+ # error: [unresolved-reference]
300+ reveal_type(j) # revealed: Unknown
301+
302+ # TODO : these should all reveal `Unknown | int` and should not have diagnostics.
303+ # (We don't generally model elsewhere in red-knot that bindings from walruses
304+ # "leak" from comprehension scopes into outer scopes, but we should.)
305+ # See https://github.com/astral-sh/ruff/issues/16954
306+ #
307+ # error: [unresolved-reference]
308+ reveal_type(g) # revealed: Unknown
309+ # error: [unresolved-reference]
310+ reveal_type(i) # revealed: Unknown
311+ # error: [unresolved-reference]
312+ reveal_type(k) # revealed: Unknown
313+ # error: [unresolved-reference]
314+ reveal_type(m) # revealed: Unknown
315+ ```
316+
317+ ### An annotation without a value is a definition in a stub but not a ` .py ` file
318+
319+ ` a.pyi ` :
320+
321+ ``` pyi
322+ X: bool
323+ ```
324+
325+ ` b.py ` :
326+
327+ ``` py
328+ Y: bool
329+ ```
330+
331+ ` c.py ` :
332+
333+ ``` py
334+ from a import *
335+ from b import *
336+
337+ # TODO : this is a false positive, should reveal `bool`
338+ # error: [unresolved-reference]
339+ reveal_type(X) # revealed: Unknown
340+
341+ # but this diagnostic is accurate!
342+ # error: [unresolved-reference]
343+ reveal_type(Y) # revealed: Unknown
344+ ```
345+
151346### Global-scope names starting with underscores
152347
153348Global-scope names starting with underscores are not imported from a ` * ` import (unless the module
@@ -263,11 +458,14 @@ if sys.version_info >= (3, 11):
263458 X: bool = True
264459else :
265460 Y: bool = False
461+ Z: int = 42
266462```
267463
268464` b.py ` :
269465
270466``` py
467+ Z: bool = True
468+
271469from a import *
272470
273471# TODO should not error, should reveal `bool`
@@ -276,6 +474,12 @@ reveal_type(X) # revealed: Unknown
276474
277475# error: [unresolved-reference]
278476reveal_type(Y) # revealed: Unknown
477+
478+ # The `*` import should not be considered a redefinition
479+ # of the global variable in this module, as the symbol in
480+ # the `a` module is in a branch that is statically known
481+ # to be dead code given the `python-version` configuration.
482+ reveal_type(Z) # revealed: Literal[True]
279483```
280484
281485### Relative ` * ` imports
@@ -662,6 +866,40 @@ reveal_type(X) # revealed: Unknown
662866reveal_type(Y) # revealed: Unknown
663867```
664868
869+ ## ` global ` statements in non-global scopes
870+
871+ A ` global ` statement in a nested function scope, combined with a definition in the same function
872+ scope of the name that was declared ` global ` , can add a symbol to the global namespace.
873+
874+ ` a.py ` :
875+
876+ ``` py
877+ def f ():
878+ global g, h
879+
880+ g: bool = True
881+
882+ f()
883+ ```
884+
885+ ` b.py ` :
886+
887+ ``` py
888+ from a import *
889+
890+ # TODO : false positive, should be `Literal[f]` with no diagnostic
891+ # error: [unresolved-reference]
892+ reveal_type(f) # revealed: Unknown
893+
894+ # TODO : false positive, should be `bool` with no diagnostic
895+ # error: [unresolved-reference]
896+ reveal_type(g) # revealed: Unknown
897+
898+ # this diagnostic is accurate, though!
899+ # error: [unresolved-reference]
900+ reveal_type(h) # revealed: Unknown
901+ ```
902+
665903## Integration test: ` collections.abc `
666904
667905The ` collections.abc ` standard-library module provides a good integration test, as all its symbols
@@ -711,5 +949,46 @@ def f():
711949 reveal_type(X) # revealed: Unknown
712950```
713951
952+ ### ` * ` combined with other aliases in the list
953+
954+ ` a.py ` :
955+
956+ ``` py
957+ X: bool = True
958+ _Y: bool = False
959+ _Z: bool = True
960+ ```
961+
962+ ` b.py ` :
963+
964+ <!-- blacken-docs:off -->
965+
966+ ``` py
967+ from a import * , _Y # error: [invalid-syntax]
968+
969+ # The import statement above is invalid syntax,
970+ # but it's pretty obvious that the user wanted to do a `*` import,
971+ # so we should import all public names from `a` anyway, to minimize cascading errors
972+ #
973+ # TODO : get rid of this error, reveal `bool`
974+ # error: [unresolved-reference]
975+ reveal_type(X) # revealed: Unknown
976+ reveal_type(_Y) # revealed: bool
977+ ```
978+
979+ These tests are more to assert that we don't panic on these various kinds of invalid syntax than
980+ anything else:
981+
982+ ` c.py ` :
983+
984+ ``` py
985+ from a import * , _Y # error: [invalid-syntax]
986+ from a import _Y, * , _Z # error: [invalid-syntax]
987+ from a import * , _Y as fooo # error: [invalid-syntax]
988+ from a import * , * , _Y # error: [invalid-syntax]
989+ ```
990+
991+ <!-- blacken-docs:on -->
992+
714993[ python language reference for import statements ] : https://docs.python.org/3/reference/simple_stmts.html#the-import-statement
715994[ typing spec ] : https://typing.python.org/en/latest/spec/distributing.html#library-interface-public-and-private-symbols
0 commit comments