diff --git a/crates/ty_ide/src/inlay_hints.rs b/crates/ty_ide/src/inlay_hints.rs index 09d522e497..802f48965f 100644 --- a/crates/ty_ide/src/inlay_hints.rs +++ b/crates/ty_ide/src/inlay_hints.rs @@ -4465,6 +4465,110 @@ mod tests { "); } + #[test] + fn test_function_call_with_unpacked_tuple_argument() { + // When an unpacked tuple fills multiple parameters, no hint should be shown + // for that argument because showing a single parameter name would be misleading. + let mut test = inlay_hint_test( + " + def foo(a: str, b: int, c: int, d: str): ... + t: tuple[int, int] = (23, 42) + foo('foo', *t, d='bar')", + ); + + // `*t` fills both `b` and `c`, so no hint is shown for it + assert_snapshot!(test.inlay_hints(), @r" + def foo(a: str, b: int, c: int, d: str): ... + t: tuple[int, int] = (23, 42) + foo([a=]'foo', *t, d='bar') + --------------------------------------------- + info[inlay-hint-location]: Inlay Hint Target + --> main.py:2:9 + | + 2 | def foo(a: str, b: int, c: int, d: str): ... + | ^ + 3 | t: tuple[int, int] = (23, 42) + 4 | foo('foo', *t, d='bar') + | + info: Source + --> main2.py:4:6 + | + 2 | def foo(a: str, b: int, c: int, d: str): ... + 3 | t: tuple[int, int] = (23, 42) + 4 | foo([a=]'foo', *t, d='bar') + | ^ + | + "); + } + + #[test] + fn test_function_call_with_unpacked_tuple_argument_single_element() { + // When an unpacked tuple fills only one parameter, a hint should be shown. + let mut test = inlay_hint_test( + " + def foo(a: str, b: int, c: str): ... + t: tuple[int] = (42,) + foo('foo', *t, 'bar')", + ); + + assert_snapshot!(test.inlay_hints(), @r" + def foo(a: str, b: int, c: str): ... + t: tuple[int] = (42,) + foo([a=]'foo', [b=]*t, [c=]'bar') + --------------------------------------------- + info[inlay-hint-location]: Inlay Hint Target + --> main.py:2:9 + | + 2 | def foo(a: str, b: int, c: str): ... + | ^ + 3 | t: tuple[int] = (42,) + 4 | foo('foo', *t, 'bar') + | + info: Source + --> main2.py:4:6 + | + 2 | def foo(a: str, b: int, c: str): ... + 3 | t: tuple[int] = (42,) + 4 | foo([a=]'foo', [b=]*t, [c=]'bar') + | ^ + | + + info[inlay-hint-location]: Inlay Hint Target + --> main.py:2:17 + | + 2 | def foo(a: str, b: int, c: str): ... + | ^ + 3 | t: tuple[int] = (42,) + 4 | foo('foo', *t, 'bar') + | + info: Source + --> main2.py:4:17 + | + 2 | def foo(a: str, b: int, c: str): ... + 3 | t: tuple[int] = (42,) + 4 | foo([a=]'foo', [b=]*t, [c=]'bar') + | ^ + | + + info[inlay-hint-location]: Inlay Hint Target + --> main.py:2:25 + | + 2 | def foo(a: str, b: int, c: str): ... + | ^ + 3 | t: tuple[int] = (42,) + 4 | foo('foo', *t, 'bar') + | + info: Source + --> main2.py:4:25 + | + 2 | def foo(a: str, b: int, c: str): ... + 3 | t: tuple[int] = (42,) + 4 | foo([a=]'foo', [b=]*t, [c=]'bar') + | ^ + | + "); + } + #[test] fn test_function_call_positional_only_and_positional_or_keyword_parameters() { let mut test = inlay_hint_test( diff --git a/crates/ty_python_semantic/src/types/ide_support.rs b/crates/ty_python_semantic/src/types/ide_support.rs index 8139582d58..daf00cb2b5 100644 --- a/crates/ty_python_semantic/src/types/ide_support.rs +++ b/crates/ty_python_semantic/src/types/ide_support.rs @@ -788,6 +788,12 @@ pub fn inlay_hint_call_argument_details<'db>( continue; } + // Skip if this argument maps to multiple parameters (e.g., unpacked tuple filling + // multiple slots). Showing a single parameter name would be misleading. + if arg_mapping.parameters.len() > 1 { + continue; + } + let Some(param_index) = arg_mapping.parameters.first() else { continue; };