Florian's OCaml compiler weekly, 5 July 2023
- July 5, 2023
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.