Skip to content

Conversation

@art-w
Copy link
Collaborator

@art-w art-w commented Oct 13, 2025

Follow up on #12392 and #12336: This PR adds the ability to instantiate parameterized libraries in library/executable dependencies. This feature requires compiler support which is only available in OxCaml atm.

In stanza dependencies, the new form (my_lib arg1 arg2 ... :as new_name) specifies that the parameterized library my_lib should be instantiated with a list of arguments (which must implement the parameters requested by the lib). The arguments are identified by the parameter interface they implement, such that the order in which they are provided doesn't matter. (It's nominal not positional. We sort them internally in dune, but only to ensure we don't duplicate efforts to instantiate both (f x y) and (f y x), since they are the same.)

The optional :as new_name allow renaming the instantiation in user code, which is useful when the same library is instantiated multiple times (otherwise if missing, by default My_lib is used as the name of the instantiation). In the alias file, each instantiation is translated to a module New_name = My_lib(Param1)(Arg1)(Param2)(Arg2)(* ... *) [@jane.non_erasable.instances]. (Note that each ArgN implements ParamN, i.e. (Arg1 : Param1), so this is not a real functor application, hence the OxCaml attribute.)

While libraries can do a partial application of their parameterized dependencies (as long as their list of parameters is a superset of missing dependency parameters), executables must fully instantiate their dependencies. Computing the transitively instantiated libraries requires passing the applied arguments to each of parameterized dependencies that requires this parameter. This impacts the existing transitive closures performed by dune, since now the same library may be required multiple times but with different arguments.

To link the executables, all the (transitively) instantiated libraries must also be built by the compiler with the new OxCaml command ocamlopt -instantiate mylib.cmx arg1.cmx arg2.cmx ... -o mylib-Arg1-Arg2...cmx. Here a partial instantiation is not allowed (all arguments must be specified). Furthermore, this should be done for all modules of mylib, in their topological order. We create an archive at the end to package everything.

Instantiated modules use dashes in their filenames to "parenthesize" their arguments, which can themselves contain dashes if they are the result of another instantiation. The number of dashes indicates the depth of the application. For example, a filename f-g--h---x-y corresponds to the application F(G(H(X)))(Y). We reuse this idea for the name of the folders where we instantiate each module of a fully instantiated parameterized dependency. A dependency (mylib arg1 arg2) (fully applied) is instantiated in the _build subfolder .parameterized/mylib!arg1!arg2. We can't use dash separators here because it's already allowed in library names and would be ambiguous, so instead we arbitrarily picked the exclamation mark instead... but let us know if there's a better choice!

Finally for opam installations, we export all the dependencies arguments (but not their concrete instantiation, since this will done by user executables when needed).

While I still have a couple of TODOs to address minor issues (or so I hope), this PR seems to be working... so I would appreciate an early review to check I haven't done anything stupid :)

Fix #12088

@rgrinberg rgrinberg self-requested a review October 13, 2025 14:48
@art-w art-w force-pushed the instantiate-parameterized branch 2 times, most recently from f50988f to c8b1102 Compare October 14, 2025 16:15
@shonfeder shonfeder requested a review from Alizter October 15, 2025 13:15
@art-w art-w force-pushed the instantiate-parameterized branch 3 times, most recently from bef411b to 9a9f67a Compare October 22, 2025 09:16
| ".parameterized" :: rest ->
let* sctx = sctx
and* scope = Scope.DB.find_by_dir dir in
Parameterized_rules.gen_rules ~sctx ~scope ~dir rest
Copy link
Member

Choose a reason for hiding this comment

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

This ~dir is always the root of the workspace, in which case the scope is just the current project. Is that correct?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I believe so yes, the parameterised libraries are always instantiated at the root _build/<profile>/.parameterised so ~dir is always the root. Should I be using something else to get the scope? (or is there an issue with using the root dir?)

Copy link
Member

Choose a reason for hiding this comment

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

The scope at the root will only be able to look up public libraries and private libraries of the project root. I think that's probably incorrect if some instances are private?

You should be able to demonstrate that this doesn't work in a test where:

$ ls
dune-project
a/
$ ls b/
dune-project

And project b/ needs some private instance instantiated.

In the jsoo rules, this isn't a problem because we only generate global rules for public libraries. But if you look at the ppx rules (ppx_driver.ml), you will see that we are careful to encode the project id into the identifier in the path.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Oh thanks for the clarifications and the ppx_driver pointer! I've extended the test of vendored parameterised libraries with private libraries and private instantiations, to show that the scope is correctly handled :)

@rgrinberg
Copy link
Member

Looks like you're going in the right direction. There's a massive amount of code here, and I couldn't go thoroughly through everything in one go. I'll try do another pass soon.

I'm a bit concerned about all the somewhat similar but not quite the same rules in parameterized_rules.ml. Do you intend to factor this out in a better way with the rest of the rules?

@rgrinberg rgrinberg added the oxcaml Related to the support to OxCaml functionnalities label Oct 23, 2025
@art-w art-w force-pushed the instantiate-parameterized branch 4 times, most recently from 360cbb1 to 8d5bd86 Compare October 30, 2025 14:11
@art-w art-w force-pushed the instantiate-parameterized branch 2 times, most recently from 80a022b to 4bbd3c2 Compare November 3, 2025 15:13
@art-w
Copy link
Collaborator Author

art-w commented Nov 3, 2025

Thanks for the review @rgrinberg! Would you have time for another round? :) I believe all the major issues have been fixed, there's more tests and documentation, and I've integrated feedback from @Alizter too.

The only pending issues I know of is with unwrapped libraries, which currently work but support could be better. The pre-existing bug #6148 is easy to trigger, and the automatic generation of module X = Lib(Param)(Impl)[@jane...] is missing due to a lack of alias file. Unless I find a short fix or support is critical, I think it's better to follow-up in another PR specifically for unwrapped libraries than grow this one further.

I'm a bit concerned about all the somewhat similar but not quite the same rules in parameterized_rules.ml. Do you intend to factor this out in a better way with the rest of the rules?

Are you thinking of the build archive rule or something else? We looked at it with @Alizter and it's unclear that this can be simplified easily without refactoring Module for instantiated artifacts (and at the PR is already big, I would again prefer not to introduce more changes here!)
Did you have something else in mind to better fit with the existing rules?

@rgrinberg
Copy link
Member

Sure, will do another round this week.

@art-w art-w force-pushed the instantiate-parameterized branch from 9488fa6 to fed00c9 Compare November 5, 2025 15:09
@shonfeder
Copy link
Member

@rgrinberg is that a request for particular work on this PR, or for followup documentation work? Just asking because it is not obvious to me whether this PR needs additional work, is currently still under review, or is in an inbetween state. :)

@rgrinberg
Copy link
Member

I'd like to see the following in this pr:

  1. Tests and comments for the current set of limitations. They would make it much easier to pick up the work for anybody in the future.

  2. Address the comment about the scope that is used for looking up instances.

Sorry if I wasn't clear.

@art-w
Copy link
Collaborator Author

art-w commented Nov 17, 2025

Thanks! I believe the scope issue is now fixed, with a test to show that private instantiations work.
Regarding unwrapped libraries, I've extended the documentation to explain the limitations and this test has a TODO item associated with lifting them.

@rgrinberg
Copy link
Member

rgrinberg commented Nov 19, 2025

By the way please user your full name to sign commits. See https://github.com/ocaml/dune/blob/main/CONTRIBUTING.md

@art-w art-w force-pushed the instantiate-parameterized branch 2 times, most recently from d9b2fa4 to d7d212a Compare November 20, 2025 10:13
@art-w
Copy link
Collaborator Author

art-w commented Nov 20, 2025

Thanks @Alizter for the review! The PR should be all clean now, rebased and squashed with my full name for the sign off :)

@Alizter
Copy link
Collaborator

Alizter commented Nov 20, 2025

I will merge this later today. One thing I've noticed is that there is slippage between the spelling of parameterised and parameterized. It doesn't bother me too much but it's better to be consistent. I think in dune we have tended for American English in the code and British English in the docs.

Do you know what the correct one should be? We don't have to fix it in this PR.

@art-w art-w force-pushed the instantiate-parameterized branch from d7d212a to c7c8cb1 Compare November 20, 2025 14:00
@art-w art-w changed the title feat(oxcaml): instantiate parameterized libraries feat(oxcaml): instantiate parameterised libraries Nov 20, 2025
@art-w
Copy link
Collaborator Author

art-w commented Nov 20, 2025

We decided to follow the British spelling used by OxCaml, with an S, so I've corrected the few instances left of parameteriZed to keep everything uniform:

$ ocamlc -config | grep param
parameterised_modules: true

