Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,24 @@
containing scientific notation or trailing zeros (i.e. `100` and `1e2`).
([ptdewey](https://github.com/ptdewey))

- Code generation for record constructor functions with arguments has been
changed. They are now generated as named functions instead of anonymous
functions, which allows them to compare equal when referenced. For example:

```gleam
pub type Foo {
Wibble
Wobble(String)
}
pub fn main() {
echo Wibble == Wibble // True
echo Wobble == Wobble // False (previously) -> True
}
```

([Adi Salimgereyev](https://github.com/abs0luty))

### Build tool

- The help text displayed by `gleam dev --help`, `gleam test --help`, and
Expand Down
98 changes: 86 additions & 12 deletions compiler-core/src/erlang.rs
Original file line number Diff line number Diff line change
Expand Up @@ -335,8 +335,20 @@ fn register_imports_and_exports(
typed_parameters,
opaque,
external_erlang,
publicity: _,
..
}) => {
// Export constructor functions for constructors with arguments
// We need to export even for private types since they're used within the module
for constructor in constructors {
let arity = constructor.arguments.len();
if arity > 0 {
let constructor_name = constructor_function_name(name, &constructor.name);

exports.push(atom_string(constructor_name).append("/").append(arity));
}
}

// Erlang doesn't allow phantom type variables in type definitions but gleam does
// so we check the type declaratinon against its constroctors and generate a phantom
// value that uses the unused type variables.
Expand Down Expand Up @@ -460,8 +472,11 @@ fn module_statement<'a>(
module_function(function, module, is_internal_module, line_numbers, src_path)
}

Definition::CustomType(CustomType {
name, constructors, ..
}) => module_constructors(name, constructors, module, line_numbers),

Definition::TypeAlias(TypeAlias { .. })
| Definition::CustomType(CustomType { .. })
| Definition::Import(Import { .. })
| Definition::ModuleConstant(ModuleConstant { .. }) => None,
}
Expand Down Expand Up @@ -562,6 +577,45 @@ fn module_function<'a>(
))
}

fn module_constructors<'a>(
type_name: &'a EcoString,
constructors: &'a [RecordConstructor<Arc<Type>>],
module: &'a str,
line_numbers: &'a LineNumbers,
) -> Option<(Document<'a>, Env<'a>)> {
let constructor_functions: Vec<_> = constructors
.iter()
.filter_map(|constructor| {
let arity = constructor.arguments.len();
if arity == 0 {
return None;
}

let constructor_name = constructor_function_name(type_name, &constructor.name);
let record_name = to_snake_case(&constructor.name);
let args = incrementing_arguments_list(arity);

Some(docvec![
atom_string(constructor_name),
"(",
args.clone(),
") -> {",
atom_string(record_name),
", ",
args,
"}.",
])
})
.collect();

if constructor_functions.is_empty() {
return None;
}

let env = Env::new(module, "", line_numbers);
Some((join(constructor_functions, lines(2)), env))
}

