Skip to content

Commit

Permalink
Merge branch 'master' into improve-inspection-is_classmethod
Browse files Browse the repository at this point in the history
  • Loading branch information
ericmarkmartin authored Feb 22, 2025
2 parents da8e2fd + 256cf68 commit 7094d74
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 27 deletions.
47 changes: 26 additions & 21 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,22 +216,22 @@

T = TypeVar("T")

DEFAULT_LAST_PASS: Final = 1 # Pass numbers start at 0
DEFAULT_LAST_PASS: Final = 2 # Pass numbers start at 0

# Maximum length of fixed tuple types inferred when narrowing from variadic tuples.
MAX_PRECISE_TUPLE_SIZE: Final = 8

DeferredNodeType: _TypeAlias = Union[FuncDef, LambdaExpr, OverloadedFuncDef, Decorator]
DeferredNodeType: _TypeAlias = Union[FuncDef, OverloadedFuncDef, Decorator]
FineGrainedDeferredNodeType: _TypeAlias = Union[FuncDef, MypyFile, OverloadedFuncDef]


# A node which is postponed to be processed during the next pass.
# In normal mode one can defer functions and methods (also decorated and/or overloaded)
# and lambda expressions. Nested functions can't be deferred -- only top-level functions
# but not lambda expressions. Nested functions can't be deferred -- only top-level functions
# and methods of classes not defined within a function can be deferred.
class DeferredNode(NamedTuple):
node: DeferredNodeType
# And its TypeInfo (for semantic analysis self type handling
# And its TypeInfo (for semantic analysis self type handling)
active_typeinfo: TypeInfo | None


Expand Down Expand Up @@ -528,10 +528,7 @@ def check_partial(self, node: DeferredNodeType | FineGrainedDeferredNodeType) ->
else:
self.recurse_into_functions = True
with self.binder.top_frame_context():
if isinstance(node, LambdaExpr):
self.expr_checker.accept(node)
else:
self.accept(node)
self.accept(node)

def check_top_level(self, node: MypyFile) -> None:
"""Check only the top-level of a module, skipping function definitions."""
Expand All @@ -558,13 +555,13 @@ def defer_node(self, node: DeferredNodeType, enclosing_class: TypeInfo | None) -
self.deferred_nodes.append(DeferredNode(node, enclosing_class))

