Skip to content

Commit 01cbfbc

Browse files
authored
Merge branch 'master' into bugfix/gh-17221-typevar-union-context
2 parents 3f04d8e + f336726 commit 01cbfbc

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+705
-195
lines changed

mypy/argmap.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ def map_actuals_to_formals(
7878
elif actual_kind.is_named():
7979
assert actual_names is not None, "Internal error: named kinds without names given"
8080
name = actual_names[ai]
81-
if name in formal_names:
81+
if name in formal_names and formal_kinds[formal_names.index(name)] != nodes.ARG_STAR:
8282
formal_to_actual[formal_names.index(name)].append(ai)
8383
elif nodes.ARG_STAR2 in formal_kinds:
8484
formal_to_actual[formal_kinds.index(nodes.ARG_STAR2)].append(ai)

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
@@ -4101,7 +4101,7 @@ def lookup_definer(typ: Instance, attr_name: str) -> str | None:
41014101
results = []
41024102
for name, method, obj, arg in variants:
41034103
with self.msg.filter_errors(save_filtered_errors=True) as local_errors:
4104-
result = self.check_method_call(op_name, obj, method, [arg], [ARG_POS], context)
4104+
result = self.check_method_call(name, obj, method, [arg], [ARG_POS], context)
41054105
if local_errors.has_new_errors():
41064106
errors.append(local_errors.filtered_errors())
41074107
results.append(result)
@@ -6302,7 +6302,13 @@ def narrow_type_from_binder(
63026302
known_type, restriction, prohibit_none_typevar_overlap=True
63036303
):
63046304
return None
6305-
return narrow_declared_type(known_type, restriction)
6305+
narrowed = narrow_declared_type(known_type, restriction)
6306+
if isinstance(get_proper_type(narrowed), UninhabitedType):
6307+
# If we hit this case, it means that we can't reliably mark the code as
6308+
# unreachable, but the resulting type can't be expressed in type system.
6309+
# Falling back to restriction is more intuitive in most cases.
6310+
return restriction
6311+
return narrowed
63066312
return known_type
63076313

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

mypy/checkmember.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -308,18 +308,21 @@ def report_missing_attribute(
308308
def analyze_instance_member_access(
309309
name: str, typ: Instance, mx: MemberContext, override_info: TypeInfo | None
310310
) -> Type:
311-
if name == "__init__" and not mx.is_super:
312-
# Accessing __init__ in statically typed code would compromise
313-
# type safety unless used via super().
314-
mx.fail(message_registry.CANNOT_ACCESS_INIT)
315-
return AnyType(TypeOfAny.from_error)
316-
317-
# The base object has an instance type.
318-
319311
info = typ.type
320312
if override_info:
321313
info = override_info
322314

315+
method = info.get_method(name)
316+
317+
if name == "__init__" and not mx.is_super and not info.is_final:
318+
if not method or not method.is_final:
319+
# Accessing __init__ in statically typed code would compromise
320+
# type safety unless used via super() or the method/class is final.
321+
mx.fail(message_registry.CANNOT_ACCESS_INIT)
322+
return AnyType(TypeOfAny.from_error)
323+
324+
# The base object has an instance type.
325+
323326
if (
324327
state.find_occurrences
325328
and info.name == state.find_occurrences[0]
@@ -329,7 +332,6 @@ def analyze_instance_member_access(
329332
mx.msg.note("Occurrence of '{}.{}'".format(*state.find_occurrences), mx.context)
330333

331334
# Look up the member. First look up the method dictionary.
332-
method = info.get_method(name)
333335
if method and not isinstance(method, Decorator):
334336
if mx.is_super and not mx.suppress_errors:
335337
validate_super_call(method, mx)

mypy/constraints.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,7 @@ def handle_recursive_union(template: UnionType, actual: Type, direction: int) ->
512512
) or infer_constraints(UnionType.make_union(type_var_items), actual, direction)
513513

514514

515-
def any_constraints(options: list[list[Constraint] | None], eager: bool) -> list[Constraint]:
515+
def any_constraints(options: list[list[Constraint] | None], *, eager: bool) -> list[Constraint]:
516516
"""Deduce what we can from a collection of constraint lists.
517517
518518
It's a given that at least one of the lists must be satisfied. A
@@ -547,14 +547,22 @@ def any_constraints(options: list[list[Constraint] | None], eager: bool) -> list
547547
if option in trivial_options:
548548
continue
549549
merged_options.append([merge_with_any(c) for c in option])
550-
return any_constraints(list(merged_options), eager)
550+
return any_constraints(list(merged_options), eager=eager)
551551

552552
# If normal logic didn't work, try excluding trivially unsatisfiable constraint (due to
553553
# upper bounds) from each option, and comparing them again.
554554
filtered_options = [filter_satisfiable(o) for o in options]
555555
if filtered_options != options:
556556
return any_constraints(filtered_options, eager=eager)
557557

558+
# Try harder: if that didn't work, try to strip typevars that aren't meta vars.
559+
# Note this is what we would always do, but unfortunately some callers may not
560+
# set the meta var status correctly (for historical reasons), so we use this as
561+
# a fallback only.
562+
filtered_options = [exclude_non_meta_vars(o) for o in options]
563+
if filtered_options != options:
564+
return any_constraints(filtered_options, eager=eager)
565+
558566
# Otherwise, there are either no valid options or multiple, inconsistent valid
559567
# options. Give up and deduce nothing.
560568
return []
@@ -569,6 +577,7 @@ def filter_satisfiable(option: list[Constraint] | None) -> list[Constraint] | No
569577
"""
570578
if not option:
571579
return option
580+
572581
satisfiable = []
573582
for c in option:
574583
if isinstance(c.origin_type_var, TypeVarType) and c.origin_type_var.values:
@@ -583,6 +592,15 @@ def filter_satisfiable(option: list[Constraint] | None) -> list[Constraint] | No
583592
return satisfiable
584593

585594

595+
def exclude_non_meta_vars(option: list[Constraint] | None) -> list[Constraint] | None:
596+
# If we had an empty list, keep it intact
597+
if not option:
598+
return option
599+
# However, if none of the options actually references meta vars, better remove
600+
# this constraint entirely.
601+
return [c for c in option if c.type_var.is_meta_var()] or None
602+
603+
586604
def is_same_constraints(x: list[Constraint], y: list[Constraint]) -> bool:
587605
for c1 in x:
588606
if not any(is_same_constraint(c1, c2) for c2 in y):

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):

0 commit comments

Comments
 (0)