Zero alloc annotation for OxCaml#1422
Conversation
art-w
left a comment
There was a problem hiding this comment.
It looks good! I have a minor nitpick, mostly to be safe because I don't know if the additional O.cut could break existing docs. Do you prefer addressing the remaining TODOs for the cmti in a separate PR?
panglesd
left a comment
There was a problem hiding this comment.
Thanks!
I've left a nitpick, a comment about payload support and one about not loading attributes in OxCaml.
Some small comments that probably require no change in this PR:
- In the oxcaml doc, they use
val[@zero_alloc] f : trather thanval f : t [@@zero_alloc]as in this PR (they are different syntax for the same thing). So maybe oxcaml users are more familiar with the former? But keeping as is is fine to me! - There is already some logic to extract special attributes (eg
@alertand@deprecated). However, they are rendered differently (using odoc@-tags). It's a pity to have two ways to handle attributes, but forzero_alloc(and other oxcaml-specific attributes to come), I think I prefer your approach.
| module Zero_alloc : sig | ||
| type t = Assume | Opt | Strict | ||
| end | ||
| type attr = Zero_alloc of Zero_alloc.t |
There was a problem hiding this comment.
According to the docs, the only allowed payloads are opt, strict and arity <n>.
If I understand correctly "assume" does not correspond to the default. (Assume in a module type would be: do not check that the implementation do not allocate, but use that information in uses outside.)
Moreover, it seems possible to use multiple (valid) payloads.
So I think we have two options in terms of type representation: the "more correct" one
| module Zero_alloc : sig | |
| type t = Assume | Opt | Strict | |
| end | |
| type attr = Zero_alloc of Zero_alloc.t | |
| module Zero_alloc : sig | |
| type t = {opt : unit option ; strict: unit option; arity: int option } | |
| end | |
| type attr = Zero_alloc of Zero_alloc.t |
or the one that uses the fact that it has already been validated by the oxcaml compiler:
| module Zero_alloc : sig | |
| type t = Assume | Opt | Strict | |
| end | |
| type attr = Zero_alloc of Zero_alloc.t | |
| module Zero_alloc : sig | |
| type t = Arity of n | Opt | Strict | |
| end | |
| type attr = Zero_alloc of Zero_alloc.t |
Both are fine to me! And the second one is probably less work.
Here are some examples of zero_alloc uses in module types to "validate" the oxcaml doc:
# module type T = sig val[@zero_alloc] f : int -> int -> int end ;;
module type T = sig val f : int -> int -> int [@@zero_alloc] end
(** The error says the accepted attributes {e for implementation}, not interface, unfortunately *)
# module type T = sig val[@zero_alloc gloubli-boulga] f : int -> int -> int end ;;
Warning 47 [attribute-payload]: illegal payload for attribute 'zero_alloc'.
It must be either 'assume', 'assume_unless_opt', 'strict', 'opt', 'opt strict', 'assume strict', 'assume never_returns_normally', 'assume never_returns_normally strict', 'assume error', 'ignore', 'arity <int_constant>', 'custom_error_message <string_constant>' or empty
(** assume is not allowed *)
# module type T = sig val[@zero_alloc assume] f : int -> int -> int end ;;
Error: zero_alloc assume attributes are not supported in signatures
(** strict, opt, and arity <n> (and combinations) are allowed *)
# module type T = sig val[@zero_alloc strict] f : int -> int -> int end ;;
module type T = sig val f : int -> int -> int [@@zero_alloc strict] end
# module type T = sig val[@zero_alloc opt] f : int -> int -> int end ;;
module type T = sig val f : int -> int -> int [@@zero_alloc opt] end
# module type T = sig val[@zero_alloc arity 1] f : int -> int -> int end ;;
module type T = sig val f : int -> int -> int [@@zero_alloc arity 1] end
# module type T = sig val[@zero_alloc strict opt] f : int -> int -> int end ;;
module type T = sig val f : int -> int -> int [@@zero_alloc strict opt] end
# module type T = sig val[@zero_alloc strict arity 2] f : int -> int -> int end ;;
module type T = sig val f : int -> int -> int [@@zero_alloc strict] end
# module type T = sig val[@zero_alloc arity 1 opt strict] f : int -> int -> int end ;;
module type T =
sig val f : int -> int -> int [@@zero_alloc strict opt arity 1] end
# module type T = sig val[@zero_alloc] f : int -> int -> int end ;;
module type T = sig val f : int -> int -> int [@@zero_alloc] end
# module type T = sig val[@zero_alloc] f : int -> int -> int end ;;
module type T = sig val f : int -> int -> int [@@zero_alloc] end
Very good point. I mostly did it this way as this is how the oxcaml toplevel prints it. So I assumed this is the way to go, but I will at least add a test-case to make sure that the |
We received feedback that the later form looks nicer for documentation :) |
|
I've rewritten the code to parse the arguments in However there are cases where the parser silently ignores errors (e.g. "arity" used but no argument given), in that case I think it would be nice to do something. Probably exiting is too extreme so I didn't want to throw but it would be nice to emit a warning that something went wrong? Or maybe its ok to silently continue because the OxCaml compiler will complain anyway? WDYT? |
I would say it is ok to silently continue, as I would say those warnings are more the responsibility of the compiler. But that made me think... Isn't this information already available in the compiler artefact? I have quickly checked in If we can avoid the parsing ourself, that would be much better! |
|
After setting up an OxCaml environment 😅 I was able to find that yes, the information is already there! In cmi files, the "zero alloc" info is stored at let za = vd.val_val.val_zero_alloc |> Zero_alloc.get in
let za = match za with
| Default_zero_alloc -> _
| Ignore_assert_all -> _
| Check _ -> _
| Assume _ -> _
in
Value.Zero_alloc zausing this info to create a lang, rather than reparse it from the attribute, seems better! And would remove all the parsing code. |
|
@panglesd Thanks for the pointers, they were very useful. I've implemented your changes, adapting the value type to be more aligned with the oxcaml compiler-libs version of it. However there's two concerns:
|
e86f3d0 to
f353e26
Compare
|
@panglesd Could you take another look and share your thoughts? I've rebased the branch on |
4bc0e98 to
e1cfd03
Compare
| doc = docs (parent :> Identifier.LabelParent.t) v.doc; | ||
| type_ = type_expr map (parent :> Identifier.LabelParent.t) v.type_; | ||
| value = v.value; | ||
| (* TODO this needs to be fixed *) |
There was a problem hiding this comment.
Without mapping between lang types and component types we'll lose all these attributes in the wrapped library (ie, default!) case.
There was a problem hiding this comment.
Indeed!
What you need to do is:
- Add
extr_attrsfield in theValue.ttype in (xref2/components.ml{i}), - Handle the conversion from "model" to component (same file,
Of_langmodule): just propagate the value. - Handle the conversion from component to model (the TODO that was left).
Component are used as an intermediate types when manipulating modules (eg when doing include M). Without this, the zero alloc info would be lost whenever such conversion happen, eg in wrapped modules.
Hmm, I think it is a problem, as I don't see value cmt loading calling into |
c8fc595 to
85022bd
Compare
f6980f8 to
0a45786
Compare
This PR aims to take the zero alloc annotation from the source and retain it in the parsed output.
Currently this PR supports the
[@@zero_alloc]annotation on signatures. Next step would be to support the[@zero_alloc]annotation on bindings; not sure if it would make sense to add it in this PR or another one.This is my first PR to Odoc so excuse me for being a bit lost, I'll happily fix up/change stuff.
cc @art-w