diff --git a/Cargo.lock b/Cargo.lock index 73e19a3..7c282d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1302,6 +1302,12 @@ dependencies = [ "wasip3", ] +[[package]] +name = "ghost-cell" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8449d342b1c67f49169e92e71deb7b9b27f30062301a16dbc27a4cc8d2351b7" + [[package]] name = "gimli" version = "0.32.3" @@ -2057,6 +2063,7 @@ dependencies = [ "ere", "fastwebsockets", "flate2", + "ghost-cell", "hashbrown 0.16.1", "hex", "http", diff --git a/nix-js/Cargo.toml b/nix-js/Cargo.toml index 342eee0..1f08c84 100644 --- a/nix-js/Cargo.toml +++ b/nix-js/Cargo.toml @@ -74,6 +74,7 @@ hyper-util = { version = "0.1", features = ["tokio"], optional = true } http-body-util = { version = "0.1", optional = true } http = { version = "1", optional = true } uuid = { version = "1", features = ["v4"], optional = true } +ghost-cell = "0.2.6" [features] inspector = ["dep:fastwebsockets", "dep:hyper", "dep:hyper-util", "dep:http-body-util", "dep:http", "dep:uuid"] diff --git a/nix-js/src/codegen.rs b/nix-js/src/codegen.rs index 18bf873..d327fbf 100644 --- a/nix-js/src/codegen.rs +++ b/nix-js/src/codegen.rs @@ -1,6 +1,9 @@ use std::fmt::{self, Write as _}; +use std::ops::Deref; use std::path::Path; +use rnix::TextRange; + use crate::ir::*; use crate::value::Symbol; @@ -26,7 +29,7 @@ macro_rules! code { }; } -pub(crate) fn compile(expr: &Ir, ctx: &impl CodegenContext) -> String { +pub(crate) fn compile(expr: RawIrRef<'_>, ctx: &impl CodegenContext) -> String { let mut buf = CodeBuffer::with_capacity(8192); code!( @@ -186,59 +189,84 @@ impl Compile for Symbol<'_> { } } -impl Compile for Ir<'_> { +impl Compile for RawIrRef<'_> { fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { - match self { + match self.deref() { Ir::Int(int) => { - code!(buf, "{}n", int.inner); + code!(buf, "{}n", int); } Ir::Float(float) => { - code!(buf, "{}", float.inner); + code!(buf, "{}", float); } Ir::Bool(bool) => { - code!(buf, "{}", bool.inner); + code!(buf, "{}", bool); } - Ir::Null(_) => { + Ir::Null => { code!(buf, ctx; "null"); } Ir::Str(s) => { - code!(buf, ctx; quoted(&s.inner)); + code!(buf, ctx; quoted(s)); } Ir::Path(p) => { // Nix.resolvePath - code!(buf, ctx; "$r(_d," p.expr ")"); + code!(buf, ctx; "$r(_d," p ")"); + } + Ir::If { cond, consq, alter } => { + code!(buf, ctx; "$fb(" cond ")?(" consq "):(" alter ")"); + } + &Ir::BinOp { lhs, rhs, kind } => compile_binop(lhs, rhs, kind, ctx, buf), + &Ir::UnOp { rhs, kind } => compile_unop(rhs, kind, ctx, buf), + &Ir::Func { + body, + ref param, + arg, + ref thunks, + } => compile_func(arg, thunks, param, body, ctx, buf), + Ir::AttrSet { stcs, dyns } => compile_attrset(stcs, dyns, ctx, buf), + Ir::List { items } => compile_list(items, ctx, buf), + Ir::Call { func, arg, span } => { + code!(buf, ctx; + "$c(" + func + "," + arg + "," + span + ")" + ); } - Ir::If(x) => x.compile(ctx, buf), - Ir::BinOp(x) => x.compile(ctx, buf), - Ir::UnOp(x) => x.compile(ctx, buf), - Ir::Func(x) => x.compile(ctx, buf), - Ir::AttrSet(x) => x.compile(ctx, buf), - Ir::List(x) => x.compile(ctx, buf), - Ir::Call(x) => x.compile(ctx, buf), Ir::Arg(x) => { - code!(buf, "a{}", x.inner.0); + code!(buf, "a{}", x.0); } - Ir::TopLevel(x) => x.compile(ctx, buf), - Ir::Select(x) => x.compile(ctx, buf), - &Ir::Thunk(Thunk { inner: expr_id, .. }) => { - code!(buf, "e{}", expr_id.0); + &Ir::TopLevel { body, ref thunks } => compile_toplevel(body, thunks, ctx, buf), + &Ir::Select { + expr, + ref attrpath, + default, + span, + } => compile_select(expr, attrpath, default, span, ctx, buf), + Ir::Thunk(ThunkId(id)) => { + code!(buf, "e{}", id); } - Ir::Builtins(_) => { + Ir::Builtins => { // Nix.builtins code!(buf, ctx; "$b"); } - &Ir::Builtin(Builtin { inner: name, .. }) => { + &Ir::Builtin(name) => { // Nix.builtins code!(buf, ctx; "$b.get(" ctx.get_sym(name) ")"); } - Ir::ConcatStrings(x) => x.compile(ctx, buf), - Ir::HasAttr(x) => x.compile(ctx, buf), - &Ir::Assert(Assert { + &Ir::ConcatStrings { + ref parts, + force_string, + } => compile_concat_strings(parts, force_string, ctx, buf), + &Ir::HasAttr { lhs, ref rhs } => compile_has_attr(lhs, rhs, ctx, buf), + Ir::Assert { assertion, expr, - ref assertion_raw, + assertion_raw, span: assert_span, - }) => { + } => { // Nix.assert code!(buf, ctx; "$a(" @@ -252,19 +280,23 @@ impl Compile for Ir<'_> { ")" ); } - Ir::CurPos(cur_pos) => { + Ir::CurPos(span) => { // Nix.mkPos - code!(buf, ctx; "$mp(" cur_pos.span ")"); + code!(buf, ctx; "$mp(" span ")"); } - &Ir::ReplBinding(ReplBinding { inner: name, .. }) => { + &Ir::ReplBinding(name) => { // Nix.getReplBinding code!(buf, ctx; "$gb(" ctx.get_sym(name) ")"); } - &Ir::ScopedImportBinding(ScopedImportBinding { inner: name, .. }) => { + &Ir::ScopedImportBinding(name) => { code!(buf, ctx; "_s.get(" ctx.get_sym(name) ")"); } - Ir::With(x) => x.compile(ctx, buf), - &Ir::WithLookup(WithLookup { inner: name, .. }) => { + &Ir::With { + namespace, + body, + ref thunks, + } => compile_with(namespace, body, thunks, ctx, buf), + &Ir::WithLookup(name) => { // Nix.lookupWith code!(buf, ctx; "$l(" ctx.get_sym(name) ",_w)"); } @@ -272,161 +304,132 @@ impl Compile for Ir<'_> { } } -impl Compile for If<'_> { - fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { - let &If { - cond, - consq, - alter, - span: _, - } = self; +fn compile_binop<'ir>( + lhs: RawIrRef<'ir>, + rhs: RawIrRef<'ir>, + kind: BinOpKind, + ctx: &impl CodegenContext, + buf: &mut CodeBuffer, +) { + use BinOpKind::*; + match kind { + Add | Sub | Mul | Div | Eq | Neq | Lt | Gt | Leq | Geq | Con | Upd => { + let op_func = match kind { + Add => "$oa", + Sub => "$os", + Mul => "$om", + Div => "$od", + Eq => "$oe", + Neq => "!$oe", + Lt => "$ol", + Gt => "$og", + Leq => "!$og", + Geq => "!$ol", + Con => "$oc", + Upd => "$ou", + _ => unreachable!(), + }; - // Nix.forceBool - code!(buf, ctx; "$fb(" cond ")?(" consq "):(" alter ")"); - } -} - -impl Compile for BinOp<'_> { - fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { - use BinOpKind::*; - - let lhs = self.lhs; - let rhs = self.rhs; - - match self.kind { - Add | Sub | Mul | Div | Eq | Neq | Lt | Gt | Leq | Geq | Con | Upd => { - let op_func = match self.kind { - Add => "$oa", - Sub => "$os", - Mul => "$om", - Div => "$od", - Eq => "$oe", - Neq => "!$oe", - Lt => "$ol", - Gt => "$og", - Leq => "!$og", - Geq => "!$ol", - Con => "$oc", - Upd => "$ou", - _ => unreachable!(), - }; - - code!( - buf, ctx; - op_func "(" lhs "," rhs ")" - ); - } - And => { - code!( - buf, ctx; - "$fb(" lhs ")" "&&" "$fb(" rhs ")" - ); - } - Or => { - code!( - buf, ctx; - "$fb(" lhs ")" "||" "$fb(" rhs ")" - ); - } - Impl => { - code!( - buf, ctx; - "!$fb(" lhs ")" "||" "$fb(" rhs ")" - ); - } - PipeL => { - code!(buf, ctx; "$c(" rhs "," lhs ")"); - } - PipeR => { - code!(buf, ctx; "$c(" lhs "," rhs ")"); - } - } - } -} - -impl Compile for UnOp<'_> { - fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { - use UnOpKind::*; - let rhs = self.rhs; - match self.kind { - Neg => { - // 0 - rhs - code!(buf, ctx; "$os(0n," rhs ")"); - } - Not => { - code!(buf, ctx; "!$fb(" rhs ")"); - } - } - } -} - -impl Compile for Func<'_> { - fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { - let Ir::Arg(Arg { - inner: ArgId(id), .. - }) = self.arg - else { - // TODO: - unreachable!() - }; - - let has_thunks = !self.thunks.is_empty(); - - if let Some(Param { - required, - optional, - ellipsis, - }) = &self.param - { - code!(buf, "$mf(a{}=>", id); - if has_thunks { - code!(buf, ctx; "{" self.thunks "return " self.body "}"); - } else { - code!(buf, ctx; "(" self.body ")"); - } - code!(buf, ctx; - ",[" - joined(required.iter(), ",", |ctx: &Ctx, buf, &(sym, _)| { - code!(buf, ctx; ctx.get_sym(sym)); - }) - "],[" - joined(optional.iter(), ",", |ctx: &Ctx, buf, &(sym, _)| { - code!(buf, ctx; ctx.get_sym(sym)); - }) - "],new Map([" - joined(required.iter().chain(optional.iter()), ",", |ctx: &Ctx, buf, &(sym, span)| { - code!(buf, ctx; "[" ctx.get_sym(sym) "," span "]"); - }) - "])," - ellipsis - ")" + code!( + buf, ctx; + op_func "(" lhs "," rhs ")" ); - } else { - code!(buf, "a{}=>", id); - if has_thunks { - code!(buf, ctx; "{" self.thunks "return " self.body "}"); - } else { - code!(buf, ctx; "(" self.body ")"); - } + } + And => { + code!( + buf, ctx; + "$fb(" lhs ")" "&&" "$fb(" rhs ")" + ); + } + Or => { + code!( + buf, ctx; + "$fb(" lhs ")" "||" "$fb(" rhs ")" + ); + } + Impl => { + code!( + buf, ctx; + "!$fb(" lhs ")" "||" "$fb(" rhs ")" + ); + } + PipeL => { + code!(buf, ctx; "$c(" rhs "," lhs ")"); + } + PipeR => { + code!(buf, ctx; "$c(" lhs "," rhs ")"); } } } -impl Compile for Call<'_> { - fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { +fn compile_unop( + rhs: RawIrRef<'_>, + kind: UnOpKind, + ctx: &impl CodegenContext, + buf: &mut CodeBuffer, +) { + use UnOpKind::*; + match kind { + Neg => { + // 0 - rhs + code!(buf, ctx; "$os(0n," rhs ")"); + } + Not => { + code!(buf, ctx; "!$fb(" rhs ")"); + } + } +} + +fn compile_func<'ir, Ctx: CodegenContext>( + ArgId(id): ArgId, + thunks: &[(ThunkId, RawIrRef<'ir>)], + param: &Option>, + body: RawIrRef<'ir>, + ctx: &Ctx, + buf: &mut CodeBuffer, +) { + let has_thunks = !thunks.is_empty(); + + if let Some(Param { + required, + optional, + ellipsis, + }) = ¶m + { + code!(buf, "$mf(a{}=>", id); + if has_thunks { + code!(buf, ctx; "{" thunks "return " body "}"); + } else { + code!(buf, ctx; "(" body ")"); + } code!(buf, ctx; - "$c(" - self.func - "," - self.arg - "," - self.span + ",[" + joined(required.iter(), ",", |ctx: &Ctx, buf, &(sym, _)| { + code!(buf, ctx; ctx.get_sym(sym)); + }) + "],[" + joined(optional.iter(), ",", |ctx: &Ctx, buf, &(sym, _)| { + code!(buf, ctx; ctx.get_sym(sym)); + }) + "],new Map([" + joined(required.iter().chain(optional.iter()), ",", |ctx: &Ctx, buf, &(sym, span)| { + code!(buf, ctx; "[" ctx.get_sym(sym) "," span "]"); + }) + "])," + ellipsis ")" ); + } else { + code!(buf, "a{}=>", id); + if has_thunks { + code!(buf, ctx; "{" thunks "return " body "}"); + } else { + code!(buf, ctx; "(" body ")"); + } } } -impl<'ir, Ctx: CodegenContext> Compile for [(ThunkId, IrRef<'ir>)] { +impl<'ir, Ctx: CodegenContext> Compile for [(ThunkId, RawIrRef<'ir>)] { fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { if self.is_empty() { return; @@ -443,156 +446,173 @@ impl<'ir, Ctx: CodegenContext> Compile for [(ThunkId, IrRef<'ir>)] { } } -impl Compile for TopLevel<'_> { - fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { - if self.thunks.is_empty() { - self.body.compile(ctx, buf); - } else { - code!(buf, ctx; "(()=>{" self.thunks "return " self.body "})()"); - } +fn compile_toplevel<'ir, Ctx: CodegenContext>( + body: RawIrRef<'ir>, + thunks: &[(ThunkId, RawIrRef<'ir>)], + ctx: &Ctx, + buf: &mut CodeBuffer, +) { + if thunks.is_empty() { + body.compile(ctx, buf); + } else { + code!(buf, ctx; "(()=>{" thunks "return " body "})()"); } } -impl Compile for With<'_> { - fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { - let namespace = self.namespace; - let body = self.body; - let has_thunks = !self.thunks.is_empty(); - if has_thunks { - code!(buf, ctx; "((_w)=>{" self.thunks "return " body "})({env:" namespace ",last:_w})"); - } else { - code!(buf, ctx; "((_w)=>(" body "))({env:" namespace ",last:_w})"); - } +fn compile_with<'ir>( + namespace: RawIrRef<'ir>, + body: RawIrRef<'ir>, + thunks: &[(ThunkId, RawIrRef<'ir>)], + ctx: &impl CodegenContext, + buf: &mut CodeBuffer, +) { + let has_thunks = !thunks.is_empty(); + if has_thunks { + code!(buf, ctx; "((_w)=>{" thunks "return " body "})({env:" namespace ",last:_w})"); + } else { + code!(buf, ctx; "((_w)=>(" body "))({env:" namespace ",last:_w})"); } } -impl Compile for Select<'_> { - fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { - if let Some(default) = self.default { - code!(buf, ctx; - "$sd(" - self.expr - ",[" - joined(self.attrpath.iter(), ",", |ctx: &Ctx, buf, attr| { - match attr { - Attr::Str(sym, _) => code!(buf, ctx; ctx.get_sym(*sym)), - Attr::Dynamic(expr_id, _) => code!(buf, ctx; *expr_id), - } - }) - "]," - default - "," - self.span - ")" - ); - } else { - code!(buf, ctx; - "$s(" - self.expr - ",[" - joined(self.attrpath.iter(), ",", |ctx: &Ctx, buf, attr| { - match attr { - Attr::Str(sym, _) => code!(buf, ctx; ctx.get_sym(*sym)), - Attr::Dynamic(expr, _) => code!(buf, ctx; expr), - } - }) - "]," - self.span - ")" - ); - } - } -} - -impl Compile for AttrSet<'_> { - fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { - if !self.dyns.is_empty() { - code!(buf, ctx; - "$ma(new Map([" - joined(self.stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(val, _))| { - let key = ctx.get_sym(sym); - code!( - buf, ctx; - "[" key "," val "]" - ); - }) - "]),new Map([" - joined(self.stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(_, span))| { - code!(buf, ctx; "[" ctx.get_sym(sym) "," span "]"); - }) - "]),{dynKeys:[" - joined(self.dyns.iter(), ",", |ctx: &Ctx, buf, (key, _, _)| { - code!(buf, ctx; key); - }) - "],dynVals:[" - joined(self.dyns.iter(), ",", |ctx: &Ctx, buf, (_, val, _)| { - code!(buf, ctx; 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; - "$ma(new Map([" - joined(self.stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(val, _))| { - let key = ctx.get_sym(sym); - code!( - buf, ctx; - "[" key "," val "]" - ); - }) - "]),new Map([" - joined(self.stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(_, span))| { - code!(buf, ctx; "[" ctx.get_sym(sym) "," span "]"); - }) - "]))" - ); - } else { - code!(buf, ctx; "$e"); - } - } -} - -impl Compile for List<'_> { - fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { +fn compile_select<'ir, Ctx: CodegenContext>( + expr: RawIrRef<'ir>, + attrpath: &[Attr>], + default: Option>, + span: TextRange, + ctx: &Ctx, + buf: &mut CodeBuffer, +) { + if let Some(default) = default { code!(buf, ctx; - "[" - joined(self.items.iter(), ",", |ctx: &Ctx, buf, item| { - code!(buf, ctx; item); - }) - "]" - ); - } -} - -impl Compile for ConcatStrings<'_> { - fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { - code!(buf, ctx; - "$cs([" - joined(self.parts.iter(), ",", |ctx: &Ctx, buf, part| { - code!(buf, ctx; part); - }) - "]," self.force_string ")" - ); - } -} - -impl Compile for HasAttr<'_> { - fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { - code!(buf, ctx; - "$h(" - self.lhs + "$sd(" + expr ",[" - joined(self.rhs.iter(), ",", |ctx: &Ctx, buf, attr| { + joined(attrpath.iter(), ",", |ctx: &Ctx, buf, attr| { + match attr { + Attr::Str(sym, _) => code!(buf, ctx; ctx.get_sym(*sym)), + Attr::Dynamic(expr_id, _) => code!(buf, ctx; *expr_id), + } + }) + "]," + default + "," + span + ")" + ); + } else { + code!(buf, ctx; + "$s(" + expr + ",[" + joined(attrpath.iter(), ",", |ctx: &Ctx, buf, attr| { match attr { Attr::Str(sym, _) => code!(buf, ctx; ctx.get_sym(*sym)), Attr::Dynamic(expr, _) => code!(buf, ctx; expr), } }) - "])" + "]," + span + ")" ); } } + +fn compile_attrset<'ir, Ctx: CodegenContext>( + stcs: &HashMap<'ir, SymId, (RawIrRef<'ir>, TextRange)>, + dyns: &[(RawIrRef<'ir>, RawIrRef<'ir>, TextRange)], + ctx: &Ctx, + buf: &mut CodeBuffer, +) { + if !dyns.is_empty() { + code!(buf, ctx; + "$ma(new Map([" + joined(stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(val, _))| { + let key = ctx.get_sym(sym); + code!( + buf, ctx; + "[" key "," val "]" + ); + }) + "]),new Map([" + joined(stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(_, span))| { + code!(buf, ctx; "[" ctx.get_sym(sym) "," span "]"); + }) + "]),{dynKeys:[" + joined(dyns.iter(), ",", |ctx: &Ctx, buf, (key, _, _)| { + code!(buf, ctx; key); + }) + "],dynVals:[" + joined(dyns.iter(), ",", |ctx: &Ctx, buf, (_, val, _)| { + code!(buf, ctx; val); + }) + "],dynSpans:[" + joined(dyns.iter(), ",", |ctx: &Ctx, buf, (_, _, attr_span)| { + code!(buf, ctx; attr_span); + }) + "]})" + ); + } else if !stcs.is_empty() { + code!(buf, ctx; + "$ma(new Map([" + joined(stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(val, _))| { + let key = ctx.get_sym(sym); + code!( + buf, ctx; + "[" key "," val "]" + ); + }) + "]),new Map([" + joined(stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(_, span))| { + code!(buf, ctx; "[" ctx.get_sym(sym) "," span "]"); + }) + "]))" + ); + } else { + code!(buf, ctx; "$e"); + } +} + +fn compile_list(items: &[RawIrRef<'_>], ctx: &Ctx, buf: &mut CodeBuffer) { + code!(buf, ctx; + "[" + joined(items.iter(), ",", |ctx: &Ctx, buf, item| { + code!(buf, ctx; item); + }) + "]" + ); +} + +fn compile_concat_strings( + parts: &[RawIrRef<'_>], + force_string: bool, + ctx: &Ctx, + buf: &mut CodeBuffer, +) { + code!(buf, ctx; + "$cs([" + joined(parts.iter(), ",", |ctx: &Ctx, buf, part| { + code!(buf, ctx; part); + }) + "]," force_string ")" + ); +} + +fn compile_has_attr<'ir, Ctx: CodegenContext>( + lhs: RawIrRef<'ir>, + rhs: &[Attr>], + ctx: &Ctx, + buf: &mut CodeBuffer, +) { + code!(buf, ctx; + "$h(" + lhs + ",[" + joined(rhs.iter(), ",", |ctx: &Ctx, buf, attr| { + match attr { + Attr::Str(sym, _) => code!(buf, ctx; ctx.get_sym(*sym)), + Attr::Dynamic(expr, _) => code!(buf, ctx; expr), + } + }) + "])" + ); +} diff --git a/nix-js/src/context.rs b/nix-js/src/context.rs index a59a31d..76acc33 100644 --- a/nix-js/src/context.rs +++ b/nix-js/src/context.rs @@ -1,18 +1,17 @@ use std::cell::UnsafeCell; +use std::hash::BuildHasher; use std::path::Path; use bumpalo::Bump; -use hashbrown::{DefaultHashBuilder, HashMap, HashSet}; +use ghost_cell::{GhostCell, GhostToken}; +use hashbrown::{DefaultHashBuilder, HashMap, HashSet, HashTable}; use rnix::TextRange; use string_interner::DefaultStringInterner; use crate::codegen::{CodegenContext, compile}; use crate::downgrade::*; use crate::error::{Error, Result, Source}; -use crate::ir::{ - Arg, ArgId, Bool, Builtin, Ir, IrKey, IrRef, Null, ReplBinding, ScopedImportBinding, SymId, - Thunk, ThunkId, ToIr as _, WithLookup, -}; +use crate::ir::{ArgId, Ir, IrKey, IrRef, RawIrRef, SymId, ThunkId, ir_content_eq}; #[cfg(feature = "inspector")] use crate::runtime::inspector::InspectorServer; use crate::runtime::{Runtime, RuntimeContext}; @@ -184,38 +183,36 @@ impl Context { struct Ctx { symbols: DefaultStringInterner, - global: HashMap>, + global: HashMap>>, sources: Vec, store: DaemonStore, spans: UnsafeCell>, thunk_count: usize, } +/// Owns the bump allocator and a read-only reference into it. +/// +/// # Safety +/// The `ir` field points into `_bump`'s storage. We use `'static` as a sentinel +/// lifetime because the struct owns the backing memory. The `as_ref` method +/// re-binds the lifetime to `&self`, preventing use-after-free. struct OwnedIr { _bump: Bump, - ir: &'static Ir<'static>, + ir: RawIrRef<'static>, } impl OwnedIr { - fn as_ref<'ir>(&'ir self) -> IrRef<'ir> { + fn as_ref(&self) -> RawIrRef<'_> { self.ir } } impl Ctx { fn new() -> Result { - use crate::ir::{Builtins, ToIr as _}; - let mut symbols = DefaultStringInterner::new(); let mut global = HashMap::new(); let builtins_sym = symbols.get_or_intern("builtins"); - global.insert( - builtins_sym, - Builtins { - span: TextRange::default(), - } - .to_ir(), - ); + global.insert(builtins_sym, Ir::Builtins); let free_globals = [ "abort", @@ -239,38 +236,14 @@ impl Ctx { "toString", ]; let consts = [ - ( - "true", - Bool { - inner: true, - span: rnix::TextRange::default(), - } - .to_ir(), - ), - ( - "false", - Bool { - inner: false, - span: rnix::TextRange::default(), - } - .to_ir(), - ), - ( - "null", - Null { - span: rnix::TextRange::default(), - } - .to_ir(), - ), + ("true", Ir::Bool(true)), + ("false", Ir::Bool(false)), + ("null", Ir::Null), ]; for name in free_globals { let name = symbols.get_or_intern(name); - let value = Builtin { - inner: name, - span: rnix::TextRange::default(), - } - .to_ir(); + let value = Ir::Builtin(name); global.insert(name, value); } for (name, value) in consts { @@ -291,14 +264,16 @@ impl Ctx { }) } - fn downgrade_ctx<'ctx, 'ir>( + fn downgrade_ctx<'ctx, 'id, 'ir>( &'ctx mut self, bump: &'ir Bump, - extra_scope: Option>, - ) -> DowngradeCtx<'ctx, 'ir> { + token: GhostToken<'id>, + extra_scope: Option>, + ) -> DowngradeCtx<'ctx, 'id, 'ir> { let source = self.get_current_source(); DowngradeCtx::new( bump, + token, &mut self.symbols, &self.global, extra_scope, @@ -325,7 +300,7 @@ impl Ctx { fn downgrade<'ctx>( &'ctx mut self, source: Source, - extra_scope: Option>, + extra_scope: Option>, ) -> Result { tracing::debug!("Parsing Nix expression"); @@ -340,17 +315,19 @@ impl Ctx { .expr() .ok_or_else(|| Error::parse_error("unexpected EOF".into()))?; let bump = Bump::new(); - let ir = self - .downgrade_ctx(&bump, extra_scope) - .downgrade_toplevel(expr)?; - let ir = unsafe { std::mem::transmute::, IrRef<'static>>(ir) }; - Ok(OwnedIr { _bump: bump, ir }) + GhostToken::new(|token| { + let ir = self + .downgrade_ctx(&bump, token, extra_scope) + .downgrade_toplevel(expr)?; + let ir = unsafe { std::mem::transmute::, RawIrRef<'static>>(ir) }; + Ok(OwnedIr { _bump: bump, ir }) + }) } fn compile<'ctx>( &'ctx mut self, source: Source, - extra_scope: Option>, + extra_scope: Option>, ) -> Result { let root = self.downgrade(source, extra_scope)?; tracing::debug!("Generating JavaScript code"); @@ -426,98 +403,104 @@ impl RuntimeContext for Ctx { } } -enum Scope<'ctx, 'ir> { - Global(&'ctx HashMap>), +enum Scope<'ctx> { + Global(&'ctx HashMap>>), Repl(&'ctx HashSet), ScopedImport(HashSet), Let(HashMap), - Param(SymId, IrRef<'ir>), + Param(SymId, ArgId), } -struct ScopeGuard<'a, 'ctx, 'ir> { - ctx: &'a mut DowngradeCtx<'ctx, 'ir>, +struct ScopeGuard<'a, 'ctx, 'id, 'ir> { + ctx: &'a mut DowngradeCtx<'ctx, 'id, 'ir>, } -impl Drop for ScopeGuard<'_, '_, '_> { +impl Drop for ScopeGuard<'_, '_, '_, '_> { fn drop(&mut self) { self.ctx.scopes.pop(); } } -impl<'ir, 'ctx> ScopeGuard<'_, 'ctx, 'ir> { - fn as_ctx(&mut self) -> &mut DowngradeCtx<'ctx, 'ir> { +impl<'id, 'ir, 'ctx> ScopeGuard<'_, 'ctx, 'id, 'ir> { + fn as_ctx(&mut self) -> &mut DowngradeCtx<'ctx, 'id, 'ir> { self.ctx } } -struct ThunkScope<'ir> { - bindings: bumpalo::collections::Vec<'ir, (ThunkId, IrRef<'ir>)>, - cache: HashMap, ThunkId>, +struct ThunkScope<'id, 'ir> { + bindings: bumpalo::collections::Vec<'ir, (ThunkId, IrRef<'id, 'ir>)>, + cache: HashTable<(IrRef<'id, 'ir>, ThunkId)>, hasher: DefaultHashBuilder, } -impl<'ir> ThunkScope<'ir> { +impl<'id, 'ir> ThunkScope<'id, 'ir> { fn new_in(bump: &'ir Bump) -> Self { Self { bindings: bumpalo::collections::Vec::new_in(bump), - cache: HashMap::new(), + cache: HashTable::new(), hasher: DefaultHashBuilder::default(), } } - fn lookup_cache(&self, key: IrKey<'ir>) -> Option { - self.cache.get(&key).copied() + fn lookup_cache(&self, key: IrRef<'id, 'ir>, token: &GhostToken<'id>) -> Option { + let hash = self.hasher.hash_one(IrKey(key, token)); + self.cache + .find(hash, |&(ir, _)| ir_content_eq(key, ir, token)) + .map(|&(_, id)| id) } - fn add_binding(&mut self, id: ThunkId, ir: IrRef<'ir>) { + fn add_binding(&mut self, id: ThunkId, ir: IrRef<'id, 'ir>, token: &GhostToken<'id>) { self.bindings.push((id, ir)); + let hash = self.hasher.hash_one(IrKey(ir, token)); + self.cache.insert_unique(hash, (ir, id), |&(ir, _)| { + self.hasher.hash_one(IrKey(ir, token)) + }); } - fn extend_bindings(&mut self, iter: impl IntoIterator)>) { + fn extend_bindings(&mut self, iter: impl IntoIterator)>) { self.bindings.extend(iter); } - - fn add_cache(&mut self, key: IrKey<'ir>, cache: ThunkId) { - self.cache.insert(key, cache); - } } -struct DowngradeCtx<'ctx, 'ir> { +struct DowngradeCtx<'ctx, 'id, 'ir> { bump: &'ir Bump, + token: GhostToken<'id>, symbols: &'ctx mut DefaultStringInterner, source: Source, - scopes: Vec>, + scopes: Vec>, with_scope_count: usize, arg_count: usize, thunk_count: &'ctx mut usize, - thunk_scopes: Vec>, + thunk_scopes: Vec>, } -fn should_thunk(ir: IrRef<'_>) -> bool { +fn should_thunk<'id>(ir: IrRef<'id, '_>, token: &GhostToken<'id>) -> bool { !matches!( - ir, + ir.borrow(token), Ir::Builtin(_) - | Ir::Builtins(_) + | Ir::Builtins | Ir::Int(_) | Ir::Float(_) | Ir::Bool(_) - | Ir::Null(_) + | Ir::Null | Ir::Str(_) | Ir::Thunk(_) ) } -impl<'ctx, 'ir> DowngradeCtx<'ctx, 'ir> { +impl<'ctx, 'id, 'ir> DowngradeCtx<'ctx, 'id, 'ir> { fn new( bump: &'ir Bump, + token: GhostToken<'id>, symbols: &'ctx mut DefaultStringInterner, - global: &'ctx HashMap>, - extra_scope: Option>, + global: &'ctx HashMap>>, + extra_scope: Option>, thunk_count: &'ctx mut usize, source: Source, ) -> Self { Self { bump, + token, symbols, source, scopes: std::iter::once(Scope::Global(global)) @@ -531,44 +514,38 @@ impl<'ctx, 'ir> DowngradeCtx<'ctx, 'ir> { } } -impl<'ctx: 'ir, 'ir> DowngradeContext<'ir> for DowngradeCtx<'ctx, 'ir> { - fn new_expr(&self, expr: Ir<'ir>) -> IrRef<'ir> { - self.bump.alloc(expr) +impl<'ctx: 'ir, 'id, 'ir> DowngradeContext<'id, 'ir> for DowngradeCtx<'ctx, 'id, 'ir> { + fn new_expr(&self, expr: Ir<'ir, IrRef<'id, 'ir>>) -> IrRef<'id, 'ir> { + IrRef::new(self.bump.alloc(GhostCell::new(expr))) } - fn new_arg(&mut self, span: TextRange) -> IrRef<'ir> { + fn new_arg(&mut self) -> ArgId { self.arg_count += 1; - self.bump.alloc( - Arg { - inner: ArgId(self.arg_count - 1), - span, - } - .to_ir(), - ) + ArgId(self.arg_count - 1) } - fn maybe_thunk(&mut self, ir: IrRef<'ir>) -> IrRef<'ir> { - if should_thunk(ir) { - let scope = self.thunk_scopes.last_mut().expect("no active cache scope"); - let key = IrKey(ir); - if let Some(id) = scope.lookup_cache(key) { - return self.new_expr( - Thunk { - inner: id, - span: ir.span(), - } - .to_ir(), - ); - } - let span = ir.span(); - let id = ThunkId(*self.thunk_count); - *self.thunk_count += 1; - scope.add_binding(id, ir); - scope.add_cache(key, id); - self.new_expr(Thunk { inner: id, span }.to_ir()) - } else { - ir + fn maybe_thunk(&mut self, ir: IrRef<'id, 'ir>) -> IrRef<'id, 'ir> { + if !should_thunk(ir, &self.token) { + return ir; } + + let cached = self + .thunk_scopes + .last() + .expect("no active cache scope") + .lookup_cache(ir, &self.token); + + if let Some(id) = cached { + return IrRef::alloc(self.bump, Ir::Thunk(id)); + } + + let id = ThunkId(*self.thunk_count); + *self.thunk_count = self.thunk_count.checked_add(1).expect("thunk id overflow"); + self.thunk_scopes + .last_mut() + .expect("no active cache scope") + .add_binding(id, ir, &self.token); + IrRef::alloc(self.bump, Ir::Thunk(id)) } fn new_sym(&mut self, sym: String) -> SymId { @@ -579,39 +556,46 @@ impl<'ctx: 'ir, 'ir> DowngradeContext<'ir> for DowngradeCtx<'ctx, 'ir> { self.symbols.resolve(id).expect("no symbol found").into() } - fn lookup(&self, sym: SymId, span: TextRange) -> Result> { + fn lookup(&self, sym: SymId, span: TextRange) -> Result> { for scope in self.scopes.iter().rev() { match scope { &Scope::Global(global_scope) => { if let Some(expr) = global_scope.get(&sym) { - return Ok(expr); + let ir = match expr { + Ir::Builtins => Ir::Builtins, + Ir::Builtin(s) => Ir::Builtin(*s), + Ir::Bool(b) => Ir::Bool(*b), + Ir::Null => Ir::Null, + _ => unreachable!("globals should only contain leaf IR nodes"), + }; + return Ok(self.new_expr(ir)); } } &Scope::Repl(repl_bindings) => { if repl_bindings.contains(&sym) { - return Ok(self.new_expr(ReplBinding { inner: sym, span }.to_ir())); + return Ok(self.new_expr(Ir::ReplBinding(sym))); } } Scope::ScopedImport(scoped_bindings) => { if scoped_bindings.contains(&sym) { - return Ok(self.new_expr(ScopedImportBinding { inner: sym, span }.to_ir())); + return Ok(self.new_expr(Ir::ScopedImportBinding(sym))); } } Scope::Let(let_scope) => { if let Some(&expr) = let_scope.get(&sym) { - return Ok(self.new_expr(Thunk { inner: expr, span }.to_ir())); + return Ok(self.new_expr(Ir::Thunk(expr))); } } - &Scope::Param(param_sym, expr) => { + &Scope::Param(param_sym, id) => { if param_sym == sym { - return Ok(expr); + return Ok(self.new_expr(Ir::Arg(id))); } } } } if self.with_scope_count > 0 { - Ok(self.new_expr(WithLookup { inner: sym, span }.to_ir())) + Ok(self.new_expr(Ir::WithLookup(sym))) } else { Err(Error::downgrade_error( format!("'{}' not found", self.get_sym(sym)), @@ -627,14 +611,19 @@ impl<'ctx: 'ir, 'ir> DowngradeContext<'ir> for DowngradeCtx<'ctx, 'ir> { fn with_let_scope(&mut self, keys: &[SymId], f: F) -> Result where - F: FnOnce(&mut Self) -> Result<(bumpalo::collections::Vec<'ir, IrRef<'ir>>, R)>, + F: FnOnce(&mut Self) -> Result<(bumpalo::collections::Vec<'ir, IrRef<'id, 'ir>>, R)>, { let base = *self.thunk_count; - *self.thunk_count += keys.len(); - let iter = keys - .iter() - .enumerate() - .map(|(offset, &key)| (key, ThunkId(base + offset))); + *self.thunk_count = self + .thunk_count + .checked_add(keys.len()) + .expect("thunk id overflow"); + let iter = keys.iter().enumerate().map(|(offset, &key)| { + ( + key, + ThunkId(unsafe { base.checked_add(offset).unwrap_unchecked() }), + ) + }); self.scopes.push(Scope::Let(iter.collect())); let (vals, ret) = { let mut guard = ScopeGuard { ctx: self }; @@ -646,7 +635,7 @@ impl<'ctx: 'ir, 'ir> DowngradeContext<'ir> for DowngradeCtx<'ctx, 'ir> { Ok(ret) } - fn with_param_scope(&mut self, param: SymId, arg: IrRef<'ir>, f: F) -> R + fn with_param_scope(&mut self, param: SymId, arg: ArgId, f: F) -> R where F: FnOnce(&mut Self) -> R, { @@ -668,7 +657,10 @@ impl<'ctx: 'ir, 'ir> DowngradeContext<'ir> for DowngradeCtx<'ctx, 'ir> { fn with_thunk_scope( &mut self, f: F, - ) -> (R, bumpalo::collections::Vec<'ir, (ThunkId, IrRef<'ir>)>) + ) -> ( + R, + bumpalo::collections::Vec<'ir, (ThunkId, IrRef<'id, 'ir>)>, + ) where F: FnOnce(&mut Self) -> R, { @@ -688,16 +680,15 @@ impl<'ctx: 'ir, 'ir> DowngradeContext<'ir> for DowngradeCtx<'ctx, 'ir> { } } -impl<'ir, 'ctx: 'ir> DowngradeCtx<'ctx, 'ir> { - fn downgrade_toplevel(mut self, root: rnix::ast::Expr) -> Result> { - use crate::ir::TopLevel; +impl<'id, 'ir, 'ctx: 'ir> DowngradeCtx<'ctx, 'id, 'ir> { + fn downgrade_toplevel(mut self, root: rnix::ast::Expr) -> Result> { let body = root.downgrade(&mut self)?; - let span = body.span(); let thunks = self .thunk_scopes .pop() .expect("no thunk scope left???") .bindings; - Ok(self.new_expr(TopLevel { body, thunks, span }.to_ir())) + let ir = IrRef::alloc(self.bump, Ir::TopLevel { body, thunks }); + Ok(ir.freeze(self.token)) } } diff --git a/nix-js/src/downgrade.rs b/nix-js/src/downgrade.rs index 1a5f95f..6bc8477 100644 --- a/nix-js/src/downgrade.rs +++ b/nix-js/src/downgrade.rs @@ -3,18 +3,18 @@ use bumpalo::collections::{CollectIn, Vec}; use hashbrown::HashSet; use hashbrown::hash_map::Entry; use rnix::TextRange; -use rnix::ast::{self, AstToken, Expr, HasEntry}; +use rnix::ast::{self, Expr, HasEntry}; use rowan::ast::AstNode; use crate::error::{Error, Result, Source}; use crate::ir::*; use crate::value::Symbol; -trait Require<'ir, Ctx: DowngradeContext<'ir>, T> { +trait Require<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>, T> { fn require(self, ctx: &Ctx, span: TextRange) -> Result; } -impl<'ir, Ctx: DowngradeContext<'ir>, T> Require<'ir, Ctx, T> for Option { +impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>, T> Require<'id, 'ir, Ctx, T> for Option { #[inline] fn require(self, ctx: &Ctx, span: TextRange) -> Result { self.ok_or_else(|| { @@ -25,7 +25,7 @@ impl<'ir, Ctx: DowngradeContext<'ir>, T> Require<'ir, Ctx, T> for Option { } } -impl<'ir, Ctx: DowngradeContext<'ir>, T, E: std::fmt::Display> Require<'ir, Ctx, T> +impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>, T, E: std::fmt::Display> Require<'id, 'ir, Ctx, T> for std::result::Result { #[inline] @@ -45,39 +45,39 @@ trait BoxIn: Sized { } impl BoxIn for T {} -pub trait DowngradeContext<'ir> { - fn new_expr(&self, expr: Ir<'ir>) -> IrRef<'ir>; - fn new_arg(&mut self, span: TextRange) -> IrRef<'ir>; - fn maybe_thunk(&mut self, ir: IrRef<'ir>) -> IrRef<'ir>; +pub trait DowngradeContext<'id: 'ir, 'ir> { + fn new_expr(&self, expr: Ir<'ir, IrRef<'id, 'ir>>) -> IrRef<'id, 'ir>; + fn new_arg(&mut self) -> ArgId; + fn maybe_thunk(&mut self, ir: IrRef<'id, 'ir>) -> IrRef<'id, 'ir>; fn new_sym(&mut self, sym: String) -> SymId; fn get_sym(&self, id: SymId) -> Symbol<'_>; - fn lookup(&self, sym: SymId, span: TextRange) -> Result>; + fn lookup(&self, sym: SymId, span: TextRange) -> Result>; fn get_current_source(&self) -> Source; - fn with_param_scope(&mut self, param: SymId, arg: IrRef<'ir>, f: F) -> R + fn with_param_scope(&mut self, param: SymId, arg: ArgId, f: F) -> R where F: FnOnce(&mut Self) -> R; fn with_let_scope(&mut self, bindings: &[SymId], f: F) -> Result where - F: FnOnce(&mut Self) -> Result<(Vec<'ir, IrRef<'ir>>, R)>; + F: FnOnce(&mut Self) -> Result<(Vec<'ir, IrRef<'id, 'ir>>, R)>; fn with_with_scope(&mut self, f: F) -> R where F: FnOnce(&mut Self) -> R; - fn with_thunk_scope(&mut self, f: F) -> (R, Vec<'ir, (ThunkId, IrRef<'ir>)>) + fn with_thunk_scope(&mut self, f: F) -> (R, Vec<'ir, (ThunkId, IrRef<'id, 'ir>)>) where F: FnOnce(&mut Self) -> R; fn bump(&self) -> &'ir bumpalo::Bump; } -pub trait Downgrade<'ir, Ctx: DowngradeContext<'ir>> { - fn downgrade(self, ctx: &mut Ctx) -> Result>; +pub trait Downgrade<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> { + fn downgrade(self, ctx: &mut Ctx) -> Result>; } -impl<'ir, Ctx: DowngradeContext<'ir>> Downgrade<'ir, Ctx> for Expr { - fn downgrade(self, ctx: &mut Ctx) -> Result> { +impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for Expr { + fn downgrade(self, ctx: &mut Ctx) -> Result> { use Expr::*; match self { Apply(apply) => apply.downgrade(ctx), @@ -106,12 +106,7 @@ impl<'ir, Ctx: DowngradeContext<'ir>> Downgrade<'ir, Ctx> for Expr { AttrSet(attrs) => attrs.downgrade(ctx), UnaryOp(op) => op.downgrade(ctx), Ident(ident) => ident.downgrade(ctx), - CurPos(curpos) => Ok(ctx.new_expr( - self::CurPos { - span: curpos.syntax().text_range(), - } - .to_ir(), - )), + CurPos(curpos) => Ok(ctx.new_expr(Ir::CurPos(curpos.syntax().text_range()))), With(with) => with.downgrade(ctx), HasAttr(has) => has.downgrade(ctx), Paren(paren) => paren @@ -126,47 +121,36 @@ impl<'ir, Ctx: DowngradeContext<'ir>> Downgrade<'ir, Ctx> for Expr { } } -impl<'ir, Ctx: DowngradeContext<'ir>> Downgrade<'ir, Ctx> for ast::Assert { - fn downgrade(self, ctx: &mut Ctx) -> Result> { +impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::Assert { + fn downgrade(self, ctx: &mut Ctx) -> Result> { let span = self.syntax().text_range(); let assertion = self.condition().require(ctx, span)?; let assertion_raw = assertion.to_string(); let assertion = assertion.downgrade(ctx)?; let expr = self.body().require(ctx, span)?.downgrade(ctx)?; - Ok(ctx.new_expr( - Assert { - assertion, - expr, - assertion_raw, - span, - } - .to_ir(), - )) + Ok(ctx.new_expr(Ir::Assert { + assertion, + expr, + assertion_raw, + span, + })) } } -impl<'ir, Ctx: DowngradeContext<'ir>> Downgrade<'ir, Ctx> for ast::IfElse { - fn downgrade(self, ctx: &mut Ctx) -> Result> { +impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::IfElse { + fn downgrade(self, ctx: &mut Ctx) -> Result> { let span = self.syntax().text_range(); let cond = self.condition().require(ctx, span)?.downgrade(ctx)?; let consq = self.body().require(ctx, span)?.downgrade(ctx)?; let alter = self.else_body().require(ctx, span)?.downgrade(ctx)?; - Ok(ctx.new_expr( - If { - cond, - consq, - alter, - span, - } - .to_ir(), - )) + Ok(ctx.new_expr(Ir::If { cond, consq, alter })) } } macro_rules! path { ($ty:ident) => { - impl<'ir, Ctx: DowngradeContext<'ir>> Downgrade<'ir, Ctx> for ast::$ty { - fn downgrade(self, ctx: &mut Ctx) -> Result> { + impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::$ty { + fn downgrade(self, ctx: &mut Ctx) -> Result> { let span = self.syntax().text_range(); downgrade_path(self.parts(), span, ctx) } @@ -176,46 +160,35 @@ macro_rules! path { path!(PathAbs); path!(PathRel); path!(PathHome); -impl<'ir, Ctx: DowngradeContext<'ir>> Downgrade<'ir, Ctx> for ast::PathSearch { - fn downgrade(self, ctx: &mut Ctx) -> Result> { +impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::PathSearch { + fn downgrade(self, ctx: &mut Ctx) -> Result> { let span = self.syntax().text_range(); let path = { let temp = self.content().require(ctx, span)?; let text = temp.text(); - ctx.new_expr( - Str { - inner: text[1..text.len() - 1].to_string().box_in(ctx.bump()), - span, - } - .to_ir(), - ) + ctx.new_expr(Ir::Str( + text[1..text.len() - 1].to_string().box_in(ctx.bump()), + )) }; let sym = ctx.new_sym("findFile".into()); - let find_file = ctx.new_expr(Builtin { inner: sym, span }.to_ir()); + let find_file = ctx.new_expr(Ir::Builtin(sym)); let sym = ctx.new_sym("nixPath".into()); - let nix_path = ctx.new_expr(Builtin { inner: sym, span }.to_ir()); - let call = ctx.new_expr( - Call { - func: find_file, - arg: nix_path, - span, - } - .to_ir(), - ); - Ok(ctx.new_expr( - Call { - func: call, - arg: path, - span, - } - .to_ir(), - )) + let nix_path = ctx.new_expr(Ir::Builtin(sym)); + let call = ctx.new_expr(Ir::Call { + func: find_file, + arg: nix_path, + span, + }); + Ok(ctx.new_expr(Ir::Call { + func: call, + arg: path, + span, + })) } } -impl<'ir, Ctx: DowngradeContext<'ir>> Downgrade<'ir, Ctx> for ast::Str { - fn downgrade(self, ctx: &mut Ctx) -> Result> { - let span = self.syntax().text_range(); +impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::Str { + fn downgrade(self, ctx: &mut Ctx) -> Result> { let normalized = self.normalized_parts(); let is_single_literal = normalized.len() == 1 && matches!(normalized.first(), Some(ast::InterpolPart::Literal(_))); @@ -224,13 +197,7 @@ impl<'ir, Ctx: DowngradeContext<'ir>> Downgrade<'ir, Ctx> for ast::Str { let parts = normalized .into_iter() .map(|part| match part { - ast::InterpolPart::Literal(lit) => Ok(ctx.new_expr( - Str { - inner: lit.box_in(bump), - span, - } - .to_ir(), - )), + ast::InterpolPart::Literal(lit) => Ok(ctx.new_expr(Ir::Str(lit.box_in(bump)))), ast::InterpolPart::Interpolation(interpol) => { let inner = interpol .expr() @@ -244,43 +211,27 @@ impl<'ir, Ctx: DowngradeContext<'ir>> Downgrade<'ir, Ctx> for ast::Str { Ok(if is_single_literal { parts.into_iter().next().expect("is_single_literal checked") } else { - ctx.new_expr( - ConcatStrings { - parts, - span, - force_string: true, - } - .to_ir(), - ) + ctx.new_expr(Ir::ConcatStrings { + parts, + force_string: true, + }) }) } } -impl<'ir, Ctx: DowngradeContext<'ir>> Downgrade<'ir, Ctx> for ast::Literal { - fn downgrade(self, ctx: &mut Ctx) -> Result> { +impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::Literal { + fn downgrade(self, ctx: &mut Ctx) -> Result> { let span = self.syntax().text_range(); Ok(ctx.new_expr(match self.kind() { - ast::LiteralKind::Integer(int) => Int { - inner: int.value().require(ctx, span)?, - span, - } - .to_ir(), - ast::LiteralKind::Float(float) => Float { - inner: float.value().require(ctx, span)?, - span, - } - .to_ir(), - ast::LiteralKind::Uri(uri) => Str { - inner: uri.to_string().box_in(ctx.bump()), - span, - } - .to_ir(), + ast::LiteralKind::Integer(int) => Ir::Int(int.value().require(ctx, span)?), + ast::LiteralKind::Float(float) => Ir::Float(float.value().require(ctx, span)?), + ast::LiteralKind::Uri(uri) => Ir::Str(uri.to_string().box_in(ctx.bump())), })) } } -impl<'ir, Ctx: DowngradeContext<'ir>> Downgrade<'ir, Ctx> for ast::Ident { - fn downgrade(self, ctx: &mut Ctx) -> Result> { +impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::Ident { + fn downgrade(self, ctx: &mut Ctx) -> Result> { let span = self.syntax().text_range(); let text = self.ident_token().require(ctx, span)?.to_string(); let sym = ctx.new_sym(text); @@ -288,25 +239,24 @@ impl<'ir, Ctx: DowngradeContext<'ir>> Downgrade<'ir, Ctx> for ast::Ident { } } -impl<'ir, Ctx: DowngradeContext<'ir>> Downgrade<'ir, Ctx> for ast::AttrSet { - fn downgrade(self, ctx: &mut Ctx) -> Result> { +impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::AttrSet { + fn downgrade(self, ctx: &mut Ctx) -> Result> { let rec = self.rec_token().is_some(); - let span = self.syntax().text_range(); - if !rec { let attrs = downgrade_attrs(self, ctx)?; - return Ok(ctx.new_expr(attrs.to_ir())); + return Ok(ctx.new_expr(Ir::AttrSet { + stcs: attrs.stcs, + dyns: attrs.dyns, + })); } - // rec { a = 1; b = a; } => let a = 1; b = a; in { inherit a b; } let entries: Vec<'ir, _> = self.entries().collect_in(ctx.bump()); - downgrade_rec_bindings(entries, ctx, span) + downgrade_rec_bindings(entries, ctx) } } -/// Downgrades a list. -impl<'ir, Ctx: DowngradeContext<'ir>> Downgrade<'ir, Ctx> for ast::List { - fn downgrade(self, ctx: &mut Ctx) -> Result> { +impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::List { + fn downgrade(self, ctx: &mut Ctx) -> Result> { let bump = ctx.bump(); let items = self .items() @@ -315,53 +265,40 @@ impl<'ir, Ctx: DowngradeContext<'ir>> Downgrade<'ir, Ctx> for ast::List { Ok(ctx.maybe_thunk(id)) }) .collect_in::>(bump)?; - let span = self.syntax().text_range(); - Ok(ctx.new_expr(List { items, span }.to_ir())) + Ok(ctx.new_expr(Ir::List { items })) } } -/// Downgrades a binary operation. -impl<'ir, Ctx: DowngradeContext<'ir>> Downgrade<'ir, Ctx> for ast::BinOp { - fn downgrade(self, ctx: &mut Ctx) -> Result> { +impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::BinOp { + fn downgrade(self, ctx: &mut Ctx) -> Result> { let span = self.syntax().text_range(); let lhs = self.lhs().require(ctx, span)?.downgrade(ctx)?; let rhs = self.rhs().require(ctx, span)?.downgrade(ctx)?; let kind = self.operator().require(ctx, span)?.into(); - Ok(ctx.new_expr( - BinOp { - lhs, - rhs, - kind, - span, - } - .to_ir(), - )) + Ok(ctx.new_expr(Ir::BinOp { lhs, rhs, kind })) } } -/// Downgrades a "has attribute" (`?`) expression. -impl<'ir, Ctx: DowngradeContext<'ir>> Downgrade<'ir, Ctx> for ast::HasAttr { - fn downgrade(self, ctx: &mut Ctx) -> Result> { +impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::HasAttr { + fn downgrade(self, ctx: &mut Ctx) -> Result> { let span = self.syntax().text_range(); let lhs = self.expr().require(ctx, span)?.downgrade(ctx)?; let rhs = downgrade_attrpath(self.attrpath().require(ctx, span)?, ctx)?; - Ok(ctx.new_expr(HasAttr { lhs, rhs, span }.to_ir())) + Ok(ctx.new_expr(Ir::HasAttr { lhs, rhs })) } } -/// Downgrades a unary operation. -impl<'ir, Ctx: DowngradeContext<'ir>> Downgrade<'ir, Ctx> for ast::UnaryOp { - fn downgrade(self, ctx: &mut Ctx) -> Result> { +impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::UnaryOp { + fn downgrade(self, ctx: &mut Ctx) -> Result> { let span = self.syntax().text_range(); let rhs = self.expr().require(ctx, span)?.downgrade(ctx)?; let kind = self.operator().require(ctx, span)?.into(); - Ok(ctx.new_expr(UnOp { rhs, kind, span }.to_ir())) + Ok(ctx.new_expr(Ir::UnOp { rhs, kind })) } } -/// Downgrades an attribute selection (`.`). -impl<'ir, Ctx: DowngradeContext<'ir>> Downgrade<'ir, Ctx> for ast::Select { - fn downgrade(self, ctx: &mut Ctx) -> Result> { +impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::Select { + fn downgrade(self, ctx: &mut Ctx) -> Result> { let span = self.syntax().text_range(); let expr = self.expr().require(ctx, span)?.downgrade(ctx)?; let attrpath = downgrade_attrpath(self.attrpath().require(ctx, span)?, ctx)?; @@ -372,42 +309,33 @@ impl<'ir, Ctx: DowngradeContext<'ir>> Downgrade<'ir, Ctx> for ast::Select { None }; let span = self.syntax().text_range(); - Ok(ctx.new_expr( - Select { - expr, - attrpath, - default, - span, - } - .to_ir(), - )) + Ok(ctx.new_expr(Ir::Select { + expr, + attrpath, + default, + span, + })) } } -/// Downgrades a `legacy let`, which is essentially a recursive attribute set. -/// The body of the `let` is accessed via `let.body`. -impl<'ir, Ctx: DowngradeContext<'ir>> Downgrade<'ir, Ctx> for ast::LegacyLet { - fn downgrade(self, ctx: &mut Ctx) -> Result> { +impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::LegacyLet { + fn downgrade(self, ctx: &mut Ctx) -> Result> { let span = self.syntax().text_range(); let entries: Vec<'ir, _> = self.entries().collect_in(ctx.bump()); - let attrset_expr = downgrade_let_bindings(entries, ctx, span, |ctx, binding_keys| { - // Create plain attrset as body with inherit - let mut attrs = AttrSet { - stcs: HashMap::new_in(ctx.bump()), - dyns: Vec::new_in(ctx.bump()), - span, - }; + let attrset_expr = downgrade_let_bindings(entries, ctx, |ctx, binding_keys| { + let mut stcs = HashMap::new_in(ctx.bump()); + let dyns = Vec::new_in(ctx.bump()); for sym in binding_keys { let expr = ctx.lookup(*sym, rnix::TextRange::default())?; - attrs.stcs.insert(*sym, (expr, rnix::TextRange::default())); + stcs.insert(*sym, (expr, rnix::TextRange::default())); } - Ok(ctx.new_expr(attrs.to_ir())) + Ok(ctx.new_expr(Ir::AttrSet { stcs, dyns })) })?; let body_sym = ctx.new_sym("body".to_string()); - let select = Select { + Ok(ctx.new_expr(Ir::Select { expr: attrset_expr, attrpath: Vec::from_iter_in( [Attr::Str(body_sym, rnix::TextRange::default())], @@ -415,28 +343,22 @@ impl<'ir, Ctx: DowngradeContext<'ir>> Downgrade<'ir, Ctx> for ast::LegacyLet { ), default: None, span, - }; - - Ok(ctx.new_expr(select.to_ir())) + })) } } -/// Downgrades a `let ... in ...` expression. -impl<'ir, Ctx: DowngradeContext<'ir>> Downgrade<'ir, Ctx> for ast::LetIn { - fn downgrade(self, ctx: &mut Ctx) -> Result> { +impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::LetIn { + fn downgrade(self, ctx: &mut Ctx) -> Result> { let entries: Vec<'ir, _> = self.entries().collect_in(ctx.bump()); let span = self.syntax().text_range(); let body_expr = self.body().require(ctx, span)?; - downgrade_let_bindings(entries, ctx, span, |ctx, _binding_keys| { - body_expr.downgrade(ctx) - }) + downgrade_let_bindings(entries, ctx, |ctx, _binding_keys| body_expr.downgrade(ctx)) } } -/// Downgrades a `with` expression. -impl<'ir, Ctx: DowngradeContext<'ir>> Downgrade<'ir, Ctx> for ast::With { - fn downgrade(self, ctx: &mut Ctx) -> Result> { +impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::With { + fn downgrade(self, ctx: &mut Ctx) -> Result> { let span = self.syntax().text_range(); let namespace = self.namespace().require(ctx, span)?.downgrade(ctx)?; let namespace = ctx.maybe_thunk(namespace); @@ -446,30 +368,24 @@ impl<'ir, Ctx: DowngradeContext<'ir>> Downgrade<'ir, Ctx> for ast::With { ctx.with_thunk_scope(|ctx| ctx.with_with_scope(|ctx| body_expr.downgrade(ctx))); let body = body?; - Ok(ctx.new_expr( - With { - namespace, - body, - thunks, - span, - } - .to_ir(), - )) + Ok(ctx.new_expr(Ir::With { + namespace, + body, + thunks, + })) } } -/// Downgrades a lambda (function) expression. -/// This involves desugaring pattern-matching arguments into `let` bindings. -impl<'ir, Ctx: DowngradeContext<'ir>> Downgrade<'ir, Ctx> for ast::Lambda { - fn downgrade(self, ctx: &mut Ctx) -> Result> { +impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::Lambda { + fn downgrade(self, ctx: &mut Ctx) -> Result> { let span = self.syntax().text_range(); let raw_param = self.param().require(ctx, span)?; let body_ast = self.body().require(ctx, span)?; - let arg = ctx.new_arg(raw_param.syntax().text_range()); + let arg = ctx.new_arg(); - struct Ret<'ir> { + struct Ret<'id, 'ir> { param: Option>, - body: IrRef<'ir>, + body: IrRef<'id, 'ir>, } let (ret, thunks) = ctx.with_thunk_scope(|ctx| { @@ -518,30 +434,23 @@ impl<'ir, Ctx: DowngradeContext<'ir>> Downgrade<'ir, Ctx> for ast::Lambda { }); let Ret { param, body } = ret?; - Ok(ctx.new_expr( - Func { - body, - param, - arg, - thunks, - span, - } - .to_ir(), - )) + Ok(ctx.new_expr(Ir::Func { + body, + param, + arg, + thunks, + })) } } -/// Downgrades a function application. -/// In Nix, function application is left-associative, so `f a b` should be parsed as `((f a) b)`. -/// Each Apply node represents a single function call with one argument. -impl<'ir, Ctx: DowngradeContext<'ir>> Downgrade<'ir, Ctx> for ast::Apply { - fn downgrade(self, ctx: &mut Ctx) -> Result> { +impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::Apply { + fn downgrade(self, ctx: &mut Ctx) -> Result> { let span = self.syntax().text_range(); let func = self.lambda().require(ctx, span)?.downgrade(ctx)?; let arg = self.argument().require(ctx, span)?.downgrade(ctx)?; let arg = ctx.maybe_thunk(arg); let span = self.syntax().text_range(); - Ok(ctx.new_expr(Call { func, arg, span }.to_ir())) + Ok(ctx.new_expr(Ir::Call { func, arg, span })) } } @@ -553,22 +462,19 @@ enum PendingValue<'ir> { ExtendedRecAttrSet { base: ast::AttrSet, extensions: Vec<'ir, ast::Entry>, - span: TextRange, }, } struct PendingAttrSet<'ir> { stcs: HashMap<'ir, SymId, (PendingValue<'ir>, TextRange)>, dyns: Vec<'ir, (ast::Attr, PendingValue<'ir>, TextRange)>, - span: TextRange, } -impl<'ir> PendingAttrSet<'ir> { - fn new_in(bump: &'ir bumpalo::Bump, span: TextRange) -> Self { +impl<'id: 'ir, 'ir> PendingAttrSet<'ir> { + fn new_in(bump: &'ir bumpalo::Bump) -> Self { Self { stcs: HashMap::new_in(bump), dyns: Vec::new_in(bump), - span, } } @@ -576,7 +482,7 @@ impl<'ir> PendingAttrSet<'ir> { &mut self, path: &[ast::Attr], value: ast::Expr, - ctx: &mut impl DowngradeContext<'ir>, + ctx: &mut impl DowngradeContext<'id, 'ir>, ) -> Result<()> { let first = path.first().expect("empty attrpath passed. this is a bug"); let rest = &path[1..]; @@ -608,7 +514,7 @@ impl<'ir> PendingAttrSet<'ir> { span: TextRange, path: &[ast::Attr], value: ast::Expr, - ctx: &mut impl DowngradeContext<'ir>, + ctx: &mut impl DowngradeContext<'id, 'ir>, ) -> Result<()> { if !path.is_empty() { match self.stcs.entry(sym) { @@ -619,7 +525,6 @@ impl<'ir> PendingAttrSet<'ir> { && attrset.rec_token().is_some() { let base = attrset.clone(); - let base_span = attrset.syntax().text_range(); let ext_entry = make_attrpath_value_entry( Vec::from_iter_in(path.iter().cloned(), ctx.bump()), value, @@ -627,7 +532,6 @@ impl<'ir> PendingAttrSet<'ir> { *existing = PendingValue::ExtendedRecAttrSet { base, extensions: Vec::from_iter_in([ext_entry], ctx.bump()), - span: base_span, }; return Ok(()); } @@ -645,7 +549,7 @@ impl<'ir> PendingAttrSet<'ir> { nested.insert(path, value, ctx) } Entry::Vacant(entry) => { - let mut nested = PendingAttrSet::new_in(ctx.bump(), span); + let mut nested = PendingAttrSet::new_in(ctx.bump()); nested.insert(path, value, ctx)?; entry.insert((PendingValue::Set(nested), span)); Ok(()) @@ -671,10 +575,10 @@ impl<'ir> PendingAttrSet<'ir> { span: TextRange, path: &[ast::Attr], value: ast::Expr, - ctx: &mut impl DowngradeContext<'ir>, + ctx: &mut impl DowngradeContext<'id, 'ir>, ) -> Result<()> { if !path.is_empty() { - let mut nested = PendingAttrSet::new_in(ctx.bump(), span); + let mut nested = PendingAttrSet::new_in(ctx.bump()); nested.insert_dynamic( path[0].clone(), path[0].syntax().text_range(), @@ -691,15 +595,14 @@ impl<'ir> PendingAttrSet<'ir> { fn ensure_pending_set<'a>( value: &'a mut PendingValue<'ir>, - ctx: &mut impl DowngradeContext<'ir>, + ctx: &mut impl DowngradeContext<'id, 'ir>, span: TextRange, ) -> Result<&'a mut PendingAttrSet<'ir>> { match value { PendingValue::Set(set) => Ok(set), PendingValue::Expr(expr) => { if let ast::Expr::AttrSet(attrset) = expr { - let mut nested = - PendingAttrSet::new_in(ctx.bump(), attrset.syntax().text_range()); + let mut nested = PendingAttrSet::new_in(ctx.bump()); nested.collect_entries(attrset.entries(), ctx)?; *value = PendingValue::Set(nested); match value { @@ -734,7 +637,7 @@ impl<'ir> PendingAttrSet<'ir> { existing_span: TextRange, new_value: ast::Expr, new_span: TextRange, - ctx: &mut impl DowngradeContext<'ir>, + ctx: &mut impl DowngradeContext<'id, 'ir>, ) -> Result<()> { match existing { PendingValue::Set(existing_set) => { @@ -756,17 +659,10 @@ impl<'ir> PendingAttrSet<'ir> { let base = existing_attrset.clone(); let extensions: Vec<'ir, _> = new_attrset.entries().collect_in(ctx.bump()); - *existing = PendingValue::ExtendedRecAttrSet { - base, - extensions, - span: existing_attrset.syntax().text_range(), - }; + *existing = PendingValue::ExtendedRecAttrSet { base, extensions }; Ok(()) } else { - let mut merged = PendingAttrSet::new_in( - ctx.bump(), - existing_attrset.syntax().text_range(), - ); + let mut merged = PendingAttrSet::new_in(ctx.bump()); merged.collect_entries(existing_attrset.entries(), ctx)?; merged.collect_entries(new_attrset.entries(), ctx)?; *existing = PendingValue::Set(merged); @@ -812,7 +708,7 @@ impl<'ir> PendingAttrSet<'ir> { fn collect_entries( &mut self, entries: impl Iterator, - ctx: &mut impl DowngradeContext<'ir>, + ctx: &mut impl DowngradeContext<'id, 'ir>, ) -> Result<()> { for entry in entries { match entry { @@ -834,7 +730,7 @@ impl<'ir> PendingAttrSet<'ir> { fn collect_inherit( &mut self, inherit: ast::Inherit, - ctx: &mut impl DowngradeContext<'ir>, + ctx: &mut impl DowngradeContext<'id, 'ir>, ) -> Result<()> { let from = inherit .from() @@ -949,19 +845,24 @@ fn make_attrpath_value_entry<'ir>(path: Vec<'ir, ast::Attr>, value: ast::Expr) - ast::Entry::cast(node).expect("constructed valid Entry node") } -/// Downgrades the entries of a non-recursive attribute set. -fn downgrade_attrs<'ir>( +struct FinalizedAttrSet<'id, 'ir> { + stcs: HashMap<'ir, SymId, (IrRef<'id, 'ir>, TextRange)>, + dyns: Vec<'ir, (IrRef<'id, 'ir>, IrRef<'id, 'ir>, TextRange)>, +} + +fn downgrade_attrs<'id, 'ir>( attrs: impl ast::HasEntry + AstNode, - ctx: &mut impl DowngradeContext<'ir>, -) -> Result> { - let span = attrs.syntax().text_range(); - let mut pending = PendingAttrSet::new_in(ctx.bump(), span); + ctx: &mut impl DowngradeContext<'id, 'ir>, +) -> Result> { + let mut pending = PendingAttrSet::new_in(ctx.bump()); pending.collect_entries(attrs.entries(), ctx)?; finalize_pending_set::<_, true>(pending, &HashMap::new_in(ctx.bump()), ctx) } -/// Downgrades a single attribute key (part of an attribute path). -fn downgrade_attr<'ir>(attr: ast::Attr, ctx: &mut impl DowngradeContext<'ir>) -> Result> { +fn downgrade_attr<'id, 'ir>( + attr: ast::Attr, + ctx: &mut impl DowngradeContext<'id, 'ir>, +) -> Result>> { use ast::Attr::*; use ast::InterpolPart::*; match attr { @@ -990,13 +891,7 @@ fn downgrade_attr<'ir>(attr: ast::Attr, ctx: &mut impl DowngradeContext<'ir>) -> let parts = parts .into_iter() .map(|part| match part { - Literal(lit) => Ok(ctx.new_expr( - self::Str { - inner: lit.box_in(bump), - span, - } - .to_ir(), - )), + Literal(lit) => Ok(ctx.new_expr(Ir::Str(lit.box_in(bump)))), Interpolation(interpol) => interpol .expr() .require(ctx, interpol.syntax().text_range())? @@ -1004,14 +899,10 @@ fn downgrade_attr<'ir>(attr: ast::Attr, ctx: &mut impl DowngradeContext<'ir>) -> }) .collect_in::>>(bump)?; Ok(Attr::Dynamic( - ctx.new_expr( - ConcatStrings { - parts, - span, - force_string: true, - } - .to_ir(), - ), + ctx.new_expr(Ir::ConcatStrings { + parts, + force_string: true, + }), span, )) } @@ -1026,11 +917,10 @@ fn downgrade_attr<'ir>(attr: ast::Attr, ctx: &mut impl DowngradeContext<'ir>) -> } } -/// Downgrades an attribute path (e.g., `a.b."${c}".d`) into a `Vec<'ir, Attr>`. -fn downgrade_attrpath<'ir>( +fn downgrade_attrpath<'id, 'ir>( attrpath: ast::Attrpath, - ctx: &mut impl DowngradeContext<'ir>, -) -> Result>> { + ctx: &mut impl DowngradeContext<'id, 'ir>, +) -> Result>>> { let bump = ctx.bump(); attrpath .attrs() @@ -1038,23 +928,23 @@ fn downgrade_attrpath<'ir>( .collect_in::>>(bump) } -struct PatternBindings<'ir> { - body: IrRef<'ir>, +struct PatternBindings<'id, 'ir> { + body: IrRef<'id, 'ir>, required: Vec<'ir, (SymId, TextRange)>, optional: Vec<'ir, (SymId, TextRange)>, } -/// Helper function for Lambda pattern parameters. -fn downgrade_pattern_bindings<'ir, Ctx>( +fn downgrade_pattern_bindings<'id, 'ir, Ctx>( pat_entries: impl Iterator, alias: Option, - arg: IrRef<'ir>, + arg: ArgId, ctx: &mut Ctx, - body_fn: impl FnOnce(&mut Ctx, &[SymId]) -> Result>, -) -> Result> + body_fn: impl FnOnce(&mut Ctx, &[SymId]) -> Result>, +) -> Result> where - Ctx: DowngradeContext<'ir>, + Ctx: DowngradeContext<'id, 'ir>, { + let arg = ctx.new_expr(Ir::Arg(arg)); struct Param { sym: SymId, sym_span: TextRange, @@ -1125,15 +1015,12 @@ where None }; - Ok(ctx.new_expr( - Select { - expr: arg, - attrpath: Vec::from_iter_in([Attr::Str(sym, sym_span)], bump), - default, - span, - } - .to_ir(), - )) + Ok(ctx.new_expr(Ir::Select { + expr: arg, + attrpath: Vec::from_iter_in([Attr::Str(sym, sym_span)], bump), + default, + span, + })) }) .chain(alias.into_iter().map(|_| Ok(arg))) .collect_in::>(bump)?; @@ -1151,61 +1038,54 @@ where }) } -/// Downgrades a `let...in` expression. This is a special case of rec attrs -/// that disallows dynamic attributes and has a body expression. -fn downgrade_let_bindings<'ir, Ctx, F>( +fn downgrade_let_bindings<'id, 'ir, Ctx, F>( entries: Vec<'ir, ast::Entry>, ctx: &mut Ctx, - span: TextRange, body_fn: F, -) -> Result> +) -> Result> where - Ctx: DowngradeContext<'ir>, - F: FnOnce(&mut Ctx, &[SymId]) -> Result>, + Ctx: DowngradeContext<'id, 'ir>, + F: FnOnce(&mut Ctx, &[SymId]) -> Result>, { - downgrade_rec_attrs_impl::<_, _, false>(entries, ctx, span, |ctx, binding_keys, _dyns| { + downgrade_rec_attrs_impl::<_, _, false>(entries, ctx, |ctx, binding_keys, _dyns| { body_fn(ctx, binding_keys) }) } -/// Downgrades a `rec` attribute set. -fn downgrade_rec_bindings<'ir, Ctx>( +fn downgrade_rec_bindings<'id, 'ir, Ctx>( entries: Vec<'ir, ast::Entry>, ctx: &mut Ctx, - span: TextRange, -) -> Result> +) -> Result> where - Ctx: DowngradeContext<'ir>, + Ctx: DowngradeContext<'id, 'ir>, { - downgrade_rec_attrs_impl::<_, _, true>(entries, ctx, span, |ctx, binding_keys, dyns| { - let mut attrs = AttrSet { - stcs: HashMap::new_in(ctx.bump()), - dyns: Vec::from_iter_in(dyns.iter().cloned(), ctx.bump()), - span, - }; + downgrade_rec_attrs_impl::<_, _, true>(entries, ctx, |ctx, binding_keys, dyns| { + let mut stcs = HashMap::new_in(ctx.bump()); + let dyns = Vec::from_iter_in(dyns.iter().cloned(), ctx.bump()); for sym in binding_keys { let expr = ctx.lookup(*sym, rnix::TextRange::default())?; - attrs.stcs.insert(*sym, (expr, rnix::TextRange::default())); + stcs.insert(*sym, (expr, rnix::TextRange::default())); } - Ok(ctx.new_expr(attrs.to_ir())) + Ok(ctx.new_expr(Ir::AttrSet { stcs, dyns })) }) } -/// Core implementation for recursive bindings (rec attrs and let-in). -/// ALLOW_DYN controls whether dynamic attributes are allowed. -fn downgrade_rec_attrs_impl<'ir, Ctx, F, const ALLOW_DYN: bool>( +fn downgrade_rec_attrs_impl<'id, 'ir, Ctx, F, const ALLOW_DYN: bool>( entries: Vec<'ir, ast::Entry>, ctx: &mut Ctx, - span: TextRange, body_fn: F, -) -> Result> +) -> Result> where - Ctx: DowngradeContext<'ir>, - F: FnOnce(&mut Ctx, &[SymId], &[(IrRef<'ir>, IrRef<'ir>, TextRange)]) -> Result>, + Ctx: DowngradeContext<'id, 'ir>, + F: FnOnce( + &mut Ctx, + &[SymId], + &[(IrRef<'id, 'ir>, IrRef<'id, 'ir>, TextRange)], + ) -> Result>, { - let mut pending = PendingAttrSet::new_in(ctx.bump(), span); + let mut pending = PendingAttrSet::new_in(ctx.bump()); pending.collect_entries(entries.iter().cloned(), ctx)?; let binding_syms = collect_binding_syms::<_, ALLOW_DYN>(&pending, ctx)?; @@ -1223,11 +1103,10 @@ where }) } -/// Collects `inherit x` lookups from the outer scope before entering the rec scope. -fn collect_inherit_lookups<'ir, Ctx: DowngradeContext<'ir>>( +fn collect_inherit_lookups<'id, 'ir, Ctx: DowngradeContext<'id, 'ir>>( entries: &[ast::Entry], ctx: &mut Ctx, -) -> Result, TextRange)>> { +) -> Result, TextRange)>> { let mut inherit_lookups = HashMap::new_in(ctx.bump()); for entry in entries { if let ast::Entry::Inherit(inherit) = entry @@ -1246,8 +1125,7 @@ fn collect_inherit_lookups<'ir, Ctx: DowngradeContext<'ir>>( Ok(inherit_lookups) } -/// Collects binding symbols from a pending set, checking for duplicates. -fn collect_binding_syms<'ir, Ctx: DowngradeContext<'ir>, const ALLOW_DYN: bool>( +fn collect_binding_syms<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>, const ALLOW_DYN: bool>( pending: &PendingAttrSet, ctx: &mut Ctx, ) -> Result> { @@ -1266,13 +1144,11 @@ fn collect_binding_syms<'ir, Ctx: DowngradeContext<'ir>, const ALLOW_DYN: bool>( Ok(binding_syms) } -/// Unified finalize function for PendingAttrSet. -/// ALLOW_DYN controls whether dynamic attributes are allowed. -fn finalize_pending_set<'ir, Ctx: DowngradeContext<'ir>, const ALLOW_DYN: bool>( +fn finalize_pending_set<'id, 'ir, Ctx: DowngradeContext<'id, 'ir>, const ALLOW_DYN: bool>( pending: PendingAttrSet, - inherit_lookups: &HashMap, TextRange)>, + inherit_lookups: &HashMap, TextRange)>, ctx: &mut Ctx, -) -> Result> { +) -> Result> { let mut stcs = HashMap::new_in(ctx.bump()); let mut dyns = Vec::new_in(ctx.bump()); @@ -1286,33 +1162,23 @@ fn finalize_pending_set<'ir, Ctx: DowngradeContext<'ir>, const ALLOW_DYN: bool>( let key_id = downgrade_attr(attr, ctx)?; let key_expr = match key_id { Attr::Dynamic(id, _) => id, - Attr::Str(sym, attr_span) => ctx.new_expr( - Str { - inner: ctx.get_sym(sym).to_string().box_in(ctx.bump()), - span: attr_span, - } - .to_ir(), - ), + Attr::Str(sym, _attr_span) => { + ctx.new_expr(Ir::Str(ctx.get_sym(sym).to_string().box_in(ctx.bump()))) + } }; let value_id = finalize_pending_value::<_, ALLOW_DYN>(value, inherit_lookups, ctx)?; dyns.push((key_expr, value_id, value_span)); } } - Ok(AttrSet { - stcs, - dyns, - span: pending.span, - }) + Ok(FinalizedAttrSet { stcs, dyns }) } -/// Unified finalize function for PendingValue. -/// ALLOW_DYN controls whether dynamic attributes are allowed. -fn finalize_pending_value<'ir, Ctx: DowngradeContext<'ir>, const ALLOW_DYN: bool>( +fn finalize_pending_value<'id, 'ir, Ctx: DowngradeContext<'id, 'ir>, const ALLOW_DYN: bool>( value: PendingValue, - inherit_lookups: &HashMap, TextRange)>, + inherit_lookups: &HashMap, TextRange)>, ctx: &mut Ctx, -) -> Result> { +) -> Result> { match value { PendingValue::Expr(expr) => { let id = Downgrade::downgrade(expr, ctx)?; @@ -1320,13 +1186,12 @@ fn finalize_pending_value<'ir, Ctx: DowngradeContext<'ir>, const ALLOW_DYN: bool } PendingValue::InheritFrom(from_expr, sym, span) => { let from_id = Downgrade::downgrade(from_expr, ctx)?; - let select = Select { + let select_id = ctx.new_expr(Ir::Select { expr: from_id, attrpath: Vec::from_iter_in([Attr::Str(sym, span)], ctx.bump()), default: None, span, - }; - let select_id = ctx.new_expr(select.to_ir()); + }); Ok(ctx.maybe_thunk(select_id)) } PendingValue::InheritScope(sym, span) => { @@ -1339,36 +1204,33 @@ fn finalize_pending_value<'ir, Ctx: DowngradeContext<'ir>, const ALLOW_DYN: bool } PendingValue::Set(set) => { let attrset = finalize_pending_set::<_, ALLOW_DYN>(set, inherit_lookups, ctx)?; - Ok(ctx.new_expr(attrset.to_ir())) + Ok(ctx.new_expr(Ir::AttrSet { + stcs: attrset.stcs, + dyns: attrset.dyns, + })) } PendingValue::ExtendedRecAttrSet { - base, - extensions, - span, + base, extensions, .. } => { let mut all_entries: Vec<'ir, _> = base.entries().collect_in(ctx.bump()); all_entries.extend(extensions); - downgrade_rec_bindings(all_entries, ctx, span) + downgrade_rec_bindings(all_entries, ctx) } } } -fn downgrade_path<'ir>( +fn downgrade_path<'id, 'ir>( parts: impl IntoIterator>, - span: rnix::TextRange, - ctx: &mut impl DowngradeContext<'ir>, -) -> Result> { + _span: rnix::TextRange, + ctx: &mut impl DowngradeContext<'id, 'ir>, +) -> Result> { let bump = ctx.bump(); let parts = parts .into_iter() .map(|part| match part { - ast::InterpolPart::Literal(lit) => Ok(ctx.new_expr( - Str { - inner: lit.text().to_string().box_in(ctx.bump()), - span: lit.syntax().text_range(), - } - .to_ir(), - )), + ast::InterpolPart::Literal(lit) => { + Ok(ctx.new_expr(Ir::Str(lit.text().to_string().box_in(ctx.bump())))) + } ast::InterpolPart::Interpolation(interpol) => interpol .expr() .require(ctx, interpol.syntax().text_range())? @@ -1378,14 +1240,10 @@ fn downgrade_path<'ir>( let expr = if parts.len() == 1 { parts.into_iter().next().expect("length checked") } else { - ctx.new_expr( - ConcatStrings { - parts, - span, - force_string: false, - } - .to_ir(), - ) + ctx.new_expr(Ir::ConcatStrings { + parts, + force_string: false, + }) }; - Ok(ctx.new_expr(Path { expr, span }.to_ir())) + Ok(ctx.new_expr(Ir::Path(expr))) } diff --git a/nix-js/src/ir.rs b/nix-js/src/ir.rs index de5f827..406c0e4 100644 --- a/nix-js/src/ir.rs +++ b/nix-js/src/ir.rs @@ -1,54 +1,151 @@ -use std::hash::{Hash, Hasher}; +use std::{ + hash::{Hash, Hasher}, + ops::Deref, +}; use bumpalo::{Bump, boxed::Box, collections::Vec}; +use ghost_cell::{GhostCell, GhostToken}; use rnix::{TextRange, ast}; use string_interner::symbol::SymbolU32; -use nix_js_macros::ir; - pub type HashMap<'ir, K, V> = hashbrown::HashMap; -pub type IrRef<'ir> = &'ir Ir<'ir>; -ir! { - Ir<'ir>; +#[repr(transparent)] +#[derive(Clone, Copy)] +pub struct IrRef<'id, 'ir>(&'ir GhostCell<'id, Ir<'ir, Self>>); - // Literals +impl<'id, 'ir> IrRef<'id, 'ir> { + pub fn new(ir: &'ir GhostCell<'id, Ir<'ir, Self>>) -> Self { + Self(ir) + } + + pub fn alloc(bump: &'ir Bump, ir: Ir<'ir, Self>) -> Self { + Self(bump.alloc(GhostCell::new(ir))) + } + + /// Freeze a mutable IR reference into a read-only one, consuming the + /// `GhostToken` to prevent any further mutation. + /// + /// # Safety + /// The transmute is sound because: + /// - `GhostCell<'id, T>` is `#[repr(transparent)]` over `T` + /// - `IrRef<'id, 'ir>` is `#[repr(transparent)]` over + /// `&'ir GhostCell<'id, Ir<'ir, Self>>` + /// - `RawIrRef<'ir>` is `#[repr(transparent)]` over `&'ir Ir<'ir, Self>` + /// - `Ir<'ir, Ref>` is `#[repr(C)]` and both ref types are pointer-sized + /// + /// Consuming the `GhostToken` guarantees no `borrow_mut` calls can occur + /// afterwards, so the shared `&Ir` references from `RawIrRef::Deref` can + /// never alias with mutable references. + pub fn freeze(self, _token: GhostToken<'id>) -> RawIrRef<'ir> { + unsafe { std::mem::transmute(self) } + } +} + +impl<'id, 'ir> Deref for IrRef<'id, 'ir> { + type Target = GhostCell<'id, Ir<'ir, IrRef<'id, 'ir>>>; + fn deref(&self) -> &Self::Target { + self.0 + } +} + +#[repr(transparent)] +#[derive(Clone, Copy)] +pub struct RawIrRef<'ir>(&'ir Ir<'ir, Self>); + +impl<'ir> Deref for RawIrRef<'ir> { + type Target = Ir<'ir, RawIrRef<'ir>>; + fn deref(&self) -> &Self::Target { + self.0 + } +} + +#[repr(C)] +pub enum Ir<'ir, Ref> { Int(i64), Float(f64), Bool(bool), Null, - Str { inner: Box<'ir, String> }, - AttrSet { stcs: HashMap<'ir, SymId, (IrRef<'ir>, TextRange)>, dyns: Vec<'ir, (IrRef<'ir>, IrRef<'ir>, TextRange)> }, - List { items: Vec<'ir, IrRef<'ir>> }, - Path { expr: IrRef<'ir> }, - ConcatStrings { parts: Vec<'ir, IrRef<'ir>>, force_string: bool }, + Str(Box<'ir, String>), + AttrSet { + stcs: HashMap<'ir, SymId, (Ref, TextRange)>, + dyns: Vec<'ir, (Ref, Ref, TextRange)>, + }, + List { + items: Vec<'ir, Ref>, + }, + Path(Ref), + ConcatStrings { + parts: Vec<'ir, Ref>, + force_string: bool, + }, // OPs - UnOp { rhs: IrRef<'ir>, kind: UnOpKind }, - BinOp { lhs: IrRef<'ir>, rhs: IrRef<'ir>, kind: BinOpKind }, - HasAttr { lhs: IrRef<'ir>, rhs: Vec<'ir, Attr<'ir>> }, - Select { expr: IrRef<'ir>, attrpath: Vec<'ir, Attr<'ir>>, default: Option> }, + UnOp { + rhs: Ref, + kind: UnOpKind, + }, + BinOp { + lhs: Ref, + rhs: Ref, + kind: BinOpKind, + }, + HasAttr { + lhs: Ref, + rhs: Vec<'ir, Attr>, + }, + Select { + expr: Ref, + attrpath: Vec<'ir, Attr>, + default: Option, + span: TextRange, + }, // Conditionals - If { cond: IrRef<'ir>, consq: IrRef<'ir>, alter: IrRef<'ir> }, - Assert { assertion: IrRef<'ir>, expr: IrRef<'ir>, assertion_raw: String }, + If { + cond: Ref, + consq: Ref, + alter: Ref, + }, + Assert { + assertion: Ref, + expr: Ref, + assertion_raw: String, + span: TextRange, + }, - With { namespace: IrRef<'ir>, body: IrRef<'ir>, thunks: Vec<'ir, (ThunkId, IrRef<'ir>)> }, + With { + namespace: Ref, + body: Ref, + thunks: Vec<'ir, (ThunkId, Ref)>, + }, WithLookup(SymId), // Function related - Func { body: IrRef<'ir>, param: Option>, arg: IrRef<'ir>, thunks: Vec<'ir, (ThunkId, IrRef<'ir>)> }, + Func { + body: Ref, + param: Option>, + arg: ArgId, + thunks: Vec<'ir, (ThunkId, Ref)>, + }, Arg(ArgId), - Call { func: IrRef<'ir>, arg: IrRef<'ir> }, + Call { + func: Ref, + arg: Ref, + span: TextRange, + }, // Builtins Builtins, Builtin(SymId), // Misc - TopLevel { body: IrRef<'ir>, thunks: Vec<'ir, (ThunkId, IrRef<'ir>)> }, + TopLevel { + body: Ref, + thunks: Vec<'ir, (ThunkId, Ref)>, + }, Thunk(ThunkId), - CurPos, + CurPos(TextRange), ReplBinding(SymId), ScopedImportBinding(SymId), } @@ -66,17 +163,17 @@ pub struct ArgId(pub usize); /// Represents a key in an attribute path. #[allow(unused)] #[derive(Debug)] -pub enum Attr<'ir> { +pub enum Attr { /// A dynamic attribute key, which is an expression that must evaluate to a string. /// Example: `attrs.${key}` - Dynamic(IrRef<'ir>, TextRange), + Dynamic(Ref, TextRange), /// A static attribute key. /// Example: `attrs.key` Str(SymId, TextRange), } /// The kinds of binary operations supported in Nix. -#[derive(Clone, Debug, Hash, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] pub enum BinOpKind { // Arithmetic Add, @@ -133,7 +230,7 @@ impl From for BinOpKind { } /// The kinds of unary operations. -#[derive(Clone, Debug, Hash, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] pub enum UnOpKind { Neg, // Negation (`-`) Not, // Logical not (`!`) @@ -157,33 +254,41 @@ pub struct Param<'ir> { } #[derive(Clone, Copy)] -pub(crate) struct IrKey<'ir>(pub IrRef<'ir>); +pub(crate) struct IrKey<'id, 'ir, 'a>(pub IrRef<'id, 'ir>, pub &'a GhostToken<'id>); -impl std::hash::Hash for IrKey<'_> { +impl std::hash::Hash for IrKey<'_, '_, '_> { fn hash(&self, state: &mut H) { - ir_content_hash(self.0, state); + ir_content_hash(self.0, self.1, state); } } -impl PartialEq for IrKey<'_> { +impl PartialEq for IrKey<'_, '_, '_> { fn eq(&self, other: &Self) -> bool { - ir_content_eq(self.0, other.0) + ir_content_eq(self.0, other.0, self.1) } } -impl Eq for IrKey<'_> {} +impl Eq for IrKey<'_, '_, '_> {} -fn attr_content_hash(attr: &Attr<'_>, state: &mut impl Hasher) { +fn attr_content_hash<'id>( + attr: &Attr>, + token: &GhostToken<'id>, + state: &mut impl Hasher, +) { core::mem::discriminant(attr).hash(state); match attr { - Attr::Dynamic(expr, _) => ir_content_hash(expr, state), + Attr::Dynamic(expr, _) => ir_content_hash(*expr, token, state), Attr::Str(sym, _) => sym.hash(state), } } -fn attr_content_eq(a: &Attr<'_>, b: &Attr<'_>) -> bool { +fn attr_content_eq<'id, 'ir>( + a: &Attr>, + b: &Attr>, + token: &GhostToken<'id>, +) -> bool { match (a, b) { - (Attr::Dynamic(ae, _), Attr::Dynamic(be, _)) => ir_content_eq(ae, be), + (Attr::Dynamic(ae, _), Attr::Dynamic(be, _)) => ir_content_eq(*ae, *be, token), (Attr::Str(a, _), Attr::Str(b, _)) => a == b, _ => false, } @@ -215,230 +320,364 @@ fn param_content_eq(a: &Param<'_>, b: &Param<'_>) -> bool { .all(|((a, _), (b, _))| a == b) } -fn thunks_content_hash(thunks: &[(ThunkId, IrRef<'_>)], state: &mut impl Hasher) { +fn thunks_content_hash<'id>( + thunks: &[(ThunkId, IrRef<'id, '_>)], + token: &GhostToken<'id>, + state: &mut impl Hasher, +) { thunks.len().hash(state); - for (id, ir) in thunks { + for &(id, ir) in thunks { id.hash(state); - ir_content_hash(ir, state); + ir_content_hash(ir, token, state); } } -fn thunks_content_eq(a: &[(ThunkId, IrRef<'_>)], b: &[(ThunkId, IrRef<'_>)]) -> bool { +fn thunks_content_eq<'id, 'ir>( + a: &[(ThunkId, IrRef<'id, 'ir>)], + b: &[(ThunkId, IrRef<'id, 'ir>)], + token: &GhostToken<'id>, +) -> bool { a.len() == b.len() && a.iter() .zip(b.iter()) - .all(|((ai, ae), (bi, be))| ai == bi && ir_content_eq(ae, be)) + .all(|(&(ai, ae), &(bi, be))| ai == bi && ir_content_eq(ae, be, token)) } -fn ir_content_hash(ir: &Ir<'_>, state: &mut impl Hasher) { +fn ir_content_hash<'id>(ir: IrRef<'id, '_>, token: &GhostToken<'id>, state: &mut impl Hasher) { + let ir = ir.borrow(token); core::mem::discriminant(ir).hash(state); match ir { - Ir::Int(x) => x.inner.hash(state), - Ir::Float(x) => x.inner.to_bits().hash(state), - Ir::Bool(x) => x.inner.hash(state), - Ir::Null(_) => {} - Ir::Str(x) => x.inner.hash(state), - Ir::AttrSet(x) => { - x.stcs.len().hash(state); + Ir::Int(x) => x.hash(state), + Ir::Float(x) => x.to_bits().hash(state), + Ir::Bool(x) => x.hash(state), + Ir::Null => {} + Ir::Str(x) => x.hash(state), + Ir::AttrSet { stcs, dyns } => { + stcs.len().hash(state); let mut combined: u64 = 0; - for (&key, (val, _)) in x.stcs.iter() { + for (&key, &(val, _)) in stcs.iter() { let mut h = std::hash::DefaultHasher::new(); key.hash(&mut h); - ir_content_hash(val, &mut h); + ir_content_hash(val, token, &mut h); combined = combined.wrapping_add(h.finish()); } combined.hash(state); - x.dyns.len().hash(state); - for (k, v, _) in x.dyns.iter() { - ir_content_hash(k, state); - ir_content_hash(v, state); + dyns.len().hash(state); + for &(k, v, _) in dyns.iter() { + ir_content_hash(k, token, state); + ir_content_hash(v, token, state); } } - Ir::List(x) => { - x.items.len().hash(state); - for item in x.items.iter() { - ir_content_hash(item, state); + Ir::List { items } => { + items.len().hash(state); + for &item in items.iter() { + ir_content_hash(item, token, state); } } - Ir::HasAttr(x) => { - ir_content_hash(x.lhs, state); - x.rhs.len().hash(state); - for attr in x.rhs.iter() { - attr_content_hash(attr, state); + Ir::HasAttr { lhs, rhs } => { + ir_content_hash(*lhs, token, state); + rhs.len().hash(state); + for attr in rhs.iter() { + attr_content_hash(attr, token, state); } } - Ir::BinOp(x) => { - ir_content_hash(x.lhs, state); - ir_content_hash(x.rhs, state); - x.kind.hash(state); + &Ir::BinOp { lhs, rhs, kind } => { + ir_content_hash(lhs, token, state); + ir_content_hash(rhs, token, state); + kind.hash(state); } - Ir::UnOp(x) => { - ir_content_hash(x.rhs, state); - x.kind.hash(state); + &Ir::UnOp { rhs, kind } => { + ir_content_hash(rhs, token, state); + kind.hash(state); } - Ir::Select(x) => { - ir_content_hash(x.expr, state); - x.attrpath.len().hash(state); - for attr in x.attrpath.iter() { - attr_content_hash(attr, state); + Ir::Select { + expr, + attrpath, + default, + .. + } => { + ir_content_hash(*expr, token, state); + attrpath.len().hash(state); + for attr in attrpath.iter() { + attr_content_hash(attr, token, state); } - x.default.is_some().hash(state); - if let Some(d) = x.default { - ir_content_hash(d, state); + default.is_some().hash(state); + if let Some(d) = default { + ir_content_hash(*d, token, state); } } - Ir::If(x) => { - ir_content_hash(x.cond, state); - ir_content_hash(x.consq, state); - ir_content_hash(x.alter, state); + &Ir::If { cond, consq, alter } => { + ir_content_hash(cond, token, state); + ir_content_hash(consq, token, state); + ir_content_hash(alter, token, state); } - Ir::Call(x) => { - ir_content_hash(x.func, state); - ir_content_hash(x.arg, state); + &Ir::Call { func, arg, .. } => { + ir_content_hash(func, token, state); + ir_content_hash(arg, token, state); } - Ir::Assert(x) => { - ir_content_hash(x.assertion, state); - ir_content_hash(x.expr, state); - x.assertion_raw.hash(state); + Ir::Assert { + assertion, + expr, + assertion_raw, + .. + } => { + ir_content_hash(*assertion, token, state); + ir_content_hash(*expr, token, state); + assertion_raw.hash(state); } - Ir::ConcatStrings(x) => { - x.force_string.hash(state); - x.parts.len().hash(state); - for part in x.parts.iter() { - ir_content_hash(part, state); + Ir::ConcatStrings { + force_string, + parts, + } => { + force_string.hash(state); + parts.len().hash(state); + for &part in parts.iter() { + ir_content_hash(part, token, state); } } - Ir::Path(x) => ir_content_hash(x.expr, state), - Ir::Func(x) => { - ir_content_hash(x.body, state); - ir_content_hash(x.arg, state); - x.param.is_some().hash(state); - if let Some(p) = &x.param { + &Ir::Path(expr) => ir_content_hash(expr, token, state), + Ir::Func { + body, + arg, + param, + thunks, + } => { + ir_content_hash(*body, token, state); + arg.hash(state); + param.is_some().hash(state); + if let Some(p) = param { param_content_hash(p, state); } - thunks_content_hash(&x.thunks, state); + thunks_content_hash(thunks, token, state); } - Ir::TopLevel(x) => { - ir_content_hash(x.body, state); - thunks_content_hash(&x.thunks, state); + Ir::TopLevel { body, thunks } => { + ir_content_hash(*body, token, state); + thunks_content_hash(thunks, token, state); } - Ir::Arg(x) => x.inner.hash(state), - Ir::Thunk(x) => x.inner.hash(state), - Ir::Builtins(_) => {} - Ir::Builtin(x) => x.inner.hash(state), - Ir::CurPos(x) => x.span.hash(state), - Ir::ReplBinding(x) => x.inner.hash(state), - Ir::ScopedImportBinding(x) => x.inner.hash(state), - Ir::With(x) => { - ir_content_hash(x.namespace, state); - ir_content_hash(x.body, state); - thunks_content_hash(&x.thunks, state); + Ir::Arg(x) => x.hash(state), + Ir::Thunk(x) => x.hash(state), + Ir::Builtins => {} + Ir::Builtin(x) => x.hash(state), + Ir::CurPos(x) => x.hash(state), + Ir::ReplBinding(x) => x.hash(state), + Ir::ScopedImportBinding(x) => x.hash(state), + &Ir::With { + namespace, + body, + ref thunks, + } => { + ir_content_hash(namespace, token, state); + ir_content_hash(body, token, state); + thunks_content_hash(thunks, token, state); } - Ir::WithLookup(x) => x.inner.hash(state), + Ir::WithLookup(x) => x.hash(state), } } -fn ir_content_eq(a: &Ir<'_>, b: &Ir<'_>) -> bool { - std::ptr::eq(a, b) - || match (a, b) { - (Ir::Int(a), Ir::Int(b)) => a.inner == b.inner, - (Ir::Float(a), Ir::Float(b)) => a.inner.to_bits() == b.inner.to_bits(), - (Ir::Bool(a), Ir::Bool(b)) => a.inner == b.inner, - (Ir::Null(_), Ir::Null(_)) => true, - (Ir::Str(a), Ir::Str(b)) => *a.inner == *b.inner, - (Ir::AttrSet(a), Ir::AttrSet(b)) => { - a.stcs.len() == b.stcs.len() - && a.dyns.len() == b.dyns.len() - && a.stcs.iter().all(|(&k, (v, _))| { - b.stcs.get(&k).is_some_and(|(bv, _)| ir_content_eq(v, bv)) +pub(crate) fn ir_content_eq<'id, 'ir>( + a: IrRef<'id, 'ir>, + b: IrRef<'id, 'ir>, + token: &GhostToken<'id>, +) -> bool { + std::ptr::eq(a.0, b.0) + || match (a.borrow(token), b.borrow(token)) { + (Ir::Int(a), Ir::Int(b)) => a == b, + (Ir::Float(a), Ir::Float(b)) => a.to_bits() == b.to_bits(), + (Ir::Bool(a), Ir::Bool(b)) => a == b, + (Ir::Null, Ir::Null) => true, + (Ir::Str(a), Ir::Str(b)) => **a == **b, + ( + Ir::AttrSet { + stcs: a_stcs, + dyns: a_dyns, + }, + Ir::AttrSet { + stcs: b_stcs, + dyns: b_dyns, + }, + ) => { + a_stcs.len() == b_stcs.len() + && a_dyns.len() == b_dyns.len() + && a_stcs.iter().all(|(&k, &(av, _))| { + b_stcs + .get(&k) + .is_some_and(|&(bv, _)| ir_content_eq(av, bv, token)) }) - && a.dyns + && a_dyns .iter() - .zip(b.dyns.iter()) - .all(|((ak, av, _), (bk, bv, _))| { - ir_content_eq(ak, bk) && ir_content_eq(av, bv) + .zip(b_dyns.iter()) + .all(|(&(ak, av, _), &(bk, bv, _))| { + ir_content_eq(ak, bk, token) && ir_content_eq(av, bv, token) }) } - (Ir::List(a), Ir::List(b)) => { - a.items.len() == b.items.len() - && a.items - .iter() - .zip(b.items.iter()) - .all(|(a, b)| ir_content_eq(a, b)) + (Ir::List { items: a }, Ir::List { items: b }) => { + a.len() == b.len() + && a.iter() + .zip(b.iter()) + .all(|(&a, &b)| ir_content_eq(a, b, token)) } - (Ir::HasAttr(a), Ir::HasAttr(b)) => { - ir_content_eq(a.lhs, b.lhs) - && a.rhs.len() == b.rhs.len() - && a.rhs + (Ir::HasAttr { lhs: al, rhs: ar }, Ir::HasAttr { lhs: bl, rhs: br }) => { + ir_content_eq(*al, *bl, token) + && ar.len() == br.len() + && ar .iter() - .zip(b.rhs.iter()) - .all(|(a, b)| attr_content_eq(a, b)) + .zip(br.iter()) + .all(|(a, b)| attr_content_eq(a, b, token)) } - (Ir::BinOp(a), Ir::BinOp(b)) => { - a.kind == b.kind && ir_content_eq(a.lhs, b.lhs) && ir_content_eq(a.rhs, b.rhs) + ( + &Ir::BinOp { + lhs: al, + rhs: ar, + kind: ak, + }, + &Ir::BinOp { + lhs: bl, + rhs: br, + kind: bk, + }, + ) => ak == bk && ir_content_eq(al, bl, token) && ir_content_eq(ar, br, token), + (&Ir::UnOp { rhs: ar, kind: ak }, &Ir::UnOp { rhs: br, kind: bk }) => { + ak == bk && ir_content_eq(ar, br, token) } - (Ir::UnOp(a), Ir::UnOp(b)) => a.kind == b.kind && ir_content_eq(a.rhs, b.rhs), - (Ir::Select(a), Ir::Select(b)) => { - ir_content_eq(a.expr, b.expr) - && a.attrpath.len() == b.attrpath.len() - && a.attrpath + ( + Ir::Select { + expr: ae, + attrpath: aa, + default: ad, + .. + }, + Ir::Select { + expr: be, + attrpath: ba, + default: bd, + .. + }, + ) => { + ir_content_eq(*ae, *be, token) + && aa.len() == ba.len() + && aa .iter() - .zip(b.attrpath.iter()) - .all(|(a, b)| attr_content_eq(a, b)) - && match (a.default, b.default) { - (Some(a), Some(b)) => ir_content_eq(a, b), + .zip(ba.iter()) + .all(|(a, b)| attr_content_eq(a, b, token)) + && match (ad, bd) { + (Some(a), Some(b)) => ir_content_eq(*a, *b, token), (None, None) => true, _ => false, } } - (Ir::If(a), Ir::If(b)) => { - ir_content_eq(a.cond, b.cond) - && ir_content_eq(a.consq, b.consq) - && ir_content_eq(a.alter, b.alter) + ( + &Ir::If { + cond: ac, + consq: acs, + alter: aa, + }, + &Ir::If { + cond: bc, + consq: bcs, + alter: ba, + }, + ) => { + ir_content_eq(ac, bc, token) + && ir_content_eq(acs, bcs, token) + && ir_content_eq(aa, ba, token) } - (Ir::Call(a), Ir::Call(b)) => { - ir_content_eq(a.func, b.func) && ir_content_eq(a.arg, b.arg) - } - (Ir::Assert(a), Ir::Assert(b)) => { - a.assertion_raw == b.assertion_raw - && ir_content_eq(a.assertion, b.assertion) - && ir_content_eq(a.expr, b.expr) - } - (Ir::ConcatStrings(a), Ir::ConcatStrings(b)) => { - a.force_string == b.force_string - && a.parts.len() == b.parts.len() - && a.parts + ( + &Ir::Call { + func: af, arg: aa, .. + }, + &Ir::Call { + func: bf, arg: ba, .. + }, + ) => ir_content_eq(af, bf, token) && ir_content_eq(aa, ba, token), + ( + Ir::Assert { + assertion: aa, + expr: ae, + assertion_raw: ar, + .. + }, + Ir::Assert { + assertion: ba, + expr: be, + assertion_raw: br, + .. + }, + ) => ar == br && ir_content_eq(*aa, *ba, token) && ir_content_eq(*ae, *be, token), + ( + Ir::ConcatStrings { + force_string: af, + parts: ap, + }, + Ir::ConcatStrings { + force_string: bf, + parts: bp, + }, + ) => { + af == bf + && ap.len() == bp.len() + && ap .iter() - .zip(b.parts.iter()) - .all(|(a, b)| ir_content_eq(a, b)) + .zip(bp.iter()) + .all(|(&a, &b)| ir_content_eq(a, b, token)) } - (Ir::Path(a), Ir::Path(b)) => ir_content_eq(a.expr, b.expr), - (Ir::Func(a), Ir::Func(b)) => { - ir_content_eq(a.body, b.body) - && ir_content_eq(a.arg, b.arg) - && match (&a.param, &b.param) { + (&Ir::Path(a), &Ir::Path(b)) => ir_content_eq(a, b, token), + ( + Ir::Func { + body: ab, + arg: aa, + param: ap, + thunks: at, + }, + Ir::Func { + body: bb, + arg: ba, + param: bp, + thunks: bt, + }, + ) => { + ir_content_eq(*ab, *bb, token) + && aa == ba + && match (ap, bp) { (Some(a), Some(b)) => param_content_eq(a, b), (None, None) => true, _ => false, } - && thunks_content_eq(&a.thunks, &b.thunks) + && thunks_content_eq(at, bt, token) } - (Ir::TopLevel(a), Ir::TopLevel(b)) => { - ir_content_eq(a.body, b.body) && thunks_content_eq(&a.thunks, &b.thunks) + ( + Ir::TopLevel { + body: ab, + thunks: at, + }, + Ir::TopLevel { + body: bb, + thunks: bt, + }, + ) => ir_content_eq(*ab, *bb, token) && thunks_content_eq(at, bt, token), + (Ir::Arg(a), Ir::Arg(b)) => a == b, + (Ir::Thunk(a), Ir::Thunk(b)) => a == b, + (Ir::Builtins, Ir::Builtins) => true, + (Ir::Builtin(a), Ir::Builtin(b)) => a == b, + (Ir::CurPos(a), Ir::CurPos(b)) => a == b, + (Ir::ReplBinding(a), Ir::ReplBinding(b)) => a == b, + (Ir::ScopedImportBinding(a), Ir::ScopedImportBinding(b)) => a == b, + ( + Ir::With { + namespace: a_ns, + body: a_body, + thunks: a_thunks, + }, + Ir::With { + namespace: b_ns, + body: b_body, + thunks: b_thunks, + }, + ) => { + ir_content_eq(*a_ns, *b_ns, token) + && ir_content_eq(*a_body, *b_body, token) + && thunks_content_eq(a_thunks, b_thunks, token) } - (Ir::Arg(a), Ir::Arg(b)) => a.inner == b.inner, - (Ir::Thunk(a), Ir::Thunk(b)) => a.inner == b.inner, - (Ir::Builtins(_), Ir::Builtins(_)) => true, - (Ir::Builtin(a), Ir::Builtin(b)) => a.inner == b.inner, - (Ir::CurPos(a), Ir::CurPos(b)) => a.span == b.span, - (Ir::ReplBinding(a), Ir::ReplBinding(b)) => a.inner == b.inner, - (Ir::ScopedImportBinding(a), Ir::ScopedImportBinding(b)) => a.inner == b.inner, - (Ir::With(a), Ir::With(b)) => { - ir_content_eq(a.namespace, b.namespace) - && ir_content_eq(a.body, b.body) - && thunks_content_eq(&a.thunks, &b.thunks) - } - (Ir::WithLookup(a), Ir::WithLookup(b)) => a.inner == b.inner, + (Ir::WithLookup(a), Ir::WithLookup(b)) => a == b, _ => false, } }