Signed-off-by: Arthur Wendling <arthur@tarides.com>
@art-w art-w force-pushed the instantiate-parameterized branch from f94ab22 to bbc8334 Compare November 21, 2025 12:03
@Alizter Alizter enabled auto-merge November 21, 2025 12:08
@Alizter Alizter merged commit c7c1170 into ocaml:main Nov 21, 2025
27 checks passed
Leonidas-from-XIV pushed a commit that referenced this pull request Dec 9, 2025
Before this change, it was producing the category subheadings at the
wrong level (level 2), instead of level 3.

An example of the corrected changelog produced:

```
3.21.0 (2025-12-08)
--------------------

### Fixed

- Fix `include_subdirs qualified` incorrectly picking the furthest module
  instead of the closest when resolving module name ambiguities. (#12587,
  @ElectreAAS and @Alizter)

- Fix: include the module alias in the transitive dependency closure with
  `(include_subdirs qualified)`. (#12299, @anmonteiro)

- Pass private modules with -H when this is available (#12666, @rgrinberg)

- Allow multiple modules in `(modules_flags ...)`, in `coq.theory` (#12733, @rlepigre)

- Improve error message for invalid version formats in both `(lang dune ...)` and
  `(using extension ...)` declarations. Changes "Atom of the form NNN.NNN expected"
  to "Invalid version. Version must be two numbers separated by a dot." (#12833, @benodiwal)

- Fix crash when running `dune build @check` on a library with virtual modules.
  (#12644, fixes #12636, @Alizter)

- Provide a more informative error message when `(pkg enabled)` is put in
  `dune-project` instead of `dune-workspace`. (#12802, fixes #12801,
  @benodiwal)

- Improve error message when invalid version strings are used in `dune-project`
  files. Non-ASCII characters and malformed versions now show a helpful hint
  with an example of the correct format. (#12794, fixes #12751, @benodiwal)

- Stop hiding the `root_module` from the include path (#12239, @rgrinberg)

- Allow `$ dune init` to work on absolute paths (#12601, fixes #7806,
  @rgrinberg)

- `(include_subdirs qualified)`: Add missing alias dependency to module group.
  (#12530, @anmonteiro)

- Add Melange compilation to the `@all` alias in libraries (#12628,
  @anmonteiro)

- melange support: don't emit empty JavaScript modules for generated module
  aliases. (#12464, @anmonteiro)

### Added

- (Experimental): Introduce the `library_parameter` stanza. It allows users to
  declare a parameter when using the OxCaml compiler.
  (#11963, implements #12084, @maiste) 


- Added the ability to scroll horizontally in TUI. (#12386, @Alizter)

- Feature: Include shell command that was executed when a cram test has
  occurred in the error message (#12307, @rgrinberg)

- Add support for `%{cmt:...}` and `%{cmti:...}` variables to reference
  compiled annotation files (.cmt and .cmti) containing typed abstract syntax
  trees with location and type information. (#12634, grants #12633, @Alizter)

- Add `$ dune describe tests` to describe the tests in the workspace
  (@Gromototo, #12545, fixes #12030)

- Allow `dune runtest` to properly run while a watch mode server is running.
  (#12473, grants #8114, @gridbugs and @ElectreAAS)

- Use copy-on-write (COW) when copying files on filesystems that support it
  (Btrfs, ZFS, XFS, etc), under Linux. (#12074, fixes #12071, @nojb)

- Add support for Tangled ATproto-based code repositories (#12197, @avsm)

- Add support for instantiating OxCaml parameterised libraries.
  (#12561, @art-w)
```

Note that the `Fixed` and `Added` headers are at level 3.

Compare with the previous, incorrect logic, produced initially in the
current release branch:
https://github.com/shonfeder/dune/blob/c5af78ff322225de3e839982600a1180caa951bf/CHANGES.md?plain=1

