Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Some more work
  • Loading branch information
ilevkivskyi committed Mar 1, 2026
commit 9e760bf0a22d96c5aca09306cf7aeab46873a10d
51 changes: 28 additions & 23 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -719,10 +719,25 @@ def accept_loop(
# Definitions
#

@contextmanager
def set_recurse_into_functions(self) -> Iterator[None]:
"""Temporarily set recurse_into_functions to True.

This is used to process top-level functions/methods as a whole.
"""
old_recurse_into_functions = self.recurse_into_functions
self.recurse_into_functions = True
try:
yield
finally:
self.recurse_into_functions = old_recurse_into_functions

def visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None:
if not self.recurse_into_functions and not defn.can_infer_self_attr:
# If a function/method can infer variable types, it should be processed as part
# of the module top level (i.e. module interface).
if not self.recurse_into_functions and not defn.can_infer_vars:
return
with self.tscope.function_scope(defn):
with self.tscope.function_scope(defn), self.set_recurse_into_functions():
self._visit_overloaded_func_def(defn)

def _visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None:
Expand Down Expand Up @@ -1196,9 +1211,9 @@ def get_generator_return_type(self, return_type: Type, is_coroutine: bool) -> Ty
return NoneType()

def visit_func_def(self, defn: FuncDef) -> None:
if not self.recurse_into_functions and not defn.can_infer_self_attr:
if not self.recurse_into_functions and not defn.can_infer_vars:
return
with self.tscope.function_scope(defn):
with self.tscope.function_scope(defn), self.set_recurse_into_functions():
self.check_func_item(defn, name=defn.name)
if not self.can_skip_diagnostics:
if defn.info:
Expand Down Expand Up @@ -1438,7 +1453,7 @@ def check_func_def(
or self.options.preserve_asts
or not isinstance(defn, FuncDef)
or defn.has_self_attr_def
or defn.can_infer_self_attr
or defn.can_infer_vars
):
self.accept(item.body)
unreachable = self.binder.is_unreachable()
Expand Down Expand Up @@ -5605,8 +5620,8 @@ def visit_decorator(self, e: Decorator) -> None:
def visit_decorator_inner(
self, e: Decorator, allow_empty: bool = False, skip_first_item: bool = False
) -> None:
if self.recurse_into_functions or e.func.can_infer_self_attr:
with self.tscope.function_scope(e.func):
if self.recurse_into_functions or e.func.can_infer_vars:
with self.tscope.function_scope(e.func), self.set_recurse_into_functions():
self.check_func_item(e.func, name=e.func.name, allow_empty=allow_empty)

# Process decorators from the inside out to determine decorated signature, which
Expand Down Expand Up @@ -7712,19 +7727,6 @@ def enter_partial_types(
partial_types, _, _ = self.partial_types.pop()
if not self.current_node_deferred:
for var, context in partial_types.items():
# If we require local partial types, there are a few exceptions where
# we fall back to inferring just "None" as the type from a None initializer:
#
# 1. If all happens within a single function this is acceptable, since only
# the topmost function is a separate target in fine-grained incremental mode.
# We primarily want to avoid "splitting" partial types across targets.
#
# 2. A None initializer in the class body if the attribute is defined in a base
# class is fine, since the attribute is already defined and it's currently okay
# to vary the type of an attribute covariantly. The None type will still be
# checked for compatibility with base classes elsewhere. Without this exception
# mypy could require an annotation for an attribute that already has been
# declared in a base class, which would be bad.
if isinstance(var.type, PartialType) and var.type.type is None and not permissive:
var.type = NoneType()
else:
Expand Down Expand Up @@ -7796,11 +7798,14 @@ def find_partial_types_in_all_scopes(
# for fine-grained incremental mode).
disallow_other_scopes = self.options.local_partial_types

# There are two exceptions:
if isinstance(var.type, PartialType) and var.type.type is not None and var.info:
# This is an ugly hack to make partial generic self attributes behave
# as if --local-partial-types is always on (because it used to be like this).
# We always prohibit non-None partial types at class scope
# for historical reasons.
disallow_other_scopes = True
if isinstance(var.type, PartialType) and var.type.type is None and var.info:
if isinstance(var.type, PartialType) and var.type.type is None:
# We always allow None partial types, since this is a common use case.
# It is special-cased in fine-grained incremental mode.
disallow_other_scopes = False

scope_active = (
Expand Down
15 changes: 13 additions & 2 deletions mypy/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -589,7 +589,7 @@ class FuncBase(Node):
"is_final", # Uses "@final"
"is_explicit_override", # Uses "@override"
"is_type_check_only", # Uses "@type_check_only"
"can_infer_self_attr",
"can_infer_vars",
"_fullname",
)

Expand All @@ -610,7 +610,18 @@ def __init__(self) -> None:
self.is_final = False
self.is_explicit_override = False
self.is_type_check_only = False
self.can_infer_self_attr = False
# Can this function/method infer types of variables defined outside? Currently,
# we only set this in cases like:
# x = None
# def foo() -> None:
# global x
# x = 1
# and
# class C:
# x = None
# def foo(self) -> None:
# self.x = 1
self.can_infer_vars = False
# Name with module prefix
self._fullname = ""

Expand Down
13 changes: 12 additions & 1 deletion mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -4573,6 +4573,17 @@ def make_name_lvalue_point_to_existing_def(
self.name_not_defined(lval.name, lval)
self.check_lvalue_validity(lval.node, lval)

if self.scope.functions and lval.name in self.global_decls[-1]:
# Technically, we only need to set this if original r.h.s. would be inferred
# as None, but it is tricky to detect reliably during semantic analysis.
if (
original_def
and isinstance(original_def.node, Var)
and original_def.node.is_inferred
):
for func in self.scope.functions:
func.can_infer_vars = True

def analyze_tuple_or_list_lvalue(self, lval: TupleExpr, explicit_type: bool = False) -> None:
"""Analyze an lvalue or assignment target that is a list or tuple."""
items = lval.items
Expand Down Expand Up @@ -4665,7 +4676,7 @@ def analyze_member_lvalue(
and cur_node.node.is_initialized_in_class
):
for func in self.scope.functions:
func.can_infer_self_attr = True
func.can_infer_vars = True
self.check_lvalue_validity(lval.node, lval)

def is_self_member_ref(self, memberexpr: MemberExpr) -> bool:
Expand Down
15 changes: 15 additions & 0 deletions mypy/server/update.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@
from mypy.modulefinder import BuildSource
from mypy.nodes import (
Decorator,
FuncBase,
FuncDef,
ImportFrom,
MypyFile,
Expand Down Expand Up @@ -953,6 +954,20 @@ def find_targets_recursive(
deferred, stale_proto = lookup_target(manager, target)
if stale_proto:
stale_protos.add(stale_proto)

# If there are function targets that can infer outer variables, they should
# be re-processed as part of the module top-level instead (for consistency).
regular = []
shared = []
for d in deferred:
if isinstance(d.node, FuncBase) and d.node.can_infer_vars:
shared.append(d)
else:
regular.append(d)
deferred = regular
if shared:
deferred.append(FineGrainedDeferredNode(manager.modules[module_id], None))

result[module_id].update(deferred)

return result, unloaded_files, stale_protos
Expand Down
77 changes: 40 additions & 37 deletions test-data/unit/check-inference.test
Original file line number Diff line number Diff line change
Expand Up @@ -2141,18 +2141,18 @@ reveal_type(b) # N: Revealed type is "Any | None"

[case testGlobalInitializedToNoneSetFromFunctionLocalPartialTypes]
# flags: --local-partial-types
a = None # E: Need type annotation for "a" (hint: "a: <type> | None = ...")
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 "None"
reveal_type(a) # N: Revealed type is "builtins.int | None"

b = None # E: Need type annotation for "b" (hint: "b: <type> | None = ...")
b = None
def unchecked():
global b
b = 42
reveal_type(b) # N: Revealed type is "None"
reveal_type(b) # N: Revealed type is "Any | None"

[case testGlobalInitializedToNoneSetFromMethod]
# flags: --no-local-partial-types
Expand All @@ -2172,22 +2172,19 @@ reveal_type(b) # N: Revealed type is "Any | None"

[case testGlobalInitializedToNoneSetFromMethodLocalPartialTypes]
# flags: --local-partial-types
a = None # E: Need type annotation for "a" (hint: "a: <type> | None = ...")
a = None
class C:
def m(self) -> None:
global a
a = 42
reveal_type(a) # N: Revealed type is "None"
reveal_type(a) # N: Revealed type is "builtins.int | None"

b = None # E: Need type annotation for "b" (hint: "b: <type> | None = ...")
b = None
class CC:
def unchecked(self):
global b
b = 42
reveal_type(b) # N: Revealed type is "None"

-- More partial type errors
-- ------------------------
reveal_type(b) # N: Revealed type is "Any | None"

[case testPartialTypeErrorSpecialCase1]
# flags: --no-local-partial-types
Expand Down Expand Up @@ -2745,58 +2742,66 @@ if bool():

[case testLocalPartialTypesWithGlobalInitializedToNone]
# flags: --local-partial-types
x = None # E: Need type annotation for "x" (hint: "x: <type> | None = ...")
x = None

def f() -> None:
global x
x = 1

# TODO: "Any" could be a better type here to avoid multiple error messages
reveal_type(x) # N: Revealed type is "None"
reveal_type(x) # N: Revealed type is "builtins.int | None"

[case testLocalPartialTypesWithGlobalInitializedToNone2]
# flags: --local-partial-types
x = None # E: Need type annotation for "x" (hint: "x: <type> | None = ...")
x = None

def f():
global x
x = 1

# TODO: "Any" could be a better type here to avoid multiple error messages
reveal_type(x) # N: Revealed type is "None"
reveal_type(x) # N: Revealed type is "Any | None"

[case testLocalPartialTypesWithGlobalInitializedToNone3]
# flags: --local-partial-types --no-strict-optional
x = None

def f() -> None:
global x
x = 1 # E: Incompatible types in assignment (expression has type "int", variable has type "str")
x = 1

x = ''
reveal_type(x) # N: Revealed type is "builtins.str"
x = '' # E: Incompatible types in assignment (expression has type "str", variable has type "int")
reveal_type(x) # N: Revealed type is "builtins.int"

[case testLocalPartialTypesWithGlobalInitializedToNoneStrictOptional]
# flags: --local-partial-types
x = None

def f() -> None:
global x
x = 1 # E: Incompatible types in assignment (expression has type "int", variable has type "str | None")
x = 1

x = ''
x = '' # E: Incompatible types in assignment (expression has type "str", variable has type "int | None")
def g() -> None:
reveal_type(x) # N: Revealed type is "builtins.str | None"
reveal_type(x) # N: Revealed type is "builtins.int | None"

[case testLocalPartialTypesWithGlobalInitializedToNone4]
# flags: --local-partial-types --no-strict-optional
a = None

def f() -> None:
reveal_type(a) # N: Revealed type is "builtins.str"
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]
# flags: --local-partial-types
a = None

def f() -> None:
reveal_type(a) # N: Revealed type is "None"

# TODO: This should probably be 'builtins.str', since there could be a
# call that causes a non-None value to be assigned
reveal_type(a) # N: Revealed type is "None"
a = ''
reveal_type(a) # N: Revealed type is "builtins.str"
Expand All @@ -2805,7 +2810,7 @@ reveal_type(a) # N: Revealed type is "builtins.str"
[case testLocalPartialTypesWithClassAttributeInitializedToNone]
# flags: --local-partial-types
class A:
x = None # E: Need type annotation for "x" (hint: "x: <type> | None = ...")
x = None

def f(self) -> None:
self.x = 1
Expand Down Expand Up @@ -2988,7 +2993,7 @@ from typing import List
def f(x): pass

class A:
x = None # E: Need type annotation for "x" (hint: "x: <type> | None = ...")
x = None

def f(self, p: List[str]) -> None:
self.x = f(p)
Expand All @@ -2998,21 +3003,18 @@ class A:
[case testLocalPartialTypesAccessPartialNoneAttribute]
# flags: --local-partial-types
class C:
a = None # E: Need type annotation for "a" (hint: "a: <type> | None = ...")
a = None

def f(self, x) -> None:
C.a.y # E: Item "None" of "Any | None" has no attribute "y"
C.a.y # E: "None" has no attribute "y"

[case testLocalPartialTypesAccessPartialNoneAttribute2]
# flags: --local-partial-types
class C:
a = None # E: Need type annotation for "a" (hint: "a: <type> | None = ...")
a = None

def f(self, x) -> None:
self.a.y # E: Item "None" of "Any | None" has no attribute "y"

-- Special case for assignment to '_'
-- ----------------------------------
self.a.y # E: "None" has no attribute "y"

[case testUnusedTargetLocal]
def foo() -> None:
Expand Down Expand Up @@ -3596,9 +3598,10 @@ if x:
reveal_type(x) # N: Revealed type is "builtins.bytes"
[builtins fixtures/dict.pyi]

[case testSuggestPep604AnnotationForPartialNone]
[case testAllowPlainNoneForPartialAtModuleScope]
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a test where multiple functions assign to a global initialize to None? It would be good to have this with and without --allow-redefinition-new.

Test that a nested function has global x / x = something (if it's not already covered, I think the self.x case is covered).

# flags: --local-partial-types --python-version 3.10
x = None # E: Need type annotation for "x" (hint: "x: <type> | None = ...")
x = None
reveal_type(x) # N: Revealed type is "None"

[case testTupleContextFromIterable]
from typing import TypeVar, Iterable, List, Union
Expand Down
Loading