-
-
Notifications
You must be signed in to change notification settings - Fork 3k
Description
Bug Report
Here are two modules:
A.py:
import typing
from B import *
if typing.TYPE_CHECKING:
from B import Foo, var
Foo()
Bar()
myvar = var
B.py:
from A import *
class Foo: pass
class Bar: pass
var: int = 42
Due to the circular import *, the imports of Foo and var are not analyzed until the final iteration.
If A is analyzed first, Foo and var are not yet found in B, and these are reported as missing imports.
But if B is analyzed first, the imports into A are resolved and there is no error.
I have altered the mypy code to make additional analysis iterations over B and A, after the previous iteration made no progress, and before the final iteration. In these iterations, any imports from an incomplete module will be analyzed, and this is repeated until no progress is made. After that comes the final iteration.
In the present case, A will create placeholders for the missing imports, then B will define everything. Then in the next iteration, A will find the names in B.
The bug is that the error message is still generated.
Here is an output file (enhanced to show more details):
mypy -v B.py A.py
LOG: Processing SCC of size 2 (A B) as inherently stale
LOG: Iteration 1:
LOG: Module A:
LOG: Before:
LOG: Name Foo not defined.
LOG: Name Bar not defined.
LOG: Name var not defined.
LOG: import * from B
LOG: (incomplete)
LOG: import Foo from B
LOG: (incomplete)
LOG: import var from B
LOG: (incomplete)
LOG: After:
LOG: Name Foo not defined.
LOG: Name Bar not defined.
LOG: Name var not defined.
LOG: progress.
LOG: deferred.
LOG: Module B:
LOG: Before:
LOG: Name Foo not defined.
LOG: Name Bar not defined.
LOG: Name var not defined.
LOG: import * from A
LOG: (incomplete)
LOG: After:
LOG: Name Foo not defined.
LOG: Name Bar not defined.
LOG: Name var not defined.
LOG: progress.
LOG: deferred.
LOG: Iteration 2:
LOG: Module A:
LOG: Before:
LOG: Name Foo not defined.
LOG: Name Bar not defined.
LOG: Name var not defined.
LOG: import * from B
LOG: (incomplete)
LOG: import Foo from B
LOG: (incomplete)
LOG: import var from B
LOG: (incomplete)
LOG: After:
LOG: Name Foo not defined.
LOG: Name Bar not defined.
LOG: Name var not defined.
LOG: deferred.
LOG: Module B:
LOG: Before:
LOG: Name Foo not defined.
LOG: Name Bar not defined.
LOG: Name var not defined.
LOG: import * from A
LOG: (incomplete)
LOG: After:
LOG: Name Foo not defined.
LOG: Name Bar not defined.
LOG: Name var not defined.
LOG: deferred.
-> extra iterations after lack of progress.
LOG: Iteration 3:
LOG: Module A:
LOG: Before:
LOG: Name Foo not defined.
LOG: Name Bar not defined.
LOG: Name var not defined.
LOG: import * from B
LOG: (incomplete)
LOG: import Foo from B
LOG: (incomplete)
LOG: error message: A 4:1 Module "B" has no attribute "Foo" <--- Wrong. B.Foo will get defined later.
LOG: add symbol: A.Foo (Var) (private) from line -1
LOG: import var from B
LOG: (incomplete)
LOG: error message: A 4:1 Module "B" has no attribute "var" <--- Wrong. B.var will get defined later.
LOG: add symbol: A.var (Var) (private) from line -1
LOG: error message: A 7:0 Name "Bar" is not defined <--- Wrong. A.Bar will get imported from B later.
LOG: add symbol: A.myvar (Var) from line 8
LOG: After:
LOG: A.Foo (Var) (private) found
LOG: Name Bar not defined.
LOG: A.var (Var) (private) found
LOG: progress.
LOG: deferred.
LOG: Module B:
LOG: Before:
LOG: Name Foo not defined.
LOG: Name Bar not defined.
LOG: Name var not defined.
LOG: import * from A
LOG: (incomplete)
LOG: add symbol: A.myvar (Var) from line 8
LOG: add symbol: B.Foo (TypeInfo) from line 3
LOG: add symbol: B.Bar (TypeInfo) from line 4
LOG: add symbol: B.var (Var) from line 5
LOG: After:
LOG: B.Foo (TypeInfo) found
LOG: B.Bar (TypeInfo) found
LOG: B.var (Var) found
LOG: progress.
LOG: deferred.
LOG: Iteration 4:
LOG: Module A:
LOG: Before:
LOG: A.Foo (Var) (private) found
LOG: Name Bar not defined.
LOG: A.var (Var) (private) found
LOG: import * from B
LOG: (incomplete)
LOG: Import B.Foo (TypeInfo) over A.Foo (Var) (private)
LOG: add symbol: B.Bar (TypeInfo) from line 4
LOG: Import B.var (Var) over A.var (Var) (private)
LOG: import Foo from B
LOG: (incomplete)
LOG: Import B.Foo (TypeInfo) over A.Foo (Var) (private)
LOG: import var from B
LOG: (incomplete)
LOG: Import B.var (Var) over A.var (Var) (private)
LOG: After:
LOG: A.Foo (Var) (private) found
LOG: B.Bar (TypeInfo) found
LOG: A.var (Var) (private) found
LOG: progress.
LOG: deferred.
LOG: Module B:
LOG: Before:
LOG: B.Foo (TypeInfo) found
LOG: B.Bar (TypeInfo) found
LOG: B.var (Var) found
LOG: import * from A
LOG: (incomplete)
LOG: After:
LOG: B.Foo (TypeInfo) found
LOG: B.Bar (TypeInfo) found
LOG: B.var (Var) found
LOG: deferred.
LOG: Iteration 5:
LOG: Module A:
LOG: Before:
LOG: A.Foo (Var) (private) found
LOG: B.Bar (TypeInfo) found
LOG: A.var (Var) (private) found
LOG: import * from B
LOG: (incomplete)
LOG: Import B.Foo (TypeInfo) over A.Foo (Var) (private)
LOG: Import B.var (Var) over A.var (Var) (private)
LOG: import Foo from B
LOG: (incomplete)
LOG: Import B.Foo (TypeInfo) over A.Foo (Var) (private)
LOG: import var from B
LOG: (incomplete)
LOG: Import B.var (Var) over A.var (Var) (private)
LOG: After:
LOG: A.Foo (Var) (private) found
LOG: B.Bar (TypeInfo) found
LOG: A.var (Var) (private) found
LOG: deferred.
LOG: Module B:
LOG: Before:
LOG: B.Foo (TypeInfo) found
LOG: B.Bar (TypeInfo) found
LOG: B.var (Var) found
LOG: import * from A
LOG: (incomplete)
LOG: After:
LOG: B.Foo (TypeInfo) found
LOG: B.Bar (TypeInfo) found
LOG: B.var (Var) found
LOG: deferred.
LOG: Iteration 6 (final):
<-- At this point, A and B are considered to be complete, as nothing new will be added to them.
LOG: Module A:
LOG: Before:
LOG: A.Foo (Var) (private) found
LOG: B.Bar (TypeInfo) found
LOG: A.var (Var) (private) found
LOG: import * from B
LOG: Import B.Foo (TypeInfo) over A.Foo (Var) (private)
LOG: Import B.var (Var) over A.var (Var) (private)
LOG: import Foo from B
LOG: Import B.Foo (TypeInfo) over A.Foo (Var) (private)
LOG: import var from B
LOG: Import B.var (Var) over A.var (Var) (private)
LOG: After:
LOG: A.Foo (Var) (private) found
LOG: B.Bar (TypeInfo) found
LOG: A.var (Var) (private) found
LOG: complete.
LOG: Module B:
LOG: Before:
LOG: B.Foo (TypeInfo) found
LOG: B.Bar (TypeInfo) found
LOG: B.var (Var) found
LOG: import * from A
LOG: After:
LOG: B.Foo (TypeInfo) found
LOG: B.Bar (TypeInfo) found
LOG: B.var (Var) found
LOG: complete.
LOG: Type checking A.
LOG: Type checking B.
A.py:4: error: Module "B" has no attribute "Foo"
A.py:4: error: Module "B" has no attribute "var"
A.py:7: error: Name "Bar" is not defined
Found 3 errors in 1 file (checked 2 source files)
This won't happen with the current mypy code because iteration 3 is the final iteration. The missing names will be reported. This is a false negative, in my opinion. When there is cycle of import *'s, then a public symbol defined in any of the modules will be a public symbol in all of them.
With my altered mypy code, the errors are generated on the logic that this could be the last chance to report the errors, and nothing later will change the errors. In actuality, the last chance to report an error is when there is no way for the name in question to get defined later.
For an imported name, if the imported module is now complete, the imported name is really missing. For a local name (such as Bar() in A.py), the name might be supplied by an import *, or an import of the same name, from an incomplete module, but if no such import * has been seen earlier in the current module, then the name is really missing.
In my altered code, the current module keeps track of names which are missing and might be supplied in a later iteration. Without going into details here, it similar to the current logic. The essence is that given any undefined name, it can tell if it is irrevocably missing or not. In the former case, the error message should now be generated, as the current module might or might not be deferred. In the latter case, no error message is generated, and the current module will be deferred.
The fix for the present issue will be part of my fix for #12592, when I finally get done with it.