This incorrect formatting caused the documented release process to
produce release with no changelog (e.g.,
https://github.com/ocaml/dune/releases/tag/3.21.0_alpha0)

Signed-off-by: Shon Feder <shon.feder@gmail.com>
shonfeder added a commit that referenced this pull request Dec 11, 2025
Follow-up on the instantiation of parameterised libraries #12561 to
support `inline_tests` (marking this PR as a draft since only the last
commit is new).

To run the inline tests of a parameterised library, the user should
specify with `(arguments ...)` which implementation of the parameters to
use, as otherwise the test can't be ran.

Fix #12110
shonfeder added a commit to shonfeder/opam-repository that referenced this pull request Dec 15, 2025
CHANGES:

### Fixed

- Fix `include_subdirs qualified` incorrectly picking the furthest module
  instead of the closest when resolving module name ambiguities. (ocaml/dune#12587,
  @ElectreAAS and @Alizter)

- Fix: include the module alias in the transitive dependency closure with
  `(include_subdirs qualified)`. (ocaml/dune#12299, @anmonteiro)

- Improve error messages for invalid version formats containing non-ASCII
  characters. Previously, non-ASCII characters in version strings (e.g., `(lang
  dune è)` or `(using menhir π3.14)`) would fail with a generic "Invalid file"
  error. Now they display a clear message: "Invalid atom: contains non-ASCII
  character(s). Atoms must only contain ASCII characters." The fix is
  implemented at the lexer level, providing consistent error handling across all
  s-expression parsing. (ocaml/dune#12844, fixes ocaml/dune#12836, @benodiwal)

- Pass private modules with -H when this is available (ocaml/dune#12666, @rgrinberg)

- Allow multiple modules in `(modules_flags ...)`, in `coq.theory` (ocaml/dune#12733, @rlepigre)

- Improve error message for invalid version formats in both `(lang dune ...)` and
  `(using extension ...)` declarations. Changes "Atom of the form NNN.NNN expected"
  to "Invalid version. Version must be two numbers separated by a dot." (ocaml/dune#12833, @benodiwal)

- Fix crash when running `dune build @check` on a library with virtual modules.
  (ocaml/dune#12644, fixes ocaml/dune#12636, @Alizter)

- Provide a more informative error message when `(pkg enabled)` is put in
  `dune-project` instead of `dune-workspace`. (ocaml/dune#12802, fixes ocaml/dune#12801,
  @benodiwal)

- Improve error message when invalid version strings are used in `dune-project`
  files. Non-ASCII characters and malformed versions now show a helpful hint
  with an example of the correct format. (ocaml/dune#12794, fixes ocaml/dune#12751, @benodiwal)

- Stop hiding the `root_module` from the include path (ocaml/dune#12239, @rgrinberg)

- Allow `$ dune init` to work on absolute paths (ocaml/dune#12601, fixes ocaml/dune#7806,
  @rgrinberg)

- `(include_subdirs qualified)`: Add missing alias dependency to module group.
  (ocaml/dune#12530, @anmonteiro)

- Add Melange compilation to the `@all` alias in libraries (ocaml/dune#12628,
  @anmonteiro)

- Fix greedy version location in lang declarations. Previously, error locations for
  invalid lang versions would span multiple bytes for multi-byte UTF-8 characters,
  causing carets to appear misaligned and seemingly include the closing
  parenthesis. Now, error locations for ASCII strings show the full length (e.g.,
  "Ali" shows `^^^`), while non-ASCII strings show only the first byte (e.g., "è"
  shows `^`) to avoid multi-byte character display issues. (ocaml/dune#12869, fixes ocaml/dune#12806,
  @benodiwal)

- melange support: don't emit empty JavaScript modules for generated module
  aliases. (ocaml/dune#12464, @anmonteiro)

### Added

- (Experimental): Introduce the `library_parameter` stanza. It allows users to
  declare a parameter when using the OxCaml compiler.
  (ocaml/dune#11963, implements ocaml/dune#12084, @maiste)

- Added the ability to scroll horizontally in TUI. (ocaml/dune#12386, @Alizter)

- Feature: Include shell command that was executed when a cram test has
  occurred in the error message (ocaml/dune#12307, @rgrinberg)

-  support expanding variables in `(promote (into ..))` (ocaml/dune#12832, fixes ocaml/dune#12742,
   @anmonteiro)

- Add support for `%{cmt:...}` and `%{cmti:...}` variables to reference
  compiled annotation files (.cmt and .cmti) containing typed abstract syntax
  trees with location and type information. (ocaml/dune#12634, grants ocaml/dune#12633, @Alizter)

- Add `$ dune describe tests` to describe the tests in the workspace
  (@Gromototo, ocaml/dune#12545, fixes ocaml/dune#12030)

- Add `argv`, the process environment, and the dune version to the config event
  in the trace (ocaml/dune#12909, @rgrinberg)

- Allow `dune runtest` to properly run while a watch mode server is running.
  (ocaml/dune#12473, grants ocaml/dune#8114, @gridbugs and @ElectreAAS)

- Use copy-on-write (COW) when copying files on filesystems that support it
  (Btrfs, ZFS, XFS, etc), under Linux. (ocaml/dune#12074, fixes ocaml/dune#12071, @nojb)

- Add support for Tangled ATproto-based code repositories (ocaml/dune#12197, @avsm)

- Add support for instantiating OxCaml parameterised libraries.
  (ocaml/dune#12561, @art-w)

- Add a `(conflict_markers error|ignore)` option to the cram stanza. When
  `(conflict_markers error)` is set, the cram test will fail in the presence of
  conflict markers. Git, diff3 and jujutsu conflict markers are detected.
  (ocaml/dune#12538, ocaml/dune#12617, ocaml/dune#12655, fixes ocaml/dune#12512, @rgrinberg, @Alizter)

- Introduce a `%{ppx:lib1+..+libn}` stanza to make it possible to refer to ppx
  executables built by dune. This is useful for writing tests (ocaml/dune#12711,
  @rgrinberg)

- Introduce a `(dir ..)` field on packages defined in the `dune-project`. This
  field allows to associate a directory with a particular package. This makes
  dune automatically filter out all stanzas in this directory and its
  descendants with `--only-packages`. All users are recommended to switch to
  using this field. (ocaml/dune#12614, fixes ocaml/dune#3255, @rgrinberg)

- Add support for `DUNE_ROOT` environment variable, similar to the existing
  `--root` CLI parameter. (fixes ocaml/dune#12399 @sir4ur0n)

- Introduce an `unused-libs` alias to detect unused libraries.
  (ocaml/dune#12623, fixes ocaml/dune#650, @rgrinberg)

- Add `--files` flag to `dune describe opam-files` to print only the names of
  the opam files line by line. (ocaml/dune#9793, @reynir and @Alizter)

- `dune exec` now accepts absolute paths inside the workspace.
  (ocaml/dune#12094, @Alizter)

- Add `coqdoc_header` and `coqdoc_footer` fields to the `coq` field of the
  `env` stanza, and to the `coq.theory` stanza, allowing to configure a
  custom header or footer respectively in the HTML output of `coqdoc`.
  (ocaml/dune#11131, @rlepigre)

- Allow `dune fmt` to properly run while a watch mode server is running.
  Note that the `--preview` flag is not supported in this mode.
  (ocaml/dune#12064, @ElectreAAS)

- Support for generating `_CoqProject` files for `coq.theory` stanzas.
  (ocaml/dune#11752, @rlepigre)

- Added `(files)` stanza, similar to `(dirs)` to control which files are visible
  to Dune on a per-directory basis. (ocaml/dune#12879, @nojb)
- Add support for %{ocaml-config:ox} (ocaml/dune#12236, @jonludlam)

- Introduce `dune promotion show` command to display the contents of corrected
  files that are ready for promotion. This allows users to preview changes
  before running `dune promote`. The command accepts file arguments to show
  specific files, or displays all promotable files when called without
  arguments. (ocaml/dune#12669, fixes ocaml/dune#3883, @MixiMaxiMouse)
- New `(lang rocq)` build mode for Rocq 9.0 and later. This new mode
  is very similar to the existing `(lang coq)`, except that it doesn't
  need the `coq*` compatibility wrappers. As of today `(lang rocq)`
  doesn't support yet composed builds with Rocq itself, this will be
  added later.  `(lang coq)` is deprecated, development is frozen, and
  will be removed at some point in the future. (ocaml/dune#12035, @ejgallego,
  @Lysxia, fixes ocaml/dune#11572)

### Changed

- Don't run `ocamldep` to compute false dependencies on the `root_module`
  (ocaml/dune#12227, @rgrinberg)

- `dune format-dune-file` now uses the syntax version of the Dune project that
  contains the file being formatted (if any) instead of using the latest version
  available, which remains the default if there is no Dune project in scope.
  (ocaml/dune#11865, @nojb)

- Persistent DB and process events have been slightly modified. Persistent
  DB events have more concise names and job events always include full
  information. (ocaml/dune#12867, @rgrinberg)

- Removed the `--trace-extended` flag. Its functionality is always enabled when
  tracing is active (ocaml/dune#12908, @rgrinberg)

- The `test/dune` file generated by `dune init proj` now depends on the project library. (ocaml/dune#12791, @shonfeder)

- Starting with version 3.21 of the Dune language, Dune no longer changes the
  default set of compiler warnings. For users that would like to keep the old
  behaviour, the variable `%{dune-warnings}` can be used in an `(env)` stanza in
  a top-level Dune file: `(env (dev (flags :standard %{dune-warnings})))`.
  (ocaml/dune#12766, @nojb)
- Fix: stop generating `cmt` files for cinaps binaries (ocaml/dune#12530, @rgrinberg)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

oxcaml Related to the support to OxCaml functionnalities

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[OxCaml] Parameterized libraries: library instantiation

4 participants