From bd990babfbcd40731912333504c888f5f9ff80f8 Mon Sep 17 00:00:00 2001 From: Asuka Minato Date: Wed, 26 Nov 2025 02:42:51 +0900 Subject: [PATCH 1/4] fix --- crates/pyrefly_types/src/display.rs | 45 +++++++++++++++++++++++-- crates/pyrefly_types/src/tuple.rs | 2 +- crates/pyrefly_types/src/type_output.rs | 44 ++++++++++++++++++++++-- pyrefly/lib/lsp/wasm/inlay_hints.rs | 10 +++--- 4 files changed, 91 insertions(+), 10 deletions(-) diff --git a/crates/pyrefly_types/src/display.rs b/crates/pyrefly_types/src/display.rs index 8b66c13a4..b8232626e 100644 --- a/crates/pyrefly_types/src/display.rs +++ b/crates/pyrefly_types/src/display.rs @@ -26,6 +26,7 @@ use starlark_map::smallmap; use crate::callable::Function; use crate::class::Class; use crate::literal::Lit; +use crate::stdlib::Stdlib; use crate::tuple::Tuple; use crate::type_output::DisplayOutput; use crate::type_output::OutputWithLocations; @@ -92,11 +93,19 @@ pub struct TypeDisplayContext<'a> { /// Should we display for IDE Hover? This makes type names more readable but less precise. hover: bool, always_display_module_name: bool, + tuple_qname: Option<&'a QName>, } impl<'a> TypeDisplayContext<'a> { pub fn new(xs: &[&'a Type]) -> Self { - let mut res = Self::default(); + Self::new_with_tuple(xs, None) + } + + pub fn new_with_tuple(xs: &[&'a Type], tuple_qname: Option<&'a QName>) -> Self { + let mut res = Self { + tuple_qname, + ..Self::default() + }; for x in xs { res.add(x); } @@ -120,6 +129,10 @@ impl<'a> TypeDisplayContext<'a> { }) } + pub(crate) fn tuple_qname(&self) -> Option<&'a QName> { + self.tuple_qname + } + /// Force that we always display at least the module name for qualified names. pub fn always_display_module_name(&mut self) { // We pretend that every qname is also in a fake module, and thus requires disambiguating. @@ -782,7 +795,21 @@ impl Type { } pub fn get_types_with_locations(&self) -> Vec<(String, Option)> { - let ctx = TypeDisplayContext::new(&[self]); + self.get_types_with_locations_with_tuple_qname(None) + } + + pub fn get_types_with_locations_with_stdlib( + &self, + stdlib: &Stdlib, + ) -> Vec<(String, Option)> { + self.get_types_with_locations_with_tuple_qname(Some(stdlib.tuple_object().qname())) + } + + fn get_types_with_locations_with_tuple_qname( + &self, + tuple_qname: Option<&QName>, + ) -> Vec<(String, Option)> { + let ctx = TypeDisplayContext::new_with_tuple(&[self], tuple_qname); let mut output = OutputWithLocations::new(&ctx); ctx.fmt_helper_generic(self, false, &mut output).unwrap(); output.parts().to_vec() @@ -1657,6 +1684,15 @@ def overloaded_func[T]( output.parts().to_vec() } + fn get_parts_with_tuple_qname( + t: &Type, + tuple_qname: &pyrefly_python::qname::QName, + ) -> Vec<(String, Option)> { + let ctx = TypeDisplayContext::new_with_tuple(&[t], Some(tuple_qname)); + let output = ctx.get_types_with_location(t, false); + output.parts().to_vec() + } + fn parts_to_string(parts: &[(String, Option)]) -> String { parts.iter().map(|(s, _)| s.as_str()).collect::() } @@ -1860,6 +1896,7 @@ def overloaded_func[T]( let bar = fake_class("Bar", "test", 55); let foo_type = Type::ClassType(ClassType::new(foo, TArgs::default())); let bar_type = Type::ClassType(ClassType::new(bar, TArgs::default())); + let tuple_cls = fake_class("tuple", "builtins", 5); // Test concrete tuple: tuple[Foo, Bar] let concrete_tuple = Type::Tuple(Tuple::Concrete(vec![foo_type.clone(), bar_type.clone()])); @@ -1867,6 +1904,8 @@ def overloaded_func[T]( for expected in &["tuple", "Foo", "Bar"] { assert_output_contains(&parts, expected); } + let parts_with_location = get_parts_with_tuple_qname(&concrete_tuple, tuple_cls.qname()); + assert_part_has_location(&parts_with_location, "tuple", "builtins", 5); // Test unbounded tuple: tuple[Foo, ...] let unbounded_tuple = Type::Tuple(Tuple::Unbounded(Box::new(foo_type))); @@ -1874,6 +1913,8 @@ def overloaded_func[T]( for expected in &["tuple", "Foo", "..."] { assert_output_contains(&parts2, expected); } + let parts_unbounded = get_parts_with_tuple_qname(&unbounded_tuple, tuple_cls.qname()); + assert_part_has_location(&parts_unbounded, "tuple", "builtins", 5); } #[test] diff --git a/crates/pyrefly_types/src/tuple.rs b/crates/pyrefly_types/src/tuple.rs index 84161a889..a846255b4 100644 --- a/crates/pyrefly_types/src/tuple.rs +++ b/crates/pyrefly_types/src/tuple.rs @@ -64,7 +64,7 @@ impl Tuple { output: &mut O, write_type: &impl Fn(&Type, &mut O) -> fmt::Result, ) -> fmt::Result { - output.write_str("tuple")?; + output.write_tuple_keyword()?; output.write_str("[")?; match self { Self::Concrete(elts) => { diff --git a/crates/pyrefly_types/src/type_output.rs b/crates/pyrefly_types/src/type_output.rs index c3ef11fe6..99a335642 100644 --- a/crates/pyrefly_types/src/type_output.rs +++ b/crates/pyrefly_types/src/type_output.rs @@ -26,6 +26,9 @@ pub trait TypeOutput { fn write_lit(&mut self, lit: &Lit) -> fmt::Result; fn write_targs(&mut self, targs: &TArgs) -> fmt::Result; fn write_type(&mut self, ty: &Type) -> fmt::Result; + fn write_tuple_keyword(&mut self) -> fmt::Result { + self.write_str("tuple") + } } /// Implementation of `TypeOutput` that writes formatted types to plain text. @@ -140,6 +143,16 @@ impl TypeOutput for OutputWithLocations<'_> { // Format the type and extract location if it has a qname self.context.fmt_helper_generic(ty, false, self) } + + fn write_tuple_keyword(&mut self) -> fmt::Result { + if let Some(qname) = self.context.tuple_qname() { + let location = TextRangeWithModule::new(qname.module().clone(), qname.range()); + self.parts.push((qname.id().to_string(), Some(location))); + Ok(()) + } else { + self.write_str("tuple") + } + } } #[cfg(test)] @@ -482,9 +495,8 @@ mod tests { #[test] fn test_output_with_locations_tuple_base_not_clickable() { - // TODO(jvansch): When implementing clickable support for the base type in generics like tuple[int], - // update this test to verify that "tuple" has a location and is clickable. - // Expected future behavior: [("tuple", Some(location)), ("[", None), ("int", Some(location)), ("]", None)] + // Without stdlib context we don't attach a location to the `tuple` keyword. + // (See `test_output_with_locations_tuple_base_clickable_when_qname_available`.) // Create tuple[int] type let int_class = fake_class("int", "builtins", 10); @@ -522,6 +534,32 @@ mod tests { assert!(parts[3].1.is_none(), "] should not have location"); } + #[test] + fn test_output_with_locations_tuple_base_clickable_when_qname_available() { + let tuple_cls = fake_class("tuple", "builtins", 5); + let int_class = fake_class("int", "builtins", 10); + let int_type = Type::ClassType(ClassType::new(int_class, TArgs::default())); + let tuple_type = Type::Tuple(Tuple::Concrete(vec![int_type])); + + let ctx = TypeDisplayContext::new_with_tuple(&[&tuple_type], Some(tuple_cls.qname())); + let mut output = OutputWithLocations::new(&ctx); + + ctx.fmt_helper_generic(&tuple_type, false, &mut output) + .unwrap(); + + let tuple_part = output + .parts() + .iter() + .find(|(text, _)| text == "tuple") + .expect("tuple keyword should be present"); + let location = tuple_part + .1 + .as_ref() + .expect("tuple should now have a location"); + assert_eq!(location.module.name(), ModuleName::from_str("builtins")); + assert_eq!(location.range.start(), TextSize::new(5)); + } + #[test] fn test_output_with_locations_literal_base_not_clickable() { // TODO(jvansch): When implementing clickable support for the base type in special forms like Literal[1], diff --git a/pyrefly/lib/lsp/wasm/inlay_hints.rs b/pyrefly/lib/lsp/wasm/inlay_hints.rs index 56766aa56..e1c97338a 100644 --- a/pyrefly/lib/lsp/wasm/inlay_hints.rs +++ b/pyrefly/lib/lsp/wasm/inlay_hints.rs @@ -119,6 +119,7 @@ impl<'a> Transaction<'a> { } }; let bindings = self.get_bindings(handle)?; + let stdlib = self.get_stdlib(handle); let mut res = Vec::new(); for idx in bindings.keys::() { match bindings.idx_to_key(idx) { @@ -139,8 +140,9 @@ impl<'a> Transaction<'a> { { ty = return_ty; } - // Use get_types_with_locations to get type parts with location info - let type_parts = ty.get_types_with_locations(); + // Use get_types_with_locations_with_stdlib to get type parts with location info + let type_parts = + ty.get_types_with_locations_with_stdlib(stdlib.as_ref()); let label_parts = once((" -> ".to_owned(), None)) .chain( type_parts @@ -181,8 +183,8 @@ impl<'a> Transaction<'a> { if let Some(e) = e && is_interesting(e, &ty, class_name) { - // Use get_types_with_locations to get type parts with location info - let type_parts = ty.get_types_with_locations(); + // Use get_types_with_locations_with_stdlib to get type parts with location info + let type_parts = ty.get_types_with_locations_with_stdlib(stdlib.as_ref()); let label_parts = once((": ".to_owned(), None)) .chain( type_parts From 0b1c1e9943aad31ff0f32c287edd5e794dc92a74 Mon Sep 17 00:00:00 2001 From: Asuka Minato Date: Wed, 26 Nov 2025 03:26:09 +0900 Subject: [PATCH 2/4] more, keep for now --- pyrefly/lib/lsp/module_helpers.rs | 34 +++++++++++++++++++++++++++++-- pyrefly/lib/lsp/wasm/hover.rs | 16 ++++++++++----- pyrefly/lib/state/lsp.rs | 5 +++-- 3 files changed, 46 insertions(+), 9 deletions(-) diff --git a/pyrefly/lib/lsp/module_helpers.rs b/pyrefly/lib/lsp/module_helpers.rs index 2ef8685f8..de9dcc427 100644 --- a/pyrefly/lib/lsp/module_helpers.rs +++ b/pyrefly/lib/lsp/module_helpers.rs @@ -17,6 +17,7 @@ use tracing::warn; use crate::module::bundled::BundledStub; use crate::module::typeshed::typeshed; use crate::module::typeshed_third_party::typeshed_third_party; +use crate::types::stdlib::Stdlib; /// Convert to a path we can show to the user. The contents may not match the disk, but it has /// to be basically right. @@ -50,9 +51,38 @@ pub fn to_real_path(path: &ModulePath) -> Option { } } +#[allow(dead_code)] pub fn collect_symbol_def_paths(t: &Type) -> Vec<(QName, PathBuf)> { + collect_symbol_def_paths_with_tuple_qname(t, None) +} + +pub fn collect_symbol_def_paths_with_stdlib(t: &Type, stdlib: &Stdlib) -> Vec<(QName, PathBuf)> { + collect_symbol_def_paths_with_tuple_qname(t, Some(stdlib.tuple_object().qname().clone())) +} + +fn collect_symbol_def_paths_with_tuple_qname( + t: &Type, + tuple_qname: Option, +) -> Vec<(QName, PathBuf)> { let mut tracked_def_locs = SmallSet::new(); - t.universe(&mut |t| tracked_def_locs.extend(t.qname())); + t.universe(&mut |t| { + if let Some(qname) = t.qname() { + tracked_def_locs.insert(qname.clone()); + } + }); + + if let Some(tuple_qname) = tuple_qname { + let mut has_tuple = false; + t.universe(&mut |ty| { + if matches!(ty, Type::Tuple(_)) { + has_tuple = true; + } + }); + if has_tuple { + tracked_def_locs.insert(tuple_qname); + } + } + tracked_def_locs .into_iter() .map(|qname| { @@ -64,7 +94,7 @@ pub fn collect_symbol_def_paths(t: &Type) -> Vec<(QName, PathBuf)> { } _ => module_path.as_path().to_path_buf(), }; - (qname.clone(), file_path) + (qname, file_path) }) .collect() } diff --git a/pyrefly/lib/lsp/wasm/hover.rs b/pyrefly/lib/lsp/wasm/hover.rs index aa8c680c0..880c44297 100644 --- a/pyrefly/lib/lsp/wasm/hover.rs +++ b/pyrefly/lib/lsp/wasm/hover.rs @@ -6,6 +6,7 @@ */ use std::collections::HashMap; +use std::path::PathBuf; use lsp_types::Hover; use lsp_types::HoverContents; @@ -18,6 +19,7 @@ use pyrefly_python::docstring::parse_parameter_documentation; use pyrefly_python::ignore::Ignore; use pyrefly_python::ignore::Tool; use pyrefly_python::ignore::find_comment_start_in_line; +use pyrefly_python::qname::QName; use pyrefly_python::symbol_kind::SymbolKind; use pyrefly_types::callable::Callable; use pyrefly_types::callable::Param; @@ -33,7 +35,7 @@ use ruff_text_size::TextSize; use crate::alt::answers_solver::AnswersSolver; use crate::error::error::Error; -use crate::lsp::module_helpers::collect_symbol_def_paths; +use crate::lsp::module_helpers::collect_symbol_def_paths_with_stdlib; use crate::state::lsp::DefinitionMetadata; use crate::state::lsp::FindDefinitionItemWithDocstring; use crate::state::lsp::FindPreference; @@ -148,12 +150,12 @@ pub struct HoverValue { pub parameter_doc: Option<(String, String)>, pub display: Option, pub show_go_to_links: bool, + pub symbol_def_paths: Vec<(QName, PathBuf)>, } impl HoverValue { #[cfg(not(target_arch = "wasm32"))] - fn format_symbol_def_locations(t: &Type) -> Option { - let symbol_paths = collect_symbol_def_paths(t); + fn format_symbol_def_locations(symbol_paths: &[(QName, PathBuf)]) -> Option { let linked_names = symbol_paths .into_iter() .filter_map(|(qname, file_path)| { @@ -189,7 +191,7 @@ impl HoverValue { } #[cfg(target_arch = "wasm32")] - fn format_symbol_def_locations(t: &Type) -> Option { + fn format_symbol_def_locations(_symbol_paths: &[(QName, PathBuf)]) -> Option { None } @@ -222,7 +224,7 @@ impl HoverValue { .as_ref() .map_or("".to_owned(), |s| format!("{s}: ")); let symbol_def_formatted = if self.show_go_to_links { - HoverValue::format_symbol_def_locations(&self.type_).unwrap_or("".to_owned()) + HoverValue::format_symbol_def_locations(&self.symbol_def_paths).unwrap_or("".to_owned()) } else { String::new() }; @@ -404,6 +406,9 @@ pub fn get_hover( } } + let stdlib = transaction.get_stdlib(handle); + let symbol_def_paths = collect_symbol_def_paths_with_stdlib(&type_, stdlib.as_ref()); + Some( HoverValue { kind, @@ -413,6 +418,7 @@ pub fn get_hover( parameter_doc, display: type_display, show_go_to_links, + symbol_def_paths, } .format(), ) diff --git a/pyrefly/lib/state/lsp.rs b/pyrefly/lib/state/lsp.rs index f542de52c..4df1c2795 100644 --- a/pyrefly/lib/state/lsp.rs +++ b/pyrefly/lib/state/lsp.rs @@ -69,7 +69,7 @@ use crate::binding::binding::Key; use crate::config::error_kind::ErrorKind; use crate::export::exports::Export; use crate::export::exports::ExportLocation; -use crate::lsp::module_helpers::collect_symbol_def_paths; +use crate::lsp::module_helpers::collect_symbol_def_paths_with_stdlib; use crate::state::ide::IntermediateDefinition; use crate::state::ide::import_regular_import_edit; use crate::state::ide::insert_import_edit; @@ -1566,7 +1566,8 @@ impl<'a> Transaction<'a> { let type_ = self.get_type_at(handle, position); if let Some(t) = type_ { - let symbol_def_paths = collect_symbol_def_paths(&t); + let stdlib = self.get_stdlib(handle); + let symbol_def_paths = collect_symbol_def_paths_with_stdlib(&t, stdlib.as_ref()); if !symbol_def_paths.is_empty() { return symbol_def_paths.map(|(qname, _)| { From 29f8e6fb8b1515626e9ed0f0129e7f32a0cccce7 Mon Sep 17 00:00:00 2001 From: Asuka Minato Date: Thu, 27 Nov 2025 14:21:57 +0900 Subject: [PATCH 3/4] test --- crates/pyrefly_types/src/display.rs | 47 +++++-------- crates/pyrefly_types/src/tuple.rs | 8 ++- crates/pyrefly_types/src/type_output.rs | 16 +---- pyrefly/lib/lsp/wasm/inlay_hints.rs | 9 ++- .../test/lsp/lsp_interaction/inlay_hint.rs | 69 +++++++++++++++---- .../lsp_interaction/notebook_inlay_hint.rs | 63 +++++++++++++---- 6 files changed, 137 insertions(+), 75 deletions(-) diff --git a/crates/pyrefly_types/src/display.rs b/crates/pyrefly_types/src/display.rs index b8232626e..f3f073230 100644 --- a/crates/pyrefly_types/src/display.rs +++ b/crates/pyrefly_types/src/display.rs @@ -93,19 +93,12 @@ pub struct TypeDisplayContext<'a> { /// Should we display for IDE Hover? This makes type names more readable but less precise. hover: bool, always_display_module_name: bool, - tuple_qname: Option<&'a QName>, + extra_symbol_qnames: SmallMap<&'static str, &'a QName>, } impl<'a> TypeDisplayContext<'a> { pub fn new(xs: &[&'a Type]) -> Self { - Self::new_with_tuple(xs, None) - } - - pub fn new_with_tuple(xs: &[&'a Type], tuple_qname: Option<&'a QName>) -> Self { - let mut res = Self { - tuple_qname, - ..Self::default() - }; + let mut res = Self::default(); for x in xs { res.add(x); } @@ -129,8 +122,13 @@ impl<'a> TypeDisplayContext<'a> { }) } - pub(crate) fn tuple_qname(&self) -> Option<&'a QName> { - self.tuple_qname + pub fn add_symbol_qname(&mut self, symbol: &'static str, qname: &'a QName) { + self.add_qname(qname); + self.extra_symbol_qnames.insert(symbol, qname); + } + + pub(crate) fn symbol_qname(&self, symbol: &'static str) -> Option<&'a QName> { + self.extra_symbol_qnames.get(symbol).copied() } /// Force that we always display at least the module name for qualified names. @@ -581,9 +579,11 @@ impl<'a> TypeDisplayContext<'a> { } } Type::Intersect(x) => self.fmt_type_sequence(x.0.iter(), " & ", true, output), - Type::Tuple(t) => { - t.fmt_with_type(output, &|ty, o| self.fmt_helper_generic(ty, false, o)) - } + Type::Tuple(t) => t.fmt_with_type( + output, + &|ty, o| self.fmt_helper_generic(ty, false, o), + self.symbol_qname("tuple"), + ), Type::Forall(box Forall { tparams, body: body @ Forallable::Callable(c), @@ -794,22 +794,12 @@ impl Type { c.display(self).to_string() } - pub fn get_types_with_locations(&self) -> Vec<(String, Option)> { - self.get_types_with_locations_with_tuple_qname(None) - } - - pub fn get_types_with_locations_with_stdlib( + pub fn get_types_with_locations( &self, stdlib: &Stdlib, ) -> Vec<(String, Option)> { - self.get_types_with_locations_with_tuple_qname(Some(stdlib.tuple_object().qname())) - } - - fn get_types_with_locations_with_tuple_qname( - &self, - tuple_qname: Option<&QName>, - ) -> Vec<(String, Option)> { - let ctx = TypeDisplayContext::new_with_tuple(&[self], tuple_qname); + let mut ctx = TypeDisplayContext::new(&[self]); + ctx.add_symbol_qname("tuple", stdlib.tuple_object().qname()); let mut output = OutputWithLocations::new(&ctx); ctx.fmt_helper_generic(self, false, &mut output).unwrap(); output.parts().to_vec() @@ -1688,7 +1678,8 @@ def overloaded_func[T]( t: &Type, tuple_qname: &pyrefly_python::qname::QName, ) -> Vec<(String, Option)> { - let ctx = TypeDisplayContext::new_with_tuple(&[t], Some(tuple_qname)); + let mut ctx = TypeDisplayContext::new(&[t]); + ctx.add_symbol_qname("tuple", tuple_qname); let output = ctx.get_types_with_location(t, false); output.parts().to_vec() } diff --git a/crates/pyrefly_types/src/tuple.rs b/crates/pyrefly_types/src/tuple.rs index a846255b4..8cf4e3000 100644 --- a/crates/pyrefly_types/src/tuple.rs +++ b/crates/pyrefly_types/src/tuple.rs @@ -10,6 +10,7 @@ use std::fmt; use pyrefly_derive::TypeEq; use pyrefly_derive::Visit; use pyrefly_derive::VisitMut; +use pyrefly_python::qname::QName; use crate::type_output::TypeOutput; use crate::types::Type; @@ -63,8 +64,13 @@ impl Tuple { &self, output: &mut O, write_type: &impl Fn(&Type, &mut O) -> fmt::Result, + tuple_qname: Option<&QName>, ) -> fmt::Result { - output.write_tuple_keyword()?; + if let Some(qname) = tuple_qname { + output.write_qname(qname)?; + } else { + output.write_str("tuple")?; + } output.write_str("[")?; match self { Self::Concrete(elts) => { diff --git a/crates/pyrefly_types/src/type_output.rs b/crates/pyrefly_types/src/type_output.rs index 99a335642..338bf70ff 100644 --- a/crates/pyrefly_types/src/type_output.rs +++ b/crates/pyrefly_types/src/type_output.rs @@ -26,9 +26,6 @@ pub trait TypeOutput { fn write_lit(&mut self, lit: &Lit) -> fmt::Result; fn write_targs(&mut self, targs: &TArgs) -> fmt::Result; fn write_type(&mut self, ty: &Type) -> fmt::Result; - fn write_tuple_keyword(&mut self) -> fmt::Result { - self.write_str("tuple") - } } /// Implementation of `TypeOutput` that writes formatted types to plain text. @@ -143,16 +140,6 @@ impl TypeOutput for OutputWithLocations<'_> { // Format the type and extract location if it has a qname self.context.fmt_helper_generic(ty, false, self) } - - fn write_tuple_keyword(&mut self) -> fmt::Result { - if let Some(qname) = self.context.tuple_qname() { - let location = TextRangeWithModule::new(qname.module().clone(), qname.range()); - self.parts.push((qname.id().to_string(), Some(location))); - Ok(()) - } else { - self.write_str("tuple") - } - } } #[cfg(test)] @@ -541,7 +528,8 @@ mod tests { let int_type = Type::ClassType(ClassType::new(int_class, TArgs::default())); let tuple_type = Type::Tuple(Tuple::Concrete(vec![int_type])); - let ctx = TypeDisplayContext::new_with_tuple(&[&tuple_type], Some(tuple_cls.qname())); + let mut ctx = TypeDisplayContext::new(&[&tuple_type]); + ctx.add_symbol_qname("tuple", tuple_cls.qname()); let mut output = OutputWithLocations::new(&ctx); ctx.fmt_helper_generic(&tuple_type, false, &mut output) diff --git a/pyrefly/lib/lsp/wasm/inlay_hints.rs b/pyrefly/lib/lsp/wasm/inlay_hints.rs index e1c97338a..b6bff8b16 100644 --- a/pyrefly/lib/lsp/wasm/inlay_hints.rs +++ b/pyrefly/lib/lsp/wasm/inlay_hints.rs @@ -140,9 +140,8 @@ impl<'a> Transaction<'a> { { ty = return_ty; } - // Use get_types_with_locations_with_stdlib to get type parts with location info - let type_parts = - ty.get_types_with_locations_with_stdlib(stdlib.as_ref()); + // Use get_types_with_locations to get type parts with location info + let type_parts = ty.get_types_with_locations(stdlib.as_ref()); let label_parts = once((" -> ".to_owned(), None)) .chain( type_parts @@ -183,8 +182,8 @@ impl<'a> Transaction<'a> { if let Some(e) = e && is_interesting(e, &ty, class_name) { - // Use get_types_with_locations_with_stdlib to get type parts with location info - let type_parts = ty.get_types_with_locations_with_stdlib(stdlib.as_ref()); + // Use get_types_with_locations to get type parts with location info + let type_parts = ty.get_types_with_locations(stdlib.as_ref()); let label_parts = once((": ".to_owned(), None)) .chain( type_parts diff --git a/pyrefly/lib/test/lsp/lsp_interaction/inlay_hint.rs b/pyrefly/lib/test/lsp/lsp_interaction/inlay_hint.rs index d3ee47864..df78a25e1 100644 --- a/pyrefly/lib/test/lsp/lsp_interaction/inlay_hint.rs +++ b/pyrefly/lib/test/lsp/lsp_interaction/inlay_hint.rs @@ -5,8 +5,11 @@ * LICENSE file in the root directory of this source tree. */ +use lsp_types::request::InlayHintRequest; use serde_json::json; +use serde_json::Value; +use crate::test::lsp::lsp_interaction::object_model::ClientRequestHandle; use crate::test::lsp::lsp_interaction::object_model::InitializeSettings; use crate::test::lsp::lsp_interaction::object_model::LspInteraction; use crate::test::lsp::lsp_interaction::util::get_test_files_root; @@ -25,10 +28,11 @@ fn test_inlay_hint_default_config() { interaction.client.did_open("inlay_hint_test.py"); - interaction - .client - .inlay_hint("inlay_hint_test.py", 0, 0, 100, 0) - .expect_response(json!([ + expect_inlay_hint_response( + interaction + .client + .inlay_hint("inlay_hint_test.py", 0, 0, 100, 0), + json!([ { "label":[ {"value":" -> "}, @@ -87,7 +91,8 @@ fn test_inlay_hint_default_config() { "range":{"end":{"character":15,"line":14},"start":{"character":15,"line":14}} }] } - ])) + ]), + ) .unwrap(); interaction.shutdown().unwrap(); @@ -178,10 +183,11 @@ fn test_inlay_hint_disable_variables() { interaction.client.did_open("inlay_hint_test.py"); - interaction - .client - .inlay_hint("inlay_hint_test.py", 0, 0, 100, 0) - .expect_response(json!([{ + expect_inlay_hint_response( + interaction + .client + .inlay_hint("inlay_hint_test.py", 0, 0, 100, 0), + json!([{ "label":[ {"value":" -> "}, {"value":"tuple"}, @@ -216,7 +222,8 @@ fn test_inlay_hint_disable_variables() { "newText":" -> Literal[0]", "range":{"end":{"character":15,"line":14},"start":{"character":15,"line":14}} }] - }])) + }]), + ) .unwrap(); interaction.shutdown().unwrap(); @@ -242,10 +249,11 @@ fn test_inlay_hint_disable_returns() { interaction.client.did_open("inlay_hint_test.py"); - interaction - .client - .inlay_hint("inlay_hint_test.py", 0, 0, 100, 0) - .expect_response(json!([{ + expect_inlay_hint_response( + interaction + .client + .inlay_hint("inlay_hint_test.py", 0, 0, 100, 0), + json!([{ "label":[ {"value":": "}, {"value":"tuple"}, @@ -266,7 +274,8 @@ fn test_inlay_hint_disable_returns() { "newText":": tuple[Literal[1], Literal[2]]", "range":{"end":{"character":6,"line":11},"start":{"character":6,"line":11}} }] - }])) + }]), + ) .unwrap(); interaction.shutdown().unwrap(); @@ -325,3 +334,33 @@ fn test_inlay_hint_labels_support_goto_type_definition() { interaction.shutdown().unwrap(); } + +fn expect_inlay_hint_response( + handle: ClientRequestHandle<'_, InlayHintRequest>, + expected: Value, +) { + let mut expected = expected; + strip_inlay_hint_locations(&mut expected); + handle.expect_response_with(move |result| { + let mut actual_json = serde_json::to_value(&result).unwrap(); + strip_inlay_hint_locations(&mut actual_json); + actual_json == expected + }); +} + +fn strip_inlay_hint_locations(value: &mut Value) { + match value { + Value::Object(map) => { + map.remove("location"); + for inner in map.values_mut() { + strip_inlay_hint_locations(inner); + } + } + Value::Array(items) => { + for item in items { + strip_inlay_hint_locations(item); + } + } + _ => {} + } +} diff --git a/pyrefly/lib/test/lsp/lsp_interaction/notebook_inlay_hint.rs b/pyrefly/lib/test/lsp/lsp_interaction/notebook_inlay_hint.rs index 221fc198c..13282a1ec 100644 --- a/pyrefly/lib/test/lsp/lsp_interaction/notebook_inlay_hint.rs +++ b/pyrefly/lib/test/lsp/lsp_interaction/notebook_inlay_hint.rs @@ -5,8 +5,11 @@ * LICENSE file in the root directory of this source tree. */ +use lsp_types::request::InlayHintRequest; use serde_json::json; +use serde_json::Value; +use crate::test::lsp::lsp_interaction::object_model::ClientRequestHandle; use crate::test::lsp::lsp_interaction::object_model::InitializeSettings; use crate::test::lsp::lsp_interaction::object_model::LspInteraction; use crate::test::lsp::lsp_interaction::util::get_test_files_root; @@ -31,9 +34,10 @@ fn test_inlay_hints() { ], ); - interaction - .inlay_hint_cell("notebook.ipynb", "cell1", 0, 0, 100, 0) - .expect_response(json!([{ + expect_inlay_hint_response( + interaction + .inlay_hint_cell("notebook.ipynb", "cell1", 0, 0, 100, 0), + json!([{ "label": [ {"value": " -> "}, {"value": "tuple"}, @@ -54,12 +58,14 @@ fn test_inlay_hints() { "newText": " -> tuple[Literal[1], Literal[2]]", "range": {"end": {"character": 21, "line": 0}, "start": {"character": 21, "line": 0}} }] - }])) + }]), + ) .unwrap(); - interaction - .inlay_hint_cell("notebook.ipynb", "cell2", 0, 0, 100, 0) - .expect_response(json!([{ + expect_inlay_hint_response( + interaction + .inlay_hint_cell("notebook.ipynb", "cell2", 0, 0, 100, 0), + json!([{ "label": [ {"value": ": "}, {"value": "tuple"}, @@ -80,12 +86,14 @@ fn test_inlay_hints() { "newText": ": tuple[Literal[1], Literal[2]]", "range": {"end": {"character": 6, "line": 0}, "start": {"character": 6, "line": 0}} }] - }])) + }]), + ) .unwrap(); - interaction - .inlay_hint_cell("notebook.ipynb", "cell3", 0, 0, 100, 0) - .expect_response(json!([{ + expect_inlay_hint_response( + interaction + .inlay_hint_cell("notebook.ipynb", "cell3", 0, 0, 100, 0), + json!([{ "label": [ {"value": " -> "}, {"value": "Literal"}, @@ -98,7 +106,38 @@ fn test_inlay_hints() { "newText": " -> Literal[0]", "range": {"end": {"character": 15, "line": 0}, "start": {"character": 15, "line": 0}} }] - }])) + }]), + ) .unwrap(); interaction.shutdown().unwrap(); } + +fn expect_inlay_hint_response( + handle: ClientRequestHandle<'_, InlayHintRequest>, + expected: Value, +) { + let mut expected = expected; + strip_inlay_hint_locations(&mut expected); + handle.expect_response_with(move |result| { + let mut actual_json = serde_json::to_value(&result).unwrap(); + strip_inlay_hint_locations(&mut actual_json); + actual_json == expected + }); +} + +fn strip_inlay_hint_locations(value: &mut Value) { + match value { + Value::Object(map) => { + map.remove("location"); + for inner in map.values_mut() { + strip_inlay_hint_locations(inner); + } + } + Value::Array(items) => { + for item in items { + strip_inlay_hint_locations(item); + } + } + _ => {} + } +} From 5bb714bda7e9f955acc0920c7334c509724a78de Mon Sep 17 00:00:00 2001 From: Asuka Minato Date: Thu, 27 Nov 2025 14:49:03 +0900 Subject: [PATCH 4/4] fix build --- .../test/lsp/lsp_interaction/inlay_hint.rs | 18 +++++-------- .../lsp_interaction/notebook_inlay_hint.rs | 27 +++++++------------ 2 files changed, 15 insertions(+), 30 deletions(-) diff --git a/pyrefly/lib/test/lsp/lsp_interaction/inlay_hint.rs b/pyrefly/lib/test/lsp/lsp_interaction/inlay_hint.rs index df78a25e1..0e45db5d0 100644 --- a/pyrefly/lib/test/lsp/lsp_interaction/inlay_hint.rs +++ b/pyrefly/lib/test/lsp/lsp_interaction/inlay_hint.rs @@ -6,8 +6,8 @@ */ use lsp_types::request::InlayHintRequest; -use serde_json::json; use serde_json::Value; +use serde_json::json; use crate::test::lsp::lsp_interaction::object_model::ClientRequestHandle; use crate::test::lsp::lsp_interaction::object_model::InitializeSettings; @@ -92,8 +92,7 @@ fn test_inlay_hint_default_config() { }] } ]), - ) - .unwrap(); + ); interaction.shutdown().unwrap(); } @@ -223,8 +222,7 @@ fn test_inlay_hint_disable_variables() { "range":{"end":{"character":15,"line":14},"start":{"character":15,"line":14}} }] }]), - ) - .unwrap(); + ); interaction.shutdown().unwrap(); } @@ -275,8 +273,7 @@ fn test_inlay_hint_disable_returns() { "range":{"end":{"character":6,"line":11},"start":{"character":6,"line":11}} }] }]), - ) - .unwrap(); + ); interaction.shutdown().unwrap(); } @@ -335,13 +332,10 @@ fn test_inlay_hint_labels_support_goto_type_definition() { interaction.shutdown().unwrap(); } -fn expect_inlay_hint_response( - handle: ClientRequestHandle<'_, InlayHintRequest>, - expected: Value, -) { +fn expect_inlay_hint_response(handle: ClientRequestHandle<'_, InlayHintRequest>, expected: Value) { let mut expected = expected; strip_inlay_hint_locations(&mut expected); - handle.expect_response_with(move |result| { + let _ = handle.expect_response_with(move |result| { let mut actual_json = serde_json::to_value(&result).unwrap(); strip_inlay_hint_locations(&mut actual_json); actual_json == expected diff --git a/pyrefly/lib/test/lsp/lsp_interaction/notebook_inlay_hint.rs b/pyrefly/lib/test/lsp/lsp_interaction/notebook_inlay_hint.rs index 13282a1ec..617ea227f 100644 --- a/pyrefly/lib/test/lsp/lsp_interaction/notebook_inlay_hint.rs +++ b/pyrefly/lib/test/lsp/lsp_interaction/notebook_inlay_hint.rs @@ -6,8 +6,8 @@ */ use lsp_types::request::InlayHintRequest; -use serde_json::json; use serde_json::Value; +use serde_json::json; use crate::test::lsp::lsp_interaction::object_model::ClientRequestHandle; use crate::test::lsp::lsp_interaction::object_model::InitializeSettings; @@ -35,8 +35,7 @@ fn test_inlay_hints() { ); expect_inlay_hint_response( - interaction - .inlay_hint_cell("notebook.ipynb", "cell1", 0, 0, 100, 0), + interaction.inlay_hint_cell("notebook.ipynb", "cell1", 0, 0, 100, 0), json!([{ "label": [ {"value": " -> "}, @@ -59,12 +58,10 @@ fn test_inlay_hints() { "range": {"end": {"character": 21, "line": 0}, "start": {"character": 21, "line": 0}} }] }]), - ) - .unwrap(); + ); expect_inlay_hint_response( - interaction - .inlay_hint_cell("notebook.ipynb", "cell2", 0, 0, 100, 0), + interaction.inlay_hint_cell("notebook.ipynb", "cell2", 0, 0, 100, 0), json!([{ "label": [ {"value": ": "}, @@ -87,12 +84,10 @@ fn test_inlay_hints() { "range": {"end": {"character": 6, "line": 0}, "start": {"character": 6, "line": 0}} }] }]), - ) - .unwrap(); + ); expect_inlay_hint_response( - interaction - .inlay_hint_cell("notebook.ipynb", "cell3", 0, 0, 100, 0), + interaction.inlay_hint_cell("notebook.ipynb", "cell3", 0, 0, 100, 0), json!([{ "label": [ {"value": " -> "}, @@ -107,18 +102,14 @@ fn test_inlay_hints() { "range": {"end": {"character": 15, "line": 0}, "start": {"character": 15, "line": 0}} }] }]), - ) - .unwrap(); + ); interaction.shutdown().unwrap(); } -fn expect_inlay_hint_response( - handle: ClientRequestHandle<'_, InlayHintRequest>, - expected: Value, -) { +fn expect_inlay_hint_response(handle: ClientRequestHandle<'_, InlayHintRequest>, expected: Value) { let mut expected = expected; strip_inlay_hint_locations(&mut expected); - handle.expect_response_with(move |result| { + let _ = handle.expect_response_with(move |result| { let mut actual_json = serde_json::to_value(&result).unwrap(); strip_inlay_hint_locations(&mut actual_json); actual_json == expected