Skip to content

Multi-version support - #100

Open
fantazio wants to merge 16 commits into
LexiFi:masterfrom
fantazio:cppo_54
Open

Multi-version support#100
fantazio wants to merge 16 commits into
LexiFi:masterfrom
fantazio:cppo_54

Conversation

@fantazio

@fantazio fantazio commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Fix #44, #45, #89

The dead_code_analyzer only supported one OCaml version at a time. Updating to a more recent version of OCaml meant losing support for the previous version. This PR adds multi-version support by relying on cppo.

The supported versions range from OCaml 4.14 to OCaml 5.5.

The project-configuration files (dune, opam, ci) have been updated. We may reduce the list of tested releases in the future, to reduce CI cost.

Tests

All the tests are passing.
New tests have been added to illustrate the fixed inconsistencies in the coding style section (observed on Frama-C and Opam).

The remaining inconsistencies are documented in #103. They mostly affect optional arguments.

A new false positive (6 occurences in the tests) is unveiled by the new tests. It is documented as the "tautological case" in #102.

Performance benchmarks on Frama-C and Opam are coherent.

On cppo

The choice of cppo was made because it is the most widely used preprocessor for specifically this category of task (version-dependent code).
The bounds in the macro conditionals are explicit : min bound and max bound are always stated, even when they are 4.14 or 5.6. The intent is for avoid having to remember which versions are supported each time one would read half bounded (min or max only) code.

Alternatively, ppx_optcomp was considered because, being a ppx, it would be more tooling-friendly. However, it comes with base as a dependency (although that should change), and I'd like to keep the project's dependencies minimal.

Spoiler: I am building an alternative for compiler-libs dependent code.

Project vaast aims at providing abstractions of compiler-libs components (such as the Typedtree), which would move the compiler version-dependency information from the preprocessor to the type checker. Thus, user code would remain tool-friendly, avoid the unsafety and code complexity that comes with cppo, and be easier to adapt to new OCaml versions.
There should be a first release to open design discussions with concerned actors soon™

This was linked to issues Sep 1, 2026
This relies on cppo. Compilation issues are naively fixed. No error is
introduced in the tests.

The project-configuration files (dune, opam, ci) have been updated.
This relies on cppo. Compilation issues are naively fixed.
FP (and corresponding FN) are introduced in the tests.

The project-configuration files (dune, opam, ci) have been updated.
…d as `Tpoly`

Since OCaml 5.5 function arguments types must be `Tpoly` nodes.
Monoporphic arguments are therefore represented as a `Tpoly` with an
empty list of names. When looking for the type description, we always
want to this pattern as the underlying monoporhic type.

This fixes the FP/FN introduced in the previous commit (adaptation to
OCaml 5.5).
This relies on cppo. Compilation issues are naively fixed. No error is
introduced in the tests.

The project-configuration files (dune, opam, ci) have been updated.
@fantazio
fantazio force-pushed the cppo_54 branch 3 times, most recently from 883efc7 to ce7aac9 Compare September 3, 2026 16:20
This relies on cppo. Compilation issues are naively fixed. No error is
introduced in the tests.

The project-configuration files (dune, opam, ci) have been updated.
This relies on cppo. Compilation issues are naively fixed. No error is
introduced in the tests.

The project-configuration files (dune, opam, ci) have been updated.
This relies on cppo. Compilation issues are naively fixed. No error is
introduced in the tests.

The project-configuration files (dune, opam, ci) have been updated.
The `let (x : t) = ...` case produces a different tree from
`let x = ...`. The former is translated to `let ((_ : t) as x) = ...`.
The particular case of `let x : t = ...` is either equivalent to the
former (from OCaml 5.1 to 5.4) or the latter.
The former is equivalent to the latter in OCaml 5.5.
Identify `let (_ as x) = ... in x` as a useless binding.
This pattern is created by e.g. `let (x : t) = ... in x`.
Notable issues are:
- Multiline lhs with multiple arguments lead to mismatch locations as
  demonstrated by `multiline_opt_arg_in_arg` and;
  `implicit_multiline_opt_arg_in_arg`
- If the arg is itself an opt arg then we only detect the style issue in
  OCaml >= 5.2 and if a default value is provided (example
  `opt_arg_in_opt_arg_val`).

There is also a non-stylistic opt arg issue related to implicit optional
arguments (demonstrated by the `implicit_*` functions): although the
optional arguments are never explicited, they are implied by the use of
the function as argument. Those optional arguments exist because they
are used by requirement, making the reports tautological.
This fixes the 2 style issues identified in the previous commit:
- Because `Texp_function`'s representation changed in OCaml 5.2,
  rather than using its location, we need to use the params' locs (which
  were unavailable in prior versions);
- The types of opt args are always wrapped in an `option` (even if a
  default valuei is provided), making them `Tconstr`. In order to
  properly check the "opt arg in arg" style on opt args, we need to
  extract the underlying type from the `Tconstr`.
If there is an optional argument with default value before the faulty
arg, then the style issue is not detected in OCaml < 5.2.
In the case where there is an opt arg with a default value before the
faulty arg, in OCaml < 5.2, an internmediate `let ... in` is intoduced
to set the value of the opt arg before the rest of the `function`.
Constrained vars are misunderstood patterns in OCaml < 5.5, leading to
`!!pattern!!` in the reports instead of a name, and even FP in case the
name was prefixed by an underscore.
Identify `((_ : unit) as x)` as a unit binding. This pattern is created
 by e.g. `(x : unit)`
The same heavy pattern matching to extract the id of a `Tpat_var` (or
`Tpat_alias (Tpat_any)`) was used in 3 different places.
This is now done in `id_of_var` which returns an option.
@fantazio
fantazio marked this pull request as ready for review September 7, 2026 10:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

OCaml 5.5 support OCaml 5.4 compatibility OCaml 4.14 compatibility

1 participant