Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expand one level of type definition on hover and fix optional field type. #584

Merged
merged 4 commits into from
Sep 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@

- Add support for prop completion for JSX V4 https://github.com/rescript-lang/rescript-vscode/pull/579
- Add support for create interface file for JSX V4 https://github.com/rescript-lang/rescript-vscode/pull/580
- Expand one level of type definition on hover. Dig into record/variant body. https://github.com/rescript-lang/rescript-vscode/pull/584

#### :bug: Bug Fix

- Fix printing of record types with optional fields https://github.com/rescript-lang/rescript-vscode/pull/584

## v1.6.0

Expand Down
58 changes: 43 additions & 15 deletions analysis/src/Hover.ml
Original file line number Diff line number Diff line change
Expand Up @@ -98,25 +98,53 @@ let newHover ~full:{file; package} locItem =
| Const_int64 _ -> "int64"
| Const_nativeint _ -> "int"))
| Typed (_, t, locKind) ->
let fromConstructorPath ~env path =
match References.digConstructor ~env ~package path with
| None -> None
| Some (_env, {name = {txt}; item = {decl}}) ->
if Utils.isUncurriedInternal path then None
else Some (decl |> Shared.declToString txt |> codeBlock)
in
let fromType ~docstring typ =
let typeString = codeBlock (typ |> Shared.typeToString) in
let extraTypeInfo =
let typeDefinitions =
(* Expand definitions of types mentioned in typ.
If typ itself is a record or variant, search its body *)
let env = QueryEnv.fromFile file in
match typ |> Shared.digConstructor with
| None -> None
| Some path -> (
match References.digConstructor ~env ~package path with
| None -> None
| Some (_env, {docstring; name = {txt}; item = {decl}}) ->
if Utils.isUncurriedInternal path then None
else Some (decl |> Shared.declToString txt, docstring))
in
let typeString, docstring =
match extraTypeInfo with
| None -> (typeString, docstring)
| Some (extra, extraDocstring) ->
(typeString ^ "\n\n" ^ codeBlock extra, extraDocstring)
let envToSearch, typesToSearch =
match typ |> Shared.digConstructor with
| Some path -> (
let labelDeclarationsTypes lds =
lds |> List.map (fun (ld : Types.label_declaration) -> ld.ld_type)
in
match References.digConstructor ~env ~package path with
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated to this PR, but it's a little big confusing that both References and Shared has a digConstructor. Or rather, hard to at a glance understand what Shared.digConstructor does (References.digConstructor is pretty easy to guess it has to do with references at least 😄 )

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the shared stuff is leftover that should probably be moved elsewhere

| None -> (env, [typ])
| Some (env1, {item = {decl}}) -> (
match decl.type_kind with
| Type_record (lds, _) ->
(env1, typ :: (lds |> labelDeclarationsTypes))
| Type_variant cds ->
( env1,
cds
|> List.map (fun (cd : Types.constructor_declaration) ->
let fromArgs =
match cd.cd_args with
| Cstr_tuple ts -> ts
| Cstr_record lds -> lds |> labelDeclarationsTypes
in
typ
::
(match cd.cd_res with
| None -> fromArgs
| Some t -> t :: fromArgs))
|> List.flatten )
| _ -> (env, [typ])))
| None -> (env, [typ])
in
let constructors = Shared.findTypeConstructors typesToSearch in
constructors |> List.filter_map (fromConstructorPath ~env:envToSearch)
in
let typeString = typeString :: typeDefinitions |> String.concat "\n\n" in
(typeString, docstring)
in
let parts =
Expand Down
38 changes: 28 additions & 10 deletions analysis/src/Shared.ml
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,38 @@ let tryReadCmi cmi =
None
| x -> Some x

(** TODO move to the Process_ stuff *)
let rec dig typ =
match typ.Types.desc with
| Types.Tlink inner -> dig inner
| Types.Tsubst inner -> dig inner
| Types.Tpoly (inner, _) -> dig inner
| _ -> typ
let rec dig (te : Types.type_expr) =
match te.desc with
| Tlink inner -> dig inner
| Tsubst inner -> dig inner
| Tpoly (inner, _) -> dig inner
| _ -> te

let digConstructor expr =
let expr = dig expr in
match expr.desc with
let digConstructor te =
match (dig te).desc with
| Tconstr (path, _args, _memo) -> Some path
| _ -> None

let findTypeConstructors (tel : Types.type_expr list) =
let paths = ref [] in
let addPath path =
if not (List.exists (Path.same path) !paths) then paths := path :: !paths
in
let rec loop (te : Types.type_expr) =
match te.desc with
| Tlink te1 | Tsubst te1 | Tpoly (te1, _) -> loop te1
| Tconstr (path, _, _) -> addPath path
| Tarrow (_, te1, te2, _) ->
loop te1;
loop te2
| Ttuple tel -> tel |> List.iter loop
| Tnil | Tvar _ | Tobject _ | Tfield _ | Tvariant _ | Tunivar _ | Tpackage _
->
()
in
tel |> List.iter loop;
!paths |> List.rev

let declToString ?(recStatus = Types.Trec_not) name t =
PrintType.printDecl ~recStatus name t

Expand Down
11 changes: 11 additions & 0 deletions analysis/tests/src/Hover.res
Original file line number Diff line number Diff line change
Expand Up @@ -186,3 +186,14 @@ module TypeSubstitutionRecords = {
// y2.content.
// ^com
}

module CompV4 = {
type props<'n, 's> = {n?: 'n, s: 's}
let make = props => {
let _ = props.n == Some (10)
React.string(props.s)
}
}

let mk = CompV4.make
// ^hov
2 changes: 1 addition & 1 deletion analysis/tests/src/expected/Auto.res.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
Hover src/Auto.res 2:13
{"contents": "```rescript\n(Belt.List.t<'a>, 'a => 'b) => Belt.List.t<'b>\n```\n\n\n Returns a new list with `f` applied to each element of `someList`.\n\n ```res example\n list{1, 2}->Belt.List.map(x => x + 1) // list{3, 4}\n ```\n"}
{"contents": "```rescript\n(Belt.List.t<'a>, 'a => 'b) => Belt.List.t<'b>\n```\n\n```rescript\ntype t<'a> = list<'a>\n```\n\n\n Returns a new list with `f` applied to each element of `someList`.\n\n ```res example\n list{1, 2}->Belt.List.map(x => x + 1) // list{3, 4}\n ```\n"}

2 changes: 1 addition & 1 deletion analysis/tests/src/expected/Definition.res.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Hover src/Definition.res 14:14
{"contents": "```rescript\n('a => 'b, list<'a>) => list<'b>\n```\n\n [List.map f [a1; ...; an]] applies function [f] to [a1, ..., an],\n and builds the list [[f a1; ...; f an]]\n with the results returned by [f]. Not tail-recursive. "}

Hover src/Definition.res 18:14
{"contents": "```rescript\n(Belt.List.t<'a>, 'a => 'b) => Belt.List.t<'b>\n```\n\n\n Returns a new list with `f` applied to each element of `someList`.\n\n ```res example\n list{1, 2}->Belt.List.map(x => x + 1) // list{3, 4}\n ```\n"}
{"contents": "```rescript\n(Belt.List.t<'a>, 'a => 'b) => Belt.List.t<'b>\n```\n\n```rescript\ntype t<'a> = list<'a>\n```\n\n\n Returns a new list with `f` applied to each element of `someList`.\n\n ```res example\n list{1, 2}->Belt.List.map(x => x + 1) // list{3, 4}\n ```\n"}

Hover src/Definition.res 23:3
{"contents": "```rescript\n(. int, int) => int\n```"}
Expand Down
2 changes: 1 addition & 1 deletion analysis/tests/src/expected/Div.res.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Hover src/Div.res 0:10
getLocItem #3: heuristic for <div>
{"contents": "```rescript\n(\n string,\n ~props: ReactDOMRe.domProps=?,\n array<React.element>,\n) => React.element\n```"}
{"contents": "```rescript\n(\n string,\n ~props: ReactDOMRe.domProps=?,\n array<React.element>,\n) => React.element\n```\n\n```rescript\ntype element\n```"}

Complete src/Div.res 3:17
posCursor:[3:17] posNoWhite:[3:16] Found expr:[3:4->3:17]
Expand Down
7 changes: 5 additions & 2 deletions analysis/tests/src/expected/Hover.res.txt
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,10 @@ Hover src/Hover.res 106:21
{"contents": "```rescript\nint\n```"}

Hover src/Hover.res 116:16
{"contents": "```rescript\nAA.cond<[< #str(string)]> => AA.cond<[< #str(string)]>\n```"}
{"contents": "```rescript\nAA.cond<[< #str(string)]> => AA.cond<[< #str(string)]>\n```\n\n```rescript\ntype cond<'a> = 'a\n constraint 'a = [< #str(string)]\n```"}

Hover src/Hover.res 119:25
{"contents": "```rescript\nAA.cond<[< #str(string)]> => AA.cond<[< #str(string)]>\n```"}
{"contents": "```rescript\nAA.cond<[< #str(string)]> => AA.cond<[< #str(string)]>\n```\n\n```rescript\ntype cond<'a> = 'a\n constraint 'a = [< #str(string)]\n```"}

Hover src/Hover.res 122:3
Nothing at that position. Now trying to use completion.
Expand Down Expand Up @@ -158,3 +158,6 @@ Completable: Cpath Value[y2].content.""
"documentation": null
}]

Hover src/Hover.res 197:4
{"contents": "```rescript\nCompV4.props<int, string> => React.element\n```\n\n```rescript\ntype props<'n, 's> = {?n: 'n, s: 's}\n```\n\n```rescript\ntype element\n```"}

2 changes: 1 addition & 1 deletion analysis/vendor/compiler-libs-406/printtyp.ml
Original file line number Diff line number Diff line change
Expand Up @@ -926,7 +926,7 @@ and tree_of_constructor cd =
(name, args, Some ret)

and tree_of_label l =
let opt = l.ld_attributes |> List.exists (fun ({txt}, _) -> txt = "optional") in
let opt = l.ld_attributes |> List.exists (fun ({txt}, _) -> txt = "ns.optional") in
let typ = match l.ld_type.desc with
| Tconstr (p, [t1], _) when opt && Path.same p Predef.path_option -> t1
| _ -> l.ld_type in
Expand Down