fn file_attribute<'a>(
path: EcoString,
function: &'a TypedFunction,
Expand Down Expand Up @@ -1439,18 +1493,28 @@ fn list<'a>(elements: Document<'a>, tail: Option<Document<'a>>) -> Document<'a>
fn var<'a>(name: &'a str, constructor: &'a ValueConstructor, env: &mut Env<'a>) -> Document<'a> {
match &constructor.variant {
ValueConstructorVariant::Record {
name: record_name, ..
name: record_name,
module,
..
} => match constructor.type_.deref() {
Type::Fn { arguments, .. } => {
let chars = incrementing_arguments_list(arguments.len());
"fun("
.to_doc()
.append(chars.clone())
.append(") -> {")
.append(atom_string(to_snake_case(record_name)))
.append(", ")
.append(chars)
.append("} end")
Type::Fn {
arguments, return_, ..
} => {
// Extract the type name from the return type
let type_name = return_
.named_type_name()
.map(|(_, name)| name)
.unwrap_or_else(|| record_name.clone());
let constructor_name = constructor_function_name(&type_name, record_name);
let module_prefix = if module == env.module {
"fun ".to_doc()
} else {
"fun ".to_doc().append(module_name_atom(module)).append(":")
};
module_prefix
.append(atom_string(constructor_name))
.append("/")
.append(arguments.len())
}
_ => atom_string(to_snake_case(record_name)),
},
Expand Down Expand Up @@ -2868,6 +2932,16 @@ fn variable_name(name: &str) -> EcoString {
first_uppercased.chain(chars).collect::<EcoString>()
}

fn constructor_function_name(type_name: &EcoString, constructor_name: &EcoString) -> EcoString {
// Use type$constructor naming to avoid collisions with regular functions
// e.g., SizedChunk.Last -> sized_chunk$last
eco_format!(
"{}${}",
to_snake_case(type_name),
to_snake_case(constructor_name)
)
}

/// When rendering a type variable to an erlang type spec we need all type variables with the
/// same id to end up with the same name in the generated erlang.
/// This function converts a usize into base 26 A-Z for this purpose.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ pub fn main() {
-module(my@mod).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "project/test/my/mod.gleam").
-export([main/0]).
-export(['fn_box$fn_box'/1, main/0]).
-export_type([fn_box/0]).

-type fn_box() :: {fn_box, fun((integer()) -> integer())}.

'fn_box$fn_box'(Field@0) -> {fn_box, Field@0}.

-file("project/test/my/mod.gleam", 6).
-spec main() -> integer().
main() ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@ pub type Box {
-module(my@mod).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "project/test/my/mod.gleam").
-export([main/1]).
-export(['box$box'/1, main/1]).
-export_type([box/0]).

-type box() :: {box, integer()}.

'box$box'(Field@0) -> {box, Field@0}.

-file("project/test/my/mod.gleam", 1).
-spec main(any()) -> fun((integer()) -> box()).
main(A) ->
fun(Field@0) -> {box, Field@0} end.
fun 'box$box'/1.
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ pub fn x() { Point(x: 4, y: 6) Point(y: 1, x: 9) }
-module(my@mod).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "project/test/my/mod.gleam").
-export([x/0]).
-export(['point$point'/2, x/0]).
-export_type([point/0]).

-type point() :: {point, integer(), integer()}.

'point$point'(Field@0, Field@1) -> {point, Field@0, Field@1}.

-file("project/test/my/mod.gleam", 2).
-spec x() -> point().
x() ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ pub fn x(y) { let Point(a, b) = y a }
-module(my@mod).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "project/test/my/mod.gleam").
-export([x/1]).
-export(['point$point'/2, x/1]).
-export_type([point/0]).

-type point() :: {point, integer(), integer()}.

'point$point'(Field@0, Field@1) -> {point, Field@0, Field@1}.

-file("project/test/my/mod.gleam", 2).
-spec x(point()) -> integer().
x(Y) ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,15 @@ pub type State{ Start(Int) End(Int) }
-module(my@mod).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "project/test/my/mod.gleam").
-export([build/1, main/0]).
-export(['state$start'/1, 'state$end'/1, build/1, main/0]).
-export_type([state/0]).

-type state() :: {start, integer()} | {'end', integer()}.

'state$start'(Field@0) -> {start, Field@0}.

'state$end'(Field@0) -> {'end', Field@0}.

-file("project/test/my/mod.gleam", 2).
-spec build(fun((integer()) -> I)) -> I.
build(Constructor) ->
Expand All @@ -24,4 +28,4 @@ build(Constructor) ->
-file("project/test/my/mod.gleam", 3).
-spec main() -> state().
main() ->
build(fun(Field@0) -> {'end', Field@0} end).
build(fun 'state$end'/1).
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ pub fn create_user(user_id) { User(age: 22, id: user_id, name: "") }
-module(my@mod).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "project/test/my/mod.gleam").
-export([create_user/1]).
-export(['user$user'/3, create_user/1]).
-export_type([user/0]).

-type user() :: {user, integer(), binary(), integer()}.

'user$user'(Field@0, Field@1, Field@2) -> {user, Field@0, Field@1, Field@2}.

