Skip to content

Commit 977cfc5

Browse files
authored
Refactor: split long function in mypy.semanal (#6055)
This splits `SemanticAnalyzerPass2._visit_overloaded_func_def` into multiple shorter functions. No behavior is changed, and the original structure is mostly preserved. Also do some other minor refactorings, and update comments and docstrings.
1 parent 512ae20 commit 977cfc5

File tree

1 file changed

+116
-80
lines changed

1 file changed

+116
-80
lines changed

mypy/semanal.py

Lines changed: 116 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
YieldFromExpr, NamedTupleExpr, NonlocalDecl, SymbolNode,
5555
SetComprehension, DictionaryComprehension, TypeAlias, TypeAliasExpr,
5656
YieldExpr, ExecStmt, BackquoteExpr, ImportBase, AwaitExpr,
57-
IntExpr, FloatExpr, UnicodeExpr, TempNode, ImportedName,
57+
IntExpr, FloatExpr, UnicodeExpr, TempNode, ImportedName, OverloadPart,
5858
COVARIANT, CONTRAVARIANT, INVARIANT, UNBOUND_IMPORTED, LITERAL_YES, nongen_builtins,
5959
get_member_expr_fullname, REVEAL_TYPE, REVEAL_LOCALS
6060
)
@@ -527,100 +527,142 @@ def _visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None:
527527
# with a @property with a setter or a deleter, and for a classic
528528
# @overload.
529529

530-
# Decide whether to analyze this as a property or an overload. If an
531-
# overload, and we're outside a stub, find the impl and set it. Remove
532-
# the impl from the item list, it's special.
533-
types = [] # type: List[CallableType]
534-
non_overload_indexes = []
530+
defn._fullname = self.qualified_name(defn.name())
535531

536-
# See if the first item is a property (and not an overload)
537532
first_item = defn.items[0]
538533
first_item.is_overload = True
539534
first_item.accept(self)
540535

541-
defn._fullname = self.qualified_name(defn.name())
542-
543536
if isinstance(first_item, Decorator) and first_item.func.is_property:
537+
# This is a property.
544538
first_item.func.is_overload = True
545539
self.analyze_property_with_multi_part_definition(defn)
546540
typ = function_type(first_item.func, self.builtin_type('builtins.function'))
547541
assert isinstance(typ, CallableType)
548542
types = [typ]
549543
else:
550-
for i, item in enumerate(defn.items):
551-
if i != 0:
552-
# The first item was already visited
553-
item.is_overload = True
554-
item.accept(self)
555-
# TODO: support decorated overloaded functions properly
556-
if isinstance(item, Decorator):
557-
callable = function_type(item.func, self.builtin_type('builtins.function'))
558-
assert isinstance(callable, CallableType)
559-
if not any(refers_to_fullname(dec, 'typing.overload')
560-
for dec in item.decorators):
561-
if i == len(defn.items) - 1 and not self.is_stub_file:
562-
# Last item outside a stub is impl
563-
defn.impl = item
564-
else:
565-
# Oops it wasn't an overload after all. A clear error
566-
# will vary based on where in the list it is, record
567-
# that.
568-
non_overload_indexes.append(i)
569-
else:
570-
item.func.is_overload = True
571-
types.append(callable)
572-
elif isinstance(item, FuncDef):
573-
if i == len(defn.items) - 1 and not self.is_stub_file:
574-
defn.impl = item
575-
else:
576-
non_overload_indexes.append(i)
544+
# This is an a normal overload. Find the item signatures, the
545+
# implementation (if outside a stub), and any missing @overload
546+
# decorators.
547+
types, impl, non_overload_indexes = self.find_overload_sigs_and_impl(defn)
548+
defn.impl = impl
577549
if non_overload_indexes:
578-
if types:
579-
# Some of them were overloads, but not all.
580-
for idx in non_overload_indexes:
581-
if self.is_stub_file:
582-
self.fail("An implementation for an overloaded function "
583-
"is not allowed in a stub file", defn.items[idx])
584-
else:
585-
self.fail("The implementation for an overloaded function "
586-
"must come last", defn.items[idx])
587-
else:
588-
for idx in non_overload_indexes[1:]:
589-
self.name_already_defined(defn.name(), defn.items[idx], first_item)
590-
if defn.impl:
591-
self.name_already_defined(defn.name(), defn.impl, first_item)
592-
# Remove the non-overloads
593-
for idx in reversed(non_overload_indexes):
594-
del defn.items[idx]
595-
# If we found an implementation, remove it from the overloads to
596-
# consider.
597-
if defn.impl is not None:
598-
assert defn.impl is defn.items[-1]
550+
self.handle_missing_overload_decorators(defn, non_overload_indexes,
551+
some_overload_decorators=len(types) > 0)
552+
# If we found an implementation, remove it from the overload item list,
553+
# as it's special.
554+
if impl is not None:
555+
assert impl is defn.items[-1]
599556
defn.items = defn.items[:-1]
600-
elif not self.is_stub_file and not non_overload_indexes:
601-
if not (self.type and not self.is_func_scope() and self.type.is_protocol):
602-
self.fail(
603-
"An overloaded function outside a stub file must have an implementation",
604-
defn)
605-
else:
606-
for item in defn.items:
607-
if isinstance(item, Decorator):
608-
item.func.is_abstract = True
609-
else:
610-
item.is_abstract = True
557+
elif not non_overload_indexes:
558+
self.handle_missing_overload_implementation(defn)
611559

612560
if types:
613561
defn.type = Overloaded(types)
614562
defn.type.line = defn.line
615563

