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.

Stdlib changes

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.

First, the hash function inside the Hashtbl.SeededHashedType module type has been renamed to seeded_hash. This changes make it possible for a module to implement both Hashtbl.SeededHashedType and Hashtbl.HashedType (#11157). 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 the Map.S or Set.S module types: the signatures has been expanded with new functions (to_list for Set.S and to_list of_list, and add_to_list for Map.S).

Consequently, three libraries that were defining new Map or Set functors using this signature as a constraint need to add those missing functions to their Map and 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.

New modules in the standard library

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):

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 Type will refer to the local module, and the standard library module will be accessible with 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 Type 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 caml_shared_try_alloc now 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

rather than

_[> `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.

Inexact explicit type annotation for anonymous row variable

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 App(struct end).

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).

Unique case

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 -for-pack, which happened to sometimes work in previous version of OCaml. OCaml 5.1 took the decision to stop relying on such happenstance, and mixing different -for-pack mode now always result in an error.