diff --git a/docs/source/config_file.rst b/docs/source/config_file.rst index 7abd1f02db68..5ef87b3ce9bc 100644 --- a/docs/source/config_file.rst +++ b/docs/source/config_file.rst @@ -437,6 +437,43 @@ Platform configuration This option may only be set in the global section (``[mypy]``). + .. note:: + + **Using multiple Python versions in a monorepo** + + Mypy currently supports only **one global target Python version** per run. + The configuration option :confval:`python_version` can be set **only in the + global section** of the configuration file. This means that a single + invocation of mypy cannot type-check different parts of a monorepo using + different Python versions. + + If your project contains directories or packages that target different + Python versions, you can use one of the following workarounds: + + * **Run mypy multiple times**, selecting a different version for each + directory. + + Example:: + + mypy --python-version=3.9 src/py39_package + mypy --python-version=3.11 backend/py311 + + * **Use separate configuration files**, each specifying its own + ``python_version``. + + Example:: + + mypy --config-file=mypy_py39.ini src/py39_package + mypy --config-file=mypy_py311.ini backend/ + + * **Use an external build/monorepo tool** (such as Pants, Bazel, or a CI + pipeline) to orchestrate multiple mypy invocations automatically. + + This documentation update clarifies the current limitation and addresses + :issue:`16944`. + + + .. confval:: platform :type: string diff --git a/mypy/scope.py b/mypy/scope.py index 766048c41180..c17bebeb4646 100644 --- a/mypy/scope.py +++ b/mypy/scope.py @@ -42,7 +42,7 @@ def current_full_target(self) -> str: """Return the current target (may be a class).""" assert self.module if self.function: - return self.function.fullname + return self.function.fullname or "" if self.classes: return self.classes[-1].fullname return self.module @@ -60,6 +60,7 @@ def module_scope(self, prefix: str) -> Iterator[None]: self.module = prefix self.classes = [] self.function = None + self.functions = [] # reset the function stack when entering a new module self.ignored = 0 yield assert self.module diff --git a/mypy/test/test_scope_behavior.py b/mypy/test/test_scope_behavior.py new file mode 100644 index 000000000000..074285b23c3c --- /dev/null +++ b/mypy/test/test_scope_behavior.py @@ -0,0 +1,18 @@ +from mypy.nodes import FuncDef, SymbolTable, TypeInfo +from mypy.scope import Scope + + +def test_scope_module_and_function_behavior() -> None: + scope = Scope() + with scope.module_scope("mod1"): + assert scope.current_module_id() == "mod1" + # simulate function + fake_func = FuncDef("f", None, None, None, None) + with scope.function_scope(fake_func): + assert "f" in scope.current_full_target() + # simulate class inside function + fake_class = TypeInfo(SymbolTable(), "C", None) + with scope.class_scope(fake_class): + assert "C" in scope.current_full_target() + # leaving function restores module + assert scope.current_full_target() == "mod1" diff --git a/test-data/unit/issue_scope.test b/test-data/unit/issue_scope.test new file mode 100644 index 000000000000..2ea013dd4e62 --- /dev/null +++ b/test-data/unit/issue_scope.test @@ -0,0 +1,17 @@ +[case testScopeOptionalIntResolution] +from typing import Optional + +x: Optional[int] = None +y: Optional[int] = None + +def f() -> None: + x = 1 + y = 1 + class C: + reveal_type(x) # should be int + reveal_type(y) # should be Optional[int] + x = 2 + +[out] +note: Revealed type is "builtins.int" +note: Revealed type is "Union[builtins.int, None]"