diff --git a/mypy/build.py b/mypy/build.py index f4be6ec73342..e4b1ec2565e8 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -2055,7 +2055,7 @@ def find_cache_meta( # Ignore cache if (relevant) options aren't the same. # Note that it's fine to mutilate cached_options since it's only used here. - cached_options = m.options + cached_options = m.options.copy() current_options = options_snapshot(id, manager) if manager.options.skip_version_check: # When we're lax about version we're also lax about platform. @@ -2063,6 +2063,18 @@ def find_cache_meta( if "debug_cache" in cached_options: # Older versions included debug_cache, but it's silly to compare it. del cached_options["debug_cache"] + if "local_partial_types" in cached_options: + local_partial_types = cached_options["local_partial_types"] + del cached_options["local_partial_types"] + is_parallel = manager.options.num_workers > 0 + if not local_partial_types and cached_options["is_parallel"] != is_parallel: + # If local partial types are disabled, behavior is too different for + # parallel and sequential runs, see write_cache() for details. + return None + del cached_options["is_parallel"] + else: + # Cache from an old mypy version. + return None if cached_options != current_options: manager.log(f"Metadata abandoned for {id}: options differ") if manager.options.verbosity >= 2: @@ -2226,7 +2238,6 @@ def validate_meta( meta.mtime = mtime meta.path = path meta.size = size - meta.options = options_snapshot(id, manager) meta_file, _, _ = get_cache_names(id, path, manager.options) if manager.logging_enabled: manager.log( @@ -2271,6 +2282,8 @@ def write_cache( trans_dep_hash: bytes, source_hash: str, ignore_all: bool, + local_partial_types: bool, + is_parallel: bool, manager: BuildManager, ) -> tuple[bytes, tuple[CacheMeta, str] | None]: """Write cache files for a module. @@ -2370,6 +2383,13 @@ def write_cache( # important, or otherwise the options would never match when # verifying the cache. assert source_hash is not None + # Local partial types need special handling as they behave differently + # in sequential and parallel runs: + # * In sequential run we respected the option, but use different SCC + # processing logic depending on whether they are enabled or disabled. + # * In parallel run they are always on, and we give an error if a user + # tries to disable them. + extra_options = {"local_partial_types": local_partial_types, "is_parallel": is_parallel} meta = CacheMeta( id=id, path=path, @@ -2381,7 +2401,7 @@ def write_cache( data_file=data_file, suppressed=suppressed, imports_ignored=imports_ignored, - options=options_snapshot(id, manager), + options=options_snapshot(id, manager) | extra_options, suppressed_deps_opts=suppressed_deps_opts, dep_prios=dep_prios, dep_lines=dep_lines, @@ -2726,6 +2746,11 @@ def new_state( meta, meta_ex = meta_pair interface_hash = meta.interface_hash meta_source_hash = meta.hash + # Update the local partial types in case they were set by an inline config. + # So we can select the correct SCC processing logic without reading the file. + local_partial_types = meta.options["local_partial_types"] + if options.local_partial_types != local_partial_types: + options = options.apply_changes({"local_partial_types": local_partial_types}) if path and source is None and manager.fscache.isdir(path): source = "" @@ -3636,6 +3661,8 @@ def write_cache(self) -> tuple[CacheMeta, str] | None: self.trans_dep_hash, self.source_hash, self.ignore_all, + self.options.local_partial_types, + self.options.num_workers > 0, self.manager, ) if new_interface_hash == self.interface_hash: @@ -4636,18 +4663,7 @@ def process_graph(graph: Graph, manager: BuildManager) -> None: # type-checking this is already done and results should be empty here. if not manager.workers: assert not results - for id, result in results.items(): - # Interface and implementation results may be mixed in the same batch - # from different workers, process each one accordingly. - if result.interface_hash is not None: - new_hash = bytes.fromhex(result.interface_hash) - if new_hash != graph[id].interface_hash: - graph[id].mark_interface_stale() - graph[id].interface_hash = new_hash - else: - manager.flush_errors( - manager.errors.simplify_path(graph[id].xpath), result.error_lines, False - ) + process_results(results, graph, manager) ready = [] for done_scc in done: for dependent in done_scc.direct_dependents: @@ -4659,6 +4675,26 @@ def process_graph(graph: Graph, manager: BuildManager) -> None: manager.trace(f"Transitive deps cache size: {sys.getsizeof(manager.transitive_deps_cache)}") +def process_results(results: dict[str, ModuleResult], graph: Graph, manager: BuildManager) -> None: + """Process results of type-checking given modules. + + This will update interface hashes and flush type-checking errors (if any). + Blockers should have been already handled by the caller. + """ + for id, result in results.items(): + # Interface and implementation results may be mixed in the same batch + # from different workers, process each one accordingly. + if result.interface_hash is not None: + new_hash = bytes.fromhex(result.interface_hash) + if new_hash != graph[id].interface_hash: + graph[id].mark_interface_stale() + graph[id].interface_hash = new_hash + else: + manager.flush_errors( + manager.errors.simplify_path(graph[id].xpath), result.error_lines, False + ) + + def order_ascc(graph: Graph, ascc: AbstractSet[str], pri_max: int = PRI_INDIRECT) -> list[str]: """Come up with the ideal processing order within an SCC. @@ -4776,7 +4812,45 @@ def maybe_load_deps(graph: Graph, ascc: SCC, manager: BuildManager) -> None: def process_stale_scc(graph: Graph, ascc: SCC, manager: BuildManager) -> None: - """Process the modules in one SCC from source code.""" + """Process the modules in one SCC from source code. + + This will process module interfaces first (when possible). This mirrors + how things are done in parallel type checking. + """ + if not all(graph[id].options.local_partial_types for id in ascc.mod_ids): + # If local partial types are disabled we must process each file sequentially. + process_stale_scc_full(graph, ascc, manager) + return + manager.parse_all([graph[id] for id in ascc.mod_ids], post_parse=False) + scc_result = process_stale_scc_interface( + graph, ascc, manager, from_cache={id for id in ascc.mod_ids if graph[id].meta} + ) + manager.commit() + + # Process interface results before starting implementations + # (to mimic parallel checking 1:1). + mod_results = {} + stale = [] + meta_files = [] + for id, mod_result, meta_file in scc_result: + stale.append(id) + mod_results[id] = mod_result + meta_files.append(meta_file) + process_results(mod_results, graph, manager) + + mod_results = {} + for id, meta_file in zip(stale, meta_files): + mod_results |= process_stale_scc_implementation(graph, [id], manager, [meta_file]) + manager.commit() + process_results(mod_results, graph, manager) + + +def process_stale_scc_full(graph: Graph, ascc: SCC, manager: BuildManager) -> None: + """Process the modules in one SCC from source code. + + This is the legacy function that processes each file sequentially (line-by-line), + thus it may interleave processing interface and implementation parts. + """ # First verify if all transitive dependencies are loaded in the current process. t0 = time.time() maybe_load_deps(graph, ascc, manager) @@ -4879,7 +4953,7 @@ def process_stale_scc(graph: Graph, ascc: SCC, manager: BuildManager) -> None: def process_stale_scc_interface( graph: Graph, ascc: SCC, manager: BuildManager, from_cache: set[str] -) -> list[tuple[str, ModuleResult, str]]: +) -> list[tuple[str, ModuleResult, str | None]]: """Process the modules' interfaces in one SCC from source code.""" # First verify if all transitive dependencies are loaded in the current process. t0 = time.time() @@ -4925,16 +4999,19 @@ def process_stale_scc_interface( for id in stale: meta_tuple = meta_tuples[id] if meta_tuple is None: - continue - meta, meta_file = meta_tuple + meta = meta_file = None + else: + meta, meta_file = meta_tuple state = graph[id] - meta.dep_hashes = [ - graph[dep].interface_hash - for dep in state.dependencies - if state.priorities.get(dep) != PRI_INDIRECT - ] - write_cache_meta(meta, manager, meta_file) - manager.commit_module(meta_file) + if meta is not None: + assert meta_file is not None + meta.dep_hashes = [ + graph[dep].interface_hash + for dep in state.dependencies + if state.priorities.get(dep) != PRI_INDIRECT + ] + write_cache_meta(meta, manager, meta_file) + manager.commit_module(meta_file) scc_result.append((id, ModuleResult(graph[id].interface_hash.hex(), []), meta_file)) manager.done_sccs.add(ascc.id) manager.add_stats( @@ -4948,7 +5025,7 @@ def process_stale_scc_interface( def process_stale_scc_implementation( - graph: Graph, stale: list[str], manager: BuildManager, meta_files: list[str] + graph: Graph, stale: list[str], manager: BuildManager, meta_files: list[str | None] ) -> dict[str, ModuleResult]: """Process implementations (top-level function/method bodies) in an SCC.""" t0 = time.time() @@ -4963,7 +5040,10 @@ def process_stale_scc_implementation( continue # We need to reset deferral count after possibly deferring any methods that # are considered part of the top-level (because they define/infer variables). - checker.pass_num = 0 + # Note we need to add one pass to compensate for function bodies not visited in + # type_check_first_pass(). So with current DEFAULT_LAST_PASS = 2 each function + # will be visited at most three times, for both single-phase and two-phase logic. + checker.pass_num = -1 checker.deferred_nodes.clear() tree = graph[id].tree assert tree is not None @@ -4993,6 +5073,18 @@ def process_stale_scc_implementation( scc_result = {} for id, meta_file in zip(stale, meta_files): state = graph[id] + # If there are no errors, only write the cache, don't send anything back + # to the caller (as a micro-optimization). + if graph[id].xpath not in manager.errors.ignored_files: + errors = manager.errors.file_messages(graph[id].xpath) + formatted = manager.errors.format_messages( + graph[id].xpath, errors, formatter=manager.error_formatter + ) + scc_result[id] = ModuleResult(None, formatted) + else: + errors = [] + if meta_file is None: + continue indirect = [dep for dep in state.dependencies if state.priorities.get(dep) == PRI_INDIRECT] meta_ex = CacheMetaEx( dependencies=indirect, @@ -5000,20 +5092,9 @@ def process_stale_scc_implementation( dep for dep in state.suppressed if state.priorities.get(dep) == PRI_INDIRECT ], dep_hashes=[graph[dep].interface_hash for dep in indirect], - error_lines=[], + error_lines=errors, ) - if graph[id].xpath not in manager.errors.ignored_files: - errors = manager.errors.file_messages(graph[id].xpath) - formatted = manager.errors.format_messages( - graph[id].xpath, errors, formatter=manager.error_formatter - ) - meta_ex.error_lines = errors - write_cache_meta_ex(meta_file, meta_ex, manager) - scc_result[id] = ModuleResult(None, formatted) - else: - # If there are no errors, only write the cache, don't send anything back - # to the caller (as a micro-optimization). - write_cache_meta_ex(meta_file, meta_ex, manager) + write_cache_meta_ex(meta_file, meta_ex, manager) manager.commit_module(meta_file) manager.add_stats(type_check_time_implementation=time.time() - t0) diff --git a/mypy/semanal.py b/mypy/semanal.py index 196a18550088..5ef5223c2346 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -719,24 +719,26 @@ def refresh_partial( self.accept(node) del self.patches + def ad_hoc_error(self, msg: str) -> None: + n = TempNode(AnyType(TypeOfAny.special_form)) + n.line = 1 + n.column = 0 + n.end_line = 1 + n.end_column = 0 + self.fail(msg, n) + def refresh_top_level(self, file_node: MypyFile) -> None: """Reanalyze a stale module top-level in fine-grained incremental mode.""" if self.options.allow_redefinition and not self.options.local_partial_types: - n = TempNode(AnyType(TypeOfAny.special_form)) - n.line = 1 - n.column = 0 - n.end_line = 1 - n.end_column = 0 - self.fail("--local-partial-types must be enabled if using --allow-redefinition", n) + self.ad_hoc_error( + "--local-partial-types must be enabled if using --allow-redefinition" + ) if self.options.allow_redefinition and self.options.allow_redefinition_old: - n = TempNode(AnyType(TypeOfAny.special_form)) - n.line = 1 - n.column = 0 - n.end_line = 1 - n.end_column = 0 - self.fail( - "--allow-redefinition-old and --allow-redefinition should not be used together", n + self.ad_hoc_error( + "--allow-redefinition-old and --allow-redefinition should not be used together" ) + if not self.options.local_partial_types and self.options.num_workers > 0: + self.ad_hoc_error("--local-partial-types must be enabled in parallel mode") self.recurse_into_functions = False self.add_implicit_module_attrs(file_node) for d in file_node.defs: @@ -4797,6 +4799,10 @@ def analyze_member_lvalue( self.type.names[lval.name] = SymbolTableNode(MDEF, v, implicit=True) for func in self.scope.functions: func.def_or_infer_vars = True + + if self.is_self_member_ref(lval) or self.is_cls_member_ref(lval): + assert self.type, "Self or cls member outside a class" + cur_node = self.type.names.get(lval.name) if ( cur_node and isinstance(cur_node.node, Var) @@ -4814,6 +4820,13 @@ def is_self_member_ref(self, memberexpr: MemberExpr) -> bool: node = memberexpr.expr.node return isinstance(node, Var) and node.is_self + def is_cls_member_ref(self, memberexpr: MemberExpr) -> bool: + """Does memberexpr to refer to an attribute of cls?""" + if not isinstance(memberexpr.expr, NameExpr): + return False + node = memberexpr.expr.node + return isinstance(node, Var) and node.is_cls + def check_lvalue_validity(self, node: Expression | SymbolNode | None, ctx: Context) -> None: if isinstance(node, TypeVarExpr): self.fail("Invalid assignment target", ctx) diff --git a/mypy/test/testcmdline.py b/mypy/test/testcmdline.py index a482ebbfc5f3..b558127d31dc 100644 --- a/mypy/test/testcmdline.py +++ b/mypy/test/testcmdline.py @@ -8,6 +8,7 @@ import os import re +import shlex import subprocess import sys import sysconfig @@ -57,7 +58,7 @@ def test_python_cmdline(testcase: DataDrivenTestCase, step: int) -> None: with open(program_path, "w", encoding="utf8") as file: for s in testcase.input: file.write(f"{s}\n") - args = parse_args(normalize_devnull(testcase.input[0])) + args = parse_args(normalize_devnull("\n".join(testcase.input)), step) custom_cwd = parse_cwd(testcase.input[1]) if len(testcase.input) > 1 else None args.append("--show-traceback") if "--error-summary" not in args: @@ -121,21 +122,25 @@ def test_python_cmdline(testcase: DataDrivenTestCase, step: int) -> None: ) -def parse_args(line: str) -> list[str]: - """Parse the first line of the program for the command line. +def parse_args(text: str, step: int) -> list[str]: + """Parse the program for the command line. This should have the form - # cmd: mypy + # cmd[N]: mypy For example: - # cmd: mypy pkg/ + # cmd: mypy pkg/ or # cmd2: mypy pkg/ """ - m = re.match("# cmd: mypy (.*)$", line) + m = re.search("# cmd: mypy (.*)$", text, flags=re.MULTILINE) + if step > 1: + alt_m = re.search(f"# cmd{step}: mypy (.*)$", text, flags=re.MULTILINE) + if alt_m is not None: + m = alt_m if not m: return [] # No args; mypy will spit out an error. - return m.group(1).split() + return shlex.split(m.group(1)) def parse_cwd(line: str) -> str | None: diff --git a/test-data/unit/check-bound.test b/test-data/unit/check-bound.test index 1f9eba612020..1c713fd77c38 100644 --- a/test-data/unit/check-bound.test +++ b/test-data/unit/check-bound.test @@ -46,7 +46,7 @@ z = G(B()) [case testBoundVoid] -# flags: --no-strict-optional --no-local-partial-types +# flags: --no-strict-optional from typing import TypeVar, Generic T = TypeVar('T', bound=int) class C(Generic[T]): @@ -75,7 +75,7 @@ z: C [case testBoundHigherOrderWithVoid] -# flags: --no-strict-optional --no-local-partial-types +# flags: --no-strict-optional from typing import TypeVar, Callable class A: pass T = TypeVar('T', bound=A) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index dc5d74abc07d..fa742f3571f8 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -9727,3 +9727,17 @@ def f() -> None: class X: ... undefined # E: Name "undefined" is not defined + +[case testPartialNoneTypeClassMethod] +# flags: --local-partial-types + +class C: + x = None + + @classmethod + def foo(cls) -> None: + if not cls.x: + cls.x = 1 + +reveal_type(C.x) # N: Revealed type is "builtins.int | None" +[builtins fixtures/classmethod.pyi] diff --git a/test-data/unit/check-columns.test b/test-data/unit/check-columns.test index ea88eedd37fc..839d13d36678 100644 --- a/test-data/unit/check-columns.test +++ b/test-data/unit/check-columns.test @@ -209,7 +209,6 @@ y: Dict[int, int] = { [builtins fixtures/dict.pyi] [case testColumnCannotDetermineType] -# flags: --no-local-partial-types (x) # E:2: Cannot determine type of "x" # E:2: Name "x" is used before definition x = None diff --git a/test-data/unit/check-custom-plugin.test b/test-data/unit/check-custom-plugin.test index dd1de1265b59..504c15314553 100644 --- a/test-data/unit/check-custom-plugin.test +++ b/test-data/unit/check-custom-plugin.test @@ -1007,7 +1007,7 @@ reveal_type(Cls.attr) # N: Revealed type is "builtins.int" plugins=/test-data/unit/plugins/class_attr_hook.py [case testClassAttrPluginPartialType] -# flags: --config-file tmp/mypy.ini --no-local-partial-types +# flags: --config-file tmp/mypy.ini class Cls: attr = None diff --git a/test-data/unit/check-errorcodes.test b/test-data/unit/check-errorcodes.test index 35e63bca7ee4..c82ef61d0140 100644 --- a/test-data/unit/check-errorcodes.test +++ b/test-data/unit/check-errorcodes.test @@ -981,7 +981,7 @@ Foo = TypedDict("Bar", {}) # E: First argument "Bar" to TypedDict() does not ma [typing fixtures/typing-typeddict.pyi] [case testTruthyBool] -# flags: --enable-error-code truthy-bool --no-local-partial-types +# flags: --enable-error-code truthy-bool from typing import List, Union, Any class Foo: diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index 4c8fec2e5a95..4cb58820d5bf 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -2664,7 +2664,7 @@ reveal_type(bar(None)) # N: Revealed type is "None" [out] [case testNoComplainInferredNone] -# flags: --no-strict-optional --no-local-partial-types +# flags: --no-strict-optional from typing import TypeVar, Optional T = TypeVar('T') def X(val: T) -> T: ... diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index b8f7a5699e19..48b0beff03d3 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -2921,8 +2921,8 @@ def mix(fs: List[Callable[[S], T]]) -> Callable[[S], List[T]]: def id(__x: U) -> U: ... fs = [id, id, id] -reveal_type(mix(fs)) # N: Revealed type is "def [S] (S`2) -> builtins.list[S`2]" -reveal_type(mix([id, id, id])) # N: Revealed type is "def [S] (S`4) -> builtins.list[S`4]" +reveal_type(mix(fs)) # N: Revealed type is "def [S] (S`1) -> builtins.list[S`1]" +reveal_type(mix([id, id, id])) # N: Revealed type is "def [S] (S`3) -> builtins.list[S`3]" [builtins fixtures/list.pyi] [case testInferenceAgainstGenericCurry] @@ -3098,14 +3098,14 @@ I = TypeVar("I", bound=int) def dec4_bound(f: Callable[[I], List[T]]) -> Callable[[I], T]: ... -reveal_type(dec1(lambda x: x)) # N: Revealed type is "def [T] (T`3) -> builtins.list[T`3]" -reveal_type(dec2(lambda x: x)) # N: Revealed type is "def [S] (S`5) -> builtins.list[S`5]" -reveal_type(dec3(lambda x: x[0])) # N: Revealed type is "def [S] (S`8) -> S`8" -reveal_type(dec4(lambda x: [x])) # N: Revealed type is "def [S] (S`11) -> S`11" +reveal_type(dec1(lambda x: x)) # N: Revealed type is "def [T] (T`1) -> builtins.list[T`1]" +reveal_type(dec2(lambda x: x)) # N: Revealed type is "def [S] (S`3) -> builtins.list[S`3]" +reveal_type(dec3(lambda x: x[0])) # N: Revealed type is "def [S] (S`6) -> S`6" +reveal_type(dec4(lambda x: [x])) # N: Revealed type is "def [S] (S`9) -> S`9" reveal_type(dec1(lambda x: 1)) # N: Revealed type is "def (builtins.int) -> builtins.list[builtins.int]" reveal_type(dec5(lambda x: x)) # N: Revealed type is "def (builtins.int) -> builtins.list[builtins.int]" -reveal_type(dec3(lambda x: x)) # N: Revealed type is "def [S] (S`19) -> builtins.list[S`19]" -reveal_type(dec4(lambda x: x)) # N: Revealed type is "def [T] (builtins.list[T`23]) -> T`23" +reveal_type(dec3(lambda x: x)) # N: Revealed type is "def [S] (S`17) -> builtins.list[S`17]" +reveal_type(dec4(lambda x: x)) # N: Revealed type is "def [T] (builtins.list[T`21]) -> T`21" dec4_bound(lambda x: x) # E: Value of type variable "I" of "dec4_bound" cannot be "list[T]" [builtins fixtures/list.pyi] diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test index 0db89c0abd23..cb66c05fb5c8 100644 --- a/test-data/unit/check-incremental.test +++ b/test-data/unit/check-incremental.test @@ -6639,7 +6639,7 @@ class C: ... [out2] [out3] -[case testNoCrashOnPartialLambdaInference] +[case testNoCrashOnPartialLambdaInference_no_parallel] # flags: --no-local-partial-types import m [file m.py] @@ -6666,6 +6666,37 @@ reveal_type(xs) [out2] tmp/m.py:9: note: Revealed type is "builtins.list[builtins.int]" +[case testNoCrashOnPartialLambdaInference_parallel_only] +# flags: --local-partial-types +import m +[file m.py] +from typing import TypeVar, Callable + +V = TypeVar("V") +def apply(val: V, func: Callable[[V], None]) -> None: + return func(val) + +xs = [] +apply(0, lambda a: xs.append(a)) +[file m.py.2] +from typing import TypeVar, Callable + +V = TypeVar("V") +def apply(val: V, func: Callable[[V], None]) -> None: + return func(val) + +xs = [] +apply(0, lambda a: xs.append(a)) +reveal_type(xs) +[builtins fixtures/list.pyi] +[out] +tmp/m.py:7: error: Need type annotation for "xs" (hint: "xs: list[] = ...") +tmp/m.py:8: error: Cannot determine type of "xs" +[out2] +tmp/m.py:7: error: Need type annotation for "xs" (hint: "xs: list[] = ...") +tmp/m.py:8: error: Cannot determine type of "xs" +tmp/m.py:9: note: Revealed type is "builtins.list[Any]" + [case testTypingSelfCoarse] import m [file lib.py] @@ -8220,3 +8251,27 @@ y = 2 [stale] [out2] [out3] + +[case testNoLocalPartialTypesInlineRespectedIncremental_no_parallel] +import a +[file a.py] +# mypy: no-local-partial-types +import dep + +items = [] + +def add() -> None: + items.append(1) + +reveal_type(items) +[file dep.py] +value: int = 1 +[file dep.py.2] +value: str = "x" +[builtins fixtures/list.pyi] +[rechecked a, dep] +[stale dep] +[out] +tmp/a.py:9: note: Revealed type is "builtins.list[builtins.int]" +[out2] +tmp/a.py:9: note: Revealed type is "builtins.list[builtins.int]" diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index a5b3ae7238a5..88de271dfa12 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -1925,7 +1925,6 @@ reveal_type(C().a) # N: Revealed type is "builtins.dict[Any, Any]" [builtins fixtures/dict.pyi] [case testInferAttributeInitializedToNoneAndAssignedClassBody] -# flags: --no-local-partial-types class C: a = None def __init__(self) -> None: @@ -2099,7 +2098,6 @@ x = 1 [out] [case testPartiallyInitializedVariableDoesNotEscapeScope2] -# flags: --no-local-partial-types x = None def f() -> None: x = None @@ -2127,22 +2125,6 @@ main:6: error: Incompatible types in assignment (expression has type "int", vari main:7: error: "None" not callable [case testGlobalInitializedToNoneSetFromFunction] -# flags: --no-local-partial-types -a = None -def f() -> None: - global a - a = 42 - reveal_type(a) # N: Revealed type is "builtins.int" -reveal_type(a) # N: Revealed type is "builtins.int | None" - -b = None -def unchecked(): - global b - b = 42 -reveal_type(b) # N: Revealed type is "Any | None" - -[case testGlobalInitializedToNoneSetFromFunctionLocalPartialTypes] -# flags: --local-partial-types a = None def f() -> None: global a @@ -2157,23 +2139,6 @@ def unchecked(): reveal_type(b) # N: Revealed type is "Any | None" [case testGlobalInitializedToNoneSetFromMethod] -# flags: --no-local-partial-types -a = None -class C: - def m(self) -> None: - global a - a = 42 -reveal_type(a) # N: Revealed type is "builtins.int | None" - -b = None -class CC: - def unchecked(self): - global b - b = 42 -reveal_type(b) # N: Revealed type is "Any | None" - -[case testGlobalInitializedToNoneSetFromMethodLocalPartialTypes] -# flags: --local-partial-types a = None class C: def m(self) -> None: @@ -2189,7 +2154,6 @@ class CC: reveal_type(b) # N: Revealed type is "Any | None" [case testPartialTypeErrorSpecialCase1] -# flags: --no-local-partial-types # This used to crash. class A: x = None @@ -2208,7 +2172,6 @@ class A: [builtins fixtures/for.pyi] [case testPartialTypeErrorSpecialCase3] -# flags: --no-local-partial-types class A: x = None def f(self) -> None: @@ -2561,7 +2524,6 @@ main:4: error: Unsupported target for indexed assignment ("type[C[T]]") main:4: error: Invalid type: try using Literal[0] instead? [case testNoCrashOnPartialMember] -# flags: --no-local-partial-types class C: x = None def __init__(self) -> None: @@ -2580,7 +2542,6 @@ reveal_type(x) # N: Revealed type is "builtins.str" [builtins fixtures/tuple.pyi] [case testNoCrashOnPartialVariable2] -# flags: --no-local-partial-types from typing import Tuple, TypeVar T = TypeVar('T', bound=str) @@ -2782,32 +2743,7 @@ x = '' # E: Incompatible types in assignment (expression has type "str", variab def g() -> None: reveal_type(x) # N: Revealed type is "builtins.int | None" --- TODO: combine 4 tests below back into 2 when possible. -[case testLocalPartialTypesWithGlobalInitializedToNone4_no_parallel] -# flags: --local-partial-types --no-strict-optional -a = None - -def f() -> None: - reveal_type(a) # N: Revealed type is "None" - -reveal_type(a) # N: Revealed type is "None" -a = '' -reveal_type(a) # N: Revealed type is "builtins.str" -[builtins fixtures/list.pyi] - -[case testLocalPartialTypesWithGlobalInitializedToNone5_no_parallel] -# flags: --local-partial-types -a = None - -def f() -> None: - reveal_type(a) # N: Revealed type is "None" - -reveal_type(a) # N: Revealed type is "None" -a = '' -reveal_type(a) # N: Revealed type is "builtins.str" -[builtins fixtures/list.pyi] - -[case testLocalPartialTypesWithGlobalInitializedToNone4_parallel_only] +[case testLocalPartialTypesWithGlobalInitializedToNone4] # flags: --local-partial-types --no-strict-optional a = None @@ -2819,7 +2755,7 @@ a = '' reveal_type(a) # N: Revealed type is "builtins.str" [builtins fixtures/list.pyi] -[case testLocalPartialTypesWithGlobalInitializedToNone5_parallel_only] +[case testLocalPartialTypesWithGlobalInitializedToNone5] # flags: --local-partial-types a = None @@ -4388,3 +4324,26 @@ x = [] # E: Need type annotation for "x" (hint: "x: list[] = ...") def f() -> None: global x x + +[case testPerFileNoLocalPartialTypesForcesSinglePhase_no_parallel] +# mypy: no-local-partial-types + +x = [] + +def func() -> None: + x.append(1) + +reveal_type(x) # N: Revealed type is "builtins.list[builtins.int]" +[builtins fixtures/list.pyi] + +[case testPerFileNoLocalPartialTypesForcesSinglePhase_parallel_only] +# mypy: no-local-partial-types + +x = [] + +def func() -> None: + x.append(1) +[builtins fixtures/list.pyi] +[out] +main:1: error: --local-partial-types must be enabled in parallel mode +main:3: error: Need type annotation for "x" (hint: "x: list[] = ...") diff --git a/test-data/unit/check-plugin-attrs.test b/test-data/unit/check-plugin-attrs.test index 5e6dd4d83ce0..7d453dcf84d0 100644 --- a/test-data/unit/check-plugin-attrs.test +++ b/test-data/unit/check-plugin-attrs.test @@ -990,10 +990,10 @@ class C(A, B): pass @attr.s class D(A): pass -reveal_type(A.__lt__) # N: Revealed type is "def [_AT] (self: _AT`29, other: _AT`29) -> builtins.bool" -reveal_type(B.__lt__) # N: Revealed type is "def [_AT] (self: _AT`30, other: _AT`30) -> builtins.bool" -reveal_type(C.__lt__) # N: Revealed type is "def [_AT] (self: _AT`31, other: _AT`31) -> builtins.bool" -reveal_type(D.__lt__) # N: Revealed type is "def [_AT] (self: _AT`32, other: _AT`32) -> builtins.bool" +reveal_type(A.__lt__) # N: Revealed type is "def [_AT] (self: _AT`5, other: _AT`5) -> builtins.bool" +reveal_type(B.__lt__) # N: Revealed type is "def [_AT] (self: _AT`6, other: _AT`6) -> builtins.bool" +reveal_type(C.__lt__) # N: Revealed type is "def [_AT] (self: _AT`7, other: _AT`7) -> builtins.bool" +reveal_type(D.__lt__) # N: Revealed type is "def [_AT] (self: _AT`8, other: _AT`8) -> builtins.bool" A() < A() B() < B() diff --git a/test-data/unit/check-protocols.test b/test-data/unit/check-protocols.test index 7d1eec6cc9cb..f9adfa51688d 100644 --- a/test-data/unit/check-protocols.test +++ b/test-data/unit/check-protocols.test @@ -2920,7 +2920,6 @@ hs(None) [case testPartialTypeProtocol] -# flags: --no-local-partial-types from typing import Protocol class Flapper(Protocol): @@ -2941,25 +2940,7 @@ class Gleemer: [case testPartialTypeProtocolHashable] -# flags: --no-strict-optional --no-local-partial-types -from typing import Protocol - -class Hashable(Protocol): - def __hash__(self) -> int: ... - -class ObjectHashable: - def __hash__(self) -> int: ... - -class DataArray(ObjectHashable): - __hash__ = None - - def f(self, x: Hashable) -> None: - reveal_type([self, x]) # N: Revealed type is "builtins.list[builtins.object]" -[builtins fixtures/tuple.pyi] - - -[case testPartialTypeProtocolHashableLocalPartialTypes] -# flags: --no-strict-optional --local-partial-types +# flags: --no-strict-optional from typing import Protocol class Hashable(Protocol): @@ -2977,7 +2958,7 @@ class DataArray(ObjectHashable): [case testPartialAttributeNoneType] -# flags: --no-strict-optional --no-local-partial-types +# flags: --no-strict-optional from typing import Optional, Protocol, runtime_checkable @runtime_checkable @@ -2995,7 +2976,6 @@ class MyClass: [case testPartialAttributeNoneTypeStrictOptional] -# flags: --no-local-partial-types from typing import Optional, Protocol, runtime_checkable @runtime_checkable diff --git a/test-data/unit/check-redefine2.test b/test-data/unit/check-redefine2.test index b7c4e17183c1..964a1d02db12 100644 --- a/test-data/unit/check-redefine2.test +++ b/test-data/unit/check-redefine2.test @@ -1160,7 +1160,7 @@ else: x = "" # E: Incompatible types in assignment (expression has type "str", variable has type "int") reveal_type(x) # N: Revealed type is "builtins.int" -[case testNewRedefineWithoutLocalPartialTypes] +[case testNewRedefineWithoutLocalPartialTypes_no_parallel] import a import b @@ -1179,6 +1179,26 @@ if int(): [out] tmp/b.py:1: error: --local-partial-types must be enabled if using --allow-redefinition +[case testNewRedefineWithoutLocalPartialTypes_parallel_only] +import a +import b + +[file a.py] +# mypy: local-partial-types, allow-redefinition +x = 0 +if int(): + x = "" + +[file b.py] +# mypy: local-partial-types=false, allow-redefinition +x = 0 +if int(): + x = "" + +[out] +tmp/b.py:1: error: --local-partial-types must be enabled if using --allow-redefinition +tmp/b.py:1: error: --local-partial-types must be enabled in parallel mode + [case testNewRedefineNestedLoopInfiniteExpansion] # flags: --allow-redefinition def a(): ... diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test index 34ce4595439f..6f73df5e05c8 100644 --- a/test-data/unit/check-selftype.test +++ b/test-data/unit/check-selftype.test @@ -2314,8 +2314,8 @@ class A: @classmethod def other_meth(cls) -> Self: - reveal_type(cls.meth) # N: Revealed type is "def [Self <: __main__.A] (self: Self`1) -> Self`1" - reveal_type(A.meth) # N: Revealed type is "def [Self <: __main__.A] (self: Self`2) -> Self`2" + reveal_type(cls.meth) # N: Revealed type is "def [Self <: __main__.A] (self: Self`2) -> Self`2" + reveal_type(A.meth) # N: Revealed type is "def [Self <: __main__.A] (self: Self`3) -> Self`3" return cls().meth() class B: diff --git a/test-data/unit/cmdline.test b/test-data/unit/cmdline.test index fe2b2f982cc8..9928f64acd11 100644 --- a/test-data/unit/cmdline.test +++ b/test-data/unit/cmdline.test @@ -1318,6 +1318,11 @@ pass error: Cache must be enabled in parallel mode == Return code: 2 +[case testCodeModeInParallelMode] +# cmd: mypy -c 'def foo() -> None: 42 + "no"' --num-workers=2 +[out] +:1: error: Unsupported operand types for + ("int" and "str") + [case testCheckingStubPackagesWorksInParallelMode] # cmd: mypy foo-stubs --num-workers=4 [file foo-stubs/__init__.pyi] @@ -1325,3 +1330,23 @@ error: Cache must be enabled in parallel mode from foo.api import bar as bar [file foo-stubs/api/bar.pyi] [out] + +[case testCacheInvalidatedForNoLocalPartialTypesParallelVsSequential] +# cmd: mypy a.py +# cmd2: mypy a.py --num-workers=2 +[file a.py] +# mypy: no-local-partial-types + +items = [] + +def add() -> None: + items.append(1) + +reveal_type(items) +[out] +a.py:8: note: Revealed type is "list[int]" +== Return code: 0 +[out2] +a.py:1: error: --local-partial-types must be enabled in parallel mode +a.py:3: error: Need type annotation for "items" (hint: "items: list[] = ...") +a.py:8: note: Revealed type is "list[Any]" diff --git a/test-data/unit/exportjson.test b/test-data/unit/exportjson.test index 2d6a8c56b20f..f1f977c6ea2c 100644 --- a/test-data/unit/exportjson.test +++ b/test-data/unit/exportjson.test @@ -298,6 +298,8 @@ from typing_extensions import Final ], "suppressed": [], "options": { + "is_parallel": false, + "local_partial_types": true, "other_options": "", "platform": ... },