Skip to content

Commit

Permalink
Port JS fixes from Gleam 1.7.0 to Nix (#40)
Browse files Browse the repository at this point in the history
* glistix/nix: add some missing tests for js for parity

* glistix: impl and test for nix and gleam files with conflicting names

* port js fix for nested string prefixes

Should not generate 'prefix = "w".wobble.whatever._0.else' but rather
'prefix = "w"'
  • Loading branch information
PgBiel authored Jan 20, 2025
1 parent 0295a45 commit 84d17b2
Show file tree
Hide file tree
Showing 10 changed files with 352 additions and 3 deletions.
50 changes: 50 additions & 0 deletions compiler-core/src/build/native_file_copier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ where
// add a special case for `.gleam`.
if extension == "gleam" {
self.check_for_conflicting_javascript_modules(&relative_path)?;
self.check_for_conflicting_nix_modules(&relative_path)?;

return Ok(());
}
Expand All @@ -113,6 +114,9 @@ where
// compiled to `.mjs`.
self.check_for_conflicting_javascript_modules(&relative_path)?;

// Same as above but for `.nix`.
self.check_for_conflicting_nix_modules(&relative_path)?;

// Check for Erlang modules conflicting between each other anywhere in
// the tree.
self.check_for_conflicting_erlang_modules(&relative_path)?;
Expand Down Expand Up @@ -202,6 +206,52 @@ where
});
}

/// Gleam files are compiled to `.nix` files, which must not conflict with
/// an FFI `.nix` file with the same name, so we check for this case here.
fn check_for_conflicting_nix_modules(
&mut self,
relative_path: &Utf8PathBuf,
) -> Result<(), Error> {
let nix_path = match relative_path.extension() {
Some("gleam") => eco_format!("{}", relative_path.with_extension("nix")),
Some("nix") => eco_format!("{}", relative_path),
_ => return Ok(()),
};

// Insert the full relative `.nix` path in `seen_modules` as there is
// no conflict if two `.nix` files have the same name but are in
// different subpaths, unlike Erlang files.
let existing = self
.seen_modules
.insert(nix_path.clone(), relative_path.clone());

// If there was no already existing one then there's no problem.
let Some(existing) = existing else {
return Ok(());
};

let existing_is_gleam = existing.extension() == Some("gleam");
if existing_is_gleam || relative_path.extension() == Some("gleam") {
let (gleam_file, native_file) = if existing_is_gleam {
(&existing, relative_path)
} else {
(relative_path, &existing)
};
return Err(Error::ClashingGleamModuleAndNativeFileName {
module: eco_format!("{}", gleam_file.with_extension("")),
gleam_file: gleam_file.clone(),
native_file: native_file.clone(),
});
}

// The only way for two `.nix` files to clash is by having
// the exact same path.
assert_eq!(&existing, relative_path);
return Err(Error::DuplicateSourceFile {
file: existing.to_string(),
});
}

/// Erlang module files cannot have the same name regardless of their
/// relative positions within the project. Ensure we raise an error if the
/// user attempts to create `.erl` files with the same name.
Expand Down
22 changes: 22 additions & 0 deletions compiler-core/src/build/native_file_copier/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -550,3 +550,25 @@ fn glistix_all_nix_files_are_copied_from_test_subfolders() {
fs.into_contents(),
);
}

#[test]
fn glistix_conflicting_gleam_and_nix_modules_result_in_an_error() {
let fs = InMemoryFileSystem::new();
fs.write(&Utf8Path::new("/src/wibble.gleam"), "1").unwrap();
fs.write(&Utf8Path::new("/src/wibble.nix"), "1").unwrap();

let copier = NativeFileCopier::new(fs.clone(), root(), root_out());
assert!(copier.run().is_err());
}

#[test]
fn glistix_differently_nested_gleam_and_nix_modules_with_same_name_are_ok() {
let fs = InMemoryFileSystem::new();
fs.write(&Utf8Path::new("/src/a/b/c/wibble.gleam"), "1")
.unwrap();
fs.write(&Utf8Path::new("/src/d/e/wibble.nix"), "1")
.unwrap();

let copier = NativeFileCopier::new(fs.clone(), root(), root_out());
assert!(copier.run().is_ok());
}
16 changes: 13 additions & 3 deletions compiler-core/src/nix/pattern.rs
Original file line number Diff line number Diff line change
Expand Up @@ -553,9 +553,19 @@ impl<'module_ctx, 'expression_gen, 'a> Generator<'module_ctx, 'expression_gen, '
// let prefix = "wibble";
// ^^^^^^^^^^^^^^^^^^^^^ we're adding this assignment inside the if clause
// the case branch gets translated into.
let left_side_string =
expression::string(left_side_string, self.expression_generator.tracker);
self.push_assignment(left_side_string, left);
//
// We also want to push this assignment without using push_assignment, since we
// do _not_ want to access the current path on the static string!
let var = self.next_local_var(left);
self.assignments.push(Assignment {
subject: expression::string(
left_side_string,
self.expression_generator.tracker,
),
path: SubjectPath::new(),
name: left,
var,
});
}
Ok(())
}
Expand Down
42 changes: 42 additions & 0 deletions compiler-core/src/nix/tests/assignments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,48 @@ pub fn main(x) {
);
}

// https://github.com/gleam-lang/gleam/issues/3894
#[test]
fn let_assert_nested_string_prefix() {
assert_nix!(
r#"
type Wibble {
Wibble(wibble: String)
}
pub fn main() {
let assert Wibble(wibble: "w" as prefix <> rest) = Wibble("wibble")
prefix <> rest
}
"#
);
}

