diff --git a/lang/src/arr/compiler/anf-loop-compiler.arr b/lang/src/arr/compiler/anf-loop-compiler.arr index fee2d61f3..52c520ea6 100644 --- a/lang/src/arr/compiler/anf-loop-compiler.arr +++ b/lang/src/arr/compiler/anf-loop-compiler.arr @@ -2179,12 +2179,13 @@ fun compile-provides(provides): j-field("origin", compile-origin(origin)), j-field("typ", compile-provided-type(t)) ])) - | v-fun(origin, t, name, flatness) => + | v-fun(origin, t, name, doc, flatness) => j-field(v, j-obj([clist: j-field("bind", j-str("fun")), j-field("origin", compile-origin(origin)), j-field("flatness", flatness.and-then(j-num).or-else(j-false)), j-field("name", j-str(name)), + j-field("doc", j-str(doc)), j-field("typ", compile-provided-type(t)) ])) end diff --git a/lang/src/arr/compiler/ast-util.arr b/lang/src/arr/compiler/ast-util.arr index 7853276ef..2fe7023b0 100644 --- a/lang/src/arr/compiler/ast-util.arr +++ b/lang/src/arr/compiler/ast-util.arr @@ -1363,7 +1363,8 @@ fun canonicalize-value-export(ve :: CS.ValueExport, uri :: URI, tn): | v-alias(o, n) => CS.v-alias(o, n) | v-just-type(o, t) => CS.v-just-type(o, canonicalize-names(t, uri, tn)) | v-var(o, t) => CS.v-var(o, canonicalize-names(t, uri, tn)) - | v-fun(o, t, name, flatness) => CS.v-fun(o, canonicalize-names(t, uri, tn), name, flatness) + | v-fun(o, t, name, doc, flatness) => + CS.v-fun(o, canonicalize-names(t, uri, tn), name, doc, flatness) end end @@ -1546,3 +1547,55 @@ fun get-typed-provides(resolved, typed :: TCS.Typed, uri :: URI, compile-env :: end end end + +fun get-fun-hover-info(expr :: A.Expr, visitor) -> {String; A.Ann}: + # empty string sounds bad but we already are parsing missing docstrings + # into the empty string, so we have to detect and elide anyways... + # similarly, we detect and elide empty annotations, + # so that is a fine default too. + doc: ``` + Extracts the docstring if one exists; empty string otherwise. + Turns annotations on function-like expressions into arrow annotation; + empty annotation otherwise. + Assumes all tuple annotations have been desugared. + ``` + + fun piece-into-arrow(params :: List, ret :: A.Ann) -> A.Ann: + a-field-params = for map(p from params): + # s-tuple-binds should be gone by now + A.a-field(p.l, p.id.tosourcestring(), p.ann) + end + A.a-arrow-argnames(A.dummy-loc, a-field-params, ret, false) + end + + # we have to visit the resulting arrow ann to resolve names. + # while doing so, names bound as type parameters in the expr need to be dealt with + # (otherwise they would be unbound). + # The proper thing to do would be to use `a-forall`, which doesn't exist, + # so we use `a-any` as a last resort (at the cost of worse hovering). + fun tparam-visitor(tparams :: List): + tparam-names = tparams.map(_.toname()) + visitor.{ + method a-name(self, l, id): + if A.is-s-name(id) and tparam-names.member(id.s): + # TODO: fix this! + A.a-any(l) + else: + visitor.a-name(l, id) + end + end + } + end + + cases(A.Expr) expr: + | s-lam(_, _, tparams, params, ann, doc, _, _, _, _) => + {doc; piece-into-arrow(params, ann).visit(tparam-visitor(tparams))} + | s-fun(_, _, tparams, _, params, ann, doc, _, _, _, _) => + {doc; piece-into-arrow(params, ann).visit(tparam-visitor(tparams))} + | s-method(_, _, tparams, params, ann, doc, _, _, _, _) => + {doc; piece-into-arrow(params, ann).visit(tparam-visitor(tparams))} + | s-method-field(_, _, tparams, params, ann, doc, _, _, _, _) => + {doc; piece-into-arrow(params, ann).visit(tparam-visitor(tparams))} + | else => {""; A.a-blank} + end +end diff --git a/lang/src/arr/compiler/compile-structs.arr b/lang/src/arr/compiler/compile-structs.arr index 88d11f8f0..87aeb555e 100644 --- a/lang/src/arr/compiler/compile-structs.arr +++ b/lang/src/arr/compiler/compile-structs.arr @@ -99,7 +99,9 @@ data ValueBind: origin :: BindOrigin, binder :: ValueBinder, atom :: A.Name, - ann :: A.Ann) + ann :: A.Ann, + # the (possible empty) doc string for hovering over this (value) binding + doc :: String) end data TypeBinder: @@ -405,7 +407,7 @@ data ValueExport: | v-alias(origin :: BindOrigin, original-name :: String) | v-just-type(origin :: BindOrigin, t :: T.Type) | v-var(origin :: BindOrigin, t :: T.Type) - | v-fun(origin :: BindOrigin, t :: T.Type, name :: String, flatness :: Option) + | v-fun(origin :: BindOrigin, t :: T.Type, name :: String, doc :: String, flatness :: Option) end data DataExport: @@ -437,7 +439,8 @@ fun value-export-from-raw(uri, val-export, tyvar-env :: SD.StringDict) - t = val-export.tag typ = type-from-raw(uri, val-export.typ, tyvar-env) if t == "v-fun": - v-fun(typ, t, none) + # TODO (ZACK): WTF is up with this? + v-fun(typ, t, none, none) else: v-just-type(typ) end @@ -587,7 +590,7 @@ fun provides-from-raw-provides(uri, raw): else: none end - vdict.set(v.name, v-fun(origin, type-from-raw(uri, v.value.typ, SD.make-string-dict()), v.value.name, flatness)) + vdict.set(v.name, v-fun(origin, type-from-raw(uri, v.value.typ, SD.make-string-dict()), v.value.name, v.value.doc, flatness)) else: origin = origin-from-raw(uri, v.value.origin, v.name) vdict.set(v.name, v-just-type(origin, type-from-raw(uri, v.value.typ, SD.make-string-dict()))) diff --git a/lang/src/arr/compiler/desugar.arr b/lang/src/arr/compiler/desugar.arr index ac785f280..f965a2901 100644 --- a/lang/src/arr/compiler/desugar.arr +++ b/lang/src/arr/compiler/desugar.arr @@ -139,13 +139,13 @@ end fun mk-id-ann(loc, base, ann) block: a = names.make-atom(loc, base) - generated-binds.set-now(a.key(), C.value-bind(C.bo-local(loc, a), C.vb-let, a, ann)) + generated-binds.set-now(a.key(), C.value-bind(C.bo-local(loc, a), C.vb-let, a, ann, "")) { id: a, id-b: A.s-bind(loc, false, a, ann), id-e: A.s-id(loc, a) } end fun mk-id-var-ann(loc, base, ann) block: a = names.make-atom(loc, base) - generated-binds.set-now(a.key(), C.value-bind(C.bo-local(loc, a), C.vb-var, a, ann)) + generated-binds.set-now(a.key(), C.value-bind(C.bo-local(loc, a), C.vb-var, a, ann, "")) { id: a, id-b: A.s-bind(loc, false, a, ann), id-e: A.s-id-var(loc, a) } end diff --git a/lang/src/arr/compiler/flatness.arr b/lang/src/arr/compiler/flatness.arr index 9ea8a85b0..2bb5e8c95 100644 --- a/lang/src/arr/compiler/flatness.arr +++ b/lang/src/arr/compiler/flatness.arr @@ -349,7 +349,7 @@ fun get-flatness-for-module-fun(id, field, mb, env) -> Flatness: | none => none | some(value-export) => cases(C.ValueExport) value-export: - | v-fun(_, _, _, flatness) => + | v-fun(_, _, _, _, flatness) => flatness | else => none end @@ -452,7 +452,7 @@ fun make-prog-flatness-env(anfed :: AA.AProg, post-env :: C.ComputedEnvironment, | none => nothing | some(ve) => cases(C.ValueExport) ve: - | v-fun(_, _, _, flatness) => sd.set-now(vb.atom.key(), flatness) + | v-fun(_, _, _, _, flatness) => sd.set-now(vb.atom.key(), flatness) | else => nothing end end @@ -462,7 +462,7 @@ fun make-prog-flatness-env(anfed :: AA.AProg, post-env :: C.ComputedEnvironment, raise("The name: " + vb.atom.toname() + " could not be found on the module " + vb.origin.uri-of-definition) | some(value-export) => cases(C.ValueExport) value-export: - | v-fun(_, _, _, flatness) => + | v-fun(_, _, _, _, flatness) => sd.set-now(k, flatness) | else => nothing @@ -568,10 +568,16 @@ fun get-flat-provides(provides, env, post-env, { flatness-env; _ }, ast) block: | v-alias(origin, name) => env.value-by-uri-value(origin.uri-of-definition, origin.original-name.toname()) | else => ve end + existing-doc = cases(C.ValueExport) existing-val: + | v-fun(_, _, _, doc, _) => doc + | else => "" + end cases(Option) maybe-flatness: | none => ve | some(flatness-result) => - C.v-fun(ve.origin, existing-val.t, k, flatness-result) + # only fall back to the existing doc if we have to + doc = if bind.doc == "": existing-doc else: bind.doc end + C.v-fun(ve.origin, existing-val.t, k, doc, flatness-result) end end s.set(k, new-val) diff --git a/lang/src/arr/compiler/resolve-scope.arr b/lang/src/arr/compiler/resolve-scope.arr index 5e076d493..e10abc49d 100644 --- a/lang/src/arr/compiler/resolve-scope.arr +++ b/lang/src/arr/compiler/resolve-scope.arr @@ -767,17 +767,12 @@ fun resolve-names(p :: A.Program, thismodule-uri :: String, initial-env :: C.Com cases(Option) val-info block: | none => raise("The value is a global that doesn't exist in any module: " + name) | some(shadow val-info) => - cases(C.ValueExport) val-info block: - | v-var(_, t) => - b = C.value-bind(C.bo-global(some(origin), uri-of-definition, origin.original-name), C.vb-var, names.s-global(A.dummy-loc, name), A.a-blank) - bindings.set-now(names.s-global(A.dummy-loc, name).key(), b) - acc.set-now(name, b) - | else => - # TODO(joe): Good place to add _location_ to valueexport to report errs better - b = C.value-bind(C.bo-global(some(origin), uri-of-definition, origin.original-name), C.vb-let, names.s-global(A.dummy-loc, name), A.a-blank) - bindings.set-now(names.s-global(A.dummy-loc, name).key(), b) - acc.set-now(name, b) - end + vbinder = if C.is-v-var(val-info): C.vb-var else: C.vb-let end + doc = if C.is-v-fun(val-info): val-info.doc else: "" end + b = C.value-bind(C.bo-global(some(origin), uri-of-definition, origin.original-name), + vbinder, names.s-global(A.dummy-loc, name), A.a-blank, doc) + bindings.set-now(names.s-global(A.dummy-loc, name).key(), b) + acc.set-now(name, b) end end acc.freeze() @@ -813,8 +808,12 @@ fun resolve-names(p :: A.Program, thismodule-uri :: String, initial-env :: C.Com # TODO(joe): I think that b.b.ann.visit below could be wrong if # a letrec'd ID is used in a refinement within the same letrec, # so state may be necessary here + ann = b.b.ann.visit(visitor) + {doc; computed-fun-ann} = U.get-fun-hover-info(b.value, visitor) + # only override if there is no annotation written + shadow ann = if A.is-a-blank(ann): computed-fun-ann else: ann end atom-env = make-atom-for(b.b.id, b.b.shadows, env, bindings, - C.value-bind(C.bo-local(b.l, b.b.id), C.vb-letrec, _, b.b.ann.visit(visitor))) + C.value-bind(C.bo-local(b.l, b.b.id), C.vb-letrec, _, ann, doc)) { atom-env.env; link(atom-env.atom, atoms) } end new-visitor = visitor.{env: env} @@ -864,7 +863,7 @@ fun resolve-names(p :: A.Program, thismodule-uri :: String, initial-env :: C.Com fun handle-column-binds(column-binds :: A.ColumnBinds, visitor): env-and-binds = for fold(acc from { env: visitor.env, cbs: [list: ] }, cb from column-binds.binds): atom-env = make-atom-for(cb.id, cb.shadows, acc.env, bindings, - C.value-bind(C.bo-local(cb.l, cb.id), C.vb-let, _, cb.ann.visit(visitor))) + C.value-bind(C.bo-local(cb.l, cb.id), C.vb-let, _, cb.ann.visit(visitor), "")) new-cb = A.s-bind(cb.l, cb.shadows, atom-env.atom, cb.ann.visit(visitor.{env: acc.env})) { env: atom-env.env, cbs: link(new-cb, acc.cbs) } end @@ -878,6 +877,7 @@ fun resolve-names(p :: A.Program, thismodule-uri :: String, initial-env :: C.Com "$included-" + to-string(include-counter) end + # ZACK: revisit like above for module related stuff... fun add-value-name(l, imp-loc, env, vname, as-name, mod-info): maybe-value-export = mod-info.values.get(vname.toname()) cases(Option) maybe-value-export block: @@ -885,12 +885,14 @@ fun resolve-names(p :: A.Program, thismodule-uri :: String, initial-env :: C.Com name-errors := link(C.name-not-provided(l, imp-loc, vname, "value"), name-errors) env | some(value-export) => - vbinder = cases(C.ValueExport) value-export block: - | v-var(_, t) => C.vb-var - | else => C.vb-let - end + vbinder = if C.is-v-var(value-export): C.vb-var else: C.vb-let end + doc = if C.is-v-fun(value-export): value-export.doc else: "" end atom-env = make-import-atom-for(as-name, value-export.origin.uri-of-definition, env, bindings, - C.value-bind(C.bo-module(as-name.l, value-export.origin.definition-bind-site, value-export.origin.uri-of-definition, value-export.origin.original-name), vbinder, _, A.a-any(vname.l))) + C.value-bind(C.bo-module(as-name.l, + value-export.origin.definition-bind-site, + value-export.origin.uri-of-definition, + value-export.origin.original-name), + vbinder, _, A.a-any(vname.l), doc)) atom-env.env end end @@ -1474,8 +1476,9 @@ fun resolve-names(p :: A.Program, thismodule-uri :: String, initial-env :: C.Com # TODO(joe): What should the TypeBindTyp be here? atom-env-t = make-atom-for(name, false, te, type-bindings, C.type-bind(C.bo-local(l2, name), C.tb-type-let, _, C.tb-none)) + # ZACK TODO: wtf is this?? atom-env = make-atom-for(tname, false, e, bindings, - C.value-bind(C.bo-local(l2, tname), C.vb-let, _, A.a-blank)) + C.value-bind(C.bo-local(l2, tname), C.vb-let, _, A.a-blank, "ZACK what this")) new-bind = A.s-newtype-bind(l2, atom-env-t.atom, atom-env.atom) { atom-env.env; atom-env-t.env; link(new-bind, bs) } end @@ -1488,11 +1491,14 @@ fun resolve-names(p :: A.Program, thismodule-uri :: String, initial-env :: C.Com {e; bs; atoms} = acc cases(A.LetBind) b block: | s-let-bind(l2, bind, expr) => - visited-ann = bind.ann.visit(self.{env: e}) + ann = bind.ann.visit(self.{env: e}) + {doc; computed-fun-ann} = U.get-fun-hover-info(b.value, self.{env: e}) + # only override if there is no annotation written + shadow ann = if A.is-a-blank(ann): computed-fun-ann else: ann end atom-env = make-atom-for(bind.id, bind.shadows, e, bindings, - C.value-bind(C.bo-local(l2, bind.id), C.vb-let, _, visited-ann)) + C.value-bind(C.bo-local(l2, bind.id), C.vb-let, _, ann, doc)) visit-expr = expr.visit(self.{env: e}) - new-bind = A.s-let-bind(l2, A.s-bind(l2, bind.shadows, atom-env.atom, visited-ann), visit-expr) + new-bind = A.s-let-bind(l2, A.s-bind(l2, bind.shadows, atom-env.atom, ann), visit-expr) { atom-env.env; link(new-bind, bs); @@ -1501,7 +1507,8 @@ fun resolve-names(p :: A.Program, thismodule-uri :: String, initial-env :: C.Com | s-var-bind(l2, bind, expr) => visited-ann = bind.ann.visit(self.{env: e}) atom-env = make-atom-for(bind.id, bind.shadows, e, bindings, - C.value-bind(C.bo-local(l2, bind.id), C.vb-var, _, visited-ann)) + # ZACK TODO: we can't do anything here right... + C.value-bind(C.bo-local(l2, bind.id), C.vb-var, _, visited-ann, "")) visit-expr = expr.visit(self.{env: e}) new-bind = A.s-var-bind(l2, A.s-bind(l2, bind.shadows, atom-env.atom, visited-ann), visit-expr) { @@ -1526,7 +1533,7 @@ fun resolve-names(p :: A.Program, thismodule-uri :: String, initial-env :: C.Com cases(A.ForBind) fb block: | s-for-bind(l2, bind, val) => atom-env = make-atom-for(bind.id, bind.shadows, env, bindings, - C.value-bind(C.bo-local(l2, bind.id), C.vb-let, _, bind.ann.visit(self))) + C.value-bind(C.bo-local(l2, bind.id), C.vb-let, _, bind.ann.visit(self), "")) new-bind = A.s-bind(bind.l, bind.shadows, atom-env.atom, bind.ann.visit(self.{env: env})) visit-val = val.visit(self) new-fb = A.s-for-bind(l2, new-bind, visit-val) @@ -1539,7 +1546,7 @@ fun resolve-names(p :: A.Program, thismodule-uri :: String, initial-env :: C.Com {env; atoms} = for fold(acc from { self.env; empty }, a from args.map(_.bind)): {env; atoms} = acc atom-env = make-atom-for(a.id, a.shadows, env, bindings, - C.value-bind(C.bo-local(a.l, a.id), C.vb-let, _, a.ann.visit(self))) + C.value-bind(C.bo-local(a.l, a.id), C.vb-let, _, a.ann.visit(self), "")) { atom-env.env; link(atom-env.atom, atoms) } end new-args = for map2(a from args, at from atoms.reverse()): @@ -1580,7 +1587,7 @@ fun resolve-names(p :: A.Program, thismodule-uri :: String, initial-env :: C.Com {env; atoms} = for fold(acc from { with-params.env; empty }, a from args): {env; atoms} = acc atom-env = make-atom-for(a.id, a.shadows, env, bindings, - C.value-bind(C.bo-local(a.l, a.id), C.vb-let, _, a.ann.visit(with-params))) + C.value-bind(C.bo-local(a.l, a.id), C.vb-let, _, a.ann.visit(with-params), "")) { atom-env.env; link(atom-env.atom, atoms) } end new-args = for map2(a from args, at from atoms.reverse()): @@ -1608,7 +1615,7 @@ fun resolve-names(p :: A.Program, thismodule-uri :: String, initial-env :: C.Com {env; atoms} = for fold(acc from { with-params.env; empty }, a from args): {env; atoms} = acc atom-env = make-atom-for(a.id, a.shadows, env, bindings, - C.value-bind(C.bo-local(a.l, a.id), C.vb-let, _, a.ann.visit(with-params))) + C.value-bind(C.bo-local(a.l, a.id), C.vb-let, _, a.ann.visit(with-params), "")) { atom-env.env; link(atom-env.atom, atoms) } end new-args = for map2(a from args, at from atoms.reverse()): @@ -1631,7 +1638,7 @@ fun resolve-names(p :: A.Program, thismodule-uri :: String, initial-env :: C.Com {env; atoms} = for fold(acc from { with-params.env; empty }, a from args): {env; atoms} = acc atom-env = make-atom-for(a.id, a.shadows, env, bindings, - C.value-bind(C.bo-local(a.l, a.id), C.vb-let, _, a.ann.visit(with-params))) + C.value-bind(C.bo-local(a.l, a.id), C.vb-let, _, a.ann.visit(with-params), "")) { atom-env.env; link(atom-env.atom, atoms) } end new-args = for map2(a from args, at from atoms.reverse()): @@ -1721,7 +1728,8 @@ fun resolve-names(p :: A.Program, thismodule-uri :: String, initial-env :: C.Com new-bind = cases(A.Bind) bind: | s-bind(l2, shadows, name, ann) => atom-env = make-atom-for(name, true, self.env, bindings, - C.value-bind(C.bo-local(l2, name), C.vb-let, _, ann.visit(self))) + # ZACK TODO: maybe improve this? + C.value-bind(C.bo-local(l2, name), C.vb-let, _, ann.visit(self), "VARIANT")) A.s-bind(l2, shadows, atom-env.atom, ann.visit(self)) end A.s-variant-member(l, typ, new-bind) diff --git a/lang/src/js/base/type-util.js b/lang/src/js/base/type-util.js index e070ca43e..743a05dc2 100644 --- a/lang/src/js/base/type-util.js +++ b/lang/src/js/base/type-util.js @@ -157,6 +157,7 @@ define("pyret-base/js/type-util", [], function() { origin: origin, bind: "fun", name: value.name || "", + doc: value.doc || "", flatness: flatness, typ: toPyretType(runtime, expandType(value.typ, shorthands)) }); @@ -287,6 +288,7 @@ define("pyret-base/js/type-util", [], function() { origin: typ.origin, flatness: typ.flatness, name: typ.name, + doc: typ.doc || "", typ: expandType(typ.typ, shorthands) }; } diff --git a/vscode/sampleFiles/lsp/hover.arr b/vscode/sampleFiles/lsp/hover.arr new file mode 100644 index 000000000..26c33f530 --- /dev/null +++ b/vscode/sampleFiles/lsp/hover.arr @@ -0,0 +1,49 @@ +x :: Number +x = 17 + +y :: Number = 38 + +fun not-zero(n :: Number) -> Boolean: + doc: ```answers "is n nonzero?"``` + n <> 0 +end + +not-zero + +div-refine :: Number, Number%(not-zero) -> Number +fun div-refine(num, den): + doc: "divides the things" + num / den +end + +div-refine + +fun destruct-some-anns({a; b}, {c :: Number; d :: Number}): + a + b + c + d +end + +destruct-some-anns + +fun tup-anns(t :: {Number; Number}, r :: {a :: Number, b :: Number}): + t.{0} + r.a +end + +tup-anns + +div2 :: ((n :: Number, m :: Number) -> Boolean) = div-refine +div2 + +g = lam(n :: Number) -> Boolean: not-zero(n) end +g + +g-ann :: Any = lam(n :: Number) -> Boolean: not-zero(n) end +g-ann + +fun generic(l :: List) -> List: + l + l +end + +generic + +map +