From 2cb85529c98242961263dfe4ba25dcd6710a56b6 Mon Sep 17 00:00:00 2001 From: imxyy_soope_ Date: Tue, 20 Jan 2026 18:54:19 +0800 Subject: [PATCH] feat: NamedSource --- nix-js/benches/utils.rs | 17 +++- nix-js/runtime-ts/src/helpers.ts | 9 +- nix-js/src/bin/eval.rs | 5 +- nix-js/src/bin/repl.rs | 4 +- nix-js/src/codegen.rs | 27 ++++-- nix-js/src/context.rs | 61 +++++-------- nix-js/src/error.rs | 128 ++++++++++++++++++--------- nix-js/src/fetcher/metadata_cache.rs | 1 - nix-js/src/ir.rs | 5 +- nix-js/src/ir/downgrade.rs | 4 +- nix-js/src/ir/span_utils.rs | 2 +- nix-js/src/ir/utils.rs | 26 +++--- nix-js/src/lib.rs | 1 - nix-js/src/runtime.rs | 28 +++--- nix-js/src/sourcemap.rs | 110 ----------------------- nix-js/src/store/daemon.rs | 8 +- nix-js/tests/io_operations.rs | 54 +++++------ nix-js/tests/string_context.rs | 17 +++- nix-js/tests/utils.rs | 10 ++- 19 files changed, 225 insertions(+), 292 deletions(-) delete mode 100644 nix-js/src/sourcemap.rs diff --git a/nix-js/benches/utils.rs b/nix-js/benches/utils.rs index d45290f..13e03d5 100644 --- a/nix-js/benches/utils.rs +++ b/nix-js/benches/utils.rs @@ -1,16 +1,25 @@ #![allow(dead_code)] use nix_js::context::Context; +use nix_js::error::{Result, Source}; use nix_js::value::Value; pub fn eval(expr: &str) -> Value { - Context::new().unwrap().eval_code(expr).unwrap() + Context::new() + .unwrap() + .eval_code(Source::new_eval(expr.into()).unwrap()) + .unwrap() } -pub fn eval_result(expr: &str) -> Result { - Context::new().unwrap().eval_code(expr) +pub fn eval_result(expr: &str) -> Result { + Context::new() + .unwrap() + .eval_code(Source::new_eval(expr.into()).unwrap()) } pub fn compile(expr: &str) -> String { - Context::new().unwrap().compile_code(expr).unwrap() + Context::new() + .unwrap() + .compile_code(Source::new_eval(expr.into()).unwrap()) + .unwrap() } diff --git a/nix-js/runtime-ts/src/helpers.ts b/nix-js/runtime-ts/src/helpers.ts index 9060a38..7918050 100644 --- a/nix-js/runtime-ts/src/helpers.ts +++ b/nix-js/runtime-ts/src/helpers.ts @@ -28,8 +28,6 @@ function enrichError(error: unknown): Error { 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}`; }); @@ -356,11 +354,14 @@ function call_impl(func: NixValue, arg: NixValue): NixValue { 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, span: string): NixValue => { if (forceBool(assertion)) { return expr; } - throw new CatchableError(`assertion '${assertionRaw}' failed`); + withContext("while evaluating assertion", span, () => { + throw new CatchableError(`assertion '${assertionRaw}' failed`); + }); + throw "unreachable"; }; export const ifFunc = (cond: NixValue, consq: NixValue, alter: NixValue) => { diff --git a/nix-js/src/bin/eval.rs b/nix-js/src/bin/eval.rs index 3113520..508dc68 100644 --- a/nix-js/src/bin/eval.rs +++ b/nix-js/src/bin/eval.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use nix_js::context::Context; +use nix_js::{context::Context, error::Source}; use std::process::exit; fn main() -> Result<()> { @@ -12,7 +12,8 @@ fn main() -> Result<()> { } args.next(); let expr = args.next().unwrap(); - match Context::new()?.eval_code(&expr) { + let src = Source::new_eval(expr)?; + match Context::new()?.eval_code(src) { Ok(value) => { println!("{value}"); Ok(()) diff --git a/nix-js/src/bin/repl.rs b/nix-js/src/bin/repl.rs index 13c4c14..d95dbd4 100644 --- a/nix-js/src/bin/repl.rs +++ b/nix-js/src/bin/repl.rs @@ -1,5 +1,6 @@ use anyhow::Result; use nix_js::context::Context; +use nix_js::error::Source; use regex::Regex; use rustyline::DefaultEditor; use rustyline::error::ReadlineError; @@ -31,7 +32,8 @@ fn main() -> Result<()> { eprintln!("Error: {}", err); } */ } else { - match context.eval_code(&line) { + let src = Source::new_repl(line)?; + match context.eval_code(src) { Ok(value) => println!("{value}"), Err(err) => eprintln!("{:?}", miette::Report::new(err)), } diff --git a/nix-js/src/codegen.rs b/nix-js/src/codegen.rs index 013d3bb..d01f904 100644 --- a/nix-js/src/codegen.rs +++ b/nix-js/src/codegen.rs @@ -61,7 +61,11 @@ impl EscapeQuote for str { } fn encode_span(span: rnix::TextRange) -> String { - format!("\"{}:{}\"", usize::from(span.start()), usize::from(span.end())) + format!( + "\"{}:{}\"", + usize::from(span.start()), + usize::from(span.end()) + ) } impl Compile for Ir { @@ -78,7 +82,10 @@ impl Compile for Ir { format!("Nix.resolvePath(currentDir,{})", path_expr) } &Ir::If(If { - cond, consq, alter, span + cond, + consq, + alter, + span: _, }) => { let cond_code = ctx.get_ir(cond).compile(ctx); let consq = ctx.get_ir(consq).compile(ctx); @@ -129,14 +136,21 @@ impl Compile for Ir { // Only add context tracking if STACK_TRACE is enabled if std::env::var("NIX_JS_STACK_TRACE").is_ok() { let assertion_span = encode_span(ctx.get_ir(assertion).span()); + let span = encode_span(span); format!( - "Nix.assert(Nix.withContext(\"while evaluating the condition of the assert statement\",{},()=>({})),{},{})", - assertion_span, assertion_code, expr, assertion_raw.escape_quote() + "Nix.assert(Nix.withContext(\"while evaluating the condition of the assert statement\",{},()=>({})),{},{},{})", + assertion_span, + assertion_code, + expr, + assertion_raw.escape_quote(), + span ) } else { format!( "Nix.assert({},{},{})", - assertion_code, expr, assertion_raw.escape_quote() + assertion_code, + expr, + assertion_raw.escape_quote() ) } } @@ -410,8 +424,7 @@ impl Compile for ConcatStrings { let parts: Vec = self .parts .iter() - .enumerate() - .map(|(_idx, part)| { + .map(|part| { let part_code = ctx.get_ir(*part).compile(ctx); if stack_trace_enabled { let part_span = encode_span(ctx.get_ir(*part).span()); diff --git a/nix-js/src/context.rs b/nix-js/src/context.rs index bfce895..798d60a 100644 --- a/nix-js/src/context.rs +++ b/nix-js/src/context.rs @@ -1,4 +1,4 @@ -use std::path::{Path, PathBuf}; +use std::path::Path; use std::ptr::NonNull; use hashbrown::{HashMap, HashSet}; @@ -8,13 +8,12 @@ use rnix::TextRange; use string_interner::DefaultStringInterner; use crate::codegen::{CodegenContext, compile}; -use crate::error::{Error, Result}; +use crate::error::{Error, Result, Source}; 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::sourcemap::NixSourceMapBuilder; use crate::store::{StoreBackend, StoreConfig}; use crate::value::Value; use std::sync::Arc; @@ -42,13 +41,13 @@ mod private { fn get_current_dir(&self) -> &Path { self.as_ref().get_current_dir() } - fn set_current_file(&mut self, path: PathBuf) { - self.as_mut().current_file = Some(path); + fn set_current_file(&mut self, source: Source) { + self.as_mut().current_file = Some(source); } - fn compile_code(&mut self, expr: &str) -> Result { - self.as_mut().compile_code(expr) + fn compile_code(&mut self, source: Source) -> Result { + self.as_mut().compile_code(source) } - fn get_current_source(&self) -> Option> { + fn get_current_source(&self) -> Option { self.as_ref().get_current_source() } } @@ -82,18 +81,12 @@ impl Context { }) } - pub fn eval_code(&mut self, expr: &str) -> Result { + pub fn eval_code(&mut self, source: Source) -> Result { tracing::info!("Starting evaluation"); - self.ctx.current_file = Some( - std::env::current_dir() - .map_err(|err| { - Error::internal(format!("Failed to get current working dir: {err}")) - })? - .join("__eval__.nix"), - ); + self.ctx.current_file = Some(source.clone()); tracing::debug!("Compiling code"); - let code = self.compile_code(expr)?; + let code = self.compile_code(source)?; self.runtime.op_state().borrow_mut().put(self.store.clone()); @@ -102,8 +95,8 @@ impl Context { .eval(format!("Nix.force({code})"), CtxPtr::new(&mut self.ctx)) } - pub fn compile_code(&mut self, expr: &str) -> Result { - self.ctx.compile_code(expr) + pub fn compile_code(&mut self, source: Source) -> Result { + self.ctx.compile_code(source) } #[allow(dead_code)] @@ -120,10 +113,8 @@ pub(crate) struct Ctx { irs: Vec, symbols: DefaultStringInterner, global: NonNull>, - current_file: Option, - source_map: HashMap>, - current_source: Option>, - js_source_maps: HashMap, + current_file: Option, + current_source: Option, } impl Default for Ctx { @@ -216,9 +207,7 @@ impl Default for Ctx { irs, global: unsafe { NonNull::new_unchecked(Box::leak(Box::new(global))) }, current_file: None, - source_map: HashMap::new(), current_source: None, - js_source_maps: HashMap::new(), } } } @@ -237,27 +226,19 @@ impl Ctx { self.current_file .as_ref() .expect("current_file is not set") - .parent() - .expect("current_file doesn't have a parent dir") + .get_dir() } - pub(crate) fn get_current_source(&self) -> Option> { + pub(crate) fn get_current_source(&self) -> Option { self.current_source.clone() } - fn compile_code(&mut self, expr: &str) -> Result { + fn compile_code(&mut self, source: Source) -> Result { tracing::debug!("Parsing Nix expression"); - // Store source text for error reporting - let source: Arc = 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(&source.src); if !root.errors().is_empty() { let error_msg = root.errors().iter().join("; "); let err = Error::parse_error(error_msg).with_source(source); @@ -450,7 +431,7 @@ impl DowngradeContext for DowngradeCtx<'_> { 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(""))) + .with_source(self.get_current_source().expect("no source set")) }) } @@ -475,13 +456,13 @@ impl DowngradeContext for DowngradeCtx<'_> { 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() + 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> { + fn get_current_source(&self) -> Option { self.ctx.current_source.clone() } diff --git a/nix-js/src/error.rs b/nix-js/src/error.rs index dcbb07c..e6029fe 100644 --- a/nix-js/src/error.rs +++ b/nix-js/src/error.rs @@ -1,16 +1,83 @@ -use miette::{Diagnostic, LabeledSpan, SourceSpan}; -use std::sync::Arc; +use miette::{Diagnostic, NamedSource, SourceSpan}; +use std::{ + path::{Path, PathBuf}, + sync::Arc, +}; use thiserror::Error; pub type Result = core::result::Result; +#[derive(Clone, Debug)] +pub enum SourceType { + /// dir + Eval(Arc), + /// dir + Repl(Arc), + /// file + File(Arc), +} + +#[derive(Clone, Debug)] +pub struct Source { + pub ty: SourceType, + pub src: Arc, +} + +impl TryFrom<&str> for Source { + type Error = Error; + fn try_from(value: &str) -> Result { + Source::new_eval(value.into()) + } +} + +impl From for NamedSource> { + fn from(value: Source) -> Self { + let name = match value.ty { + SourceType::Eval(_) => "«eval»".into(), + SourceType::Repl(_) => "«repl»".into(), + SourceType::File(path) => path.as_os_str().to_string_lossy().to_string(), + }; + NamedSource::new(name, value.src.clone()) + } +} + +impl Source { + pub fn new_eval(src: String) -> Result { + Ok(Self { + ty: std::env::current_dir() + .map_err(|err| Error::internal(format!("Failed to get current working dir: {err}"))) + .map(Arc::new) + .map(SourceType::Eval)?, + src: src.into(), + }) + } + + pub fn new_repl(src: String) -> Result { + Ok(Self { + ty: std::env::current_dir() + .map_err(|err| Error::internal(format!("Failed to get current working dir: {err}"))) + .map(Arc::new) + .map(SourceType::Repl)?, + src: src.into(), + }) + } + + pub fn get_dir(&self) -> &Path { + use SourceType::*; + match &self.ty { + Eval(dir) | Repl(dir) => dir.as_ref(), + File(file) => file.as_path().parent().unwrap(), + } + } +} + #[derive(Error, Debug, Diagnostic)] pub enum Error { #[error("Parse error: {message}")] #[diagnostic(code(nix::parse))] ParseError { #[source_code] - src: Option>, + src: Option>>, #[label("error occurred here")] span: Option, message: String, @@ -20,19 +87,17 @@ pub enum Error { #[diagnostic(code(nix::downgrade))] DowngradeError { #[source_code] - src: Option>, + src: Option>>, #[label("{message}")] span: Option, message: String, - // #[related] - // related: Vec, }, #[error("Evaluation error: {message}")] #[diagnostic(code(nix::eval))] EvalError { #[source_code] - src: Option>, + src: Option>>, #[label("error occurred here")] span: Option, message: String, @@ -67,19 +132,9 @@ impl Error { src: None, span: None, message: msg, - // related: Vec::new(), } } - // pub fn downgrade_error_with_related(msg: String, related: Vec) -> Self { - // Error::DowngradeError { - // src: None, - // span: None, - // message: msg, - // related, - // } - // } - pub fn eval_error(msg: String, backtrace: Option) -> Self { Error::EvalError { src: None, @@ -109,16 +164,10 @@ impl Error { span: source_span, message, }, - Error::DowngradeError { - src, - message, - // related, - .. - } => Error::DowngradeError { + Error::DowngradeError { src, message, .. } => Error::DowngradeError { src, span: source_span, message, - // related, }, Error::EvalError { src, @@ -135,21 +184,13 @@ impl Error { } } - pub fn with_source(self, source: Arc) -> Self { - let src = Some(source); + pub fn with_source(self, source: Source) -> Self { + let src = Some(source.into()); match self { Error::ParseError { span, message, .. } => Error::ParseError { src, span, message }, - Error::DowngradeError { - span, - message, - // related, - .. - } => Error::DowngradeError { - src, - span, - message, - // related, - }, + Error::DowngradeError { span, message, .. } => { + Error::DowngradeError { src, span, message } + } Error::EvalError { span, message, @@ -207,10 +248,7 @@ pub(crate) fn parse_nix_stack(stack: &str) -> Vec { Err(_) => continue, }; - let span = rnix::TextRange::new( - rnix::TextSize::from(start), - rnix::TextSize::from(end) - ); + 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 { @@ -248,8 +286,12 @@ pub(crate) fn format_stack_trace(frames: &[NixStackFrame]) -> Vec { // 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.push(format!( + "{} at {}:{}", + frame.message, + usize::from(frame.span.start()), + usize::from(frame.span.end()) + )); } lines diff --git a/nix-js/src/fetcher/metadata_cache.rs b/nix-js/src/fetcher/metadata_cache.rs index bf80473..c9b7144 100644 --- a/nix-js/src/fetcher/metadata_cache.rs +++ b/nix-js/src/fetcher/metadata_cache.rs @@ -1,6 +1,5 @@ use rusqlite::{Connection, OptionalExtension, params}; use serde::{Deserialize, Serialize}; -use serde_json; use std::path::PathBuf; use std::time::{SystemTime, UNIX_EPOCH}; diff --git a/nix-js/src/ir.rs b/nix-js/src/ir.rs index 2338329..e451fba 100644 --- a/nix-js/src/ir.rs +++ b/nix-js/src/ir.rs @@ -1,11 +1,10 @@ use derive_more::{IsVariant, TryUnwrap, Unwrap}; use hashbrown::{HashMap, HashSet}; use rnix::{TextRange, ast}; -use std::sync::Arc; use string_interner::symbol::SymbolU32; use crate::context::SccInfo; -use crate::error::{Error, Result}; +use crate::error::{Error, Result, Source}; use crate::value::format_symbol; use nix_js_macros::ir; @@ -32,7 +31,7 @@ pub trait DowngradeContext { fn replace_expr(&mut self, id: ExprId, expr: Ir); fn reserve_slots(&mut self, slots: usize) -> impl Iterator + Clone + use; fn get_span(&self, id: ExprId) -> TextRange; - fn get_current_source(&self) -> Option>; + fn get_current_source(&self) -> Option; fn with_param_scope(&mut self, param: SymId, arg: ExprId, f: F) -> R where diff --git a/nix-js/src/ir/downgrade.rs b/nix-js/src/ir/downgrade.rs index 8e0fe86..7dee934 100644 --- a/nix-js/src/ir/downgrade.rs +++ b/nix-js/src/ir/downgrade.rs @@ -1,8 +1,6 @@ // Assume no parse error #![allow(clippy::unwrap_used)] -use std::sync::Arc; - use rnix::ast::{self, AstToken, Expr, HasEntry}; use rowan::ast::AstNode; @@ -23,7 +21,7 @@ impl Downgrade for Expr { 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("")))) + .with_source(ctx.get_current_source().expect("no source set"))) } IfElse(ifelse) => ifelse.downgrade(ctx), Select(select) => select.downgrade(ctx), diff --git a/nix-js/src/ir/span_utils.rs b/nix-js/src/ir/span_utils.rs index d7ed2fe..bcf1c67 100644 --- a/nix-js/src/ir/span_utils.rs +++ b/nix-js/src/ir/span_utils.rs @@ -2,7 +2,7 @@ use rnix::TextRange; pub fn merge_spans(spans: impl IntoIterator) -> TextRange { let mut spans = spans.into_iter(); - let first = spans.next().unwrap_or_else(|| synthetic_span()); + let first = spans.next().unwrap_or_else(synthetic_span); spans.fold(first, |acc, span| { let start = acc.start().min(span.start()); diff --git a/nix-js/src/ir/utils.rs b/nix-js/src/ir/utils.rs index a1287da..34a96be 100644 --- a/nix-js/src/ir/utils.rs +++ b/nix-js/src/ir/utils.rs @@ -1,15 +1,13 @@ // Assume no parse error #![allow(clippy::unwrap_used)] -use std::sync::Arc; - use hashbrown::hash_map::Entry; use hashbrown::{HashMap, HashSet}; use rnix::ast; use rowan::ast::AstNode; use crate::error::{Error, Result}; -use crate::ir::{Attr, AttrSet, ConcatStrings, ExprId, Ir, Select, Str, SymId}; +use crate::ir::{Attr, AttrSet, ConcatStrings, ExprId, Select, Str, SymId}; use crate::value::format_symbol; use super::*; @@ -28,7 +26,7 @@ pub fn maybe_thunk(mut expr: ast::Expr, ctx: &mut impl DowngradeContext) -> Resu 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("")))); + .with_source(ctx.get_current_source().expect("no source set"))); } Ident(ident) => return ident.downgrade(ctx), Literal(lit) => return lit.downgrade(ctx), @@ -59,7 +57,7 @@ pub fn maybe_thunk(mut expr: ast::Expr, ctx: &mut impl DowngradeContext) -> Resu inner: id, // span: ctx.get_span(id), // FIXME: span - span: synthetic_span() + span: synthetic_span(), } .to_ir(), )) @@ -138,7 +136,7 @@ pub fn downgrade_inherit( "dynamic attributes not allowed in inherit".to_string(), ) .with_span(span) - .with_source(ctx.get_current_source().unwrap_or_else(|| Arc::from("")))); + .with_source(ctx.get_current_source().expect("no source set"))); } }; let expr = if let Some(expr) = from { @@ -168,7 +166,7 @@ pub fn downgrade_inherit( format_symbol(ctx.get_sym(*occupied.key())) )) .with_span(span) - .with_source(ctx.get_current_source().unwrap_or_else(|| Arc::from("")))); + .with_source(ctx.get_current_source().expect("no source set"))); } Entry::Vacant(vacant) => vacant.insert(expr), }; @@ -250,7 +248,7 @@ pub fn downgrade_static_attrpathvalue( "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("")))); + .with_source(ctx.get_current_source().expect("no source set"))); } let value = value.value().unwrap().downgrade(ctx)?; attrs.insert(path, value, ctx) @@ -298,7 +296,7 @@ where format_symbol(ctx.get_sym(sym)) )) .with_span(span) - .with_source(ctx.get_current_source().unwrap_or_else(|| Arc::from("")))); + .with_source(ctx.get_current_source().expect("no source set"))); } let default_ast = entry.default(); @@ -439,7 +437,7 @@ where inner: expr, // span: ctx.get_span(expr), // FIXME: span - span: synthetic_span() + span: synthetic_span(), } .to_ir(), ); @@ -449,7 +447,7 @@ where format_symbol(ctx.get_sym(sym)) )) .with_span(synthetic_span()) - .with_source(ctx.get_current_source().unwrap_or_else(|| Arc::from("")))); + .with_source(ctx.get_current_source().expect("no source set"))); } } @@ -489,7 +487,7 @@ where format_symbol(ctx.get_sym(sym)) )) .with_span(ident.syntax().text_range()) - .with_source(ctx.get_current_source().unwrap_or_else(|| Arc::from("")))); + .with_source(ctx.get_current_source().expect("no source set"))); } } } @@ -509,7 +507,7 @@ where format_symbol(ctx.get_sym(sym)) )) .with_span(ident.syntax().text_range()) - .with_source(ctx.get_current_source().unwrap_or_else(|| Arc::from("")))); + .with_source(ctx.get_current_source().expect("no source set"))); } } } else if attrs_vec.len() > 1 { @@ -532,7 +530,7 @@ where let mut temp_attrs = AttrSet { stcs: HashMap::new(), dyns: Vec::new(), - span: synthetic_span() + span: synthetic_span(), }; for entry in entries { diff --git a/nix-js/src/lib.rs b/nix-js/src/lib.rs index 27ea6c8..2f729c1 100644 --- a/nix-js/src/lib.rs +++ b/nix-js/src/lib.rs @@ -11,7 +11,6 @@ mod ir; mod nar; mod nix_hash; mod runtime; -mod sourcemap; mod store; #[global_allocator] diff --git a/nix-js/src/runtime.rs b/nix-js/src/runtime.rs index 24e47be..5d997ea 100644 --- a/nix-js/src/runtime.rs +++ b/nix-js/src/runtime.rs @@ -1,12 +1,12 @@ use std::borrow::Cow; use std::marker::PhantomData; use std::path::{Component, Path, PathBuf}; -use std::sync::{Arc, Once}; +use std::sync::Once; use deno_core::{Extension, ExtensionFileSource, JsRuntime, OpState, RuntimeOptions, v8}; use deno_error::JsErrorClass; -use crate::error::{Error, Result}; +use crate::error::{Error, Result, Source}; use crate::value::{AttrSet, List, Symbol, Value}; type ScopeRef<'p, 's> = v8::PinnedRef<'p, v8::HandleScope<'s>>; @@ -15,9 +15,9 @@ type LocalSymbol<'a> = v8::Local<'a, v8::Symbol>; pub(crate) trait RuntimeContext: 'static { fn get_current_dir(&self) -> &Path; - fn set_current_file(&mut self, path: PathBuf); - fn compile_code(&mut self, code: &str) -> Result; - fn get_current_source(&self) -> Option>; + fn set_current_file(&mut self, path: Source); + fn compile_code(&mut self, source: Source) -> Result; + fn get_current_source(&self) -> Option; } fn runtime_extension() -> Extension { @@ -96,13 +96,17 @@ fn op_import( tracing::info!("Importing file: {}", absolute_path.display()); - let content = std::fs::read_to_string(&absolute_path) + let content = std::fs::read_to_string(absolute_path.as_path()) .map_err(|e| format!("Failed to read {}: {}", absolute_path.display(), e))?; + let source = Source { + ty: crate::error::SourceType::File(absolute_path.into()), + src: content.into(), + }; tracing::debug!("Compiling file"); - ctx.set_current_file(absolute_path); + ctx.set_current_file(source.clone()); - Ok(ctx.compile_code(&content).map_err(|err| err.to_string())?) + Ok(ctx.compile_code(source).map_err(|err| err.to_string())?) } #[deno_core::op2] @@ -467,10 +471,10 @@ impl Runtime { // 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::() { - if let Some(source) = ctx.get_current_source() { - error = error.with_source(source); - } + if let Some(ctx) = op_state_borrow.try_borrow::() + && let Some(source) = ctx.get_current_source() + { + error = error.with_source(source); } } } diff --git a/nix-js/src/sourcemap.rs b/nix-js/src/sourcemap.rs deleted file mode 100644 index 03c6896..0000000 --- a/nix-js/src/sourcemap.rs +++ /dev/null @@ -1,110 +0,0 @@ -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, - generated_code: String, -} - -impl NixSourceMapBuilder { - pub fn new(source_name: impl Into, source_content: Arc) -> 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::::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()); - } -} diff --git a/nix-js/src/store/daemon.rs b/nix-js/src/store/daemon.rs index e78c499..60a5a57 100644 --- a/nix-js/src/store/daemon.rs +++ b/nix-js/src/store/daemon.rs @@ -210,9 +210,7 @@ impl Store for DaemonStore { path: store_path, deriver: None, nar_hash: unsafe { - std::mem::transmute::<[u8; 32], nix_compat::nix_daemon::types::NarHash>( - nar_hash, - ) + std::mem::transmute::<[u8; 32], nix_compat::nix_daemon::types::NarHash>(nar_hash) }, references: ref_store_paths, registration_time: 0, @@ -286,9 +284,7 @@ impl Store for DaemonStore { path: store_path, deriver: None, nar_hash: unsafe { - std::mem::transmute::<[u8; 32], nix_compat::nix_daemon::types::NarHash>( - nar_hash, - ) + std::mem::transmute::<[u8; 32], nix_compat::nix_daemon::types::NarHash>(nar_hash) }, references: ref_store_paths, registration_time: 0, diff --git a/nix-js/tests/io_operations.rs b/nix-js/tests/io_operations.rs index dc9875c..d6f3341 100644 --- a/nix-js/tests/io_operations.rs +++ b/nix-js/tests/io_operations.rs @@ -1,25 +1,24 @@ mod utils; use nix_js::context::Context; +use nix_js::error::Source; use nix_js::value::Value; +use crate::utils::{eval, eval_result}; + #[test] fn import_absolute_path() { - let mut ctx = Context::new().unwrap(); - let temp_dir = tempfile::tempdir().unwrap(); let lib_path = temp_dir.path().join("nix_test_lib.nix"); std::fs::write(&lib_path, "{ add = a: b: a + b; }").unwrap(); let expr = format!(r#"(import "{}").add 3 5"#, lib_path.display()); - assert_eq!(ctx.eval_code(&expr).unwrap(), Value::Int(8)); + assert_eq!(eval(&expr), Value::Int(8)); } #[test] fn import_nested() { - let mut ctx = Context::new().unwrap(); - let temp_dir = tempfile::tempdir().unwrap(); let lib_path = temp_dir.path().join("lib.nix"); @@ -33,13 +32,11 @@ fn import_nested() { std::fs::write(&main_path, main_content).unwrap(); let expr = format!(r#"(import "{}").result"#, main_path.display()); - assert_eq!(ctx.eval_code(&expr).unwrap(), Value::Int(30)); + assert_eq!(eval(&expr), Value::Int(30)); } #[test] fn import_relative_path() { - let mut ctx = Context::new().unwrap(); - let temp_dir = tempfile::tempdir().unwrap(); let subdir = temp_dir.path().join("subdir"); std::fs::create_dir_all(&subdir).unwrap(); @@ -63,27 +60,24 @@ fn import_relative_path() { std::fs::write(&main_path, main_content).unwrap(); let expr = format!(r#"let x = import "{}"; in x.result1"#, main_path.display()); - assert_eq!(ctx.eval_code(&expr).unwrap(), Value::Int(12)); + assert_eq!(eval(&expr), Value::Int(12)); let expr = format!(r#"let x = import "{}"; in x.result2"#, main_path.display()); - assert_eq!(ctx.eval_code(&expr).unwrap(), Value::Int(7)); + assert_eq!(eval(&expr), Value::Int(7)); } #[test] fn import_returns_function() { - let mut ctx = Context::new().unwrap(); - let temp_dir = tempfile::tempdir().unwrap(); let func_path = temp_dir.path().join("nix_test_func.nix"); std::fs::write(&func_path, "x: x * 2").unwrap(); let expr = format!(r#"(import "{}") 5"#, func_path.display()); - assert_eq!(ctx.eval_code(&expr).unwrap(), Value::Int(10)); + assert_eq!(eval(&expr), Value::Int(10)); } #[test] fn import_with_complex_dependency_graph() { - let mut ctx = Context::new().unwrap(); let temp_dir = tempfile::tempdir().unwrap(); let utils_path = temp_dir.path().join("utils.nix"); @@ -98,7 +92,7 @@ fn import_with_complex_dependency_graph() { std::fs::write(&main_path, main_content).unwrap(); let expr = format!(r#"import "{}""#, main_path.display()); - assert_eq!(ctx.eval_code(&expr).unwrap(), Value::Int(15)); + assert_eq!(eval(&expr), Value::Int(15)); } // Tests for builtins.path @@ -111,7 +105,7 @@ fn path_with_file() { std::fs::write(&test_file, "Hello, World!").unwrap(); let expr = format!(r#"builtins.path {{ path = {}; }}"#, test_file.display()); - let result = ctx.eval_code(&expr).unwrap(); + let result = ctx.eval_code(Source::new_eval(expr).unwrap()).unwrap(); // Should return a store path string if let Value::String(store_path) = result { @@ -124,7 +118,6 @@ fn path_with_file() { #[test] fn path_with_custom_name() { - let mut ctx = Context::new().unwrap(); let temp_dir = tempfile::tempdir().unwrap(); let test_file = temp_dir.path().join("original.txt"); std::fs::write(&test_file, "Content").unwrap(); @@ -133,7 +126,7 @@ fn path_with_custom_name() { r#"builtins.path {{ path = {}; name = "custom-name"; }}"#, test_file.display() ); - let result = ctx.eval_code(&expr).unwrap(); + let result = eval(&expr); if let Value::String(store_path) = result { assert!(store_path.contains("custom-name")); @@ -156,7 +149,7 @@ fn path_with_directory_recursive() { r#"builtins.path {{ path = {}; recursive = true; }}"#, test_dir.display() ); - let result = ctx.eval_code(&expr).unwrap(); + let result = ctx.eval_code(Source::new_eval(expr).unwrap()).unwrap(); if let Value::String(store_path) = result { assert!(store_path.starts_with(ctx.get_store_dir())); @@ -177,7 +170,7 @@ fn path_flat_with_file() { r#"builtins.path {{ path = {}; recursive = false; }}"#, test_file.display() ); - let result = ctx.eval_code(&expr).unwrap(); + let result = ctx.eval_code(Source::new_eval(expr).unwrap()).unwrap(); if let Value::String(store_path) = result { assert!(store_path.starts_with(ctx.get_store_dir())); @@ -188,7 +181,6 @@ fn path_flat_with_file() { #[test] fn path_flat_with_directory_fails() { - let mut ctx = Context::new().unwrap(); let temp_dir = tempfile::tempdir().unwrap(); let test_dir = temp_dir.path().join("mydir"); std::fs::create_dir_all(&test_dir).unwrap(); @@ -197,7 +189,7 @@ fn path_flat_with_directory_fails() { r#"builtins.path {{ path = {}; recursive = false; }}"#, test_dir.display() ); - let result = ctx.eval_code(&expr); + let result = eval_result(&expr); assert!(result.is_err()); let err_msg = result.unwrap_err().to_string(); @@ -206,10 +198,8 @@ fn path_flat_with_directory_fails() { #[test] fn path_nonexistent_fails() { - let mut ctx = Context::new().unwrap(); - let expr = r#"builtins.path { path = "/nonexistent/path/that/should/not/exist"; }"#; - let result = ctx.eval_code(expr); + let result = eval_result(expr); assert!(result.is_err()); let err_msg = result.unwrap_err().to_string(); @@ -218,10 +208,8 @@ fn path_nonexistent_fails() { #[test] fn path_missing_path_param() { - let mut ctx = Context::new().unwrap(); - let expr = r#"builtins.path { name = "test"; }"#; - let result = ctx.eval_code(expr); + let result = eval_result(expr); assert!(result.is_err()); let err_msg = result.unwrap_err().to_string(); @@ -230,14 +218,13 @@ fn path_missing_path_param() { #[test] fn path_with_sha256() { - let mut ctx = Context::new().unwrap(); let temp_dir = tempfile::tempdir().unwrap(); let test_file = temp_dir.path().join("hash_test.txt"); std::fs::write(&test_file, "Test content for hashing").unwrap(); // First, get the hash by calling without sha256 let expr1 = format!(r#"builtins.path {{ path = {}; }}"#, test_file.display()); - let result1 = ctx.eval_code(&expr1).unwrap(); + let result1 = eval(&expr1); let store_path1 = match result1 { Value::String(s) => s, _ => panic!("Expected string"), @@ -246,7 +233,7 @@ fn path_with_sha256() { // Compute the actual hash (for testing, we'll just verify the same path is returned) // In real usage, the user would know the hash beforehand let expr2 = format!(r#"builtins.path {{ path = {}; }}"#, test_file.display()); - let result2 = ctx.eval_code(&expr2).unwrap(); + let result2 = eval(&expr2); let store_path2 = match result2 { Value::String(s) => s, _ => panic!("Expected string"), @@ -258,7 +245,6 @@ fn path_with_sha256() { #[test] fn path_deterministic() { - let mut ctx = Context::new().unwrap(); let temp_dir = tempfile::tempdir().unwrap(); let test_file = temp_dir.path().join("deterministic.txt"); std::fs::write(&test_file, "Same content").unwrap(); @@ -268,8 +254,8 @@ fn path_deterministic() { test_file.display() ); - let result1 = ctx.eval_code(&expr).unwrap(); - let result2 = ctx.eval_code(&expr).unwrap(); + let result1 = eval(&expr); + let result2 = eval(&expr); // Same inputs should produce same store path assert_eq!(result1, result2); diff --git a/nix-js/tests/string_context.rs b/nix-js/tests/string_context.rs index 8f2bb29..10d6ae6 100644 --- a/nix-js/tests/string_context.rs +++ b/nix-js/tests/string_context.rs @@ -1,9 +1,12 @@ +mod utils; + use nix_js::context::Context; use nix_js::value::Value; +use utils::eval_result; fn eval(expr: &str) -> Value { let mut ctx = Context::new().unwrap(); - ctx.eval_code(expr).unwrap_or_else(|e| panic!("{}", e)) + eval_result(expr).unwrap_or_else(|e| panic!("{}", e)) } #[test] @@ -165,7 +168,9 @@ fn context_in_derivation_args() { args = [ ((builtins.toString dep) + "/bin/run") ]; }; in drv.drvPath - "#, + "# + .try_into() + .unwrap(), ) .unwrap(); match result { @@ -192,7 +197,9 @@ fn context_in_derivation_env() { myDep = builtins.toString dep; }; in drv.drvPath - "#, + "# + .try_into() + .unwrap(), ) .unwrap(); match result { @@ -226,7 +233,9 @@ fn interpolation_derivation_returns_outpath() { let drv = derivation { name = "test"; builder = "/bin/sh"; system = "x86_64-linux"; }; in "${drv}" - "#, + "# + .try_into() + .unwrap(), ) .unwrap(); match result { diff --git a/nix-js/tests/utils.rs b/nix-js/tests/utils.rs index 5eafd8f..554c4ed 100644 --- a/nix-js/tests/utils.rs +++ b/nix-js/tests/utils.rs @@ -1,12 +1,18 @@ #![allow(dead_code)] use nix_js::context::Context; +use nix_js::error::Source; use nix_js::value::Value; pub fn eval(expr: &str) -> Value { - Context::new().unwrap().eval_code(expr).unwrap() + Context::new() + .unwrap() + .eval_code(Source::new_eval(expr.into()).unwrap()) + .unwrap() } pub fn eval_result(expr: &str) -> Result { - Context::new().unwrap().eval_code(expr) + Context::new() + .unwrap() + .eval_code(Source::new_eval(expr.into()).unwrap()) }