// Inspired by https://github.com/gleam-lang/gleam/issues/2931
#[test]
fn keyword_assignment() {
assert_nix!(
r#"
pub fn main() {
let with = 10
let in = 50
in
}
"#
);
}

// Inspired by https://github.com/gleam-lang/gleam/issues/3004
#[test]
fn escaped_variables_in_constants() {
assert_nix!(
r#"
pub const with = 5
pub const in = with
"#
);
}

#[test]
fn message() {
assert_nix!(
Expand Down
45 changes: 45 additions & 0 deletions compiler-core/src/nix/tests/case.rs
Original file line number Diff line number Diff line change
Expand Up @@ -287,3 +287,48 @@ pub fn main() {
"#
)
}

// https://github.com/gleam-lang/gleam/issues/3894
#[test]
fn nested_string_prefix_assignment() {
assert_nix!(
r#"
type Wibble {
Wibble(wobble: String)
}
pub fn main() {
let tmp = Wibble(wobble: "wibble")
case tmp {
Wibble(wobble: "w" as wibble <> rest) -> wibble <> rest
_ -> panic
}
}
"#
)
}

#[test]
fn deeply_nested_string_prefix_assignment() {
assert_nix!(
r#"
type Wibble {
Wibble(Wobble)
}
type Wobble {
Wobble(wabble: Wabble)
}
type Wabble {
Wabble(tuple: #(Int, String))
}
pub fn main() {
let tmp = Wibble(Wobble(Wabble(#(42, "wibble"))))
case tmp {
Wibble(Wobble(Wabble(#(_int, "w" as wibble <> rest)))) -> wibble <> rest
_ -> panic
}
}
"#
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
source: compiler-core/src/nix/tests/assignments.rs
expression: "\npub const with = 5\npub const in = with\n"
---
----- SOURCE CODE

pub const with = 5
pub const in = with


----- COMPILED NIX
let with' = 5; in' = with'; in { inherit with' in'; }
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
source: compiler-core/src/nix/tests/assignments.rs
expression: "\npub fn main() {\n let with = 10\n let in = 50\n in\n}\n"
---
----- SOURCE CODE

pub fn main() {
let with = 10
let in = 50
in
}


----- COMPILED NIX
let main = { }: let with' = 10; in' = 50; in in'; in { inherit main; }
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
---
source: compiler-core/src/nix/tests/assignments.rs
expression: "\ntype Wibble {\n Wibble(wibble: String)\n}\n\npub fn main() {\n let assert Wibble(wibble: \"w\" as prefix <> rest) = Wibble(\"wibble\")\n prefix <> rest\n}\n"
---
----- SOURCE CODE

type Wibble {
Wibble(wibble: String)
}

pub fn main() {
let assert Wibble(wibble: "w" as prefix <> rest) = Wibble("wibble")
prefix <> rest
}


----- COMPILED NIX
let
inherit (builtins.import ./../gleam.nix) strHasPrefix makeError;

Wibble = wibble: { __gleamTag = "Wibble"; inherit wibble; };

main =
{ }:
let
_pat' = (Wibble "wibble");
_assert' =
if _pat'.__gleamTag != "Wibble" || !(strHasPrefix "w" _pat'.wibble) then
builtins.throw
(makeError
"let_assert"
"my/mod"
7
"main"
"Pattern match failed, no pattern matched the value."
{ value = _pat'; })
else null;
rest = builtins.seq _assert' (builtins.substring 1 (-1) _pat'.wibble);
prefix = builtins.seq _assert' "w";
in
builtins.seq _assert' (prefix + rest);
in
{ inherit main; }
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
---
source: compiler-core/src/nix/tests/case.rs
expression: "\ntype Wibble {\n Wibble(Wobble)\n}\ntype Wobble {\n Wobble(wabble: Wabble)\n}\ntype Wabble {\n Wabble(tuple: #(Int, String))\n}\n\npub fn main() {\n let tmp = Wibble(Wobble(Wabble(#(42, \"wibble\"))))\n case tmp {\n Wibble(Wobble(Wabble(#(_int, \"w\" as wibble <> rest)))) -> wibble <> rest\n _ -> panic\n }\n}\n"
---
----- SOURCE CODE

type Wibble {
Wibble(Wobble)
}
type Wobble {
Wobble(wabble: Wabble)
}
type Wabble {
Wabble(tuple: #(Int, String))
}

pub fn main() {
let tmp = Wibble(Wobble(Wabble(#(42, "wibble"))))
case tmp {
Wibble(Wobble(Wabble(#(_int, "w" as wibble <> rest)))) -> wibble <> rest
_ -> panic
}
}


----- COMPILED NIX
let
inherit (builtins.import ./../gleam.nix) strHasPrefix makeError;

Wibble = x0: { __gleamTag = "Wibble"; _0 = x0; };

Wobble = wabble: { __gleamTag = "Wobble"; inherit wabble; };

Wabble = tuple: { __gleamTag = "Wabble"; inherit tuple; };

main =
{ }:
let
tmp = Wibble (Wobble (Wabble [ 42 "wibble" ]));
in
if
tmp.__gleamTag == "Wibble" &&
tmp._0.__gleamTag == "Wobble" &&
tmp._0.wabble.__gleamTag == "Wabble" &&
strHasPrefix "w" (builtins.elemAt tmp._0.wabble.tuple 1)
then
let
rest =
(builtins.substring 1 (-1) (builtins.elemAt tmp._0.wabble.tuple 1));
wibble = "w";
in
wibble + rest
else
builtins.throw
(makeError
"panic"
"my/mod"
16
"main"
"`panic` expression evaluated."
{ });
in
{ inherit main; }
Loading

0 comments on commit 84d17b2

Please sign in to comment.