616564
if not defn.items:
617-
# It was not any kind of overload def after all. We've visited the
618-
# redefinitions already.
565+
# It was not a real overload after all, but function redefinition. We've
566+
# visited the redefinition(s) already.
619567
return
620568

621-
# Check final status, if the implementation is marked
622-
# as @final (or the first overload in stubs), then the whole overloaded
623-
# definition if @final.
569+
# We know this is an overload def. Infer properties and perform some checks.
570+
self.process_final_in_overload(defn)
571+
self.process_static_or_class_method_in_overload(defn)
572+
573+
if self.type and not self.is_func_scope():
574+
self.type.names[defn.name()] = SymbolTableNode(MDEF, defn)
575+
defn.info = self.type
576+
elif self.is_func_scope():
577+
self.add_local(defn, defn)
578+
579+
def find_overload_sigs_and_impl(
580+
self,
581+
defn: OverloadedFuncDef) -> Tuple[List[CallableType],
582+
Optional[OverloadPart],
583+
List[int]]:
584+
"""Find overload signatures, the implementation, and items with missing @overload.
585+
586+
Assume that the first was already analyzed. As a side effect:
587+
analyzes remaining items and updates 'is_overload' flags.
588+
"""
589+
types = []
590+
non_overload_indexes = []
591+
impl = None # type: Optional[OverloadPart]
592+
for i, item in enumerate(defn.items):
593+
if i != 0:
594+
# Assume that the first item was already visited
595+
item.is_overload = True
596+
item.accept(self)
597+
# TODO: support decorated overloaded functions properly
598+
if isinstance(item, Decorator):
599+
callable = function_type(item.func, self.builtin_type('builtins.function'))
600+
assert isinstance(callable, CallableType)
601+
if not any(refers_to_fullname(dec, 'typing.overload')
602+
for dec in item.decorators):
603+
if i == len(defn.items) - 1 and not self.is_stub_file:
604+
# Last item outside a stub is impl
605+
impl = item
606+
else:
607+
# Oops it wasn't an overload after all. A clear error
608+
# will vary based on where in the list it is, record
609+
# that.
610+
non_overload_indexes.append(i)
611+
else:
612+
item.func.is_overload = True
613+
types.append(callable)
614+
elif isinstance(item, FuncDef):
615+
if i == len(defn.items) - 1 and not self.is_stub_file:
616+
impl = item
617+
else:
618+
non_overload_indexes.append(i)
619+
return types, impl, non_overload_indexes
620+
621+
def handle_missing_overload_decorators(self,
622+
defn: OverloadedFuncDef,
623+
non_overload_indexes: List[int],
624+
some_overload_decorators: bool) -> None:
625+
"""Generate errors for overload items without @overload.
626+
627+
Side effect: remote non-overload items.
628+
"""
629+
if some_overload_decorators:
630+
# Some of them were overloads, but not all.
631+
for idx in non_overload_indexes:
632+
if self.is_stub_file:
633+
self.fail("An implementation for an overloaded function "
634+
"is not allowed in a stub file", defn.items[idx])
635+
else:
636+
self.fail("The implementation for an overloaded function "
637+
"must come last", defn.items[idx])
638+
else:
639+
for idx in non_overload_indexes[1:]:
640+
self.name_already_defined(defn.name(), defn.items[idx], defn.items[0])
641+
if defn.impl:
642+
self.name_already_defined(defn.name(), defn.impl, defn.items[0])
643+
# Remove the non-overloads
644+
for idx in reversed(non_overload_indexes):
645+
del defn.items[idx]
646+
647+
def handle_missing_overload_implementation(self, defn: OverloadedFuncDef) -> None:
648+
"""Generate error about missing overload implementation (only if needed)."""
649+
if not self.is_stub_file:
650+
if self.type and self.type.is_protocol and not self.is_func_scope():
651+
# An overloded protocol method doesn't need an implementation.
652+
for item in defn.items:
653+
if isinstance(item, Decorator):
654+
item.func.is_abstract = True
655+
else:
656+
item.is_abstract = True
657+
else:
658+
self.fail(
659+
"An overloaded function outside a stub file must have an implementation",
660+
defn)
661+
662+
def process_final_in_overload(self, defn: OverloadedFuncDef) -> None:
663+
"""Detect the @final status of an overloaded function (and perform checks)."""
664+
# If the implementation is marked as @final (or the first overload in
665+
# stubs), then the whole overloaded definition if @final.
624666
if any(item.is_final for item in defn.items):
625667
# We anyway mark it as final because it was probably the intention.
626668
defn.is_final = True
@@ -636,7 +678,7 @@ def _visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None:
636678
if defn.impl is not None and defn.impl.is_final:
637679
defn.is_final = True
638680

639-
# We know this is an overload def -- let's handle classmethod and staticmethod
681+
def process_static_or_class_method_in_overload(self, defn: OverloadedFuncDef) -> None:
640682
class_status = []
641683
static_status = []
642684
for item in defn.items:
@@ -667,12 +709,6 @@ def _visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None:
667709
defn.is_class = class_status[0]
668710
defn.is_static = static_status[0]
669711

670-
if self.type and not self.is_func_scope():
671-
self.type.names[defn.name()] = SymbolTableNode(MDEF, defn)
672-
defn.info = self.type
673-
elif self.is_func_scope():
674-
self.add_local(defn, defn)
675-
676712
def analyze_property_with_multi_part_definition(self, defn: OverloadedFuncDef) -> None:
677713
"""Analyze a property defined using multiple methods (e.g., using @x.setter).
678714

0 commit comments

Comments
 (0)