@@ -148,6 +148,201 @@ reveal_type(X) # revealed: Unknown
148
148
reveal_type(Y) # revealed: Unknown
149
149
```
150
150
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
+
151
346
### Global-scope names starting with underscores
152
347
153
348
Global-scope names starting with underscores are not imported from a ` * ` import (unless the module
@@ -263,11 +458,14 @@ if sys.version_info >= (3, 11):
263
458
X: bool = True
264
459
else :
265
460
Y: bool = False
461
+ Z: int = 42
266
462
```
267
463
268
464
` b.py ` :
269
465
270
466
``` py
467
+ Z: bool = True
468
+
271
469
from a import *
272
470
273
471
# TODO should not error, should reveal `bool`
@@ -276,6 +474,12 @@ reveal_type(X) # revealed: Unknown
276
474
277
475
# error: [unresolved-reference]
278
476
reveal_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]
279
483
```
280
484
281
485
### Relative ` * ` imports
@@ -662,6 +866,40 @@ reveal_type(X) # revealed: Unknown
662
866
reveal_type(Y) # revealed: Unknown
663
867
```
664
868
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
+
665
903
## Integration test: ` collections.abc `
666
904
667
905
The ` collections.abc ` standard-library module provides a good integration test, as all its symbols
@@ -711,5 +949,46 @@ def f():
711
949
reveal_type(X) # revealed: Unknown
712
950
```
713
951
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
+
714
993
[ python language reference for import statements ] : https://docs.python.org/3/reference/simple_stmts.html#the-import-statement
715
994
[ typing spec ] : https://typing.python.org/en/latest/spec/distributing.html#library-interface-public-and-private-symbols
0 commit comments