gleam-lang / gleam

⭐️ A friendly language for building type-safe, scalable systems!
https://gleam.run
Apache License 2.0
17.48k stars 726 forks source link

Compiler mishandling case statements #3250

Closed mscharley closed 2 months ago

mscharley commented 3 months ago

I'm running Gleam 1.2.0 on Linux.

The following code compiles into erlang, but the erlang code is invalid:

import gleam/io

pub fn main() {
  let wibble = "wibble"
  let wobble = "wobble"

  // Multi-case is important here, I couldn't trigger this on a single input.
  case wibble, wobble {
    _, "\t" as space <> rest | _, " " as space <> rest ->
      io.debug(#(space, rest))
    _, _ -> io.debug(#("Nothing matched", wibble))
  }
}
-module(bug_repro).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).

-export([main/0]).

-spec main() -> {binary(), binary()}.
main() ->
    Wibble = <<"wibble"/utf8>>,
    Wobble = <<"wobble"/utf8>>,
    case {Wibble, Wobble} of
        {_, <<Space:1/binary, Rest/binary>> when Space =:= <<"\t"/utf8>>} ->
            gleam@io:debug({Space, Rest});

        {_, <<Space:1/binary, Rest/binary>> when Space =:= <<" "/utf8>>} ->
            gleam@io:debug({Space, Rest});

        {_, _} ->
            gleam@io:debug({<<"Nothing matched"/utf8>>, Wibble})
    end.

Compiler output:

~/build/dev/erlang/commonmark/_gleam_artefacts/bug_repro.erl:11:45: syntax error before: 'when'
%   11|         {_, <<Space:1/binary, Rest/binary>> when Space =:= <<"\t"/utf8>>} ->
%     |                                             ^

~/build/dev/erlang/commonmark/_gleam_artefacts/bug_repro.erl:4:2: function main/0 undefined
%    4| -export([main/0]).
%     |  ^

~/build/dev/erlang/commonmark/_gleam_artefacts/bug_repro.erl:6:2: spec for undefined function main/0
%    6| -spec main() -> {binary(), binary()}.
%     |  ^
inoas commented 3 months ago

I think the tuple wrapping happens to early when there is also a guard from

// buggy
fn multi_case() {
  let a = "wibble"
  let b = "wobble"

  case a, b {
    // The bug appears here where the generated when guard is wrapped within the tuple
    // passed into the branches of the case expression instead of after it
    _, "wabble" as c <> rest -> c <> rest
    _, _ -> b
  }
}

// working
fn single_case() {
  let b = "wibble"

  case b {
    "wabble" as c <> rest -> c <> rest
    _ -> b
  }
}

-spec multi_case() -> binary().
multi_case() ->
    A = <<"wibble"/utf8>>,
    B = <<"wobble"/utf8>>,
    case {A, B} of
                                      % } should be here              ↓ but not here
        {_, <<C:6/binary, Rest/binary>>   when C =:= <<"wabble"/utf8>>} ->
            <<C/binary, Rest/binary>>;

        {_, _} ->
            B
    end.
inoas commented 3 months ago

I wanted to edit the generated erlang and run it directly (but gleam run always recompiles) via erl but failed to pass a file as a string into bash off the bat: erl -noinput -eval 'erlang code here.' -s init stop to confirm if that's the problem/solution.

lpil commented 3 months ago

Oh dear, that is horrid! We'll get this fixed soon