mirror of
https://github.com/astral-sh/ruff.git
synced 2025-11-18 19:41:34 +00:00
[ty] elide redundant inlay hints for function args (#21365)
This elides the following inlay hints: ```py foo([x=]x) foo([x=]y.x) foo([x=]x[0]) foo([x=]x(...)) # composes to complex situations foo([x=]y.x(..)[0]) ``` Fixes https://github.com/astral-sh/ty/issues/1514
This commit is contained in:
parent
835e31b3ff
commit
4821c050ef
1 changed files with 197 additions and 2 deletions
|
|
@ -4,7 +4,7 @@ use crate::Db;
|
|||
use ruff_db::files::File;
|
||||
use ruff_db::parsed::parsed_module;
|
||||
use ruff_python_ast::visitor::source_order::{self, SourceOrderVisitor, TraversalSignal};
|
||||
use ruff_python_ast::{AnyNodeRef, Expr, Stmt};
|
||||
use ruff_python_ast::{AnyNodeRef, ArgOrKeyword, Expr, Stmt};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
use ty_python_semantic::types::Type;
|
||||
use ty_python_semantic::types::ide_support::inlay_hint_function_argument_details;
|
||||
|
|
@ -283,7 +283,9 @@ impl SourceOrderVisitor<'_> for InlayHintVisitor<'_, '_> {
|
|||
self.visit_expr(&call.func);
|
||||
|
||||
for (index, arg_or_keyword) in call.arguments.arguments_source_order().enumerate() {
|
||||
if let Some(name) = argument_names.get(&index) {
|
||||
if let Some(name) = argument_names.get(&index)
|
||||
&& !arg_matches_name(&arg_or_keyword, name)
|
||||
{
|
||||
self.add_call_argument_name(arg_or_keyword.range().start(), name);
|
||||
}
|
||||
self.visit_expr(arg_or_keyword.value());
|
||||
|
|
@ -296,6 +298,32 @@ impl SourceOrderVisitor<'_> for InlayHintVisitor<'_, '_> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Given a positional argument, check if the expression is the "same name"
|
||||
/// as the function argument itself.
|
||||
///
|
||||
/// This allows us to filter out reptitive inlay hints like `x=x`, `x=y.x`, etc.
|
||||
fn arg_matches_name(arg_or_keyword: &ArgOrKeyword, name: &str) -> bool {
|
||||
// Only care about positional args
|
||||
let ArgOrKeyword::Arg(arg) = arg_or_keyword else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let mut expr = *arg;
|
||||
loop {
|
||||
match expr {
|
||||
// `x=x(1, 2)` counts as a match, recurse for it
|
||||
Expr::Call(expr_call) => expr = &expr_call.func,
|
||||
// `x=x[0]` is a match, recurse for it
|
||||
Expr::Subscript(expr_subscript) => expr = &expr_subscript.value,
|
||||
// `x=x` is a match
|
||||
Expr::Name(expr_name) => return expr_name.id.as_str() == name,
|
||||
// `x=y.x` is a match
|
||||
Expr::Attribute(expr_attribute) => return expr_attribute.attr.as_str() == name,
|
||||
_ => return false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
@ -485,6 +513,173 @@ mod tests {
|
|||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_function_call_with_positional_or_keyword_parameter_redundant_name() {
|
||||
let test = inlay_hint_test(
|
||||
"
|
||||
def foo(x: int): pass
|
||||
x = 1
|
||||
y = 2
|
||||
foo(x)
|
||||
foo(y)",
|
||||
);
|
||||
|
||||
assert_snapshot!(test.inlay_hints(), @r"
|
||||
def foo(x: int): pass
|
||||
x[: Literal[1]] = 1
|
||||
y[: Literal[2]] = 2
|
||||
foo(x)
|
||||
foo([x=]y)
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_function_call_with_positional_or_keyword_parameter_redundant_attribute() {
|
||||
let test = inlay_hint_test(
|
||||
"
|
||||
def foo(x: int): pass
|
||||
class MyClass:
|
||||
def __init__():
|
||||
self.x: int = 1
|
||||
self.y: int = 2
|
||||
val = MyClass()
|
||||
|
||||
foo(val.x)
|
||||
foo(val.y)",
|
||||
);
|
||||
|
||||
assert_snapshot!(test.inlay_hints(), @r"
|
||||
def foo(x: int): pass
|
||||
class MyClass:
|
||||
def __init__():
|
||||
self.x: int = 1
|
||||
self.y: int = 2
|
||||
val[: MyClass] = MyClass()
|
||||
|
||||
foo(val.x)
|
||||
foo([x=]val.y)
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_function_call_with_positional_or_keyword_parameter_redundant_attribute_not() {
|
||||
// This one checks that we don't allow elide `x=` for `x.y`
|
||||
let test = inlay_hint_test(
|
||||
"
|
||||
def foo(x: int): pass
|
||||
class MyClass:
|
||||
def __init__():
|
||||
self.x: int = 1
|
||||
self.y: int = 2
|
||||
x = MyClass()
|
||||
|
||||
foo(x.x)
|
||||
foo(x.y)",
|
||||
);
|
||||
|
||||
assert_snapshot!(test.inlay_hints(), @r"
|
||||
def foo(x: int): pass
|
||||
class MyClass:
|
||||
def __init__():
|
||||
self.x: int = 1
|
||||
self.y: int = 2
|
||||
x[: MyClass] = MyClass()
|
||||
|
||||
foo(x.x)
|
||||
foo([x=]x.y)
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_function_call_with_positional_or_keyword_parameter_redundant_call() {
|
||||
let test = inlay_hint_test(
|
||||
"
|
||||
def foo(x: int): pass
|
||||
class MyClass:
|
||||
def __init__():
|
||||
def x() -> int:
|
||||
return 1
|
||||
def y() -> int:
|
||||
return 2
|
||||
val = MyClass()
|
||||
|
||||
foo(val.x())
|
||||
foo(val.y())",
|
||||
);
|
||||
|
||||
assert_snapshot!(test.inlay_hints(), @r"
|
||||
def foo(x: int): pass
|
||||
class MyClass:
|
||||
def __init__():
|
||||
def x() -> int:
|
||||
return 1
|
||||
def y() -> int:
|
||||
return 2
|
||||
val[: MyClass] = MyClass()
|
||||
|
||||
foo(val.x())
|
||||
foo([x=]val.y())
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_function_call_with_positional_or_keyword_parameter_redundant_complex() {
|
||||
let test = inlay_hint_test(
|
||||
"
|
||||
from typing import List
|
||||
|
||||
def foo(x: int): pass
|
||||
class MyClass:
|
||||
def __init__():
|
||||
def x() -> List[int]:
|
||||
return 1
|
||||
def y() -> List[int]:
|
||||
return 2
|
||||
val = MyClass()
|
||||
|
||||
foo(val.x()[0])
|
||||
foo(val.y()[1])",
|
||||
);
|
||||
|
||||
assert_snapshot!(test.inlay_hints(), @r"
|
||||
from typing import List
|
||||
|
||||
def foo(x: int): pass
|
||||
class MyClass:
|
||||
def __init__():
|
||||
def x() -> List[int]:
|
||||
return 1
|
||||
def y() -> List[int]:
|
||||
return 2
|
||||
val[: MyClass] = MyClass()
|
||||
|
||||
foo(val.x()[0])
|
||||
foo([x=]val.y()[1])
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_function_call_with_positional_or_keyword_parameter_redundant_subscript() {
|
||||
let test = inlay_hint_test(
|
||||
"
|
||||
def foo(x: int): pass
|
||||
x = [1]
|
||||
y = [2]
|
||||
|
||||
foo(x[0])
|
||||
foo(y[0])",
|
||||
);
|
||||
|
||||
assert_snapshot!(test.inlay_hints(), @r"
|
||||
def foo(x: int): pass
|
||||
x[: list[Unknown | int]] = [1]
|
||||
y[: list[Unknown | int]] = [2]
|
||||
|
||||
foo(x[0])
|
||||
foo([x=]y[0])
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_function_call_with_positional_only_parameter() {
|
||||
let test = inlay_hint_test(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue