diff --git a/crates/ty_python_core/src/builder.rs b/crates/ty_python_core/src/builder.rs index 8602cd638da002..d05c9e3399dba9 100644 --- a/crates/ty_python_core/src/builder.rs +++ b/crates/ty_python_core/src/builder.rs @@ -4528,6 +4528,17 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { } } + // An augmented single-element assignment on the collection object. Slices + // produce another collection and require a separate constraint model. + ruff_python_ast::Stmt::AugAssign(ast::StmtAugAssign { target, .. }) => { + matches!( + target.as_ref(), + ast::Expr::Subscript(ast::ExprSubscript { value, slice, .. }) + if !matches!(slice.as_ref(), ast::Expr::Slice(_)) + && ExpressionNodeKey::from(value) == *use_expression + ) + } + // An annotated assignment assigning the collection object to a new binding. ruff_python_ast::Stmt::AnnAssign(_) => true, diff --git a/crates/ty_python_semantic/resources/mdtest/assignment/augmented.md b/crates/ty_python_semantic/resources/mdtest/assignment/augmented.md index 1787834ee654e3..323d0d7f94c17e 100644 --- a/crates/ty_python_semantic/resources/mdtest/assignment/augmented.md +++ b/crates/ty_python_semantic/resources/mdtest/assignment/augmented.md @@ -187,6 +187,329 @@ def f(flag: bool, flag2: bool): reveal_type(f) # revealed: float | str ``` +## Annotated name targets + +An augmented assignment to an annotated name must validate its result against the declaration. An +unannotated name can instead change type. + +```py +class Value: + def __add__(self, other: int) -> object: + return other + +annotated: Value = Value() +# error: [invalid-assignment] +annotated += 1 +reveal_type(annotated) # revealed: Value + +inferred = Value() +inferred += 1 +reveal_type(inferred) # revealed: object +``` + +## Attribute targets + +The result must satisfy the attribute's write contract, whether the operation uses `__iadd__` or +falls back to `__add__`. + +```py +class AddValue: + def __add__(self, other: int) -> object: + return other + +class InplaceValue: + def __iadd__(self, other: int) -> object: + return other + +class Holder: + add: AddValue + inplace: InplaceValue + +holder = Holder() +# error: [invalid-assignment] +holder.add += 1 +reveal_type(holder.add) # revealed: AddValue + +# error: [invalid-assignment] +holder.inplace += 1 +reveal_type(holder.inplace) # revealed: InplaceValue +``` + +## Inferred attribute targets in loops + +An inferred attribute can change type across assignments. Its initial value must not become a +declaration that pollutes the loop-carried type after the attribute has been reassigned. + +```py +class Counter: + def update(self, increment: float) -> None: + self.value = None + self.value = 0 + for _ in range(1): + self.value += increment + +reveal_type(Counter().value) # revealed: None | float +``` + +## Inferred public attribute targets + +An inferred class attribute has the same public write contract for augmented and ordinary +assignments. + +```py +class Holder: + value = 1 + +holder = Holder() +# error: [invalid-assignment] +holder.value += 0.5 +``` + +## Attribute descriptors + +Even an in-place operation writes its result back, so a read-only property rejects augmented +assignment. + +```py +class ReadOnly: + @property + def value(self) -> int: + return 1 + +read_only = ReadOnly() +# error: [invalid-assignment] +read_only.value += 1 +reveal_type(read_only.value) # revealed: int +``` + +A property's setter can accept a different type than its getter returns. The operation's result must +satisfy the setter, while later reads continue to use the getter type. + +```py +class ReadValue: + def __iadd__(self, other: int) -> str: + return "updated" + +class Writable: + @property + def value(self) -> ReadValue: + return ReadValue() + + @value.setter + def value(self, value: str) -> None: + pass + +writable = Writable() +writable.value += 1 +reveal_type(writable.value) # revealed: ReadValue +``` + +Unannotated data descriptors still impose the write contract declared by their setter. + +```py +class Descriptor: + def __get__(self, instance: object, owner: type[object] | None = None) -> int: + return 1 + + def __set__(self, instance: object, value: str) -> None: + pass + +class Custom: + value = Descriptor() + +custom = Custom() +# error: [invalid-assignment] +custom.value += 1 +``` + +## Subscript targets + +An augmented subscript assignment must pass its result, not the operator's right-hand operand, to +the target's `__setitem__` method. + +```py +class Value: + def __iadd__(self, other: int) -> object: + return other + +class Container: + def __getitem__(self, key: int) -> Value: + return Value() + + def __setitem__(self, key: int, value: Value) -> None: + pass + +container = Container() +# error: [invalid-assignment] +container[0] += 1 +reveal_type(container[0]) # revealed: Value +``` + +A custom setter may accept a broader type than its getter returns. + +```py +class PermissiveContainer: + def __getitem__(self, key: int) -> Value: + return Value() + + def __setitem__(self, key: int, value: object) -> None: + pass + +permissive = PermissiveContainer() +permissive[0] += 1 +reveal_type(permissive[0]) # revealed: Value +``` + +Explicitly annotated lists and dictionaries retain their write contracts. + +```py +items: list[Value] = [Value()] +# error: [invalid-assignment] +items[0] += 1 +reveal_type(items[0]) # revealed: Value + +mapping: dict[str, Value] = {"value": Value()} +# error: [invalid-assignment] +mapping["value"] += 1 +reveal_type(mapping["value"]) # revealed: Value +``` + +Declared collection-valued attributes also retain their write contracts. + +```py +class Holder: + values: list[Value] + +holder = Holder() +# error: [invalid-assignment] +holder.values[0] += 1 +``` + +Typed dictionary entries validate the value written back to their declared fields. + +```py +from typing import TypedDict + +class Payload(TypedDict): + value: Value + +payload: Payload = {"value": Value()} +# error: [invalid-assignment] +payload["value"] += 1 +reveal_type(payload["value"]) # revealed: Value +``` + +## Read-only subscripts + +A readable subscript is not necessarily writable. + +```py +values: tuple[int] = (1,) +# error: [invalid-assignment] +values[0] += 1 +``` + +## Failed attribute and subscript loads + +Both the load and the store are checked, just as they are for an ordinary assignment whose value +reads the same target. + +```py +class Missing: ... + +missing = Missing() +# error: [unresolved-attribute] +# error: [unresolved-attribute] +missing.value += 1 + +mapping: dict[str, int] = {} +# error: [invalid-argument-type] +# error: [invalid-assignment] +mapping[1] += 1 +``` + +## Failed augmented operations + +An operation that cannot run does not perform a store. + +```py +class Value: + def __iadd__(self, other: int) -> object: + return other + +class Holder: + value: Value + +holder = Holder() +# error: [unsupported-operator] +holder.value += "invalid" +``` + +## Correlated union targets + +The result of an operation on one union member must not be checked against another member's write +contract. + +```py +class AValue: + def __iadd__(self, other: int) -> "AValue": + return self + +class BValue: + def __iadd__(self, other: int) -> "BValue": + return self + +class A: + value: AValue + +class B: + value: BValue + +def update(value: A | B) -> None: + # TODO: Preserve receiver correlation, which is also lost in ordinary assignments. + # error: [invalid-assignment] + value.value += 1 +``` + +## Union subscript targets + +An augmented assignment must reject a union alternative that does not support the write. + +```py +def update(value: list[int] | tuple[int, ...]) -> None: + # error: [invalid-assignment] + value[0] += 1 +``` + +## Union subscript keys + +Each possible typed-dictionary key must accept the value written by the augmented assignment. + +```py +from typing import Literal, TypedDict + +class Payload(TypedDict): + first: int + second: int + +def update(value: Payload, key: Literal["first", "second"]) -> None: + value[key] += 1 + + # error: [invalid-assignment] + # error: [invalid-assignment] + value[key] /= 2 +``` + +## Inferred collection targets + +Augmented subscript assignments contribute their operator result to full-scope collection inference. + +```py +values = [1] +values[0] /= 2 +reveal_type(values) # revealed: list[float] +``` + ## Implicit dunder calls on class objects ```py diff --git a/crates/ty_python_semantic/resources/mdtest/attributes.md b/crates/ty_python_semantic/resources/mdtest/attributes.md index 03e5a1dfca79b4..8467866cf6e9b7 100644 --- a/crates/ty_python_semantic/resources/mdtest/attributes.md +++ b/crates/ty_python_semantic/resources/mdtest/attributes.md @@ -269,9 +269,8 @@ class C: self.w = Weird() self.w += None -# TODO: Mypy and pyright do not support this, but it would be great if we could -# infer `str` here (`Weird` is not a possible type for the `w` attribute). -reveal_type(C().w) # revealed: Weird +# TODO: Infer `str` alone, since the initial `Weird` value has been overwritten. +reveal_type(C().w) # revealed: Weird | str ``` #### Nested augmented assignments after narrowing diff --git a/crates/ty_python_semantic/resources/mdtest/bidirectional.md b/crates/ty_python_semantic/resources/mdtest/bidirectional.md index c03a3df57bf96c..59a072d7d6b867 100644 --- a/crates/ty_python_semantic/resources/mdtest/bidirectional.md +++ b/crates/ty_python_semantic/resources/mdtest/bidirectional.md @@ -2424,6 +2424,141 @@ x24[1] = "b" reveal_type(x24) # revealed: dict[int | str, str | int] ``` +## Augmented subscript collection inference + +An augmented subscript assignment constrains an inferred list using the operator's result, not its +right-hand operand. + +```py +integers = [0] +integers[0] += 1.0 +reveal_type(integers) # revealed: list[float] + +values = [1] +values[0] /= 2 +reveal_type(values) # revealed: list[float] +``` + +Augmented assignments to inferred dictionary entries similarly constrain the value type while +preserving the key type. + +```py +mapping = {"value": 1} +mapping["value"] /= 2 +reveal_type(mapping) # revealed: dict[str, float] +``` + +Custom operators can produce a value unrelated to either the original element or the operand. + +```py +class Updated: + def __add__(self, other: int) -> "Updated": + return self + +class Initial: + def __add__(self, other: int) -> Updated: + return Updated() + +custom = [Initial()] +custom[0] += 1 +reveal_type(custom) # revealed: list[Updated | Initial] +``` + +Augmented subscript assignments also combine with the existing constraints on empty collection +constructors. + +```py +constructed = list() +constructed.append(1) +constructed[0] /= 2 +reveal_type(constructed) # revealed: list[float] + +constructed_mapping = dict() +constructed_mapping["value"] = 1 +constructed_mapping["value"] /= 2 +reveal_type(constructed_mapping) # revealed: dict[str, float] +``` + +The same collection may also appear in the right-hand operand. + +```py +self_referential = [1] +self_referential[0] /= self_referential[0] +reveal_type(self_referential) # revealed: list[float] +``` + +An existing augmented slice assignment must not lose its inferred element type. + +```py +sliced = [1] +sliced[:] += [2] +reveal_type(sliced) # revealed: list[int] + +dynamic_slice = [1] +dynamic_slice[slice(1)] += [2] +reveal_type(dynamic_slice) # revealed: list[int] +``` + +A failed operator does not widen the collection or suppress its original diagnostic. + +```py +invalid = [1] +# error: [unsupported-operator] +invalid[0] += "value" +reveal_type(invalid) # revealed: list[int] +``` + +An invalid index likewise cannot contribute a value constraint. + +```py +invalid_index = [1] +# error: [invalid-argument-type] +# error: [invalid-assignment] +invalid_index["value"] /= 2 +reveal_type(invalid_index) # revealed: list[int] +``` + +An explicit annotation still fixes the collection's element type. + +```py +annotated: list[int] = [1] +# error: [invalid-assignment] +annotated[0] /= 2 +reveal_type(annotated) # revealed: list[int] +``` + +## Augmented subscript inference for nested comprehensions + +Collections nested inside a comprehension do not yet participate in full-scope inference through the +outer collection. + +```py +coverage = {key: [0] for key in ["value"]} +# TODO: Widen the nested list to `list[float]` instead of rejecting the assignment. +# error: [invalid-assignment] +coverage["value"][0] += 1.0 +reveal_type(coverage) # revealed: dict[str, list[int]] +``` + +## Rejected ordinary writes do not constrain collections + +An invalid ordinary subscript assignment must not widen an inferred collection and introduce +additional errors at earlier reads or later returns. + +```py +def accepts_strings(first: str, second: str, values: list[str]) -> None: ... +def example(condition: bool, value: str | None) -> list[str]: + if condition: + values = ["initial"] + ["second"] + else: + values = ["initial"] + + accepts_strings(values[0], values[0], [values[0]]) + # error: [invalid-assignment] + values[0] = value + return values +``` + ## Multi-inference diagnostics Diagnostics unrelated to the type-context are only reported once: diff --git a/crates/ty_python_semantic/src/types/class/static_literal.rs b/crates/ty_python_semantic/src/types/class/static_literal.rs index e36a3935af95c1..b94428ddf531b0 100644 --- a/crates/ty_python_semantic/src/types/class/static_literal.rs +++ b/crates/ty_python_semantic/src/types/class/static_literal.rs @@ -48,7 +48,7 @@ use crate::{ is_implicit_staticmethod, }, generics::Specialization, - infer::infer_unpack_types, + infer::{infer_definition_types, infer_unpack_types}, infer_expression_type, inferred_declaration, known_instance::DeprecatedInstance, member::{Member, class_member}, @@ -3079,8 +3079,7 @@ impl<'db> StaticClassLiteral<'db> { } } DefinitionKind::AugmentedAssignment(_) => { - // TODO: - None + Some(infer_definition_types(db, binding).binding_type(binding)) } DefinitionKind::NamedExpression(_) => { // A named expression whose target is an attribute is syntactically prohibited diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs index b8c5448e318ce2..4f47f87fb1de99 100644 --- a/crates/ty_python_semantic/src/types/infer/builder.rs +++ b/crates/ty_python_semantic/src/types/infer/builder.rs @@ -87,6 +87,7 @@ use crate::types::generics::{ }; use crate::types::infer::builder::named_tuple::NamedTupleKind; use crate::types::infer::builder::paramspec_validation::validate_paramspec_components; +use crate::types::infer::builder::subscript::SubscriptAssignmentKind; use crate::types::infer::{ StatementInference, StatementInferenceInner, StatementInferenceInnerExtra, TypeAndRange, TypeExpressionFlags, infer_statement_types, nearest_enclosing_class, @@ -1485,7 +1486,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { fn fallback_member_declared_type(&mut self, node: AnyNodeRef<'_>) -> Option> { let db = self.db(); if let AnyNodeRef::ExprAttribute(ast::ExprAttribute { value, attr, .. }) = node { - let value_type = self.infer_maybe_standalone_expression(value, TypeContext::default()); + let value_type = self.try_expression_type(value).unwrap_or_else(|| { + self.infer_maybe_standalone_expression(value, TypeContext::default()) + }); if let Place::Defined(DefinedPlace { ty, definedness: Definedness::AlwaysDefined, @@ -1505,8 +1508,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { }, ) = node { - let value_ty = self.infer_expression(value, TypeContext::default()); - let slice_ty = self.infer_expression(slice, TypeContext::default()); + let value_ty = self.get_or_infer_expression(value, TypeContext::default()); + let slice_ty = self.get_or_infer_expression(slice, TypeContext::default()); Some(self.infer_subscript_expression_types(subscript, value_ty, slice_ty, *ctx)) } else { None @@ -3213,13 +3216,25 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } ast::Expr::Subscript(subscript_expr) => { if let Some(infer_assigned_ty) = infer_assigned_ty { + let object_ty = + self.infer_expression(&subscript_expr.value, TypeContext::default()); + let mut infer_slice_ty = |builder: &mut Self, tcx| { + builder.infer_expression(&subscript_expr.slice, tcx) + }; let infer_assigned_ty = &mut |builder: &mut Self, tcx| { let assigned_ty = infer_assigned_ty(builder, tcx); builder.store_expression_type(target, assigned_ty); assigned_ty }; - self.validate_subscript_assignment(subscript_expr, value, infer_assigned_ty); + self.validate_subscript_assignment( + subscript_expr, + value, + SubscriptAssignmentKind::Ordinary, + object_ty, + &mut infer_slice_ty, + infer_assigned_ty, + ); } } @@ -4630,23 +4645,60 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { self.infer_definition(assignment); } else { // Non-name assignment targets are inferred as ordinary expressions, not definitions. - self.infer_augment_assignment(assignment); + // A divergent result is provisional cycle recovery, not a value that can constrain a + // collection initializer or satisfy a concrete write contract. + if let Ok(result_ty) = self.infer_augment_assignment(assignment) + && !any_over_type( + self.db(), + self.program_environment(), + result_ty, + false, + |ty| ty.is_divergent(), + ) + { + let target = assignment.target.as_ref(); + match target { + ast::Expr::Attribute(attribute) => { + let object_ty = self.expression_type(&attribute.value); + self.validate_attribute_assignment( + attribute, + target, + object_ty, + attribute.attr.id(), + &mut |_, _| result_ty, + true, + ); + } + ast::Expr::Subscript(subscript) => { + let object_ty = self.expression_type(&subscript.value); + let slice_ty = self.expression_type(&subscript.slice); + self.validate_subscript_assignment( + subscript, + target, + SubscriptAssignmentKind::Augmented, + object_ty, + &mut |_, _| slice_ty, + &mut |_, _| result_ty, + ); + } + _ => {} + } + } if let ast::Expr::Attribute(attr_expr) = assignment.target.as_ref() { - let object_ty = self.expression_type(&attr_expr.value); self.report_undeclared_protocol_attribute(attr_expr); - self.validate_final_attribute_assignment(attr_expr, object_ty, attr_expr.attr.id()); } } } + /// Infer an augmented operator, returning its recovery type if the operation fails. fn infer_augmented_op( &mut self, assignment: &ast::StmtAugAssign, target_type: Type<'db>, value_expr: &ast::Expr, infer_value_ty: &mut dyn FnMut(&mut Self, TypeContext<'db>) -> Type<'db>, - ) -> Type<'db> { + ) -> Result, Type<'db>> { let db = self.db(); let env = self.program_environment(); // If the target defines, e.g., `__iadd__`, infer the augmented assignment as a call to that @@ -4657,7 +4709,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let binary_return_ty = |builder: &mut Self, value_ty| { builder .infer_binary_expression_type(assignment.into(), false, target_type, value_ty, op) - .unwrap_or_else(|| { + .ok_or_else(|| { report_unsupported_augmented_assignment( &builder.context, assignment, @@ -4676,14 +4728,27 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { // equally applicable type contexts for each union member. infer_value_ty.infer_loud(self, TypeContext::default()); - union.map(db, env, |&elem_type| { - self.infer_augmented_op( + let mut operation_failed = false; + let result_ty = union.map(db, env, |&elem_type| { + match self.infer_augmented_op( assignment, elem_type, value_expr, &mut |builder, tcx| infer_value_ty.infer_silent(builder, tcx), - ) - }) + ) { + Ok(ty) => ty, + Err(recovery_ty) => { + operation_failed = true; + recovery_ty + } + } + }); + + if operation_failed { + Err(result_ty) + } else { + Ok(result_ty) + } } _ => { @@ -4695,7 +4760,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { infer_value_ty, ) { - return typed_dict_update_ty; + return Ok(typed_dict_update_ty); } let ast_arguments = [ArgOrKeyword::Arg(value_expr)]; @@ -4711,7 +4776,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { TypeContext::default(), ); match call { - Ok(outcome) => outcome.return_type(db, env), + Ok(outcome) => Ok(outcome.return_type(db, env)), Err(CallDunderError::MethodNotAvailable) => { let value_ty = infer_value_ty(self, TypeContext::default()); binary_return_ty(self, value_ty) @@ -4720,12 +4785,20 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { bindings: outcome, .. }) => { let value_ty = outcome.type_for_argument(&call_arguments, 0); - UnionType::from_two_elements( - db, - env, - outcome.return_type(db, env), - binary_return_ty(self, value_ty), - ) + match binary_return_ty(self, value_ty) { + Ok(binary_ty) => Ok(UnionType::from_two_elements( + db, + env, + outcome.return_type(db, env), + binary_ty, + )), + Err(recovery_ty) => Err(UnionType::from_two_elements( + db, + env, + outcome.return_type(db, env), + recovery_ty, + )), + } } Err(CallDunderError::CallError(_, bindings, _)) => { let value_ty = bindings.type_for_argument(&call_arguments, 0); @@ -4735,7 +4808,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { target_type, value_ty, ); - bindings.return_type(db, env) + Err(bindings.return_type(db, env)) } } } @@ -4747,12 +4820,17 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { assignment: &'ast ast::StmtAugAssign, definition: Definition<'db>, ) { - let target_ty = self.infer_augment_assignment(assignment); - self.add_binding(assignment.into(), definition) + let target_ty = self + .infer_augment_assignment(assignment) + .unwrap_or_else(|recovery_ty| recovery_ty); + self.add_binding(assignment.target.as_ref().into(), definition) .insert(self, target_ty); } - fn infer_augment_assignment(&mut self, assignment: &ast::StmtAugAssign) -> Type<'db> { + fn infer_augment_assignment( + &mut self, + assignment: &ast::StmtAugAssign, + ) -> Result, Type<'db>> { let ast::StmtAugAssign { range: _, node_index: _, diff --git a/crates/ty_python_semantic/src/types/infer/builder/subscript.rs b/crates/ty_python_semantic/src/types/infer/builder/subscript.rs index d5f9680793d18d..b0674e9787a51f 100644 --- a/crates/ty_python_semantic/src/types/infer/builder/subscript.rs +++ b/crates/ty_python_semantic/src/types/infer/builder/subscript.rs @@ -38,6 +38,12 @@ use ty_python_core::place::{PlaceExpr, PlaceExprRef}; use ty_python_core::scope::FileScopeId; use ty_python_core::{SemanticIndex, place_table}; +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub(super) enum SubscriptAssignmentKind { + Ordinary, + Augmented, +} + /// Given a string literal or a union of string literals, return an iterator over the contained /// strings, or `None` if the type is neither. fn string_literal_values<'db>( @@ -1553,6 +1559,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { &mut self, target: &ast::ExprSubscript, rhs_value: &ast::Expr, + assignment_kind: SubscriptAssignmentKind, + object_ty: Type<'db>, + infer_slice_ty: &mut dyn FnMut(&mut Self, TypeContext<'db>) -> Type<'db>, infer_rhs_value: &mut dyn FnMut(&mut Self, TypeContext<'db>) -> Type<'db>, ) -> bool { let env = self.program_environment(); @@ -1566,23 +1575,22 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let db = self.db(); - let object_ty = self.infer_expression(object, TypeContext::default()); self.store_typed_dict_key_expected_type(slice, object_ty); - let mut infer_slice_ty = |builder: &mut Self, tcx| builder.infer_expression(slice, tcx); let is_valid_assignment = self.validate_subscript_assignment_impl( target, None, object_ty, - &mut infer_slice_ty, + infer_slice_ty, rhs_value, infer_rhs_value, true, ); - // Record the constraints for the object of the subscript assignment, if the object is an - // unannotated collection initializer. - if is_valid_assignment + // An augmented store can initially fail against a provisional specialization and become + // valid after its operator result widens the collection. Rejected ordinary stores must not + // contribute constraints because they would also change unrelated reads and returns. + if (is_valid_assignment || assignment_kind == SubscriptAssignmentKind::Augmented) && let Some(collection_def) = self.index.unannotated_collection_initializer(object) && let Some((class_literal, _)) = object_ty.class_specialization(db, env) { @@ -1621,14 +1629,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { .infer_and_check_argument_types( ArgumentsIter::synthesized(&ast_arguments), &mut call_arguments, - &mut |builder, (_, expr, tcx)| { - // TODO: The argument types have already been inferred and stored in `call_arguments`. - // However, `object` would have been inferred to a be a collection with `Divergent` - // element types, meaning the type context for a given argument, by which the inferred - // type is keyed, may not be the same as the type context we get here. It is not immediately - // clear how to retrieve those types, and so we just re-infer the argument expressions - // for simplicity. - builder.infer_maybe_standalone_expression(expr, tcx) + &mut |builder, (argument_index, _, tcx)| { + if argument_index == 0 { + infer_slice_ty(builder, tcx) + } else { + infer_rhs_value(builder, tcx) + } }, &mut identity_bindings, TypeContext::default(),