Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions mypy/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
15 changes: 13 additions & 2 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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()

Expand Down
5 changes: 3 additions & 2 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
101 changes: 96 additions & 5 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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":
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand All @@ -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:
Expand All @@ -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)
Expand Down Expand Up @@ -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:
Expand All @@ -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:]


Expand Down
6 changes: 3 additions & 3 deletions test-data/unit/check-generics.test
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand Down
16 changes: 8 additions & 8 deletions test-data/unit/check-incomplete-fixture.test
Original file line number Diff line number Diff line change
Expand Up @@ -16,48 +16,48 @@ 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]
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

Expand All @@ -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
2 changes: 1 addition & 1 deletion test-data/unit/check-statements.test
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
12 changes: 10 additions & 2 deletions test-data/unit/check-type-aliases.test
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 2 additions & 2 deletions test-data/unit/check-typevar-defaults.test
Original file line number Diff line number Diff line change
Expand Up @@ -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]"
Expand Down
13 changes: 13 additions & 0 deletions test-data/unit/check-unreachable-code.test
Original file line number Diff line number Diff line change
Expand Up @@ -1650,3 +1650,16 @@ def x() -> None:
main:4: error: Statement is unreachable
if 5:
^~~~~

[case testUnreachableSemanalErrors]
# flags: --warn-unreachable
def f() -> None:
if True:
return

x = xyz # E: Statement is unreachable

if True:
assert False

x = xyz # E: Statement is unreachable
Loading