Skip to content

Zero alloc annotation for OxCaml#1422

Open
Leonidas-from-XIV wants to merge 8 commits intoocaml:masterfrom
Leonidas-from-XIV:zero-alloc-annotation
Open

Zero alloc annotation for OxCaml#1422
Leonidas-from-XIV wants to merge 8 commits intoocaml:masterfrom
Leonidas-from-XIV:zero-alloc-annotation

Conversation

@Leonidas-from-XIV
Copy link
Copy Markdown

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

Copy link
Copy Markdown
Collaborator

@art-w art-w left a comment

Choose a reason for hiding this comment

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

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?

Comment thread src/document/generator.ml Outdated
Comment thread src/loader/doc_attr.ml Outdated
let name, _, _ = attribute_unpack attr in
match String.equal name "zero_alloc" with
| true -> Some Lang.Value.Zero_alloc
| false -> None
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The documentation mentions that it's possible to add some option to the zero_alloc, e.g. [@@zero_alloc strict] or assume or opt etc. This doesn't look critical since strict/assume shouldn't be relevant for end-users consuming the documentation, but maybe opt is important?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good idea! I've looked into how the OxCaml toplevel prints it and it looks like this:

Implementation Inferred interface
[@zero_alloc assume] [@@zero_alloc]
[@zero_alloc strict] [@@zero_alloc strict]
[@zero_alloc opt] [@@zero_alloc opt]

I will add these.

Copy link
Copy Markdown
Collaborator

@panglesd panglesd left a comment

Choose a reason for hiding this comment

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

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 : t rather than val 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 @alert and @deprecated). However, they are rendered differently (using odoc @-tags). It's a pity to have two ways to handle attributes, but for zero_alloc (and other oxcaml-specific attributes to come), I think I prefer your approach.

Comment thread src/model/lang.ml
Comment on lines +324 to +327
module Zero_alloc : sig
type t = Assume | Opt | Strict
end
type attr = Zero_alloc of Zero_alloc.t
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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

Suggested change
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:

Suggested change
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

Comment thread src/loader/doc_attr.ml
| { Location.txt = name; loc }, attr_payload -> (name, attr_payload, loc)
#endif

let ident (pexp_desc : Parsetree.expression_desc) =
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Extreme nitpick: I find the name slightly too generic. Maybe "ident_of_expression"?

Comment thread src/loader/doc_attr.ml
@Leonidas-from-XIV
Copy link
Copy Markdown
Author

In the oxcaml doc, they use val[@zero_alloc] f : t rather than val 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!

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 val[@zero_alloc] variant is supported correctly.

@art-w
Copy link
Copy Markdown
Collaborator

art-w commented May 6, 2026

In the oxcaml doc, they use val[@zero_alloc] f : t rather than val f : t [@@zero_alloc] as in this PR (they are different syntax for the same thing).

We received feedback that the later form looks nicer for documentation :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants