[ty] Function argument inlay hints (#19269)
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks-instrumented (push) Blocked by required conditions
CI / benchmarks-walltime (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run

This commit is contained in:
Matthew Mckee 2025-08-12 14:56:54 +01:00 committed by GitHub
parent 3458f365da
commit ad28b80f96
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 692 additions and 67 deletions

View file

@ -53,7 +53,7 @@ use crate::types::generics::{
pub use crate::types::ide_support::{
CallSignatureDetails, Member, all_members, call_signature_details, definition_kind_for_name,
definitions_for_attribute, definitions_for_imported_symbol, definitions_for_keyword_argument,
definitions_for_name,
definitions_for_name, find_active_signature_from_details, inlay_hint_function_argument_details,
};
use crate::types::infer::infer_unpack_types;
use crate::types::mro::{Mro, MroError, MroIterator};

View file

@ -1,4 +1,5 @@
use std::cmp::Ordering;
use std::collections::HashMap;
use crate::place::{
Place, builtins_module_scope, imported_symbol, place_from_bindings, place_from_declarations,
@ -15,8 +16,8 @@ use crate::types::{ClassBase, ClassLiteral, DynamicType, KnownClass, KnownInstan
use crate::{Db, HasType, NameKind, SemanticModel};
use ruff_db::files::{File, FileRange};
use ruff_db::parsed::parsed_module;
use ruff_python_ast as ast;
use ruff_python_ast::name::Name;
use ruff_python_ast::{self as ast};
use ruff_text_size::{Ranged, TextRange};
use rustc_hash::FxHashSet;
@ -805,17 +806,16 @@ pub struct CallSignatureDetails<'db> {
/// (in case of overloads or union types).
pub fn call_signature_details<'db>(
db: &'db dyn Db,
file: File,
model: &SemanticModel<'db>,
call_expr: &ast::ExprCall,
) -> Vec<CallSignatureDetails<'db>> {
let model = SemanticModel::new(db, file);
let func_type = call_expr.func.inferred_type(&model);
let func_type = call_expr.func.inferred_type(model);
// Use into_callable to handle all the complex type conversions
if let Some(callable_type) = func_type.into_callable(db) {
let call_arguments =
CallArguments::from_arguments(db, &call_expr.arguments, |_, splatted_value| {
splatted_value.inferred_type(&model)
splatted_value.inferred_type(model)
});
let bindings = callable_type.bindings(db).match_parameters(&call_arguments);
@ -845,6 +845,102 @@ pub fn call_signature_details<'db>(
}
}
/// Find the active signature index from `CallSignatureDetails`.
/// The active signature is the first signature where all arguments present in the call
/// have valid mappings to parameters (i.e., none of the mappings are None).
pub fn find_active_signature_from_details(
signature_details: &[CallSignatureDetails],
) -> Option<usize> {
let first = signature_details.first()?;
// If there are no arguments in the mapping, just return the first signature.
if first.argument_to_parameter_mapping.is_empty() {
return Some(0);
}
// First, try to find a signature where all arguments have valid parameter mappings.
let perfect_match = signature_details.iter().position(|details| {
// Check if all arguments have valid parameter mappings.
details
.argument_to_parameter_mapping
.iter()
.all(|mapping| mapping.matched)
});
if let Some(index) = perfect_match {
return Some(index);
}
// If no perfect match, find the signature with the most valid argument mappings.
let (best_index, _) = signature_details
.iter()
.enumerate()
.max_by_key(|(_, details)| {
details
.argument_to_parameter_mapping
.iter()
.filter(|mapping| mapping.matched)
.count()
})?;
Some(best_index)
}
#[derive(Default)]
pub struct InlayHintFunctionArgumentDetails {
pub argument_names: HashMap<usize, String>,
}
pub fn inlay_hint_function_argument_details<'db>(
db: &'db dyn Db,
model: &SemanticModel<'db>,
call_expr: &ast::ExprCall,
) -> Option<InlayHintFunctionArgumentDetails> {
let signature_details = call_signature_details(db, model, call_expr);
if signature_details.is_empty() {
return None;
}
let active_signature_index = find_active_signature_from_details(&signature_details)?;
let call_signature_details = signature_details.get(active_signature_index)?;
let parameters = call_signature_details.signature.parameters();
let mut argument_names = HashMap::new();
for arg_index in 0..call_expr.arguments.args.len() {
let Some(arg_mapping) = call_signature_details
.argument_to_parameter_mapping
.get(arg_index)
else {
continue;
};
if !arg_mapping.matched {
continue;
}
let Some(param_index) = arg_mapping.parameters.first() else {
continue;
};
let Some(param) = parameters.get(*param_index) else {
continue;
};
// Only add hints for parameters that can be specified by name
if !param.is_positional_only() && !param.is_variadic() && !param.is_keyword_variadic() {
let Some(name) = param.name() else {
continue;
};
argument_names.insert(arg_index, name.to_string());
}
}
Some(InlayHintFunctionArgumentDetails { argument_names })
}
/// Find the text range of a specific parameter in function parameters by name.
/// Only searches for parameters that can be addressed by name in keyword arguments.
fn find_parameter_range(parameters: &ast::Parameters, parameter_name: &str) -> Option<TextRange> {