def handle_cannot_determine_type(self, name: str, context: Context) -> None:
node = self.scope.top_non_lambda_function()
node = self.scope.top_level_function()
if self.pass_num < self.last_pass and isinstance(node, FuncDef):
# Don't report an error yet. Just defer. Note that we don't defer
# lambdas because they are coupled to the surrounding function
# through the binder and the inferred type of the lambda, so it
# would get messy.
enclosing_class = self.scope.enclosing_class()
enclosing_class = self.scope.enclosing_class(node)
self.defer_node(node, enclosing_class)
# Set a marker so that we won't infer additional types in this
# function. Any inferred types could be bogus, because there's at
Expand Down Expand Up @@ -2156,7 +2153,14 @@ def check_method_override_for_base_with_name(
if self.pass_num < self.last_pass:
# If there are passes left, defer this node until next pass,
# otherwise try reconstructing the method type from available information.
self.defer_node(defn, defn.info)
# For consistency, defer an enclosing top-level function (if any).
top_level = self.scope.top_level_function()
if isinstance(top_level, FuncDef):
self.defer_node(top_level, self.scope.enclosing_class(top_level))
else:
# Specify enclosing class explicitly, as we check type override before
# entering e.g. decorators or overloads.
self.defer_node(defn, defn.info)
return True
elif isinstance(original_node, (FuncDef, OverloadedFuncDef)):
original_type = self.function_type(original_node)
Expand Down Expand Up @@ -4767,7 +4771,7 @@ def visit_return_stmt(self, s: ReturnStmt) -> None:
self.binder.unreachable()

def check_return_stmt(self, s: ReturnStmt) -> None:
defn = self.scope.top_function()
defn = self.scope.current_function()
if defn is not None:
if defn.is_generator:
return_type = self.get_generator_return_type(
Expand All @@ -4779,7 +4783,7 @@ def check_return_stmt(self, s: ReturnStmt) -> None:
return_type = self.return_types[-1]
return_type = get_proper_type(return_type)

is_lambda = isinstance(self.scope.top_function(), LambdaExpr)
is_lambda = isinstance(defn, LambdaExpr)
if isinstance(return_type, UninhabitedType):
# Avoid extra error messages for failed inference in lambdas
if not is_lambda and not return_type.ambiguous:
Expand Down Expand Up @@ -8554,14 +8558,15 @@ class CheckerScope:
def __init__(self, module: MypyFile) -> None:
self.stack = [module]

def top_function(self) -> FuncItem | None:
def current_function(self) -> FuncItem | None:
for e in reversed(self.stack):
if isinstance(e, FuncItem):
return e
return None

def top_non_lambda_function(self) -> FuncItem | None:
for e in reversed(self.stack):
def top_level_function(self) -> FuncItem | None:
"""Return top-level non-lambda function."""
for e in self.stack:
if isinstance(e, FuncItem) and not isinstance(e, LambdaExpr):
return e
return None
Expand All @@ -8571,11 +8576,11 @@ def active_class(self) -> TypeInfo | None:
return self.stack[-1]
return None

def enclosing_class(self) -> TypeInfo | None:
def enclosing_class(self, func: FuncItem | None = None) -> TypeInfo | None:
"""Is there a class *directly* enclosing this function?"""
top = self.top_function()
assert top, "This method must be called from inside a function"
index = self.stack.index(top)
func = func or self.current_function()
assert func, "This method must be called from inside a function"
index = self.stack.index(func)
assert index, "CheckerScope stack must always start with a module"
enclosing = self.stack[index - 1]
if isinstance(enclosing, TypeInfo):
Expand All @@ -8589,7 +8594,7 @@ def active_self_type(self) -> Instance | TupleType | None:
In particular, inside a function nested in method this returns None.
"""
info = self.active_class()
if not info and self.top_function():
if not info and self.current_function():
info = self.enclosing_class()
if info:
return fill_typevars(info)
Expand Down
4 changes: 2 additions & 2 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -5523,7 +5523,7 @@ def visit_super_expr(self, e: SuperExpr) -> Type:
if type_info in mro:
index = mro.index(type_info)
else:
method = self.chk.scope.top_function()
method = self.chk.scope.current_function()
# Mypy explicitly allows supertype upper bounds (and no upper bound at all)
# for annotating self-types. However, if such an annotation is used for
# checking super() we will still get an error. So to be consistent, we also
Expand Down Expand Up @@ -5598,7 +5598,7 @@ def _super_arg_types(self, e: SuperExpr) -> Type | tuple[Type, Type]:
type_type: ProperType = TypeType(current_type)

# Use the type of the self argument, in case it was annotated
method = self.chk.scope.top_function()
method = self.chk.scope.current_function()
assert method is not None
if method.arguments:
instance_type: Type = method.arguments[0].variable.type or current_type
Expand Down
4 changes: 2 additions & 2 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -3708,9 +3708,9 @@ def store_final_status(self, s: AssignmentStmt) -> None:
cur_node = self.type.names.get(lval.name, None)
if cur_node and isinstance(cur_node.node, Var) and cur_node.node.is_final:
assert self.function_stack
top_function = self.function_stack[-1]
current_function = self.function_stack[-1]
if (
top_function.name == "__init__"
current_function.name == "__init__"
and cur_node.node.final_unset_in_class
and not cur_node.node.final_set_in_init
and not (isinstance(s.rvalue, TempNode) and s.rvalue.no_rhs)
Expand Down
32 changes: 32 additions & 0 deletions test-data/unit/check-inference.test
Original file line number Diff line number Diff line change
Expand Up @@ -3913,3 +3913,35 @@ x = "abc"
for x in list[int]():
reveal_type(x) # N: Revealed type is "builtins.int"
reveal_type(x) # N: Revealed type is "Union[builtins.int, builtins.str]"

[case testNarrowInFunctionDefer]
from typing import Optional, Callable, TypeVar

def top() -> None:
x: Optional[int]
assert x is not None

def foo() -> None:
defer()
reveal_type(x) # N: Revealed type is "builtins.int"

T = TypeVar("T")
def deco(fn: Callable[[], T]) -> Callable[[], T]: ...

@deco
def defer() -> int: ...

[case testDeferMethodOfNestedClass]
from typing import Optional, Callable, TypeVar

class Out:
def meth(self) -> None:
class In:
def meth(self) -> None:
reveal_type(defer()) # N: Revealed type is "builtins.int"

T = TypeVar("T")
def deco(fn: Callable[[], T]) -> Callable[[], T]: ...

@deco
def defer() -> int: ...
4 changes: 2 additions & 2 deletions test-data/unit/check-modules.test
Original file line number Diff line number Diff line change
Expand Up @@ -1388,14 +1388,14 @@ import b
import b
class C:
def f1(self) -> None:
self.x2
reveal_type(self.x2)
def f2(self) -> None:
self.x2 = b.b
[file b.py]
import a
b = 1 + int()
[out]
tmp/a.py:4: error: Cannot determine type of "x2"
tmp/a.py:4: note: Revealed type is "builtins.int"

[case testErrorInPassTwo1]
import b
Expand Down

0 comments on commit 7094d74

Please sign in to comment.