Skip to content

Commit cf33caa

Browse files
committed
Merge remote-tracking branch 'upstream/master' into experiments/st-exclude-foreign-tvars-from-constraints
2 parents 9090192 + e7405c9 commit cf33caa

35 files changed

+578
-173
lines changed

mypy/checker.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -750,6 +750,9 @@ def _visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None:
750750
defn.is_explicit_override
751751
and not found_method_base_classes
752752
and found_method_base_classes is not None
753+
# If the class has Any fallback, we can't be certain that a method
754+
# is really missing - it might come from unfollowed import.
755+
and not defn.info.fallback_to_any
753756
):
754757
self.msg.no_overridable_method(defn.name, defn)
755758
self.check_explicit_override_decorator(defn, found_method_base_classes, defn.impl)
@@ -5285,12 +5288,15 @@ def visit_decorator_inner(
52855288
# For overloaded functions/properties we already checked override for overload as a whole.
52865289
if allow_empty or skip_first_item:
52875290
return
5288-
if e.func.info and not e.func.is_dynamic() and not e.is_overload:
5291+
if e.func.info and not e.is_overload:
52895292
found_method_base_classes = self.check_method_override(e)
52905293
if (
52915294
e.func.is_explicit_override
52925295
and not found_method_base_classes
52935296
and found_method_base_classes is not None
5297+
# If the class has Any fallback, we can't be certain that a method
5298+
# is really missing - it might come from unfollowed import.
5299+
and not e.func.info.fallback_to_any
52945300
):
52955301
self.msg.no_overridable_method(e.func.name, e.func)
52965302
self.check_explicit_override_decorator(e.func, found_method_base_classes)

mypy/checkexpr.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4096,7 +4096,7 @@ def lookup_definer(typ: Instance, attr_name: str) -> str | None:
40964096
results = []
40974097
for name, method, obj, arg in variants:
40984098
with self.msg.filter_errors(save_filtered_errors=True) as local_errors:
4099-
result = self.check_method_call(op_name, obj, method, [arg], [ARG_POS], context)
4099+
result = self.check_method_call(name, obj, method, [arg], [ARG_POS], context)
41004100
if local_errors.has_new_errors():
41014101
errors.append(local_errors.filtered_errors())
41024102
results.append(result)
@@ -6297,7 +6297,13 @@ def narrow_type_from_binder(
62976297
known_type, restriction, prohibit_none_typevar_overlap=True
62986298
):
62996299
return None
6300-
return narrow_declared_type(known_type, restriction)
6300+
narrowed = narrow_declared_type(known_type, restriction)
6301+
if isinstance(get_proper_type(narrowed), UninhabitedType):
6302+
# If we hit this case, it means that we can't reliably mark the code as
6303+
# unreachable, but the resulting type can't be expressed in type system.
6304+
# Falling back to restriction is more intuitive in most cases.
6305+
return restriction
6306+
return narrowed
63016307
return known_type
63026308

63036309
def has_abstract_type_part(self, caller_type: ProperType, callee_type: ProperType) -> bool:

mypy/dmypy/client.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,16 @@
2929

3030

3131
class AugmentedHelpFormatter(argparse.RawDescriptionHelpFormatter):
32-
def __init__(self, prog: str) -> None:
33-
super().__init__(prog=prog, max_help_position=30)
32+
def __init__(self, prog: str, **kwargs: Any) -> None:
33+
super().__init__(prog=prog, max_help_position=30, **kwargs)
3434

3535

3636
parser = argparse.ArgumentParser(
3737
prog="dmypy", description="Client for mypy daemon mode", fromfile_prefix_chars="@"
3838
)
39+
if sys.version_info >= (3, 14):
40+
parser.color = True # Set as init arg in 3.14
41+
3942
parser.set_defaults(action=None)
4043
parser.add_argument(
4144
"--status-file", default=DEFAULT_STATUS_FILE, help="status file to retrieve daemon details"

mypy/main.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -249,8 +249,8 @@ def show_messages(
249249

250250
# Make the help output a little less jarring.
251251
class AugmentedHelpFormatter(argparse.RawDescriptionHelpFormatter):
252-
def __init__(self, prog: str) -> None:
253-
super().__init__(prog=prog, max_help_position=28)
252+
def __init__(self, prog: str, **kwargs: Any) -> None:
253+
super().__init__(prog=prog, max_help_position=28, **kwargs)
254254

255255
def _fill_text(self, text: str, width: int, indent: str) -> str:
256256
if "\n" in text:
@@ -491,6 +491,8 @@ def process_options(
491491
stdout=stdout,
492492
stderr=stderr,
493493
)
494+
if sys.version_info >= (3, 14):
495+
parser.color = True # Set as init arg in 3.14
494496

495497
strict_flag_names: list[str] = []
496498
strict_flag_assignments: list[tuple[str, bool]] = []
@@ -1449,9 +1451,7 @@ def set_strict_flags() -> None:
14491451
process_cache_map(parser, special_opts, options)
14501452

14511453
# Process --strict-bytes
1452-
if options.strict_bytes:
1453-
options.disable_bytearray_promotion = True
1454-
options.disable_memoryview_promotion = True
1454+
options.process_strict_bytes()
14551455

14561456
# An explicitly specified cache_fine_grained implies local_partial_types
14571457
# (because otherwise the cache is not compatible with dmypy)

mypy/modulefinder.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -506,21 +506,24 @@ def _find_module(self, id: str, use_typeshed: bool) -> ModuleSearchResult:
506506
dir_prefix = base_dir
507507
for _ in range(len(components) - 1):
508508
dir_prefix = os.path.dirname(dir_prefix)
509+
510+
# Stubs-only packages always take precedence over py.typed packages
511+
path_stubs = f"{base_path}-stubs{sepinit}.pyi"
512+
if fscache.isfile_case(path_stubs, dir_prefix):
513+
if verify and not verify_module(fscache, id, path_stubs, dir_prefix):
514+
near_misses.append((path_stubs, dir_prefix))
515+
else:
516+
return path_stubs
517+
509518
# Prefer package over module, i.e. baz/__init__.py* over baz.py*.
510519
for extension in PYTHON_EXTENSIONS:
511520
path = base_path + sepinit + extension
512-
path_stubs = base_path + "-stubs" + sepinit + extension
513521
if fscache.isfile_case(path, dir_prefix):
514522
has_init = True
515523
if verify and not verify_module(fscache, id, path, dir_prefix):
516524
near_misses.append((path, dir_prefix))
517525
continue
518526
return path
519-
elif fscache.isfile_case(path_stubs, dir_prefix):
520-
if verify and not verify_module(fscache, id, path_stubs, dir_prefix):
521-
near_misses.append((path_stubs, dir_prefix))
522-
continue
523-
return path_stubs
524527

525528
# In namespace mode, register a potential namespace package
526529
if self.options and self.options.namespace_packages:

mypy/options.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,16 @@ def process_incomplete_features(
466466
if feature in COMPLETE_FEATURES:
467467
warning_callback(f"Warning: {feature} is already enabled by default")
468468

469+
def process_strict_bytes(self) -> None:
470+
# Sync `--strict-bytes` and `--disable-{bytearray,memoryview}-promotion`
471+
if self.strict_bytes:
472+
# backwards compatibility
473+
self.disable_bytearray_promotion = True
474+
self.disable_memoryview_promotion = True
475+
elif self.disable_bytearray_promotion and self.disable_memoryview_promotion:
476+
# forwards compatibility
477+
self.strict_bytes = True
478+
469479
def apply_changes(self, changes: dict[str, object]) -> Options:
470480
# Note: effects of this method *must* be idempotent.
471481
new_options = Options()

mypy/semanal.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6382,6 +6382,8 @@ class C:
63826382
if node.name not in self.globals:
63836383
return True
63846384
global_node = self.globals[node.name]
6385+
if not self.is_textually_before_class(global_node.node):
6386+
return True
63856387
return not self.is_type_like(global_node.node)
63866388
return False
63876389

@@ -6409,6 +6411,13 @@ def is_textually_before_statement(self, node: SymbolNode) -> bool:
64096411
else:
64106412
return line_diff > 0
64116413

6414+
def is_textually_before_class(self, node: SymbolNode | None) -> bool:
6415+
"""Similar to above, but check if a node is defined before current class."""
6416+
assert self.type is not None
6417+
if node is None:
6418+
return False
6419+
return node.line < self.type.defn.line
6420+
64126421
def is_overloaded_item(self, node: SymbolNode, statement: Statement) -> bool:
64136422
"""Check whether the function belongs to the overloaded variants"""
64146423
if isinstance(node, OverloadedFuncDef) and isinstance(statement, FuncDef):

mypy/stubgen.py

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -920,13 +920,20 @@ def visit_assignment_stmt(self, o: AssignmentStmt) -> None:
920920
continue
921921
if (
922922
isinstance(lvalue, NameExpr)
923-
and not self.is_private_name(lvalue.name)
924-
# it is never an alias with explicit annotation
925-
and not o.unanalyzed_type
926923
and self.is_alias_expression(o.rvalue)
924+
and not self.is_private_name(lvalue.name)
927925
):
928-
self.process_typealias(lvalue, o.rvalue)
929-
continue
926+
is_explicit_type_alias = (
927+
o.unanalyzed_type and getattr(o.type, "name", None) == "TypeAlias"
928+
)
929+
if is_explicit_type_alias:
930+
self.process_typealias(lvalue, o.rvalue, is_explicit_type_alias=True)
931+
continue
932+
933+
if not o.unanalyzed_type:
934+
self.process_typealias(lvalue, o.rvalue)
935+
continue
936+
930937
if isinstance(lvalue, (TupleExpr, ListExpr)):
931938
items = lvalue.items
932939
if isinstance(o.unanalyzed_type, TupleType): # type: ignore[misc]
@@ -1139,9 +1146,15 @@ def is_alias_expression(self, expr: Expression, top_level: bool = True) -> bool:
11391146
else:
11401147
return False
11411148

1142-
def process_typealias(self, lvalue: NameExpr, rvalue: Expression) -> None:
1149+
def process_typealias(
1150+
self, lvalue: NameExpr, rvalue: Expression, is_explicit_type_alias: bool = False
1151+
) -> None:
11431152
p = AliasPrinter(self)
1144-
self.add(f"{self._indent}{lvalue.name} = {rvalue.accept(p)}\n")
1153+
if is_explicit_type_alias:
1154+
self.import_tracker.require_name("TypeAlias")
1155+
self.add(f"{self._indent}{lvalue.name}: TypeAlias = {rvalue.accept(p)}\n")
1156+
else:
1157+
self.add(f"{self._indent}{lvalue.name} = {rvalue.accept(p)}\n")
11451158
self.record_name(lvalue.name)
11461159
self._vars[-1].append(lvalue.name)
11471160

@@ -1838,6 +1851,8 @@ def parse_options(args: list[str]) -> Options:
18381851
parser = argparse.ArgumentParser(
18391852
prog="stubgen", usage=HEADER, description=DESCRIPTION, fromfile_prefix_chars="@"
18401853
)
1854+
if sys.version_info >= (3, 14):
1855+
parser.color = True # Set as init arg in 3.14
18411856

18421857
parser.add_argument(
18431858
"--ignore-errors",

mypy/stubtest.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2003,6 +2003,7 @@ def warning_callback(msg: str) -> None:
20032003
options.process_incomplete_features(
20042004
error_callback=error_callback, warning_callback=warning_callback
20052005
)
2006+
options.process_strict_bytes()
20062007

20072008
try:
20082009
modules = build_stubs(modules, options, find_submodules=not args.check_typeshed)
@@ -2083,6 +2084,8 @@ def parse_options(args: list[str]) -> _Arguments:
20832084
parser = argparse.ArgumentParser(
20842085
description="Compares stubs to objects introspected from the runtime."
20852086
)
2087+
if sys.version_info >= (3, 14):
2088+
parser.color = True # Set as init arg in 3.14
20862089
parser.add_argument("modules", nargs="*", help="Modules to test")
20872090
parser.add_argument(
20882091
"--concise",

mypy/suggestions.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,15 @@
5252
SymbolNode,
5353
SymbolTable,
5454
TypeInfo,
55+
Var,
5556
reverse_builtin_aliases,
5657
)
5758
from mypy.options import Options
5859
from mypy.plugin import FunctionContext, MethodContext, Plugin
5960
from mypy.server.update import FineGrainedBuildManager
6061
from mypy.state import state
6162
from mypy.traverser import TraverserVisitor
62-
from mypy.typeops import make_simplified_union
63+
from mypy.typeops import bind_self, make_simplified_union
6364
from mypy.types import (
6465
AnyType,
6566
CallableType,
@@ -638,15 +639,20 @@ def find_node_by_file_and_line(self, file: str, line: int) -> tuple[str, SymbolN
638639
def extract_from_decorator(self, node: Decorator) -> FuncDef | None:
639640
for dec in node.decorators:
640641
typ = None
641-
if isinstance(dec, RefExpr) and isinstance(dec.node, FuncDef):
642-
typ = dec.node.type
642+
if isinstance(dec, RefExpr) and isinstance(dec.node, (Var, FuncDef)):
643+
typ = get_proper_type(dec.node.type)
643644
elif (
644645
isinstance(dec, CallExpr)
645646
and isinstance(dec.callee, RefExpr)
646-
and isinstance(dec.callee.node, FuncDef)
647-
and isinstance(dec.callee.node.type, CallableType)
647+
and isinstance(dec.callee.node, (Decorator, FuncDef, Var))
648+
and isinstance((call_tp := get_proper_type(dec.callee.node.type)), CallableType)
648649
):
649-
typ = get_proper_type(dec.callee.node.type.ret_type)
650+
typ = get_proper_type(call_tp.ret_type)
651+
652+
if isinstance(typ, Instance):
653+
call_method = typ.type.get_method("__call__")
654+
if isinstance(call_method, FuncDef) and isinstance(call_method.type, FunctionLike):
655+
typ = bind_self(call_method.type, None)
650656

651657
if not isinstance(typ, FunctionLike):
652658
return None

0 commit comments

Comments
 (0)