Mailing list for all users of the OCaml language and system.
 help / color / mirror / Atom feed
* [Caml-list] automatically resolving open?
@ 2025-04-23 14:10 Kenichi Asai
  2025-04-23 14:32 ` Francois Pottier
  2025-04-24  6:39 ` Virgile Prevosto
  0 siblings, 2 replies; 10+ messages in thread
From: Kenichi Asai @ 2025-04-23 14:10 UTC (permalink / raw)
  To: caml-list

Would it be possible to transform an OCaml file to the one that does
not use open?  For example, if I have:

open List
let test = map (fun x -> x + 1) [1; 2; 3]

I want to obtain:

let test = List.map (fun x -> x + 1) [1; 2; 3]

where all the opened identifiers are prefixed with the opened module
names.

ocamlc -dtypedtree appears to produce a typed tree where all the
opened variable references are resolved.  Would it be possible to
transform this typed tree back to a source program?

-- 
Kenichi Asai

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [Caml-list] automatically resolving open?
  2025-04-23 14:10 [Caml-list] automatically resolving open? Kenichi Asai
@ 2025-04-23 14:32 ` Francois Pottier
  2025-04-23 14:38   ` BOBOT François
                     ` (2 more replies)
  2025-04-24  6:39 ` Virgile Prevosto
  1 sibling, 3 replies; 10+ messages in thread
From: Francois Pottier @ 2025-04-23 14:32 UTC (permalink / raw)
  To: Kenichi Asai, caml-list


Hello,

Le 23/04/2025 à 16:10, Kenichi Asai a écrit :
> Would it be possible to transform an OCaml file to the one that does
> not use open?

I don't know whether it is possible/easy to do this today,
but it would certainly interesting and useful to have such
a tool.

I note that the output that you expect cannot always be
produced, due to name shadowing issues. For example if
the program is

open List
module List = struct end
let test = map (fun x -> x + 1) [1; 2; 3]

then the best output that one can expect is

let map = List.map
module List = struct end
let test = map (fun x -> x + 1) [1; 2; 3]

That said (contradicting myself), one can actually obtain
better output if one is careful to always use absolute paths.
In this example one could write:

module List = struct end
let test = Stdlib.List.map (fun x -> x + 1) [1; 2; 3]

which relies on the fact that the name "Stdlib" is not shadowed.

As far as I know there is currently no syntax for absolute
paths in OCaml (every path is relative, and every name can
be shadowed). Maybe we should consider adding such a syntax?

-- 
François Pottier
francois.pottier@inria.fr
https://cambium.inria.fr/~fpottier/


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [Caml-list] automatically resolving open?
  2025-04-23 14:32 ` Francois Pottier
@ 2025-04-23 14:38   ` BOBOT François
  2025-04-23 14:45   ` Ivan Gotovchits
  2025-04-24  4:33   ` Oleg
  2 siblings, 0 replies; 10+ messages in thread
From: BOBOT François @ 2025-04-23 14:38 UTC (permalink / raw)
  To: francois.pottier, asai, caml-list

