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
Next Next commit
Initial implementation
  • Loading branch information
ilevkivskyi committed Feb 28, 2026
commit f4ad60976f58614893108dd6106cb653a9d8780c
21 changes: 7 additions & 14 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -720,7 +720,7 @@ def accept_loop(
#

def visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None:
if not self.recurse_into_functions:
if not self.recurse_into_functions and not defn.can_infer_self_attr:
return
with self.tscope.function_scope(defn):
self._visit_overloaded_func_def(defn)
Expand Down Expand Up @@ -1196,7 +1196,7 @@ 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:
if not self.recurse_into_functions and not defn.can_infer_self_attr:
return
with self.tscope.function_scope(defn):
self.check_func_item(defn, name=defn.name)
Expand Down Expand Up @@ -1438,6 +1438,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
):
self.accept(item.body)
unreachable = self.binder.is_unreachable()
Expand Down Expand Up @@ -5604,7 +5605,7 @@ 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:
if self.recurse_into_functions or e.func.can_infer_self_attr:
with self.tscope.function_scope(e.func):
self.check_func_item(e.func, name=e.func.name, allow_empty=allow_empty)

Expand Down Expand Up @@ -7724,17 +7725,7 @@ def enter_partial_types(
# 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.
allow_none = (
not self.options.local_partial_types
or is_function
or (is_class and self.is_defined_in_base_class(var))
)
if (
allow_none
and isinstance(var.type, PartialType)
and var.type.type is None
and not permissive
):
if isinstance(var.type, PartialType) and var.type.type is None and not permissive:
var.type = NoneType()
else:
if var not in self.partial_reported and not permissive:
Expand Down Expand Up @@ -7809,6 +7800,8 @@ def find_partial_types_in_all_scopes(
# 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).
disallow_other_scopes = True
if isinstance(var.type, PartialType) and var.type.type is None and var.info:
disallow_other_scopes = False

scope_active = (
not disallow_other_scopes or scope.is_local == self.partial_types[-1].is_local
Expand Down
2 changes: 2 additions & 0 deletions mypy/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +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",
"_fullname",
)

Expand All @@ -609,6 +610,7 @@ 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
# Name with module prefix
self._fullname = ""

Expand Down
8 changes: 8 additions & 0 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -4658,6 +4658,14 @@ def analyze_member_lvalue(
for func in self.scope.functions:
if isinstance(func, FuncDef):
func.has_self_attr_def = True
if (
cur_node
and isinstance(cur_node.node, Var)
and cur_node.node.is_inferred
and cur_node.node.is_initialized_in_class
):
for func in self.scope.functions:
func.can_infer_self_attr = True
self.check_lvalue_validity(lval.node, lval)

def is_self_member_ref(self, memberexpr: MemberExpr) -> bool:
Expand Down
19 changes: 19 additions & 0 deletions test-data/unit/fine-grained.test
Original file line number Diff line number Diff line change
Expand Up @@ -11532,3 +11532,22 @@ def lol() -> None: pass
==
a.py:2: error: Unexpected keyword argument "uhhhh" for "lol"
a.py:2: note: "lol" defined in "b"

[case testPartialNoneTypeFineInClass]
from typing import Optional
from m import C
def foo(x: C) -> Optional[int]:
return x.x
[file m.py]
from n import f
class C:
x = None
def __init__(self, x: int) -> None:
self.x = f()
[file n.py]
def f() -> int: ...
[file n.py.2]
def f() -> str: ...
[out]
==
main:4: error: Incompatible return value type (got "str | None", expected "int | None")