refactor(codegen): less allocation

This commit is contained in:
2026-01-27 11:08:53 +08:00
parent 86953dd9d3
commit 058ef44259
3 changed files with 519 additions and 298 deletions

View File

@@ -1,34 +1,179 @@
use std::fmt::{self, Write as _};
use std::path::Path; use std::path::Path;
use itertools::Itertools as _;
use crate::ir::*; use crate::ir::*;
pub(crate) fn compile(expr: &Ir, ctx: &impl CodegenContext) -> String { pub(crate) struct CodeBuffer {
let code = expr.compile(ctx); buf: String,
}
let mut debug_flags = Vec::new(); impl fmt::Write for CodeBuffer {
if std::env::var("NIX_JS_DEBUG_THUNKS").is_ok() { #[inline]
debug_flags.push("Nix.DEBUG_THUNKS.enabled=true"); fn write_str(&mut self, s: &str) -> fmt::Result {
self.buf.push_str(s);
Ok(())
} }
let debug_prefix = if debug_flags.is_empty() { }
String::new()
} else { impl CodeBuffer {
format!("{};", debug_flags.join(";")) #[inline]
fn with_capacity(capacity: usize) -> Self {
Self {
buf: String::with_capacity(capacity),
}
}
#[inline]
fn push_str(&mut self, s: &str) {
self.buf.push_str(s);
}
#[inline]
fn into_string(self) -> String {
self.buf
}
}
struct Quoted<'a>(&'a str);
#[inline]
fn quoted(s: &str) -> Quoted<'_> {
Quoted(s)
}
struct Escaped<'a>(&'a str);
impl<Ctx: CodegenContext> Compile<Ctx> for Escaped<'_> {
fn compile(&self, _ctx: &Ctx, buf: &mut CodeBuffer) {
for c in self.0.chars() {
let _ = match c {
'\\' => buf.write_str("\\\\"),
'"' => buf.write_str("\\\""),
'\n' => buf.write_str("\\n"),
'\r' => buf.write_str("\\r"),
'\t' => buf.write_str("\\t"),
_ => buf.write_char(c),
};
}
}
}
#[inline]
fn escaped(s: &str) -> Escaped<'_> {
Escaped(s)
}
struct Joined<I, F> {
items: I,
sep: &'static str,
write_fn: F,
}
#[inline]
fn joined<Ctx: CodegenContext, I: Iterator, F: Fn(&Ctx, &mut CodeBuffer, I::Item)>(
items: I,
sep: &'static str,
write_fn: F,
) -> Joined<I, F> {
Joined {
items,
sep,
write_fn,
}
}
macro_rules! code {
($buf:expr, $ctx:expr; $($item:expr),* $(,)?) => {{
$(
($item).compile($ctx, $buf);
)*
}};
($buf:expr, $ctx:expr; $($item:expr)*) => {{
$(
($item).compile($ctx, $buf);
)*
}};
($buf:expr, $fmt:literal, $($arg:tt)*) => {
write!($buf, $fmt, $($arg)*).unwrap()
}; };
let cur_dir = ctx.get_current_dir().display().to_string().escape_quote(); ($buf:expr, $lit:literal) => {
format!( $buf.push_str($lit)
"(()=>{{{}Nix.builtins.storeDir={};const currentDir={};return {}}})()", };
debug_prefix, }
ctx.get_store_dir().escape_quote(),
cur_dir, pub(crate) fn compile(expr: &Ir, ctx: &impl CodegenContext) -> String {
code let mut buf = CodeBuffer::with_capacity(8192);
)
code!(&mut buf, ctx; "(()=>{");
if std::env::var("NIX_JS_DEBUG_THUNKS").is_ok() {
code!(&mut buf, ctx; "Nix.DEBUG_THUNKS.enabled=true;");
}
code!(&mut buf, ctx;
"Nix.builtins.storeDir="
quoted(ctx.get_store_dir())
";const currentDir="
quoted(&ctx.get_current_dir().display().to_string())
";return "
expr
"})()");
buf.into_string()
} }
trait Compile<Ctx: CodegenContext> { trait Compile<Ctx: CodegenContext> {
fn compile(&self, ctx: &Ctx) -> String; fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer);
}
impl<Ctx: CodegenContext> Compile<Ctx> for str {
fn compile(&self, _ctx: &Ctx, buf: &mut CodeBuffer) {
buf.push_str(self);
}
}
impl<Ctx: CodegenContext> Compile<Ctx> for usize {
fn compile(&self, _ctx: &Ctx, buf: &mut CodeBuffer) {
let _ = write!(buf, "{self}");
}
}
impl<Ctx: CodegenContext> Compile<Ctx> for Quoted<'_> {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
code!(buf, ctx; "\"" escaped(self.0) "\"")
}
}
impl<Ctx: CodegenContext, I, F> Compile<Ctx> for Joined<I, F>
where
I: IntoIterator + Clone,
F: Fn(&Ctx, &mut CodeBuffer, I::Item) + Clone,
{
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
let mut iter = self.items.clone().into_iter();
if let Some(first) = iter.next() {
(self.write_fn)(ctx, buf, first);
for item in iter {
buf.push_str(self.sep);
(self.write_fn)(ctx, buf, item);
}
}
}
}
impl<Ctx: CodegenContext> Compile<Ctx> for rnix::TextRange {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
code!(
buf,
"\"{}:{}:{}\"",
ctx.get_current_source_id(),
usize::from(self.start()),
usize::from(self.end())
);
}
} }
pub(crate) trait CodegenContext { pub(crate) trait CodegenContext {
@@ -40,179 +185,224 @@ pub(crate) trait CodegenContext {
fn get_current_source(&self) -> crate::error::Source; fn get_current_source(&self) -> crate::error::Source;
} }
trait EscapeQuote { impl<Ctx: CodegenContext> Compile<Ctx> for ExprId {
fn escape_quote(&self) -> String; fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
} ctx.get_ir(*self).compile(ctx, buf);
impl EscapeQuote for str {
fn escape_quote(&self) -> String {
let mut escaped = String::with_capacity(self.len() + 2);
escaped.push('"');
for c in self.chars() {
match c {
'\\' => escaped.push_str("\\\\"),
'\"' => escaped.push_str("\\\""),
'\n' => escaped.push_str("\\n"),
'\r' => escaped.push_str("\\r"),
'\t' => escaped.push_str("\\t"),
_ => escaped.push(c),
} }
}
escaped.push('"');
escaped
}
}
fn encode_span(span: rnix::TextRange, ctx: &impl CodegenContext) -> String {
format!(
"\"{}:{}:{}\"",
ctx.get_current_source_id(),
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, buf: &mut CodeBuffer) {
match self { match self {
Ir::Int(int) => format!("{}n", int.inner), // Generate BigInt literal Ir::Int(int) => {
Ir::Float(float) => float.inner.to_string(), code!(buf, "{}n", int.inner);
Ir::Bool(bool) => bool.inner.to_string(), }
Ir::Null(_) => "null".to_string(), Ir::Float(float) => {
Ir::Str(s) => s.val.escape_quote(), code!(buf, "{}", float.inner);
}
Ir::Bool(bool) => {
code!(buf, "{}", bool.inner);
}
Ir::Null(_) => {
code!(buf, ctx; "null");
}
Ir::Str(s) => {
code!(buf, ctx; quoted(&s.val));
}
Ir::Path(p) => { Ir::Path(p) => {
// Path needs runtime resolution code!(buf, ctx; "Nix.resolvePath(currentDir," ctx.get_ir(p.expr) ")");
let path_expr = ctx.get_ir(p.expr).compile(ctx);
format!("Nix.resolvePath(currentDir,{})", path_expr)
} }
&Ir::If(If { Ir::If(x) => x.compile(ctx, buf),
cond, Ir::BinOp(x) => x.compile(ctx, buf),
consq, Ir::UnOp(x) => x.compile(ctx, buf),
alter, Ir::Func(x) => x.compile(ctx, buf),
span: _, Ir::AttrSet(x) => x.compile(ctx, buf),
}) => { Ir::List(x) => x.compile(ctx, buf),
let cond_code = ctx.get_ir(cond).compile(ctx); Ir::Call(x) => x.compile(ctx, buf),
let consq = ctx.get_ir(consq).compile(ctx); Ir::Arg(x) => {
let alter = ctx.get_ir(alter).compile(ctx); code!(buf, "arg{}", x.inner.0);
let cond_span = encode_span(ctx.get_ir(cond).span(), ctx);
format!(
"(Nix.withContext(\"while evaluating a branch condition\",{},()=>Nix.forceBool({})))?({}):({})",
cond_span, cond_code, consq, alter
)
} }
Ir::BinOp(x) => x.compile(ctx), Ir::TopLevel(x) => x.compile(ctx, buf),
Ir::UnOp(x) => x.compile(ctx), Ir::Select(x) => x.compile(ctx, buf),
Ir::Func(x) => x.compile(ctx),
Ir::AttrSet(x) => x.compile(ctx),
Ir::List(x) => x.compile(ctx),
Ir::Call(x) => x.compile(ctx),
Ir::Arg(x) => format!("arg{}", x.inner.0),
Ir::TopLevel(x) => x.compile(ctx),
Ir::Select(x) => x.compile(ctx),
&Ir::Thunk(Thunk { inner: expr_id, .. }) => { &Ir::Thunk(Thunk { inner: expr_id, .. }) => {
format!("expr{}", expr_id.0) code!(buf, "expr{}", expr_id.0);
}
Ir::Builtins(_) => {
code!(buf, ctx; "Nix.builtins");
} }
Ir::Builtins(_) => "Nix.builtins".to_string(),
&Ir::Builtin(Builtin { inner: name, .. }) => { &Ir::Builtin(Builtin { inner: name, .. }) => {
format!("Nix.builtins[{}]", ctx.get_sym(name).escape_quote()) code!(buf, ctx;
"Nix.builtins[",
quoted(ctx.get_sym(name)),
"]"
);
} }
Ir::ConcatStrings(x) => x.compile(ctx), Ir::ConcatStrings(x) => x.compile(ctx, buf),
Ir::HasAttr(x) => x.compile(ctx), Ir::HasAttr(x) => x.compile(ctx, buf),
&Ir::Assert(Assert { &Ir::Assert(Assert {
assertion, assertion,
expr, expr,
ref assertion_raw, ref assertion_raw,
span, span: assert_span,
}) => { }) => {
let assertion_code = ctx.get_ir(assertion).compile(ctx); let assertion_ir = ctx.get_ir(assertion);
let expr = ctx.get_ir(expr).compile(ctx); let assertion_span = assertion_ir.span();
let assertion_span = encode_span(ctx.get_ir(assertion).span(), ctx);
let span = encode_span(span, ctx); code!(buf, ctx;
format!( "Nix.assert(Nix.withContext(\"while evaluating the condition of the assert statement\",",
"Nix.assert(Nix.withContext(\"while evaluating the condition of the assert statement\",{},()=>({})),{},{},{})",
assertion_span, assertion_span,
assertion_code, ",()=>(",
expr, assertion_ir,
assertion_raw.escape_quote(), ")),",
span ctx.get_ir(expr),
) ",",
quoted(assertion_raw),
",",
assert_span,
")"
);
} }
Ir::CurPos(cur_pos) => { Ir::CurPos(cur_pos) => {
let span_str = encode_span(cur_pos.span, ctx); code!(buf, ctx;
format!("Nix.mkPos({})", span_str) "Nix.mkPos(",
cur_pos.span,
")"
);
} }
} }
} }
} }
impl<Ctx: CodegenContext> Compile<Ctx> for If {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
let &If {
cond,
consq,
alter,
span: _,
} = self;
let cond_ir = ctx.get_ir(cond);
let cond_span = cond_ir.span();
code!(buf, ctx;
"(Nix.withContext(\"while evaluating a branch condition\"," cond_span ",()=>Nix.forceBool(" cond_ir ")))"
"?(" consq "):(" alter ")"
);
}
}
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, buf: &mut CodeBuffer) {
use BinOpKind::*; use BinOpKind::*;
let lhs = ctx.get_ir(self.lhs).compile(ctx); let lhs = ctx.get_ir(self.lhs);
let rhs = ctx.get_ir(self.rhs).compile(ctx); let rhs = ctx.get_ir(self.rhs);
let with_ctx = |op_name: &str, op_call: String| {
let span = encode_span(self.span, ctx);
format!(
"Nix.withContext(\"while evaluating the {} operator\",{},()=>({}))",
op_name, span, op_call
)
};
match self.kind { match self.kind {
Add => with_ctx("+", format!("Nix.op.add({},{})", lhs, rhs)), Add | Sub | Mul | Div | Eq | Neq | Lt | Gt | Leq | Geq | Con | Upd => {
Sub => with_ctx("-", format!("Nix.op.sub({},{})", lhs, rhs)), let op_name = match self.kind {
Mul => with_ctx("*", format!("Nix.op.mul({},{})", lhs, rhs)), Add => "+",
Div => with_ctx("/", format!("Nix.op.div({},{})", lhs, rhs)), Sub => "-",
Eq => with_ctx("==", format!("Nix.op.eq({},{})", lhs, rhs)), Mul => "*",
Neq => with_ctx("!=", format!("Nix.op.neq({},{})", lhs, rhs)), Div => "/",
Lt => with_ctx("<", format!("Nix.op.lt({},{})", lhs, rhs)), Eq => "==",
Gt => with_ctx(">", format!("Nix.op.gt({},{})", lhs, rhs)), Neq => "!=",
Leq => with_ctx("<=", format!("Nix.op.lte({},{})", lhs, rhs)), Lt => "<",
Geq => with_ctx(">=", format!("Nix.op.gte({},{})", lhs, rhs)), Gt => ">",
// Short-circuit operators: use JavaScript native && and || Leq => "<=",
And => with_ctx( Geq => ">=",
"&&", Con => "++",
format!("Nix.forceBool({})&&Nix.forceBool({})", lhs, rhs), Upd => "//",
), _ => unreachable!(),
Or => with_ctx( };
"||", let op_func = match self.kind {
format!("Nix.forceBool({})||Nix.forceBool({})", lhs, rhs), Add => "Nix.op.add",
), Sub => "Nix.op.sub",
Impl => with_ctx( Mul => "Nix.op.mul",
"->", Div => "Nix.op.div",
format!("(!Nix.forceBool({})||Nix.forceBool({}))", lhs, rhs), Eq => "Nix.op.eq",
), Neq => "Nix.op.neq",
Con => with_ctx("++", format!("Nix.op.concat({},{})", lhs, rhs)), Lt => "Nix.op.lt",
Upd => with_ctx("//", format!("Nix.op.update({},{})", lhs, rhs)), Gt => "Nix.op.gt",
PipeL => format!("Nix.call({},{})", rhs, lhs), Leq => "Nix.op.lte",
PipeR => format!("Nix.call({},{})", lhs, rhs), Geq => "Nix.op.gte",
Con => "Nix.op.concat",
Upd => "Nix.op.update",
_ => unreachable!(),
};
code!(
buf, ctx;
"Nix.withContext(\"while evaluating the " op_name " operator\"," self.span ",()=>(" op_func "(" lhs "," rhs ")))"
);
}
And => {
code!(
buf, ctx;
"Nix.withContext(\"while evaluating the && operator\"," self.span ",()=>(Nix.forceBool(" lhs ")&&Nix.forceBool(" rhs ")))"
);
}
Or => {
code!(
buf, ctx;
"Nix.withContext(\"while evaluating the || operator\"," self.span ",()=>(Nix.forceBool(" lhs ")||Nix.forceBool(" rhs ")))"
);
}
Impl => {
code!(
buf, ctx;
"Nix.withContext(\"while evaluating the || operator\"," self.span ",()=>(Nix.forceBool(" lhs ")||Nix.forceBool(" rhs ")))"
);
}
PipeL => {
code!(buf, ctx;
"Nix.call(",
rhs,
",",
lhs,
")"
);
}
PipeR => {
code!(buf, ctx;
"Nix.call(",
lhs,
",",
rhs,
")"
);
}
} }
} }
} }
impl<Ctx: CodegenContext> Compile<Ctx> for UnOp { impl<Ctx: CodegenContext> Compile<Ctx> for UnOp {
fn compile(&self, ctx: &Ctx) -> String { fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
use UnOpKind::*; use UnOpKind::*;
let rhs = ctx.get_ir(self.rhs).compile(ctx);
match self.kind { match self.kind {
Neg => format!("Nix.op.sub(0n,{rhs})"), Neg => {
Not => format!("Nix.op.bnot({rhs})"), code!(buf, ctx;
"Nix.op.sub(0n,",
ctx.get_ir(self.rhs),
")"
);
}
Not => {
code!(buf, ctx;
"Nix.op.bnot(",
ctx.get_ir(self.rhs),
")"
);
}
} }
} }
} }
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, buf: &mut CodeBuffer) {
let id = ctx.get_ir(self.arg).as_ref().unwrap_arg().inner.0; let id = ctx.get_ir(self.arg).as_ref().unwrap_arg().inner.0;
let thunk_defs = compile_thunks(&self.thunks, ctx);
let body_code = ctx.get_ir(self.body).compile(ctx); let has_thunks = !self.thunks.is_empty();
let body = if thunk_defs.is_empty() {
body_code
} else {
format!("{{{}return {}}}", thunk_defs, body_code)
};
if let Some(Param { if let Some(Param {
required, required,
@@ -220,199 +410,230 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Func {
ellipsis, ellipsis,
}) = &self.param }) = &self.param
{ {
let mut required = required.iter().map(|&sym| ctx.get_sym(sym).escape_quote()); code!(buf, "Nix.mkFunction(arg{}=>", id);
let required = format!("[{}]", required.join(",")); if has_thunks {
let mut optional = optional.iter().map(|&sym| ctx.get_sym(sym).escape_quote()); code!(buf, ctx; "{", &self.thunks, "return ", self.body, "}");
let optional = format!("[{}]", optional.join(","));
if thunk_defs.is_empty() {
format!("Nix.mkFunction(arg{id}=>({body}),{required},{optional},{ellipsis})")
} else { } else {
format!("Nix.mkFunction(arg{id}=>{body},{required},{optional},{ellipsis})") code!(buf, ctx; "(", self.body, ")");
} }
code!(buf, ctx;
",["
joined(required.iter(), ",", |ctx: &Ctx, buf, &sym| {
code!(buf, ctx; quoted(ctx.get_sym(sym)));
})
"],["
joined(optional.iter(), ",", |ctx: &Ctx, buf, &sym| {
code!(buf, ctx; quoted(ctx.get_sym(sym)));
})
"],"
);
code!(buf, "{})", ellipsis);
} else { } else {
if thunk_defs.is_empty() { code!(buf, "arg{}=>", id);
format!("arg{id}=>({body})") if has_thunks {
code!(buf, ctx; "{", &self.thunks, "return ", self.body, "}");
} else { } else {
format!("arg{id}=>{body}") code!(buf, ctx; "(", self.body, ")");
} }
} }
} }
} }
impl<Ctx: CodegenContext> Compile<Ctx> for Call { impl<Ctx: CodegenContext> Compile<Ctx> for Call {
fn compile(&self, ctx: &Ctx) -> String { fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
let func = ctx.get_ir(self.func).compile(ctx); code!(buf, ctx;
let arg = ctx.get_ir(self.arg).compile(ctx); "Nix.call(",
let span_str = encode_span(self.span, ctx); ctx.get_ir(self.func),
format!("Nix.call({func},{arg},{span_str})") ",",
ctx.get_ir(self.arg),
",",
self.span,
")"
);
} }
} }
fn compile_thunks<Ctx: CodegenContext>(thunks: &[(ExprId, ExprId)], ctx: &Ctx) -> String { impl<Ctx: CodegenContext> Compile<Ctx> for [(ExprId, ExprId)] {
if thunks.is_empty() { fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
return String::new(); if self.is_empty() {
return;
} }
thunks
.iter() for &(slot, inner) in self {
.map(|&(slot, inner)| { let inner_ir = ctx.get_ir(inner);
let inner_code = ctx.get_ir(inner).compile(ctx); let inner_span = inner_ir.span();
let inner_span = ctx.get_ir(inner).span();
format!( code!(buf, "let expr{}=Nix.createThunk(()=>(", slot.0);
"let expr{}=Nix.createThunk(()=>({}),\"expr{} {}:{}:{}\")", inner_ir.compile(ctx, buf);
slot.0, code!(
inner_code, buf,
"),\"expr{} {}:{}:{}\");",
slot.0, slot.0,
ctx.get_current_source().get_name(), ctx.get_current_source().get_name(),
usize::from(inner_span.start()), usize::from(inner_span.start()),
usize::from(inner_span.end()) usize::from(inner_span.end())
) );
}) }
.join(";") }
+ ";"
} }
impl<Ctx: CodegenContext> Compile<Ctx> for TopLevel { impl<Ctx: CodegenContext> Compile<Ctx> for TopLevel {
fn compile(&self, ctx: &Ctx) -> String { fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
let thunk_defs = compile_thunks(&self.thunks, ctx); if self.thunks.is_empty() {
let body = ctx.get_ir(self.body).compile(ctx); ctx.get_ir(self.body).compile(ctx, buf);
if thunk_defs.is_empty() {
body
} else { } else {
format!("(()=>{{{}return {}}})()", thunk_defs, body) code!(buf, "(()=>{");
code!(buf, ctx; &self.thunks);
code!(buf, "return ");
ctx.get_ir(self.body).compile(ctx, buf);
code!(buf, "})()");
} }
} }
} }
impl<Ctx: CodegenContext> Compile<Ctx> for Select { impl<Ctx: CodegenContext> Compile<Ctx> for Select {
fn compile(&self, ctx: &Ctx) -> String { fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
let lhs = ctx.get_ir(self.expr).compile(ctx);
let attrpath = self
.attrpath
.iter()
.map(|attr| match attr {
Attr::Str(sym, _) => ctx.get_sym(*sym).escape_quote(),
Attr::Dynamic(expr_id, _) => ctx.get_ir(*expr_id).compile(ctx),
})
.join(",");
let span_str = encode_span(self.span, ctx);
if let Some(default) = self.default { if let Some(default) = self.default {
format!( code!(buf, ctx;
"Nix.selectWithDefault({lhs},[{attrpath}],{},{span_str})", "Nix.selectWithDefault("
ctx.get_ir(default).compile(ctx) ctx.get_ir(self.expr)
) ",["
joined(self.attrpath.iter(), ",", |ctx: &Ctx, buf, attr| {
match attr {
Attr::Str(sym, _) => code!(buf, ctx; quoted(ctx.get_sym(*sym))),
Attr::Dynamic(expr_id, _) => code!(buf, ctx; ctx.get_ir(*expr_id)),
}
})
"],"
ctx.get_ir(default)
","
self.span
")"
);
} else { } else {
format!("Nix.select({lhs},[{attrpath}],{span_str})") code!(buf, ctx;
"Nix.select("
ctx.get_ir(self.expr)
",["
joined(self.attrpath.iter(), ",", |ctx: &Ctx, buf, attr| {
match attr {
Attr::Str(sym, _) => code!(buf, ctx; quoted(ctx.get_sym(*sym))),
Attr::Dynamic(expr_id, _) => code!(buf, ctx; ctx.get_ir(*expr_id)),
}
})
"],"
self.span
")"
);
} }
} }
} }
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, buf: &mut CodeBuffer) {
let mut attrs = Vec::new();
let mut attr_positions = Vec::new();
for (&sym, &(expr, attr_span)) in &self.stcs {
let key = ctx.get_sym(sym);
let value_code = ctx.get_ir(expr).compile(ctx);
let value_span = encode_span(ctx.get_ir(expr).span(), ctx);
let value = format!(
"Nix.withContext(\"while evaluating the attribute '{}'\",{},()=>({}))",
key, value_span, value_code
);
attrs.push(format!("{}:{}", key.escape_quote(), value));
let attr_pos_str = encode_span(attr_span, ctx);
attr_positions.push(format!("{}:{}", key.escape_quote(), attr_pos_str));
}
if !self.dyns.is_empty() { if !self.dyns.is_empty() {
let (keys, vals, dyn_spans) = self code!(buf, ctx;
.dyns "Nix.mkAttrsWithPos({"
.iter() joined(self.stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(expr, _))| {
.map(|(key, val, attr_span)| { let key = ctx.get_sym(sym);
let key = ctx.get_ir(*key).compile(ctx); let val = ctx.get_ir(expr);
let val_expr = ctx.get_ir(*val);
let val = val_expr.compile(ctx); code!(
let span = val_expr.span(); buf, ctx;
let span = encode_span(span, ctx); quoted(key) ":Nix.withContext(\"while evaluating the attribute '" escaped(key) "'\"," val.span() ",()=>(" val "))"
let val = format!(
"Nix.withContext(\"while evaluating a dynamic attribute\",{},()=>({}))",
span, val
); );
let dyn_span_str = encode_span(*attr_span, ctx);
(key, val, dyn_span_str)
}) })
.multiunzip::<(Vec<_>, Vec<_>, Vec<_>)>(); "},{"
format!( joined(self.stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(_, span))| {
"Nix.mkAttrsWithPos({{{}}},{{{}}},{{dynKeys:[{}],dynVals:[{}],dynSpans:[{}]}})", code!(buf, ctx; quoted(ctx.get_sym(sym)) ":" span);
attrs.join(","), })
attr_positions.join(","), "},{dynKeys:["
keys.join(","), joined(self.dyns.iter(), ",", |ctx: &Ctx, buf, (key, _, _)| {
vals.join(","), code!(buf, ctx; ctx.get_ir(*key));
dyn_spans.join(",") })
) "],dynVals:["
} else if !attr_positions.is_empty() { joined(self.dyns.iter(), ",", |ctx: &Ctx, buf, (_, val, _)| {
format!( let val = ctx.get_ir(*val);
"Nix.mkAttrsWithPos({{{}}},{{{}}})", code!(
attrs.join(","), buf, ctx;
attr_positions.join(",") "Nix.withContext(\"while evaluating a dynamic attribute\"," val.span() ",()=>(" val "))"
) );
})
"],dynSpans:["
joined(self.dyns.iter(), ",", |ctx: &Ctx, buf, (_, _, attr_span)| {
code!(buf, ctx; attr_span);
})
"]})"
);
} else if !self.stcs.is_empty() {
code!(buf, ctx;
"Nix.mkAttrsWithPos({"
joined(self.stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(expr, _))| {
let key = ctx.get_sym(sym);
let val = ctx.get_ir(expr);
code!(
buf, ctx;
quoted(key) ":Nix.withContext(\"while evaluating the attribute '" escaped(key) "'\"," val.span() ",()=>(" val "))"
);
})
"},{"
joined(self.stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(_, span))| {
code!(buf, ctx; quoted(ctx.get_sym(sym)) ":" span);
})
"})"
);
} else { } else {
format!("{{{}}}", attrs.join(",")) code!(buf, ctx; "{}");
} }
} }
} }
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, buf: &mut CodeBuffer) {
let list = self code!(buf, ctx;
.items "["
.iter() joined(self.items.iter().enumerate(), ",", |ctx: &Ctx, buf, (idx, item)| {
.enumerate() let item = ctx.get_ir(*item);
.map(|(idx, item)| { code!(
let item_code = ctx.get_ir(*item).compile(ctx); buf, ctx;
let item_span = encode_span(ctx.get_ir(*item).span(), ctx); "Nix.withContext(\"while evaluating list element " idx "\"," item.span() ",()=>(" item "))"
format!( );
"Nix.withContext(\"while evaluating list element {}\",{},()=>({}))",
idx, item_span, item_code
)
}) })
.join(","); "]"
format!("[{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, buf: &mut CodeBuffer) {
let parts: Vec<String> = self code!(buf, ctx;
.parts "Nix.concatStringsWithContext(["
.iter() joined(self.parts.iter(), ",", |ctx: &Ctx, buf, part| {
.map(|part| { let part = ctx.get_ir(*part);
let part_code = ctx.get_ir(*part).compile(ctx); code!(
let part_span = encode_span(ctx.get_ir(*part).span(), ctx); buf, ctx;
format!( "Nix.withContext(\"while evaluating a path segment\"," part.span() ",()=>(" part "))"
"Nix.withContext(\"while evaluating a path segment\",{},()=>({}))", );
part_span, part_code
)
}) })
.collect(); "])"
);
format!("Nix.concatStringsWithContext([{}])", parts.join(","))
} }
} }
impl<Ctx: CodegenContext> Compile<Ctx> for HasAttr { impl<Ctx: CodegenContext> Compile<Ctx> for HasAttr {
fn compile(&self, ctx: &Ctx) -> String { fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
let lhs = ctx.get_ir(self.lhs).compile(ctx); code!(buf, ctx;
let attrpath = self "Nix.hasAttr("
.rhs ctx.get_ir(self.lhs)
.iter() ",["
.map(|attr| match attr { joined(self.rhs.iter(), ",", |ctx: &Ctx, buf, attr| {
Attr::Str(sym, _) => ctx.get_sym(*sym).escape_quote(), match attr {
Attr::Dynamic(expr_id, _) => ctx.get_ir(*expr_id).compile(ctx), Attr::Str(sym, _) => code!(buf, ctx; quoted(ctx.get_sym(*sym))),
Attr::Dynamic(expr_id, _) => code!(buf, ctx; ctx.get_ir(*expr_id)),
}
}) })
.join(","); "])"
format!("Nix.hasAttr({lhs},[{attrpath}])") );
} }
} }

