feat: better error handling
This commit is contained in:
155
Cargo.lock
generated
155
Cargo.lock
generated
@@ -2,6 +2,15 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 4
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "addr2line"
|
||||||
|
version = "0.25.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b"
|
||||||
|
dependencies = [
|
||||||
|
"gimli",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "adler2"
|
name = "adler2"
|
||||||
version = "2.0.1"
|
version = "2.0.1"
|
||||||
@@ -79,6 +88,30 @@ version = "1.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973"
|
checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "backtrace"
|
||||||
|
version = "0.3.76"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6"
|
||||||
|
dependencies = [
|
||||||
|
"addr2line",
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"miniz_oxide",
|
||||||
|
"object",
|
||||||
|
"rustc-demangle",
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "backtrace-ext"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "537beee3be4a18fb023b570f80e3ae28003db9167a751266b259926e25539d50"
|
||||||
|
dependencies = [
|
||||||
|
"backtrace",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.22.1"
|
version = "0.22.1"
|
||||||
@@ -1125,6 +1158,12 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gimli"
|
||||||
|
version = "0.32.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "glob"
|
name = "glob"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
@@ -1523,6 +1562,12 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "is_ci"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "is_executable"
|
name = "is_executable"
|
||||||
version = "1.0.5"
|
version = "1.0.5"
|
||||||
@@ -1735,6 +1780,36 @@ dependencies = [
|
|||||||
"autocfg",
|
"autocfg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miette"
|
||||||
|
version = "7.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7"
|
||||||
|
dependencies = [
|
||||||
|
"backtrace",
|
||||||
|
"backtrace-ext",
|
||||||
|
"cfg-if",
|
||||||
|
"miette-derive",
|
||||||
|
"owo-colors",
|
||||||
|
"supports-color",
|
||||||
|
"supports-hyperlinks",
|
||||||
|
"supports-unicode",
|
||||||
|
"terminal_size",
|
||||||
|
"textwrap",
|
||||||
|
"unicode-width 0.1.14",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miette-derive"
|
||||||
|
version = "7.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mimalloc"
|
name = "mimalloc"
|
||||||
version = "0.1.48"
|
version = "0.1.48"
|
||||||
@@ -1830,6 +1905,7 @@ name = "nix-js"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"base64",
|
||||||
"bzip2",
|
"bzip2",
|
||||||
"criterion",
|
"criterion",
|
||||||
"deno_core",
|
"deno_core",
|
||||||
@@ -1840,6 +1916,7 @@ dependencies = [
|
|||||||
"hashbrown 0.16.1",
|
"hashbrown 0.16.1",
|
||||||
"hex",
|
"hex",
|
||||||
"itertools 0.14.0",
|
"itertools 0.14.0",
|
||||||
|
"miette",
|
||||||
"mimalloc",
|
"mimalloc",
|
||||||
"nix-compat",
|
"nix-compat",
|
||||||
"nix-js-macros",
|
"nix-js-macros",
|
||||||
@@ -1848,11 +1925,13 @@ dependencies = [
|
|||||||
"regex",
|
"regex",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"rnix",
|
"rnix",
|
||||||
|
"rowan",
|
||||||
"rusqlite",
|
"rusqlite",
|
||||||
"rustyline",
|
"rustyline",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sha2",
|
"sha2",
|
||||||
|
"sourcemap",
|
||||||
"string-interner",
|
"string-interner",
|
||||||
"tar",
|
"tar",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
@@ -1971,6 +2050,15 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "object"
|
||||||
|
version = "0.37.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.21.3"
|
version = "1.21.3"
|
||||||
@@ -1995,6 +2083,12 @@ version = "0.5.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e"
|
checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "owo-colors"
|
||||||
|
version = "4.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
version = "0.12.5"
|
version = "0.12.5"
|
||||||
@@ -2493,6 +2587,12 @@ dependencies = [
|
|||||||
"smallvec",
|
"smallvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustc-demangle"
|
||||||
|
version = "0.1.27"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc-hash"
|
name = "rustc-hash"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@@ -2598,7 +2698,7 @@ dependencies = [
|
|||||||
"nix",
|
"nix",
|
||||||
"radix_trie",
|
"radix_trie",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
"unicode-width",
|
"unicode-width 0.1.14",
|
||||||
"utf8parse",
|
"utf8parse",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
@@ -2876,6 +2976,27 @@ version = "2.6.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "supports-color"
|
||||||
|
version = "3.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c64fc7232dd8d2e4ac5ce4ef302b1d81e0b80d055b9d77c7c4f51f6aa4c867d6"
|
||||||
|
dependencies = [
|
||||||
|
"is_ci",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "supports-hyperlinks"
|
||||||
|
version = "3.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e396b6523b11ccb83120b115a0b7366de372751aa6edf19844dfb13a6af97e91"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "supports-unicode"
|
||||||
|
version = "3.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "symlink"
|
name = "symlink"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -3007,12 +3128,32 @@ dependencies = [
|
|||||||
"writeable",
|
"writeable",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "terminal_size"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0"
|
||||||
|
dependencies = [
|
||||||
|
"rustix 1.1.3",
|
||||||
|
"windows-sys 0.60.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "text-size"
|
name = "text-size"
|
||||||
version = "1.1.1"
|
version = "1.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233"
|
checksum = "f18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "textwrap"
|
||||||
|
version = "0.16.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-linebreak",
|
||||||
|
"unicode-width 0.2.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.69"
|
version = "1.0.69"
|
||||||
@@ -3327,6 +3468,12 @@ version = "1.0.18"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-linebreak"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-segmentation"
|
name = "unicode-segmentation"
|
||||||
version = "1.12.0"
|
version = "1.12.0"
|
||||||
@@ -3339,6 +3486,12 @@ version = "0.1.14"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
|
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-width"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-xid"
|
name = "unicode-xid"
|
||||||
version = "0.2.6"
|
version = "0.2.6"
|
||||||
|
|||||||
@@ -99,6 +99,14 @@ pub fn ir_impl(input: TokenStream) -> TokenStream {
|
|||||||
match variant {
|
match variant {
|
||||||
VariantInput::Unit(name) => {
|
VariantInput::Unit(name) => {
|
||||||
let inner_type = name.clone();
|
let inner_type = name.clone();
|
||||||
|
|
||||||
|
struct_defs.push(quote! {
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct #name {
|
||||||
|
pub span: rnix::TextRange,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
enum_variants.push(quote! { #name(#inner_type) });
|
enum_variants.push(quote! { #name(#inner_type) });
|
||||||
ref_variants.push(quote! { #name(&'a #inner_type) });
|
ref_variants.push(quote! { #name(&'a #inner_type) });
|
||||||
mut_variants.push(quote! { #name(&'a mut #inner_type) });
|
mut_variants.push(quote! { #name(&'a mut #inner_type) });
|
||||||
@@ -116,14 +124,45 @@ pub fn ir_impl(input: TokenStream) -> TokenStream {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
VariantInput::Tuple(name, ty) => {
|
VariantInput::Tuple(name, ty) => {
|
||||||
enum_variants.push(quote! { #name(#ty) });
|
let field_name = format_ident!("inner");
|
||||||
ref_variants.push(quote! { #name(&'a #ty) });
|
|
||||||
mut_variants.push(quote! { #name(&'a mut #ty) });
|
struct_defs.push(quote! {
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct #name {
|
||||||
|
pub #field_name: #ty,
|
||||||
|
pub span: rnix::TextRange,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let inner_type = name.clone();
|
||||||
|
enum_variants.push(quote! { #name(#inner_type) });
|
||||||
|
ref_variants.push(quote! { #name(&'a #inner_type) });
|
||||||
|
mut_variants.push(quote! { #name(&'a mut #inner_type) });
|
||||||
as_ref_arms.push(quote! { Self::#name(inner) => #ref_name::#name(inner) });
|
as_ref_arms.push(quote! { Self::#name(inner) => #ref_name::#name(inner) });
|
||||||
as_mut_arms.push(quote! { Self::#name(inner) => #mut_name::#name(inner) });
|
as_mut_arms.push(quote! { Self::#name(inner) => #mut_name::#name(inner) });
|
||||||
|
from_impls.push(quote! {
|
||||||
|
impl From<#inner_type> for #base_name {
|
||||||
|
fn from(val: #inner_type) -> Self { #base_name::#name(val) }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
to_trait_impls.push(quote! {
|
||||||
|
impl #to_trait_name for #name {
|
||||||
|
fn #to_trait_fn_name(self) -> #base_name { #base_name::from(self) }
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
VariantInput::Struct(name, fields) => {
|
VariantInput::Struct(name, mut fields) => {
|
||||||
let inner_type = name.clone();
|
let inner_type = name.clone();
|
||||||
|
|
||||||
|
fields.named.push(syn::Field {
|
||||||
|
attrs: vec![],
|
||||||
|
vis: syn::Visibility::Public(syn::token::Pub::default()),
|
||||||
|
mutability: syn::FieldMutability::None,
|
||||||
|
ident: Some(format_ident!("span")),
|
||||||
|
colon_token: Some(syn::token::Colon::default()),
|
||||||
|
ty: syn::parse_quote!(rnix::TextRange),
|
||||||
|
});
|
||||||
|
|
||||||
struct_defs.push(quote! {
|
struct_defs.push(quote! {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct #name #fields
|
pub struct #name #fields
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
|||||||
|
|
||||||
derive_more = { version = "2", features = ["full"] }
|
derive_more = { version = "2", features = ["full"] }
|
||||||
thiserror = "2"
|
thiserror = "2"
|
||||||
|
miette = { version = "7.4", features = ["fancy"] }
|
||||||
|
|
||||||
hashbrown = "0.16"
|
hashbrown = "0.16"
|
||||||
petgraph = "0.8"
|
petgraph = "0.8"
|
||||||
@@ -40,6 +41,9 @@ nix-nar = "0.3"
|
|||||||
sha2 = "0.10"
|
sha2 = "0.10"
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
|
|
||||||
|
sourcemap = "9.0"
|
||||||
|
base64 = "0.22"
|
||||||
|
|
||||||
# Fetcher dependencies
|
# Fetcher dependencies
|
||||||
reqwest = { version = "0.12", features = ["blocking", "rustls-tls"], default-features = false }
|
reqwest = { version = "0.12", features = ["blocking", "rustls-tls"], default-features = false }
|
||||||
tar = "0.4"
|
tar = "0.4"
|
||||||
@@ -54,6 +58,7 @@ tempfile = "3.24"
|
|||||||
rusqlite = { version = "0.33", features = ["bundled"] }
|
rusqlite = { version = "0.33", features = ["bundled"] }
|
||||||
|
|
||||||
rnix = "0.12"
|
rnix = "0.12"
|
||||||
|
rowan = "0.15"
|
||||||
|
|
||||||
nix-js-macros = { path = "../nix-js-macros" }
|
nix-js-macros = { path = "../nix-js-macros" }
|
||||||
|
|
||||||
|
|||||||
@@ -360,20 +360,15 @@ export const toFile =
|
|||||||
(contentsArg: NixValue): StringWithContext => {
|
(contentsArg: NixValue): StringWithContext => {
|
||||||
const name = forceString(nameArg);
|
const name = forceString(nameArg);
|
||||||
|
|
||||||
if (name.includes('/')) {
|
if (name.includes("/")) {
|
||||||
throw new Error("builtins.toFile: name cannot contain '/'");
|
throw new Error("builtins.toFile: name cannot contain '/'");
|
||||||
}
|
}
|
||||||
if (name === '.' || name === '..') {
|
if (name === "." || name === "..") {
|
||||||
throw new Error("builtins.toFile: invalid name");
|
throw new Error("builtins.toFile: invalid name");
|
||||||
}
|
}
|
||||||
|
|
||||||
const context: NixStringContext = new Set();
|
const context: NixStringContext = new Set();
|
||||||
const contents = coerceToString(
|
const contents = coerceToString(contentsArg, StringCoercionMode.ToString, false, context);
|
||||||
contentsArg,
|
|
||||||
StringCoercionMode.ToString,
|
|
||||||
false,
|
|
||||||
context
|
|
||||||
);
|
|
||||||
|
|
||||||
const references: string[] = Array.from(context);
|
const references: string[] = Array.from(context);
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,75 @@ import { force } from "./thunk";
|
|||||||
import { mkPath } from "./path";
|
import { mkPath } from "./path";
|
||||||
import { CatchableError, isNixPath } from "./types";
|
import { CatchableError, isNixPath } from "./types";
|
||||||
|
|
||||||
|
interface StackFrame {
|
||||||
|
span: string;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const callStack: StackFrame[] = [];
|
||||||
|
const MAX_STACK_DEPTH = 1000;
|
||||||
|
|
||||||
|
export const STACK_TRACE = { enabled: false };
|
||||||
|
|
||||||
|
function enrichError(error: unknown): Error {
|
||||||
|
const err = error instanceof Error ? error : new Error(String(error));
|
||||||
|
|
||||||
|
if (!STACK_TRACE.enabled || callStack.length === 0) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use compact format for easy parsing (no regex needed)
|
||||||
|
// Format: NIX_STACK_FRAME:context:start:end:message
|
||||||
|
const nixStackLines = callStack.map((frame) => {
|
||||||
|
return `NIX_STACK_FRAME:context:${frame.span}:${frame.message}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Prepend stack frames to error stack
|
||||||
|
err.stack = `${nixStackLines.join('\n')}\n${err.stack || ''}`;
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Push an error context onto the stack
|
||||||
|
* Used for tracking evaluation context (e.g., "while evaluating the condition")
|
||||||
|
*/
|
||||||
|
export const pushContext = (message: string, span: string): void => {
|
||||||
|
if (!STACK_TRACE.enabled) return;
|
||||||
|
|
||||||
|
if (callStack.length >= MAX_STACK_DEPTH) {
|
||||||
|
callStack.shift();
|
||||||
|
}
|
||||||
|
callStack.push({ span, message });
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pop an error context from the stack
|
||||||
|
*/
|
||||||
|
export const popContext = (): void => {
|
||||||
|
if (!STACK_TRACE.enabled) return;
|
||||||
|
callStack.pop();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a function with error context tracking
|
||||||
|
* Automatically pushes context before execution and pops after
|
||||||
|
*/
|
||||||
|
export const withContext = <T>(message: string, span: string, fn: () => T): T => {
|
||||||
|
if (!STACK_TRACE.enabled) {
|
||||||
|
return fn();
|
||||||
|
}
|
||||||
|
|
||||||
|
pushContext(message, span);
|
||||||
|
try {
|
||||||
|
return fn();
|
||||||
|
} catch (error) {
|
||||||
|
throw enrichError(error);
|
||||||
|
} finally {
|
||||||
|
popContext();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Concatenate multiple values into a string or path with context
|
* Concatenate multiple values into a string or path with context
|
||||||
* This is used for string interpolation like "hello ${world}"
|
* This is used for string interpolation like "hello ${world}"
|
||||||
@@ -107,7 +176,29 @@ export const resolvePath = (currentDir: string, path: NixValue): NixPath => {
|
|||||||
return mkPath(resolved);
|
return mkPath(resolved);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const select = (obj: NixValue, attrpath: NixValue[]): NixValue => {
|
export const select = (obj: NixValue, attrpath: NixValue[], span?: string): NixValue => {
|
||||||
|
if (STACK_TRACE.enabled && span) {
|
||||||
|
const pathStrings = attrpath.map(a => forceString(a));
|
||||||
|
const path = pathStrings.join('.');
|
||||||
|
const message = path ? `while selecting attribute [${path}]` : 'while selecting attribute';
|
||||||
|
|
||||||
|
if (callStack.length >= MAX_STACK_DEPTH) {
|
||||||
|
callStack.shift();
|
||||||
|
}
|
||||||
|
callStack.push({ span, message });
|
||||||
|
try {
|
||||||
|
return select_impl(obj, attrpath);
|
||||||
|
} catch (error) {
|
||||||
|
throw enrichError(error);
|
||||||
|
} finally {
|
||||||
|
callStack.pop();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return select_impl(obj, attrpath);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function select_impl(obj: NixValue, attrpath: NixValue[]): NixValue {
|
||||||
let attrs = forceAttrs(obj);
|
let attrs = forceAttrs(obj);
|
||||||
|
|
||||||
for (const attr of attrpath.slice(0, -1)) {
|
for (const attr of attrpath.slice(0, -1)) {
|
||||||
@@ -124,9 +215,31 @@ export const select = (obj: NixValue, attrpath: NixValue[]): NixValue => {
|
|||||||
throw new Error(`Attribute '${last}' not found`);
|
throw new Error(`Attribute '${last}' not found`);
|
||||||
}
|
}
|
||||||
return attrs[last];
|
return attrs[last];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const selectWithDefault = (obj: NixValue, attrpath: NixValue[], default_val: NixValue, span?: string): NixValue => {
|
||||||
|
if (STACK_TRACE.enabled && span) {
|
||||||
|
const pathStrings = attrpath.map(a => forceString(a));
|
||||||
|
const path = pathStrings.join('.');
|
||||||
|
const message = path ? `while selecting attribute [${path}]` : 'while selecting attribute';
|
||||||
|
|
||||||
|
if (callStack.length >= MAX_STACK_DEPTH) {
|
||||||
|
callStack.shift();
|
||||||
|
}
|
||||||
|
callStack.push({ span, message });
|
||||||
|
try {
|
||||||
|
return selectWithDefault_impl(obj, attrpath, default_val);
|
||||||
|
} catch (error) {
|
||||||
|
throw enrichError(error);
|
||||||
|
} finally {
|
||||||
|
callStack.pop();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return selectWithDefault_impl(obj, attrpath, default_val);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const selectWithDefault = (obj: NixValue, attrpath: NixValue[], default_val: NixValue): NixValue => {
|
function selectWithDefault_impl(obj: NixValue, attrpath: NixValue[], default_val: NixValue): NixValue {
|
||||||
let attrs = forceAttrs(obj);
|
let attrs = forceAttrs(obj);
|
||||||
|
|
||||||
for (const attr of attrpath.slice(0, -1)) {
|
for (const attr of attrpath.slice(0, -1)) {
|
||||||
@@ -146,7 +259,7 @@ export const selectWithDefault = (obj: NixValue, attrpath: NixValue[], default_v
|
|||||||
return attrs[last];
|
return attrs[last];
|
||||||
}
|
}
|
||||||
return default_val;
|
return default_val;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const hasAttr = (obj: NixValue, attrpath: NixValue[]): NixBool => {
|
export const hasAttr = (obj: NixValue, attrpath: NixValue[]): NixBool => {
|
||||||
if (!isAttrs(obj)) {
|
if (!isAttrs(obj)) {
|
||||||
@@ -208,7 +321,25 @@ export const validateParams = (
|
|||||||
return forced_arg;
|
return forced_arg;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const call = (func: NixValue, arg: NixValue): NixValue => {
|
export const call = (func: NixValue, arg: NixValue, span?: string): NixValue => {
|
||||||
|
if (STACK_TRACE.enabled && span) {
|
||||||
|
if (callStack.length >= MAX_STACK_DEPTH) {
|
||||||
|
callStack.shift();
|
||||||
|
}
|
||||||
|
callStack.push({ span, message: 'from call site' });
|
||||||
|
try {
|
||||||
|
return call_impl(func, arg);
|
||||||
|
} catch (error) {
|
||||||
|
throw enrichError(error);
|
||||||
|
} finally {
|
||||||
|
callStack.pop();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return call_impl(func, arg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function call_impl(func: NixValue, arg: NixValue): NixValue {
|
||||||
const forcedFunc = force(func);
|
const forcedFunc = force(func);
|
||||||
if (typeof forcedFunc === "function") {
|
if (typeof forcedFunc === "function") {
|
||||||
return forcedFunc(arg);
|
return forcedFunc(arg);
|
||||||
@@ -223,7 +354,7 @@ export const call = (func: NixValue, arg: NixValue): NixValue => {
|
|||||||
return forceFunction(functor(forcedFunc))(arg);
|
return forceFunction(functor(forcedFunc))(arg);
|
||||||
}
|
}
|
||||||
throw new Error(`attempt to call something which is not a function but ${typeName(forcedFunc)}`);
|
throw new Error(`attempt to call something which is not a function but ${typeName(forcedFunc)}`);
|
||||||
};
|
}
|
||||||
|
|
||||||
export const assert = (assertion: NixValue, expr: NixValue, assertionRaw: string): NixValue => {
|
export const assert = (assertion: NixValue, expr: NixValue, assertionRaw: string): NixValue => {
|
||||||
if (forceBool(assertion)) {
|
if (forceBool(assertion)) {
|
||||||
@@ -231,3 +362,10 @@ export const assert = (assertion: NixValue, expr: NixValue, assertionRaw: string
|
|||||||
}
|
}
|
||||||
throw new CatchableError(`assertion '${assertionRaw}' failed`);
|
throw new CatchableError(`assertion '${assertionRaw}' failed`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const ifFunc = (cond: NixValue, consq: NixValue, alter: NixValue) => {
|
||||||
|
if (forceBool(cond)) {
|
||||||
|
return consq;
|
||||||
|
}
|
||||||
|
return alter;
|
||||||
|
};
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ import {
|
|||||||
concatStringsWithContext,
|
concatStringsWithContext,
|
||||||
call,
|
call,
|
||||||
assert,
|
assert,
|
||||||
|
STACK_TRACE,
|
||||||
|
pushContext,
|
||||||
|
popContext,
|
||||||
|
withContext,
|
||||||
} from "./helpers";
|
} from "./helpers";
|
||||||
import { op } from "./operators";
|
import { op } from "./operators";
|
||||||
import { builtins, PRIMOP_METADATA } from "./builtins";
|
import { builtins, PRIMOP_METADATA } from "./builtins";
|
||||||
@@ -34,6 +38,7 @@ export const Nix = {
|
|||||||
HAS_CONTEXT,
|
HAS_CONTEXT,
|
||||||
IS_PATH,
|
IS_PATH,
|
||||||
DEBUG_THUNKS,
|
DEBUG_THUNKS,
|
||||||
|
STACK_TRACE,
|
||||||
|
|
||||||
assert,
|
assert,
|
||||||
call,
|
call,
|
||||||
@@ -46,6 +51,10 @@ export const Nix = {
|
|||||||
concatStringsWithContext,
|
concatStringsWithContext,
|
||||||
StringCoercionMode,
|
StringCoercionMode,
|
||||||
|
|
||||||
|
pushContext,
|
||||||
|
popContext,
|
||||||
|
withContext,
|
||||||
|
|
||||||
op,
|
op,
|
||||||
builtins,
|
builtins,
|
||||||
PRIMOP_METADATA,
|
PRIMOP_METADATA,
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ fn main() -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
eprintln!("Error: {err}");
|
eprintln!("{:?}", miette::Report::new(err));
|
||||||
Err(anyhow::anyhow!("{err}"))
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ fn main() -> Result<()> {
|
|||||||
} else {
|
} else {
|
||||||
match context.eval_code(&line) {
|
match context.eval_code(&line) {
|
||||||
Ok(value) => println!("{value}"),
|
Ok(value) => println!("{value}"),
|
||||||
Err(err) => eprintln!("Error: {err}"),
|
Err(err) => eprintln!("{:?}", miette::Report::new(err)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,13 +6,25 @@ use crate::ir::*;
|
|||||||
|
|
||||||
pub(crate) fn compile(expr: &Ir, ctx: &impl CodegenContext) -> String {
|
pub(crate) fn compile(expr: &Ir, ctx: &impl CodegenContext) -> String {
|
||||||
let code = expr.compile(ctx);
|
let code = expr.compile(ctx);
|
||||||
let debug_prefix = if std::env::var("NIX_JS_DEBUG_THUNKS").is_ok() {
|
|
||||||
"Nix.DEBUG_THUNKS.enabled=true;"
|
let mut debug_flags = Vec::new();
|
||||||
|
if std::env::var("NIX_JS_DEBUG_THUNKS").is_ok() {
|
||||||
|
debug_flags.push("Nix.DEBUG_THUNKS.enabled=true");
|
||||||
|
}
|
||||||
|
if std::env::var("NIX_JS_STACK_TRACE").is_ok() {
|
||||||
|
debug_flags.push("Nix.STACK_TRACE.enabled=true");
|
||||||
|
}
|
||||||
|
let debug_prefix = if debug_flags.is_empty() {
|
||||||
|
String::new()
|
||||||
} else {
|
} else {
|
||||||
""
|
format!("{};", debug_flags.join(";"))
|
||||||
};
|
};
|
||||||
|
|
||||||
let cur_dir = ctx.get_current_dir().display().to_string().escape_quote();
|
let cur_dir = ctx.get_current_dir().display().to_string().escape_quote();
|
||||||
format!("(()=>{{{}const currentDir={};return {}}})()", debug_prefix, cur_dir, code)
|
format!(
|
||||||
|
"(()=>{{{}const currentDir={};return {}}})()",
|
||||||
|
debug_prefix, cur_dir, code
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
trait Compile<Ctx: CodegenContext> {
|
trait Compile<Ctx: CodegenContext> {
|
||||||
@@ -48,12 +60,16 @@ impl EscapeQuote for str {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn encode_span(span: rnix::TextRange) -> String {
|
||||||
|
format!("\"{}:{}\"", usize::from(span.start()), usize::from(span.end()))
|
||||||
|
}
|
||||||
|
|
||||||
impl<Ctx: CodegenContext> Compile<Ctx> for Ir {
|
impl<Ctx: CodegenContext> Compile<Ctx> for Ir {
|
||||||
fn compile(&self, ctx: &Ctx) -> String {
|
fn compile(&self, ctx: &Ctx) -> String {
|
||||||
match self {
|
match self {
|
||||||
Ir::Int(int) => format!("{int}n"), // Generate BigInt literal
|
Ir::Int(int) => format!("{}n", int.inner), // Generate BigInt literal
|
||||||
Ir::Float(float) => float.to_string(),
|
Ir::Float(float) => float.inner.to_string(),
|
||||||
Ir::Bool(bool) => bool.to_string(),
|
Ir::Bool(bool) => bool.inner.to_string(),
|
||||||
Ir::Null(_) => "null".to_string(),
|
Ir::Null(_) => "null".to_string(),
|
||||||
Ir::Str(s) => s.val.escape_quote(),
|
Ir::Str(s) => s.val.escape_quote(),
|
||||||
Ir::Path(p) => {
|
Ir::Path(p) => {
|
||||||
@@ -61,11 +77,23 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Ir {
|
|||||||
let path_expr = ctx.get_ir(p.expr).compile(ctx);
|
let path_expr = ctx.get_ir(p.expr).compile(ctx);
|
||||||
format!("Nix.resolvePath(currentDir,{})", path_expr)
|
format!("Nix.resolvePath(currentDir,{})", path_expr)
|
||||||
}
|
}
|
||||||
&Ir::If(If { cond, consq, alter }) => {
|
&Ir::If(If {
|
||||||
let cond = ctx.get_ir(cond).compile(ctx);
|
cond, consq, alter, span
|
||||||
|
}) => {
|
||||||
|
let cond_code = ctx.get_ir(cond).compile(ctx);
|
||||||
let consq = ctx.get_ir(consq).compile(ctx);
|
let consq = ctx.get_ir(consq).compile(ctx);
|
||||||
let alter = ctx.get_ir(alter).compile(ctx);
|
let alter = ctx.get_ir(alter).compile(ctx);
|
||||||
format!("({cond})?({consq}):({alter})")
|
|
||||||
|
// Only add context tracking if STACK_TRACE is enabled
|
||||||
|
if std::env::var("NIX_JS_STACK_TRACE").is_ok() {
|
||||||
|
let cond_span = encode_span(ctx.get_ir(cond).span());
|
||||||
|
format!(
|
||||||
|
"(Nix.withContext(\"while evaluating a branch condition\",{},()=>({})))?({}):({})",
|
||||||
|
cond_span, cond_code, consq, alter
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
format!("({cond_code})?({consq}):({alter})")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ir::BinOp(x) => x.compile(ctx),
|
Ir::BinOp(x) => x.compile(ctx),
|
||||||
Ir::UnOp(x) => x.compile(ctx),
|
Ir::UnOp(x) => x.compile(ctx),
|
||||||
@@ -73,18 +101,18 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Ir {
|
|||||||
Ir::AttrSet(x) => x.compile(ctx),
|
Ir::AttrSet(x) => x.compile(ctx),
|
||||||
Ir::List(x) => x.compile(ctx),
|
Ir::List(x) => x.compile(ctx),
|
||||||
Ir::Call(x) => x.compile(ctx),
|
Ir::Call(x) => x.compile(ctx),
|
||||||
Ir::Arg(x) => format!("arg{}", x.0),
|
Ir::Arg(x) => format!("arg{}", x.inner.0),
|
||||||
Ir::Let(x) => x.compile(ctx),
|
Ir::Let(x) => x.compile(ctx),
|
||||||
Ir::Select(x) => x.compile(ctx),
|
Ir::Select(x) => x.compile(ctx),
|
||||||
&Ir::Thunk(expr_id) => {
|
&Ir::Thunk(Thunk { inner: expr_id, .. }) => {
|
||||||
let inner = ctx.get_ir(expr_id).compile(ctx);
|
let inner = ctx.get_ir(expr_id).compile(ctx);
|
||||||
format!("Nix.createThunk(()=>({}),\"expr{}\")", inner, expr_id.0)
|
format!("Nix.createThunk(()=>({}),\"expr{}\")", inner, expr_id.0)
|
||||||
}
|
}
|
||||||
&Ir::ExprRef(expr_id) => {
|
&Ir::ExprRef(ExprRef { inner: expr_id, .. }) => {
|
||||||
format!("expr{}", expr_id.0)
|
format!("expr{}", expr_id.0)
|
||||||
}
|
}
|
||||||
Ir::Builtins(_) => "Nix.builtins".to_string(),
|
Ir::Builtins(_) => "Nix.builtins".to_string(),
|
||||||
&Ir::Builtin(Builtin(name)) => {
|
&Ir::Builtin(Builtin { inner: name, .. }) => {
|
||||||
format!("Nix.builtins[{}]", ctx.get_sym(name).escape_quote())
|
format!("Nix.builtins[{}]", ctx.get_sym(name).escape_quote())
|
||||||
}
|
}
|
||||||
Ir::ConcatStrings(x) => x.compile(ctx),
|
Ir::ConcatStrings(x) => x.compile(ctx),
|
||||||
@@ -93,13 +121,24 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Ir {
|
|||||||
assertion,
|
assertion,
|
||||||
expr,
|
expr,
|
||||||
ref assertion_raw,
|
ref assertion_raw,
|
||||||
|
span,
|
||||||
}) => {
|
}) => {
|
||||||
let assertion = ctx.get_ir(assertion).compile(ctx);
|
let assertion_code = ctx.get_ir(assertion).compile(ctx);
|
||||||
let expr = ctx.get_ir(expr).compile(ctx);
|
let expr = ctx.get_ir(expr).compile(ctx);
|
||||||
format!(
|
|
||||||
"Nix.assert({assertion},{expr},{})",
|
// Only add context tracking if STACK_TRACE is enabled
|
||||||
assertion_raw.escape_quote()
|
if std::env::var("NIX_JS_STACK_TRACE").is_ok() {
|
||||||
)
|
let assertion_span = encode_span(ctx.get_ir(assertion).span());
|
||||||
|
format!(
|
||||||
|
"Nix.assert(Nix.withContext(\"while evaluating the condition of the assert statement\",{},()=>({})),{},{})",
|
||||||
|
assertion_span, assertion_code, expr, assertion_raw.escape_quote()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
format!(
|
||||||
|
"Nix.assert({},{},{})",
|
||||||
|
assertion_code, expr, assertion_raw.escape_quote()
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,25 +147,43 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Ir {
|
|||||||
impl<Ctx: CodegenContext> Compile<Ctx> for BinOp {
|
impl<Ctx: CodegenContext> Compile<Ctx> for BinOp {
|
||||||
fn compile(&self, ctx: &Ctx) -> String {
|
fn compile(&self, ctx: &Ctx) -> String {
|
||||||
use BinOpKind::*;
|
use BinOpKind::*;
|
||||||
|
|
||||||
let lhs = ctx.get_ir(self.lhs).compile(ctx);
|
let lhs = ctx.get_ir(self.lhs).compile(ctx);
|
||||||
let rhs = ctx.get_ir(self.rhs).compile(ctx);
|
let rhs = ctx.get_ir(self.rhs).compile(ctx);
|
||||||
|
|
||||||
|
// Only add context tracking if STACK_TRACE is enabled
|
||||||
|
let stack_trace_enabled = std::env::var("NIX_JS_STACK_TRACE").is_ok();
|
||||||
|
|
||||||
|
// Helper to wrap operation with context (only if enabled)
|
||||||
|
let with_ctx = |op_name: &str, op_call: String| {
|
||||||
|
if stack_trace_enabled {
|
||||||
|
let span = encode_span(self.span);
|
||||||
|
format!(
|
||||||
|
"Nix.withContext(\"while evaluating the {} operator\",{},()=>({}))",
|
||||||
|
op_name, span, op_call
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
op_call
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
match self.kind {
|
match self.kind {
|
||||||
Add => format!("Nix.op.add({},{})", lhs, rhs),
|
Add => with_ctx("+", format!("Nix.op.add({},{})", lhs, rhs)),
|
||||||
Sub => format!("Nix.op.sub({},{})", lhs, rhs),
|
Sub => with_ctx("-", format!("Nix.op.sub({},{})", lhs, rhs)),
|
||||||
Mul => format!("Nix.op.mul({},{})", lhs, rhs),
|
Mul => with_ctx("*", format!("Nix.op.mul({},{})", lhs, rhs)),
|
||||||
Div => format!("Nix.op.div({},{})", lhs, rhs),
|
Div => with_ctx("/", format!("Nix.op.div({},{})", lhs, rhs)),
|
||||||
Eq => format!("Nix.op.eq({},{})", lhs, rhs),
|
Eq => with_ctx("==", format!("Nix.op.eq({},{})", lhs, rhs)),
|
||||||
Neq => format!("Nix.op.neq({},{})", lhs, rhs),
|
Neq => with_ctx("!=", format!("Nix.op.neq({},{})", lhs, rhs)),
|
||||||
Lt => format!("Nix.op.lt({},{})", lhs, rhs),
|
Lt => with_ctx("<", format!("Nix.op.lt({},{})", lhs, rhs)),
|
||||||
Gt => format!("Nix.op.gt({},{})", lhs, rhs),
|
Gt => with_ctx(">", format!("Nix.op.gt({},{})", lhs, rhs)),
|
||||||
Leq => format!("Nix.op.lte({},{})", lhs, rhs),
|
Leq => with_ctx("<=", format!("Nix.op.lte({},{})", lhs, rhs)),
|
||||||
Geq => format!("Nix.op.gte({},{})", lhs, rhs),
|
Geq => with_ctx(">=", format!("Nix.op.gte({},{})", lhs, rhs)),
|
||||||
// Short-circuit operators: use JavaScript native && and ||
|
// Short-circuit operators: use JavaScript native && and ||
|
||||||
And => format!("Nix.force({})&&Nix.force({})", lhs, rhs),
|
And => with_ctx("&&", format!("Nix.force({})&&Nix.force({})", lhs, rhs)),
|
||||||
Or => format!("Nix.force({})||Nix.force({})", lhs, rhs),
|
Or => with_ctx("||", format!("Nix.force({})||Nix.force({})", lhs, rhs)),
|
||||||
Impl => format!("(!Nix.force({})||Nix.force({}))", lhs, rhs),
|
Impl => with_ctx("->", format!("(!Nix.force({})||Nix.force({}))", lhs, rhs)),
|
||||||
Con => format!("Nix.op.concat({},{})", lhs, rhs),
|
Con => with_ctx("++", format!("Nix.op.concat({},{})", lhs, rhs)),
|
||||||
Upd => format!("Nix.op.update({},{})", lhs, rhs),
|
Upd => with_ctx("//", format!("Nix.op.update({},{})", lhs, rhs)),
|
||||||
PipeL => format!("Nix.call({},{})", rhs, lhs),
|
PipeL => format!("Nix.call({},{})", rhs, lhs),
|
||||||
PipeR => format!("Nix.call({},{})", lhs, rhs),
|
PipeR => format!("Nix.call({},{})", lhs, rhs),
|
||||||
}
|
}
|
||||||
@@ -146,7 +203,7 @@ impl<Ctx: CodegenContext> Compile<Ctx> for UnOp {
|
|||||||
|
|
||||||
impl<Ctx: CodegenContext> Compile<Ctx> for Func {
|
impl<Ctx: CodegenContext> Compile<Ctx> for Func {
|
||||||
fn compile(&self, ctx: &Ctx) -> String {
|
fn compile(&self, ctx: &Ctx) -> String {
|
||||||
let id = ctx.get_ir(self.arg).as_ref().unwrap_arg().0;
|
let id = ctx.get_ir(self.arg).as_ref().unwrap_arg().inner.0;
|
||||||
let body = ctx.get_ir(self.body).compile(ctx);
|
let body = ctx.get_ir(self.body).compile(ctx);
|
||||||
|
|
||||||
// Generate parameter validation code
|
// Generate parameter validation code
|
||||||
@@ -170,7 +227,7 @@ impl Func {
|
|||||||
return String::new();
|
return String::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
let id = ctx.get_ir(self.arg).as_ref().unwrap_arg().0;
|
let id = ctx.get_ir(self.arg).as_ref().unwrap_arg().inner.0;
|
||||||
|
|
||||||
// Build required parameter array
|
// Build required parameter array
|
||||||
let required = if let Some(req) = &self.param.required {
|
let required = if let Some(req) = &self.param.required {
|
||||||
@@ -203,7 +260,8 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Call {
|
|||||||
fn compile(&self, ctx: &Ctx) -> String {
|
fn compile(&self, ctx: &Ctx) -> String {
|
||||||
let func = ctx.get_ir(self.func).compile(ctx);
|
let func = ctx.get_ir(self.func).compile(ctx);
|
||||||
let arg = ctx.get_ir(self.arg).compile(ctx);
|
let arg = ctx.get_ir(self.arg).compile(ctx);
|
||||||
format!("Nix.call({func},{arg})")
|
let span_str = encode_span(self.span);
|
||||||
|
format!("Nix.call({func},{arg},{span_str})")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,7 +280,7 @@ fn should_keep_thunk(ir: &Ir) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn unwrap_thunk(ir: &Ir, ctx: &impl CodegenContext) -> String {
|
fn unwrap_thunk(ir: &Ir, ctx: &impl CodegenContext) -> String {
|
||||||
if let Ir::Thunk(inner) = ir {
|
if let Ir::Thunk(Thunk { inner, .. }) = ir {
|
||||||
let inner_ir = ctx.get_ir(*inner);
|
let inner_ir = ctx.get_ir(*inner);
|
||||||
if should_keep_thunk(inner_ir) {
|
if should_keep_thunk(inner_ir) {
|
||||||
ir.compile(ctx)
|
ir.compile(ctx)
|
||||||
@@ -266,13 +324,14 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Select {
|
|||||||
Attr::Dynamic(expr_id) => ctx.get_ir(*expr_id).compile(ctx),
|
Attr::Dynamic(expr_id) => ctx.get_ir(*expr_id).compile(ctx),
|
||||||
})
|
})
|
||||||
.join(",");
|
.join(",");
|
||||||
|
let span_str = encode_span(self.span);
|
||||||
if let Some(default) = self.default {
|
if let Some(default) = self.default {
|
||||||
format!(
|
format!(
|
||||||
"Nix.selectWithDefault({lhs},[{attrpath}],{})",
|
"Nix.selectWithDefault({lhs},[{attrpath}],{},{span_str})",
|
||||||
ctx.get_ir(default).compile(ctx)
|
ctx.get_ir(default).compile(ctx)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
format!("Nix.select({lhs},[{attrpath}])")
|
format!("Nix.select({lhs},[{attrpath}],{span_str})")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -280,16 +339,38 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Select {
|
|||||||
impl<Ctx: CodegenContext> Compile<Ctx> for AttrSet {
|
impl<Ctx: CodegenContext> Compile<Ctx> for AttrSet {
|
||||||
fn compile(&self, ctx: &Ctx) -> String {
|
fn compile(&self, ctx: &Ctx) -> String {
|
||||||
let mut attrs = Vec::new();
|
let mut attrs = Vec::new();
|
||||||
|
let stack_trace_enabled = std::env::var("NIX_JS_STACK_TRACE").is_ok();
|
||||||
|
|
||||||
for (&sym, &expr) in &self.stcs {
|
for (&sym, &expr) in &self.stcs {
|
||||||
let key = ctx.get_sym(sym);
|
let key = ctx.get_sym(sym);
|
||||||
let value = ctx.get_ir(expr).compile(ctx);
|
let value_code = ctx.get_ir(expr).compile(ctx);
|
||||||
|
|
||||||
|
let value = if stack_trace_enabled {
|
||||||
|
let value_span = encode_span(ctx.get_ir(expr).span());
|
||||||
|
format!(
|
||||||
|
"Nix.withContext(\"while evaluating the attribute '{}'\",{},()=>({}))",
|
||||||
|
key, value_span, value_code
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
value_code
|
||||||
|
};
|
||||||
attrs.push(format!("{}:{}", key.escape_quote(), value));
|
attrs.push(format!("{}:{}", key.escape_quote(), value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: duplicated key
|
||||||
for (key_expr, value_expr) in &self.dyns {
|
for (key_expr, value_expr) in &self.dyns {
|
||||||
let key = ctx.get_ir(*key_expr).compile(ctx);
|
let key = ctx.get_ir(*key_expr).compile(ctx);
|
||||||
let value = ctx.get_ir(*value_expr).compile(ctx);
|
let value_code = ctx.get_ir(*value_expr).compile(ctx);
|
||||||
|
|
||||||
|
let value = if stack_trace_enabled {
|
||||||
|
let value_span = encode_span(ctx.get_ir(*value_expr).span());
|
||||||
|
format!(
|
||||||
|
"Nix.withContext(\"while evaluating a dynamic attribute\",{},()=>({}))",
|
||||||
|
value_span, value_code
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
value_code
|
||||||
|
};
|
||||||
attrs.push(format!("[{}]:{}", key, value));
|
attrs.push(format!("[{}]:{}", key, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,10 +380,24 @@ impl<Ctx: CodegenContext> Compile<Ctx> for AttrSet {
|
|||||||
|
|
||||||
impl<Ctx: CodegenContext> Compile<Ctx> for List {
|
impl<Ctx: CodegenContext> Compile<Ctx> for List {
|
||||||
fn compile(&self, ctx: &Ctx) -> String {
|
fn compile(&self, ctx: &Ctx) -> String {
|
||||||
|
let stack_trace_enabled = std::env::var("NIX_JS_STACK_TRACE").is_ok();
|
||||||
|
|
||||||
let list = self
|
let list = self
|
||||||
.items
|
.items
|
||||||
.iter()
|
.iter()
|
||||||
.map(|item| ctx.get_ir(*item).compile(ctx))
|
.enumerate()
|
||||||
|
.map(|(idx, item)| {
|
||||||
|
let item_code = ctx.get_ir(*item).compile(ctx);
|
||||||
|
if stack_trace_enabled {
|
||||||
|
let item_span = encode_span(ctx.get_ir(*item).span());
|
||||||
|
format!(
|
||||||
|
"Nix.withContext(\"while evaluating list element {}\",{},()=>({}))",
|
||||||
|
idx, item_span, item_code
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
item_code
|
||||||
|
}
|
||||||
|
})
|
||||||
.join(",");
|
.join(",");
|
||||||
format!("[{list}]")
|
format!("[{list}]")
|
||||||
}
|
}
|
||||||
@@ -310,10 +405,24 @@ impl<Ctx: CodegenContext> Compile<Ctx> for List {
|
|||||||
|
|
||||||
impl<Ctx: CodegenContext> Compile<Ctx> for ConcatStrings {
|
impl<Ctx: CodegenContext> Compile<Ctx> for ConcatStrings {
|
||||||
fn compile(&self, ctx: &Ctx) -> String {
|
fn compile(&self, ctx: &Ctx) -> String {
|
||||||
|
let stack_trace_enabled = std::env::var("NIX_JS_STACK_TRACE").is_ok();
|
||||||
|
|
||||||
let parts: Vec<String> = self
|
let parts: Vec<String> = self
|
||||||
.parts
|
.parts
|
||||||
.iter()
|
.iter()
|
||||||
.map(|part| ctx.get_ir(*part).compile(ctx))
|
.enumerate()
|
||||||
|
.map(|(_idx, part)| {
|
||||||
|
let part_code = ctx.get_ir(*part).compile(ctx);
|
||||||
|
if stack_trace_enabled {
|
||||||
|
let part_span = encode_span(ctx.get_ir(*part).span());
|
||||||
|
format!(
|
||||||
|
"Nix.withContext(\"while evaluating a path segment\",{},()=>({}))",
|
||||||
|
part_span, part_code
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
part_code
|
||||||
|
}
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
format!("Nix.concatStringsWithContext([{}])", parts.join(","))
|
format!("Nix.concatStringsWithContext([{}])", parts.join(","))
|
||||||
|
|||||||
@@ -4,12 +4,17 @@ use std::ptr::NonNull;
|
|||||||
use hashbrown::{HashMap, HashSet};
|
use hashbrown::{HashMap, HashSet};
|
||||||
use itertools::Itertools as _;
|
use itertools::Itertools as _;
|
||||||
use petgraph::graphmap::DiGraphMap;
|
use petgraph::graphmap::DiGraphMap;
|
||||||
|
use rnix::TextRange;
|
||||||
use string_interner::DefaultStringInterner;
|
use string_interner::DefaultStringInterner;
|
||||||
|
|
||||||
use crate::codegen::{CodegenContext, compile};
|
use crate::codegen::{CodegenContext, compile};
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
use crate::ir::{ArgId, Builtin, Downgrade as _, DowngradeContext, ExprId, Ir, SymId, ToIr as _};
|
use crate::ir::{
|
||||||
|
Arg, ArgId, Bool, Builtin, Downgrade as _, DowngradeContext, ExprId, ExprRef, Ir, Null, SymId,
|
||||||
|
ToIr as _, synthetic_span,
|
||||||
|
};
|
||||||
use crate::runtime::{Runtime, RuntimeContext};
|
use crate::runtime::{Runtime, RuntimeContext};
|
||||||
|
use crate::sourcemap::NixSourceMapBuilder;
|
||||||
use crate::store::{StoreBackend, StoreConfig};
|
use crate::store::{StoreBackend, StoreConfig};
|
||||||
use crate::value::Value;
|
use crate::value::Value;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@@ -43,6 +48,9 @@ mod private {
|
|||||||
fn compile_code(&mut self, expr: &str) -> Result<String> {
|
fn compile_code(&mut self, expr: &str) -> Result<String> {
|
||||||
self.as_mut().compile_code(expr)
|
self.as_mut().compile_code(expr)
|
||||||
}
|
}
|
||||||
|
fn get_current_source(&self) -> Option<Arc<str>> {
|
||||||
|
self.as_ref().get_current_source()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
use private::CtxPtr;
|
use private::CtxPtr;
|
||||||
@@ -67,7 +75,11 @@ impl Context {
|
|||||||
let config = StoreConfig::from_env();
|
let config = StoreConfig::from_env();
|
||||||
let store = Arc::new(StoreBackend::new(config)?);
|
let store = Arc::new(StoreBackend::new(config)?);
|
||||||
|
|
||||||
Ok(Self { ctx, runtime, store })
|
Ok(Self {
|
||||||
|
ctx,
|
||||||
|
runtime,
|
||||||
|
store,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn eval_code(&mut self, expr: &str) -> Result<Value> {
|
pub fn eval_code(&mut self, expr: &str) -> Result<Value> {
|
||||||
@@ -83,10 +95,7 @@ impl Context {
|
|||||||
tracing::debug!("Compiling code");
|
tracing::debug!("Compiling code");
|
||||||
let code = self.compile_code(expr)?;
|
let code = self.compile_code(expr)?;
|
||||||
|
|
||||||
self.runtime
|
self.runtime.op_state().borrow_mut().put(self.store.clone());
|
||||||
.op_state()
|
|
||||||
.borrow_mut()
|
|
||||||
.put(self.store.clone());
|
|
||||||
|
|
||||||
tracing::debug!("Executing JavaScript");
|
tracing::debug!("Executing JavaScript");
|
||||||
self.runtime
|
self.runtime
|
||||||
@@ -112,6 +121,9 @@ pub(crate) struct Ctx {
|
|||||||
symbols: DefaultStringInterner,
|
symbols: DefaultStringInterner,
|
||||||
global: NonNull<HashMap<SymId, ExprId>>,
|
global: NonNull<HashMap<SymId, ExprId>>,
|
||||||
current_file: Option<PathBuf>,
|
current_file: Option<PathBuf>,
|
||||||
|
source_map: HashMap<PathBuf, Arc<str>>,
|
||||||
|
current_source: Option<Arc<str>>,
|
||||||
|
js_source_maps: HashMap<PathBuf, sourcemap::SourceMap>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Ctx {
|
impl Default for Ctx {
|
||||||
@@ -122,7 +134,12 @@ impl Default for Ctx {
|
|||||||
let mut irs = Vec::new();
|
let mut irs = Vec::new();
|
||||||
let mut global = HashMap::new();
|
let mut global = HashMap::new();
|
||||||
|
|
||||||
irs.push(Builtins.to_ir());
|
irs.push(
|
||||||
|
Builtins {
|
||||||
|
span: synthetic_span(),
|
||||||
|
}
|
||||||
|
.to_ir(),
|
||||||
|
);
|
||||||
let builtins_expr = ExprId(0);
|
let builtins_expr = ExprId(0);
|
||||||
|
|
||||||
let builtins_sym = symbols.get_or_intern("builtins");
|
let builtins_sym = symbols.get_or_intern("builtins");
|
||||||
@@ -150,15 +167,41 @@ impl Default for Ctx {
|
|||||||
"toString",
|
"toString",
|
||||||
];
|
];
|
||||||
let consts = [
|
let consts = [
|
||||||
("true", Ir::Bool(true)),
|
(
|
||||||
("false", Ir::Bool(false)),
|
"true",
|
||||||
("null", Ir::Null(())),
|
Bool {
|
||||||
|
inner: true,
|
||||||
|
span: synthetic_span(),
|
||||||
|
}
|
||||||
|
.to_ir(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"false",
|
||||||
|
Bool {
|
||||||
|
inner: false,
|
||||||
|
span: synthetic_span(),
|
||||||
|
}
|
||||||
|
.to_ir(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"null",
|
||||||
|
Null {
|
||||||
|
span: synthetic_span(),
|
||||||
|
}
|
||||||
|
.to_ir(),
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
for name in free_globals {
|
for name in free_globals {
|
||||||
let name_sym = symbols.get_or_intern(name);
|
let name_sym = symbols.get_or_intern(name);
|
||||||
let id = ExprId(irs.len());
|
let id = ExprId(irs.len());
|
||||||
irs.push(Builtin(name_sym).to_ir());
|
irs.push(
|
||||||
|
Builtin {
|
||||||
|
inner: name_sym,
|
||||||
|
span: synthetic_span(),
|
||||||
|
}
|
||||||
|
.to_ir(),
|
||||||
|
);
|
||||||
global.insert(name_sym, id);
|
global.insert(name_sym, id);
|
||||||
}
|
}
|
||||||
for (name, value) in consts {
|
for (name, value) in consts {
|
||||||
@@ -173,6 +216,9 @@ impl Default for Ctx {
|
|||||||
irs,
|
irs,
|
||||||
global: unsafe { NonNull::new_unchecked(Box::leak(Box::new(global))) },
|
global: unsafe { NonNull::new_unchecked(Box::leak(Box::new(global))) },
|
||||||
current_file: None,
|
current_file: None,
|
||||||
|
source_map: HashMap::new(),
|
||||||
|
current_source: None,
|
||||||
|
js_source_maps: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -195,11 +241,27 @@ impl Ctx {
|
|||||||
.expect("current_file doesn't have a parent dir")
|
.expect("current_file doesn't have a parent dir")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_current_source(&self) -> Option<Arc<str>> {
|
||||||
|
self.current_source.clone()
|
||||||
|
}
|
||||||
|
|
||||||
fn compile_code(&mut self, expr: &str) -> Result<String> {
|
fn compile_code(&mut self, expr: &str) -> Result<String> {
|
||||||
tracing::debug!("Parsing Nix expression");
|
tracing::debug!("Parsing Nix expression");
|
||||||
|
|
||||||
|
// Store source text for error reporting
|
||||||
|
let source: Arc<str> = Arc::from(expr);
|
||||||
|
self.current_source = Some(source.clone());
|
||||||
|
|
||||||
|
// Store source in source_map if we have a current_file
|
||||||
|
if let Some(ref file) = self.current_file {
|
||||||
|
self.source_map.insert(file.clone(), source.clone());
|
||||||
|
}
|
||||||
|
|
||||||
let root = rnix::Root::parse(expr);
|
let root = rnix::Root::parse(expr);
|
||||||
if !root.errors().is_empty() {
|
if !root.errors().is_empty() {
|
||||||
return Err(Error::parse_error(root.errors().iter().join("; ")));
|
let error_msg = root.errors().iter().join("; ");
|
||||||
|
let err = Error::parse_error(error_msg).with_source(source);
|
||||||
|
return Err(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::unwrap_used)]
|
#[allow(clippy::unwrap_used)]
|
||||||
@@ -283,8 +345,14 @@ impl DowngradeContext for DowngradeCtx<'_> {
|
|||||||
ExprId(self.ctx.irs.len() + self.irs.len() - 1)
|
ExprId(self.ctx.irs.len() + self.irs.len() - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_arg(&mut self) -> ExprId {
|
fn new_arg(&mut self, span: TextRange) -> ExprId {
|
||||||
self.irs.push(Some(Ir::Arg(ArgId(self.arg_id))));
|
self.irs.push(Some(
|
||||||
|
Arg {
|
||||||
|
inner: ArgId(self.arg_id),
|
||||||
|
span,
|
||||||
|
}
|
||||||
|
.to_ir(),
|
||||||
|
));
|
||||||
self.arg_id += 1;
|
self.arg_id += 1;
|
||||||
ExprId(self.ctx.irs.len() + self.irs.len() - 1)
|
ExprId(self.ctx.irs.len() + self.irs.len() - 1)
|
||||||
}
|
}
|
||||||
@@ -297,7 +365,7 @@ impl DowngradeContext for DowngradeCtx<'_> {
|
|||||||
self.ctx.get_sym(id)
|
self.ctx.get_sym(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lookup(&mut self, sym: SymId) -> Result<ExprId> {
|
fn lookup(&mut self, sym: SymId, span: TextRange) -> Result<ExprId> {
|
||||||
for scope in self.scopes.iter().rev() {
|
for scope in self.scopes.iter().rev() {
|
||||||
match scope {
|
match scope {
|
||||||
&Scope::Global(global_scope) => {
|
&Scope::Global(global_scope) => {
|
||||||
@@ -345,7 +413,7 @@ impl DowngradeContext for DowngradeCtx<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(self.new_expr(Ir::ExprRef(expr)));
|
return Ok(self.new_expr(ExprRef { inner: expr, span }.to_ir()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&Scope::Param(param_sym, expr) => {
|
&Scope::Param(param_sym, expr) => {
|
||||||
@@ -375,10 +443,15 @@ impl DowngradeContext for DowngradeCtx<'_> {
|
|||||||
expr: namespace,
|
expr: namespace,
|
||||||
attrpath: vec![Attr::Str(sym)],
|
attrpath: vec![Attr::Str(sym)],
|
||||||
default: result, // Link to outer With or None
|
default: result, // Link to outer With or None
|
||||||
|
span,
|
||||||
};
|
};
|
||||||
result = Some(self.new_expr(select.to_ir()));
|
result = Some(self.new_expr(select.to_ir()));
|
||||||
}
|
}
|
||||||
result.ok_or_else(|| Error::downgrade_error(format!("'{}' not found", self.get_sym(sym))))
|
result.ok_or_else(|| {
|
||||||
|
Error::downgrade_error(format!("'{}' not found", self.get_sym(sym)))
|
||||||
|
.with_span(span)
|
||||||
|
.with_source(self.get_current_source().unwrap_or_else(|| Arc::from("")))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract_expr(&mut self, id: ExprId) -> Ir {
|
fn extract_expr(&mut self, id: ExprId) -> Ir {
|
||||||
@@ -399,6 +472,19 @@ impl DowngradeContext for DowngradeCtx<'_> {
|
|||||||
.insert(expr);
|
.insert(expr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_span(&self, id: ExprId) -> rnix::TextRange {
|
||||||
|
dbg!(id);
|
||||||
|
if id.0 >= self.ctx.irs.len() {
|
||||||
|
return self.ctx.irs.get(id.0).unwrap().span()
|
||||||
|
}
|
||||||
|
let local_id = id.0 - self.ctx.irs.len();
|
||||||
|
self.irs.get(local_id).unwrap().as_ref().unwrap().span()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_current_source(&self) -> Option<Arc<str>> {
|
||||||
|
self.ctx.current_source.clone()
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(refining_impl_trait)]
|
#[allow(refining_impl_trait)]
|
||||||
fn reserve_slots(&mut self, slots: usize) -> impl Iterator<Item = ExprId> + Clone + use<> {
|
fn reserve_slots(&mut self, slots: usize) -> impl Iterator<Item = ExprId> + Clone + use<> {
|
||||||
let start = self.ctx.irs.len() + self.irs.len();
|
let start = self.ctx.irs.len() + self.irs.len();
|
||||||
|
|||||||
@@ -1,135 +1,256 @@
|
|||||||
|
use miette::{Diagnostic, LabeledSpan, SourceSpan};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
pub type Result<T> = core::result::Result<T, Error>;
|
pub type Result<T> = core::result::Result<T, Error>;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug, Diagnostic)]
|
||||||
pub enum ErrorKind {
|
pub enum Error {
|
||||||
#[error("error occurred during parse stage: {0}")]
|
#[error("Parse error: {message}")]
|
||||||
ParseError(String),
|
#[diagnostic(code(nix::parse))]
|
||||||
#[error("error occurred during downgrade stage: {0}")]
|
ParseError {
|
||||||
DowngradeError(String),
|
#[source_code]
|
||||||
#[error(
|
src: Option<Arc<str>>,
|
||||||
"error occurred during evaluation stage: {msg}{}",
|
#[label("error occurred here")]
|
||||||
backtrace.as_ref().map_or("".into(), |backtrace| format!("\nBacktrace: {backtrace}"))
|
span: Option<SourceSpan>,
|
||||||
)]
|
message: String,
|
||||||
EvalError {
|
|
||||||
msg: String,
|
|
||||||
backtrace: Option<String>,
|
|
||||||
},
|
},
|
||||||
#[error("internal error occurred: {0}")]
|
|
||||||
InternalError(String),
|
#[error("Downgrade error: {message}")]
|
||||||
#[error("{0}")]
|
#[diagnostic(code(nix::downgrade))]
|
||||||
Catchable(String),
|
DowngradeError {
|
||||||
#[error("an unknown or unexpected error occurred")]
|
#[source_code]
|
||||||
|
src: Option<Arc<str>>,
|
||||||
|
#[label("{message}")]
|
||||||
|
span: Option<SourceSpan>,
|
||||||
|
message: String,
|
||||||
|
// #[related]
|
||||||
|
// related: Vec<LabeledSpan>,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[error("Evaluation error: {message}")]
|
||||||
|
#[diagnostic(code(nix::eval))]
|
||||||
|
EvalError {
|
||||||
|
#[source_code]
|
||||||
|
src: Option<Arc<str>>,
|
||||||
|
#[label("error occurred here")]
|
||||||
|
span: Option<SourceSpan>,
|
||||||
|
message: String,
|
||||||
|
// #[help]
|
||||||
|
js_backtrace: Option<String>,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[error("Internal error: {message}")]
|
||||||
|
#[diagnostic(code(nix::internal))]
|
||||||
|
InternalError { message: String },
|
||||||
|
|
||||||
|
#[error("{message}")]
|
||||||
|
#[diagnostic(code(nix::catchable))]
|
||||||
|
Catchable { message: String },
|
||||||
|
|
||||||
|
#[error("Unknown error")]
|
||||||
|
#[diagnostic(code(nix::unknown))]
|
||||||
Unknown,
|
Unknown,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Error {
|
|
||||||
pub kind: ErrorKind,
|
|
||||||
pub span: Option<rnix::TextRange>,
|
|
||||||
pub source: Option<Arc<str>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for Error {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
// Basic display
|
|
||||||
write!(f, "{}", self.kind)?;
|
|
||||||
|
|
||||||
// If we have source and span, print context
|
|
||||||
if let (Some(source), Some(span)) = (&self.source, self.span) {
|
|
||||||
let start_byte = usize::from(span.start());
|
|
||||||
let end_byte = usize::from(span.end());
|
|
||||||
|
|
||||||
if start_byte > source.len() || end_byte > source.len() {
|
|
||||||
return Ok(()); // Span is out of bounds
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut start_line = 1;
|
|
||||||
let mut start_col = 1usize;
|
|
||||||
let mut line_start_byte = 0;
|
|
||||||
for (i, c) in source.char_indices() {
|
|
||||||
if i >= start_byte {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if c == '\n' {
|
|
||||||
start_line += 1;
|
|
||||||
start_col = 1;
|
|
||||||
line_start_byte = i + 1;
|
|
||||||
} else {
|
|
||||||
start_col += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let line_end_byte = source[line_start_byte..]
|
|
||||||
.find('\n')
|
|
||||||
.map(|i| line_start_byte + i)
|
|
||||||
.unwrap_or(source.len());
|
|
||||||
|
|
||||||
let line_str = &source[line_start_byte..line_end_byte];
|
|
||||||
|
|
||||||
let underline_len = if end_byte > start_byte {
|
|
||||||
end_byte - start_byte
|
|
||||||
} else {
|
|
||||||
1
|
|
||||||
};
|
|
||||||
|
|
||||||
write!(f, "\n --> {}:{}", start_line, start_col)?;
|
|
||||||
write!(f, "\n |\n")?;
|
|
||||||
writeln!(f, "{:4} | {}", start_line, line_str)?;
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
" | {}{}",
|
|
||||||
" ".repeat(start_col.saturating_sub(1)),
|
|
||||||
"^".repeat(underline_len)
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::error::Error for Error {
|
|
||||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
|
||||||
Some(&self.kind)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Error {
|
impl Error {
|
||||||
pub fn new(kind: ErrorKind) -> Self {
|
pub fn parse_error(msg: String) -> Self {
|
||||||
Self {
|
Error::ParseError {
|
||||||
kind,
|
src: None,
|
||||||
span: None,
|
span: None,
|
||||||
source: None,
|
message: msg,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_span(mut self, span: rnix::TextRange) -> Self {
|
|
||||||
self.span = Some(span);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_source(mut self, source: Arc<str>) -> Self {
|
|
||||||
self.source = Some(source);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_error(msg: String) -> Self {
|
|
||||||
Self::new(ErrorKind::ParseError(msg))
|
|
||||||
}
|
|
||||||
pub fn downgrade_error(msg: String) -> Self {
|
pub fn downgrade_error(msg: String) -> Self {
|
||||||
Self::new(ErrorKind::DowngradeError(msg))
|
Error::DowngradeError {
|
||||||
|
src: None,
|
||||||
|
span: None,
|
||||||
|
message: msg,
|
||||||
|
// related: Vec::new(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pub fn downgrade_error_with_related(msg: String, related: Vec<LabeledSpan>) -> Self {
|
||||||
|
// Error::DowngradeError {
|
||||||
|
// src: None,
|
||||||
|
// span: None,
|
||||||
|
// message: msg,
|
||||||
|
// related,
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
pub fn eval_error(msg: String, backtrace: Option<String>) -> Self {
|
pub fn eval_error(msg: String, backtrace: Option<String>) -> Self {
|
||||||
Self::new(ErrorKind::EvalError { msg, backtrace })
|
Error::EvalError {
|
||||||
|
src: None,
|
||||||
|
span: None,
|
||||||
|
message: msg,
|
||||||
|
js_backtrace: backtrace,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn internal(msg: String) -> Self {
|
pub fn internal(msg: String) -> Self {
|
||||||
Self::new(ErrorKind::InternalError(msg))
|
Error::InternalError { message: msg }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn catchable(msg: String) -> Self {
|
pub fn catchable(msg: String) -> Self {
|
||||||
Self::new(ErrorKind::Catchable(msg))
|
Error::Catchable { message: msg }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unknown() -> Self {
|
pub fn unknown() -> Self {
|
||||||
Self::new(ErrorKind::Unknown)
|
Error::Unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_span(self, span: rnix::TextRange) -> Self {
|
||||||
|
let source_span = Some(text_range_to_source_span(span));
|
||||||
|
match self {
|
||||||
|
Error::ParseError { src, message, .. } => Error::ParseError {
|
||||||
|
src,
|
||||||
|
span: source_span,
|
||||||
|
message,
|
||||||
|
},
|
||||||
|
Error::DowngradeError {
|
||||||
|
src,
|
||||||
|
message,
|
||||||
|
// related,
|
||||||
|
..
|
||||||
|
} => Error::DowngradeError {
|
||||||
|
src,
|
||||||
|
span: source_span,
|
||||||
|
message,
|
||||||
|
// related,
|
||||||
|
},
|
||||||
|
Error::EvalError {
|
||||||
|
src,
|
||||||
|
message,
|
||||||
|
js_backtrace,
|
||||||
|
..
|
||||||
|
} => Error::EvalError {
|
||||||
|
src,
|
||||||
|
span: source_span,
|
||||||
|
message,
|
||||||
|
js_backtrace,
|
||||||
|
},
|
||||||
|
other => other,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_source(self, source: Arc<str>) -> Self {
|
||||||
|
let src = Some(source);
|
||||||
|
match self {
|
||||||
|
Error::ParseError { span, message, .. } => Error::ParseError { src, span, message },
|
||||||
|
Error::DowngradeError {
|
||||||
|
span,
|
||||||
|
message,
|
||||||
|
// related,
|
||||||
|
..
|
||||||
|
} => Error::DowngradeError {
|
||||||
|
src,
|
||||||
|
span,
|
||||||
|
message,
|
||||||
|
// related,
|
||||||
|
},
|
||||||
|
Error::EvalError {
|
||||||
|
span,
|
||||||
|
message,
|
||||||
|
js_backtrace,
|
||||||
|
..
|
||||||
|
} => Error::EvalError {
|
||||||
|
src,
|
||||||
|
span,
|
||||||
|
message,
|
||||||
|
js_backtrace,
|
||||||
|
},
|
||||||
|
other => other,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn text_range_to_source_span(range: rnix::TextRange) -> SourceSpan {
|
||||||
|
let start = usize::from(range.start());
|
||||||
|
let len = usize::from(range.end()) - start;
|
||||||
|
SourceSpan::new(start.into(), len)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stack frame types from Nix evaluation
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub(crate) struct NixStackFrame {
|
||||||
|
pub span: rnix::TextRange,
|
||||||
|
pub message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse Nix stack trace from V8 Error.stack
|
||||||
|
/// Returns vector of stack frames (in order from oldest to newest)
|
||||||
|
pub(crate) fn parse_nix_stack(stack: &str) -> Vec<NixStackFrame> {
|
||||||
|
let mut frames = Vec::new();
|
||||||
|
|
||||||
|
for line in stack.lines() {
|
||||||
|
if !line.starts_with("NIX_STACK_FRAME:") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format: NIX_STACK_FRAME:type:start:end[:extra_data]
|
||||||
|
let rest = line.strip_prefix("NIX_STACK_FRAME:").unwrap();
|
||||||
|
let parts: Vec<&str> = rest.splitn(4, ':').collect();
|
||||||
|
|
||||||
|
if parts.len() < 3 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let frame_type = parts[0];
|
||||||
|
let start: u32 = match parts[1].parse() {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(_) => continue,
|
||||||
|
};
|
||||||
|
let end: u32 = match parts[2].parse() {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(_) => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
let span = rnix::TextRange::new(
|
||||||
|
rnix::TextSize::from(start),
|
||||||
|
rnix::TextSize::from(end)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Convert all frame types to context frames with descriptive messages
|
||||||
|
let message = match frame_type {
|
||||||
|
"call" => "from call site".to_string(),
|
||||||
|
"select" => {
|
||||||
|
let path = if parts.len() >= 4 { parts[3] } else { "" };
|
||||||
|
if path.is_empty() {
|
||||||
|
"while selecting attribute".to_string()
|
||||||
|
} else {
|
||||||
|
format!("while selecting attribute [{}]", path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"context" => {
|
||||||
|
if parts.len() >= 4 {
|
||||||
|
parts[3].to_string()
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
frames.push(NixStackFrame { span, message });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deduplicate consecutive identical frames
|
||||||
|
frames.dedup_by(|a, b| a.span == b.span && a.message == b.message);
|
||||||
|
|
||||||
|
frames
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Format stack trace for display (reversed order, newest at bottom)
|
||||||
|
pub(crate) fn format_stack_trace(frames: &[NixStackFrame]) -> Vec<String> {
|
||||||
|
let mut lines = Vec::new();
|
||||||
|
|
||||||
|
// Reverse order: oldest first, newest last
|
||||||
|
for frame in frames.iter().rev() {
|
||||||
|
lines.push(format!("{} at {}:{}",
|
||||||
|
frame.message, usize::from(frame.span.start()), usize::from(frame.span.end())));
|
||||||
|
}
|
||||||
|
|
||||||
|
lines
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use deno_core::op2;
|
|
||||||
use deno_core::OpState;
|
use deno_core::OpState;
|
||||||
|
use deno_core::op2;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use tracing::{debug, info, warn};
|
use tracing::{debug, info, warn};
|
||||||
|
|
||||||
@@ -14,8 +14,8 @@ pub use cache::FetcherCache;
|
|||||||
pub use download::Downloader;
|
pub use download::Downloader;
|
||||||
pub use metadata_cache::MetadataCache;
|
pub use metadata_cache::MetadataCache;
|
||||||
|
|
||||||
use crate::runtime::NixError;
|
|
||||||
use crate::nar;
|
use crate::nar;
|
||||||
|
use crate::runtime::NixError;
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct FetchUrlResult {
|
pub struct FetchUrlResult {
|
||||||
@@ -69,8 +69,7 @@ pub fn op_fetch_url(
|
|||||||
let file_name =
|
let file_name =
|
||||||
name.unwrap_or_else(|| url.rsplit('/').next().unwrap_or("download").to_string());
|
name.unwrap_or_else(|| url.rsplit('/').next().unwrap_or("download").to_string());
|
||||||
|
|
||||||
let metadata_cache =
|
let metadata_cache = MetadataCache::new(3600).map_err(|e| NixError::from(e.to_string()))?;
|
||||||
MetadataCache::new(3600).map_err(|e| NixError::from(e.to_string()))?;
|
|
||||||
|
|
||||||
let input = serde_json::json!({
|
let input = serde_json::json!({
|
||||||
"type": "file",
|
"type": "file",
|
||||||
@@ -156,10 +155,7 @@ pub fn op_fetch_url(
|
|||||||
.add(&input, &info, &store_path, true)
|
.add(&input, &info, &store_path, true)
|
||||||
.map_err(|e| NixError::from(e.to_string()))?;
|
.map_err(|e| NixError::from(e.to_string()))?;
|
||||||
|
|
||||||
Ok(FetchUrlResult {
|
Ok(FetchUrlResult { store_path, hash })
|
||||||
store_path,
|
|
||||||
hash,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[op2]
|
#[op2]
|
||||||
@@ -178,8 +174,7 @@ pub fn op_fetch_tarball(
|
|||||||
info!("fetchTarball started");
|
info!("fetchTarball started");
|
||||||
|
|
||||||
let dir_name = name.unwrap_or_else(|| "source".to_string());
|
let dir_name = name.unwrap_or_else(|| "source".to_string());
|
||||||
let metadata_cache =
|
let metadata_cache = MetadataCache::new(3600).map_err(|e| NixError::from(e.to_string()))?;
|
||||||
MetadataCache::new(3600).map_err(|e| NixError::from(e.to_string()))?;
|
|
||||||
|
|
||||||
let input = serde_json::json!({
|
let input = serde_json::json!({
|
||||||
"type": "tarball",
|
"type": "tarball",
|
||||||
|
|||||||
@@ -78,7 +78,10 @@ impl FetcherCache {
|
|||||||
self.hg_cache_dir().join(key)
|
self.hg_cache_dir().join(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extract_tarball_to_temp(&self, data: &[u8]) -> Result<(PathBuf, tempfile::TempDir), CacheError> {
|
pub fn extract_tarball_to_temp(
|
||||||
|
&self,
|
||||||
|
data: &[u8],
|
||||||
|
) -> Result<(PathBuf, tempfile::TempDir), CacheError> {
|
||||||
let temp_dir = tempfile::tempdir()?;
|
let temp_dir = tempfile::tempdir()?;
|
||||||
let extracted_path = super::archive::extract_archive(data, temp_dir.path())?;
|
let extracted_path = super::archive::extract_archive(data, temp_dir.path())?;
|
||||||
Ok((extracted_path, temp_dir))
|
Ok((extracted_path, temp_dir))
|
||||||
|
|||||||
@@ -34,7 +34,8 @@ pub fn fetch_git(
|
|||||||
let nar_hash = crate::nar::compute_nar_hash(&checkout_dir)
|
let nar_hash = crate::nar::compute_nar_hash(&checkout_dir)
|
||||||
.map_err(|e| GitError::NarHashError(e.to_string()))?;
|
.map_err(|e| GitError::NarHashError(e.to_string()))?;
|
||||||
|
|
||||||
let store_path = store.add_to_store_from_path(name, &checkout_dir, vec![])
|
let store_path = store
|
||||||
|
.add_to_store_from_path(name, &checkout_dir, vec![])
|
||||||
.map_err(|e| GitError::StoreError(e.to_string()))?;
|
.map_err(|e| GitError::StoreError(e.to_string()))?;
|
||||||
|
|
||||||
let rev_count = get_rev_count(&bare_repo, &target_rev)?;
|
let rev_count = get_rev_count(&bare_repo, &target_rev)?;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use rusqlite::{params, Connection, OptionalExtension};
|
use rusqlite::{Connection, OptionalExtension, params};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json;
|
use serde_json;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
@@ -72,8 +72,9 @@ impl MetadataCache {
|
|||||||
.unwrap_or_else(|| PathBuf::from("/tmp"))
|
.unwrap_or_else(|| PathBuf::from("/tmp"))
|
||||||
.join("nix-js");
|
.join("nix-js");
|
||||||
|
|
||||||
std::fs::create_dir_all(&cache_dir)
|
std::fs::create_dir_all(&cache_dir).map_err(|e| {
|
||||||
.map_err(|e| CacheError::Database(rusqlite::Error::ToSqlConversionFailure(Box::new(e))))?;
|
CacheError::Database(rusqlite::Error::ToSqlConversionFailure(Box::new(e)))
|
||||||
|
})?;
|
||||||
|
|
||||||
let db_path = cache_dir.join("fetcher-cache.sqlite");
|
let db_path = cache_dir.join("fetcher-cache.sqlite");
|
||||||
let conn = Connection::open(db_path)?;
|
let conn = Connection::open(db_path)?;
|
||||||
@@ -156,15 +157,15 @@ impl MetadataCache {
|
|||||||
.optional()?;
|
.optional()?;
|
||||||
|
|
||||||
match entry {
|
match entry {
|
||||||
Some((input_json, info_json, store_path, immutable, timestamp)) => Ok(Some(
|
Some((input_json, info_json, store_path, immutable, timestamp)) => {
|
||||||
CacheEntry {
|
Ok(Some(CacheEntry {
|
||||||
input: serde_json::from_str(&input_json)?,
|
input: serde_json::from_str(&input_json)?,
|
||||||
info: serde_json::from_str(&info_json)?,
|
info: serde_json::from_str(&info_json)?,
|
||||||
store_path,
|
store_path,
|
||||||
immutable: immutable != 0,
|
immutable: immutable != 0,
|
||||||
timestamp: timestamp as u64,
|
timestamp: timestamp as u64,
|
||||||
},
|
}))
|
||||||
)),
|
}
|
||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
214
nix-js/src/ir.rs
214
nix-js/src/ir.rs
@@ -1,6 +1,7 @@
|
|||||||
use derive_more::{IsVariant, TryUnwrap, Unwrap};
|
use derive_more::{IsVariant, TryUnwrap, Unwrap};
|
||||||
use hashbrown::{HashMap, HashSet};
|
use hashbrown::{HashMap, HashSet};
|
||||||
use rnix::ast;
|
use rnix::{TextRange, ast};
|
||||||
|
use std::sync::Arc;
|
||||||
use string_interner::symbol::SymbolU32;
|
use string_interner::symbol::SymbolU32;
|
||||||
|
|
||||||
use crate::context::SccInfo;
|
use crate::context::SccInfo;
|
||||||
@@ -9,24 +10,29 @@ use crate::value::format_symbol;
|
|||||||
use nix_js_macros::ir;
|
use nix_js_macros::ir;
|
||||||
|
|
||||||
mod downgrade;
|
mod downgrade;
|
||||||
|
mod span_utils;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
use utils::*;
|
use utils::*;
|
||||||
|
|
||||||
pub use downgrade::Downgrade;
|
pub use downgrade::Downgrade;
|
||||||
|
pub(crate) use span_utils::*;
|
||||||
|
|
||||||
pub trait DowngradeContext {
|
pub trait DowngradeContext {
|
||||||
fn downgrade(self, expr: rnix::ast::Expr) -> Result<ExprId>;
|
fn downgrade(self, expr: rnix::ast::Expr) -> Result<ExprId>;
|
||||||
|
|
||||||
fn new_expr(&mut self, expr: Ir) -> ExprId;
|
fn new_expr(&mut self, expr: Ir) -> ExprId;
|
||||||
fn new_arg(&mut self) -> ExprId;
|
fn new_arg(&mut self, span: TextRange) -> ExprId;
|
||||||
|
|
||||||
fn new_sym(&mut self, sym: String) -> SymId;
|
fn new_sym(&mut self, sym: String) -> SymId;
|
||||||
fn get_sym(&self, id: SymId) -> &str;
|
fn get_sym(&self, id: SymId) -> &str;
|
||||||
fn lookup(&mut self, sym: SymId) -> Result<ExprId>;
|
fn lookup(&mut self, sym: SymId, span: TextRange) -> Result<ExprId>;
|
||||||
|
|
||||||
fn extract_expr(&mut self, id: ExprId) -> Ir;
|
fn extract_expr(&mut self, id: ExprId) -> Ir;
|
||||||
fn replace_expr(&mut self, id: ExprId, expr: Ir);
|
fn replace_expr(&mut self, id: ExprId, expr: Ir);
|
||||||
fn reserve_slots(&mut self, slots: usize) -> impl Iterator<Item = ExprId> + Clone + use<Self>;
|
fn reserve_slots(&mut self, slots: usize) -> impl Iterator<Item = ExprId> + Clone + use<Self>;
|
||||||
|
fn get_span(&self, id: ExprId) -> TextRange;
|
||||||
|
fn get_current_source(&self) -> Option<Arc<str>>;
|
||||||
|
|
||||||
fn with_param_scope<F, R>(&mut self, param: SymId, arg: ExprId, f: F) -> R
|
fn with_param_scope<F, R>(&mut self, param: SymId, arg: ExprId, f: F) -> R
|
||||||
where
|
where
|
||||||
@@ -51,27 +57,57 @@ ir! {
|
|||||||
Int(i64),
|
Int(i64),
|
||||||
Float(f64),
|
Float(f64),
|
||||||
Bool(bool),
|
Bool(bool),
|
||||||
Null(()),
|
Null,
|
||||||
Str,
|
Str { pub val: String },
|
||||||
AttrSet,
|
AttrSet { pub stcs: HashMap<SymId, ExprId>, pub dyns: Vec<(ExprId, ExprId)> },
|
||||||
List,
|
List { pub items: Vec<ExprId> },
|
||||||
|
|
||||||
HasAttr,
|
HasAttr { pub lhs: ExprId, pub rhs: Vec<Attr> },
|
||||||
BinOp,
|
BinOp { pub lhs: ExprId, pub rhs: ExprId, pub kind: BinOpKind },
|
||||||
UnOp,
|
UnOp { pub rhs: ExprId, pub kind: UnOpKind },
|
||||||
Select,
|
Select { pub expr: ExprId, pub attrpath: Vec<Attr>, pub default: Option<ExprId> },
|
||||||
If,
|
If { pub cond: ExprId, pub consq: ExprId, pub alter: ExprId },
|
||||||
Call,
|
Call { pub func: ExprId, pub arg: ExprId },
|
||||||
Assert,
|
Assert { pub assertion: ExprId, pub expr: ExprId, pub assertion_raw: String },
|
||||||
ConcatStrings,
|
ConcatStrings { pub parts: Vec<ExprId> },
|
||||||
Path,
|
Path { pub expr: ExprId },
|
||||||
Func,
|
Func { pub body: ExprId, pub param: Param, pub arg: ExprId },
|
||||||
Let,
|
Let { pub binding_sccs: SccInfo, pub body: ExprId },
|
||||||
Arg(ArgId),
|
Arg(ArgId),
|
||||||
ExprRef(ExprId),
|
ExprRef(ExprId),
|
||||||
Thunk(ExprId),
|
Thunk(ExprId),
|
||||||
Builtins,
|
Builtins,
|
||||||
Builtin,
|
Builtin(SymId),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ir {
|
||||||
|
pub fn span(&self) -> TextRange {
|
||||||
|
match self {
|
||||||
|
Ir::Int(i) => i.span,
|
||||||
|
Ir::Float(f) => f.span,
|
||||||
|
Ir::Bool(b) => b.span,
|
||||||
|
Ir::Null(n) => n.span,
|
||||||
|
Ir::Str(s) => s.span,
|
||||||
|
Ir::AttrSet(a) => a.span,
|
||||||
|
Ir::List(l) => l.span,
|
||||||
|
Ir::HasAttr(h) => h.span,
|
||||||
|
Ir::BinOp(b) => b.span,
|
||||||
|
Ir::UnOp(u) => u.span,
|
||||||
|
Ir::Select(s) => s.span,
|
||||||
|
Ir::If(i) => i.span,
|
||||||
|
Ir::Call(c) => c.span,
|
||||||
|
Ir::Assert(a) => a.span,
|
||||||
|
Ir::ConcatStrings(c) => c.span,
|
||||||
|
Ir::Path(p) => p.span,
|
||||||
|
Ir::Func(f) => f.span,
|
||||||
|
Ir::Let(l) => l.span,
|
||||||
|
Ir::Arg(a) => a.span,
|
||||||
|
Ir::ExprRef(e) => e.span,
|
||||||
|
Ir::Thunk(t) => t.span,
|
||||||
|
Ir::Builtins(b) => b.span,
|
||||||
|
Ir::Builtin(b) => b.span,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AttrSet {
|
impl AttrSet {
|
||||||
@@ -105,7 +141,12 @@ impl AttrSet {
|
|||||||
result?;
|
result?;
|
||||||
} else {
|
} else {
|
||||||
// Create a new sub-attrset because this path doesn't exist yet.
|
// Create a new sub-attrset because this path doesn't exist yet.
|
||||||
let mut attrs = AttrSet::default();
|
// FIXME: span
|
||||||
|
let mut attrs = AttrSet {
|
||||||
|
stcs: Default::default(),
|
||||||
|
dyns: Default::default(),
|
||||||
|
span: synthetic_span(),
|
||||||
|
};
|
||||||
attrs._insert(path, name, value, ctx)?;
|
attrs._insert(path, name, value, ctx)?;
|
||||||
let attrs = ctx.new_expr(attrs.to_ir());
|
let attrs = ctx.new_expr(attrs.to_ir());
|
||||||
self.stcs.insert(ident, attrs);
|
self.stcs.insert(ident, attrs);
|
||||||
@@ -115,7 +156,12 @@ impl AttrSet {
|
|||||||
Attr::Dynamic(dynamic) => {
|
Attr::Dynamic(dynamic) => {
|
||||||
// If the next attribute is a dynamic expression, we must create a new sub-attrset.
|
// If the next attribute is a dynamic expression, we must create a new sub-attrset.
|
||||||
// We cannot merge with existing dynamic attributes at this stage.
|
// We cannot merge with existing dynamic attributes at this stage.
|
||||||
let mut attrs = AttrSet::default();
|
// FIXME: span
|
||||||
|
let mut attrs = AttrSet {
|
||||||
|
stcs: Default::default(),
|
||||||
|
dyns: Default::default(),
|
||||||
|
span: synthetic_span(),
|
||||||
|
};
|
||||||
attrs._insert(path, name, value, ctx)?;
|
attrs._insert(path, name, value, ctx)?;
|
||||||
self.dyns.push((dynamic, ctx.new_expr(attrs.to_ir())));
|
self.dyns.push((dynamic, ctx.new_expr(attrs.to_ir())));
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -165,15 +211,6 @@ pub type SymId = SymbolU32;
|
|||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
pub struct ArgId(pub usize);
|
pub struct ArgId(pub usize);
|
||||||
|
|
||||||
/// Represents a Nix attribute set.
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub struct AttrSet {
|
|
||||||
/// Statically known attributes (key is a string).
|
|
||||||
pub stcs: HashMap<SymId, ExprId>,
|
|
||||||
/// Dynamically computed attributes, where both the key and value are expressions.
|
|
||||||
pub dyns: Vec<(ExprId, ExprId)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents a key in an attribute path.
|
/// Represents a key in an attribute path.
|
||||||
#[derive(Debug, TryUnwrap)]
|
#[derive(Debug, TryUnwrap)]
|
||||||
pub enum Attr {
|
pub enum Attr {
|
||||||
@@ -185,30 +222,6 @@ pub enum Attr {
|
|||||||
Str(SymId),
|
Str(SymId),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a Nix list.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct List {
|
|
||||||
/// The expressions that are elements of the list.
|
|
||||||
pub items: Vec<ExprId>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents a "has attribute" check (`?` operator).
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct HasAttr {
|
|
||||||
/// The expression to check for the attribute (the left-hand side).
|
|
||||||
pub lhs: ExprId,
|
|
||||||
/// The attribute path to look for (the right-hand side).
|
|
||||||
pub rhs: Vec<Attr>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents a binary operation.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct BinOp {
|
|
||||||
pub lhs: ExprId,
|
|
||||||
pub rhs: ExprId,
|
|
||||||
pub kind: BinOpKind,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The kinds of binary operations supported in Nix.
|
/// The kinds of binary operations supported in Nix.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum BinOpKind {
|
pub enum BinOpKind {
|
||||||
@@ -266,13 +279,6 @@ impl From<ast::BinOpKind> for BinOpKind {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a unary operation.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct UnOp {
|
|
||||||
pub rhs: ExprId,
|
|
||||||
pub kind: UnOpKind,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The kinds of unary operations.
|
/// The kinds of unary operations.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum UnOpKind {
|
pub enum UnOpKind {
|
||||||
@@ -289,45 +295,6 @@ impl From<ast::UnaryOpKind> for UnOpKind {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents an attribute selection from an attribute set.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Select {
|
|
||||||
/// The expression that should evaluate to an attribute set.
|
|
||||||
pub expr: ExprId,
|
|
||||||
/// The path of attributes to select.
|
|
||||||
pub attrpath: Vec<Attr>,
|
|
||||||
/// An optional default value to return if the selection fails.
|
|
||||||
pub default: Option<ExprId>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents an `if-then-else` expression.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct If {
|
|
||||||
pub cond: ExprId,
|
|
||||||
pub consq: ExprId, // Consequence (then branch)
|
|
||||||
pub alter: ExprId, // Alternative (else branch)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents a function value (a lambda).
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Func {
|
|
||||||
/// The body of the function
|
|
||||||
pub body: ExprId,
|
|
||||||
/// The parameter specification for the function.
|
|
||||||
pub param: Param,
|
|
||||||
|
|
||||||
pub arg: ExprId,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents a `let ... in ...` expression.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Let {
|
|
||||||
/// The bindings in the `let` expression, group in SCCs
|
|
||||||
pub binding_sccs: SccInfo,
|
|
||||||
/// The body expression evaluated in the scope of the bindings.
|
|
||||||
pub body: ExprId,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Describes the parameters of a function.
|
/// Describes the parameters of a function.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Param {
|
pub struct Param {
|
||||||
@@ -337,50 +304,3 @@ pub struct Param {
|
|||||||
/// If `None`, any attribute is allowed (ellipsis `...` is present).
|
/// If `None`, any attribute is allowed (ellipsis `...` is present).
|
||||||
pub allowed: Option<HashSet<SymId>>,
|
pub allowed: Option<HashSet<SymId>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a function call.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Call {
|
|
||||||
/// The expression that evaluates to the function to be called.
|
|
||||||
pub func: ExprId,
|
|
||||||
pub arg: ExprId,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents an `assert` expression.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Assert {
|
|
||||||
/// The condition to assert.
|
|
||||||
pub assertion: ExprId,
|
|
||||||
/// The expression to return if the assertion is true.
|
|
||||||
pub expr: ExprId,
|
|
||||||
pub assertion_raw: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents the concatenation of multiple string expressions.
|
|
||||||
/// This is typically the result of downgrading an interpolated string.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ConcatStrings {
|
|
||||||
pub parts: Vec<ExprId>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents a simple, non-interpolated string literal.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Str {
|
|
||||||
pub val: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents a path literal.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Path {
|
|
||||||
/// The expression that evaluates to the string content of the path.
|
|
||||||
/// This can be a simple `Str` or a `ConcatStrings` for interpolated paths.
|
|
||||||
pub expr: ExprId,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents the special `builtins` global object.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Builtins;
|
|
||||||
|
|
||||||
/// Represents an attribute in `builtins`.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Builtin(pub SymId);
|
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
// Assume no parse error
|
// Assume no parse error
|
||||||
#![allow(clippy::unwrap_used)]
|
#![allow(clippy::unwrap_used)]
|
||||||
|
|
||||||
use rnix::ast::{self, Expr, HasEntry};
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use rnix::ast::{self, AstToken, Expr, HasEntry};
|
||||||
|
use rowan::ast::AstNode;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
@@ -16,7 +19,12 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for Expr {
|
|||||||
match self {
|
match self {
|
||||||
Apply(apply) => apply.downgrade(ctx),
|
Apply(apply) => apply.downgrade(ctx),
|
||||||
Assert(assert) => assert.downgrade(ctx),
|
Assert(assert) => assert.downgrade(ctx),
|
||||||
Error(error) => Err(self::Error::downgrade_error(error.to_string())),
|
Error(error) => {
|
||||||
|
let span = error.syntax().text_range();
|
||||||
|
Err(self::Error::downgrade_error(error.to_string())
|
||||||
|
.with_span(span)
|
||||||
|
.with_source(ctx.get_current_source().unwrap_or_else(|| Arc::from(""))))
|
||||||
|
}
|
||||||
IfElse(ifelse) => ifelse.downgrade(ctx),
|
IfElse(ifelse) => ifelse.downgrade(ctx),
|
||||||
Select(select) => select.downgrade(ctx),
|
Select(select) => select.downgrade(ctx),
|
||||||
Str(str) => str.downgrade(ctx),
|
Str(str) => str.downgrade(ctx),
|
||||||
@@ -44,11 +52,13 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Assert {
|
|||||||
let assertion_raw = assertion.to_string();
|
let assertion_raw = assertion.to_string();
|
||||||
let assertion = assertion.downgrade(ctx)?;
|
let assertion = assertion.downgrade(ctx)?;
|
||||||
let expr = self.body().unwrap().downgrade(ctx)?;
|
let expr = self.body().unwrap().downgrade(ctx)?;
|
||||||
|
let span = self.syntax().text_range();
|
||||||
Ok(ctx.new_expr(
|
Ok(ctx.new_expr(
|
||||||
Assert {
|
Assert {
|
||||||
assertion,
|
assertion,
|
||||||
expr,
|
expr,
|
||||||
assertion_raw,
|
assertion_raw,
|
||||||
|
span,
|
||||||
}
|
}
|
||||||
.to_ir(),
|
.to_ir(),
|
||||||
))
|
))
|
||||||
@@ -60,18 +70,29 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::IfElse {
|
|||||||
let cond = self.condition().unwrap().downgrade(ctx)?;
|
let cond = self.condition().unwrap().downgrade(ctx)?;
|
||||||
let consq = self.body().unwrap().downgrade(ctx)?;
|
let consq = self.body().unwrap().downgrade(ctx)?;
|
||||||
let alter = self.else_body().unwrap().downgrade(ctx)?;
|
let alter = self.else_body().unwrap().downgrade(ctx)?;
|
||||||
Ok(ctx.new_expr(If { cond, consq, alter }.to_ir()))
|
let span = self.syntax().text_range();
|
||||||
|
Ok(ctx.new_expr(
|
||||||
|
If {
|
||||||
|
cond,
|
||||||
|
consq,
|
||||||
|
alter,
|
||||||
|
span,
|
||||||
|
}
|
||||||
|
.to_ir(),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Path {
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Path {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
|
let span = self.syntax().text_range();
|
||||||
let parts = self
|
let parts = self
|
||||||
.parts()
|
.parts()
|
||||||
.map(|part| match part {
|
.map(|part| match part {
|
||||||
ast::InterpolPart::Literal(lit) => Ok(ctx.new_expr(
|
ast::InterpolPart::Literal(lit) => Ok(ctx.new_expr(
|
||||||
Str {
|
Str {
|
||||||
val: lit.to_string(),
|
val: lit.to_string(),
|
||||||
|
span: lit.syntax().text_range(),
|
||||||
}
|
}
|
||||||
.to_ir(),
|
.to_ir(),
|
||||||
)),
|
)),
|
||||||
@@ -84,14 +105,15 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Path {
|
|||||||
let expr = if parts.len() == 1 {
|
let expr = if parts.len() == 1 {
|
||||||
parts.into_iter().next().unwrap()
|
parts.into_iter().next().unwrap()
|
||||||
} else {
|
} else {
|
||||||
ctx.new_expr(ConcatStrings { parts }.to_ir())
|
ctx.new_expr(ConcatStrings { parts, span }.to_ir())
|
||||||
};
|
};
|
||||||
Ok(ctx.new_expr(Path { expr }.to_ir()))
|
Ok(ctx.new_expr(Path { expr, span }.to_ir()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Str {
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Str {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
|
let span = self.syntax().text_range();
|
||||||
let normalized = self.normalized_parts();
|
let normalized = self.normalized_parts();
|
||||||
let is_single_literal = normalized.len() == 1
|
let is_single_literal = normalized.len() == 1
|
||||||
&& matches!(normalized.first(), Some(ast::InterpolPart::Literal(_)));
|
&& matches!(normalized.first(), Some(ast::InterpolPart::Literal(_)));
|
||||||
@@ -99,7 +121,7 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Str {
|
|||||||
let parts = normalized
|
let parts = normalized
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|part| match part {
|
.map(|part| match part {
|
||||||
ast::InterpolPart::Literal(lit) => Ok(ctx.new_expr(Str { val: lit }.to_ir())),
|
ast::InterpolPart::Literal(lit) => Ok(ctx.new_expr(Str { val: lit, span }.to_ir())),
|
||||||
ast::InterpolPart::Interpolation(interpol) => {
|
ast::InterpolPart::Interpolation(interpol) => {
|
||||||
interpol.expr().unwrap().downgrade(ctx)
|
interpol.expr().unwrap().downgrade(ctx)
|
||||||
}
|
}
|
||||||
@@ -109,18 +131,28 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Str {
|
|||||||
Ok(if is_single_literal {
|
Ok(if is_single_literal {
|
||||||
parts.into_iter().next().unwrap()
|
parts.into_iter().next().unwrap()
|
||||||
} else {
|
} else {
|
||||||
ctx.new_expr(ConcatStrings { parts }.to_ir())
|
ctx.new_expr(ConcatStrings { parts, span }.to_ir())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Literal {
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Literal {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
|
let span = self.syntax().text_range();
|
||||||
Ok(ctx.new_expr(match self.kind() {
|
Ok(ctx.new_expr(match self.kind() {
|
||||||
ast::LiteralKind::Integer(int) => Ir::Int(int.value().unwrap()),
|
ast::LiteralKind::Integer(int) => Int {
|
||||||
ast::LiteralKind::Float(float) => Ir::Float(float.value().unwrap()),
|
inner: int.value().unwrap(),
|
||||||
|
span,
|
||||||
|
}
|
||||||
|
.to_ir(),
|
||||||
|
ast::LiteralKind::Float(float) => Float {
|
||||||
|
inner: float.value().unwrap(),
|
||||||
|
span,
|
||||||
|
}
|
||||||
|
.to_ir(),
|
||||||
ast::LiteralKind::Uri(uri) => Str {
|
ast::LiteralKind::Uri(uri) => Str {
|
||||||
val: uri.to_string(),
|
val: uri.to_string(),
|
||||||
|
span,
|
||||||
}
|
}
|
||||||
.to_ir(),
|
.to_ir(),
|
||||||
}))
|
}))
|
||||||
@@ -131,13 +163,14 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Ident {
|
|||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
let sym = self.ident_token().unwrap().to_string();
|
let sym = self.ident_token().unwrap().to_string();
|
||||||
let sym = ctx.new_sym(sym);
|
let sym = ctx.new_sym(sym);
|
||||||
ctx.lookup(sym)
|
ctx.lookup(sym, self.syntax().text_range())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::AttrSet {
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::AttrSet {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
let rec = self.rec_token().is_some();
|
let rec = self.rec_token().is_some();
|
||||||
|
let span = self.syntax().text_range();
|
||||||
|
|
||||||
if !rec {
|
if !rec {
|
||||||
let attrs = downgrade_attrs(self, ctx)?;
|
let attrs = downgrade_attrs(self, ctx)?;
|
||||||
@@ -151,17 +184,26 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::AttrSet {
|
|||||||
let mut attrs = AttrSet {
|
let mut attrs = AttrSet {
|
||||||
stcs: HashMap::new(),
|
stcs: HashMap::new(),
|
||||||
dyns: Vec::new(),
|
dyns: Vec::new(),
|
||||||
|
span,
|
||||||
};
|
};
|
||||||
|
|
||||||
for sym in binding_keys {
|
for sym in binding_keys {
|
||||||
let expr = ctx.lookup(*sym)?;
|
// FIXME: span
|
||||||
|
let expr = ctx.lookup(*sym, synthetic_span())?;
|
||||||
attrs.stcs.insert(*sym, expr);
|
attrs.stcs.insert(*sym, expr);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(ctx.new_expr(attrs.to_ir()))
|
Ok(ctx.new_expr(attrs.to_ir()))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(ctx.new_expr(Let { body, binding_sccs }.to_ir()))
|
Ok(ctx.new_expr(
|
||||||
|
Let {
|
||||||
|
body,
|
||||||
|
binding_sccs,
|
||||||
|
span,
|
||||||
|
}
|
||||||
|
.to_ir(),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,7 +214,8 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::List {
|
|||||||
.items()
|
.items()
|
||||||
.map(|item| maybe_thunk(item, ctx))
|
.map(|item| maybe_thunk(item, ctx))
|
||||||
.collect::<Result<_>>()?;
|
.collect::<Result<_>>()?;
|
||||||
Ok(ctx.new_expr(List { items }.to_ir()))
|
let span = self.syntax().text_range();
|
||||||
|
Ok(ctx.new_expr(List { items, span }.to_ir()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,7 +225,16 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::BinOp {
|
|||||||
let lhs = self.lhs().unwrap().downgrade(ctx)?;
|
let lhs = self.lhs().unwrap().downgrade(ctx)?;
|
||||||
let rhs = self.rhs().unwrap().downgrade(ctx)?;
|
let rhs = self.rhs().unwrap().downgrade(ctx)?;
|
||||||
let kind = self.operator().unwrap().into();
|
let kind = self.operator().unwrap().into();
|
||||||
Ok(ctx.new_expr(BinOp { lhs, rhs, kind }.to_ir()))
|
let span = self.syntax().text_range();
|
||||||
|
Ok(ctx.new_expr(
|
||||||
|
BinOp {
|
||||||
|
lhs,
|
||||||
|
rhs,
|
||||||
|
kind,
|
||||||
|
span,
|
||||||
|
}
|
||||||
|
.to_ir(),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,7 +243,8 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::HasAttr {
|
|||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
let lhs = self.expr().unwrap().downgrade(ctx)?;
|
let lhs = self.expr().unwrap().downgrade(ctx)?;
|
||||||
let rhs = downgrade_attrpath(self.attrpath().unwrap(), ctx)?;
|
let rhs = downgrade_attrpath(self.attrpath().unwrap(), ctx)?;
|
||||||
Ok(ctx.new_expr(HasAttr { lhs, rhs }.to_ir()))
|
let span = self.syntax().text_range();
|
||||||
|
Ok(ctx.new_expr(HasAttr { lhs, rhs, span }.to_ir()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,7 +253,8 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::UnaryOp {
|
|||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
let rhs = self.expr().unwrap().downgrade(ctx)?;
|
let rhs = self.expr().unwrap().downgrade(ctx)?;
|
||||||
let kind = self.operator().unwrap().into();
|
let kind = self.operator().unwrap().into();
|
||||||
Ok(ctx.new_expr(UnOp { rhs, kind }.to_ir()))
|
let span = self.syntax().text_range();
|
||||||
|
Ok(ctx.new_expr(UnOp { rhs, kind, span }.to_ir()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,16 +264,27 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Select {
|
|||||||
let expr = self.expr().unwrap().downgrade(ctx)?;
|
let expr = self.expr().unwrap().downgrade(ctx)?;
|
||||||
let attrpath = downgrade_attrpath(self.attrpath().unwrap(), ctx)?;
|
let attrpath = downgrade_attrpath(self.attrpath().unwrap(), ctx)?;
|
||||||
let default = if let Some(default) = self.default_expr() {
|
let default = if let Some(default) = self.default_expr() {
|
||||||
|
let span = default.syntax().text_range();
|
||||||
let default_expr = default.downgrade(ctx)?;
|
let default_expr = default.downgrade(ctx)?;
|
||||||
Some(ctx.new_expr(Ir::Thunk(default_expr)))
|
Some(
|
||||||
|
ctx.new_expr(
|
||||||
|
Thunk {
|
||||||
|
inner: default_expr,
|
||||||
|
span,
|
||||||
|
}
|
||||||
|
.to_ir(),
|
||||||
|
),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
let span = self.syntax().text_range();
|
||||||
Ok(ctx.new_expr(
|
Ok(ctx.new_expr(
|
||||||
Select {
|
Select {
|
||||||
expr,
|
expr,
|
||||||
attrpath,
|
attrpath,
|
||||||
default,
|
default,
|
||||||
|
span,
|
||||||
}
|
}
|
||||||
.to_ir(),
|
.to_ir(),
|
||||||
))
|
))
|
||||||
@@ -230,6 +295,7 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Select {
|
|||||||
/// The body of the `let` is accessed via `let.body`.
|
/// The body of the `let` is accessed via `let.body`.
|
||||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::LegacyLet {
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::LegacyLet {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
|
let span = self.syntax().text_range();
|
||||||
let bindings = downgrade_static_attrs(self, ctx)?;
|
let bindings = downgrade_static_attrs(self, ctx)?;
|
||||||
let binding_keys: Vec<_> = bindings.keys().copied().collect();
|
let binding_keys: Vec<_> = bindings.keys().copied().collect();
|
||||||
|
|
||||||
@@ -237,10 +303,12 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::LegacyLet {
|
|||||||
let mut attrs = AttrSet {
|
let mut attrs = AttrSet {
|
||||||
stcs: HashMap::new(),
|
stcs: HashMap::new(),
|
||||||
dyns: Vec::new(),
|
dyns: Vec::new(),
|
||||||
|
span,
|
||||||
};
|
};
|
||||||
|
|
||||||
for sym in binding_keys {
|
for sym in binding_keys {
|
||||||
let expr = ctx.lookup(sym)?;
|
// FIXME: span
|
||||||
|
let expr = ctx.lookup(sym, synthetic_span())?;
|
||||||
attrs.stcs.insert(sym, expr);
|
attrs.stcs.insert(sym, expr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,6 +320,7 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::LegacyLet {
|
|||||||
expr: attrset_expr,
|
expr: attrset_expr,
|
||||||
attrpath: vec![Attr::Str(body_sym)],
|
attrpath: vec![Attr::Str(body_sym)],
|
||||||
default: None,
|
default: None,
|
||||||
|
span,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(ctx.new_expr(select.to_ir()))
|
Ok(ctx.new_expr(select.to_ir()))
|
||||||
@@ -263,11 +332,19 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::LetIn {
|
|||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
let entries: Vec<_> = self.entries().collect();
|
let entries: Vec<_> = self.entries().collect();
|
||||||
let body_expr = self.body().unwrap();
|
let body_expr = self.body().unwrap();
|
||||||
|
let span = self.syntax().text_range();
|
||||||
|
|
||||||
let (binding_sccs, body) =
|
let (binding_sccs, body) =
|
||||||
downgrade_let_bindings(entries, ctx, |ctx, _binding_keys| body_expr.downgrade(ctx))?;
|
downgrade_let_bindings(entries, ctx, |ctx, _binding_keys| body_expr.downgrade(ctx))?;
|
||||||
|
|
||||||
Ok(ctx.new_expr(Let { body, binding_sccs }.to_ir()))
|
Ok(ctx.new_expr(
|
||||||
|
Let {
|
||||||
|
body,
|
||||||
|
binding_sccs,
|
||||||
|
span,
|
||||||
|
}
|
||||||
|
.to_ir(),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,13 +365,15 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::With {
|
|||||||
/// This involves desugaring pattern-matching arguments into `let` bindings.
|
/// This involves desugaring pattern-matching arguments into `let` bindings.
|
||||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Lambda {
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Lambda {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
let arg = ctx.new_arg();
|
let param = self.param().unwrap();
|
||||||
|
let arg = ctx.new_arg(param.syntax().text_range());
|
||||||
|
|
||||||
let required;
|
let required;
|
||||||
let allowed;
|
let allowed;
|
||||||
let body;
|
let body;
|
||||||
|
let span = self.body().unwrap().syntax().text_range();
|
||||||
|
|
||||||
match self.param().unwrap() {
|
match param {
|
||||||
ast::Param::IdentParam(id) => {
|
ast::Param::IdentParam(id) => {
|
||||||
// Simple case: `x: body`
|
// Simple case: `x: body`
|
||||||
let param_sym = ctx.new_sym(id.to_string());
|
let param_sym = ctx.new_sym(id.to_string());
|
||||||
@@ -334,6 +413,7 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Lambda {
|
|||||||
Let {
|
Let {
|
||||||
body: inner_body,
|
body: inner_body,
|
||||||
binding_sccs: scc_info,
|
binding_sccs: scc_info,
|
||||||
|
span,
|
||||||
}
|
}
|
||||||
.to_ir(),
|
.to_ir(),
|
||||||
);
|
);
|
||||||
@@ -341,8 +421,17 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Lambda {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let param = Param { required, allowed };
|
let param = Param { required, allowed };
|
||||||
|
let span = self.syntax().text_range();
|
||||||
// The function's body and parameters are now stored directly in the `Func` node.
|
// The function's body and parameters are now stored directly in the `Func` node.
|
||||||
Ok(ctx.new_expr(Func { body, param, arg }.to_ir()))
|
Ok(ctx.new_expr(
|
||||||
|
Func {
|
||||||
|
body,
|
||||||
|
param,
|
||||||
|
arg,
|
||||||
|
span,
|
||||||
|
}
|
||||||
|
.to_ir(),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -353,6 +442,7 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Apply {
|
|||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
let func = self.lambda().unwrap().downgrade(ctx)?;
|
let func = self.lambda().unwrap().downgrade(ctx)?;
|
||||||
let arg = maybe_thunk(self.argument().unwrap(), ctx)?;
|
let arg = maybe_thunk(self.argument().unwrap(), ctx)?;
|
||||||
Ok(ctx.new_expr(Call { func, arg }.to_ir()))
|
let span = self.syntax().text_range();
|
||||||
|
Ok(ctx.new_expr(Call { func, arg, span }.to_ir()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
20
nix-js/src/ir/span_utils.rs
Normal file
20
nix-js/src/ir/span_utils.rs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
use rnix::TextRange;
|
||||||
|
|
||||||
|
pub fn merge_spans(spans: impl IntoIterator<Item = TextRange>) -> TextRange {
|
||||||
|
let mut spans = spans.into_iter();
|
||||||
|
let first = spans.next().unwrap_or_else(|| synthetic_span());
|
||||||
|
|
||||||
|
spans.fold(first, |acc, span| {
|
||||||
|
let start = acc.start().min(span.start());
|
||||||
|
let end = acc.end().max(span.end());
|
||||||
|
TextRange::new(start, end)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn point_span() -> TextRange {
|
||||||
|
TextRange::new(0.into(), 0.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn synthetic_span() -> TextRange {
|
||||||
|
TextRange::new(0.into(), 0.into())
|
||||||
|
}
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
// Assume no parse error
|
// Assume no parse error
|
||||||
#![allow(clippy::unwrap_used)]
|
#![allow(clippy::unwrap_used)]
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use hashbrown::hash_map::Entry;
|
use hashbrown::hash_map::Entry;
|
||||||
use hashbrown::{HashMap, HashSet};
|
use hashbrown::{HashMap, HashSet};
|
||||||
use rnix::ast;
|
use rnix::ast;
|
||||||
|
use rowan::ast::AstNode;
|
||||||
|
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
use crate::ir::{Attr, AttrSet, ConcatStrings, ExprId, Ir, Select, Str, SymId};
|
use crate::ir::{Attr, AttrSet, ConcatStrings, ExprId, Ir, Select, Str, SymId};
|
||||||
@@ -21,7 +24,12 @@ pub fn maybe_thunk(mut expr: ast::Expr, ctx: &mut impl DowngradeContext) -> Resu
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
match expr {
|
match expr {
|
||||||
Error(error) => return Err(self::Error::downgrade_error(error.to_string())),
|
Error(error) => {
|
||||||
|
let span = error.syntax().text_range();
|
||||||
|
return Err(self::Error::downgrade_error(error.to_string())
|
||||||
|
.with_span(span)
|
||||||
|
.with_source(ctx.get_current_source().unwrap_or_else(|| Arc::from(""))));
|
||||||
|
}
|
||||||
Ident(ident) => return ident.downgrade(ctx),
|
Ident(ident) => return ident.downgrade(ctx),
|
||||||
Literal(lit) => return lit.downgrade(ctx),
|
Literal(lit) => return lit.downgrade(ctx),
|
||||||
Str(str) => return str.downgrade(ctx),
|
Str(str) => return str.downgrade(ctx),
|
||||||
@@ -46,19 +54,28 @@ pub fn maybe_thunk(mut expr: ast::Expr, ctx: &mut impl DowngradeContext) -> Resu
|
|||||||
|
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}?;
|
}?;
|
||||||
Ok(ctx.new_expr(Ir::Thunk(id)))
|
Ok(ctx.new_expr(
|
||||||
|
Thunk {
|
||||||
|
inner: id,
|
||||||
|
// span: ctx.get_span(id),
|
||||||
|
// FIXME: span
|
||||||
|
span: synthetic_span()
|
||||||
|
}
|
||||||
|
.to_ir(),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Downgrades the entries of an attribute set.
|
/// Downgrades the entries of an attribute set.
|
||||||
/// This handles `inherit` and `attrpath = value;` entries.
|
/// This handles `inherit` and `attrpath = value;` entries.
|
||||||
pub fn downgrade_attrs(
|
pub fn downgrade_attrs(
|
||||||
attrs: impl ast::HasEntry,
|
attrs: impl ast::HasEntry + AstNode,
|
||||||
ctx: &mut impl DowngradeContext,
|
ctx: &mut impl DowngradeContext,
|
||||||
) -> Result<AttrSet> {
|
) -> Result<AttrSet> {
|
||||||
let entries = attrs.entries();
|
let entries = attrs.entries();
|
||||||
let mut attrs = AttrSet {
|
let mut attrs = AttrSet {
|
||||||
stcs: HashMap::new(),
|
stcs: HashMap::new(),
|
||||||
dyns: Vec::new(),
|
dyns: Vec::new(),
|
||||||
|
span: attrs.syntax().text_range(),
|
||||||
};
|
};
|
||||||
|
|
||||||
for entry in entries {
|
for entry in entries {
|
||||||
@@ -75,13 +92,14 @@ pub fn downgrade_attrs(
|
|||||||
/// This is a stricter version of `downgrade_attrs` that disallows dynamic attributes,
|
/// This is a stricter version of `downgrade_attrs` that disallows dynamic attributes,
|
||||||
/// as `let` bindings must be statically known.
|
/// as `let` bindings must be statically known.
|
||||||
pub fn downgrade_static_attrs(
|
pub fn downgrade_static_attrs(
|
||||||
attrs: impl ast::HasEntry,
|
attrs: impl ast::HasEntry + AstNode,
|
||||||
ctx: &mut impl DowngradeContext,
|
ctx: &mut impl DowngradeContext,
|
||||||
) -> Result<HashMap<SymId, ExprId>> {
|
) -> Result<HashMap<SymId, ExprId>> {
|
||||||
let entries = attrs.entries();
|
let entries = attrs.entries();
|
||||||
let mut attrs = AttrSet {
|
let mut attrs = AttrSet {
|
||||||
stcs: HashMap::new(),
|
stcs: HashMap::new(),
|
||||||
dyns: Vec::new(),
|
dyns: Vec::new(),
|
||||||
|
span: attrs.syntax().text_range(),
|
||||||
};
|
};
|
||||||
|
|
||||||
for entry in entries {
|
for entry in entries {
|
||||||
@@ -111,13 +129,16 @@ pub fn downgrade_inherit(
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
for attr in inherit.attrs() {
|
for attr in inherit.attrs() {
|
||||||
|
let span = attr.syntax().text_range();
|
||||||
let ident = match downgrade_attr(attr, ctx)? {
|
let ident = match downgrade_attr(attr, ctx)? {
|
||||||
Attr::Str(ident) => ident,
|
Attr::Str(ident) => ident,
|
||||||
_ => {
|
_ => {
|
||||||
// `inherit` does not allow dynamic attributes.
|
// `inherit` does not allow dynamic attributes.
|
||||||
return Err(Error::downgrade_error(
|
return Err(Error::downgrade_error(
|
||||||
"dynamic attributes not allowed in inherit".to_string(),
|
"dynamic attributes not allowed in inherit".to_string(),
|
||||||
));
|
)
|
||||||
|
.with_span(span)
|
||||||
|
.with_source(ctx.get_current_source().unwrap_or_else(|| Arc::from(""))));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let expr = if let Some(expr) = from {
|
let expr = if let Some(expr) = from {
|
||||||
@@ -126,19 +147,28 @@ pub fn downgrade_inherit(
|
|||||||
expr,
|
expr,
|
||||||
attrpath: vec![Attr::Str(ident)],
|
attrpath: vec![Attr::Str(ident)],
|
||||||
default: None,
|
default: None,
|
||||||
|
span,
|
||||||
}
|
}
|
||||||
.to_ir(),
|
.to_ir(),
|
||||||
);
|
);
|
||||||
ctx.new_expr(Ir::Thunk(select_expr))
|
ctx.new_expr(
|
||||||
|
Thunk {
|
||||||
|
inner: select_expr,
|
||||||
|
span,
|
||||||
|
}
|
||||||
|
.to_ir(),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
ctx.lookup(ident)?
|
ctx.lookup(ident, span)?
|
||||||
};
|
};
|
||||||
match stcs.entry(ident) {
|
match stcs.entry(ident) {
|
||||||
Entry::Occupied(occupied) => {
|
Entry::Occupied(occupied) => {
|
||||||
return Err(Error::downgrade_error(format!(
|
return Err(Error::downgrade_error(format!(
|
||||||
"attribute '{}' already defined",
|
"attribute '{}' already defined",
|
||||||
format_symbol(ctx.get_sym(*occupied.key()))
|
format_symbol(ctx.get_sym(*occupied.key()))
|
||||||
)));
|
))
|
||||||
|
.with_span(span)
|
||||||
|
.with_source(ctx.get_current_source().unwrap_or_else(|| Arc::from(""))));
|
||||||
}
|
}
|
||||||
Entry::Vacant(vacant) => vacant.insert(expr),
|
Entry::Vacant(vacant) => vacant.insert(expr),
|
||||||
};
|
};
|
||||||
@@ -151,6 +181,7 @@ pub fn downgrade_inherit(
|
|||||||
pub fn downgrade_attr(attr: ast::Attr, ctx: &mut impl DowngradeContext) -> Result<Attr> {
|
pub fn downgrade_attr(attr: ast::Attr, ctx: &mut impl DowngradeContext) -> Result<Attr> {
|
||||||
use ast::Attr::*;
|
use ast::Attr::*;
|
||||||
use ast::InterpolPart::*;
|
use ast::InterpolPart::*;
|
||||||
|
let span = attr.syntax().text_range();
|
||||||
match attr {
|
match attr {
|
||||||
Ident(ident) => Ok(Attr::Str(ctx.new_sym(ident.to_string()))),
|
Ident(ident) => Ok(Attr::Str(ctx.new_sym(ident.to_string()))),
|
||||||
Str(string) => {
|
Str(string) => {
|
||||||
@@ -170,11 +201,13 @@ pub fn downgrade_attr(attr: ast::Attr, ctx: &mut impl DowngradeContext) -> Resul
|
|||||||
let parts = parts
|
let parts = parts
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|part| match part {
|
.map(|part| match part {
|
||||||
Literal(lit) => Ok(ctx.new_expr(self::Str { val: lit }.to_ir())),
|
Literal(lit) => Ok(ctx.new_expr(self::Str { val: lit, span }.to_ir())),
|
||||||
Interpolation(interpol) => interpol.expr().unwrap().downgrade(ctx),
|
Interpolation(interpol) => interpol.expr().unwrap().downgrade(ctx),
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>>>()?;
|
.collect::<Result<Vec<_>>>()?;
|
||||||
Ok(Attr::Dynamic(ctx.new_expr(ConcatStrings { parts }.to_ir())))
|
Ok(Attr::Dynamic(
|
||||||
|
ctx.new_expr(ConcatStrings { parts, span }.to_ir()),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Dynamic(dynamic) => Ok(Attr::Dynamic(dynamic.expr().unwrap().downgrade(ctx)?)),
|
Dynamic(dynamic) => Ok(Attr::Dynamic(dynamic.expr().unwrap().downgrade(ctx)?)),
|
||||||
@@ -210,11 +243,14 @@ pub fn downgrade_static_attrpathvalue(
|
|||||||
attrs: &mut AttrSet,
|
attrs: &mut AttrSet,
|
||||||
ctx: &mut impl DowngradeContext,
|
ctx: &mut impl DowngradeContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let path = downgrade_attrpath(value.attrpath().unwrap(), ctx)?;
|
let attrpath_node = value.attrpath().unwrap();
|
||||||
|
let path = downgrade_attrpath(attrpath_node.clone(), ctx)?;
|
||||||
if path.iter().any(|attr| matches!(attr, Attr::Dynamic(_))) {
|
if path.iter().any(|attr| matches!(attr, Attr::Dynamic(_))) {
|
||||||
return Err(Error::downgrade_error(
|
return Err(Error::downgrade_error(
|
||||||
"dynamic attributes not allowed in let bindings".to_string(),
|
"dynamic attributes not allowed in let bindings".to_string(),
|
||||||
));
|
)
|
||||||
|
.with_span(attrpath_node.syntax().text_range())
|
||||||
|
.with_source(ctx.get_current_source().unwrap_or_else(|| Arc::from(""))));
|
||||||
}
|
}
|
||||||
let value = value.value().unwrap().downgrade(ctx)?;
|
let value = value.value().unwrap().downgrade(ctx)?;
|
||||||
attrs.insert(path, value, ctx)
|
attrs.insert(path, value, ctx)
|
||||||
@@ -249,21 +285,26 @@ where
|
|||||||
{
|
{
|
||||||
let mut param_syms = Vec::new();
|
let mut param_syms = Vec::new();
|
||||||
let mut param_defaults = Vec::new();
|
let mut param_defaults = Vec::new();
|
||||||
|
let mut param_spans = Vec::new();
|
||||||
let mut seen_params = HashSet::new();
|
let mut seen_params = HashSet::new();
|
||||||
|
|
||||||
for entry in pat_entries {
|
for entry in pat_entries {
|
||||||
let sym = ctx.new_sym(entry.ident().unwrap().to_string());
|
let sym = ctx.new_sym(entry.ident().unwrap().to_string());
|
||||||
|
let span = entry.ident().unwrap().syntax().text_range();
|
||||||
|
|
||||||
if !seen_params.insert(sym) {
|
if !seen_params.insert(sym) {
|
||||||
return Err(Error::downgrade_error(format!(
|
return Err(Error::downgrade_error(format!(
|
||||||
"duplicate parameter '{}'",
|
"duplicate parameter '{}'",
|
||||||
format_symbol(ctx.get_sym(sym))
|
format_symbol(ctx.get_sym(sym))
|
||||||
)));
|
))
|
||||||
|
.with_span(span)
|
||||||
|
.with_source(ctx.get_current_source().unwrap_or_else(|| Arc::from(""))));
|
||||||
}
|
}
|
||||||
|
|
||||||
let default_ast = entry.default();
|
let default_ast = entry.default();
|
||||||
param_syms.push(sym);
|
param_syms.push(sym);
|
||||||
param_defaults.push(default_ast);
|
param_defaults.push(default_ast);
|
||||||
|
param_spans.push(span);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut binding_keys: Vec<SymId> = param_syms.clone();
|
let mut binding_keys: Vec<SymId> = param_syms.clone();
|
||||||
@@ -292,7 +333,11 @@ where
|
|||||||
|ctx, sym_to_slot| {
|
|ctx, sym_to_slot| {
|
||||||
let mut bindings = HashMap::new();
|
let mut bindings = HashMap::new();
|
||||||
|
|
||||||
for (sym, default_ast) in param_syms.iter().zip(param_defaults.iter()) {
|
for ((sym, default_ast), span) in param_syms
|
||||||
|
.iter()
|
||||||
|
.zip(param_defaults.iter())
|
||||||
|
.zip(param_spans.iter())
|
||||||
|
{
|
||||||
let slot = *sym_to_slot.get(sym).unwrap();
|
let slot = *sym_to_slot.get(sym).unwrap();
|
||||||
ctx.set_current_binding(Some(slot));
|
ctx.set_current_binding(Some(slot));
|
||||||
|
|
||||||
@@ -307,6 +352,7 @@ where
|
|||||||
expr: arg,
|
expr: arg,
|
||||||
attrpath: vec![Attr::Str(*sym)],
|
attrpath: vec![Attr::Str(*sym)],
|
||||||
default,
|
default,
|
||||||
|
span: *span,
|
||||||
}
|
}
|
||||||
.to_ir(),
|
.to_ir(),
|
||||||
);
|
);
|
||||||
@@ -387,12 +433,23 @@ where
|
|||||||
|
|
||||||
for (sym, slot) in binding_keys.iter().copied().zip(slots.iter()) {
|
for (sym, slot) in binding_keys.iter().copied().zip(slots.iter()) {
|
||||||
if let Some(&expr) = bindings.get(&sym) {
|
if let Some(&expr) = bindings.get(&sym) {
|
||||||
ctx.replace_expr(*slot, Ir::Thunk(expr));
|
ctx.replace_expr(
|
||||||
|
*slot,
|
||||||
|
Thunk {
|
||||||
|
inner: expr,
|
||||||
|
// span: ctx.get_span(expr),
|
||||||
|
// FIXME: span
|
||||||
|
span: synthetic_span()
|
||||||
|
}
|
||||||
|
.to_ir(),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return Err(Error::downgrade_error(format!(
|
return Err(Error::downgrade_error(format!(
|
||||||
"binding '{}' not found",
|
"binding '{}' not found",
|
||||||
format_symbol(ctx.get_sym(sym))
|
format_symbol(ctx.get_sym(sym))
|
||||||
)));
|
))
|
||||||
|
.with_span(synthetic_span())
|
||||||
|
.with_source(ctx.get_current_source().unwrap_or_else(|| Arc::from(""))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -430,7 +487,9 @@ where
|
|||||||
return Err(Error::downgrade_error(format!(
|
return Err(Error::downgrade_error(format!(
|
||||||
"attribute '{}' already defined",
|
"attribute '{}' already defined",
|
||||||
format_symbol(ctx.get_sym(sym))
|
format_symbol(ctx.get_sym(sym))
|
||||||
)));
|
))
|
||||||
|
.with_span(ident.syntax().text_range())
|
||||||
|
.with_source(ctx.get_current_source().unwrap_or_else(|| Arc::from(""))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -448,7 +507,9 @@ where
|
|||||||
return Err(Error::downgrade_error(format!(
|
return Err(Error::downgrade_error(format!(
|
||||||
"attribute '{}' already defined",
|
"attribute '{}' already defined",
|
||||||
format_symbol(ctx.get_sym(sym))
|
format_symbol(ctx.get_sym(sym))
|
||||||
)));
|
))
|
||||||
|
.with_span(ident.syntax().text_range())
|
||||||
|
.with_source(ctx.get_current_source().unwrap_or_else(|| Arc::from(""))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if attrs_vec.len() > 1 {
|
} else if attrs_vec.len() > 1 {
|
||||||
@@ -471,6 +532,7 @@ where
|
|||||||
let mut temp_attrs = AttrSet {
|
let mut temp_attrs = AttrSet {
|
||||||
stcs: HashMap::new(),
|
stcs: HashMap::new(),
|
||||||
dyns: Vec::new(),
|
dyns: Vec::new(),
|
||||||
|
span: synthetic_span()
|
||||||
};
|
};
|
||||||
|
|
||||||
for entry in entries {
|
for entry in entries {
|
||||||
|
|||||||
@@ -8,9 +8,10 @@ pub mod value;
|
|||||||
mod codegen;
|
mod codegen;
|
||||||
mod fetcher;
|
mod fetcher;
|
||||||
mod ir;
|
mod ir;
|
||||||
mod nix_hash;
|
|
||||||
mod nar;
|
mod nar;
|
||||||
|
mod nix_hash;
|
||||||
mod runtime;
|
mod runtime;
|
||||||
|
mod sourcemap;
|
||||||
mod store;
|
mod store;
|
||||||
|
|
||||||
#[global_allocator]
|
#[global_allocator]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::env;
|
use std::env;
|
||||||
use std::io::IsTerminal;
|
use std::io::IsTerminal;
|
||||||
use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Layer};
|
use tracing_subscriber::{EnvFilter, Layer, fmt, layer::SubscriberExt, util::SubscriberInitExt};
|
||||||
|
|
||||||
pub fn init_logging() {
|
pub fn init_logging() {
|
||||||
let is_terminal = std::io::stderr().is_terminal();
|
let is_terminal = std::io::stderr().is_terminal();
|
||||||
@@ -19,9 +19,7 @@ pub fn init_logging() {
|
|||||||
.with_level(true);
|
.with_level(true);
|
||||||
|
|
||||||
let fmt_layer = if show_time {
|
let fmt_layer = if show_time {
|
||||||
fmt_layer
|
fmt_layer.with_timer(fmt::time::uptime()).boxed()
|
||||||
.with_timer(fmt::time::uptime())
|
|
||||||
.boxed()
|
|
||||||
} else {
|
} else {
|
||||||
fmt_layer.without_time().boxed()
|
fmt_layer.without_time().boxed()
|
||||||
};
|
};
|
||||||
@@ -30,34 +28,20 @@ pub fn init_logging() {
|
|||||||
.with(filter)
|
.with(filter)
|
||||||
.with(fmt_layer)
|
.with(fmt_layer)
|
||||||
.init();
|
.init();
|
||||||
|
|
||||||
|
init_miette_handler();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[macro_export]
|
fn init_miette_handler() {
|
||||||
macro_rules! trace_span {
|
let is_terminal = std::io::stderr().is_terminal();
|
||||||
($name:expr) => {
|
miette::set_hook(Box::new(move |_| {
|
||||||
tracing::trace_span!($name)
|
Box::new(
|
||||||
};
|
miette::MietteHandlerOpts::new()
|
||||||
($name:expr, $($field:tt)*) => {
|
.terminal_links(is_terminal)
|
||||||
tracing::trace_span!($name, $($field)*)
|
.unicode(is_terminal)
|
||||||
};
|
.color(is_terminal)
|
||||||
}
|
.build(),
|
||||||
|
)
|
||||||
#[macro_export]
|
}))
|
||||||
macro_rules! debug_span {
|
.ok();
|
||||||
($name:expr) => {
|
|
||||||
tracing::debug_span!($name)
|
|
||||||
};
|
|
||||||
($name:expr, $($field:tt)*) => {
|
|
||||||
tracing::debug_span!($name, $($field)*)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! info_span {
|
|
||||||
($name:expr) => {
|
|
||||||
tracing::info_span!($name)
|
|
||||||
};
|
|
||||||
($name:expr, $($field:tt)*) => {
|
|
||||||
tracing::info_span!($name, $($field)*)
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::path::{Component, Path, PathBuf};
|
use std::path::{Component, Path, PathBuf};
|
||||||
use std::sync::Once;
|
use std::sync::{Arc, Once};
|
||||||
|
|
||||||
use deno_core::{Extension, ExtensionFileSource, JsRuntime, OpState, RuntimeOptions, v8};
|
use deno_core::{Extension, ExtensionFileSource, JsRuntime, OpState, RuntimeOptions, v8};
|
||||||
use deno_error::JsErrorClass;
|
use deno_error::JsErrorClass;
|
||||||
@@ -17,6 +17,7 @@ pub(crate) trait RuntimeContext: 'static {
|
|||||||
fn get_current_dir(&self) -> &Path;
|
fn get_current_dir(&self) -> &Path;
|
||||||
fn set_current_file(&mut self, path: PathBuf);
|
fn set_current_file(&mut self, path: PathBuf);
|
||||||
fn compile_code(&mut self, code: &str) -> Result<String>;
|
fn compile_code(&mut self, code: &str) -> Result<String>;
|
||||||
|
fn get_current_source(&self) -> Option<Arc<str>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn runtime_extension<Ctx: RuntimeContext>() -> Extension {
|
fn runtime_extension<Ctx: RuntimeContext>() -> Extension {
|
||||||
@@ -134,8 +135,7 @@ fn op_resolve_path(
|
|||||||
dir.push(path);
|
dir.push(path);
|
||||||
dir
|
dir
|
||||||
} else {
|
} else {
|
||||||
let mut dir = std::env::home_dir()
|
let mut dir = std::env::home_dir().ok_or("home dir not defined")?;
|
||||||
.ok_or("home dir not defined")?;
|
|
||||||
dir.push(&path[2..]);
|
dir.push(&path[2..]);
|
||||||
dir
|
dir
|
||||||
};
|
};
|
||||||
@@ -434,7 +434,49 @@ impl<Ctx: RuntimeContext> Runtime<Ctx> {
|
|||||||
let global_value = self
|
let global_value = self
|
||||||
.js_runtime
|
.js_runtime
|
||||||
.execute_script("<eval>", script)
|
.execute_script("<eval>", script)
|
||||||
.map_err(|e| Error::eval_error(format!("{}", e.get_message()), e.stack))?;
|
.map_err(|e| {
|
||||||
|
let msg = format!("{}", e.get_message());
|
||||||
|
let stack_str = e.stack.as_ref().map(|s| s.to_string());
|
||||||
|
|
||||||
|
let mut error = Error::eval_error(msg.clone(), None);
|
||||||
|
|
||||||
|
// Parse Nix stack trace frames
|
||||||
|
if let Some(ref stack) = stack_str {
|
||||||
|
let frames = crate::error::parse_nix_stack(stack);
|
||||||
|
|
||||||
|
if !frames.is_empty() {
|
||||||
|
// Get the last frame (where error occurred) for span
|
||||||
|
if let Some(last_frame) = frames.last() {
|
||||||
|
let span = last_frame.span;
|
||||||
|
error = error.with_span(span);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format stack trace (reversed, newest at bottom)
|
||||||
|
let trace_lines = crate::error::format_stack_trace(&frames);
|
||||||
|
if !trace_lines.is_empty() {
|
||||||
|
let formatted_trace = trace_lines.join("\n");
|
||||||
|
error = Error::eval_error(msg, Some(formatted_trace));
|
||||||
|
|
||||||
|
// Re-apply span after recreating error
|
||||||
|
if let Some(last_frame) = frames.last() {
|
||||||
|
let span = last_frame.span;
|
||||||
|
error = error.with_span(span);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get current source from Context
|
||||||
|
let op_state = self.js_runtime.op_state();
|
||||||
|
let op_state_borrow = op_state.borrow();
|
||||||
|
if let Some(ctx) = op_state_borrow.try_borrow::<Ctx>() {
|
||||||
|
if let Some(source) = ctx.get_current_source() {
|
||||||
|
error = error.with_source(source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
error
|
||||||
|
})?;
|
||||||
|
|
||||||
// Retrieve scope from JsRuntime
|
// Retrieve scope from JsRuntime
|
||||||
deno_core::scope!(scope, self.js_runtime);
|
deno_core::scope!(scope, self.js_runtime);
|
||||||
|
|||||||
110
nix-js/src/sourcemap.rs
Normal file
110
nix-js/src/sourcemap.rs
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
use base64::{engine::general_purpose::STANDARD, Engine};
|
||||||
|
use rnix::TextRange;
|
||||||
|
use sourcemap::{SourceMap, SourceMapBuilder};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
pub struct NixSourceMapBuilder {
|
||||||
|
builder: SourceMapBuilder,
|
||||||
|
source_name: String,
|
||||||
|
source_content: Arc<str>,
|
||||||
|
generated_code: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NixSourceMapBuilder {
|
||||||
|
pub fn new(source_name: impl Into<String>, source_content: Arc<str>) -> Self {
|
||||||
|
let mut builder = SourceMapBuilder::new(None);
|
||||||
|
let source_name = source_name.into();
|
||||||
|
builder.add_source(&source_name);
|
||||||
|
builder.set_source_contents(0, Some(&source_content));
|
||||||
|
|
||||||
|
Self {
|
||||||
|
builder,
|
||||||
|
source_name,
|
||||||
|
source_content,
|
||||||
|
generated_code: String::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_mapping(&mut self, js_offset: usize, nix_span: TextRange) {
|
||||||
|
let (js_line, js_col) = byte_to_line_col(&self.generated_code, js_offset);
|
||||||
|
let (nix_line, nix_col) = byte_to_line_col(&self.source_content, nix_span.start().into());
|
||||||
|
|
||||||
|
self.builder.add_raw(
|
||||||
|
js_line,
|
||||||
|
js_col,
|
||||||
|
nix_line,
|
||||||
|
nix_col,
|
||||||
|
Some(0),
|
||||||
|
None,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_generated_code(&mut self, code: String) {
|
||||||
|
self.generated_code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(self) -> Result<(SourceMap, String), sourcemap::Error> {
|
||||||
|
let sourcemap = self.builder.into_sourcemap();
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
sourcemap.to_writer(&mut buf)?;
|
||||||
|
|
||||||
|
let encoded = STANDARD.encode(&buf);
|
||||||
|
let data_url = format!(
|
||||||
|
"data:application/json;charset=utf-8;base64,{}",
|
||||||
|
encoded
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok((sourcemap, data_url))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn byte_to_line_col(text: &str, byte_offset: usize) -> (u32, u32) {
|
||||||
|
let mut line = 0;
|
||||||
|
let mut col = 0;
|
||||||
|
let mut current_offset = 0;
|
||||||
|
|
||||||
|
for ch in text.chars() {
|
||||||
|
if current_offset >= byte_offset {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ch == '\n' {
|
||||||
|
line += 1;
|
||||||
|
col = 0;
|
||||||
|
} else {
|
||||||
|
col += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
current_offset += ch.len_utf8();
|
||||||
|
}
|
||||||
|
|
||||||
|
(line, col)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_byte_to_line_col() {
|
||||||
|
let text = "line1\nline2\nline3";
|
||||||
|
|
||||||
|
assert_eq!(byte_to_line_col(text, 0), (0, 0));
|
||||||
|
assert_eq!(byte_to_line_col(text, 5), (0, 5));
|
||||||
|
assert_eq!(byte_to_line_col(text, 6), (1, 0));
|
||||||
|
assert_eq!(byte_to_line_col(text, 12), (2, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sourcemap_builder() {
|
||||||
|
let source = Arc::<str>::from("let x = 1; in x");
|
||||||
|
let mut builder = NixSourceMapBuilder::new("test.nix", source);
|
||||||
|
|
||||||
|
let span = TextRange::new(4.into(), 5.into());
|
||||||
|
builder.add_mapping(0, span);
|
||||||
|
|
||||||
|
let result = builder.build();
|
||||||
|
assert!(result.is_ok());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -48,7 +48,7 @@ pub trait Store: Send + Sync {
|
|||||||
pub enum StoreBackend {
|
pub enum StoreBackend {
|
||||||
Simulated(SimulatedStore),
|
Simulated(SimulatedStore),
|
||||||
#[cfg(feature = "daemon")]
|
#[cfg(feature = "daemon")]
|
||||||
Daemon(DaemonStore),
|
Daemon(Box<DaemonStore>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StoreBackend {
|
impl StoreBackend {
|
||||||
@@ -56,12 +56,14 @@ impl StoreBackend {
|
|||||||
match config.mode {
|
match config.mode {
|
||||||
#[cfg(feature = "daemon")]
|
#[cfg(feature = "daemon")]
|
||||||
StoreMode::Daemon => {
|
StoreMode::Daemon => {
|
||||||
let daemon = DaemonStore::connect(&config.daemon_socket)?;
|
let daemon = Box::new(DaemonStore::connect(&config.daemon_socket)?);
|
||||||
Ok(StoreBackend::Daemon(daemon))
|
Ok(StoreBackend::Daemon(daemon))
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "daemon"))]
|
#[cfg(not(feature = "daemon"))]
|
||||||
StoreMode::Daemon => {
|
StoreMode::Daemon => {
|
||||||
tracing::warn!("Daemon mode not available (nix-js not compiled with 'daemon' feature), falling back to simulated store");
|
tracing::warn!(
|
||||||
|
"Daemon mode not available (nix-js not compiled with 'daemon' feature), falling back to simulated store"
|
||||||
|
);
|
||||||
let simulated = SimulatedStore::new()?;
|
let simulated = SimulatedStore::new()?;
|
||||||
Ok(StoreBackend::Simulated(simulated))
|
Ok(StoreBackend::Simulated(simulated))
|
||||||
}
|
}
|
||||||
@@ -72,17 +74,11 @@ impl StoreBackend {
|
|||||||
#[cfg(feature = "daemon")]
|
#[cfg(feature = "daemon")]
|
||||||
StoreMode::Auto => match DaemonStore::connect(&config.daemon_socket) {
|
StoreMode::Auto => match DaemonStore::connect(&config.daemon_socket) {
|
||||||
Ok(daemon) => {
|
Ok(daemon) => {
|
||||||
tracing::debug!(
|
tracing::debug!("Using nix-daemon at {}", config.daemon_socket.display());
|
||||||
"Using nix-daemon at {}",
|
Ok(StoreBackend::Daemon(Box::new(daemon)))
|
||||||
config.daemon_socket.display()
|
|
||||||
);
|
|
||||||
Ok(StoreBackend::Daemon(daemon))
|
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::warn!(
|
tracing::warn!("Daemon unavailable ({}), using simulated store", e);
|
||||||
"Daemon unavailable ({}), using simulated store",
|
|
||||||
e
|
|
||||||
);
|
|
||||||
let simulated = SimulatedStore::new()?;
|
let simulated = SimulatedStore::new()?;
|
||||||
Ok(StoreBackend::Simulated(simulated))
|
Ok(StoreBackend::Simulated(simulated))
|
||||||
}
|
}
|
||||||
@@ -99,7 +95,7 @@ impl StoreBackend {
|
|||||||
match self {
|
match self {
|
||||||
StoreBackend::Simulated(s) => s,
|
StoreBackend::Simulated(s) => s,
|
||||||
#[cfg(feature = "daemon")]
|
#[cfg(feature = "daemon")]
|
||||||
StoreBackend::Daemon(d) => d,
|
StoreBackend::Daemon(d) => d.as_ref(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,10 +24,7 @@ impl StoreConfig {
|
|||||||
Ok("simulated") => StoreMode::Simulated,
|
Ok("simulated") => StoreMode::Simulated,
|
||||||
Ok("auto") | Err(_) => StoreMode::Auto,
|
Ok("auto") | Err(_) => StoreMode::Auto,
|
||||||
Ok(other) => {
|
Ok(other) => {
|
||||||
tracing::warn!(
|
tracing::warn!("Invalid NIX_JS_STORE_MODE '{}', using 'auto'", other);
|
||||||
"Invalid NIX_JS_STORE_MODE '{}', using 'auto'",
|
|
||||||
other
|
|
||||||
);
|
|
||||||
StoreMode::Auto
|
StoreMode::Auto
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use std::io::{Error as IoError, ErrorKind as IoErrorKind, Result as IoResult};
|
use std::io::{Error as IoError, ErrorKind as IoErrorKind, Result as IoResult};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
@@ -180,18 +182,12 @@ impl Store for DaemonStore {
|
|||||||
|
|
||||||
let nar_data = crate::nar::pack_nar(source_path)?;
|
let nar_data = crate::nar::pack_nar(source_path)?;
|
||||||
|
|
||||||
let nar_hash_hex = {
|
let nar_hash: [u8; 32] = {
|
||||||
let mut hasher = Sha256::new();
|
let mut hasher = Sha256::new();
|
||||||
hasher.update(&nar_data);
|
hasher.update(&nar_data);
|
||||||
hex::encode(hasher.finalize())
|
hasher.finalize().into()
|
||||||
};
|
};
|
||||||
|
let ca_hash = CAHash::Nar(NixHash::Sha256(nar_hash));
|
||||||
let nar_hash_bytes = hex::decode(&nar_hash_hex)
|
|
||||||
.map_err(|e| Error::internal(format!("Invalid nar hash: {}", e)))?;
|
|
||||||
let mut nar_hash_arr = [0u8; 32];
|
|
||||||
nar_hash_arr.copy_from_slice(&nar_hash_bytes);
|
|
||||||
|
|
||||||
let ca_hash = CAHash::Nar(NixHash::Sha256(nar_hash_arr));
|
|
||||||
|
|
||||||
let ref_store_paths: std::result::Result<Vec<StorePath<String>>, _> = references
|
let ref_store_paths: std::result::Result<Vec<StorePath<String>>, _> = references
|
||||||
.iter()
|
.iter()
|
||||||
@@ -215,7 +211,7 @@ impl Store for DaemonStore {
|
|||||||
deriver: None,
|
deriver: None,
|
||||||
nar_hash: unsafe {
|
nar_hash: unsafe {
|
||||||
std::mem::transmute::<[u8; 32], nix_compat::nix_daemon::types::NarHash>(
|
std::mem::transmute::<[u8; 32], nix_compat::nix_daemon::types::NarHash>(
|
||||||
nar_hash_arr,
|
nar_hash,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
references: ref_store_paths,
|
references: ref_store_paths,
|
||||||
@@ -258,17 +254,12 @@ impl Store for DaemonStore {
|
|||||||
|
|
||||||
let nar_data = crate::nar::pack_nar(temp_file.path())?;
|
let nar_data = crate::nar::pack_nar(temp_file.path())?;
|
||||||
|
|
||||||
let nar_hash_hex = {
|
let nar_hash: [u8; 32] = {
|
||||||
let mut hasher = Sha256::new();
|
let mut hasher = Sha256::new();
|
||||||
hasher.update(&nar_data);
|
hasher.update(&nar_data);
|
||||||
hex::encode(hasher.finalize())
|
hasher.finalize().into()
|
||||||
};
|
};
|
||||||
|
|
||||||
let nar_hash_bytes = hex::decode(&nar_hash_hex)
|
|
||||||
.map_err(|e| Error::internal(format!("Invalid nar hash: {}", e)))?;
|
|
||||||
let mut nar_hash_arr = [0u8; 32];
|
|
||||||
nar_hash_arr.copy_from_slice(&nar_hash_bytes);
|
|
||||||
|
|
||||||
let content_hash = {
|
let content_hash = {
|
||||||
let mut hasher = Sha256::new();
|
let mut hasher = Sha256::new();
|
||||||
hasher.update(content.as_bytes());
|
hasher.update(content.as_bytes());
|
||||||
@@ -296,7 +287,7 @@ impl Store for DaemonStore {
|
|||||||
deriver: None,
|
deriver: None,
|
||||||
nar_hash: unsafe {
|
nar_hash: unsafe {
|
||||||
std::mem::transmute::<[u8; 32], nix_compat::nix_daemon::types::NarHash>(
|
std::mem::transmute::<[u8; 32], nix_compat::nix_daemon::types::NarHash>(
|
||||||
nar_hash_arr,
|
nar_hash,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
references: ref_store_paths,
|
references: ref_store_paths,
|
||||||
|
|||||||
@@ -24,10 +24,6 @@ impl SimulatedStore {
|
|||||||
|
|
||||||
Ok(Self { cache, store_dir })
|
Ok(Self { cache, store_dir })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cache(&self) -> &FetcherCache {
|
|
||||||
&self.cache
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Store for SimulatedStore {
|
impl Store for SimulatedStore {
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ mod tests {
|
|||||||
let valid_paths = vec![
|
let valid_paths = vec![
|
||||||
"/nix/store/0123456789abcdfghijklmnpqrsvwxyz-hello",
|
"/nix/store/0123456789abcdfghijklmnpqrsvwxyz-hello",
|
||||||
"/nix/store/abcdfghijklmnpqrsvwxyz0123456789-hello-1.0",
|
"/nix/store/abcdfghijklmnpqrsvwxyz0123456789-hello-1.0",
|
||||||
"/nix/store/00000000000000000000000000000000-test_+-.?="
|
"/nix/store/00000000000000000000000000000000-test_+-.?=",
|
||||||
];
|
];
|
||||||
|
|
||||||
for path in valid_paths {
|
for path in valid_paths {
|
||||||
@@ -109,15 +109,36 @@ mod tests {
|
|||||||
("/nix/store/tooshort-name", "hash too short"),
|
("/nix/store/tooshort-name", "hash too short"),
|
||||||
(
|
(
|
||||||
"/nix/store/abc123defghijklmnopqrstuvwxyz123-name",
|
"/nix/store/abc123defghijklmnopqrstuvwxyz123-name",
|
||||||
"hash too long"
|
"hash too long",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"/nix/store/abcd1234abcd1234abcd1234abcd123e-name",
|
||||||
|
"e in hash",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"/nix/store/abcd1234abcd1234abcd1234abcd123o-name",
|
||||||
|
"o in hash",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"/nix/store/abcd1234abcd1234abcd1234abcd123u-name",
|
||||||
|
"u in hash",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"/nix/store/abcd1234abcd1234abcd1234abcd123t-name",
|
||||||
|
"t in hash",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"/nix/store/abcd1234abcd1234abcd1234abcd1234-.name",
|
||||||
|
"name starts with dot",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"/nix/store/abcd1234abcd1234abcd1234abcd1234-na/me",
|
||||||
|
"slash in name",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"/nix/store/abcd1234abcd1234abcd1234abcd1234",
|
||||||
|
"missing name",
|
||||||
),
|
),
|
||||||
("/nix/store/abcd1234abcd1234abcd1234abcd123e-name", "e in hash"),
|
|
||||||
("/nix/store/abcd1234abcd1234abcd1234abcd123o-name", "o in hash"),
|
|
||||||
("/nix/store/abcd1234abcd1234abcd1234abcd123u-name", "u in hash"),
|
|
||||||
("/nix/store/abcd1234abcd1234abcd1234abcd123t-name", "t in hash"),
|
|
||||||
("/nix/store/abcd1234abcd1234abcd1234abcd1234-.name", "name starts with dot"),
|
|
||||||
("/nix/store/abcd1234abcd1234abcd1234abcd1234-na/me", "slash in name"),
|
|
||||||
("/nix/store/abcd1234abcd1234abcd1234abcd1234", "missing name"),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
for (path, reason) in invalid_paths {
|
for (path, reason) in invalid_paths {
|
||||||
|
|||||||
@@ -9,9 +9,13 @@ fn init() {
|
|||||||
static INIT: Once = Once::new();
|
static INIT: Once = Once::new();
|
||||||
INIT.call_once(|| {
|
INIT.call_once(|| {
|
||||||
#[cfg(not(feature = "daemon"))]
|
#[cfg(not(feature = "daemon"))]
|
||||||
unsafe { std::env::set_var("NIX_JS_STORE_MODE", "simulated") };
|
unsafe {
|
||||||
|
std::env::set_var("NIX_JS_STORE_MODE", "simulated")
|
||||||
|
};
|
||||||
#[cfg(feature = "daemon")]
|
#[cfg(feature = "daemon")]
|
||||||
unsafe { std::env::set_var("NIX_JS_STORE_MODE", "daemon") };
|
unsafe {
|
||||||
|
std::env::set_var("NIX_JS_STORE_MODE", "daemon")
|
||||||
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user