[-- Attachment #1: Type: text/plain, Size: 641 bytes --]

Le mercredi 23 avril 2025 à 16:32 +0200, Francois Pottier a écrit :
As far as I know there is currently no syntax for absolute
paths in OCaml (every path is relative, and every name can
be shadowed). Maybe we should consider adding such a syntax?


In a dune library, one can require the generation of a root module:

(root_module <module>)

This field instructs Dune to generate a module that will contain module aliases for every library specified in dependencies.
This is useful whenever a library is shadowed by a local module. The library may then still be accessible via this root module.

Best,

--
François Bobot

[-- Attachment #2: Type: text/html, Size: 1400 bytes --]

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [Caml-list] automatically resolving open?
  2025-04-23 14:32 ` Francois Pottier
  2025-04-23 14:38   ` BOBOT François
@ 2025-04-23 14:45   ` Ivan Gotovchits
  2025-04-23 15:33     ` Jeremy Yallop
  2025-04-24  4:33   ` Oleg
  2 siblings, 1 reply; 10+ messages in thread
From: Ivan Gotovchits @ 2025-04-23 14:45 UTC (permalink / raw)
  To: Francois Pottier; +Cc: Kenichi Asai, caml-list

[-- Attachment #1: Type: text/plain, Size: 1812 bytes --]

An interesting corner case is
```
open struct let map f x = f x end
let () = map (fun _ -> ()) 0
```
since OCaml let's us open anonymous modules. In this case, -dtypedtree
generates an unqualified names. So it would be rather easy to remove all
opens to of the named modules, since OCaml will generate fully qualified
names, e.g., `Texp_ident "Stdlib!.List.map"`, but for anonymous modules, it
would be harder to do.

On Wed, Apr 23, 2025 at 10:32 AM Francois Pottier <francois.pottier@inria.fr>
wrote:

>
> Hello,
>
> Le 23/04/2025 à 16:10, Kenichi Asai a écrit :
> > Would it be possible to transform an OCaml file to the one that does
> > not use open?
>
> I don't know whether it is possible/easy to do this today,
> but it would certainly interesting and useful to have such
> a tool.
>
> I note that the output that you expect cannot always be
> produced, due to name shadowing issues. For example if
> the program is
>
> open List
> module List = struct end
> let test = map (fun x -> x + 1) [1; 2; 3]
>
> then the best output that one can expect is
>
> let map = List.map
> module List = struct end
> let test = map (fun x -> x + 1) [1; 2; 3]
>
> That said (contradicting myself), one can actually obtain
> better output if one is careful to always use absolute paths.
> In this example one could write:
>
> module List = struct end
> let test = Stdlib.List.map (fun x -> x + 1) [1; 2; 3]
>
> which relies on the fact that the name "Stdlib" is not shadowed.
>
> As far as I know there is currently no syntax for absolute
> paths in OCaml (every path is relative, and every name can
> be shadowed). Maybe we should consider adding such a syntax?
>
> --
> François Pottier
> francois.pottier@inria.fr
> https://cambium.inria.fr/~fpottier/
>
>

[-- Attachment #2: Type: text/html, Size: 2446 bytes --]

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [Caml-list] automatically resolving open?
  2025-04-23 14:45   ` Ivan Gotovchits
@ 2025-04-23 15:33     ` Jeremy Yallop
  0 siblings, 0 replies; 10+ messages in thread
From: Jeremy Yallop @ 2025-04-23 15:33 UTC (permalink / raw)
  To: Ivan Gotovchits; +Cc: Francois Pottier, Kenichi Asai, caml-list

On Wed, 23 Apr 2025 at 14:32, Francois Pottier
<francois.pottier@inria.fr> wrote:
> open List
> module List = struct end
> let test = map (fun x -> x + 1) [1; 2; 3]

On Wed, 23 Apr 2025 at 14:46, Ivan Gotovchits <ivg@ieee.org> wrote:
> open struct let map f x = f x end
> let () = map (fun _ -> ()) 0

We could perhaps add structure-level substitutions to handle these
kinds of cases:

module L := List
module List = struct end
let test = L.map (fun x -> x + 1) [1; 2; 3]

module M := struct let map f x = f x end
let () = M.map (fun _ -> ()) 0

As in signatures, the idea would be that names like 'L' and 'M' bound
in this way would be internal-only, and would not appear in the
signature.

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [Caml-list] automatically resolving open?
  2025-04-23 14:32 ` Francois Pottier
  2025-04-23 14:38   ` BOBOT François
  2025-04-23 14:45   ` Ivan Gotovchits
@ 2025-04-24  4:33   ` Oleg
  2 siblings, 0 replies; 10+ messages in thread
From: Oleg @ 2025-04-24  4:33 UTC (permalink / raw)
  To: francois.pottier; +Cc: Kenichi Asai, caml-list


First of all, to some small degree, such tool already exists: MetaOCaml

> open List
> let test = map (fun x -> x + 1) [1; 2; 3]
>
> I want to obtain:
>
> let test = List.map (fun x -> x + 1) [1; 2; 3]

   open List;;
   .<map (fun x -> x + 1) [1; 2; 3]>.;;

- : int list code = .<
Stdlib.List.map (fun x_1 -> x_1 + 1)
  (Stdlib.List.(::)
     (1, (Stdlib.List.(::) (2, (Stdlib.List.(::) (3, Stdlib.List.[]))))))>.

Everything is fully qualified: perhaps even more than
expected. MetaOCaml has functions to write the code to the file
(without the enclosing brackets). The obvious and major limitation is
that MetaOCaml brackets may only contain expressions (rather than
structures). Furtherfore, local modules etc. in those expressions are
not allowed (since it opens a huge can of worms for an uncertain
benefit: I'm not aware of any compelling example for allowing local
modules in brackets, which cannot be simply worked around in
traditional ways.)

> As far as I know there is currently no syntax for absolute
> paths in OCaml (every path is relative, and every name can
> be shadowed). Maybe we should consider adding such a syntax?

Actually, there is a hack for it in OCaml (OCaml source itself says
it's a hack.) See typing/typeclass.ml in OCaml repo and search for
"*predef*". It comes in the context of default arguments, which are
re-written during type checking into code using Some x and None. 
Those Some and None must be the pre-defined ones, rather than
locally re-defined. Thus the re-writing machinery produces
*predef*.Some and *predef*.None. The typing/env.ml and 
typing/persistent_env.ml has code to deal with
*predef*. Specifically, env.ml when asked to locale *predef*.l
looks up `l' in the initial environment.

It might be good to right this hack. The reason I care about it is
that naively pretty-printing Parsetree (converted from a Typedtree)
may produce identifiers like *predef*.None, which are not
syntactically valid. Therefore, I have to hack the pretty-printer to
ensure that the output is at least parseable, always. Incidentally,
the same typeclass.ml code also generates variable names like *sth*
and *opt* (I think typecore.ml has something similar). Now that we
have Ident.create_local, there is no need for such strange names.


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [Caml-list]  automatically resolving open?
  2025-04-23 14:10 [Caml-list] automatically resolving open? Kenichi Asai
  2025-04-23 14:32 ` Francois Pottier
@ 2025-04-24  6:39 ` Virgile Prevosto
  2025-04-24  9:16   ` Ulysse Gérard
  1 sibling, 1 reply; 10+ messages in thread
From: Virgile Prevosto @ 2025-04-24  6:39 UTC (permalink / raw)
  To: caml-list

Hello,

this is one of the built-in transformations offered by the recently advertised `ocamlmig` tool:
https://github.com/v-gb/ocamlmig/blob/main/doc/using.md#removing-opens
However, it comes with caveats, including that "when identifiers are requalified, not all possible shadowings are taken into account" (and I've checked that indeed in François' example we end up with a `List.map` below the `module List = struct end`...)

Best regards,


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [Caml-list] automatically resolving open?
  2025-04-24  6:39 ` Virgile Prevosto
@ 2025-04-24  9:16   ` Ulysse Gérard
  2025-04-24 13:06     ` Kenichi Asai
  0 siblings, 1 reply; 10+ messages in thread
From: Ulysse Gérard @ 2025-04-24  9:16 UTC (permalink / raw)
  To: caml-list

Refactor open is actually a feature of Merlin which have long been exposed in the custom protocol:
https://github.com/ocaml/merlin/blob/main/doc/dev/PROTOCOL.md#refactor-open--postion-position--action-qualifyunqualify

- In the merlin.el mode the commands are: `merlin-refactor-open` and `merlin-refactor-open-qualify`.
- In the vim plugin it is `merlin#RefactorOpen`.
- In LSP based plugins, such as `ocaml-eglot` for emacs and `vscode-ocaml-platform`, it is available as a code action. It appears to be working in only one direction however: from the `open` statement it is possible to qualifying all identifiers coming from that statement.

As for other solutions, potential shadowing is not checked. This is a fairly obscur command of Merlin, please open an issue if you encounter any issue. Additionally, if someone wants to step in to improve the feature in LSP we will be happy to welcome their contribution :-)

Ulysse

> On 24 Apr 2025, at 08:39, Virgile Prevosto <virgile.prevosto@m4x.org> wrote:
> 
> Hello,
> 
> this is one of the built-in transformations offered by the recently advertised `ocamlmig` tool:
> https://github.com/v-gb/ocamlmig/blob/main/doc/using.md#removing-opens
> However, it comes with caveats, including that "when identifiers are requalified, not all possible shadowings are taken into account" (and I've checked that indeed in François' example we end up with a `List.map` below the `module List = struct end`...)
> 
> Best regards,


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [Caml-list] automatically resolving open?
  2025-04-24  9:16   ` Ulysse Gérard
@ 2025-04-24 13:06     ` Kenichi Asai
  2025-04-24 13:44       ` Ulysse Gérard
  0 siblings, 1 reply; 10+ messages in thread
From: Kenichi Asai @ 2025-04-24 13:06 UTC (permalink / raw)
  To: Ulysse Gérard; +Cc: caml-list

Thank you all for the information.  I realize the problem is much
harder than I first thought.  But the refactor open of Merlin appears
to be the closest to what I wanted.  I confirmed that I can add and
remove module names in emacs and VS Code.  Can I do the same thing
using a stand-alone command line tool?  If not, can I do it from the
VS Code extension programatically?

> It appears to be working in only one direction however: from the
> `open` statement it is possible to qualifying all identifiers coming
> from that statement.

In my environment, I could do both direction.

Sincerely,

-- 
Kenichi Asai


On Thu, Apr 24, 2025 at 11:16:36AM +0200,
 Ulysse Gérard wrote:

> Refactor open is actually a feature of Merlin which have long been exposed in the custom protocol:
> https://github.com/ocaml/merlin/blob/main/doc/dev/PROTOCOL.md#refactor-open--postion-position--action-qualifyunqualify
> 
> - In the merlin.el mode the commands are: `merlin-refactor-open` and `merlin-refactor-open-qualify`.
> - In the vim plugin it is `merlin#RefactorOpen`.
> - In LSP based plugins, such as `ocaml-eglot` for emacs and `vscode-ocaml-platform`, it is available as a code action. It appears to be working in only one direction however: from the `open` statement it is possible to qualifying all identifiers coming from that statement.
> 
> As for other solutions, potential shadowing is not checked. This is a fairly obscur command of Merlin, please open an issue if you encounter any issue. Additionally, if someone wants to step in to improve the feature in LSP we will be happy to welcome their contribution :-)
> 
> Ulysse
> 
> > On 24 Apr 2025, at 08:39, Virgile Prevosto <virgile.prevosto@m4x.org> wrote:
> > 
> > Hello,
> > 
> > this is one of the built-in transformations offered by the recently advertised `ocamlmig` tool:
> > https://github.com/v-gb/ocamlmig/blob/main/doc/using.md#removing-opens
> > However, it comes with caveats, including that "when identifiers are requalified, not all possible shadowings are taken into account" (and I've checked that indeed in François' example we end up with a `List.map` below the `module List = struct end`...)
> > 
> > Best regards,

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [Caml-list] automatically resolving open?
  2025-04-24 13:06     ` Kenichi Asai
@ 2025-04-24 13:44       ` Ulysse Gérard
  0 siblings, 0 replies; 10+ messages in thread
