diff --git a/mypy/build.py b/mypy/build.py index 1357047d78a0..0c377971bcbd 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -2554,6 +2554,7 @@ def type_checker(self) -> TypeChecker: self.xpath, manager.plugin, self.per_line_checking_time_ns, + manager.semantic_analyzer.delayed_errors, ) return self._type_checker diff --git a/mypy/checker.py b/mypy/checker.py index f90fc4be41f4..9be64fcb54a9 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -409,6 +409,7 @@ def __init__( path: str, plugin: Plugin, per_line_checking_time_ns: dict[int, int], + semanal_delayed_errors: dict[tuple[str, int, int], list[ErrorInfo]], ) -> None: """Construct a type checker. @@ -442,6 +443,7 @@ def __init__( self.inferred_attribute_types = None self.allow_constructor_cache = True self.local_type_map = LocalTypeMap(self) + self.semanal_delayed_errors = semanal_delayed_errors # If True, process function definitions. If False, don't. This is used # for processing module top levels in fine-grained incremental mode. @@ -527,7 +529,7 @@ def check_first_pass(self) -> None: self.msg.unreachable_statement(d) break else: - self.accept(d) + self.accept_with_delayed_errors(d) assert not self.current_node_deferred @@ -635,6 +637,15 @@ def handle_cannot_determine_type(self, name: str, context: Context) -> None: else: self.msg.cannot_determine_type(name, context) + def accept_with_delayed_errors(self, stmt: Statement) -> None: + curr_module = self.scope.stack[0] + if isinstance(curr_module, MypyFile): + key = (curr_module.fullname, stmt.line, stmt.column) + if key in self.semanal_delayed_errors: + self.msg.add_errors(self.semanal_delayed_errors[key]) + + self.accept(stmt) + def accept(self, stmt: Statement) -> None: """Type check a node in the given type context.""" try: @@ -3156,7 +3167,7 @@ def visit_block(self, b: Block) -> None: self.msg.unreachable_statement(s) break else: - self.accept(s) + self.accept_with_delayed_errors(s) # Clear expression cache after each statement to avoid unlimited growth. self.expr_checker.expr_cache.clear() diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 9990caaeb7a1..989347503366 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -5495,8 +5495,9 @@ def visit_lambda_expr(self, e: LambdaExpr) -> Type: # Lambdas can have more than one element in body, # when we add "fictional" AssignmentStatement nodes, like in: # `lambda (a, b): a` - for stmt in e.body.body[:-1]: - stmt.accept(self.chk) + with self.chk.binder.frame_context(can_skip=True, fall_through=0): + self.chk.accept(e.body) + # Only type check the return expression, not the return statement. # There's no useful type context. ret_type = self.accept(e.expr(), allow_none_return=True) diff --git a/mypy/semanal.py b/mypy/semanal.py index adbd32ad51b1..7e960e2c9f0e 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -59,7 +59,7 @@ from mypy import errorcodes as codes, message_registry from mypy.constant_fold import constant_fold_expr from mypy.errorcodes import PROPERTY_DECORATOR, ErrorCode -from mypy.errors import Errors, report_internal_error +from mypy.errors import ErrorInfo, Errors, report_internal_error from mypy.exprtotype import TypeTranslationError, expr_to_unanalyzed_type from mypy.message_registry import ErrorMessage from mypy.messages import ( @@ -546,6 +546,9 @@ def __init__( # import foo.bar self.transitive_submodule_imports: dict[str, set[str]] = {} + self.delayed_errors: dict[tuple[str, int, int], list[ErrorInfo]] = {} + self.associated_node: Statement | None = None + # mypyc doesn't properly handle implementing an abstractproperty # with a regular attribute so we make them properties @property @@ -716,7 +719,7 @@ def refresh_top_level(self, file_node: MypyFile) -> None: self.recurse_into_functions = False self.add_implicit_module_attrs(file_node) for d in file_node.defs: - self.accept(d) + self.accept_delaying_errors(d) if file_node.fullname == "typing": self.add_builtin_aliases(file_node) if file_node.fullname == "typing_extensions": @@ -5383,7 +5386,7 @@ def visit_block(self, b: Block) -> None: return self.block_depth[-1] += 1 for s in b.body: - self.accept(s) + self.accept_delaying_errors(s) self.block_depth[-1] -= 1 def visit_block_maybe(self, b: Block | None) -> None: @@ -7093,6 +7096,7 @@ def _get_node_for_class_scoped_import( ) -> SymbolNode | None: if symbol_node is None: return None + # TODO: remove supposedly unnecessary `f` # I promise this type checks; I'm just making mypyc issues go away. # mypyc is absolutely convinced that `symbol_node` narrows to a Var in the following, # when it can also be a FuncBase. Once fixed, `f` in the following can be removed. @@ -7551,7 +7555,7 @@ def fail( if code is None: code = msg.code msg = msg.value - self.errors.report( + err_info = self.create_error_info( ctx.line, ctx.column, msg, @@ -7560,11 +7564,86 @@ def fail( end_line=ctx.end_line, end_column=ctx.end_column, ) + if self.errors._filter_error(self.errors.file, err_info): + return + + if blocker or self.associated_node is None or self.options.semantic_analysis_only: + self.errors.add_error_info(err_info) + else: + node = self.associated_node + assign_to = (self.cur_mod_id, node.line, node.column) + self.delayed_errors.setdefault(assign_to, []) + self.delayed_errors[assign_to].append(err_info) def note(self, msg: str, ctx: Context, code: ErrorCode | None = None) -> None: if not self.in_checked_function(): return - self.errors.report(ctx.line, ctx.column, msg, severity="note", code=code) + err_info = self.create_error_info(ctx.line, ctx.column, msg, severity="note", code=code) + if self.errors._filter_error(self.errors.file, err_info): + return + + if self.associated_node is None or self.options.semantic_analysis_only: + self.errors.add_error_info(err_info) + else: + node = self.associated_node + assign_to = (self.cur_mod_id, node.line, node.column) + self.delayed_errors.setdefault(assign_to, []) + self.delayed_errors[assign_to].append(err_info) + + def create_error_info( + self, + line: int, + column: int | None, + message: str, + code: ErrorCode | None = None, + *, + blocker: bool = False, + severity: str = "error", + end_line: int | None = None, + end_column: int | None = None, + ) -> ErrorInfo: + # TODO: move this into `errors.py`, probably + if self.errors.scope: + type = self.errors.scope.current_type_name() + if self.errors.scope.ignored > 0: + type = None # Omit type context if nested function + function = self.errors.scope.current_function_name() + else: + type = None + function = None + + if column is None: + column = -1 + if end_column is None: + if column == -1: + end_column = -1 + else: + end_column = column + 1 + + if end_line is None: + end_line = line + + code = code or (codes.MISC if not blocker else None) + + return ErrorInfo( + import_ctx=self.errors.import_context(), + file=self.errors.file, + module=self.errors.current_module(), + typ=type, + function_or_member=function, + line=line, + column=column, + end_line=end_line, + end_column=end_column, + severity=severity, + message=message, + code=code, + blocker=blocker, + only_once=False, + origin=(self.errors.file, [line]), + target=self.errors.current_target(), + parent_error=None, + ) def incomplete_feature_enabled(self, feature: str, ctx: Context) -> bool: if feature not in self.options.enable_incomplete_feature: @@ -7576,6 +7655,15 @@ def incomplete_feature_enabled(self, feature: str, ctx: Context) -> bool: return False return True + def accept_delaying_errors(self, node: Statement) -> None: + previously_associated = self.associated_node + self.associated_node = node + try: + node.accept(self) + except Exception as err: + report_internal_error(err, self.errors.file, node.line, self.errors, self.options) + self.associated_node = previously_associated + def accept(self, node: Node) -> None: try: node.accept(self) @@ -8067,6 +8155,8 @@ def isolated_error_analysis(self) -> Iterator[None]: original_deferral_debug_context_len = len(self.deferral_debug_context) self.errors = Errors(Options()) + previous_association = self.associated_node + self.associated_node = None try: yield finally: @@ -8075,6 +8165,7 @@ def isolated_error_analysis(self) -> Iterator[None]: self.num_incomplete_refs = original_num_incomplete_refs self.progress = original_progress self.deferred = original_deferred + self.associated_node = previous_association del self.deferral_debug_context[original_deferral_debug_context_len:] diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index 32975350e20a..f18f5560228e 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -3100,11 +3100,11 @@ 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(dec4(lambda x: [x])) # N: Revealed type is "def [S] (S`12) -> S`12" 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`20) -> builtins.list[S`20]" +reveal_type(dec4(lambda x: x)) # N: Revealed type is "def [T] (builtins.list[T`24]) -> T`24" 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-incomplete-fixture.test b/test-data/unit/check-incomplete-fixture.test index 146494df1bd6..0bf79ee7ae60 100644 --- a/test-data/unit/check-incomplete-fixture.test +++ b/test-data/unit/check-incomplete-fixture.test @@ -16,38 +16,38 @@ m.x # E: "object" has no attribute "x" from typing import Set def f(x: Set[int]) -> None: pass [out] -main:1: error: Module "typing" has no attribute "Set" main:1: note: Maybe your test fixture does not define "builtins.set"? main:1: note: Consider adding [builtins fixtures/set.pyi] to your test description +main:1: error: Module "typing" has no attribute "Set" [case testBaseExceptionMissingFromStubs] e: BaseException [out] -main:1: error: Name "BaseException" is not defined main:1: note: Maybe your test fixture does not define "builtins.BaseException"? main:1: note: Consider adding [builtins fixtures/exception.pyi] to your test description +main:1: error: Name "BaseException" is not defined [case testExceptionMissingFromStubs] e: Exception [out] -main:1: error: Name "Exception" is not defined main:1: note: Maybe your test fixture does not define "builtins.Exception"? main:1: note: Consider adding [builtins fixtures/exception.pyi] to your test description +main:1: error: Name "Exception" is not defined [case testIsinstanceMissingFromStubs] if isinstance(1, int): pass [out] -main:1: error: Name "isinstance" is not defined main:1: note: Maybe your test fixture does not define "builtins.isinstance"? main:1: note: Consider adding [builtins fixtures/isinstancelist.pyi] to your test description +main:1: error: Name "isinstance" is not defined [case testTupleMissingFromStubs1] tuple() [out] -main:1: error: Name "tuple" is not defined main:1: note: Maybe your test fixture does not define "builtins.tuple"? main:1: note: Consider adding [builtins fixtures/tuple.pyi] to your test description +main:1: error: Name "tuple" is not defined main:1: note: Did you forget to import it from "typing"? (Suggestion: "from typing import Tuple") [case testTupleMissingFromStubs2] @@ -55,9 +55,9 @@ tuple() from typing import Tuple x: Tuple[int, str] [out] -main:1: error: Name "tuple" is not defined main:1: note: Maybe your test fixture does not define "builtins.tuple"? main:1: note: Consider adding [builtins fixtures/tuple.pyi] to your test description +main:1: error: Name "tuple" is not defined main:1: note: Did you forget to import it from "typing"? (Suggestion: "from typing import Tuple") main:3: error: Name "tuple" is not defined @@ -66,15 +66,15 @@ class A: @classmethod def f(cls): pass [out] -main:2: error: Name "classmethod" is not defined main:2: note: Maybe your test fixture does not define "builtins.classmethod"? main:2: note: Consider adding [builtins fixtures/classmethod.pyi] to your test description +main:2: error: Name "classmethod" is not defined [case testPropertyMissingFromStubs] class A: @property def f(self): pass [out] -main:2: error: Name "property" is not defined main:2: note: Maybe your test fixture does not define "builtins.property"? main:2: note: Consider adding [builtins fixtures/property.pyi] to your test description +main:2: error: Name "property" is not defined diff --git a/test-data/unit/check-statements.test b/test-data/unit/check-statements.test index 880df82671d4..c149cf67bd94 100644 --- a/test-data/unit/check-statements.test +++ b/test-data/unit/check-statements.test @@ -2359,9 +2359,9 @@ from typing import _FutureFeatureFixture # that day comes this suggestion will also be less helpful than it is today. import typing_extensions [out] -main:1: error: Module "typing" has no attribute "_FutureFeatureFixture" main:1: note: Use `from typing_extensions import _FutureFeatureFixture` instead main:1: note: See https://mypy.readthedocs.io/en/stable/runtime_troubles.html#using-new-additions-to-the-typing-module +main:1: error: Module "typing" has no attribute "_FutureFeatureFixture" [builtins fixtures/tuple.pyi] [case testNoCrashOnBreakOutsideLoopFunction] diff --git a/test-data/unit/check-type-aliases.test b/test-data/unit/check-type-aliases.test index 0d2e6b5f0c9d..f5c0ef3a5303 100644 --- a/test-data/unit/check-type-aliases.test +++ b/test-data/unit/check-type-aliases.test @@ -1216,14 +1216,22 @@ reveal_type(x) # N: Revealed type is "builtins.dict[builtins.str, Any]" [builtins fixtures/dict.pyi] [typing fixtures/typing-full.pyi] -[case testTypeAliasTypeNoUnpackInTypeParams311] +[case testTypeAliasTypeNoUnpackInTypeParams1_311] # flags: --python-version 3.11 from typing_extensions import TypeAliasType, TypeVar, TypeVarTuple, Unpack -T = TypeVar("T") Ts = TypeVarTuple("Ts") +# note that the following is a blocker, so the assignment after isn't checked Ta1 = TypeAliasType("Ta1", None, type_params=(*Ts,)) # E: can't use starred expression here +[builtins fixtures/tuple.pyi] + +[case testTypeAliasTypeNoUnpackInTypeParams2_311] +# flags: --python-version 3.11 +from typing_extensions import TypeAliasType, TypeVar, TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") + Ta2 = TypeAliasType("Ta2", None, type_params=(Unpack[Ts],)) # E: Free type variable expected in type_params argument to TypeAliasType \ # N: Don't Unpack type variables in type_params diff --git a/test-data/unit/check-typevar-defaults.test b/test-data/unit/check-typevar-defaults.test index 2f9506b71bfb..401d202c00e2 100644 --- a/test-data/unit/check-typevar-defaults.test +++ b/test-data/unit/check-typevar-defaults.test @@ -930,8 +930,8 @@ reveal_type(C) # N: Revealed type is "def [T2 = Any] () -> __main__.C[T2`1 = An c: C reveal_type(c) # N: Revealed type is "__main__.C[Any]" -class D(Generic[T2, T1]): ... # E: Type variable T1 referenced in the default of T2 is unbound \ - # E: "T1" cannot appear after "T2" in type parameter list because it has no default type +class D(Generic[T2, T1]): ... # E: "T1" cannot appear after "T2" in type parameter list because it has no default type \ + # E: Type variable T1 referenced in the default of T2 is unbound reveal_type(D) # N: Revealed type is "def [T2 = Any, T1 = Any] () -> __main__.D[T2`1 = Any, T1`2 = Any]" d: D reveal_type(d) # N: Revealed type is "__main__.D[Any, Any]"