Skip to content
Open
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
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
invocation, eliminating the need for shell scripting (@davesnx, #1387)
- Support for OxCaml (@lukemaurer, @art-w, #1399)
- OCaml 5.5.0 support (@panglesd, @xvw, #1406)
- Support for OxCaml zero alloc definitions (@Leonidas-from-XIV, #1422)

### Fixed
- Fix compile-time crashing bugs #930 and #1385 (@jonludlam, #1400)
Expand Down
25 changes: 24 additions & 1 deletion src/document/generator.ml
Original file line number Diff line number Diff line change
Expand Up @@ -988,13 +988,36 @@ module Make (Syntax : SYNTAX) = struct
| External _ -> ([ "external" ], Syntax.Type.External.semicolon)
in
let name = Paths.Identifier.name t.id in
let zero_alloc =
match
List.find
(function Odoc_model.Lang.Value.Zero_alloc _ -> true)
t.ext_attr
with
| exception Not_found -> O.noop
| Zero_alloc { opt; strict; arity; custom_error_msg } ->
let ext_arg =
match (opt, strict) with
| true, false -> " opt"
| false, true -> " strict"
| _, _ -> ""
in
let ext_arg = ext_arg ^ Printf.sprintf " arity %d" arity in
let ext_arg =
match custom_error_msg with
| None -> ext_arg
| Some s -> ext_arg ^ Printf.sprintf "custom_error_message %S" s
in
let ext_attr = Printf.sprintf "[@@zero_alloc%s]" ext_arg in
O.cut ++ O.txt " " ++ O.txt ext_attr
in
let content =
O.documentedSrc
(O.box_hv
@@ O.keyword Syntax.Value.variable_keyword
++ O.txt " " ++ O.txt name
++ O.txt Syntax.Type.annotation_separator
++ O.cut ++ type_expr t.type_
++ O.cut ++ type_expr t.type_ ++ zero_alloc
++ if semicolon then O.txt ";" else O.noop)
in
let attr = [ "value" ] @ extra_attr in
Expand Down
3 changes: 2 additions & 1 deletion src/loader/cmi.ml
Original file line number Diff line number Diff line change
Expand Up @@ -757,7 +757,8 @@ let read_value_description ({ident_env ; warnings_tag} as env) parent id vd =
External primitives
| _ -> assert false
in
Value { Value.id; source_loc; doc; type_; value }
let ext_attr = Doc_attr.attrs_of_value_description vd in
Value { Value.id; source_loc; doc; type_; value; ext_attr }

#if defined OXCAML
let is_mutable = Types.is_mutable
Expand Down
7 changes: 5 additions & 2 deletions src/loader/cmt.ml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ let rec read_pattern env parent doc pat =
Cmi.mark_type_expr pat.pat_type;
let type_ = Cmi.read_type_expr env pat.pat_type in
let value = Abstract in
[Value {id; source_loc; doc; type_; value}]
(* TODO read ext_attr out of id *)
let ext_attr = [] in
[Value {id; source_loc; doc; type_; value; ext_attr}]
#if OCAML_VERSION < (5,2, 0)
| Tpat_alias(pat, id, _) ->
#elif defined OXCAML
Expand All @@ -68,7 +70,8 @@ let rec read_pattern env parent doc pat =
Cmi.mark_type_expr pat.pat_type;
let type_ = Cmi.read_type_expr env pat.pat_type in
let value = Abstract in
Value {id; source_loc; doc; type_; value} :: read_pattern env parent doc pat
let ext_attr = [] in
Value {id; source_loc; doc; type_; value; ext_attr} :: read_pattern env parent doc pat
| Tpat_constant _ -> []
| Tpat_tuple pats ->
#if OCAML_VERSION >= (5, 4, 0) || defined OXCAML
Expand Down
3 changes: 2 additions & 1 deletion src/loader/cmti.ml
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,8 @@ let read_value_description env parent vd =
| [] -> Value.Abstract
| primitives -> External primitives
in
Value { Value.id; source_loc; doc; type_; value }
let ext_attr = Doc_attr.attrs_of_value_description vd.val_val in
Value { Value.id; source_loc; doc; type_; value; ext_attr }

let read_type_parameter (ctyp, var_and_injectivity) =
let open TypeDecl in
Expand Down
13 changes: 13 additions & 0 deletions src/loader/doc_attr.ml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,19 @@ let attribute_unpack = function
| { Location.txt = name; loc }, attr_payload -> (name, attr_payload, loc)
#endif

let attrs_of_value_description (vd : Types.value_description) =
let zero_alloc = match vd.val_zero_alloc |> Zero_alloc.get with
| Default_zero_alloc -> None
| Ignore_assert_all -> None
| Assume { arity; _} ->
Some ( Lang.Value.Zero_alloc.{ opt = false; strict = false; arity; custom_error_msg = None })
| Check { strict; opt; arity; custom_error_msg } ->
Some ( Lang.Value.Zero_alloc.{ opt; strict; arity; custom_error_msg })
in
match zero_alloc with
| Some za -> [Lang.Value.Zero_alloc za]
| None -> []

type payload = string * Location.t

type parsed_attribute =
Expand Down
1 change: 1 addition & 0 deletions src/loader/doc_attr.mli
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,4 @@ type parsed_attribute =
]

val parse_attribute : Parsetree.attribute -> parsed_attribute option
val attrs_of_value_description : Types.value_description -> Lang.Value.attr list
11 changes: 11 additions & 0 deletions src/model/lang.ml
Original file line number Diff line number Diff line change
Expand Up @@ -321,12 +321,23 @@ end =
and Value : sig
type value = Abstract | External of string list

module Zero_alloc : sig
type t = {
opt : bool;
strict : bool;
arity : int;
custom_error_msg : string option;
}
end
type attr = Zero_alloc of Zero_alloc.t
Comment on lines +324 to +332
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


type t = {
id : Identifier.Value.t;
source_loc : Identifier.SourceLocation.t option;
value : value;
doc : Comment.docs;
type_ : TypeExpr.t;
ext_attr : attr list;
}
end =
Value
Expand Down
2 changes: 2 additions & 0 deletions src/xref2/lang_of.ml
Original file line number Diff line number Diff line change
Expand Up @@ -692,6 +692,8 @@ and value_ map parent id v =
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 *)
ext_attr = [];
}

and typ_ext map parent t =
Expand Down
16 changes: 16 additions & 0 deletions test/generators/cases/oxcaml.mli
Original file line number Diff line number Diff line change
@@ -1,2 +1,18 @@
val f : int -> ('a . 'a -> 'a) -> unit
(** Polymorphic arguments require parentheses *)

val add : bool -> int -> int -> int [@@zero_alloc]
(** Zero allocation bindings have an extension attribute attached.
See https://oxcaml.org/documentation/miscellaneous-extensions/zero_alloc_check/
*)

val add_opt : bool -> int -> int -> int [@@zero_alloc opt]
(** Like [add] but with an [opt] attribute.
*)

val add_strict : bool -> int -> int -> int [@@zero_alloc strict]
(** Like [add] but with a [strict] attribute.
*)

val[@zero_alloc] f : int -> int
(** Alternative syntax for zero alloc annotation *)
63 changes: 54 additions & 9 deletions test/generators/html/Oxcaml.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,68 @@
<h1>Module <code><span>Oxcaml</span></code></h1>
</header>
<div class="odoc-content">
<div class="odoc-spec">
<div class="spec value anchored" id="val-add">
<a href="#val-add" class="anchor"></a>
<code>
<span><span class="keyword">val</span> add :
<span>bool <span class="arrow">&#45;&gt;</span></span>
<span>int <span class="arrow">&#45;&gt;</span></span>
<span>int <span class="arrow">&#45;&gt;</span></span> int
[@@zero_alloc arity 3]
</span>
</code>
</div>
<div class="spec-doc">
<p>Zero allocation bindings have an extension attribute attached.
See
https://oxcaml.org/documentation/miscellaneous-extensions/zero_alloc_check/
</p>
</div>
</div>
<div class="odoc-spec">
<div class="spec value anchored" id="val-add_opt">
<a href="#val-add_opt" class="anchor"></a>
<code>
<span><span class="keyword">val</span> add_opt :
<span>bool <span class="arrow">&#45;&gt;</span></span>
<span>int <span class="arrow">&#45;&gt;</span></span>
<span>int <span class="arrow">&#45;&gt;</span></span> int
[@@zero_alloc opt arity 3]
</span>
</code>
</div>
<div class="spec-doc">
<p>Like <code>add</code> but with an <code>opt</code> attribute.</p>
</div>
</div>
<div class="odoc-spec">
<div class="spec value anchored" id="val-add_strict">
<a href="#val-add_strict" class="anchor"></a>
<code>
<span><span class="keyword">val</span> add_strict :
<span>bool <span class="arrow">&#45;&gt;</span></span>
<span>int <span class="arrow">&#45;&gt;</span></span>
<span>int <span class="arrow">&#45;&gt;</span></span> int
[@@zero_alloc strict arity 3]
</span>
</code>
</div>
<div class="spec-doc">
<p>Like <code>add</code> but with a <code>strict</code> attribute.</p>
</div>
</div>
<div class="odoc-spec">
<div class="spec value anchored" id="val-f">
<a href="#val-f" class="anchor"></a>
<code>
<span><span class="keyword">val</span> f :
<span>int <span class="arrow">&#45;&gt;</span></span>
<span>
<span>('a.
<span><span class="type-var">'a</span>
<span class="arrow">&#45;&gt;</span>
</span> <span class="type-var">'a</span>)
</span> <span class="arrow">&#45;&gt;</span>
</span> unit
<span>int <span class="arrow">&#45;&gt;</span></span> int
[@@zero_alloc arity 1]
</span>
</code>
</div>
<div class="spec-doc"><p>Polymorphic arguments require parentheses</p>
<div class="spec-doc"><p>Alternative syntax for zero alloc annotation</p>
</div>
</div>
</div>
Expand Down
8 changes: 7 additions & 1 deletion test/generators/latex/Oxcaml.tex
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
\section{Module \ocamlinlinecode{Oxcaml}}\label{Oxcaml}%
\label{Oxcaml--val-f}\ocamlcodefragment{\ocamltag{keyword}{val} f : int \ocamltag{arrow}{$\rightarrow$} ('a.\allowbreak{} \ocamltag{type-var}{'a} \ocamltag{arrow}{$\rightarrow$} \ocamltag{type-var}{'a}) \ocamltag{arrow}{$\rightarrow$} unit}\begin{ocamlindent}Polymorphic arguments require parentheses\end{ocamlindent}%
\label{Oxcaml--val-add}\ocamlcodefragment{\ocamltag{keyword}{val} add : bool \ocamltag{arrow}{$\rightarrow$} int \ocamltag{arrow}{$\rightarrow$} int \ocamltag{arrow}{$\rightarrow$} int [@@zero\_\allowbreak{}alloc arity 3]}\begin{ocamlindent}Zero allocation bindings have an extension attribute attached. See https://oxcaml.org/documentation/miscellaneous-extensions/zero\_alloc\_check/\end{ocamlindent}%
\medbreak
\label{Oxcaml--val-add_opt}\ocamlcodefragment{\ocamltag{keyword}{val} add\_\allowbreak{}opt : bool \ocamltag{arrow}{$\rightarrow$} int \ocamltag{arrow}{$\rightarrow$} int \ocamltag{arrow}{$\rightarrow$} int [@@zero\_\allowbreak{}alloc opt arity 3]}\begin{ocamlindent}Like \ocamlinlinecode{add} but with an \ocamlinlinecode{opt} attribute.\end{ocamlindent}%
\medbreak
\label{Oxcaml--val-add_strict}\ocamlcodefragment{\ocamltag{keyword}{val} add\_\allowbreak{}strict : bool \ocamltag{arrow}{$\rightarrow$} int \ocamltag{arrow}{$\rightarrow$} int \ocamltag{arrow}{$\rightarrow$} int [@@zero\_\allowbreak{}alloc strict arity 3]}\begin{ocamlindent}Like \ocamlinlinecode{add} but with a \ocamlinlinecode{strict} attribute.\end{ocamlindent}%
\medbreak
\label{Oxcaml--val-f}\ocamlcodefragment{\ocamltag{keyword}{val} f : int \ocamltag{arrow}{$\rightarrow$} int [@@zero\_\allowbreak{}alloc arity 1]}\begin{ocamlindent}Alternative syntax for zero alloc annotation\end{ocamlindent}%
\medbreak


25 changes: 23 additions & 2 deletions test/generators/man/Oxcaml.3o
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,31 @@ Oxcaml
.SH Documentation
.sp
.nf
\f[CB]val\fR f : int \f[CB]\->\fR ('a\. \f[CB]'a\fR \f[CB]\->\fR \f[CB]'a\fR) \f[CB]\->\fR unit
\f[CB]val\fR add : bool \f[CB]\->\fR int \f[CB]\->\fR int \f[CB]\->\fR int [@@zero_alloc arity 3]
.fi
.br
.ti +2
Polymorphic arguments require parentheses
Zero allocation bindings have an extension attribute attached\. See https://oxcaml\.org/documentation/miscellaneous-extensions/zero_alloc_check/
.nf
.sp
\f[CB]val\fR add_opt : bool \f[CB]\->\fR int \f[CB]\->\fR int \f[CB]\->\fR int [@@zero_alloc opt arity 3]
.fi
.br
.ti +2
Like add but with an opt attribute\.
.nf
.sp
\f[CB]val\fR add_strict : bool \f[CB]\->\fR int \f[CB]\->\fR int \f[CB]\->\fR int [@@zero_alloc strict arity 3]
.fi
.br
.ti +2
Like add but with a strict attribute\.
.nf
.sp
\f[CB]val\fR f : int \f[CB]\->\fR int [@@zero_alloc arity 1]
.fi
.br
.ti +2
Alternative syntax for zero alloc annotation
.nf

19 changes: 17 additions & 2 deletions test/generators/markdown/Oxcaml.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,21 @@
# Module `Oxcaml`

```ocaml
val f : int -> ('a. 'a -> 'a) -> unit
val add : bool -> int -> int -> int [@@zero_alloc arity 3]
```
Polymorphic arguments require parentheses
Zero allocation bindings have an extension attribute attached. See https://oxcaml.org/documentation/miscellaneous-extensions/zero\_alloc\_check/

```ocaml
val add_opt : bool -> int -> int -> int [@@zero_alloc opt arity 3]
```
Like `add` but with an `opt` attribute.

```ocaml
val add_strict : bool -> int -> int -> int [@@zero_alloc strict arity 3]
```
Like `add` but with a `strict` attribute.

```ocaml
val f : int -> int [@@zero_alloc arity 1]
```
Alternative syntax for zero alloc annotation