diff --git a/compiler/src/language_server/inlayhint.re b/compiler/src/language_server/inlayhint.re index b88cdf262..0536aaff1 100644 --- a/compiler/src/language_server/inlayhint.re +++ b/compiler/src/language_server/inlayhint.re @@ -33,6 +33,29 @@ let send_no_result = (~id: Protocol.message_id) => { Protocol.response(~id, `Null); }; +let build_hint = + (position: Protocol.position, message: string): ResponseResult.inlay_hint => { + {label: ": " ++ message, position}; +}; + +let rec resolve_typ = (typ: Types.type_expr) => { + switch (typ.desc) { + | TTyLink(type_expr) + | TTySubst(type_expr) => resolve_typ(type_expr) + | _ => typ + }; +}; +let string_of_typ = (typ: Types.type_expr) => { + Printtyp.string_of_type_scheme(resolve_typ(typ)); +}; + +let is_func_typ = (typ: Types.type_expr) => { + switch (resolve_typ(typ).desc) { + | TTyArrow(_, _, _) => true + | _ => false + }; +}; + let find_hints = program => { let hints = ref([]); open Typedtree; @@ -40,7 +63,70 @@ let find_hints = program => { module Iterator = TypedtreeIter.MakeIterator({ include TypedtreeIter.DefaultIteratorArgument; - // Inlay hints for various expressions can be included here. + + let enter_expression = ({exp_desc, exp_type}: expression) => + switch (exp_desc) { + | TExpLambda(bindings, _) => + List.iter( + ({mb_pat, mb_loc}: match_branch) => { + switch (mb_pat.pat_desc) { + | TPatTuple(args) => + switch (resolve_typ(exp_type).desc) { + | TTyArrow(typ_args, _, _) => + let argument_typs = + List.map( + ((arg, typ: Types.type_expr)) => + switch (arg) { + | Default(_) => None + | _ => Some(typ) + }, + typ_args, + ); + if (List.length(argument_typs) == List.length(args)) { + List.iter( + ((arg: pattern, typ: option(Types.type_expr))) => { + switch (arg.pat_desc, typ) { + | (TPatVar(_, _), Some(typ)) => + let bind_end = arg.pat_loc.loc_end; + let p: Protocol.position = { + line: bind_end.pos_lnum - 1, + character: bind_end.pos_cnum - bind_end.pos_bol, + }; + let typeSignature = string_of_typ(typ); + hints := [build_hint(p, typeSignature), ...hints^]; + | _ => () + } + }, + List.combine(args, argument_typs), + ); + }; + | _ => () + } + | _ => () + } + }, + bindings, + ) + | _ => () + }; + + let enter_binding = ({vb_pat, vb_expr}: value_binding, toplevel: bool) => + if (!toplevel) { + switch (vb_pat) { + | {pat_extra: [], pat_desc: TPatVar(_, {loc})} => + let bind_end = loc.loc_end; + let p: Protocol.position = { + line: bind_end.pos_lnum - 1, + character: bind_end.pos_cnum - bind_end.pos_bol, + }; + let typ = vb_pat.pat_type; + if (!is_func_typ(typ)) { + let typeSignature = string_of_typ(typ); + hints := [build_hint(p, typeSignature), ...hints^]; + }; + | _ => () + }; + }; }); Iterator.iter_typed_program(program); hints^; diff --git a/compiler/src/typed/typedtreeIter.re b/compiler/src/typed/typedtreeIter.re index 585477558..ee14a4cf6 100644 --- a/compiler/src/typed/typedtreeIter.re +++ b/compiler/src/typed/typedtreeIter.re @@ -30,8 +30,8 @@ module type IteratorArgument = { let leave_core_type: core_type => unit; let leave_toplevel_stmt: toplevel_stmt => unit; - let enter_bindings: (rec_flag, mut_flag) => unit; - let enter_binding: value_binding => unit; + let enter_bindings: (rec_flag, mut_flag, bool) => unit; + let enter_binding: (value_binding, bool) => unit; let leave_binding: value_binding => unit; let leave_bindings: (rec_flag, mut_flag) => unit; @@ -74,16 +74,16 @@ module MakeIterator = Iter.leave_core_type(ct); } - and iter_binding = ({vb_pat, vb_expr} as vb) => { - Iter.enter_binding(vb); + and iter_binding = (~toplevel=false, {vb_pat, vb_expr} as vb) => { + Iter.enter_binding(vb, toplevel); iter_pattern(vb_pat); iter_expression(vb_expr); Iter.leave_binding(vb); } - and iter_bindings = (rec_flag, mut_flag, binds) => { - Iter.enter_bindings(rec_flag, mut_flag); - List.iter(iter_binding, binds); + and iter_bindings = (~toplevel=false, rec_flag, mut_flag, binds) => { + Iter.enter_bindings(rec_flag, mut_flag, toplevel); + List.iter(iter_binding(~toplevel), binds); Iter.leave_bindings(rec_flag, mut_flag); } @@ -132,7 +132,7 @@ module MakeIterator = | TTopModule({tmod_statements}) => iter_toplevel_stmts(tmod_statements) | TTopExpr(e) => iter_expression(e) | TTopLet(recflag, mutflag, binds) => - iter_bindings(recflag, mutflag, binds) + iter_bindings(recflag, mutflag, binds, ~toplevel=true) }; Iter.leave_toplevel_stmt(stmt); } @@ -206,7 +206,7 @@ module MakeIterator = | TExpIdent(_) | TExpConstant(_) => () | TExpLet(recflag, mutflag, binds) => - iter_bindings(recflag, mutflag, binds) + iter_bindings(recflag, mutflag, binds, ~toplevel=false) | TExpLambda(branches, _) => iter_match_branches(branches) | TExpApp(exp, _, args) => iter_expression(exp); @@ -277,8 +277,8 @@ module DefaultIteratorArgument: IteratorArgument = { let enter_expression = _ => (); let enter_core_type = _ => (); let enter_toplevel_stmt = _ => (); - let enter_bindings = (_, _) => (); - let enter_binding = _ => (); + let enter_bindings = (_, _, _) => (); + let enter_binding = (_, _) => (); let enter_data_declaration = _ => (); let enter_data_declarations = () => (); diff --git a/compiler/src/typed/typedtreeIter.rei b/compiler/src/typed/typedtreeIter.rei index 498467162..b1a3abf55 100644 --- a/compiler/src/typed/typedtreeIter.rei +++ b/compiler/src/typed/typedtreeIter.rei @@ -30,8 +30,8 @@ module type IteratorArgument = { let leave_core_type: core_type => unit; let leave_toplevel_stmt: toplevel_stmt => unit; - let enter_bindings: (rec_flag, mut_flag) => unit; - let enter_binding: value_binding => unit; + let enter_bindings: (rec_flag, mut_flag, bool) => unit; + let enter_binding: (value_binding, bool) => unit; let leave_binding: value_binding => unit; let leave_bindings: (rec_flag, mut_flag) => unit; diff --git a/compiler/test/suites/grainlsp.re b/compiler/test/suites/grainlsp.re index 8ef461155..e2b489303 100644 --- a/compiler/test/suites/grainlsp.re +++ b/compiler/test/suites/grainlsp.re @@ -788,4 +788,77 @@ let Some(a) = Some(1) ), ]), ); + + assertLspOutput( + "inlay_hints", + "file:///a.gr", + {| + module Main + + // Top Level Values + let a = 1 + and b = 2 + + let c = true + let d = [] + // Patterns + enum A { + S(Number), + } + let S(t) = S(1) + // Functions + let e = (a, b) => a + b + let e = (a, b) => { + return (a, b) => a + b + } + let test = (a, b, c: Number) => { + let x = 1 + let S(t) = S(1) + return a + b + c + } + // Record + record Nested { + a: Number, + b: String, + } + record Test { + test: String, + test2: Nested, + } + let test = { test: "test", test2: { a: 1, b: "test" } } + { + let test = { test: "test", test2: { a: 1, b: "test" } } + } + |}, + lsp_input( + "textDocument/inlayHint", + `Assoc([ + ("textDocument", `Assoc([("uri", `String("file:///a.gr"))])), + ("range", lsp_range((0, 0), (37, 0))), + ]), + ), + `List( + List.map( + ((label, (line, char))) => { + `Assoc([ + ("label", `String(label)), + ("position", lsp_position(line, char)), + ]) + }, + [ + // (label, position) + (": Test", (35, 14)), + (": Number", (20, 11)), + (": Number", (19, 20)), + (": Number", (19, 17)), + (": Number", (17, 18)), + (": Number", (17, 15)), + (": a", (16, 17)), + (": a", (16, 14)), + (": Number", (15, 17)), + (": Number", (15, 14)), + ], + ), + ), + ); });