-file("project/test/my/mod.gleam", 3).
-spec create_user(integer()) -> user().
create_user(User_id) ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ pub fn x() { X(x: 1, y: 2.) X(y: 3., x: 4) }
-module(my@mod).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "project/test/my/mod.gleam").
-export([x/0]).
-export(['x$x'/2, x/0]).
-export_type([x/0]).

-type x() :: {x, integer(), float()}.

'x$x'(Field@0, Field@1) -> {x, Field@0, Field@1}.

-file("project/test/my/mod.gleam", 2).
-spec x() -> x().
x() ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ pub fn pound(x) { Pound(x) }
-module(my@mod).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "project/test/my/mod.gleam").
-export([pound/1]).
-export(['money$pound'/1, pound/1]).
-export_type([money/0]).

-type money() :: {pound, integer()}.

'money$pound'(Field@0) -> {pound, Field@0}.

-file("project/test/my/mod.gleam", 2).
-spec pound(integer()) -> money().
pound(X) ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@ pub fn main() {
-module(my@mod).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "project/test/my/mod.gleam").
-export([factory/2, main/0]).
-export(['box$box'/1, factory/2, main/0]).
-export_type([box/0]).

-type box() :: {box, integer()}.

'box$box'(Field@0) -> {box, Field@0}.

-file("project/test/my/mod.gleam", 2).
-spec factory(fun((J) -> N), J) -> N.
factory(F, I) ->
Expand All @@ -34,4 +36,4 @@ factory(F, I) ->
-file("project/test/my/mod.gleam", 10).
-spec main() -> box().
main() ->
factory(fun(Field@0) -> {box, Field@0} end, 0).
factory(fun 'box$box'/1, 0).
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ pub fn y() { fn() { Point }()(4, 6) }
-module(my@mod).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "project/test/my/mod.gleam").
-export([y/0]).
-export(['point$point'/2, y/0]).
-export_type([point/0]).

-type point() :: {point, integer(), integer()}.

'point$point'(Field@0, Field@1) -> {point, Field@0, Field@1}.

-file("project/test/my/mod.gleam", 2).
-spec y() -> point().
y() ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ pub type Pair(x, y) { Pair(x: x, y: y) } pub fn x() { Pair(1, 2) Pair(3., 4.) }
-module(my@mod).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "project/test/my/mod.gleam").
-export([x/0]).
-export(['pair$pair'/2, x/0]).
-export_type([pair/2]).

-type pair(I, J) :: {pair, I, J}.

'pair$pair'(Field@0, Field@1) -> {pair, Field@0, Field@1}.

-file("project/test/my/mod.gleam", 1).
-spec x() -> pair(float(), float()).
x() ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ pub type X { Fun(Int) }
-module(my@mod).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "project/test/my/mod.gleam").
-export(['x$fun'/1]).
-export_type([x/0]).

-type x() :: {'fun', integer()}.

'x$fun'(Field@0) -> {'fun', Field@0}.
17 changes: 17 additions & 0 deletions compiler-core/src/erlang/tests/custom_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,20 @@ pub fn get(dict: Dict(key, value), key: key) -> Result(value, Nil)
"#
);
}

#[test]
fn constructor_equality() {
assert_erl!(
r#"
pub type Comparison {
Wibble
Wobble(String)
}
pub fn main() {
echo Wibble == Wibble
echo Wobble == Wobble
}
"#
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,17 @@ expression: "\n fn identity(a) {\n a\n }\n\n
-module(my@mod).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "project/test/my/mod.gleam").
-export([identity/1]).
-export(['mapper$mapper'/1, 'funcs$funcs'/1, identity/1]).
-export_type([mapper/1, funcs/1]).

-type mapper(I) :: {mapper, fun((I) -> I)}.

-type funcs(J) :: {funcs, mapper(J)}.

'mapper$mapper'(Field@0) -> {mapper, Field@0}.

'funcs$funcs'(Field@0) -> {funcs, Field@0}.

-file("project/test/my/mod.gleam", 2).
-spec identity(K) -> K.
identity(A) ->
Expand Down
Loading
Loading