diff --git a/mypy/applytype.py b/mypy/applytype.py index 884be287e33d..c7da67d6140b 100644 --- a/mypy/applytype.py +++ b/mypy/applytype.py @@ -3,6 +3,7 @@ from typing import Callable, Sequence import mypy.subtypes +from mypy.erasetype import erase_typevars from mypy.expandtype import expand_type from mypy.nodes import Context from mypy.types import ( @@ -62,6 +63,11 @@ def get_target_type( report_incompatible_typevar_value(callable, type, tvar.name, context) else: upper_bound = tvar.upper_bound + if tvar.name == "Self": + # Internally constructed Self-types contain class type variables in upper bound, + # so we need to erase them to avoid false positives. This is safe because we do + # not support type variables in upper bounds of user defined types. + upper_bound = erase_typevars(upper_bound) if not mypy.subtypes.is_subtype(type, upper_bound): if skip_unsatisfied: return None @@ -121,6 +127,7 @@ def apply_generic_arguments( # Apply arguments to argument types. var_arg = callable.var_arg() if var_arg is not None and isinstance(var_arg.typ, UnpackType): + # Same as for ParamSpec, callable with variadic types needs to be expanded as a whole. callable = expand_type(callable, id_to_type) assert isinstance(callable, CallableType) return callable.copy_modified(variables=[tv for tv in tvars if tv.id not in id_to_type]) diff --git a/mypy/checker.py b/mypy/checker.py index fd633b209438..62ba642256bf 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1852,7 +1852,6 @@ def expand_typevars( if defn.info: # Class type variables tvars += defn.info.defn.type_vars or [] - # TODO(PEP612): audit for paramspec for tvar in tvars: if isinstance(tvar, TypeVarType) and tvar.values: subst.append([(tvar.id, value) for value in tvar.values]) @@ -2538,6 +2537,9 @@ def check_protocol_variance(self, defn: ClassDef) -> None: object_type = Instance(info.mro[-1], []) tvars = info.defn.type_vars for i, tvar in enumerate(tvars): + if not isinstance(tvar, TypeVarType): + # Variance of TypeVarTuple and ParamSpec is underspecified by PEPs. + continue up_args: list[Type] = [ object_type if i == j else AnyType(TypeOfAny.special_form) for j, _ in enumerate(tvars) @@ -2554,7 +2556,7 @@ def check_protocol_variance(self, defn: ClassDef) -> None: expected = CONTRAVARIANT else: expected = INVARIANT - if isinstance(tvar, TypeVarType) and expected != tvar.variance: + if expected != tvar.variance: self.msg.bad_proto_variance(tvar.variance, tvar.name, expected, defn) def check_multiple_inheritance(self, typ: TypeInfo) -> None: @@ -6695,19 +6697,6 @@ def check_possible_missing_await( return self.msg.possible_missing_await(context, code) - def contains_none(self, t: Type) -> bool: - t = get_proper_type(t) - return ( - isinstance(t, NoneType) - or (isinstance(t, UnionType) and any(self.contains_none(ut) for ut in t.items)) - or (isinstance(t, TupleType) and any(self.contains_none(tt) for tt in t.items)) - or ( - isinstance(t, Instance) - and bool(t.args) - and any(self.contains_none(it) for it in t.args) - ) - ) - def named_type(self, name: str) -> Instance: """Return an instance type with given name and implicit Any type args. @@ -7471,10 +7460,22 @@ def builtin_item_type(tp: Type) -> Type | None: return None if not isinstance(get_proper_type(tp.args[0]), AnyType): return tp.args[0] - elif isinstance(tp, TupleType) and all( - not isinstance(it, AnyType) for it in get_proper_types(tp.items) - ): - return make_simplified_union(tp.items) # this type is not externally visible + elif isinstance(tp, TupleType): + normalized_items = [] + for it in tp.items: + # This use case is probably rare, but not handling unpacks here can cause crashes. + if isinstance(it, UnpackType): + unpacked = get_proper_type(it.type) + if isinstance(unpacked, TypeVarTupleType): + unpacked = get_proper_type(unpacked.upper_bound) + assert ( + isinstance(unpacked, Instance) and unpacked.type.fullname == "builtins.tuple" + ) + normalized_items.append(unpacked.args[0]) + else: + normalized_items.append(it) + if all(not isinstance(it, AnyType) for it in get_proper_types(normalized_items)): + return make_simplified_union(normalized_items) # this type is not externally visible elif isinstance(tp, TypedDictType): # TypedDict always has non-optional string keys. Find the key type from the Mapping # base class. diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 9ece4680f59e..df6000050986 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -410,7 +410,7 @@ def analyze_ref_expr(self, e: RefExpr, lvalue: bool = False) -> Type: result = self.alias_type_in_runtime_context( node, ctx=e, alias_definition=e.is_alias_rvalue or lvalue ) - elif isinstance(node, (TypeVarExpr, ParamSpecExpr)): + elif isinstance(node, (TypeVarExpr, ParamSpecExpr, TypeVarTupleExpr)): result = self.object_type() else: if isinstance(node, PlaceholderNode): @@ -3316,6 +3316,7 @@ def infer_literal_expr_type(self, value: LiteralValue, fallback_name: str) -> Ty def concat_tuples(self, left: TupleType, right: TupleType) -> TupleType: """Concatenate two fixed length tuples.""" + assert not (find_unpack_in_list(left.items) and find_unpack_in_list(right.items)) return TupleType( items=left.items + right.items, fallback=self.named_type("builtins.tuple") ) @@ -6507,8 +6508,8 @@ def merge_typevars_in_callables_by_name( for tv in target.variables: name = tv.fullname if name not in unique_typevars: - # TODO(PEP612): fix for ParamSpecType - if isinstance(tv, ParamSpecType): + # TODO: support ParamSpecType and TypeVarTuple. + if isinstance(tv, (ParamSpecType, TypeVarTupleType)): continue assert isinstance(tv, TypeVarType) unique_typevars[name] = tv diff --git a/mypy/checkpattern.py b/mypy/checkpattern.py index 3f9a99b21530..c0061f1c3e72 100644 --- a/mypy/checkpattern.py +++ b/mypy/checkpattern.py @@ -45,9 +45,13 @@ Type, TypedDictType, TypeOfAny, + TypeVarTupleType, UninhabitedType, UnionType, + UnpackType, + find_unpack_in_list, get_proper_type, + split_with_prefix_and_suffix, ) from mypy.typevars import fill_typevars from mypy.visitor import PatternVisitor @@ -239,13 +243,29 @@ def visit_sequence_pattern(self, o: SequencePattern) -> PatternType: # # get inner types of original type # + unpack_index = None if isinstance(current_type, TupleType): inner_types = current_type.items - size_diff = len(inner_types) - required_patterns - if size_diff < 0: - return self.early_non_match() - elif size_diff > 0 and star_position is None: - return self.early_non_match() + unpack_index = find_unpack_in_list(inner_types) + if unpack_index is None: + size_diff = len(inner_types) - required_patterns + if size_diff < 0: + return self.early_non_match() + elif size_diff > 0 and star_position is None: + return self.early_non_match() + else: + normalized_inner_types = [] + for it in inner_types: + # Unfortunately, it is not possible to "split" the TypeVarTuple + # into individual items, so we just use its upper bound for the whole + # analysis instead. + if isinstance(it, UnpackType) and isinstance(it.type, TypeVarTupleType): + it = UnpackType(it.type.upper_bound) + normalized_inner_types.append(it) + inner_types = normalized_inner_types + current_type = current_type.copy_modified(items=normalized_inner_types) + if len(inner_types) - 1 > required_patterns and star_position is None: + return self.early_non_match() else: inner_type = self.get_sequence_type(current_type, o) if inner_type is None: @@ -270,10 +290,10 @@ def visit_sequence_pattern(self, o: SequencePattern) -> PatternType: self.update_type_map(captures, type_map) new_inner_types = self.expand_starred_pattern_types( - contracted_new_inner_types, star_position, len(inner_types) + contracted_new_inner_types, star_position, len(inner_types), unpack_index is not None ) rest_inner_types = self.expand_starred_pattern_types( - contracted_rest_inner_types, star_position, len(inner_types) + contracted_rest_inner_types, star_position, len(inner_types), unpack_index is not None ) # @@ -281,7 +301,7 @@ def visit_sequence_pattern(self, o: SequencePattern) -> PatternType: # new_type: Type rest_type: Type = current_type - if isinstance(current_type, TupleType): + if isinstance(current_type, TupleType) and unpack_index is None: narrowed_inner_types = [] inner_rest_types = [] for inner_type, new_inner_type in zip(inner_types, new_inner_types): @@ -301,6 +321,14 @@ def visit_sequence_pattern(self, o: SequencePattern) -> PatternType: if all(is_uninhabited(typ) for typ in inner_rest_types): # All subpatterns always match, so we can apply negative narrowing rest_type = TupleType(rest_inner_types, current_type.partial_fallback) + elif isinstance(current_type, TupleType): + # For variadic tuples it is too tricky to match individual items like for fixed + # tuples, so we instead try to narrow the entire type. + # TODO: use more precise narrowing when possible (e.g. for identical shapes). + new_tuple_type = TupleType(new_inner_types, current_type.partial_fallback) + new_type, rest_type = self.chk.conditional_types_with_intersection( + new_tuple_type, [get_type_range(current_type)], o, default=new_tuple_type + ) else: new_inner_type = UninhabitedType() for typ in new_inner_types: @@ -345,17 +373,45 @@ def contract_starred_pattern_types( If star_pos in None the types are returned unchanged. """ - if star_pos is None: - return types - new_types = types[:star_pos] - star_length = len(types) - num_patterns - new_types.append(make_simplified_union(types[star_pos : star_pos + star_length])) - new_types += types[star_pos + star_length :] - - return new_types + unpack_index = find_unpack_in_list(types) + if unpack_index is not None: + # Variadic tuples require "re-shaping" to match the requested pattern. + unpack = types[unpack_index] + assert isinstance(unpack, UnpackType) + unpacked = get_proper_type(unpack.type) + # This should be guaranteed by the normalization in the caller. + assert isinstance(unpacked, Instance) and unpacked.type.fullname == "builtins.tuple" + if star_pos is None: + missing = num_patterns - len(types) + 1 + new_types = types[:unpack_index] + new_types += [unpacked.args[0]] * missing + new_types += types[unpack_index + 1 :] + return new_types + prefix, middle, suffix = split_with_prefix_and_suffix( + tuple([UnpackType(unpacked) if isinstance(t, UnpackType) else t for t in types]), + star_pos, + num_patterns - star_pos, + ) + new_middle = [] + for m in middle: + # The existing code expects the star item type, rather than the type of + # the whole tuple "slice". + if isinstance(m, UnpackType): + new_middle.append(unpacked.args[0]) + else: + new_middle.append(m) + return list(prefix) + [make_simplified_union(new_middle)] + list(suffix) + else: + if star_pos is None: + return types + new_types = types[:star_pos] + star_length = len(types) - num_patterns + new_types.append(make_simplified_union(types[star_pos : star_pos + star_length])) + new_types += types[star_pos + star_length :] + return new_types def expand_starred_pattern_types( - self, types: list[Type], star_pos: int | None, num_types: int + self, types: list[Type], star_pos: int | None, num_types: int, original_unpack: bool ) -> list[Type]: """Undoes the contraction done by contract_starred_pattern_types. @@ -364,6 +420,17 @@ def expand_starred_pattern_types( """ if star_pos is None: return types + if original_unpack: + # In the case where original tuple type has an unpack item, it is not practical + # to coerce pattern type back to the original shape (and may not even be possible), + # so we only restore the type of the star item. + res = [] + for i, t in enumerate(types): + if i != star_pos: + res.append(t) + else: + res.append(UnpackType(self.chk.named_generic_type("builtins.tuple", [t]))) + return res new_types = types[:star_pos] star_length = num_types - len(types) + 1 new_types += [types[star_pos]] * star_length @@ -459,7 +526,15 @@ def visit_class_pattern(self, o: ClassPattern) -> PatternType: return self.early_non_match() if isinstance(type_info, TypeInfo): any_type = AnyType(TypeOfAny.implementation_artifact) - typ: Type = Instance(type_info, [any_type] * len(type_info.defn.type_vars)) + args: list[Type] = [] + for tv in type_info.defn.type_vars: + if isinstance(tv, TypeVarTupleType): + args.append( + UnpackType(self.chk.named_generic_type("builtins.tuple", [any_type])) + ) + else: + args.append(any_type) + typ: Type = Instance(type_info, args) elif isinstance(type_info, TypeAlias): typ = type_info.target elif ( diff --git a/mypy/constraints.py b/mypy/constraints.py index 6f611736a72a..49e542a49e56 100644 --- a/mypy/constraints.py +++ b/mypy/constraints.py @@ -28,6 +28,7 @@ Instance, LiteralType, NoneType, + NormalizedCallableType, Overloaded, Parameters, ParamSpecType, @@ -1388,7 +1389,7 @@ def find_matching_overload_items( return res -def get_tuple_fallback_from_unpack(unpack: UnpackType) -> TypeInfo | None: +def get_tuple_fallback_from_unpack(unpack: UnpackType) -> TypeInfo: """Get builtins.tuple type from available types to construct homogeneous tuples.""" tp = get_proper_type(unpack.type) if isinstance(tp, Instance) and tp.type.fullname == "builtins.tuple": @@ -1399,10 +1400,10 @@ def get_tuple_fallback_from_unpack(unpack: UnpackType) -> TypeInfo | None: for base in tp.partial_fallback.type.mro: if base.fullname == "builtins.tuple": return base - return None + assert False, "Invalid unpack type" -def repack_callable_args(callable: CallableType, tuple_type: TypeInfo | None) -> list[Type]: +def repack_callable_args(callable: CallableType, tuple_type: TypeInfo) -> list[Type]: """Present callable with star unpack in a normalized form. Since positional arguments cannot follow star argument, they are packed in a suffix, @@ -1417,12 +1418,8 @@ def repack_callable_args(callable: CallableType, tuple_type: TypeInfo | None) -> star_type = callable.arg_types[star_index] suffix_types = [] if not isinstance(star_type, UnpackType): - if tuple_type is not None: - # Re-normalize *args: X -> *args: *tuple[X, ...] - star_type = UnpackType(Instance(tuple_type, [star_type])) - else: - # This is unfortunate, something like tuple[Any, ...] would be better. - star_type = UnpackType(AnyType(TypeOfAny.from_error)) + # Re-normalize *args: X -> *args: *tuple[X, ...] + star_type = UnpackType(Instance(tuple_type, [star_type])) else: tp = get_proper_type(star_type.type) if isinstance(tp, TupleType): @@ -1544,7 +1541,9 @@ def infer_directed_arg_constraints(left: Type, right: Type, direction: int) -> l def infer_callable_arguments_constraints( - template: CallableType | Parameters, actual: CallableType | Parameters, direction: int + template: NormalizedCallableType | Parameters, + actual: NormalizedCallableType | Parameters, + direction: int, ) -> list[Constraint]: """Infer constraints between argument types of two callables. diff --git a/mypy/erasetype.py b/mypy/erasetype.py index 7231ede66c65..b41eefcd4821 100644 --- a/mypy/erasetype.py +++ b/mypy/erasetype.py @@ -100,7 +100,9 @@ def visit_parameters(self, t: Parameters) -> ProperType: raise RuntimeError("Parameters should have been bound to a class") def visit_type_var_tuple(self, t: TypeVarTupleType) -> ProperType: - return AnyType(TypeOfAny.special_form) + # Likely, we can never get here because of aggressive erasure of types that + # can contain this, but better still return a valid replacement. + return t.tuple_fallback.copy_modified(args=[AnyType(TypeOfAny.special_form)]) def visit_unpack_type(self, t: UnpackType) -> ProperType: return AnyType(TypeOfAny.special_form) diff --git a/mypy/join.py b/mypy/join.py index 2e2939f9fbc8..d33cbd98726d 100644 --- a/mypy/join.py +++ b/mypy/join.py @@ -36,6 +36,7 @@ TypedDictType, TypeOfAny, TypeType, + TypeVarLikeType, TypeVarTupleType, TypeVarType, TypeVisitor, @@ -715,11 +716,9 @@ def is_similar_callables(t: CallableType, s: CallableType) -> bool: def join_similar_callables(t: CallableType, s: CallableType) -> CallableType: - from mypy.meet import meet_types - arg_types: list[Type] = [] for i in range(len(t.arg_types)): - arg_types.append(meet_types(t.arg_types[i], s.arg_types[i])) + arg_types.append(safe_meet(t.arg_types[i], s.arg_types[i])) # TODO in combine_similar_callables also applies here (names and kinds; user metaclasses) # The fallback type can be either 'function', 'type', or some user-provided metaclass. # The result should always use 'function' as a fallback if either operands are using it. @@ -736,10 +735,42 @@ def join_similar_callables(t: CallableType, s: CallableType) -> CallableType: ) +def safe_join(t: Type, s: Type) -> Type: + # This is a temporary solution to prevent crashes in combine_similar_callables() etc., + # until relevant TODOs on handling arg_kinds will be addressed there. + if not isinstance(t, UnpackType) and not isinstance(s, UnpackType): + return join_types(t, s) + if isinstance(t, UnpackType) and isinstance(s, UnpackType): + return UnpackType(join_types(t.type, s.type)) + return object_or_any_from_type(get_proper_type(t)) + + +def safe_meet(t: Type, s: Type) -> Type: + # Similar to above but for meet_types(). + from mypy.meet import meet_types + + if not isinstance(t, UnpackType) and not isinstance(s, UnpackType): + return meet_types(t, s) + if isinstance(t, UnpackType) and isinstance(s, UnpackType): + unpacked = get_proper_type(t.type) + if isinstance(unpacked, TypeVarTupleType): + fallback_type = unpacked.tuple_fallback.type + elif isinstance(unpacked, TupleType): + fallback_type = unpacked.partial_fallback.type + else: + assert isinstance(unpacked, Instance) and unpacked.type.fullname == "builtins.tuple" + fallback_type = unpacked.type + res = meet_types(t.type, s.type) + if isinstance(res, UninhabitedType): + res = Instance(fallback_type, [res]) + return UnpackType(res) + return UninhabitedType() + + def combine_similar_callables(t: CallableType, s: CallableType) -> CallableType: arg_types: list[Type] = [] for i in range(len(t.arg_types)): - arg_types.append(join_types(t.arg_types[i], s.arg_types[i])) + arg_types.append(safe_join(t.arg_types[i], s.arg_types[i])) # TODO kinds and argument names # TODO what should happen if one fallback is 'type' and the other is a user-provided metaclass? # The fallback type can be either 'function', 'type', or some user-provided metaclass. @@ -806,7 +837,7 @@ def object_or_any_from_type(typ: ProperType) -> ProperType: return object_from_instance(typ.partial_fallback) elif isinstance(typ, TypeType): return object_or_any_from_type(typ.item) - elif isinstance(typ, TypeVarType) and isinstance(typ.upper_bound, ProperType): + elif isinstance(typ, TypeVarLikeType) and isinstance(typ.upper_bound, ProperType): return object_or_any_from_type(typ.upper_bound) elif isinstance(typ, UnionType): for item in typ.items: @@ -814,6 +845,8 @@ def object_or_any_from_type(typ: ProperType) -> ProperType: candidate = object_or_any_from_type(item) if isinstance(candidate, Instance): return candidate + elif isinstance(typ, UnpackType): + object_or_any_from_type(get_proper_type(typ.type)) return AnyType(TypeOfAny.implementation_artifact) diff --git a/mypy/maptype.py b/mypy/maptype.py index 0d54a83127df..59ecb2bc9993 100644 --- a/mypy/maptype.py +++ b/mypy/maptype.py @@ -31,6 +31,9 @@ def map_instance_to_supertype(instance: Instance, superclass: TypeInfo) -> Insta import mypy.typeops return mypy.typeops.tuple_fallback(tuple_type) + elif isinstance(tuple_type, Instance): + # This can happen after normalizing variadic tuples. + return tuple_type if not superclass.type_vars: # Fast path: `superclass` has no type variables to map to. diff --git a/mypy/meet.py b/mypy/meet.py index fa9bd6a83743..d2fb16808425 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -869,16 +869,17 @@ def meet_tuples(self, s: TupleType, t: TupleType) -> list[Type] | None: return None if s_unpack_index is not None and t_unpack_index is not None: # The only simple case we can handle if both tuples are variadic - # is when they are purely variadic. Other cases are tricky because + # is when their structure fully matches. Other cases are tricky because # a variadic item is effectively a union of tuples of all length, thus # potentially causing overlap between a suffix in `s` and a prefix # in `t` (see how this is handled in is_subtype() for details). # TODO: handle more cases (like when both prefix/suffix are shorter in s or t). - if s.length() == 1 and t.length() == 1: - s_unpack = s.items[0] + if s.length() == t.length() and s_unpack_index == t_unpack_index: + unpack_index = s_unpack_index + s_unpack = s.items[unpack_index] assert isinstance(s_unpack, UnpackType) s_unpacked = get_proper_type(s_unpack.type) - t_unpack = t.items[0] + t_unpack = t.items[unpack_index] assert isinstance(t_unpack, UnpackType) t_unpacked = get_proper_type(t_unpack.type) if not (isinstance(s_unpacked, Instance) and isinstance(t_unpacked, Instance)): @@ -886,7 +887,13 @@ def meet_tuples(self, s: TupleType, t: TupleType) -> list[Type] | None: meet = self.meet(s_unpacked, t_unpacked) if not isinstance(meet, Instance): return None - return [UnpackType(meet)] + m_prefix: list[Type] = [] + for si, ti in zip(s.items[:unpack_index], t.items[:unpack_index]): + m_prefix.append(meet_types(si, ti)) + m_suffix: list[Type] = [] + for si, ti in zip(s.items[unpack_index + 1 :], t.items[unpack_index + 1 :]): + m_suffix.append(meet_types(si, ti)) + return m_prefix + [UnpackType(meet)] + m_suffix return None if s_unpack_index is not None: variadic = s @@ -1006,11 +1013,11 @@ def default(self, typ: Type) -> ProperType: def meet_similar_callables(t: CallableType, s: CallableType) -> CallableType: - from mypy.join import join_types + from mypy.join import safe_join arg_types: list[Type] = [] for i in range(len(t.arg_types)): - arg_types.append(join_types(t.arg_types[i], s.arg_types[i])) + arg_types.append(safe_join(t.arg_types[i], s.arg_types[i])) # TODO in combine_similar_callables also applies here (names and kinds) # The fallback type can be either 'function' or 'type'. The result should have 'function' as # fallback only if both operands have it as 'function'. diff --git a/mypy/semanal_shared.py b/mypy/semanal_shared.py index 425e5906926a..e8edfe65c8d4 100644 --- a/mypy/semanal_shared.py +++ b/mypy/semanal_shared.py @@ -45,6 +45,8 @@ TypeOfAny, TypeVarId, TypeVarLikeType, + TypeVarTupleType, + UnpackType, get_proper_type, ) @@ -286,7 +288,23 @@ def calculate_tuple_fallback(typ: TupleType) -> None: """ fallback = typ.partial_fallback assert fallback.type.fullname == "builtins.tuple" - fallback.args = (join.join_type_list(list(typ.items)),) + fallback.args[1:] + items = [] + for item in typ.items: + # TODO: this duplicates some logic in typeops.tuple_fallback(). + if isinstance(item, UnpackType): + unpacked_type = get_proper_type(item.type) + if isinstance(unpacked_type, TypeVarTupleType): + unpacked_type = get_proper_type(unpacked_type.upper_bound) + if ( + isinstance(unpacked_type, Instance) + and unpacked_type.type.fullname == "builtins.tuple" + ): + items.append(unpacked_type.args[0]) + else: + raise NotImplementedError + else: + items.append(item) + fallback.args = (join.join_type_list(items),) class _NamedTypeCallback(Protocol): diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 383e6eddd317..6d129683c3f5 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -734,9 +734,13 @@ def visit_tuple_type(self, left: TupleType) -> bool: for li in left.items: if isinstance(li, UnpackType): unpack = get_proper_type(li.type) - if isinstance(unpack, Instance): - assert unpack.type.fullname == "builtins.tuple" - li = unpack.args[0] + if isinstance(unpack, TypeVarTupleType): + unpack = get_proper_type(unpack.upper_bound) + assert ( + isinstance(unpack, Instance) + and unpack.type.fullname == "builtins.tuple" + ) + li = unpack.args[0] if not self._is_subtype(li, iter_type): return False return True @@ -1578,6 +1582,18 @@ def are_parameters_compatible( return True trivial_suffix = is_trivial_suffix(right) and not is_proper_subtype + if ( + right.arg_kinds == [ARG_STAR] + and isinstance(get_proper_type(right.arg_types[0]), AnyType) + and not is_proper_subtype + ): + # Similar to how (*Any, **Any) is considered a supertype of all callables, we consider + # (*Any) a supertype of all callables with positional arguments. This is needed in + # particular because we often refuse to try type inference if actual type is not + # a subtype of erased template type. + if all(k.is_positional() for k in left.arg_kinds) and ignore_pos_arg_names: + return True + # Match up corresponding arguments and check them for compatibility. In # every pair (argL, argR) of corresponding arguments from L and R, argL must # be "more general" than argR if L is to be a subtype of R. diff --git a/mypy/typeops.py b/mypy/typeops.py index dff43775fe3d..2eb3b284e729 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -39,6 +39,7 @@ Instance, LiteralType, NoneType, + NormalizedCallableType, Overloaded, Parameters, ParamSpecType, @@ -364,7 +365,7 @@ def erase_to_bound(t: Type) -> Type: def callable_corresponding_argument( - typ: CallableType | Parameters, model: FormalArgument + typ: NormalizedCallableType | Parameters, model: FormalArgument ) -> FormalArgument | None: """Return the argument a function that corresponds to `model`""" diff --git a/mypy/types_utils.py b/mypy/types_utils.py index f289ac3e9ed1..1cd56eae5835 100644 --- a/mypy/types_utils.py +++ b/mypy/types_utils.py @@ -144,8 +144,7 @@ def store_argument_type( elif isinstance(arg_type, UnpackType): unpacked_type = get_proper_type(arg_type.type) if isinstance(unpacked_type, TupleType): - # Instead of using Tuple[Unpack[Tuple[...]]], just use - # Tuple[...] + # Instead of using Tuple[Unpack[Tuple[...]]], just use Tuple[...] arg_type = unpacked_type elif ( isinstance(unpacked_type, Instance) @@ -153,6 +152,7 @@ def store_argument_type( ): arg_type = unpacked_type else: + # TODO: verify that we can only have a TypeVarTuple here. arg_type = TupleType( [arg_type], fallback=named_type("builtins.tuple", [named_type("builtins.object", [])]), diff --git a/mypy/typevars.py b/mypy/typevars.py index 027a8e3f7fc5..3d74a40c303f 100644 --- a/mypy/typevars.py +++ b/mypy/typevars.py @@ -6,6 +6,7 @@ AnyType, Instance, ParamSpecType, + ProperType, TupleType, Type, TypeOfAny, @@ -55,6 +56,7 @@ def fill_typevars(typ: TypeInfo) -> Instance | TupleType: ) tvs.append(tv) inst = Instance(typ, tvs) + # TODO: do we need to also handle typeddict_type here and below? if typ.tuple_type is None: return inst return typ.tuple_type.copy_modified(fallback=inst) @@ -62,10 +64,23 @@ def fill_typevars(typ: TypeInfo) -> Instance | TupleType: def fill_typevars_with_any(typ: TypeInfo) -> Instance | TupleType: """Apply a correct number of Any's as type arguments to a type.""" - inst = Instance(typ, [AnyType(TypeOfAny.special_form)] * len(typ.defn.type_vars)) + args: list[Type] = [] + for tv in typ.defn.type_vars: + # Valid erasure for *Ts is *tuple[Any, ...], not just Any. + if isinstance(tv, TypeVarTupleType): + args.append( + UnpackType(tv.tuple_fallback.copy_modified(args=[AnyType(TypeOfAny.special_form)])) + ) + else: + args.append(AnyType(TypeOfAny.special_form)) + inst = Instance(typ, args) if typ.tuple_type is None: return inst - return typ.tuple_type.copy_modified(fallback=inst) + erased_tuple_type = erase_typevars(typ.tuple_type, {tv.id for tv in typ.defn.type_vars}) + assert isinstance(erased_tuple_type, ProperType) + if isinstance(erased_tuple_type, TupleType): + return typ.tuple_type.copy_modified(fallback=inst) + return inst def has_no_typevars(typ: Type) -> bool: diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test index f2625b869c19..eb7a795f99c0 100644 --- a/test-data/unit/check-incremental.test +++ b/test-data/unit/check-incremental.test @@ -6454,6 +6454,49 @@ class C(Generic[P]): def __init__(self, fn: Callable[P, int]) -> None: ... [builtins fixtures/dict.pyi] +[case testVariadicClassIncrementalUpdateRegularToVariadic] +from typing import Any +from lib import C + +x: C[int, str] + +[file lib.py] +from typing import Generic, TypeVar + +T = TypeVar("T") +S = TypeVar("S") +class C(Generic[T, S]): ... + +[file lib.py.2] +from typing import Generic +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +class C(Generic[Unpack[Ts]]): ... +[builtins fixtures/tuple.pyi] + +[case testVariadicClassIncrementalUpdateVariadicToRegular] +from typing import Any +from lib import C + +x: C[int, str, int] + +[file lib.py] +from typing import Generic +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +class C(Generic[Unpack[Ts]]): ... +[file lib.py.2] +from typing import Generic, TypeVar + +T = TypeVar("T") +S = TypeVar("S") +class C(Generic[T, S]): ... +[builtins fixtures/tuple.pyi] +[out2] +main:4: error: "C" expects 2 type arguments, but 3 given + [case testVariadicTupleIncrementalUpdateNoCrash] import m [file m.py] diff --git a/test-data/unit/check-python310.test b/test-data/unit/check-python310.test index 640e64c78d5f..d3cdf3af849d 100644 --- a/test-data/unit/check-python310.test +++ b/test-data/unit/check-python310.test @@ -700,6 +700,21 @@ match m: reveal_type(m) # N: Revealed type is "__main__.A[Any]" reveal_type(i) # N: Revealed type is "Any" +[case testMatchClassPatternCaptureVariadicGeneric] +from typing import Generic, Tuple +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple('Ts') +class A(Generic[Unpack[Ts]]): + a: Tuple[Unpack[Ts]] + +m: object +match m: + case A(a=i): + reveal_type(m) # N: Revealed type is "__main__.A[Unpack[builtins.tuple[Any, ...]]]" + reveal_type(i) # N: Revealed type is "builtins.tuple[Any, ...]" +[builtins fixtures/tuple.pyi] + [case testMatchClassPatternCaptureGenericAlreadyKnown] from typing import Generic, TypeVar @@ -2026,3 +2041,105 @@ def f4(e: int | str | bytes) -> int: return 0 [builtins fixtures/primitives.pyi] + +[case testMatchSequencePatternVariadicTupleNotTooShort] +from typing import Tuple +from typing_extensions import Unpack + +fm1: Tuple[int, int, Unpack[Tuple[str, ...]], int] +match fm1: + case [fa1, fb1, fc1]: + reveal_type(fa1) # N: Revealed type is "builtins.int" + reveal_type(fb1) # N: Revealed type is "builtins.int" + reveal_type(fc1) # N: Revealed type is "builtins.int" + +fm2: Tuple[int, int, Unpack[Tuple[str, ...]], int] +match fm2: + case [fa2, fb2]: + reveal_type(fa2) + reveal_type(fb2) + +fm3: Tuple[int, int, Unpack[Tuple[str, ...]], int] +match fm3: + case [fa3, fb3, fc3, fd3, fe3]: + reveal_type(fa3) # N: Revealed type is "builtins.int" + reveal_type(fb3) # N: Revealed type is "builtins.int" + reveal_type(fc3) # N: Revealed type is "builtins.str" + reveal_type(fd3) # N: Revealed type is "builtins.str" + reveal_type(fe3) # N: Revealed type is "builtins.int" + +m1: Tuple[int, Unpack[Tuple[str, ...]], int] +match m1: + case [a1, *b1, c1]: + reveal_type(a1) # N: Revealed type is "builtins.int" + reveal_type(b1) # N: Revealed type is "builtins.list[builtins.str]" + reveal_type(c1) # N: Revealed type is "builtins.int" + +m2: Tuple[int, Unpack[Tuple[str, ...]], int] +match m2: + case [a2, b2, *c2, d2, e2]: + reveal_type(a2) # N: Revealed type is "builtins.int" + reveal_type(b2) # N: Revealed type is "builtins.str" + reveal_type(c2) # N: Revealed type is "builtins.list[builtins.str]" + reveal_type(d2) # N: Revealed type is "builtins.str" + reveal_type(e2) # N: Revealed type is "builtins.int" + +m3: Tuple[int, int, Unpack[Tuple[str, ...]], int, int] +match m3: + case [a3, *b3, c3]: + reveal_type(a3) # N: Revealed type is "builtins.int" + reveal_type(b3) # N: Revealed type is "builtins.list[Union[builtins.int, builtins.str]]" + reveal_type(c3) # N: Revealed type is "builtins.int" +[builtins fixtures/tuple.pyi] + +[case testMatchSequencePatternTypeVarTupleNotTooShort] +from typing import Tuple +from typing_extensions import Unpack, TypeVarTuple + +Ts = TypeVarTuple("Ts") +def test(xs: Tuple[Unpack[Ts]]) -> None: + fm1: Tuple[int, int, Unpack[Ts], int] + match fm1: + case [fa1, fb1, fc1]: + reveal_type(fa1) # N: Revealed type is "builtins.int" + reveal_type(fb1) # N: Revealed type is "builtins.int" + reveal_type(fc1) # N: Revealed type is "builtins.int" + + fm2: Tuple[int, int, Unpack[Ts], int] + match fm2: + case [fa2, fb2]: + reveal_type(fa2) + reveal_type(fb2) + + fm3: Tuple[int, int, Unpack[Ts], int] + match fm3: + case [fa3, fb3, fc3, fd3, fe3]: + reveal_type(fa3) # N: Revealed type is "builtins.int" + reveal_type(fb3) # N: Revealed type is "builtins.int" + reveal_type(fc3) # N: Revealed type is "builtins.object" + reveal_type(fd3) # N: Revealed type is "builtins.object" + reveal_type(fe3) # N: Revealed type is "builtins.int" + + m1: Tuple[int, Unpack[Ts], int] + match m1: + case [a1, *b1, c1]: + reveal_type(a1) # N: Revealed type is "builtins.int" + reveal_type(b1) # N: Revealed type is "builtins.list[builtins.object]" + reveal_type(c1) # N: Revealed type is "builtins.int" + + m2: Tuple[int, Unpack[Ts], int] + match m2: + case [a2, b2, *c2, d2, e2]: + reveal_type(a2) # N: Revealed type is "builtins.int" + reveal_type(b2) # N: Revealed type is "builtins.object" + reveal_type(c2) # N: Revealed type is "builtins.list[builtins.object]" + reveal_type(d2) # N: Revealed type is "builtins.object" + reveal_type(e2) # N: Revealed type is "builtins.int" + + m3: Tuple[int, int, Unpack[Ts], int, int] + match m3: + case [a3, *b3, c3]: + reveal_type(a3) # N: Revealed type is "builtins.int" + reveal_type(b3) # N: Revealed type is "builtins.list[builtins.object]" + reveal_type(c3) # N: Revealed type is "builtins.int" +[builtins fixtures/tuple.pyi] diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test index bf7a928ff51d..29abe9cb025b 100644 --- a/test-data/unit/check-selftype.test +++ b/test-data/unit/check-selftype.test @@ -2036,3 +2036,23 @@ class Ben(Object): foo_method = cls.MY_MAP["foo"] return foo_method(Foo()) [builtins fixtures/isinstancelist.pyi] + +[case testSelfTypeOnGenericClassObjectNewStyleBound] +from typing import Generic, TypeVar, Self + +T = TypeVar("T") +S = TypeVar("S") +class B(Generic[T, S]): + def copy(self) -> Self: ... + +b: B[int, str] +reveal_type(B.copy(b)) # N: Revealed type is "__main__.B[builtins.int, builtins.str]" + +class C(B[T, S]): ... + +c: C[int, str] +reveal_type(C.copy(c)) # N: Revealed type is "__main__.C[builtins.int, builtins.str]" + +B.copy(42) # E: Value of type variable "Self" of "copy" of "B" cannot be "int" +C.copy(42) # E: Value of type variable "Self" of "copy" of "B" cannot be "int" +[builtins fixtures/tuple.pyi] diff --git a/test-data/unit/check-typevar-tuple.test b/test-data/unit/check-typevar-tuple.test index 1a2573898170..7b8a22313b36 100644 --- a/test-data/unit/check-typevar-tuple.test +++ b/test-data/unit/check-typevar-tuple.test @@ -1911,6 +1911,180 @@ z = C[int]() # E: Bad number of arguments, expected: at least 2, given: 1 reveal_type(z) # N: Revealed type is "__main__.C[Any, Unpack[builtins.tuple[Any, ...]], Any]" [builtins fixtures/tuple.pyi] +[case testVariadicTupleTupleSubclassPrefixSuffix] +from typing import Tuple +from typing_extensions import Unpack + +i: int + +class A(Tuple[int, Unpack[Tuple[int, ...]]]): ... +a: A +reveal_type(a[i]) # N: Revealed type is "builtins.int" + +class B(Tuple[Unpack[Tuple[int, ...]], int]): ... +b: B +reveal_type(b[i]) # N: Revealed type is "builtins.int" +[builtins fixtures/tuple.pyi] + +[case testVariadicClassSubclassInit] +from typing import Tuple, Generic, TypeVar +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +class B(Generic[Unpack[Ts]]): + def __init__(self, x: Tuple[Unpack[Ts]], *args: Unpack[Ts]) -> None: ... +reveal_type(B) # N: Revealed type is "def [Ts] (x: Tuple[Unpack[Ts`1]], *args: Unpack[Ts`1]) -> __main__.B[Unpack[Ts`1]]" + +T = TypeVar("T") +S = TypeVar("S") +class C(B[T, S]): ... +reveal_type(C) # N: Revealed type is "def [T, S] (x: Tuple[T`1, S`2], T`1, S`2) -> __main__.C[T`1, S`2]" +[builtins fixtures/tuple.pyi] + +[case testVariadicClassGenericSelf] +from typing import Tuple, Generic, TypeVar +from typing_extensions import TypeVarTuple, Unpack + +T = TypeVar("T") +S = TypeVar("S") +Ts = TypeVarTuple("Ts") +class B(Generic[Unpack[Ts]]): + def copy(self: T) -> T: ... + def on_pair(self: B[T, S]) -> Tuple[T, S]: ... + +b1: B[int] +reveal_type(b1.on_pair()) # E: Invalid self argument "B[int]" to attribute function "on_pair" with type "Callable[[B[T, S]], Tuple[T, S]]" \ + # N: Revealed type is "Tuple[Never, Never]" +b2: B[int, str] +reveal_type(b2.on_pair()) # N: Revealed type is "Tuple[builtins.int, builtins.str]" +b3: B[int, str, int] +reveal_type(b3.on_pair()) # E: Invalid self argument "B[int, str, int]" to attribute function "on_pair" with type "Callable[[B[T, S]], Tuple[T, S]]" \ + # N: Revealed type is "Tuple[Never, Never]" + +class C(B[T, S]): ... +c: C[int, str] +reveal_type(c.copy()) # N: Revealed type is "__main__.C[builtins.int, builtins.str]" +[builtins fixtures/tuple.pyi] + +[case testVariadicClassNewStyleSelf] +from typing import Generic, TypeVar, Self +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +class B(Generic[Unpack[Ts]]): + next: Self + def copy(self) -> Self: + return self.next + +b: B[int, str, int] +reveal_type(b.next) # N: Revealed type is "__main__.B[builtins.int, builtins.str, builtins.int]" +reveal_type(b.copy()) # N: Revealed type is "__main__.B[builtins.int, builtins.str, builtins.int]" +reveal_type(B.copy(b)) # N: Revealed type is "__main__.B[builtins.int, builtins.str, builtins.int]" + +T = TypeVar("T") +S = TypeVar("S") +class C(B[T, S]): ... +c: C[int, str] + +reveal_type(c.next) # N: Revealed type is "__main__.C[builtins.int, builtins.str]" +reveal_type(c.copy()) # N: Revealed type is "__main__.C[builtins.int, builtins.str]" +reveal_type(C.copy(c)) # N: Revealed type is "__main__.C[builtins.int, builtins.str]" +[builtins fixtures/tuple.pyi] + +[case testVariadicTupleDataclass] +from dataclasses import dataclass +from typing import Generic, TypeVar, Tuple +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") + +@dataclass +class B(Generic[Unpack[Ts]]): + items: Tuple[Unpack[Ts]] + +reveal_type(B) # N: Revealed type is "def [Ts] (items: Tuple[Unpack[Ts`1]]) -> __main__.B[Unpack[Ts`1]]" +b = B((1, "yes")) +reveal_type(b.items) # N: Revealed type is "Tuple[builtins.int, builtins.str]" + +T = TypeVar("T") +S = TypeVar("S") + +@dataclass +class C(B[T, S]): + first: T + second: S + +reveal_type(C) # N: Revealed type is "def [T, S] (items: Tuple[T`1, S`2], first: T`1, second: S`2) -> __main__.C[T`1, S`2]" +c = C((1, "yes"), 2, "no") +reveal_type(c.items) # N: Revealed type is "Tuple[builtins.int, builtins.str]" +reveal_type(c.first) # N: Revealed type is "builtins.int" +reveal_type(c.second) # N: Revealed type is "builtins.str" +[builtins fixtures/dataclasses.pyi] +[typing fixtures/typing-medium.pyi] + +[case testVariadicTupleInProtocol] +from typing import Protocol, Tuple, List +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +class P(Protocol[Unpack[Ts]]): + def items(self) -> Tuple[Unpack[Ts]]: ... + +class PC(Protocol[Unpack[Ts]]): + def meth(self, *args: Unpack[Ts]) -> None: ... + +def get_items(x: P[Unpack[Ts]]) -> Tuple[Unpack[Ts]]: ... +def match(x: PC[Unpack[Ts]]) -> Tuple[Unpack[Ts]]: ... + +class Bad: + def items(self) -> List[int]: ... + def meth(self, *, named: int) -> None: ... + +class Good: + def items(self) -> Tuple[int, str]: ... + def meth(self, __x: int, y: str) -> None: ... + +g: Good +reveal_type(get_items(g)) # N: Revealed type is "Tuple[builtins.int, builtins.str]" +reveal_type(match(g)) # N: Revealed type is "Tuple[builtins.int, builtins.str]" + +b: Bad +get_items(b) # E: Argument 1 to "get_items" has incompatible type "Bad"; expected "P[Unpack[Tuple[Never, ...]]]" \ + # N: Following member(s) of "Bad" have conflicts: \ + # N: Expected: \ + # N: def items(self) -> Tuple[Never, ...] \ + # N: Got: \ + # N: def items(self) -> List[int] +match(b) # E: Argument 1 to "match" has incompatible type "Bad"; expected "PC[Unpack[Tuple[Never, ...]]]" \ + # N: Following member(s) of "Bad" have conflicts: \ + # N: Expected: \ + # N: def meth(self, *args: Never) -> None \ + # N: Got: \ + # N: def meth(self, *, named: int) -> None +[builtins fixtures/tuple.pyi] + +[case testVariadicTupleCollectionCheck] +from typing import Tuple, Optional +from typing_extensions import Unpack + +allowed: Tuple[int, Unpack[Tuple[int, ...]]] + +x: Optional[int] +if x in allowed: + reveal_type(x) # N: Revealed type is "builtins.int" +[builtins fixtures/tuple.pyi] + +[case testJoinOfVariadicTupleCallablesNoCrash] +from typing import Callable, Tuple + +f: Callable[[int, *Tuple[str, ...], int], None] +g: Callable[[int, *Tuple[str, ...], int], None] +reveal_type([f, g]) # N: Revealed type is "builtins.list[def (builtins.int, *Unpack[Tuple[Unpack[builtins.tuple[builtins.str, ...]], builtins.int]])]" + +h: Callable[[int, *Tuple[str, ...], str], None] +reveal_type([f, h]) # N: Revealed type is "builtins.list[def (builtins.int, *Unpack[Tuple[Unpack[builtins.tuple[builtins.str, ...]], Never]])]" +[builtins fixtures/tuple.pyi] + [case testTypeVarTupleBothUnpacksSimple] from typing import Tuple from typing_extensions import Unpack, TypeVarTuple, TypedDict diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index cb24467cbf41..5dc42bd62d9b 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -9908,6 +9908,128 @@ x = 0 # Arbitrary change to trigger reprocessing == a.py:3: note: Revealed type is "Tuple[Literal[1]?, Literal['x']?]" +[case testVariadicClassFineUpdateRegularToVariadic] +from typing import Any +from lib import C + +x: C[int, str] + +[file lib.py] +from typing import Generic, TypeVar + +T = TypeVar("T") +S = TypeVar("S") +class C(Generic[T, S]): ... + +[file lib.py.2] +from typing import Generic +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +class C(Generic[Unpack[Ts]]): ... +[builtins fixtures/tuple.pyi] +[out] +== + +[case testVariadicClassFineUpdateVariadicToRegular] +from typing import Any +from lib import C + +x: C[int, str, int] + +[file lib.py] +from typing import Generic +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +class C(Generic[Unpack[Ts]]): ... +[file lib.py.2] +from typing import Generic, TypeVar + +T = TypeVar("T") +S = TypeVar("S") +class C(Generic[T, S]): ... +[builtins fixtures/tuple.pyi] +[out] +== +main:4: error: "C" expects 2 type arguments, but 3 given + +-- Order of error messages is different, so we repeat the test twice. +[case testVariadicClassFineUpdateValidToInvalidCached-only_when_cache] +from typing import Any +from lib import C + +x: C[int, str] + +[file lib.py] +from typing import Generic +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +class C(Generic[Unpack[Ts]]): ... + +[file lib.py.2] +from typing import Generic +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +class C(Generic[Ts]): ... +[builtins fixtures/tuple.pyi] +[out] +== +main:4: error: "C" expects no type arguments, but 2 given +lib.py:5: error: Free type variable expected in Generic[...] + +[case testVariadicClassFineUpdateValidToInvalid-only_when_nocache] +from typing import Any +from lib import C + +x: C[int, str] + +[file lib.py] +from typing import Generic +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +class C(Generic[Unpack[Ts]]): ... + +[file lib.py.2] +from typing import Generic +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +class C(Generic[Ts]): ... +[builtins fixtures/tuple.pyi] +[out] +== +lib.py:5: error: Free type variable expected in Generic[...] +main:4: error: "C" expects no type arguments, but 2 given + +[case testVariadicClassFineUpdateInvalidToValid] +from typing import Any +from lib import C + +x: C[int, str] + +[file lib.py] +from typing import Generic +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +class C(Generic[Ts]): ... + +[file lib.py.2] +from typing import Generic +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +class C(Generic[Unpack[Ts]]): ... +[builtins fixtures/tuple.pyi] +[out] +lib.py:5: error: Free type variable expected in Generic[...] +main:4: error: "C" expects no type arguments, but 2 given +== + [case testUnpackKwargsUpdateFine] import m [file shared.py]