This series of blog posts aims to give a short weekly glimpse into my (Florian Angeletti) daily work on the OCaml compiler. The subject this week is a cartography of the source of opam packages breakage in OCaml 5.1.0 .
With the recent release of the first beta for OCaml 5.1, I have spent some time at the state of the opam ecosystem looking for package that broke with OCaml 5.1 .
Interestingly, for this beta there most of those incompatibility stemmed from 7 changes in OCaml 5.1, which is a small enough number that I can list all those potentially package-breaking changes in this blog post.
Unsurprisingly, most of the package build failures finds their source in small changes of the standard library. Those changes accounts for at least 8 package build failures in the opam repository at the time of the first beta release.
Updated module types in the standard library
More precisely, one source of build failure is the changes in module types defined in the standard library. Such module types are a known source of backward compatibility difficulty. Depending on the uses of those module types, any change in the module types can create a build failure.
And OCaml 5.1 updated three of such module types.
hash function inside the
Hashtbl.SeededHashedType module type has been renamed to
seeded_hash. This changes make it possible for a module to
Unfortunately, this change breaks modules that were using
Hashtbl.MakeSeeded with the previous signature for the
argument of the functor.
When the change was proposed there were only 6 opam packages affected
by this change. Thus, the improved usability for the
Hashtbl.MakeSeeded functor seemed worth the price. And at
the time of the first beta release, I have only seen two remaining
packages still affected by this change.
Second, a more subtle problem occurred for libraries that were using
Set.S module types: the
signatures has been expanded with new functions (
Consequently, three libraries that were defining new
Set functors using this signature as a constraint need
to add those missing functions to their
Set implementations. Those failures are maybe less
surprising: if one library use a module type provided by the standard
library for one of its own implementation, it inevitably couple strongly
itself to the standard library specification.
Another source of difficulty is that the standard library has been
added a new
Type module in OCaml 5.1. This new module
defines the well-know equality GADT (Generalized Abstract Data
type (_, _) eq = Equal : ('a, 'a) eq
and type identity witnesses. In other words, this is mostly a module for heavy users of GADTs.
Normally, adding a new module to the standard library can be done
painlessly: Standard library modules have a lower priority compared to
local modules. Thus, if someone has a project which defines a
Type module, the non-qualified name
refer to the local module, and the standard library module will be
Stdlib.Type. However, this low priority
behaviour requires some special support in the compiler and alternative
standard library lacks this support. Consequently, libraries (at least
three) that are defining a local
Type module while using
alternative standard library (like
base) might be required
to find a non-conflicting short-name for their local
module (which might be as simple as
module Ty = Type open! Base
Internal API changes
The second ex æquo source of build failures in opam packages is the changes in internal API, either in the OCaml runtime or in the compiler library.
Changes in the runtime internal API
The internal runtime function
takes the number of reserved bits in the header as a supplementary
argument. This change affected at least one opam package.
Change in the compiler-libs API
To improve the rendering of weakly polymorphic row variables, OCaml 5.1 has switched its high-level display representation of type aliases to make it easier to display “weakly polymorphic aliases”:
[> `X of int] as _weak1
_[> `X of int]
This caused a build failure for at least one package that was relying on the previous API.
Type system change
The third ex æquo source of build failures is small changes in the type system, where package that were at the frontier of the technically correct and bugs ended up falling on the other side of the fence during this release.
For instance, due to a bug fix, OCaml 5.1 is stricter when mixing explicitly polymorphic type annotations and anonymous row variables. Even with all the precautions described in http://gallium.inria.fr/blog/florian-compiler-weekly-2023-04-28, there was at least one opam package that was affected. On the bright side, this was probably a bug in the lone affected package.
Generative functors must be used generatively
When a functor is defined as an applicative functor
module App() = struct type t end
OCaml 5.1 forbids now to apply as if it was a generative functor:
module Ok = App(struct end) module New_error = App()
Previous version of OCaml did not make any difference between
struct end or
() in functor applications and
thus allowed the form
The reverse situation, where a generative functor is applied to
struct end is allowed but emits a warning
module Gen() = struct type t end module New_warning = Gen(struct end)
Warning 73 [generative-application-expects-unit]: A generative functor should be applied to '()'; using '(struct end)' is deprecated.
This restriction is there to make clearer the distinction between applicative and generative application. But at least $one opam package needed to be updated (at the time of the beta release).
Sometimes, there are also backward compatible issue with packages
that were using the compiler in surprising ways. For instance, this
time, one package build failed because it was trying to link without
-for-pack modules compiled with
which happened to sometimes work in previous version of OCaml. OCaml 5.1
took the decision to stop relying on such happenstance, and mixing
-for-pack mode now always result in an error.