leostera / caramel

:candy: a functional language for building type-safe, scalable, and maintainable applications
https://caramel.run
Apache License 2.0
1.06k stars 25 forks source link

Generate unique variable names to deal with name shadowing and Erlang bindings escaping expression blocks #93

Open michallepicki opened 3 years ago

michallepicki commented 3 years ago

From @erszcz 's comment Describe the bug We're now translating Caramel's expression blocks (begin ... end or ( ... )) and some of let bindings (nested ones intentionally, and some of other ones unintentionally but that's not causing major issues other than reduced readability), to Erlang's begin/end blocks. Unfortunately, because of the way how expression sequences in the Ocaml's Typedtree are represented (unnecessary blocks are getting "erased"), and because of Erlang scoping rules (Erlang has different than "traditional FP" rules about variable scoping where Erlang bindings from within expression blocks are available in the rest of the function as well), sometimes when not shadowing variable names in Caramel code, we're introducing clashes in the generated Erlang code so it fails at runtime.

  1. Create a file main.ml with
    
    let print thing = Io.format "~0tp~n" [ thing ]

let main _ = begin let a = 1 in print a end; begin let a = 5 in print a end

The blocks are there to trick Caramel into thinking there is no shadowing. This is just one example of such situation, there are more e.g. when a binding escapes erlang's `begin/end` block or a `case` expressions etc.

2. Run command `caramel compile main.ml && escript main.erl`
3. See result:

$ caramel compile main.ml && escript main.erl Compiling main.erl OK 1 escript: exception error: no match of right hand side value 5 in function erl_eval:expr/5 (erl_eval.erl, line 453) in call from escript:eval_exprs/5 (escript.erl, line 872) in call from erl_eval:local_func/6 (erl_eval.erl, line 567) in call from escript:interpret/4 (escript.erl, line 788) in call from escript:start/1 (escript.erl, line 277) in call from init:start_em/1 in call from init:do_boot/3


The generated Erlang code right now is:
```erlang
% Source code generated with Caramel.
-module(main).

-export([main/1]).
-export([print/1]).

-spec print(_) -> ok.
print(Thing) -> io:format(<<"~0tp~n">>, [Thing | []]).

-spec main(_) -> ok.
main(_) ->
  A = 1,
  print(A),
  A = 5,
  print(A).

(I think the first begin/end we typed into the .ml file is actually not present in the typedtree, which for OCaml is fine because it doesn't change anything, and because an expression sequence is similar to a "linked list" we are flattening out the second one instead of printing begin/end everywhere)

Expected behavior The result of running the code should be:

1
5

similar to when running this ocaml file:

(* let print thing = Io.format "~0tp~n" [ thing ] *)

let main _ =
  begin
    let a = 1 in print_int a
  end;
  begin
    let a = 5 in print_int a
  end ;;

main ()
$ ocaml main.ml
15

I think the correct Erlang to generate here could be:

% Source code generated with Caramel.
-module(main).

-export([main/1]).
-export([print/1]).

-spec print(_) -> ok.
print(Thing) -> io:format(<<"~0tp~n">>, [Thing | []]).

-spec main(_) -> ok.
main(_) ->
  A = 1,
  print(A),
  A@1 = 5,
  print(A@1).

similarily to what Gleam generates in such situations.

If we keep track of variable names (or append some unique reference from typedtree to them), solving this should allow shadowing regular variable names in funcitons in general 🚀

Environment (please complete the following information):

michallepicki commented 3 years ago
                            pattern (main.ml[5,76+14]..main.ml[5,76+15])
                              Tpat_var "a/23"
                            expression (main.ml[5,76+18]..main.ml[5,76+25])
                              Texp_apply
                              expression (main.ml[5,76+18]..main.ml[5,76+23])
                                Texp_ident "print/15"
                              [
                                <arg>
                                  Nolabel
                                  expression (main.ml[5,76+24]..main.ml[5,76+25])
                                    Texp_ident "a/23"
                              ]

I'm looking at those a/23 names, which suggests there's some number assigned by OCaml to names in the typedtree already that we may be able to just re-use...

michallepicki commented 3 years ago

Those are stamps and sometimes scope numbers of an Ident.t - source - I think we can use them!