Skip to content

Commit ebb9951

Browse files
committed
Use named functions for record constructors code generation
1 parent afcd7e9 commit ebb9951

File tree

76 files changed

+454
-107
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

76 files changed

+454
-107
lines changed

CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,24 @@
5151
containing scientific notation or trailing zeros (i.e. `100` and `1e2`).
5252
([ptdewey](https://github.com/ptdewey))
5353

54+
- Code generation for record constructor functions with arguments has been
55+
changed. They are now generated as named functions instead of anonymous
56+
functions, which allows them to compare equal when referenced. For example:
57+
58+
```gleam
59+
pub type Foo {
60+
Wibble
61+
Wobble(String)
62+
}
63+
64+
pub fn main() {
65+
echo Wibble == Wibble // True
66+
echo Wobble == Wobble // False (previously) -> True
67+
}
68+
```
69+
70+
([Adi Salimgereyev](https://github.com/abs0luty))
71+
5472
### Build tool
5573

5674
- The help text displayed by `gleam dev --help`, `gleam test --help`, and

compiler-core/src/erlang.rs

Lines changed: 86 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -335,8 +335,20 @@ fn register_imports_and_exports(
335335
typed_parameters,
336336
opaque,
337337
external_erlang,
338+
publicity: _,
338339
..
339340
}) => {
341+
// Export constructor functions for constructors with arguments
342+
// We need to export even for private types since they're used within the module
343+
for constructor in constructors {
344+
let arity = constructor.arguments.len();
345+
if arity > 0 {
346+
let constructor_name = constructor_function_name(name, &constructor.name);
347+
348+
exports.push(atom_string(constructor_name).append("/").append(arity));
349+
}
350+
}
351+
340352
// Erlang doesn't allow phantom type variables in type definitions but gleam does
341353
// so we check the type declaratinon against its constroctors and generate a phantom
342354
// value that uses the unused type variables.
@@ -460,8 +472,11 @@ fn module_statement<'a>(
460472
module_function(function, module, is_internal_module, line_numbers, src_path)
461473
}
462474

475+
Definition::CustomType(CustomType {
476+
name, constructors, ..
477+
}) => module_constructors(name, constructors, module, line_numbers),
478+
463479
Definition::TypeAlias(TypeAlias { .. })
464-
| Definition::CustomType(CustomType { .. })
465480
| Definition::Import(Import { .. })
466481
| Definition::ModuleConstant(ModuleConstant { .. }) => None,
467482
}
@@ -562,6 +577,45 @@ fn module_function<'a>(
562577
))
563578
}
564579

580+
fn module_constructors<'a>(
581+
type_name: &'a EcoString,
582+
constructors: &'a [RecordConstructor<Arc<Type>>],
583+
module: &'a str,
584+
line_numbers: &'a LineNumbers,
585+
) -> Option<(Document<'a>, Env<'a>)> {
586+
let constructor_functions: Vec<_> = constructors
587+
.iter()
588+
.filter_map(|constructor| {
589+
let arity = constructor.arguments.len();
590+
if arity == 0 {
591+
return None;
592+
}
593+
594+
let constructor_name = constructor_function_name(type_name, &constructor.name);
595+
let record_name = to_snake_case(&constructor.name);
596+
let args = incrementing_arguments_list(arity);
597+
598+
Some(docvec![
599+
atom_string(constructor_name),
600+
"(",
601+
args.clone(),
602+
") -> {",
603+
atom_string(record_name),
604+
", ",
605+
args,
606+
"}.",
607+
])
608+
})
609+
.collect();
610+
611+
if constructor_functions.is_empty() {
612+
return None;
613+
}
614+
615+
let env = Env::new(module, "", line_numbers);
616+
Some((join(constructor_functions, lines(2)), env))
617+
}
618+
565619
fn file_attribute<'a>(
566620
path: EcoString,
567621
function: &'a TypedFunction,
@@ -1439,18 +1493,28 @@ fn list<'a>(elements: Document<'a>, tail: Option<Document<'a>>) -> Document<'a>
14391493
fn var<'a>(name: &'a str, constructor: &'a ValueConstructor, env: &mut Env<'a>) -> Document<'a> {
14401494
match &constructor.variant {
14411495
ValueConstructorVariant::Record {
1442-
name: record_name, ..
1496+
name: record_name,
1497+
module,
1498+
..
14431499
} => match constructor.type_.deref() {
1444-
Type::Fn { arguments, .. } => {
1445-
let chars = incrementing_arguments_list(arguments.len());
1446-
"fun("
1447-
.to_doc()
1448-
.append(chars.clone())
1449-
.append(") -> {")
1450-
.append(atom_string(to_snake_case(record_name)))
1451-
.append(", ")
1452-
.append(chars)
1453-
.append("} end")
1500+
Type::Fn {
1501+
arguments, return_, ..
1502+
} => {
1503+
// Extract the type name from the return type
1504+
let type_name = return_
1505+
.named_type_name()
1506+
.map(|(_, name)| name)
1507+
.unwrap_or_else(|| record_name.clone());
1508+
let constructor_name = constructor_function_name(&type_name, record_name);
1509+
let module_prefix = if module == env.module {
1510+
"fun ".to_doc()
1511+
} else {
1512+
"fun ".to_doc().append(module_name_atom(module)).append(":")
1513+
};
1514+
module_prefix
1515+
.append(atom_string(constructor_name))
1516+
.append("/")
1517+
.append(arguments.len())
14541518
}
14551519
_ => atom_string(to_snake_case(record_name)),
14561520
},
@@ -2868,6 +2932,16 @@ fn variable_name(name: &str) -> EcoString {
28682932
first_uppercased.chain(chars).collect::<EcoString>()
28692933
}
28702934

2935+
fn constructor_function_name(type_name: &EcoString, constructor_name: &EcoString) -> EcoString {
2936+
// Use type$constructor naming to avoid collisions with regular functions
2937+
// e.g., SizedChunk.Last -> sized_chunk$last
2938+
eco_format!(
2939+
"{}${}",
2940+
to_snake_case(type_name),
2941+
to_snake_case(constructor_name)
2942+
)
2943+
}
2944+
28712945
/// When rendering a type variable to an erlang type spec we need all type variables with the
28722946
/// same id to end up with the same name in the generated erlang.
28732947
/// This function converts a usize into base 26 A-Z for this purpose.

compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__field_access_function_call.snap

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@ pub fn main() {
1818
-module(my@mod).
1919
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
2020
-define(FILEPATH, "project/test/my/mod.gleam").
21-
-export([main/0]).
21+
-export(['fn_box$fn_box'/1, main/0]).
2222
-export_type([fn_box/0]).
2323

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

26+
'fn_box$fn_box'(Field@0) -> {fn_box, Field@0}.
27+
2628
-file("project/test/my/mod.gleam", 6).
2729
-spec main() -> integer().
2830
main() ->

compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__function_argument_shadowing.snap

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@ pub type Box {
1616
-module(my@mod).
1717
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
1818
-define(FILEPATH, "project/test/my/mod.gleam").
19-
-export([main/1]).
19+
-export(['box$box'/1, main/1]).
2020
-export_type([box/0]).
2121

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

24+
'box$box'(Field@0) -> {box, Field@0}.
25+
2426
-file("project/test/my/mod.gleam", 1).
2527
-spec main(any()) -> fun((integer()) -> box()).
2628
main(A) ->
27-
fun(Field@0) -> {box, Field@0} end.
29+
fun 'box$box'/1.

compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__integration_test11.snap

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@ pub fn x() { Point(x: 4, y: 6) Point(y: 1, x: 9) }
1010
-module(my@mod).
1111
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
1212
-define(FILEPATH, "project/test/my/mod.gleam").
13-
-export([x/0]).
13+
-export(['point$point'/2, x/0]).
1414
-export_type([point/0]).
1515

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

18+
'point$point'(Field@0, Field@1) -> {point, Field@0, Field@1}.
19+
1820
-file("project/test/my/mod.gleam", 2).
1921
-spec x() -> point().
2022
x() ->

compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__integration_test12.snap

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@ pub fn x(y) { let Point(a, b) = y a }
1010
-module(my@mod).
1111
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
1212
-define(FILEPATH, "project/test/my/mod.gleam").
13-
-export([x/1]).
13+
-export(['point$point'/2, x/1]).
1414
-export_type([point/0]).
1515

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

18+
'point$point'(Field@0, Field@1) -> {point, Field@0, Field@1}.
19+
1820
-file("project/test/my/mod.gleam", 2).
1921
-spec x(point()) -> integer().
2022
x(Y) ->

compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__integration_test13.snap

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,15 @@ pub type State{ Start(Int) End(Int) }
1111
-module(my@mod).
1212
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
1313
-define(FILEPATH, "project/test/my/mod.gleam").
14-
-export([build/1, main/0]).
14+
-export(['state$start'/1, 'state$end'/1, build/1, main/0]).
1515
-export_type([state/0]).
1616

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

19+
'state$start'(Field@0) -> {start, Field@0}.
20+
21+
'state$end'(Field@0) -> {'end', Field@0}.
22+
1923
-file("project/test/my/mod.gleam", 2).
2024
-spec build(fun((integer()) -> I)) -> I.
2125
build(Constructor) ->
@@ -24,4 +28,4 @@ build(Constructor) ->
2428
-file("project/test/my/mod.gleam", 3).
2529
-spec main() -> state().
2630
main() ->
27-
build(fun(Field@0) -> {'end', Field@0} end).
31+
build(fun 'state$end'/1).

compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__integration_test17.snap

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@ pub fn create_user(user_id) { User(age: 22, id: user_id, name: "") }
1212
-module(my@mod).
1313
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
1414
-define(FILEPATH, "project/test/my/mod.gleam").
15-
-export([create_user/1]).
15+
-export(['user$user'/3, create_user/1]).
1616
-export_type([user/0]).
1717

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

20+
'user$user'(Field@0, Field@1, Field@2) -> {user, Field@0, Field@1, Field@2}.
21+
2022
-file("project/test/my/mod.gleam", 3).
2123
-spec create_user(integer()) -> user().
2224
create_user(User_id) ->

compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__integration_test19.snap

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@ pub fn x() { X(x: 1, y: 2.) X(y: 3., x: 4) }
1010
-module(my@mod).
1111
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
1212
-define(FILEPATH, "project/test/my/mod.gleam").
13-
-export([x/0]).
13+
-export(['x$x'/2, x/0]).
1414
-export_type([x/0]).
1515

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

18+
'x$x'(Field@0, Field@1) -> {x, Field@0, Field@1}.
19+
1820
-file("project/test/my/mod.gleam", 2).
1921
-spec x() -> x().
2022
x() ->

compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__integration_test1_1.snap

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@ pub fn pound(x) { Pound(x) }
1010
-module(my@mod).
1111
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
1212
-define(FILEPATH, "project/test/my/mod.gleam").
13-
-export([pound/1]).
13+
-export(['money$pound'/1, pound/1]).
1414
-export_type([money/0]).
1515

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

18+
'money$pound'(Field@0) -> {pound, Field@0}.
19+
1820
-file("project/test/my/mod.gleam", 2).
1921
-spec pound(integer()) -> money().
2022
pound(X) ->

0 commit comments

Comments
 (0)