@@ -4,7 +4,7 @@ use crate::Db;
44use ruff_db:: files:: File ;
55use ruff_db:: parsed:: parsed_module;
66use ruff_python_ast:: visitor:: source_order:: { self , SourceOrderVisitor , TraversalSignal } ;
7- use ruff_python_ast:: { AnyNodeRef , Expr , Stmt } ;
7+ use ruff_python_ast:: { AnyNodeRef , ArgOrKeyword , Expr , Stmt } ;
88use ruff_text_size:: { Ranged , TextRange , TextSize } ;
99use ty_python_semantic:: types:: Type ;
1010use ty_python_semantic:: types:: ide_support:: inlay_hint_function_argument_details;
@@ -283,7 +283,9 @@ impl SourceOrderVisitor<'_> for InlayHintVisitor<'_, '_> {
283283 self . visit_expr ( & call. func ) ;
284284
285285 for ( index, arg_or_keyword) in call. arguments . arguments_source_order ( ) . enumerate ( ) {
286- if let Some ( name) = argument_names. get ( & index) {
286+ if let Some ( name) = argument_names. get ( & index)
287+ && !arg_matches_name ( & arg_or_keyword, name)
288+ {
287289 self . add_call_argument_name ( arg_or_keyword. range ( ) . start ( ) , name) ;
288290 }
289291 self . visit_expr ( arg_or_keyword. value ( ) ) ;
@@ -296,6 +298,32 @@ impl SourceOrderVisitor<'_> for InlayHintVisitor<'_, '_> {
296298 }
297299}
298300
301+ /// Given a positional argument, check if the expression is the "same name"
302+ /// as the function argument itself.
303+ ///
304+ /// This allows us to filter out reptitive inlay hints like `x=x`, `x=y.x`, etc.
305+ fn arg_matches_name ( arg_or_keyword : & ArgOrKeyword , name : & str ) -> bool {
306+ // Only care about positional args
307+ let ArgOrKeyword :: Arg ( arg) = arg_or_keyword else {
308+ return false ;
309+ } ;
310+
311+ let mut expr = * arg;
312+ loop {
313+ match expr {
314+ // `x=x(1, 2)` counts as a match, recurse for it
315+ Expr :: Call ( expr_call) => expr = & expr_call. func ,
316+ // `x=x[0]` is a match, recurse for it
317+ Expr :: Subscript ( expr_subscript) => expr = & expr_subscript. value ,
318+ // `x=x` is a match
319+ Expr :: Name ( expr_name) => return expr_name. id . as_str ( ) == name,
320+ // `x=y.x` is a match
321+ Expr :: Attribute ( expr_attribute) => return expr_attribute. attr . as_str ( ) == name,
322+ _ => return false ,
323+ }
324+ }
325+ }
326+
299327#[ cfg( test) ]
300328mod tests {
301329 use super :: * ;
@@ -485,6 +513,173 @@ mod tests {
485513 " ) ;
486514 }
487515
516+ #[ test]
517+ fn test_function_call_with_positional_or_keyword_parameter_redundant_name ( ) {
518+ let test = inlay_hint_test (
519+ "
520+ def foo(x: int): pass
521+ x = 1
522+ y = 2
523+ foo(x)
524+ foo(y)" ,
525+ ) ;
526+
527+ assert_snapshot ! ( test. inlay_hints( ) , @r"
528+ def foo(x: int): pass
529+ x[: Literal[1]] = 1
530+ y[: Literal[2]] = 2
531+ foo(x)
532+ foo([x=]y)
533+ " ) ;
534+ }
535+
536+ #[ test]
537+ fn test_function_call_with_positional_or_keyword_parameter_redundant_attribute ( ) {
538+ let test = inlay_hint_test (
539+ "
540+ def foo(x: int): pass
541+ class MyClass:
542+ def __init__():
543+ self.x: int = 1
544+ self.y: int = 2
545+ val = MyClass()
546+
547+ foo(val.x)
548+ foo(val.y)" ,
549+ ) ;
550+
551+ assert_snapshot ! ( test. inlay_hints( ) , @r"
552+ def foo(x: int): pass
553+ class MyClass:
554+ def __init__():
555+ self.x: int = 1
556+ self.y: int = 2
557+ val[: MyClass] = MyClass()
558+
559+ foo(val.x)
560+ foo([x=]val.y)
561+ " ) ;
562+ }
563+
564+ #[ test]
565+ fn test_function_call_with_positional_or_keyword_parameter_redundant_attribute_not ( ) {
566+ // This one checks that we don't allow elide `x=` for `x.y`
567+ let test = inlay_hint_test (
568+ "
569+ def foo(x: int): pass
570+ class MyClass:
571+ def __init__():
572+ self.x: int = 1
573+ self.y: int = 2
574+ x = MyClass()
575+
576+ foo(x.x)
577+ foo(x.y)" ,
578+ ) ;
579+
580+ assert_snapshot ! ( test. inlay_hints( ) , @r"
581+ def foo(x: int): pass
582+ class MyClass:
583+ def __init__():
584+ self.x: int = 1
585+ self.y: int = 2
586+ x[: MyClass] = MyClass()
587+
588+ foo(x.x)
589+ foo([x=]x.y)
590+ " ) ;
591+ }
592+
593+ #[ test]
594+ fn test_function_call_with_positional_or_keyword_parameter_redundant_call ( ) {
595+ let test = inlay_hint_test (
596+ "
597+ def foo(x: int): pass
598+ class MyClass:
599+ def __init__():
600+ def x() -> int:
601+ return 1
602+ def y() -> int:
603+ return 2
604+ val = MyClass()
605+
606+ foo(val.x())
607+ foo(val.y())" ,
608+ ) ;
609+
610+ assert_snapshot ! ( test. inlay_hints( ) , @r"
611+ def foo(x: int): pass
612+ class MyClass:
613+ def __init__():
614+ def x() -> int:
615+ return 1
616+ def y() -> int:
617+ return 2
618+ val[: MyClass] = MyClass()
619+
620+ foo(val.x())
621+ foo([x=]val.y())
622+ " ) ;
623+ }
624+
625+ #[ test]
626+ fn test_function_call_with_positional_or_keyword_parameter_redundant_complex ( ) {
627+ let test = inlay_hint_test (
628+ "
629+ from typing import List
630+
631+ def foo(x: int): pass
632+ class MyClass:
633+ def __init__():
634+ def x() -> List[int]:
635+ return 1
636+ def y() -> List[int]:
637+ return 2
638+ val = MyClass()
639+
640+ foo(val.x()[0])
641+ foo(val.y()[1])" ,
642+ ) ;
643+
644+ assert_snapshot ! ( test. inlay_hints( ) , @r"
645+ from typing import List
646+
647+ def foo(x: int): pass
648+ class MyClass:
649+ def __init__():
650+ def x() -> List[int]:
651+ return 1
652+ def y() -> List[int]:
653+ return 2
654+ val[: MyClass] = MyClass()
655+
656+ foo(val.x()[0])
657+ foo([x=]val.y()[1])
658+ " ) ;
659+ }
660+
661+ #[ test]
662+ fn test_function_call_with_positional_or_keyword_parameter_redundant_subscript ( ) {
663+ let test = inlay_hint_test (
664+ "
665+ def foo(x: int): pass
666+ x = [1]
667+ y = [2]
668+
669+ foo(x[0])
670+ foo(y[0])" ,
671+ ) ;
672+
673+ assert_snapshot ! ( test. inlay_hints( ) , @r"
674+ def foo(x: int): pass
675+ x[: list[Unknown | int]] = [1]
676+ y[: list[Unknown | int]] = [2]
677+
678+ foo(x[0])
679+ foo([x=]y[0])
680+ " ) ;
681+ }
682+
488683 #[ test]
489684 fn test_function_call_with_positional_only_parameter ( ) {
490685 let test = inlay_hint_test (
0 commit comments