Skip to content

Commit 92ecfe7

Browse files
committed
Use named functions for record constructors code generation
1 parent 0eb9202 commit 92ecfe7

File tree

76 files changed

+455
-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

+455
-107
lines changed

CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,24 @@
4545
than stopping at the syntax error.
4646
([mxtthias](https://github.com/mxtthias))
4747

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

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

compiler-core/src/erlang.rs

Lines changed: 87 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,7 @@ fn module_document<'a>(
296296
Ok(module.append(line()))
297297
}
298298

299+
#[allow(clippy::too_many_arguments)]
299300
fn register_imports_and_exports(
300301
definition: &TypedDefinition,
301302
exports: &mut Vec<Document<'_>>,
@@ -335,8 +336,20 @@ fn register_imports_and_exports(
335336
typed_parameters,
336337
opaque,
337338
external_erlang,
339+
publicity: _,
338340
..
339341
}) => {
342+
// Export constructor functions for constructors with arguments
343+
// We need to export even for private types since they're used within the module
344+
for constructor in constructors {
345+
let arity = constructor.arguments.len();
346+
if arity > 0 {
347+
let constructor_name = constructor_function_name(name, &constructor.name);
348+
349+
exports.push(atom_string(constructor_name).append("/").append(arity));
350+
}
351+
}
352+
340353
// Erlang doesn't allow phantom type variables in type definitions but gleam does
341354
// so we check the type declaratinon against its constroctors and generate a phantom
342355
// value that uses the unused type variables.
@@ -460,8 +473,11 @@ fn module_statement<'a>(
460473
module_function(function, module, is_internal_module, line_numbers, src_path)
461474
}
462475

476+
Definition::CustomType(CustomType {
477+
name, constructors, ..
478+
}) => module_constructors(name, constructors, module, line_numbers),
479+
463480
Definition::TypeAlias(TypeAlias { .. })
464-
| Definition::CustomType(CustomType { .. })
465481
| Definition::Import(Import { .. })
466482
| Definition::ModuleConstant(ModuleConstant { .. }) => None,
467483
}
@@ -562,6 +578,45 @@ fn module_function<'a>(
562578
))
563579
}
564580

581+
fn module_constructors<'a>(
582+
type_name: &'a EcoString,
583+
constructors: &'a [RecordConstructor<Arc<Type>>],
584+
module: &'a str,
585+
line_numbers: &'a LineNumbers,
586+
) -> Option<(Document<'a>, Env<'a>)> {
587+
let constructor_functions: Vec<_> = constructors
588+
.iter()
589+
.filter_map(|constructor| {
590+
let arity = constructor.arguments.len();
591+
if arity == 0 {
592+
return None;
593+
}
594+
595+
let constructor_name = constructor_function_name(type_name, &constructor.name);
596+
let record_name = to_snake_case(&constructor.name);
597+
let args = incrementing_arguments_list(arity);
598+
599+
Some(docvec![
600+
atom_string(constructor_name),
601+
"(",
602+
args.clone(),
603+
") -> {",
604+
atom_string(record_name),
605+
", ",
606+
args,
607+
"}.",
608+
])
609+
})
610+
.collect();
611+
612+
if constructor_functions.is_empty() {
613+
return None;
614+
}
615+
616+
let env = Env::new(module, "", line_numbers);
617+
Some((join(constructor_functions, lines(2)), env))
618+
}
619+
565620
fn file_attribute<'a>(
566621
path: EcoString,
567622
function: &'a TypedFunction,
@@ -1426,18 +1481,28 @@ fn list<'a>(elements: Document<'a>, tail: Option<Document<'a>>) -> Document<'a>
14261481
fn var<'a>(name: &'a str, constructor: &'a ValueConstructor, env: &mut Env<'a>) -> Document<'a> {
14271482
match &constructor.variant {
14281483
ValueConstructorVariant::Record {
1429-
name: record_name, ..
1484+
name: record_name,
1485+
module,
1486+
..
14301487
} => match constructor.type_.deref() {
1431-
Type::Fn { arguments, .. } => {
1432-
let chars = incrementing_arguments_list(arguments.len());
1433-
"fun("
1434-
.to_doc()
1435-
.append(chars.clone())
1436-
.append(") -> {")
1437-
.append(atom_string(to_snake_case(record_name)))
1438-
.append(", ")
1439-
.append(chars)
1440-
.append("} end")
1488+
Type::Fn {
1489+
arguments, return_, ..
1490+
} => {
1491+
// Extract the type name from the return type
1492+
let type_name = return_
1493+
.named_type_name()
1494+
.map(|(_, name)| name)
1495+
.unwrap_or_else(|| record_name.clone());
1496+
let constructor_name = constructor_function_name(&type_name, record_name);
1497+
let module_prefix = if module == env.module {
1498+
"fun ".to_doc()
1499+
} else {
1500+
"fun ".to_doc().append(module_name_atom(module)).append(":")
1501+
};
1502+
module_prefix
1503+
.append(atom_string(constructor_name))
1504+
.append("/")
1505+
.append(arguments.len())
14411506
}
14421507
_ => atom_string(to_snake_case(record_name)),
14431508
},
@@ -2822,6 +2887,16 @@ fn variable_name(name: &str) -> EcoString {
28222887
first_uppercased.chain(chars).collect::<EcoString>()
28232888
}
28242889

2890+
fn constructor_function_name(type_name: &EcoString, constructor_name: &EcoString) -> EcoString {
2891+
// Use type$constructor naming to avoid collisions with regular functions
2892+
// e.g., SizedChunk.Last -> sized_chunk$last
2893+
eco_format!(
2894+
"{}${}",
2895+
to_snake_case(type_name),
2896+
to_snake_case(constructor_name)
2897+
)
2898+
}
2899+
28252900
/// When rendering a type variable to an erlang type spec we need all type variables with the
28262901
/// same id to end up with the same name in the generated erlang.
28272902
/// 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)