refactor: error handling
This commit is contained in:
4
Justfile
4
Justfile
@@ -8,8 +8,8 @@
|
||||
|
||||
[no-exit-message]
|
||||
@replr:
|
||||
cargo run --bin repl --release
|
||||
RUST_LOG=info cargo run --bin repl --release
|
||||
|
||||
[no-exit-message]
|
||||
@evalr expr:
|
||||
cargo run --bin eval --release -- '{{expr}}'
|
||||
RUST_LOG=info cargo run --bin eval --release -- '{{expr}}'
|
||||
|
||||
18
flake.lock
generated
18
flake.lock
generated
@@ -8,11 +8,11 @@
|
||||
"rust-analyzer-src": "rust-analyzer-src"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1767250179,
|
||||
"narHash": "sha256-PnQdWvPZqHp+7yaHWDFX3NYSKaOy0fjkwpR+rIQC7AY=",
|
||||
"lastModified": 1768892055,
|
||||
"narHash": "sha256-zatCoDgFd0C8YEOztMeBcom6cka0GqJGfc0aAXvpktc=",
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"rev": "a3eaf682db8800962943a77ab77c0aae966f9825",
|
||||
"rev": "81d6a7547e090f7e760b95b9cc534461f6045e43",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -37,11 +37,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1767116409,
|
||||
"narHash": "sha256-5vKw92l1GyTnjoLzEagJy5V5mDFck72LiQWZSOnSicw=",
|
||||
"lastModified": 1768886240,
|
||||
"narHash": "sha256-C2TjvwYZ2VDxYWeqvvJ5XPPp6U7H66zeJlRaErJKoEM=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "cad22e7d996aea55ecab064e84834289143e44a0",
|
||||
"rev": "80e4adbcf8992d3fd27ad4964fbb84907f9478b0",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -61,11 +61,11 @@
|
||||
"rust-analyzer-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1767191410,
|
||||
"narHash": "sha256-cCZGjubgDWmstvFkS6eAw2qk2ihgWkycw55u2dtLd70=",
|
||||
"lastModified": 1768816483,
|
||||
"narHash": "sha256-bXeWgVkvxN76QEw12OaWFbRhO1yt+5QETz/BxBX4dk0=",
|
||||
"owner": "rust-lang",
|
||||
"repo": "rust-analyzer",
|
||||
"rev": "a9026e6d5068172bf5a0d52a260bb290961d1cb4",
|
||||
"rev": "1b8952b49fa10cae9020f0e46d0b8938563a6b64",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -37,10 +37,12 @@ export const throwFunc = (s: NixValue): never => {
|
||||
throw new CatchableError(coerceToString(s, StringCoercionMode.Base));
|
||||
};
|
||||
|
||||
export const trace = (e1: NixValue, e2: NixValue): NixValue => {
|
||||
console.log(`trace: ${force(e1)}`);
|
||||
return e2;
|
||||
};
|
||||
export const trace =
|
||||
(e1: NixValue) =>
|
||||
(e2: NixValue): NixValue => {
|
||||
console.log(`trace: ${coerceToString(e1, StringCoercionMode.Base)}`);
|
||||
return e2;
|
||||
};
|
||||
|
||||
export const warn =
|
||||
(e1: NixValue) =>
|
||||
|
||||
@@ -29,11 +29,11 @@ function enrichError(error: unknown): Error {
|
||||
}
|
||||
|
||||
const nixStackLines = callStack.map((frame) => {
|
||||
return `NIX_STACK_FRAME:context:${frame.span}:${frame.message}`;
|
||||
return `NIX_STACK_FRAME:${frame.span}:${frame.message}`;
|
||||
});
|
||||
|
||||
// Prepend stack frames to error stack
|
||||
err.stack = `${nixStackLines.join('\n')}\n${err.stack || ''}`;
|
||||
err.stack = `${nixStackLines.join("\n")}\n${err.stack || ""}`;
|
||||
|
||||
return err;
|
||||
}
|
||||
@@ -176,9 +176,9 @@ export const resolvePath = (currentDir: string, path: NixValue): NixPath => {
|
||||
|
||||
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';
|
||||
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();
|
||||
@@ -215,11 +215,16 @@ function select_impl(obj: NixValue, attrpath: NixValue[]): NixValue {
|
||||
return attrs[last];
|
||||
}
|
||||
|
||||
export const selectWithDefault = (obj: NixValue, attrpath: NixValue[], default_val: NixValue, span?: string): NixValue => {
|
||||
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';
|
||||
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();
|
||||
@@ -324,7 +329,7 @@ export const call = (func: NixValue, arg: NixValue, span?: string): NixValue =>
|
||||
if (callStack.length >= MAX_STACK_DEPTH) {
|
||||
callStack.shift();
|
||||
}
|
||||
callStack.push({ span, message: 'from call site' });
|
||||
callStack.push({ span, message: "from call site" });
|
||||
try {
|
||||
return call_impl(func, arg);
|
||||
} catch (error) {
|
||||
|
||||
@@ -19,7 +19,7 @@ fn main() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("{:?}", miette::Report::new(err));
|
||||
eprintln!("{:?}", miette::Report::new(*err));
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ fn main() -> Result<()> {
|
||||
let src = Source::new_repl(line)?;
|
||||
match context.eval_code(src) {
|
||||
Ok(value) => println!("{value}"),
|
||||
Err(err) => eprintln!("{:?}", miette::Report::new(err)),
|
||||
Err(err) => eprintln!("{:?}", miette::Report::new(*err)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ pub(crate) trait CodegenContext {
|
||||
fn get_ir(&self, id: ExprId) -> &Ir;
|
||||
fn get_sym(&self, id: SymId) -> &str;
|
||||
fn get_current_dir(&self) -> &Path;
|
||||
fn get_current_source_id(&self) -> usize;
|
||||
}
|
||||
|
||||
trait EscapeQuote {
|
||||
@@ -60,9 +61,10 @@ impl EscapeQuote for str {
|
||||
}
|
||||
}
|
||||
|
||||
fn encode_span(span: rnix::TextRange) -> String {
|
||||
fn encode_span(span: rnix::TextRange, ctx: &impl CodegenContext) -> String {
|
||||
format!(
|
||||
"\"{}:{}\"",
|
||||
"\"{}:{}:{}\"",
|
||||
ctx.get_current_source_id(),
|
||||
usize::from(span.start()),
|
||||
usize::from(span.end())
|
||||
)
|
||||
@@ -93,7 +95,7 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Ir {
|
||||
|
||||
// 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());
|
||||
let cond_span = encode_span(ctx.get_ir(cond).span(), ctx);
|
||||
format!(
|
||||
"(Nix.withContext(\"while evaluating a branch condition\",{},()=>({})))?({}):({})",
|
||||
cond_span, cond_code, consq, alter
|
||||
@@ -135,8 +137,8 @@ impl<Ctx: CodegenContext> Compile<Ctx> 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);
|
||||
let assertion_span = encode_span(ctx.get_ir(assertion).span(), ctx);
|
||||
let span = encode_span(span, ctx);
|
||||
format!(
|
||||
"Nix.assert(Nix.withContext(\"while evaluating the condition of the assert statement\",{},()=>({})),{},{},{})",
|
||||
assertion_span,
|
||||
@@ -171,7 +173,7 @@ impl<Ctx: CodegenContext> Compile<Ctx> for BinOp {
|
||||
// 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);
|
||||
let span = encode_span(self.span, ctx);
|
||||
format!(
|
||||
"Nix.withContext(\"while evaluating the {} operator\",{},()=>({}))",
|
||||
op_name, span, op_call
|
||||
@@ -274,7 +276,7 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Call {
|
||||
fn compile(&self, ctx: &Ctx) -> String {
|
||||
let func = ctx.get_ir(self.func).compile(ctx);
|
||||
let arg = ctx.get_ir(self.arg).compile(ctx);
|
||||
let span_str = encode_span(self.span);
|
||||
let span_str = encode_span(self.span, ctx);
|
||||
format!("Nix.call({func},{arg},{span_str})")
|
||||
}
|
||||
}
|
||||
@@ -338,7 +340,7 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Select {
|
||||
Attr::Dynamic(expr_id) => ctx.get_ir(*expr_id).compile(ctx),
|
||||
})
|
||||
.join(",");
|
||||
let span_str = encode_span(self.span);
|
||||
let span_str = encode_span(self.span, ctx);
|
||||
if let Some(default) = self.default {
|
||||
format!(
|
||||
"Nix.selectWithDefault({lhs},[{attrpath}],{},{span_str})",
|
||||
@@ -360,7 +362,7 @@ impl<Ctx: CodegenContext> Compile<Ctx> for AttrSet {
|
||||
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());
|
||||
let value_span = encode_span(ctx.get_ir(expr).span(), ctx);
|
||||
format!(
|
||||
"Nix.withContext(\"while evaluating the attribute '{}'\",{},()=>({}))",
|
||||
key, value_span, value_code
|
||||
@@ -377,7 +379,7 @@ impl<Ctx: CodegenContext> Compile<Ctx> for AttrSet {
|
||||
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());
|
||||
let value_span = encode_span(ctx.get_ir(*value_expr).span(), ctx);
|
||||
format!(
|
||||
"Nix.withContext(\"while evaluating a dynamic attribute\",{},()=>({}))",
|
||||
value_span, value_code
|
||||
@@ -403,7 +405,7 @@ impl<Ctx: CodegenContext> Compile<Ctx> for List {
|
||||
.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());
|
||||
let item_span = encode_span(ctx.get_ir(*item).span(), ctx);
|
||||
format!(
|
||||
"Nix.withContext(\"while evaluating list element {}\",{},()=>({}))",
|
||||
idx, item_span, item_code
|
||||
@@ -427,7 +429,7 @@ impl<Ctx: CodegenContext> Compile<Ctx> for ConcatStrings {
|
||||
.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());
|
||||
let part_span = encode_span(ctx.get_ir(*part).span(), ctx);
|
||||
format!(
|
||||
"Nix.withContext(\"while evaluating a path segment\",{},()=>({}))",
|
||||
part_span, part_code
|
||||
|
||||
@@ -41,15 +41,18 @@ mod private {
|
||||
fn get_current_dir(&self) -> &Path {
|
||||
self.as_ref().get_current_dir()
|
||||
}
|
||||
fn set_current_file(&mut self, source: Source) {
|
||||
self.as_mut().current_file = Some(source);
|
||||
fn add_source(&mut self, source: Source) {
|
||||
self.as_mut().sources.push(source);
|
||||
}
|
||||
fn compile_code(&mut self, source: Source) -> Result<String> {
|
||||
self.as_mut().compile_code(source)
|
||||
}
|
||||
fn get_current_source(&self) -> Option<Source> {
|
||||
fn get_current_source(&self) -> Source {
|
||||
self.as_ref().get_current_source()
|
||||
}
|
||||
fn get_source(&self, id: usize) -> Source {
|
||||
self.as_ref().get_source(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
use private::CtxPtr;
|
||||
@@ -83,7 +86,6 @@ impl Context {
|
||||
|
||||
pub fn eval_code(&mut self, source: Source) -> Result<Value> {
|
||||
tracing::info!("Starting evaluation");
|
||||
self.ctx.current_file = Some(source.clone());
|
||||
|
||||
tracing::debug!("Compiling code");
|
||||
let code = self.compile_code(source)?;
|
||||
@@ -113,8 +115,7 @@ pub(crate) struct Ctx {
|
||||
irs: Vec<Ir>,
|
||||
symbols: DefaultStringInterner,
|
||||
global: NonNull<HashMap<SymId, ExprId>>,
|
||||
current_file: Option<Source>,
|
||||
current_source: Option<Source>,
|
||||
sources: Vec<Source>,
|
||||
}
|
||||
|
||||
impl Default for Ctx {
|
||||
@@ -206,8 +207,7 @@ impl Default for Ctx {
|
||||
symbols,
|
||||
irs,
|
||||
global: unsafe { NonNull::new_unchecked(Box::leak(Box::new(global))) },
|
||||
current_file: None,
|
||||
current_source: None,
|
||||
sources: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -223,20 +223,28 @@ impl Ctx {
|
||||
}
|
||||
|
||||
pub(crate) fn get_current_dir(&self) -> &Path {
|
||||
self.current_file
|
||||
self.sources
|
||||
.last()
|
||||
.as_ref()
|
||||
.expect("current_file is not set")
|
||||
.expect("current_source is not set")
|
||||
.get_dir()
|
||||
}
|
||||
|
||||
pub(crate) fn get_current_source(&self) -> Option<Source> {
|
||||
self.current_source.clone()
|
||||
pub(crate) fn get_current_source(&self) -> Source {
|
||||
self.sources
|
||||
.last()
|
||||
.expect("current_source is not set")
|
||||
.clone()
|
||||
}
|
||||
|
||||
pub(crate) fn get_source(&self, id: usize) -> Source {
|
||||
self.sources.get(id).expect("source not found").clone()
|
||||
}
|
||||
|
||||
fn compile_code(&mut self, source: Source) -> Result<String> {
|
||||
tracing::debug!("Parsing Nix expression");
|
||||
|
||||
self.current_source = Some(source.clone());
|
||||
self.sources.push(source.clone());
|
||||
|
||||
let root = rnix::Root::parse(&source.src);
|
||||
if !root.errors().is_empty() {
|
||||
@@ -267,6 +275,12 @@ impl CodegenContext for Ctx {
|
||||
fn get_current_dir(&self) -> &std::path::Path {
|
||||
self.get_current_dir()
|
||||
}
|
||||
fn get_current_source_id(&self) -> usize {
|
||||
self.sources
|
||||
.len()
|
||||
.checked_sub(1)
|
||||
.expect("current_source not set")
|
||||
}
|
||||
}
|
||||
|
||||
struct DependencyTracker {
|
||||
@@ -431,7 +445,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().expect("no source set"))
|
||||
.with_source(self.get_current_source())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -453,17 +467,8 @@ impl DowngradeContext for DowngradeCtx<'_> {
|
||||
.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<Source> {
|
||||
self.ctx.current_source.clone()
|
||||
fn get_current_source(&self) -> Source {
|
||||
self.ctx.get_current_source()
|
||||
}
|
||||
|
||||
#[allow(refining_impl_trait)]
|
||||
|
||||
@@ -5,7 +5,9 @@ use std::{
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
pub type Result<T> = core::result::Result<T, Error>;
|
||||
use crate::{context::Ctx, runtime::RuntimeContext};
|
||||
|
||||
pub type Result<T> = core::result::Result<T, Box<Error>>;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum SourceType {
|
||||
@@ -24,7 +26,7 @@ pub struct Source {
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for Source {
|
||||
type Error = Error;
|
||||
type Error = Box<Error>;
|
||||
fn try_from(value: &str) -> Result<Self> {
|
||||
Source::new_eval(value.into())
|
||||
}
|
||||
@@ -66,7 +68,7 @@ impl Source {
|
||||
use SourceType::*;
|
||||
match &self.ty {
|
||||
Eval(dir) | Repl(dir) => dir.as_ref(),
|
||||
File(file) => file.as_path().parent().unwrap(),
|
||||
File(file) => file.as_path().parent().expect("source file must have a parent dir"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -101,7 +103,7 @@ pub enum Error {
|
||||
#[label("error occurred here")]
|
||||
span: Option<SourceSpan>,
|
||||
message: String,
|
||||
// #[help]
|
||||
#[help]
|
||||
js_backtrace: Option<String>,
|
||||
},
|
||||
|
||||
@@ -119,91 +121,64 @@ pub enum Error {
|
||||
}
|
||||
|
||||
impl Error {
|
||||
pub fn parse_error(msg: String) -> Self {
|
||||
pub fn parse_error(msg: String) -> Box<Self> {
|
||||
Error::ParseError {
|
||||
src: None,
|
||||
span: None,
|
||||
message: msg,
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn downgrade_error(msg: String) -> Self {
|
||||
pub fn downgrade_error(msg: String) -> Box<Self> {
|
||||
Error::DowngradeError {
|
||||
src: None,
|
||||
span: None,
|
||||
message: msg,
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn eval_error(msg: String, backtrace: Option<String>) -> Self {
|
||||
pub fn eval_error(msg: String, backtrace: Option<String>) -> Box<Self> {
|
||||
Error::EvalError {
|
||||
src: None,
|
||||
span: None,
|
||||
message: msg,
|
||||
js_backtrace: backtrace,
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn internal(msg: String) -> Self {
|
||||
Error::InternalError { message: msg }
|
||||
pub fn internal(msg: String) -> Box<Self> {
|
||||
Error::InternalError { message: msg }.into()
|
||||
}
|
||||
|
||||
pub fn catchable(msg: String) -> Self {
|
||||
Error::Catchable { message: msg }
|
||||
pub fn catchable(msg: String) -> Box<Self> {
|
||||
Error::Catchable { message: msg }.into()
|
||||
}
|
||||
|
||||
pub fn unknown() -> Self {
|
||||
Error::Unknown
|
||||
}
|
||||
|
||||
pub fn with_span(self, span: rnix::TextRange) -> Self {
|
||||
pub fn with_span(mut self: Box<Self>, span: rnix::TextRange) -> Box<Self> {
|
||||
use Error::*;
|
||||
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, .. } => Error::DowngradeError {
|
||||
src,
|
||||
span: source_span,
|
||||
message,
|
||||
},
|
||||
Error::EvalError {
|
||||
src,
|
||||
message,
|
||||
js_backtrace,
|
||||
..
|
||||
} => Error::EvalError {
|
||||
src,
|
||||
span: source_span,
|
||||
message,
|
||||
js_backtrace,
|
||||
},
|
||||
other => other,
|
||||
}
|
||||
let (ParseError { span, .. } | DowngradeError { span, .. } | EvalError { span, .. }) =
|
||||
self.as_mut()
|
||||
else {
|
||||
return self;
|
||||
};
|
||||
*span = source_span;
|
||||
self
|
||||
}
|
||||
|
||||
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, .. } => {
|
||||
Error::DowngradeError { src, span, message }
|
||||
}
|
||||
Error::EvalError {
|
||||
span,
|
||||
message,
|
||||
js_backtrace,
|
||||
..
|
||||
} => Error::EvalError {
|
||||
src,
|
||||
span,
|
||||
message,
|
||||
js_backtrace,
|
||||
},
|
||||
other => other,
|
||||
}
|
||||
pub fn with_source(mut self: Box<Self>, source: Source) -> Box<Self> {
|
||||
use Error::*;
|
||||
let new_src = Some(source.into());
|
||||
let (ParseError { src, .. } | DowngradeError { src, .. } | EvalError { src, .. }) =
|
||||
self.as_mut()
|
||||
else {
|
||||
return self;
|
||||
};
|
||||
*src = new_src;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,27 +193,27 @@ pub fn text_range_to_source_span(range: rnix::TextRange) -> SourceSpan {
|
||||
pub(crate) struct NixStackFrame {
|
||||
pub span: rnix::TextRange,
|
||||
pub message: String,
|
||||
pub source: Source,
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
pub(crate) fn parse_nix_stack(stack: &str, ctx: &impl RuntimeContext) -> Vec<NixStackFrame> {
|
||||
let mut frames = Vec::new();
|
||||
|
||||
for line in stack.lines() {
|
||||
if !line.starts_with("NIX_STACK_FRAME:") {
|
||||
// Format: NIX_STACK_FRAME:start:end[:extra_data]
|
||||
let Some(rest) = line.strip_prefix("NIX_STACK_FRAME:") else {
|
||||
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 source = match parts[0].parse() {
|
||||
Ok(id) => ctx.get_source(id),
|
||||
Err(_) => continue,
|
||||
};
|
||||
let start: u32 = match parts[1].parse() {
|
||||
Ok(v) => v,
|
||||
Err(_) => continue,
|
||||
@@ -250,28 +225,15 @@ pub(crate) fn parse_nix_stack(stack: &str) -> Vec<NixStackFrame> {
|
||||
|
||||
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)
|
||||
}
|
||||
let message = {
|
||||
if parts.len() == 4 {
|
||||
parts[3].to_string()
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
"context" => {
|
||||
if parts.len() >= 4 {
|
||||
parts[3].to_string()
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
}
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
frames.push(NixStackFrame { span, message });
|
||||
frames.push(NixStackFrame { span, message, source });
|
||||
}
|
||||
|
||||
// Deduplicate consecutive identical frames
|
||||
@@ -279,20 +241,3 @@ pub(crate) fn parse_nix_stack(stack: &str) -> Vec<NixStackFrame> {
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ impl CacheEntry {
|
||||
|
||||
let now = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.expect("Clock may have gone backwards")
|
||||
.as_secs();
|
||||
|
||||
now > self.timestamp + ttl_seconds
|
||||
@@ -180,7 +180,7 @@ impl MetadataCache {
|
||||
let info_str = serde_json::to_string(info)?;
|
||||
let timestamp = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.expect("Clock may have gone backwards")
|
||||
.as_secs();
|
||||
|
||||
self.conn.execute(
|
||||
@@ -202,7 +202,7 @@ impl MetadataCache {
|
||||
let input_str = serde_json::to_string(input)?;
|
||||
let timestamp = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.expect("Clock may have gone backwards")
|
||||
.as_secs();
|
||||
|
||||
self.conn.execute(
|
||||
|
||||
@@ -30,8 +30,7 @@ pub trait DowngradeContext {
|
||||
fn extract_expr(&mut self, id: ExprId) -> 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 get_span(&self, id: ExprId) -> TextRange;
|
||||
fn get_current_source(&self) -> Option<Source>;
|
||||
fn get_current_source(&self) -> Source;
|
||||
|
||||
fn with_param_scope<F, R>(&mut self, param: SymId, arg: ExprId, f: F) -> R
|
||||
where
|
||||
|
||||
@@ -21,7 +21,7 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> 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().expect("no source set")))
|
||||
.with_source(ctx.get_current_source()))
|
||||
}
|
||||
IfElse(ifelse) => ifelse.downgrade(ctx),
|
||||
Select(select) => select.downgrade(ctx),
|
||||
@@ -310,7 +310,7 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::LegacyLet {
|
||||
attrs.stcs.insert(sym, expr);
|
||||
}
|
||||
|
||||
Ok(ctx.new_expr(attrs.to_ir()))
|
||||
Result::Ok(ctx.new_expr(attrs.to_ir()))
|
||||
})?;
|
||||
|
||||
let body_sym = ctx.new_sym("body".to_string());
|
||||
|
||||
@@ -26,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().expect("no source set")));
|
||||
.with_source(ctx.get_current_source()));
|
||||
}
|
||||
Ident(ident) => return ident.downgrade(ctx),
|
||||
Literal(lit) => return lit.downgrade(ctx),
|
||||
@@ -136,7 +136,7 @@ pub fn downgrade_inherit(
|
||||
"dynamic attributes not allowed in inherit".to_string(),
|
||||
)
|
||||
.with_span(span)
|
||||
.with_source(ctx.get_current_source().expect("no source set")));
|
||||
.with_source(ctx.get_current_source()));
|
||||
}
|
||||
};
|
||||
let expr = if let Some(expr) = from {
|
||||
@@ -166,7 +166,7 @@ pub fn downgrade_inherit(
|
||||
format_symbol(ctx.get_sym(*occupied.key()))
|
||||
))
|
||||
.with_span(span)
|
||||
.with_source(ctx.get_current_source().expect("no source set")));
|
||||
.with_source(ctx.get_current_source()));
|
||||
}
|
||||
Entry::Vacant(vacant) => vacant.insert(expr),
|
||||
};
|
||||
@@ -248,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().expect("no source set")));
|
||||
.with_source(ctx.get_current_source()));
|
||||
}
|
||||
let value = value.value().unwrap().downgrade(ctx)?;
|
||||
attrs.insert(path, value, ctx)
|
||||
@@ -296,7 +296,7 @@ where
|
||||
format_symbol(ctx.get_sym(sym))
|
||||
))
|
||||
.with_span(span)
|
||||
.with_source(ctx.get_current_source().expect("no source set")));
|
||||
.with_source(ctx.get_current_source()));
|
||||
}
|
||||
|
||||
let default_ast = entry.default();
|
||||
@@ -447,7 +447,7 @@ where
|
||||
format_symbol(ctx.get_sym(sym))
|
||||
))
|
||||
.with_span(synthetic_span())
|
||||
.with_source(ctx.get_current_source().expect("no source set")));
|
||||
.with_source(ctx.get_current_source()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -487,7 +487,7 @@ where
|
||||
format_symbol(ctx.get_sym(sym))
|
||||
))
|
||||
.with_span(ident.syntax().text_range())
|
||||
.with_source(ctx.get_current_source().expect("no source set")));
|
||||
.with_source(ctx.get_current_source()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -507,7 +507,7 @@ where
|
||||
format_symbol(ctx.get_sym(sym))
|
||||
))
|
||||
.with_span(ident.syntax().text_range())
|
||||
.with_source(ctx.get_current_source().expect("no source set")));
|
||||
.with_source(ctx.get_current_source()));
|
||||
}
|
||||
}
|
||||
} else if attrs_vec.len() > 1 {
|
||||
|
||||
@@ -5,6 +5,7 @@ use std::sync::Once;
|
||||
|
||||
use deno_core::{Extension, ExtensionFileSource, JsRuntime, OpState, RuntimeOptions, v8};
|
||||
use deno_error::JsErrorClass;
|
||||
use itertools::Itertools as _;
|
||||
|
||||
use crate::error::{Error, Result, Source};
|
||||
use crate::value::{AttrSet, List, Symbol, Value};
|
||||
@@ -15,9 +16,10 @@ 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: Source);
|
||||
fn add_source(&mut self, path: Source);
|
||||
fn compile_code(&mut self, source: Source) -> Result<String>;
|
||||
fn get_current_source(&self) -> Option<Source>;
|
||||
fn get_current_source(&self) -> Source;
|
||||
fn get_source(&self, id: usize) -> Source;
|
||||
}
|
||||
|
||||
fn runtime_extension<Ctx: RuntimeContext>() -> Extension {
|
||||
@@ -104,7 +106,7 @@ fn op_import<Ctx: RuntimeContext>(
|
||||
};
|
||||
|
||||
tracing::debug!("Compiling file");
|
||||
ctx.set_current_file(source.clone());
|
||||
ctx.add_source(source.clone());
|
||||
|
||||
Ok(ctx.compile_code(source).map_err(|err| err.to_string())?)
|
||||
}
|
||||
@@ -439,46 +441,39 @@ impl<Ctx: RuntimeContext> Runtime<Ctx> {
|
||||
.js_runtime
|
||||
.execute_script("<eval>", script)
|
||||
.map_err(|e| {
|
||||
let msg = format!("{}", e.get_message());
|
||||
let stack_str = e.stack.as_ref().map(|s| s.to_string());
|
||||
// Get current source from Context
|
||||
let op_state = self.js_runtime.op_state();
|
||||
let op_state_borrow = op_state.borrow();
|
||||
let ctx = op_state_borrow.borrow::<Ctx>();
|
||||
|
||||
let mut error = Error::eval_error(msg.clone(), None);
|
||||
let msg = e.get_message().to_string();
|
||||
let mut span = None;
|
||||
let mut source = None;
|
||||
|
||||
// Parse Nix stack trace frames
|
||||
if let Some(ref stack) = stack_str {
|
||||
let frames = crate::error::parse_nix_stack(stack);
|
||||
if let Some(stack) = &e.stack {
|
||||
let frames = crate::error::parse_nix_stack(stack, ctx);
|
||||
|
||||
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>()
|
||||
&& let Some(source) = ctx.get_current_source()
|
||||
{
|
||||
error = error.with_source(source);
|
||||
}
|
||||
if let Some(last_frame) = frames.last() {
|
||||
span = Some(last_frame.span);
|
||||
source = Some(last_frame.source.clone())
|
||||
}
|
||||
}
|
||||
|
||||
let js_backtrace = e.stack.map(|stack| {
|
||||
stack
|
||||
.lines()
|
||||
.filter(|line| !line.starts_with("NIX_STACK_FRAME:"))
|
||||
.join("\n")
|
||||
});
|
||||
let mut error = Error::eval_error(msg.clone(), js_backtrace);
|
||||
if let Some(span) = span {
|
||||
error = error.with_span(span);
|
||||
}
|
||||
if let Some(source) = source {
|
||||
error = error.with_source(source);
|
||||
}
|
||||
|
||||
error
|
||||
})?;
|
||||
|
||||
@@ -532,7 +527,7 @@ impl<Ctx: RuntimeContext> Runtime<Ctx> {
|
||||
"failed to convert {symbol} Value to Symbol ({err})"
|
||||
))
|
||||
})?;
|
||||
Ok(v8::Global::new(scope, sym))
|
||||
Result::Ok(v8::Global::new(scope, sym))
|
||||
};
|
||||
|
||||
let is_thunk = get_symbol("IS_THUNK")?;
|
||||
|
||||
@@ -5,7 +5,6 @@ use nix_js::value::Value;
|
||||
use utils::eval_result;
|
||||
|
||||
fn eval(expr: &str) -> Value {
|
||||
let mut ctx = Context::new().unwrap();
|
||||
eval_result(expr).unwrap_or_else(|e| panic!("{}", e))
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use nix_js::context::Context;
|
||||
use nix_js::error::Source;
|
||||
use nix_js::error::{Result, Source};
|
||||
use nix_js::value::Value;
|
||||
|
||||
pub fn eval(expr: &str) -> Value {
|
||||
@@ -11,7 +11,7 @@ pub fn eval(expr: &str) -> Value {
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn eval_result(expr: &str) -> Result<Value, nix_js::error::Error> {
|
||||
pub fn eval_result(expr: &str) -> Result<Value> {
|
||||
Context::new()
|
||||
.unwrap()
|
||||
.eval_code(Source::new_eval(expr.into()).unwrap())
|
||||
|
||||
Reference in New Issue
Block a user