diff --git a/crates/ty_ide/src/inlay_hints.rs b/crates/ty_ide/src/inlay_hints.rs index af92d3440c..2ae416e9b8 100644 --- a/crates/ty_ide/src/inlay_hints.rs +++ b/crates/ty_ide/src/inlay_hints.rs @@ -6,7 +6,7 @@ use ruff_python_ast::{AnyNodeRef, Expr, Stmt}; use ruff_text_size::{Ranged, TextRange, TextSize}; use std::fmt; use std::fmt::Formatter; -use ty_python_semantic::types::Type; +use ty_python_semantic::types::{Type, inlay_hint_function_argument_details}; use ty_python_semantic::{HasType, SemanticModel}; #[derive(Debug, Clone, Eq, PartialEq)] @@ -24,7 +24,7 @@ impl<'db> InlayHint<'db> { #[derive(Debug, Clone, Eq, PartialEq)] pub enum InlayHintContent<'db> { Type(Type<'db>), - ReturnType(Type<'db>), + FunctionArgumentName(String), } impl<'db> InlayHintContent<'db> { @@ -44,8 +44,8 @@ impl fmt::Display for DisplayInlayHint<'_, '_> { InlayHintContent::Type(ty) => { write!(f, ": {}", ty.display(self.db)) } - InlayHintContent::ReturnType(ty) => { - write!(f, " -> {}", ty.display(self.db)) + InlayHintContent::FunctionArgumentName(name) => { + write!(f, "{name}=") } } } @@ -76,9 +76,18 @@ pub struct InlayHintSettings { /// x": Literal[1]" = 1 /// ``` pub variable_types: bool, + /// Whether to show function argument names. + /// + /// For example, this would enable / disable hints like the ones quoted below: + /// ```python + /// def foo(x: int): pass + /// foo("x="1) + /// ``` + pub function_argument_names: bool, } struct InlayHintVisitor<'a, 'db> { + db: &'db dyn Db, model: SemanticModel<'db>, hints: Vec>, in_assignment: bool, @@ -89,6 +98,7 @@ struct InlayHintVisitor<'a, 'db> { impl<'a, 'db> InlayHintVisitor<'a, 'db> { fn new(db: &'db dyn Db, file: File, range: TextRange, settings: &'a InlayHintSettings) -> Self { Self { + db, model: SemanticModel::new(db, file), hints: Vec::new(), in_assignment: false, @@ -98,11 +108,29 @@ impl<'a, 'db> InlayHintVisitor<'a, 'db> { } fn add_type_hint(&mut self, position: TextSize, ty: Type<'db>) { + if !self.settings.variable_types { + return; + } self.hints.push(InlayHint { position, content: InlayHintContent::Type(ty), }); } + + fn add_function_argument_name(&mut self, position: TextSize, name: String) { + if !self.settings.function_argument_names { + return; + } + + if name.starts_with('_') { + return; + } + + self.hints.push(InlayHint { + position, + content: InlayHintContent::FunctionArgumentName(name), + }); + } } impl SourceOrderVisitor<'_> for InlayHintVisitor<'_, '_> { @@ -123,25 +151,23 @@ impl SourceOrderVisitor<'_> for InlayHintVisitor<'_, '_> { match stmt { Stmt::Assign(assign) => { - if !self.settings.variable_types { - return; - } - self.in_assignment = true; for target in &assign.targets { self.visit_expr(target); } self.in_assignment = false; + self.visit_expr(&assign.value); + + return; + } + Stmt::Expr(expr) => { + self.visit_expr(&expr.value); return; } // TODO Stmt::FunctionDef(_) => {} Stmt::For(_) => {} - Stmt::Expr(_) => { - // Don't traverse into expression statements because we don't show any hints. - return; - } _ => {} } @@ -149,15 +175,32 @@ impl SourceOrderVisitor<'_> for InlayHintVisitor<'_, '_> { } fn visit_expr(&mut self, expr: &'_ Expr) { - if !self.in_assignment { - return; - } - match expr { Expr::Name(name) => { - if name.ctx.is_store() { - let ty = expr.inferred_type(&self.model); - self.add_type_hint(expr.range().end(), ty); + if self.in_assignment { + if name.ctx.is_store() { + let ty = expr.inferred_type(&self.model); + self.add_type_hint(expr.range().end(), ty); + } + } + source_order::walk_expr(self, expr); + } + Expr::Call(call) => { + let argument_names = + inlay_hint_function_argument_details(self.db, &self.model, call) + .map(|details| details.argument_names) + .unwrap_or_default(); + + 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) { + self.add_function_argument_name( + arg_or_keyword.range().start(), + name.to_string(), + ); + } + self.visit_expr(arg_or_keyword.value()); } } _ => { @@ -177,6 +220,7 @@ mod tests { files::{File, system_path_to_file}, source::source_text, }; + use ruff_python_trivia::textwrap::dedent; use ruff_text_size::TextSize; use ruff_db::system::{DbWithWritableSystem, SystemPathBuf}; @@ -194,6 +238,8 @@ mod tests { SystemPathBuf::from("/"), )); + let source = dedent(source); + let start = source.find(START); let end = source .find(END) @@ -245,6 +291,7 @@ mod tests { fn inlay_hints(&self) -> String { self.inlay_hints_with_settings(&InlayHintSettings { variable_types: true, + function_argument_names: true, }) } @@ -314,16 +361,529 @@ mod tests { } #[test] - fn disabled_variable_types() { + fn test_disabled_variable_types() { let test = inlay_hint_test("x = 1"); assert_snapshot!( test.inlay_hints_with_settings(&InlayHintSettings { variable_types: false, + ..Default::default() }), @r" x = 1 " ); } + + #[test] + fn test_function_call_with_positional_or_keyword_parameter() { + let test = inlay_hint_test( + " + def foo(x: int): pass + foo(1)", + ); + + assert_snapshot!(test.inlay_hints(), @r" + def foo(x: int): pass + foo([x=]1) + "); + } + + #[test] + fn test_function_call_with_positional_only_parameter() { + let test = inlay_hint_test( + " + def foo(x: int, /): pass + foo(1)", + ); + + assert_snapshot!(test.inlay_hints(), @r" + def foo(x: int, /): pass + foo(1) + "); + } + + #[test] + fn test_function_call_with_variadic_parameter() { + let test = inlay_hint_test( + " + def foo(*args: int): pass + foo(1)", + ); + + assert_snapshot!(test.inlay_hints(), @r" + def foo(*args: int): pass + foo(1) + "); + } + + #[test] + fn test_function_call_with_keyword_variadic_parameter() { + let test = inlay_hint_test( + " + def foo(**kwargs: int): pass + foo(x=1)", + ); + + assert_snapshot!(test.inlay_hints(), @r" + def foo(**kwargs: int): pass + foo(x=1) + "); + } + + #[test] + fn test_function_call_with_keyword_only_parameter() { + let test = inlay_hint_test( + " + def foo(*, x: int): pass + foo(x=1)", + ); + + assert_snapshot!(test.inlay_hints(), @r" + def foo(*, x: int): pass + foo(x=1) + "); + } + + #[test] + fn test_function_call_positional_only_and_positional_or_keyword_parameters() { + let test = inlay_hint_test( + " + def foo(x: int, /, y: int): pass + foo(1, 2)", + ); + + assert_snapshot!(test.inlay_hints(), @r" + def foo(x: int, /, y: int): pass + foo(1, [y=]2) + "); + } + + #[test] + fn test_function_call_positional_only_and_variadic_parameters() { + let test = inlay_hint_test( + " + def foo(x: int, /, *args: int): pass + foo(1, 2, 3)", + ); + + assert_snapshot!(test.inlay_hints(), @r" + def foo(x: int, /, *args: int): pass + foo(1, 2, 3) + "); + } + + #[test] + fn test_function_call_positional_only_and_keyword_variadic_parameters() { + let test = inlay_hint_test( + " + def foo(x: int, /, **kwargs: int): pass + foo(1, x=2)", + ); + + assert_snapshot!(test.inlay_hints(), @r" + def foo(x: int, /, **kwargs: int): pass + foo(1, x=2) + "); + } + + #[test] + fn test_class_constructor_call_init() { + let test = inlay_hint_test( + " + class Foo: + def __init__(self, x: int): pass + Foo(1) + f = Foo(1)", + ); + + assert_snapshot!(test.inlay_hints(), @r" + class Foo: + def __init__(self, x: int): pass + Foo([x=]1) + f[: Foo] = Foo([x=]1) + "); + } + + #[test] + fn test_class_constructor_call_new() { + let test = inlay_hint_test( + " + class Foo: + def __new__(cls, x: int): pass + Foo(1) + f = Foo(1)", + ); + + assert_snapshot!(test.inlay_hints(), @r" + class Foo: + def __new__(cls, x: int): pass + Foo([x=]1) + f[: Foo] = Foo([x=]1) + "); + } + + #[test] + fn test_class_constructor_call_meta_class_call() { + let test = inlay_hint_test( + " + class MetaFoo: + def __call__(self, x: int): pass + class Foo(metaclass=MetaFoo): + pass + Foo(1)", + ); + + assert_snapshot!(test.inlay_hints(), @r" + class MetaFoo: + def __call__(self, x: int): pass + class Foo(metaclass=MetaFoo): + pass + Foo([x=]1) + "); + } + + #[test] + fn test_callable_call() { + let test = inlay_hint_test( + " + from typing import Callable + def foo(x: Callable[[int], int]): + x(1)", + ); + + assert_snapshot!(test.inlay_hints(), @r" + from typing import Callable + def foo(x: Callable[[int], int]): + x(1) + "); + } + + #[test] + fn test_instance_method_call() { + let test = inlay_hint_test( + " + class Foo: + def bar(self, y: int): pass + Foo().bar(2)", + ); + + assert_snapshot!(test.inlay_hints(), @r" + class Foo: + def bar(self, y: int): pass + Foo().bar([y=]2) + "); + } + + #[test] + fn test_class_method_call() { + let test = inlay_hint_test( + " + class Foo: + @classmethod + def bar(cls, y: int): pass + Foo.bar(2)", + ); + + assert_snapshot!(test.inlay_hints(), @r" + class Foo: + @classmethod + def bar(cls, y: int): pass + Foo.bar([y=]2) + "); + } + + #[test] + fn test_static_method_call() { + let test = inlay_hint_test( + " + class Foo: + @staticmethod + def bar(y: int): pass + Foo.bar(2)", + ); + + assert_snapshot!(test.inlay_hints(), @r" + class Foo: + @staticmethod + def bar(y: int): pass + Foo.bar([y=]2) + "); + } + + #[test] + fn test_function_call_with_union_type() { + let test = inlay_hint_test( + " + def foo(x: int | str): pass + foo(1) + foo('abc')", + ); + + assert_snapshot!(test.inlay_hints(), @r" + def foo(x: int | str): pass + foo([x=]1) + foo([x=]'abc') + "); + } + + #[test] + fn test_function_call_multiple_positional_arguments() { + let test = inlay_hint_test( + " + def foo(x: int, y: str, z: bool): pass + foo(1, 'hello', True)", + ); + + assert_snapshot!(test.inlay_hints(), @r" + def foo(x: int, y: str, z: bool): pass + foo([x=]1, [y=]'hello', [z=]True) + "); + } + + #[test] + fn test_function_call_mixed_positional_and_keyword() { + let test = inlay_hint_test( + " + def foo(x: int, y: str, z: bool): pass + foo(1, z=True, y='hello')", + ); + + assert_snapshot!(test.inlay_hints(), @r" + def foo(x: int, y: str, z: bool): pass + foo([x=]1, z=True, y='hello') + "); + } + + #[test] + fn test_function_call_with_default_parameters() { + let test = inlay_hint_test( + " + def foo(x: int, y: str = 'default', z: bool = False): pass + foo(1) + foo(1, 'custom') + foo(1, 'custom', True)", + ); + + assert_snapshot!(test.inlay_hints(), @r" + def foo(x: int, y: str = 'default', z: bool = False): pass + foo([x=]1) + foo([x=]1, [y=]'custom') + foo([x=]1, [y=]'custom', [z=]True) + "); + } + + #[test] + fn test_nested_function_calls() { + let test = inlay_hint_test( + " + def foo(x: int) -> int: + return x * 2 + + def bar(y: str) -> str: + return y + + def baz(a: int, b: str, c: bool): pass + + baz(foo(5), bar(bar('test')), True)", + ); + + assert_snapshot!(test.inlay_hints(), @r" + def foo(x: int) -> int: + return x * 2 + + def bar(y: str) -> str: + return y + + def baz(a: int, b: str, c: bool): pass + + baz([a=]foo([x=]5), [b=]bar([y=]bar([y=]'test')), [c=]True) + "); + } + + #[test] + fn test_method_chaining() { + let test = inlay_hint_test( + " + class A: + def foo(self, value: int) -> 'A': + return self + def bar(self, name: str) -> 'A': + return self + def baz(self): pass + A().foo(42).bar('test').baz()", + ); + + assert_snapshot!(test.inlay_hints(), @r" + class A: + def foo(self, value: int) -> 'A': + return self + def bar(self, name: str) -> 'A': + return self + def baz(self): pass + A().foo([value=]42).bar([name=]'test').baz() + "); + } + + #[test] + fn test_nexted_keyword_function_calls() { + let test = inlay_hint_test( + " + def foo(x: str) -> str: + return x + def bar(y: int): pass + bar(y=foo('test')) + ", + ); + + assert_snapshot!(test.inlay_hints(), @r" + def foo(x: str) -> str: + return x + def bar(y: int): pass + bar(y=foo([x=]'test')) + "); + } + + #[test] + fn test_lambda_function_calls() { + let test = inlay_hint_test( + " + foo = lambda x: x * 2 + bar = lambda a, b: a + b + foo(5) + bar(1, 2)", + ); + + assert_snapshot!(test.inlay_hints(), @r" + foo[: (x) -> Unknown] = lambda x: x * 2 + bar[: (a, b) -> Unknown] = lambda a, b: a + b + foo([x=]5) + bar([a=]1, [b=]2) + "); + } + + #[test] + fn test_complex_parameter_combinations() { + let test = inlay_hint_test( + " + def foo(a: int, b: str, /, c: float, d: bool = True, *, e: int, f: str = 'default'): pass + foo(1, 'pos', 3.14, False, e=42) + foo(1, 'pos', 3.14, e=42, f='custom')", + ); + + assert_snapshot!(test.inlay_hints(), @r" + def foo(a: int, b: str, /, c: float, d: bool = True, *, e: int, f: str = 'default'): pass + foo(1, 'pos', [c=]3.14, [d=]False, e=42) + foo(1, 'pos', [c=]3.14, e=42, f='custom') + "); + } + + #[test] + fn test_generic_function_calls() { + let test = inlay_hint_test( + " + from typing import TypeVar, Generic + + T = TypeVar('T') + + def identity(x: T) -> T: + return x + + identity(42) + identity('hello')", + ); + + assert_snapshot!(test.inlay_hints(), @r#" + from typing import TypeVar, Generic + + T[: typing.TypeVar("T")] = TypeVar([name=]'T') + + def identity(x: T) -> T: + return x + + identity([x=]42) + identity([x=]'hello') + "#); + } + + #[test] + fn test_overloaded_function_calls() { + let test = inlay_hint_test( + " + from typing import overload + + @overload + def foo(x: int) -> str: ... + @overload + def foo(x: str) -> int: ... + def foo(x): + return x + + foo(42) + foo('hello')", + ); + + assert_snapshot!(test.inlay_hints(), @r" + from typing import overload + + @overload + def foo(x: int) -> str: ... + @overload + def foo(x: str) -> int: ... + def foo(x): + return x + + foo([x=]42) + foo([x=]'hello') + "); + } + + #[test] + fn test_disabled_function_argument_names() { + let test = inlay_hint_test( + " + def foo(x: int): pass + foo(1)", + ); + + assert_snapshot!(test.inlay_hints_with_settings(&InlayHintSettings { + function_argument_names: false, + ..Default::default() + }), @r" + def foo(x: int): pass + foo(1) + "); + } + + #[test] + fn test_function_call_out_of_range() { + let test = inlay_hint_test( + " + def foo(x: int): pass + def bar(y: int): pass + foo(1) + bar(2)", + ); + + assert_snapshot!(test.inlay_hints(), @r" + def foo(x: int): pass + def bar(y: int): pass + foo([x=]1) + bar(2) + "); + } + + #[test] + fn test_function_call_with_argument_name_starting_with_underscore() { + let test = inlay_hint_test( + " + def foo(_x: int, y: int): pass + foo(1, 2)", + ); + + assert_snapshot!(test.inlay_hints(), @r" + def foo(_x: int, y: int): pass + foo(1, [y=]2) + "); + } } diff --git a/crates/ty_ide/src/signature_help.rs b/crates/ty_ide/src/signature_help.rs index bfb223b9cf..1ce3c0b2a5 100644 --- a/crates/ty_ide/src/signature_help.rs +++ b/crates/ty_ide/src/signature_help.rs @@ -14,8 +14,11 @@ use ruff_db::parsed::parsed_module; use ruff_python_ast::{self as ast, AnyNodeRef}; use ruff_text_size::{Ranged, TextRange, TextSize}; use ty_python_semantic::ResolvedDefinition; +use ty_python_semantic::SemanticModel; use ty_python_semantic::semantic_index::definition::Definition; -use ty_python_semantic::types::{CallSignatureDetails, call_signature_details}; +use ty_python_semantic::types::{ + CallSignatureDetails, call_signature_details, find_active_signature_from_details, +}; // TODO: We may want to add special-case handling for calls to constructors // so the class docstring is used in place of (or inaddition to) any docstring @@ -66,9 +69,11 @@ pub fn signature_help(db: &dyn Db, file: File, offset: TextSize) -> Option> = - call_signature_details(db, file, call_expr); + call_signature_details(db, &model, call_expr); if signature_details.is_empty() { return None; @@ -258,45 +263,6 @@ fn create_parameters_from_offsets( .collect() } -/// 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). -fn find_active_signature_from_details(signature_details: &[CallSignatureDetails]) -> Option { - 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) -} - #[cfg(test)] mod tests { use crate::signature_help::SignatureHelpInfo; diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 3e4e2e3a23..fbbc0e0b36 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -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}; diff --git a/crates/ty_python_semantic/src/types/ide_support.rs b/crates/ty_python_semantic/src/types/ide_support.rs index da53963671..ceee7ac0ee 100644 --- a/crates/ty_python_semantic/src/types/ide_support.rs +++ b/crates/ty_python_semantic/src/types/ide_support.rs @@ -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> { - 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 { + 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, +} + +pub fn inlay_hint_function_argument_details<'db>( + db: &'db dyn Db, + model: &SemanticModel<'db>, + call_expr: &ast::ExprCall, +) -> Option { + 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 { diff --git a/crates/ty_server/src/session/options.rs b/crates/ty_server/src/session/options.rs index dc874215f1..009c9e016b 100644 --- a/crates/ty_server/src/session/options.rs +++ b/crates/ty_server/src/session/options.rs @@ -237,12 +237,14 @@ impl WorkspaceOptions { #[serde(rename_all = "camelCase")] struct InlayHintOptions { variable_types: Option, + function_argument_names: Option, } impl InlayHintOptions { fn into_settings(self) -> InlayHintSettings { InlayHintSettings { variable_types: self.variable_types.unwrap_or_default(), + function_argument_names: self.function_argument_names.unwrap_or_default(), } } } diff --git a/crates/ty_wasm/src/lib.rs b/crates/ty_wasm/src/lib.rs index 817cabdb17..b4caa26ced 100644 --- a/crates/ty_wasm/src/lib.rs +++ b/crates/ty_wasm/src/lib.rs @@ -438,6 +438,7 @@ impl Workspace { // TODO: Provide a way to configure this &InlayHintSettings { variable_types: true, + function_argument_names: true, }, );