From 84d17b23450124a77254575707754b91c80589d8 Mon Sep 17 00:00:00 2001 From: PgBiel <9021226+PgBiel@users.noreply.github.com> Date: Mon, 20 Jan 2025 01:21:09 -0300 Subject: [PATCH] Port JS fixes from Gleam 1.7.0 to Nix (#40) * 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"' --- compiler-core/src/build/native_file_copier.rs | 50 +++++++++++++++ .../src/build/native_file_copier/tests.rs | 22 +++++++ compiler-core/src/nix/pattern.rs | 16 ++++- compiler-core/src/nix/tests/assignments.rs | 42 +++++++++++++ compiler-core/src/nix/tests/case.rs | 45 +++++++++++++ ...ments__escaped_variables_in_constants.snap | 12 ++++ ...ests__assignments__keyword_assignment.snap | 15 +++++ ...ents__let_assert_nested_string_prefix.snap | 43 +++++++++++++ ...eeply_nested_string_prefix_assignment.snap | 63 +++++++++++++++++++ ...case__nested_string_prefix_assignment.snap | 47 ++++++++++++++ 10 files changed, 352 insertions(+), 3 deletions(-) create mode 100644 compiler-core/src/nix/tests/snapshots/glistix_core__nix__tests__assignments__escaped_variables_in_constants.snap create mode 100644 compiler-core/src/nix/tests/snapshots/glistix_core__nix__tests__assignments__keyword_assignment.snap create mode 100644 compiler-core/src/nix/tests/snapshots/glistix_core__nix__tests__assignments__let_assert_nested_string_prefix.snap create mode 100644 compiler-core/src/nix/tests/snapshots/glistix_core__nix__tests__case__deeply_nested_string_prefix_assignment.snap create mode 100644 compiler-core/src/nix/tests/snapshots/glistix_core__nix__tests__case__nested_string_prefix_assignment.snap diff --git a/compiler-core/src/build/native_file_copier.rs b/compiler-core/src/build/native_file_copier.rs index e850cdf7b..25b5a442f 100644 --- a/compiler-core/src/build/native_file_copier.rs +++ b/compiler-core/src/build/native_file_copier.rs @@ -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(()); } @@ -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)?; @@ -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. diff --git a/compiler-core/src/build/native_file_copier/tests.rs b/compiler-core/src/build/native_file_copier/tests.rs index 78b048173..95f5778ca 100644 --- a/compiler-core/src/build/native_file_copier/tests.rs +++ b/compiler-core/src/build/native_file_copier/tests.rs @@ -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()); +} diff --git a/compiler-core/src/nix/pattern.rs b/compiler-core/src/nix/pattern.rs index 34ed76ca6..e29729606 100644 --- a/compiler-core/src/nix/pattern.rs +++ b/compiler-core/src/nix/pattern.rs @@ -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(()) } diff --git a/compiler-core/src/nix/tests/assignments.rs b/compiler-core/src/nix/tests/assignments.rs index 61301423d..572392a67 100644 --- a/compiler-core/src/nix/tests/assignments.rs +++ b/compiler-core/src/nix/tests/assignments.rs @@ -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!( diff --git a/compiler-core/src/nix/tests/case.rs b/compiler-core/src/nix/tests/case.rs index b439bea60..0b018fc19 100644 --- a/compiler-core/src/nix/tests/case.rs +++ b/compiler-core/src/nix/tests/case.rs @@ -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 + } +} +"# + ) +} diff --git a/compiler-core/src/nix/tests/snapshots/glistix_core__nix__tests__assignments__escaped_variables_in_constants.snap b/compiler-core/src/nix/tests/snapshots/glistix_core__nix__tests__assignments__escaped_variables_in_constants.snap new file mode 100644 index 000000000..a6d64406a --- /dev/null +++ b/compiler-core/src/nix/tests/snapshots/glistix_core__nix__tests__assignments__escaped_variables_in_constants.snap @@ -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'; } diff --git a/compiler-core/src/nix/tests/snapshots/glistix_core__nix__tests__assignments__keyword_assignment.snap b/compiler-core/src/nix/tests/snapshots/glistix_core__nix__tests__assignments__keyword_assignment.snap new file mode 100644 index 000000000..9a852d3aa --- /dev/null +++ b/compiler-core/src/nix/tests/snapshots/glistix_core__nix__tests__assignments__keyword_assignment.snap @@ -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; } diff --git a/compiler-core/src/nix/tests/snapshots/glistix_core__nix__tests__assignments__let_assert_nested_string_prefix.snap b/compiler-core/src/nix/tests/snapshots/glistix_core__nix__tests__assignments__let_assert_nested_string_prefix.snap new file mode 100644 index 000000000..dd72e0fd4 --- /dev/null +++ b/compiler-core/src/nix/tests/snapshots/glistix_core__nix__tests__assignments__let_assert_nested_string_prefix.snap @@ -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; } diff --git a/compiler-core/src/nix/tests/snapshots/glistix_core__nix__tests__case__deeply_nested_string_prefix_assignment.snap b/compiler-core/src/nix/tests/snapshots/glistix_core__nix__tests__case__deeply_nested_string_prefix_assignment.snap new file mode 100644 index 000000000..dfe762a18 --- /dev/null +++ b/compiler-core/src/nix/tests/snapshots/glistix_core__nix__tests__case__deeply_nested_string_prefix_assignment.snap @@ -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; } diff --git a/compiler-core/src/nix/tests/snapshots/glistix_core__nix__tests__case__nested_string_prefix_assignment.snap b/compiler-core/src/nix/tests/snapshots/glistix_core__nix__tests__case__nested_string_prefix_assignment.snap new file mode 100644 index 000000000..849a52570 --- /dev/null +++ b/compiler-core/src/nix/tests/snapshots/glistix_core__nix__tests__case__nested_string_prefix_assignment.snap @@ -0,0 +1,47 @@ +--- +source: compiler-core/src/nix/tests/case.rs +expression: "\ntype Wibble {\n Wibble(wobble: String)\n}\n\npub fn main() {\n let tmp = Wibble(wobble: \"wibble\")\n case tmp {\n Wibble(wobble: \"w\" as wibble <> rest) -> wibble <> rest\n _ -> panic\n }\n}\n" +--- +----- SOURCE CODE + +type Wibble { + Wibble(wobble: String) +} + +pub fn main() { + let tmp = Wibble(wobble: "wibble") + case tmp { + Wibble(wobble: "w" as wibble <> rest) -> wibble <> rest + _ -> panic + } +} + + +----- COMPILED NIX +let + inherit (builtins.import ./../gleam.nix) strHasPrefix makeError; + + Wibble = wobble: { __gleamTag = "Wibble"; inherit wobble; }; + + main = + { }: + let + tmp = Wibble "wibble"; + in + if tmp.__gleamTag == "Wibble" && strHasPrefix "w" tmp.wobble then + let + rest = (builtins.substring 1 (-1) tmp.wobble); + wibble = "w"; + in + wibble + rest + else + builtins.throw + (makeError + "panic" + "my/mod" + 10 + "main" + "`panic` expression evaluated." + { }); +in +{ inherit main; }