1
1
# Wildcard (` * ` ) imports
2
2
3
+ See the [ Python language reference for import statements] .
4
+
3
5
## Basic functionality
4
6
5
7
### A simple ` * ` import
@@ -130,6 +132,130 @@ from c import X # error: [unresolved-import]
130
132
from c import Y # error: [unresolved-import]
131
133
```
132
134
135
+ ### Global-scope symbols defined using walrus expressions
136
+
137
+ ` a.py ` :
138
+
139
+ ``` py
140
+ X = (Y := 3 ) + 4
141
+ ```
142
+
143
+ ` b.py ` :
144
+
145
+ ``` py
146
+ # TODO : should not error
147
+ from a import * # error: [unresolved-import]
148
+
149
+ # TODO should not error, should reveal `Literal[7] | Unknown`
150
+ # error: [unresolved-reference]
151
+ reveal_type(X) # revealed: Unknown
152
+ # TODO should not error, should reveal `Literal[3] | Unknown`
153
+ # error: [unresolved-reference]
154
+ reveal_type(Y) # revealed: Unknown
155
+ ```
156
+
157
+ ### Global-scope names starting with underscores
158
+
159
+ Global-scope names starting with underscores are not imported from a ` * ` import (unless the module
160
+ has ` __all__ ` and they are included in ` __all__ ` ):
161
+
162
+ ` a.py ` :
163
+
164
+ ``` py
165
+ _private: bool = False
166
+ __protected: bool = False
167
+ __dunder__: bool = False
168
+ ___thunder___: bool = False
169
+
170
+ Y: bool = True
171
+ ```
172
+
173
+ ` b.py ` :
174
+
175
+ ``` py
176
+ # TODO : should not error
177
+ from a import * # error: [unresolved-import]
178
+
179
+ # These errors are correct:
180
+ #
181
+ # error: [unresolved-reference]
182
+ reveal_type(_private) # revealed: Unknown
183
+ # error: [unresolved-reference]
184
+ reveal_type(__protected) # revealed: Unknown
185
+ # error: [unresolved-reference]
186
+ reveal_type(__dunder__) # revealed: Unknown
187
+ # error: [unresolved-reference]
188
+ reveal_type(___thunder___) # revealed: Unknown
189
+
190
+ # TODO : this error is incorrect (should reveal `bool`):
191
+ #
192
+ # error: [unresolved-reference]
193
+ reveal_type(Y) # revealed: Unknown
194
+ ```
195
+
196
+ ### All public symbols are considered re-exported from ` .py ` files
197
+
198
+ For ` .py ` files, we should consider all public symbols in the global namespace exported by that
199
+ module when considering which symbols are made available by a ` * ` import. Here, ` b.py ` does not use
200
+ the explicit ` from a import X as X ` syntax to explicitly mark it as publicly re-exported, and ` X ` is
201
+ not included in ` __all__ ` ; whether it should be considered a "public name" in module ` b ` is
202
+ ambiguous. We could consider an opt-in rule to warn the user when they use ` X ` in ` c.py ` that it was
203
+ not included in ` __all__ ` and was not marked as an explicit re-export.
204
+
205
+ ` a.py ` :
206
+
207
+ ``` py
208
+ X: bool = True
209
+ ```
210
+
211
+ ` b.py ` :
212
+
213
+ ``` py
214
+ from a import X
215
+ ```
216
+
217
+ ` c.py ` :
218
+
219
+ ``` py
220
+ # TODO : should not error
221
+ from b import * # error: [unresolved-import]
222
+
223
+ # TODO : this is a false positive, but we could consider a different opt-in diagnostic
224
+ # (see prose commentary above)
225
+ #
226
+ # error: [unresolved-reference]
227
+ reveal_type(X) # revealed: Unknown
228
+ ```
229
+
230
+ ### Only explicit re-exports are considered re-exported from ` .pyi ` files
231
+
232
+ For ` .pyi ` files, we should consider all imports private to the stub unless they are included in
233
+ ` __all__ ` or use the explict ` from foo import X as X ` syntax.
234
+
235
+ ` a.pyi ` :
236
+
237
+ ``` pyi
238
+ X: bool = True
239
+ ```
240
+
241
+ ` b.pyi ` :
242
+
243
+ ``` pyi
244
+ from a import X
245
+ ```
246
+
247
+ ` c.py ` :
248
+
249
+ ``` py
250
+ # TODO : should not error
251
+ from b import * # error: [unresolved-import]
252
+
253
+ # This error is correct, as `X` is not considered re-exported from module `b`:
254
+ #
255
+ # error: [unresolved-reference]
256
+ reveal_type(X) # revealed: Unknown
257
+ ```
258
+
133
259
### Symbols in statically known branches
134
260
135
261
``` toml
@@ -190,17 +316,22 @@ reveal_type(X) # revealed: Unknown
190
316
191
317
## Star imports with ` __all__ `
192
318
193
- If a module ` x ` contains ` __all__ ` , only symbols included in ` x.__all__ ` are imported by
194
- ` from x import * ` .
319
+ If a module ` x ` contains ` __all__ ` , all symbols included in ` x.__all__ ` are imported by
320
+ ` from x import * ` (but no other symbols are) .
195
321
196
322
### Simple tuple ` __all__ `
197
323
198
324
` a.py ` :
199
325
200
326
``` py
201
- __all__ = (" X" ,)
327
+ __all__ = (" X" , " _private " , " __protected " , " __dunder__ " , " ___thunder___ " )
202
328
203
329
X: bool = True
330
+ _private: bool = True
331
+ __protected: bool = True
332
+ __dunder__: bool = True
333
+ ___thunder___: bool = True
334
+
204
335
Y: bool = False
205
336
```
206
337
@@ -210,10 +341,20 @@ Y: bool = False
210
341
# TODO should not error
211
342
from a import * # error: [unresolved-import]
212
343
213
- # TODO should not error, should reveal `bool`
344
+ # TODO none of these should error, should all reveal `bool`
214
345
# error: [unresolved-reference]
215
346
reveal_type(X) # revealed: Unknown
347
+ # error: [unresolved-reference]
348
+ reveal_type(_private) # revealed: Unknown
349
+ # error: [unresolved-reference]
350
+ reveal_type(__protected) # revealed: Unknown
351
+ # error: [unresolved-reference]
352
+ reveal_type(__dunder__) # revealed: Unknown
353
+ # error: [unresolved-reference]
354
+ reveal_type(___thunder___) # revealed: Unknown
216
355
356
+ # but this diagnostic is accurate!
357
+ #
217
358
# error: [unresolved-reference]
218
359
reveal_type(Y) # revealed: Unknown
219
360
```
@@ -361,16 +502,22 @@ from a import * # fails with `AttributeError: module 'foo' has no attribute 'b'
361
502
362
503
### Dynamic ` __all__ `
363
504
364
- We'll need to decide what to do if ` __all__ ` contains members that are dynamically computed. Mypy
365
- simply ignores any members that are not statically known when determining which symbols are
366
- available (which can lead to false positives).
505
+ If ` __all__ ` contains members that are dynamically computed, we should check that all members of
506
+ ` __all__ ` are assignable to ` str ` . For the purposes of evaluating ` * ` imports, however, we should
507
+ treat the module as though it has no ` __all__ ` at all: all global-scope members of the module should
508
+ be considered imported by the import statement. We should probably also emit a warning telling the
509
+ user that we cannot statically determine the elements of ` __all__ ` .
367
510
368
511
` a.py ` :
369
512
370
513
``` py
371
514
def f () -> str :
372
515
return " f"
373
516
517
+ def g () -> int :
518
+ return 42
519
+
520
+ # TODO we should emit a warning here for the dynamically constructed `__all__` member.
374
521
__all__ = [f()]
375
522
```
376
523
@@ -380,14 +527,57 @@ __all__ = [f()]
380
527
# TODO : should not error
381
528
from a import * # error: [unresolved-import]
382
529
383
- # Strictly speaking this is a false positive, since there *is* an `f` symbol imported
384
- # by the `*` import at runtime.
530
+ # TODO : we should avoid both errors here.
531
+ #
532
+ # At runtime, `f` is imported but `g` is not; to avoid false positives, however,
533
+ # we should treat `a` as though it does not have `__all__` at all,
534
+ # which would imply that both symbols would be present.
385
535
#
386
536
# error: [unresolved-reference]
387
537
reveal_type(f) # revealed: Unknown
538
+ # error: [unresolved-reference]
539
+ reveal_type(g) # revealed: Unknown
388
540
```
389
541
390
- ### ` __all__ ` combined with statically known branches
542
+ ### ` __all__ ` conditionally defined in a statically known branch
543
+
544
+ ``` toml
545
+ [environment ]
546
+ python-version = " 3.11"
547
+ ```
548
+
549
+ ` a.py ` :
550
+
551
+ ``` py
552
+ import sys
553
+
554
+ X: bool = True
555
+
556
+ if sys.version_info >= (3 , 11 ):
557
+ __all__ = [" X" , " Y" ]
558
+ Y: bool = True
559
+ else :
560
+ __all__ = (" Z" ,)
561
+ Z: bool = True
562
+ ```
563
+
564
+ ` b.py ` :
565
+
566
+ ``` py
567
+ # TODO should not error
568
+ from a import * # error: [unresolved-import]
569
+
570
+ # TODO neither should error, both should be `bool`
571
+ # error: [unresolved-reference]
572
+ reveal_type(X) # revealed: Unknown
573
+ # error: [unresolved-reference]
574
+ reveal_type(Y) # revealed: Unknown
575
+
576
+ # error: [unresolved-reference]
577
+ reveal_type(Z) # revealed: Unknown
578
+ ```
579
+
580
+ ### ` __all__ ` conditionally mutated in a statically known branch
391
581
392
582
``` toml
393
583
[environment ]
@@ -426,6 +616,75 @@ reveal_type(Y) # revealed: Unknown
426
616
reveal_type(Z) # revealed: Unknown
427
617
```
428
618
619
+ ### Empty ` __all__ `
620
+
621
+ An empty ` __all__ ` is valid, but a ` * ` import from a module with an empty ` __all__ ` results in 0
622
+ bindings being added from the import:
623
+
624
+ ` a.py ` :
625
+
626
+ ``` py
627
+ X: bool = True
628
+
629
+ __all__ = ()
630
+ ```
631
+
632
+ ` b.py ` :
633
+
634
+ ``` py
635
+ Y: bool = True
636
+
637
+ __all__ = []
638
+ ```
639
+
640
+ ` c.py ` :
641
+
642
+ ``` py
643
+ # TODO : should not error for either import statement:
644
+ from a import * # error: [unresolved-import]
645
+ from b import * # error: [unresolved-import]
646
+
647
+ # error: [unresolved-reference]
648
+ reveal_type(X) # revealed: Unknown
649
+ # error: [unresolved-reference]
650
+ reveal_type(Y) # revealed: Unknown
651
+ ```
652
+
653
+ ### ` __all__ ` in a stub file
654
+
655
+ If a name is included in ` __all__ ` in a stub file, it is considered re-exported even if it was only
656
+ defined using an import without the explicit ` from foo import X as X ` syntax:
657
+
658
+ ` a.py ` :
659
+
660
+ ``` py
661
+ X: bool = True
662
+ Y: bool = True
663
+ ```
664
+
665
+ ` b.py ` :
666
+
667
+ ``` py
668
+ from a import X, Y
669
+
670
+ __all__ = [" X" ]
671
+ ```
672
+
673
+ ` c.py ` :
674
+
675
+ ``` py
676
+ # TODO : should not error
677
+ from b import * # error: [unresolved-import]
678
+
679
+ # TODO : should not error, should reveal `bool`
680
+ # error: [unresolved-reference]
681
+ reveal_type(X) # revealed: Unknown
682
+
683
+ # this error is correct:
684
+ # error: [unresolved-reference]
685
+ reveal_type(Y) # revealed: Unknown
686
+ ```
687
+
429
688
## Integration test: ` collections.abc `
430
689
431
690
The ` collections.abc ` standard-library module provides a good integration test, as all its symbols
@@ -452,4 +711,28 @@ If the module is unresolved, we emit a diagnostic just like for any other unreso
452
711
from foo import * # error: [unresolved-import]
453
712
```
454
713
714
+ ### Nested scope
715
+
716
+ A ` * ` import in a nested scope are always a syntax error. Red-knot does not infer any bindings from
717
+ them:
718
+
719
+ ` a.py ` :
720
+
721
+ ``` py
722
+ X: bool = True
723
+ ```
724
+
725
+ ` b.py ` :
726
+
727
+ ``` py
728
+ def f ():
729
+ # TODO : it's correct for us to raise an error here, but the error code and error message are incorrect.
730
+ # It should be a syntax errror (tracked by https://github.com/astral-sh/ruff/issues/11934)
731
+ from a import * # error: [unresolved-import] "Module `a` has no member `*`"
732
+
733
+ # error: [unresolved-reference]
734
+ reveal_type(X) # revealed: Unknown
735
+ ```
736
+
737
+ [ python language reference for import statements ] : https://docs.python.org/3/reference/simple_stmts.html#the-import-statement
455
738
[ typing spec ] : https://typing.python.org/en/latest/spec/distributing.html#library-interface-public-and-private-symbols
0 commit comments