View File

@@ -4,8 +4,8 @@
use hashbrown::hash_map::Entry; use hashbrown::hash_map::Entry;
use hashbrown::{HashMap, HashSet}; use hashbrown::{HashMap, HashSet};
use itertools::Itertools as _; use itertools::Itertools as _;
use rnix::ast;
use rnix::TextRange; use rnix::TextRange;
use rnix::ast;
use rowan::ast::AstNode; use rowan::ast::AstNode;
use crate::error::{Error, Result}; use crate::error::{Error, Result};
@@ -388,15 +388,15 @@ where
)); ));
} }
} }
} else if attrs_vec.len() > 1 { } else if attrs_vec.len() > 1
if let Some(ast::Attr::Ident(ident)) = attrs_vec.first() { && let Some(ast::Attr::Ident(ident)) = attrs_vec.first()
{
let sym = ctx.new_sym(ident.to_string()); let sym = ctx.new_sym(ident.to_string());
binding_syms.insert(sym); binding_syms.insert(sym);
} }
} }
} }
} }
}
let binding_keys: Vec<_> = binding_syms.into_iter().collect(); let binding_keys: Vec<_> = binding_syms.into_iter().collect();
let slots: Vec<_> = ctx.reserve_slots(binding_keys.len()).collect(); let slots: Vec<_> = ctx.reserve_slots(binding_keys.len()).collect();

View File

@@ -542,7 +542,7 @@ fn op_get_env(#[string] key: String) -> std::result::Result<String, NixError> {
match std::env::var(key) { match std::env::var(key) {
Ok(val) => Ok(val), Ok(val) => Ok(val),
Err(std::env::VarError::NotPresent) => Ok("".into()), Err(std::env::VarError::NotPresent) => Ok("".into()),
Err(err) => Err(format!("Failed to read env var: {err}").into()) Err(err) => Err(format!("Failed to read env var: {err}").into()),
} }
} }