Skip to content

Commit 14b314e

Browse files
Merge branch 'master' into just
2 parents 16af994 + 15b8ca9 commit 14b314e

File tree

6 files changed

+300
-71
lines changed

6 files changed

+300
-71
lines changed

mypy/checkexpr.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -582,7 +582,10 @@ def visit_call_expr_inner(self, e: CallExpr, allow_none_return: bool = False) ->
582582
and not node.node.no_args
583583
and not (
584584
isinstance(union_target := get_proper_type(node.node.target), UnionType)
585-
and union_target.uses_pep604_syntax
585+
and (
586+
union_target.uses_pep604_syntax
587+
or self.chk.options.python_version >= (3, 10)
588+
)
586589
)
587590
):
588591
self.msg.type_arguments_not_allowed(e)

mypy/checkpattern.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ def visit_or_pattern(self, o: OrPattern) -> PatternType:
192192
for capture_list in capture_types.values():
193193
typ = UninhabitedType()
194194
for _, other in capture_list:
195-
typ = join_types(typ, other)
195+
typ = make_simplified_union([typ, other])
196196

197197
captures[capture_list[0][0]] = typ
198198

@@ -539,12 +539,12 @@ def visit_class_pattern(self, o: ClassPattern) -> PatternType:
539539
#
540540
type_info = o.class_ref.node
541541
if type_info is None:
542-
return PatternType(AnyType(TypeOfAny.from_error), AnyType(TypeOfAny.from_error), {})
543-
if isinstance(type_info, TypeAlias) and not type_info.no_args:
542+
typ: Type = AnyType(TypeOfAny.from_error)
543+
elif isinstance(type_info, TypeAlias) and not type_info.no_args:
544544
self.msg.fail(message_registry.CLASS_PATTERN_GENERIC_TYPE_ALIAS, o)
545545
return self.early_non_match()
546-
if isinstance(type_info, TypeInfo):
547-
typ: Type = fill_typevars_with_any(type_info)
546+
elif isinstance(type_info, TypeInfo):
547+
typ = fill_typevars_with_any(type_info)
548548
elif isinstance(type_info, TypeAlias):
549549
typ = type_info.target
550550
elif (

mypy/stubtest.py

Lines changed: 157 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -506,28 +506,57 @@ def _is_disjoint_base(typ: type[object]) -> bool:
506506
def _verify_disjoint_base(
507507
stub: nodes.TypeInfo, runtime: type[object], object_path: list[str]
508508
) -> Iterator[Error]:
509-
# If it's final, doesn't matter whether it's a disjoint base or not
510-
if stub.is_final:
511-
return
512509
is_disjoint_runtime = _is_disjoint_base(runtime)
513510
# Don't complain about missing @disjoint_base if there are __slots__, because
514511
# in that case we can infer that it's a disjoint base.
515-
if is_disjoint_runtime and not stub.is_disjoint_base and not runtime.__dict__.get("__slots__"):
512+
if (
513+
is_disjoint_runtime
514+
and not stub.is_disjoint_base
515+
and not runtime.__dict__.get("__slots__")
516+
and not stub.is_final
517+
and not (stub.is_enum and stub.enum_members)
518+
):
516519
yield Error(
517520
object_path,
518521
"is a disjoint base at runtime, but isn't marked with @disjoint_base in the stub",
519522
stub,
520523
runtime,
521524
stub_desc=repr(stub),
522525
)
523-
elif not is_disjoint_runtime and stub.is_disjoint_base:
524-
yield Error(
525-
object_path,
526-
"is marked with @disjoint_base in the stub, but isn't a disjoint base at runtime",
527-
stub,
528-
runtime,
529-
stub_desc=repr(stub),
530-
)
526+
elif stub.is_disjoint_base:
527+
if not is_disjoint_runtime:
528+
yield Error(
529+
object_path,
530+
"is marked with @disjoint_base in the stub, but isn't a disjoint base at runtime",
531+
stub,
532+
runtime,
533+
stub_desc=repr(stub),
534+
)
535+
if runtime.__dict__.get("__slots__"):
536+
yield Error(
537+
object_path,
538+
"is marked as @disjoint_base, but also has slots; add __slots__ instead",
539+
stub,
540+
runtime,
541+
stub_desc=repr(stub),
542+
)
543+
elif stub.is_final:
544+
yield Error(
545+
object_path,
546+
"is marked as @disjoint_base, but also marked as @final; remove @disjoint_base",
547+
stub,
548+
runtime,
549+
stub_desc=repr(stub),
550+
)
551+
elif stub.is_enum and stub.enum_members:
552+
yield Error(
553+
object_path,
554+
"is marked as @disjoint_base, but is an enum with members, which is implicitly final; "
555+
"remove @disjoint_base",
556+
stub,
557+
runtime,
558+
stub_desc=repr(stub),
559+
)
531560

532561

533562
def _verify_metaclass(
@@ -747,8 +776,8 @@ def names_approx_match(a: str, b: str) -> bool:
747776
if stub_arg.variable.name == "_self":
748777
return
749778
yield (
750-
f'stub argument "{stub_arg.variable.name}" '
751-
f'differs from runtime argument "{runtime_arg.name}"'
779+
f'stub parameter "{stub_arg.variable.name}" '
780+
f'differs from runtime parameter "{runtime_arg.name}"'
752781
)
753782

754783

@@ -759,8 +788,8 @@ def _verify_arg_default_value(
759788
if runtime_arg.default is not inspect.Parameter.empty:
760789
if stub_arg.kind.is_required():
761790
yield (
762-
f'runtime argument "{runtime_arg.name}" '
763-
"has a default value but stub argument does not"
791+
f'runtime parameter "{runtime_arg.name}" '
792+
"has a default value but stub parameter does not"
764793
)
765794
else:
766795
runtime_type = get_mypy_type_of_runtime_value(runtime_arg.default)
@@ -781,9 +810,9 @@ def _verify_arg_default_value(
781810
and not is_subtype_helper(runtime_type, stub_type)
782811
):
783812
yield (
784-
f'runtime argument "{runtime_arg.name}" '
813+
f'runtime parameter "{runtime_arg.name}" '
785814
f"has a default value of type {runtime_type}, "
786-
f"which is incompatible with stub argument type {stub_type}"
815+
f"which is incompatible with stub parameter type {stub_type}"
787816
)
788817
if stub_arg.initializer is not None:
789818
stub_default = evaluate_expression(stub_arg.initializer)
@@ -807,15 +836,15 @@ def _verify_arg_default_value(
807836
defaults_match = False
808837
if not defaults_match:
809838
yield (
810-
f'runtime argument "{runtime_arg.name}" '
839+
f'runtime parameter "{runtime_arg.name}" '
811840
f"has a default value of {runtime_arg.default!r}, "
812-
f"which is different from stub argument default {stub_default!r}"
841+
f"which is different from stub parameter default {stub_default!r}"
813842
)
814843
else:
815844
if stub_arg.kind.is_optional():
816845
yield (
817-
f'stub argument "{stub_arg.variable.name}" has a default value '
818-
f"but runtime argument does not"
846+
f'stub parameter "{stub_arg.variable.name}" has a default value '
847+
f"but runtime parameter does not"
819848
)
820849

821850

@@ -925,21 +954,36 @@ def from_overloadedfuncdef(stub: nodes.OverloadedFuncDef) -> Signature[nodes.Arg
925954
# For most dunder methods, just assume all args are positional-only
926955
assume_positional_only = is_dunder(stub.name, exclude_special=True)
927956

928-
all_args: dict[str, list[tuple[nodes.Argument, int]]] = {}
957+
is_arg_pos_only: defaultdict[str, set[bool]] = defaultdict(set)
929958
for func in map(_resolve_funcitem_from_decorator, stub.items):
930959
assert func is not None, "Failed to resolve decorated overload"
931960
args = maybe_strip_cls(stub.name, func.arguments)
932961
for index, arg in enumerate(args):
933-
# For positional-only args, we allow overloads to have different names for the same
934-
# argument. To accomplish this, we just make up a fake index-based name.
935-
name = (
936-
f"__{index}"
937-
if arg.variable.name.startswith("__")
962+
if (
963+
arg.variable.name.startswith("__")
938964
or arg.pos_only
939965
or assume_positional_only
940966
or arg.variable.name.strip("_") == "self"
941-
else arg.variable.name
942-
)
967+
or (index == 0 and arg.variable.name.strip("_") == "cls")
968+
):
969+
is_arg_pos_only[arg.variable.name].add(True)
970+
else:
971+
is_arg_pos_only[arg.variable.name].add(False)
972+
973+
all_args: dict[str, list[tuple[nodes.Argument, int]]] = {}
974+
for func in map(_resolve_funcitem_from_decorator, stub.items):
975+
assert func is not None, "Failed to resolve decorated overload"
976+
args = maybe_strip_cls(stub.name, func.arguments)
977+
for index, arg in enumerate(args):
978+
# For positional-only args, we allow overloads to have different names for the same
979+
# argument. To accomplish this, we just make up a fake index-based name.
980+
# We can only use the index-based name if the argument is always
981+
# positional only. Sometimes overloads have an arg as positional-only
982+
# in some but not all branches of the overload.
983+
name = arg.variable.name
984+
if is_arg_pos_only[name] == {True}:
985+
name = f"__{index}"
986+
943987
all_args.setdefault(name, []).append((arg, index))
944988

945989
def get_position(arg_name: str) -> int:
@@ -1008,21 +1052,23 @@ def _verify_signature(
10081052
and not stub_arg.pos_only
10091053
and not stub_arg.variable.name.startswith("__")
10101054
and stub_arg.variable.name.strip("_") != "self"
1055+
and stub_arg.variable.name.strip("_") != "cls"
10111056
and not is_dunder(function_name, exclude_special=True) # noisy for dunder methods
10121057
):
10131058
yield (
1014-
f'stub argument "{stub_arg.variable.name}" should be positional-only '
1059+
f'stub parameter "{stub_arg.variable.name}" should be positional-only '
10151060
f'(add "/", e.g. "{runtime_arg.name}, /")'
10161061
)
10171062
if (
10181063
runtime_arg.kind != inspect.Parameter.POSITIONAL_ONLY
10191064
and (stub_arg.pos_only or stub_arg.variable.name.startswith("__"))
10201065
and not runtime_arg.name.startswith("__")
10211066
and stub_arg.variable.name.strip("_") != "self"
1067+
and stub_arg.variable.name.strip("_") != "cls"
10221068
and not is_dunder(function_name, exclude_special=True) # noisy for dunder methods
10231069
):
10241070
yield (
1025-
f'stub argument "{stub_arg.variable.name}" should be positional or keyword '
1071+
f'stub parameter "{stub_arg.variable.name}" should be positional or keyword '
10261072
'(remove "/")'
10271073
)
10281074

@@ -1037,28 +1083,28 @@ def _verify_signature(
10371083
# If the variable is in runtime.kwonly, it's just mislabelled as not a
10381084
# keyword-only argument
10391085
if stub_arg.variable.name not in runtime.kwonly:
1040-
msg = f'runtime does not have argument "{stub_arg.variable.name}"'
1086+
msg = f'runtime does not have parameter "{stub_arg.variable.name}"'
10411087
if runtime.varkw is not None:
10421088
msg += ". Maybe you forgot to make it keyword-only in the stub?"
10431089
yield msg
10441090
else:
1045-
yield f'stub argument "{stub_arg.variable.name}" is not keyword-only'
1091+
yield f'stub parameter "{stub_arg.variable.name}" is not keyword-only'
10461092
if stub.varpos is not None:
1047-
yield f'runtime does not have *args argument "{stub.varpos.variable.name}"'
1093+
yield f'runtime does not have *args parameter "{stub.varpos.variable.name}"'
10481094
elif len(stub.pos) < len(runtime.pos):
10491095
for runtime_arg in runtime.pos[len(stub.pos) :]:
10501096
if runtime_arg.name not in stub.kwonly:
10511097
if not _is_private_parameter(runtime_arg):
1052-
yield f'stub does not have argument "{runtime_arg.name}"'
1098+
yield f'stub does not have parameter "{runtime_arg.name}"'
10531099
else:
1054-
yield f'runtime argument "{runtime_arg.name}" is not keyword-only'
1100+
yield f'runtime parameter "{runtime_arg.name}" is not keyword-only'
10551101

10561102
# Checks involving *args
10571103
if len(stub.pos) <= len(runtime.pos) or runtime.varpos is None:
10581104
if stub.varpos is None and runtime.varpos is not None:
1059-
yield f'stub does not have *args argument "{runtime.varpos.name}"'
1105+
yield f'stub does not have *args parameter "{runtime.varpos.name}"'
10601106
if stub.varpos is not None and runtime.varpos is None:
1061-
yield f'runtime does not have *args argument "{stub.varpos.variable.name}"'
1107+
yield f'runtime does not have *args parameter "{stub.varpos.variable.name}"'
10621108

10631109
# Check keyword-only args
10641110
for arg in sorted(set(stub.kwonly) & set(runtime.kwonly)):
@@ -1077,20 +1123,20 @@ def _verify_signature(
10771123
if arg in {runtime_arg.name for runtime_arg in runtime.pos}:
10781124
# Don't report this if we've reported it before
10791125
if arg not in {runtime_arg.name for runtime_arg in runtime.pos[len(stub.pos) :]}:
1080-
yield f'runtime argument "{arg}" is not keyword-only'
1126+
yield f'runtime parameter "{arg}" is not keyword-only'
10811127
else:
1082-
yield f'runtime does not have argument "{arg}"'
1128+
yield f'runtime does not have parameter "{arg}"'
10831129
for arg in sorted(set(runtime.kwonly) - set(stub.kwonly)):
10841130
if arg in {stub_arg.variable.name for stub_arg in stub.pos}:
10851131
# Don't report this if we've reported it before
10861132
if not (
10871133
runtime.varpos is None
10881134
and arg in {stub_arg.variable.name for stub_arg in stub.pos[len(runtime.pos) :]}
10891135
):
1090-
yield f'stub argument "{arg}" is not keyword-only'
1136+
yield f'stub parameter "{arg}" is not keyword-only'
10911137
else:
10921138
if not _is_private_parameter(runtime.kwonly[arg]):
1093-
yield f'stub does not have argument "{arg}"'
1139+
yield f'stub does not have parameter "{arg}"'
10941140

10951141
# Checks involving **kwargs
10961142
if stub.varkw is None and runtime.varkw is not None:
@@ -1100,9 +1146,9 @@ def _verify_signature(
11001146
stub_pos_names = {stub_arg.variable.name for stub_arg in stub.pos}
11011147
# Ideally we'd do a strict subset check, but in practice the errors from that aren't useful
11021148
if not set(runtime.kwonly).issubset(set(stub.kwonly) | stub_pos_names):
1103-
yield f'stub does not have **kwargs argument "{runtime.varkw.name}"'
1149+
yield f'stub does not have **kwargs parameter "{runtime.varkw.name}"'
11041150
if stub.varkw is not None and runtime.varkw is None:
1105-
yield f'runtime does not have **kwargs argument "{stub.varkw.variable.name}"'
1151+
yield f'runtime does not have **kwargs parameter "{stub.varkw.variable.name}"'
11061152

11071153

11081154
def _is_private_parameter(arg: inspect.Parameter) -> bool:
@@ -1422,7 +1468,7 @@ def apply_decorator_to_funcitem(
14221468
if decorator.fullname == "builtins.classmethod":
14231469
if func.arguments[0].variable.name not in ("cls", "mcs", "metacls"):
14241470
raise StubtestFailure(
1425-
f"unexpected class argument name {func.arguments[0].variable.name!r} "
1471+
f"unexpected class parameter name {func.arguments[0].variable.name!r} "
14261472
f"in {dec.fullname}"
14271473
)
14281474
# FuncItem is written so that copy.copy() actually works, even when compiled
@@ -1662,6 +1708,71 @@ def is_read_only_property(runtime: object) -> bool:
16621708

16631709

16641710
def safe_inspect_signature(runtime: Any) -> inspect.Signature | None:
1711+
if (
1712+
hasattr(runtime, "__name__")
1713+
and runtime.__name__ == "__init__"
1714+
and hasattr(runtime, "__text_signature__")
1715+
and runtime.__text_signature__ == "($self, /, *args, **kwargs)"
1716+
and hasattr(runtime, "__objclass__")
1717+
and hasattr(runtime.__objclass__, "__text_signature__")
1718+
and runtime.__objclass__.__text_signature__ is not None
1719+
):
1720+
# This is an __init__ method with the generic C-class signature.
1721+
# In this case, the underlying class often has a better signature,
1722+
# which we can convert into an __init__ signature by adding in the
1723+
# self parameter.
1724+
try:
1725+
s = inspect.signature(runtime.__objclass__)
1726+
1727+
parameter_kind: inspect._ParameterKind = inspect.Parameter.POSITIONAL_OR_KEYWORD
1728+
if s.parameters:
1729+
first_parameter = next(iter(s.parameters.values()))
1730+
if first_parameter.kind == inspect.Parameter.POSITIONAL_ONLY:
1731+
parameter_kind = inspect.Parameter.POSITIONAL_ONLY
1732+
return s.replace(
1733+
parameters=[inspect.Parameter("self", parameter_kind), *s.parameters.values()]
1734+
)
1735+
except Exception:
1736+
pass
1737+
1738+
if (
1739+
hasattr(runtime, "__name__")
1740+
and runtime.__name__ == "__new__"
1741+
and hasattr(runtime, "__text_signature__")
1742+
and runtime.__text_signature__ == "($type, *args, **kwargs)"
1743+
and hasattr(runtime, "__self__")
1744+
and hasattr(runtime.__self__, "__text_signature__")
1745+
and runtime.__self__.__text_signature__ is not None
1746+
):
1747+
# This is a __new__ method with the generic C-class signature.
1748+
# In this case, the underlying class often has a better signature,
1749+
# which we can convert into a __new__ signature by adding in the
1750+
# cls parameter.
1751+
1752+
# If the attached class has a valid __init__, skip recovering a
1753+
# signature for this __new__ method.
1754+
has_init = False
1755+
if (
1756+
hasattr(runtime.__self__, "__init__")
1757+
and hasattr(runtime.__self__.__init__, "__objclass__")
1758+
and runtime.__self__.__init__.__objclass__ is runtime.__self__
1759+
):
1760+
has_init = True
1761+
1762+
if not has_init:
1763+
try:
1764+
s = inspect.signature(runtime.__self__)
1765+
parameter_kind = inspect.Parameter.POSITIONAL_OR_KEYWORD
1766+
if s.parameters:
1767+
first_parameter = next(iter(s.parameters.values()))
1768+
if first_parameter.kind == inspect.Parameter.POSITIONAL_ONLY:
1769+
parameter_kind = inspect.Parameter.POSITIONAL_ONLY
1770+
return s.replace(
1771+
parameters=[inspect.Parameter("cls", parameter_kind), *s.parameters.values()]
1772+
)
1773+
except Exception:
1774+
pass
1775+
16651776
try:
16661777
try:
16671778
return inspect.signature(runtime)

0 commit comments

Comments
 (0)