Skip to content

Commit

Permalink
Handle types.ModuleType (#3107)
Browse files Browse the repository at this point in the history
* Allowed use of types.ModuleType

* Allow module name override for stub files

* Remove superfluous class module

* Hard code module renaming

* Address CR

* Updated typeshed

* Fix fine-grained tests

* Fix whitespace
  • Loading branch information
pkch authored and ilevkivskyi committed Apr 13, 2017
1 parent 77e3237 commit 73acdb8
Show file tree
Hide file tree
Showing 18 changed files with 80 additions and 48 deletions.
2 changes: 1 addition & 1 deletion mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ def analyze_ref_expr(self, e: RefExpr, lvalue: bool = False) -> Type:
result = type_object_type(node, self.named_type)
elif isinstance(node, MypyFile):
# Reference to a module object.
result = self.named_type('builtins.module')
result = self.named_type('types.ModuleType')
elif isinstance(node, Decorator):
result = self.analyze_var_ref(node.var, e)
else:
Expand Down
2 changes: 1 addition & 1 deletion mypy/checkmember.py
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@ def analyze_class_attribute_access(itype: Instance,

if isinstance(node.node, MypyFile):
# Reference to a module object.
return builtin_type('builtins.module')
return builtin_type('types.ModuleType')

if is_decorated:
# TODO: Return type of decorated function. This is quick hack to work around #998.
Expand Down
9 changes: 9 additions & 0 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,13 @@
'typing.typevar': 'typing.TypeVar',
}

# Rename objects placed in _importlib_modulespec due to circular imports
module_rename_map = {
'_importlib_modulespec.ModuleType': 'types.ModuleType',
'_importlib_modulespec.ModuleSpec': 'importlib.machinery.ModuleSpec',
'_importlib_modulespec.Loader': 'importlib.abc.Loader'
}

# Hard coded type promotions (shared between all Python versions).
# These add extra ad-hoc edges to the subtyping relation. For example,
# int is considered a subtype of float, even though there is no
Expand Down Expand Up @@ -3445,6 +3452,8 @@ def visit_file(self, file: MypyFile, fnam: str, mod_id: str, options: Options) -

for d in defs:
d.accept(self)
if isinstance(d, ClassDef):
d.info._fullname = module_rename_map.get(d.info._fullname, d.info._fullname)

# Add implicit definition of literals/keywords to builtins, as we
# cannot define a variable with them explicitly.
Expand Down
6 changes: 4 additions & 2 deletions mypy/server/deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,10 +207,12 @@ def visit_tuple_type(self, typ: TupleType) -> List[str]:
raise NotImplementedError

def visit_type_type(self, typ: TypeType) -> List[str]:
raise NotImplementedError
# TODO: replace with actual implementation
return []

def visit_type_var(self, typ: TypeVarType) -> List[str]:
raise NotImplementedError
# TODO: replace with actual implementation
return []

def visit_typeddict_type(self, typ: TypedDictType) -> List[str]:
raise NotImplementedError
Expand Down
4 changes: 2 additions & 2 deletions test-data/unit/check-ignore.test
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ import b # type: ignore
reveal_type(a.foo) # E: Revealed type is 'Any'
reveal_type(b.foo) # E: Revealed type is 'builtins.int'
a.bar()
b.bar() # E: "module" has no attribute "bar"
b.bar() # E: "ModuleType" has no attribute "bar"

[file b.py]
foo = 3
Expand Down Expand Up @@ -76,7 +76,7 @@ class B(A):
import m
m.x = object # type: ignore
m.f() # type: ignore
m.y # E: "module" has no attribute "y"
m.y # E: "ModuleType" has no attribute "y"
[file m.py]
[builtins fixtures/module.pyi]

Expand Down
6 changes: 3 additions & 3 deletions test-data/unit/check-incremental.test
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ const = 3
[stale mod3]
[builtins fixtures/module.pyi]
[out2]
tmp/mod1.py:3: error: "module" has no attribute "mod4"
tmp/mod1.py:3: error: "ModuleType" has no attribute "mod4"

[case testIncrementalLongBrokenCascade]
import mod1
Expand Down Expand Up @@ -335,7 +335,7 @@ const = 3
[stale mod6]
[builtins fixtures/module.pyi]
[out2]
tmp/mod1.py:3: error: "module" has no attribute "mod7"
tmp/mod1.py:3: error: "ModuleType" has no attribute "mod7"

[case testIncrementalNestedBrokenCascade]
import mod1
Expand All @@ -361,7 +361,7 @@ const = 3
[stale mod2.mod3]
[builtins fixtures/module.pyi]
[out2]
tmp/mod1.py:3: error: "module" has no attribute "mod4"
tmp/mod1.py:3: error: "ModuleType" has no attribute "mod4"

[case testIncrementalNestedBrokenCascadeWithType1]
import mod1, mod2.mod3.mod5
Expand Down
26 changes: 18 additions & 8 deletions test-data/unit/check-modules.test
Original file line number Diff line number Diff line change
Expand Up @@ -153,17 +153,18 @@ def f(c:str) -> None: pass
[case testInvalidOperationsOnModules]
import m
import typing

class A: pass
m() # E: "module" not callable
a = m # type: A # E: Incompatible types in assignment (expression has type "module", variable has type "A")
m + None # E: Unsupported left operand type for + ("module")
m() # E: "ModuleType" not callable
a = m # type: A # E: Incompatible types in assignment (expression has type "ModuleType", variable has type "A")
m + None # E: Unsupported left operand type for + ("ModuleType")
[file m.py]
[builtins fixtures/module.pyi]

[case testNameDefinedInDifferentModule]
import m, n
import typing
m.x # E: "module" has no attribute "x"
m.x # E: "ModuleType" has no attribute "x"
[file m.py]
y = object()
[file n.py]
Expand Down Expand Up @@ -329,7 +330,7 @@ import nonexistent
[out]
tmp/x.py:1: error: Cannot find module named 'nonexistent'
tmp/x.py:1: note: (Perhaps setting MYPYPATH or using the "--ignore-missing-imports" flag would help)
main:3: error: "module" has no attribute "z"
main:3: error: "ModuleType" has no attribute "z"

[case testUnknownModuleImportedWithinFunction]
def f():
Expand Down Expand Up @@ -647,7 +648,7 @@ def f(x: str) -> None: pass
if object():
import m
else:
m = 1 # E: Incompatible types in assignment (expression has type "int", variable has type "module")
m = 1 # E: Incompatible types in assignment (expression has type "int", variable has type "ModuleType")
[file m.py]
[builtins fixtures/module.pyi]
[out]
Expand Down Expand Up @@ -751,7 +752,7 @@ value = 3.2
[case testSubmoduleImportFromDoesNotAddParents]
from a import b
reveal_type(b.value) # E: Revealed type is 'builtins.str'
b.c.value # E: "module" has no attribute "c"
b.c.value # E: "ModuleType" has no attribute "c"
a.value # E: Name 'a' is not defined

[file a/__init__.py]
Expand Down Expand Up @@ -852,7 +853,7 @@ bar = parent.unrelated.ShouldNotLoad()
[builtins fixtures/module.pyi]
[out]
tmp/parent/child.py:8: error: Revealed type is 'parent.common.SomeClass'
tmp/parent/child.py:9: error: "module" has no attribute "unrelated"
tmp/parent/child.py:9: error: "ModuleType" has no attribute "unrelated"

[case testSubmoduleMixingImportFromAndImport2]
import parent.child
Expand Down Expand Up @@ -1406,3 +1407,12 @@ reveal_type(cb) # E: Revealed type is 'def (*Any, **Any) -> Any'
from typing import Callable, Any
AnyCallable = Callable[..., Any]
[out]

[case testRevealType]
import types
def f() -> types.ModuleType:
return types
reveal_type(f()) # E: Revealed type is 'types.ModuleType'
reveal_type(types) # E: Revealed type is 'types.ModuleType'

[builtins fixtures/module.pyi]
8 changes: 4 additions & 4 deletions test-data/unit/cmdline.test
Original file line number Diff line number Diff line change
Expand Up @@ -365,8 +365,8 @@ x += '' # Error reported here
a.py:2: error: Unsupported operand types for + ("int" and "str")
main.py:3: error: Unsupported operand types for + ("int" and "str")
main.py:6: error: Unsupported operand types for + ("int" and "str")
main.py:7: error: "module" has no attribute "y"
main.py:8: error: Unsupported operand types for + ("module" and "int")
main.py:7: error: "ModuleType" has no attribute "y"
main.py:8: error: Unsupported operand types for + ("ModuleType" and "int")

[case testConfigFollowImportsSilent]
# cmd: mypy main.py
Expand All @@ -386,8 +386,8 @@ x += '' # No error reported
[out]
main.py:2: error: Unsupported operand types for + ("int" and "str")
main.py:4: error: Unsupported operand types for + ("int" and "str")
main.py:5: error: "module" has no attribute "y"
main.py:6: error: Unsupported operand types for + ("module" and "int")
main.py:5: error: "ModuleType" has no attribute "y"
main.py:6: error: Unsupported operand types for + ("ModuleType" and "int")

[case testConfigFollowImportsSkip]
# cmd: mypy main.py
Expand Down
24 changes: 12 additions & 12 deletions test-data/unit/fine-grained.test
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def g(x: str) -> None: pass
[builtins fixtures/fine_grained.pyi]
[out]
==
main:3: error: "module" has no attribute "f"
main:3: error: "ModuleType" has no attribute "f"

[case testTopLevelMissingModuleAttribute]
import m
Expand All @@ -81,7 +81,7 @@ def g(x: int) -> None: pass
[builtins fixtures/fine_grained.pyi]
[out]
==
main:2: error: "module" has no attribute "f"
main:2: error: "ModuleType" has no attribute "f"

[case testClassChangedIntoFunction]
import m
Expand Down Expand Up @@ -241,7 +241,7 @@ class A: pass
[builtins fixtures/fine_grained.pyi]
[out]
==
main:3: error: "module" has no attribute "A"
main:3: error: "ModuleType" has no attribute "A"
==

[case testContinueToReportTypeCheckError]
Expand Down Expand Up @@ -281,10 +281,10 @@ class A: pass
[builtins fixtures/fine_grained.pyi]
[out]
==
main:3: error: "module" has no attribute "A"
main:5: error: "module" has no attribute "B"
main:3: error: "ModuleType" has no attribute "A"
main:5: error: "ModuleType" has no attribute "B"
==
main:5: error: "module" has no attribute "B"
main:5: error: "ModuleType" has no attribute "B"

[case testContinueToReportErrorAtTopLevel]
import n
Expand Down Expand Up @@ -348,9 +348,9 @@ def g() -> None: pass
[builtins fixtures/fine_grained.pyi]
[out]
main:3: error: Too few arguments for "f"
main:5: error: "module" has no attribute "g"
main:5: error: "ModuleType" has no attribute "g"
==
main:5: error: "module" has no attribute "g"
main:5: error: "ModuleType" has no attribute "g"
==

[case testKeepReportingErrorIfNoChanges]
Expand All @@ -361,9 +361,9 @@ def h() -> None:
[file m.py.2]
[builtins fixtures/fine_grained.pyi]
[out]
main:3: error: "module" has no attribute "g"
main:3: error: "ModuleType" has no attribute "g"
==
main:3: error: "module" has no attribute "g"
main:3: error: "ModuleType" has no attribute "g"

[case testFixErrorAndReintroduce]
import m
Expand All @@ -375,10 +375,10 @@ def g() -> None: pass
[file m.py.3]
[builtins fixtures/fine_grained.pyi]
[out]
main:3: error: "module" has no attribute "g"
main:3: error: "ModuleType" has no attribute "g"
==
==
main:3: error: "module" has no attribute "g"
main:3: error: "ModuleType" has no attribute "g"

[case testAddBaseClassMethodCausingInvalidOverride]
import m
Expand Down
1 change: 0 additions & 1 deletion test-data/unit/fixtures/args.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,3 @@ class int:
class str: pass
class bool: pass
class function: pass
class module: pass
6 changes: 5 additions & 1 deletion test-data/unit/fixtures/async_await.pyi
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import typing

T = typing.TypeVar('T')
class list(typing.Generic[T], typing.Sequence[T]): pass

class object:
def __init__(self): pass
class type: pass
class function: pass
class int: pass
class str: pass
class dict: pass
class list: pass
class set: pass
class tuple: pass
class BaseException: pass
class StopIteration(BaseException): pass
class StopAsyncIteration(BaseException): pass
def iter(obj: typing.Any) -> typing.Any: pass
def next(obj: typing.Any) -> typing.Any: pass
class ellipsis: ...
4 changes: 3 additions & 1 deletion test-data/unit/fixtures/fine_grained.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
# TODO: Migrate to regular stubs once fine-grained incremental is robust
# enough to handle them.

import types

class Any: pass

class object:
Expand All @@ -21,4 +23,4 @@ class bytes: pass
class tuple: pass
class function: pass
class ellipsis: pass
class module: pass
class list: pass
11 changes: 6 additions & 5 deletions test-data/unit/fixtures/module.pyi
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
from typing import Any, Dict, Generic, TypeVar
from typing import Any, Dict, Generic, TypeVar, Sequence
from types import ModuleType

T = TypeVar('T')
S = TypeVar('S')

class list(Generic[T], Sequence[T]): pass

class object:
def __init__(self) -> None: pass
class module:
__name__ = ... # type: str
__file__ = ... # type: str
__dict__ = ... # type: Dict[str, Any]
class type: pass
class function: pass
class int: pass
class str: pass
class bool: pass
class tuple: pass
class dict(Generic[T, S]): pass
class ellipsis: pass

5 changes: 4 additions & 1 deletion test-data/unit/fixtures/module_all.pyi
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
from typing import Generic, Sequence, TypeVar
from types import ModuleType

_T = TypeVar('_T')

class object:
def __init__(self) -> None: pass
class module: pass
class type: pass
class function: pass
class int: pass
class str: pass
class bool: pass
class list(Generic[_T], Sequence[_T]):
def append(self, x: _T): pass
def extend(self, x: Sequence[_T]): pass
def __add__(self, rhs: Sequence[_T]) -> list[_T]: pass
class tuple: pass
class ellipsis: pass
1 change: 0 additions & 1 deletion test-data/unit/fixtures/module_all_python2.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ _T = TypeVar('_T')

class object:
def __init__(self) -> None: pass
class module: pass
class type: pass
class function: pass
class int: pass
Expand Down
2 changes: 0 additions & 2 deletions test-data/unit/fixtures/ops.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,3 @@ class float: pass
class BaseException: pass

def __print(a1=None, a2=None, a3=None, a4=None): pass

class module: pass
7 changes: 6 additions & 1 deletion test-data/unit/lib-stub/types.pyi
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
from typing import TypeVar
from typing import TypeVar, Optional, List, Any, Generic, Sequence
T = TypeVar('T')

def coroutine(func: T) -> T:
return func

class bool: ...

class ModuleType: ...
4 changes: 2 additions & 2 deletions test-data/unit/pythoneval.test
Original file line number Diff line number Diff line change
Expand Up @@ -1169,8 +1169,8 @@ collections.Deque()
typing.deque()

[out]
_testDequeWrongCase.py:4: error: "module" has no attribute "Deque"
_testDequeWrongCase.py:5: error: "module" has no attribute "deque"
_testDequeWrongCase.py:4: error: "ModuleType" has no attribute "Deque"
_testDequeWrongCase.py:5: error: "ModuleType" has no attribute "deque"

[case testDictUpdateInference]
from typing import Dict, Optional
Expand Down

0 comments on commit 73acdb8

Please sign in to comment.