From: Ulysse Gérard @ 2025-04-24 13:44 UTC (permalink / raw)
  To: Kenichi Asai; +Cc: caml-list

> On 24 Apr 2025, at 15:06, Kenichi Asai <asai@is.ocha.ac.jp> wrote:
> 
> Thank you all for the information.  I realize the problem is much
> harder than I first thought.  But the refactor open of Merlin appears
> to be the closest to what I wanted.  I confirmed that I can add and
> remove module names in emacs and VS Code.  Can I do the same thing
> using a stand-alone command line tool?  If not, can I do it from the
> VS Code extension programatically?

You can try to query manually to Merlin directly from the command line. For that you will still need to build your project so that the configuration is available. Then you will want to run something like this:

> ocamlmerlin single refactor-open -action qualify -position 4:6 -filename path/to/file.ml <path/to/file.ml

(note that you need to input the file content on stdin or the call will hang)

And then parse the output...

The commands are documented here: https://github.com/ocaml/merlin/blob/main/doc/dev/PROTOCOL.md
You can find examples in the test suite: https://github.com/ocaml/merlin/tree/main/tests/test-dirs/refactor-open

> 
>> It appears to be working in only one direction however: from the
>> `open` statement it is possible to qualifying all identifiers coming
>> from that statement.
> 
> In my environment, I could do both direction.

That's good to know !


^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2025-04-24 13:44 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-04-23 14:10 [Caml-list] automatically resolving open? Kenichi Asai
2025-04-23 14:32 ` Francois Pottier
2025-04-23 14:38   ` BOBOT François
2025-04-23 14:45   ` Ivan Gotovchits
2025-04-23 15:33     ` Jeremy Yallop
2025-04-24  4:33   ` Oleg
2025-04-24  6:39 ` Virgile Prevosto
2025-04-24  9:16   ` Ulysse Gérard
2025-04-24 13:06     ` Kenichi Asai
2025-04-24 13:44       ` Ulysse Gérard

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox