diff --git a/lsp/nls/src/requests/hover.rs b/lsp/nls/src/requests/hover.rs index 92cb54c7e5..1299b1d5c1 100644 --- a/lsp/nls/src/requests/hover.rs +++ b/lsp/nls/src/requests/hover.rs @@ -156,13 +156,43 @@ pub fn handle( if let Some(hover) = hover_data { let mut contents = Vec::new(); - if let Some(ty) = hover.ty { - contents.push(nickel_string(ty.to_string())); + // If we can't determine a static type through the typechecker because we are outside of a + // statically typed block, but the term points to a definition with a type annotation, we + // use this annotation insted. + let mut type_annots: Vec<_> = hover + .metadata + .iter() + .filter_map(|m| Some(m.annotation.typ.as_ref()?.typ.to_string())) + .collect(); + + let ty = hover + .ty + .as_ref() + .map(Type::to_string) + // Unclear whether it's useful to report `Dyn` all the time when there's no type found, + // but it matches the old behavior. + .unwrap_or_else(|| "Dyn".to_owned()); + + // If the type is `Dyn`, and we can find a type annotation somewhere in the metadata, we + // use the latter instead, which will be more precise. + let ty = if ty == "Dyn" { + // Ordering isn't meaningful here: metadata are aggregated from merged values (and + // merge is commutative). This list will also be sorted for deduplication later anyway. + // So we just pop the last one. + type_annots.pop().unwrap_or(ty) } else { - // Unclear whether it's useful to report `Dyn` all the - // time, but it matches the old behavior. - contents.push(nickel_string("Dyn".to_string())); - } + // If the type is both statically known and present as an annotation, we don't want to + // report a second time with the other contracts, so we remove it from the list. + // + // Note that there might be duplicates, and we need to remove them all, hence the + // `retain` (instead of a potentially more performant `iter().position(..)` followed by + // `swap_remove`). + type_annots.retain(|annot| annot != &ty); + + ty + }; + + contents.push(nickel_string(ty)); let mut contracts: Vec<_> = hover .metadata @@ -170,6 +200,7 @@ pub fn handle( .flat_map(|m| &m.annotation.contracts) .chain(hover.values.iter().flat_map(annotated_contracts)) .map(|contract| contract.label.typ.to_string()) + .chain(type_annots) .collect(); contracts.sort(); diff --git a/lsp/nls/tests/inputs/hover-stdlib-type.ncl b/lsp/nls/tests/inputs/hover-stdlib-type.ncl new file mode 100644 index 0000000000..da05d0dfdd --- /dev/null +++ b/lsp/nls/tests/inputs/hover-stdlib-type.ncl @@ -0,0 +1,12 @@ +### /main.ncl +let foo = { bar.baz : String -> Number = fun x => 1 } +in +foo.bar.baz "0" + std.string.to_number "1" +### [[request]] +### type = "Hover" +### textDocument.uri = "file:///main.ncl" +### position = { line = 2, character = 10 } +### [[request]] +### type = "Hover" +### textDocument.uri = "file:///main.ncl" +### position = { line = 2, character = 30 } diff --git a/lsp/nls/tests/snapshots/main__lsp__nls__tests__inputs__hover-stdlib-type.ncl.snap b/lsp/nls/tests/snapshots/main__lsp__nls__tests__inputs__hover-stdlib-type.ncl.snap new file mode 100644 index 0000000000..5b7d5aadc9 --- /dev/null +++ b/lsp/nls/tests/snapshots/main__lsp__nls__tests__inputs__hover-stdlib-type.ncl.snap @@ -0,0 +1,19 @@ +--- +source: lsp/nls/tests/main.rs +expression: output +--- +<2:0-2:11>[```nickel +String -> Number +```] +<2:18-2:38>[Converts a string that represents a number to that number. + +# Examples + +```nickel +std.string.to_number "123" + => 123 +```, ```nickel +NumberLiteral -> Dyn +```, ```nickel +String -> Number +```]