gleam icon indicating copy to clipboard operation
gleam copied to clipboard

Raise a compile error if a module would have the same name as an existing Erlang module

Open TanklesXL opened this issue 2 years ago • 2 comments

Heyo,

So I was playing around with some of the erlang file functions, and I noticed some interesting behaviour when it came to naming my file file.gleam.

When importing a module that has the same name as the erlang module that is being referenced in external function declarations, the resulting erlang call ends up failing with exception error: undefined function file:open_file/2.

Here is an example, I have file.gleam as follows:

import gleam/erlang/charlist.{Charlist}

pub external type Reason

pub external type IODevice

pub external fn read_file(String) -> Result(String, Reason) =
  "file" "read_file"

pub type FileMode {
  Write
}

pub external fn open_file(String, FileMode) -> Result(IODevice, Reason) =
  "file" "open"

pub type WriteResult {
  Ok
  Error(Reason)
}

pub external fn write_file(IODevice, Charlist) -> WriteResult =
  "file" "write"

which compiles to the following erlang:

-module(file).
-compile(no_auto_import).

-export([read_file/1, open_file/2, write_file/2]).
-export_type([reason/0, io_device/0, file_mode/0, write_result/0]).

-type reason() :: any().

-type io_device() :: any().

-type file_mode() :: write.

-type write_result() :: ok | {error, reason()}.

-spec read_file(binary()) -> {ok, binary()} | {error, reason()}.
read_file(A) ->
    file:read_file(A).

-spec open_file(binary(), file_mode()) -> {ok, io_device()} | {error, reason()}.
open_file(A, B) ->
    file:open(A, B).

-spec write_file(io_device(), gleam@erlang@charlist:charlist()) -> write_result().
write_file(A, B) ->
    file:write(A, B).

and within an escript example.gleam I have:

import file

pub external type CharList

pub fn main(_args: List(CharList)) {
  file.open_file("test.txt", file.Write)
}

The result being:

escript: exception error: undefined function file:open_file/2
  in function  escript:run/2 (escript.erl, line 750)
  in call from escript:start/1 (escript.erl, line 277)
  in call from init:start_em/1 
  in call from init:do_boot/3 

Now the interesting bit is when i modify the escript example.gleam to be:

import file.{Write, open_file}

pub external type CharList

pub fn main(_args: List(CharList)) {
  open_file("test.txt", Write)
}

this works perfectly fine because the compiler circumvents the use of the custom open_file by inlining the call to open_file to be:

file:open(<<"test.txt"/utf8>>, write).

TanklesXL avatar Sep 14 '21 18:09 TanklesXL

Thanks for the report! Yes this is the two file modules conflicting with each other. I wonder what we should do in this case... Perhaps the Gleam build tool could have a list of invalid module names for Erlang and then emit a warning or error if one is encountered?

lpil avatar Sep 17 '21 10:09 lpil

An idea: we can keep a list of all the Erlang built in modules and then we can scan each ebin directory of rebar3 deps to get the names of the others. If a collision is detected we can error.

lpil avatar Dec 31 '22 13:12 lpil