Weird glitch in 5.3.0 switch using utop in Emacs

Hello OCaml friends,

I write OCaml using Opam + Dune + Emacs + Tuareg + Merlin. Emacs version 30.1 and Tuareg version 3.0.1. I run utop as my REPL via tuareg-interactive-process, using a pipe as my process-connection-type. (For more info on that, see this Discuss thread.)

I decided to start messing around with a 5.3.0 switch the other day and encountered the world’s weirdest glitch:

OCaml version 5.3.0
Enter "#help;;" for help.

# Some 12;;
Line 1, characters 0-4:
Error: Unbound constructor "Some"
# ();;
Line 1, characters 0-2:
Error: Unbound constructor "()"
# print_endline;;
Line 1, characters 0-13:
Error: Unbound value "print_endline"

That’s right! Your eyes are not deceiving you: this REPL has absolutely nothing from Stdlib in scope. It does, bizarrely, have Stdlib itself in scope:

# #show Stdlib;;
module Stdlib :
  sig
    external raise : exn -> 'a = "%raise"
    external raise_notrace : exn -> 'a = "%raise_notrace"
	
	... etc, you get the picture ...

    module Weak = Weak
  end
# #show Stdlib.Option.Some;;
type 'a Stdlib.Option.t = 'a option = None | Some of 'a

Here are some further curious facts:

  • it does not arise in a 5.2.0 switch (or below); everything works fine
  • it does not arise when you run utop in a terminal, in any switch, including a 5.3.0 switch

I’m definitely hoping to fix this problem, since I’d like to be able to do my development work using the latest compiler without having to gut/remodel my entire dev environment. But building up to fixing it, I’m just baffled as to why it would even happen. Does anyone have any idea how this could possibly even happen?

If you are an Emacs etc. user, you’ll need to eval the following workaround code to run utop via tuareg-interactive-process:

(advice-add 'make-comint :around #'my-utop-workaround)

(defun my-utop-workaround (orig-fun name &rest args)
  (if (not (equal name "OCaml"))
      (apply orig-fun name args)
    (let ((process-connection-type nil))
      (apply orig-fun name args))))

Then you can take the following steps to reproduce the glitch:

  • opam switch create glitchy 5.3.0
  • opam switch set glitchy
  • eval $(opam env --switch=glitchy)
  • opam install utop
  • emacs -q
  • eval the workaround code
  • M-x package-initialize
  • M-x run-ocaml RET opam exec -- utop RET

You can confirm that it works in a terminal by running utop in a terminal, in a shell environment that is aware of the glitchy switch.

You can confirm that it works in Emacs in a 5.2.0 switch by following these steps:

  • opam switch create worksfine 5.2.0
  • opam switch set worksfine
  • eval $(opam env --switch=worksfine)
  • opam install utop
  • emacs -q
  • eval the workaround code
  • M-x package-initialize
  • M-x run-ocaml RET opam exec -- utop RET

It should look like this:

OCaml version 5.2.0
Enter #help;; for help.

# ();;
- : unit = ()
# Some 12;;
- : int option = Some 12
# print_endline;;
- : string -> unit = <fun>