refactor: use GhostCell to provide interior mutability in Ir

This commit is contained in:
2026-02-24 16:52:33 +08:00
parent c24d6a8bb3
commit 843ae6cfb4
6 changed files with 1133 additions and 1017 deletions

7
Cargo.lock generated
View File

@@ -1302,6 +1302,12 @@ dependencies = [
"wasip3", "wasip3",
] ]
[[package]]
name = "ghost-cell"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8449d342b1c67f49169e92e71deb7b9b27f30062301a16dbc27a4cc8d2351b7"
[[package]] [[package]]
name = "gimli" name = "gimli"
version = "0.32.3" version = "0.32.3"
@@ -2057,6 +2063,7 @@ dependencies = [
"ere", "ere",
"fastwebsockets", "fastwebsockets",
"flate2", "flate2",
"ghost-cell",
"hashbrown 0.16.1", "hashbrown 0.16.1",
"hex", "hex",
"http", "http",

View File

@@ -74,6 +74,7 @@ hyper-util = { version = "0.1", features = ["tokio"], optional = true }
http-body-util = { version = "0.1", optional = true } http-body-util = { version = "0.1", optional = true }
http = { version = "1", optional = true } http = { version = "1", optional = true }
uuid = { version = "1", features = ["v4"], optional = true } uuid = { version = "1", features = ["v4"], optional = true }
ghost-cell = "0.2.6"
[features] [features]
inspector = ["dep:fastwebsockets", "dep:hyper", "dep:hyper-util", "dep:http-body-util", "dep:http", "dep:uuid"] inspector = ["dep:fastwebsockets", "dep:hyper", "dep:hyper-util", "dep:http-body-util", "dep:http", "dep:uuid"]

View File

@@ -1,6 +1,9 @@
use std::fmt::{self, Write as _}; use std::fmt::{self, Write as _};
use std::ops::Deref;
use std::path::Path; use std::path::Path;
use rnix::TextRange;
use crate::ir::*; use crate::ir::*;
use crate::value::Symbol; use crate::value::Symbol;
@@ -26,7 +29,7 @@ macro_rules! code {
}; };
} }
pub(crate) fn compile<const SCOPED: bool>(expr: &Ir, ctx: &impl CodegenContext) -> String { pub(crate) fn compile<const SCOPED: bool>(expr: RawIrRef<'_>, ctx: &impl CodegenContext) -> String {
let mut buf = CodeBuffer::with_capacity(8192); let mut buf = CodeBuffer::with_capacity(8192);
code!( code!(
@@ -186,59 +189,84 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Symbol<'_> {
} }
} }
impl<Ctx: CodegenContext> Compile<Ctx> for Ir<'_> { impl<Ctx: CodegenContext> Compile<Ctx> for RawIrRef<'_> {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
match self { match self.deref() {
Ir::Int(int) => { Ir::Int(int) => {
code!(buf, "{}n", int.inner); code!(buf, "{}n", int);
} }
Ir::Float(float) => { Ir::Float(float) => {
code!(buf, "{}", float.inner); code!(buf, "{}", float);
} }
Ir::Bool(bool) => { Ir::Bool(bool) => {
code!(buf, "{}", bool.inner); code!(buf, "{}", bool);
} }
Ir::Null(_) => { Ir::Null => {
code!(buf, ctx; "null"); code!(buf, ctx; "null");
} }
Ir::Str(s) => { Ir::Str(s) => {
code!(buf, ctx; quoted(&s.inner)); code!(buf, ctx; quoted(s));
} }
Ir::Path(p) => { Ir::Path(p) => {
// Nix.resolvePath // 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) => { Ir::Arg(x) => {
code!(buf, "a{}", x.inner.0); code!(buf, "a{}", x.0);
} }
Ir::TopLevel(x) => x.compile(ctx, buf), &Ir::TopLevel { body, ref thunks } => compile_toplevel(body, thunks, ctx, buf),
Ir::Select(x) => x.compile(ctx, buf), &Ir::Select {
&Ir::Thunk(Thunk { inner: expr_id, .. }) => { expr,
code!(buf, "e{}", expr_id.0); 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 // Nix.builtins
code!(buf, ctx; "$b"); code!(buf, ctx; "$b");
} }
&Ir::Builtin(Builtin { inner: name, .. }) => { &Ir::Builtin(name) => {
// Nix.builtins // Nix.builtins
code!(buf, ctx; "$b.get(" ctx.get_sym(name) ")"); code!(buf, ctx; "$b.get(" ctx.get_sym(name) ")");
} }
Ir::ConcatStrings(x) => x.compile(ctx, buf), &Ir::ConcatStrings {
Ir::HasAttr(x) => x.compile(ctx, buf), ref parts,
&Ir::Assert(Assert { 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, assertion,
expr, expr,
ref assertion_raw, assertion_raw,
span: assert_span, span: assert_span,
}) => { } => {
// Nix.assert // Nix.assert
code!(buf, ctx; code!(buf, ctx;
"$a(" "$a("
@@ -252,19 +280,23 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Ir<'_> {
")" ")"
); );
} }
Ir::CurPos(cur_pos) => { Ir::CurPos(span) => {
// Nix.mkPos // Nix.mkPos
code!(buf, ctx; "$mp(" cur_pos.span ")"); code!(buf, ctx; "$mp(" span ")");
} }
&Ir::ReplBinding(ReplBinding { inner: name, .. }) => { &Ir::ReplBinding(name) => {
// Nix.getReplBinding // Nix.getReplBinding
code!(buf, ctx; "$gb(" ctx.get_sym(name) ")"); 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) ")"); code!(buf, ctx; "_s.get(" ctx.get_sym(name) ")");
} }
Ir::With(x) => x.compile(ctx, buf), &Ir::With {
&Ir::WithLookup(WithLookup { inner: name, .. }) => { namespace,
body,
ref thunks,
} => compile_with(namespace, body, thunks, ctx, buf),
&Ir::WithLookup(name) => {
// Nix.lookupWith // Nix.lookupWith
code!(buf, ctx; "$l(" ctx.get_sym(name) ",_w)"); code!(buf, ctx; "$l(" ctx.get_sym(name) ",_w)");
} }
@@ -272,30 +304,17 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Ir<'_> {
} }
} }
impl<Ctx: CodegenContext> Compile<Ctx> for If<'_> { fn compile_binop<'ir>(
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { lhs: RawIrRef<'ir>,
let &If { rhs: RawIrRef<'ir>,
cond, kind: BinOpKind,
consq, ctx: &impl CodegenContext,
alter, buf: &mut CodeBuffer,
span: _, ) {
} = self;
// Nix.forceBool
code!(buf, ctx; "$fb(" cond ")?(" consq "):(" alter ")");
}
}
impl<Ctx: CodegenContext> Compile<Ctx> for BinOp<'_> {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
use BinOpKind::*; use BinOpKind::*;
match kind {
let lhs = self.lhs;
let rhs = self.rhs;
match self.kind {
Add | Sub | Mul | Div | Eq | Neq | Lt | Gt | Leq | Geq | Con | Upd => { Add | Sub | Mul | Div | Eq | Neq | Lt | Gt | Leq | Geq | Con | Upd => {
let op_func = match self.kind { let op_func = match kind {
Add => "$oa", Add => "$oa",
Sub => "$os", Sub => "$os",
Mul => "$om", Mul => "$om",
@@ -341,14 +360,16 @@ impl<Ctx: CodegenContext> Compile<Ctx> for BinOp<'_> {
code!(buf, ctx; "$c(" lhs "," rhs ")"); code!(buf, ctx; "$c(" lhs "," rhs ")");
} }
} }
}
} }
impl<Ctx: CodegenContext> Compile<Ctx> for UnOp<'_> { fn compile_unop(
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { rhs: RawIrRef<'_>,
kind: UnOpKind,
ctx: &impl CodegenContext,
buf: &mut CodeBuffer,
) {
use UnOpKind::*; use UnOpKind::*;
let rhs = self.rhs; match kind {
match self.kind {
Neg => { Neg => {
// 0 - rhs // 0 - rhs
code!(buf, ctx; "$os(0n," rhs ")"); code!(buf, ctx; "$os(0n," rhs ")");
@@ -357,32 +378,29 @@ impl<Ctx: CodegenContext> Compile<Ctx> for UnOp<'_> {
code!(buf, ctx; "!$fb(" rhs ")"); code!(buf, ctx; "!$fb(" rhs ")");
} }
} }
}
} }
impl<Ctx: CodegenContext> Compile<Ctx> for Func<'_> { fn compile_func<'ir, Ctx: CodegenContext>(
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { ArgId(id): ArgId,
let Ir::Arg(Arg { thunks: &[(ThunkId, RawIrRef<'ir>)],
inner: ArgId(id), .. param: &Option<Param<'ir>>,
}) = self.arg body: RawIrRef<'ir>,
else { ctx: &Ctx,
// TODO: buf: &mut CodeBuffer,
unreachable!() ) {
}; let has_thunks = !thunks.is_empty();
let has_thunks = !self.thunks.is_empty();
if let Some(Param { if let Some(Param {
required, required,
optional, optional,
ellipsis, ellipsis,
}) = &self.param }) = &param
{ {
code!(buf, "$mf(a{}=>", id); code!(buf, "$mf(a{}=>", id);
if has_thunks { if has_thunks {
code!(buf, ctx; "{" self.thunks "return " self.body "}"); code!(buf, ctx; "{" thunks "return " body "}");
} else { } else {
code!(buf, ctx; "(" self.body ")"); code!(buf, ctx; "(" body ")");
} }
code!(buf, ctx; code!(buf, ctx;
",[" ",["
@@ -404,29 +422,14 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Func<'_> {
} else { } else {
code!(buf, "a{}=>", id); code!(buf, "a{}=>", id);
if has_thunks { if has_thunks {
code!(buf, ctx; "{" self.thunks "return " self.body "}"); code!(buf, ctx; "{" thunks "return " body "}");
} else { } else {
code!(buf, ctx; "(" self.body ")"); code!(buf, ctx; "(" body ")");
}
} }
} }
} }
impl<Ctx: CodegenContext> Compile<Ctx> for Call<'_> { impl<'ir, Ctx: CodegenContext> Compile<Ctx> for [(ThunkId, RawIrRef<'ir>)] {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
code!(buf, ctx;
"$c("
self.func
","
self.arg
","
self.span
")"
);
}
}
impl<'ir, Ctx: CodegenContext> Compile<Ctx> for [(ThunkId, IrRef<'ir>)] {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
if self.is_empty() { if self.is_empty() {
return; return;
@@ -443,37 +446,48 @@ impl<'ir, Ctx: CodegenContext> Compile<Ctx> for [(ThunkId, IrRef<'ir>)] {
} }
} }
impl<Ctx: CodegenContext> Compile<Ctx> for TopLevel<'_> { fn compile_toplevel<'ir, Ctx: CodegenContext>(
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { body: RawIrRef<'ir>,
if self.thunks.is_empty() { thunks: &[(ThunkId, RawIrRef<'ir>)],
self.body.compile(ctx, buf); ctx: &Ctx,
buf: &mut CodeBuffer,
) {
if thunks.is_empty() {
body.compile(ctx, buf);
} else { } else {
code!(buf, ctx; "(()=>{" self.thunks "return " self.body "})()"); code!(buf, ctx; "(()=>{" thunks "return " body "})()");
}
} }
} }
impl<Ctx: CodegenContext> Compile<Ctx> for With<'_> { fn compile_with<'ir>(
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { namespace: RawIrRef<'ir>,
let namespace = self.namespace; body: RawIrRef<'ir>,
let body = self.body; thunks: &[(ThunkId, RawIrRef<'ir>)],
let has_thunks = !self.thunks.is_empty(); ctx: &impl CodegenContext,
buf: &mut CodeBuffer,
) {
let has_thunks = !thunks.is_empty();
if has_thunks { if has_thunks {
code!(buf, ctx; "((_w)=>{" self.thunks "return " body "})({env:" namespace ",last:_w})"); code!(buf, ctx; "((_w)=>{" thunks "return " body "})({env:" namespace ",last:_w})");
} else { } else {
code!(buf, ctx; "((_w)=>(" body "))({env:" namespace ",last:_w})"); code!(buf, ctx; "((_w)=>(" body "))({env:" namespace ",last:_w})");
} }
}
} }
impl<Ctx: CodegenContext> Compile<Ctx> for Select<'_> { fn compile_select<'ir, Ctx: CodegenContext>(
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { expr: RawIrRef<'ir>,
if let Some(default) = self.default { attrpath: &[Attr<RawIrRef<'ir>>],
default: Option<RawIrRef<'ir>>,
span: TextRange,
ctx: &Ctx,
buf: &mut CodeBuffer,
) {
if let Some(default) = default {
code!(buf, ctx; code!(buf, ctx;
"$sd(" "$sd("
self.expr expr
",[" ",["
joined(self.attrpath.iter(), ",", |ctx: &Ctx, buf, attr| { joined(attrpath.iter(), ",", |ctx: &Ctx, buf, attr| {
match attr { match attr {
Attr::Str(sym, _) => code!(buf, ctx; ctx.get_sym(*sym)), Attr::Str(sym, _) => code!(buf, ctx; ctx.get_sym(*sym)),
Attr::Dynamic(expr_id, _) => code!(buf, ctx; *expr_id), Attr::Dynamic(expr_id, _) => code!(buf, ctx; *expr_id),
@@ -482,34 +496,37 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Select<'_> {
"]," "],"
default default
"," ","
self.span span
")" ")"
); );
} else { } else {
code!(buf, ctx; code!(buf, ctx;
"$s(" "$s("
self.expr expr
",[" ",["
joined(self.attrpath.iter(), ",", |ctx: &Ctx, buf, attr| { joined(attrpath.iter(), ",", |ctx: &Ctx, buf, attr| {
match attr { match attr {
Attr::Str(sym, _) => code!(buf, ctx; ctx.get_sym(*sym)), Attr::Str(sym, _) => code!(buf, ctx; ctx.get_sym(*sym)),
Attr::Dynamic(expr, _) => code!(buf, ctx; expr), Attr::Dynamic(expr, _) => code!(buf, ctx; expr),
} }
}) })
"]," "],"
self.span span
")" ")"
); );
} }
}
} }
impl<Ctx: CodegenContext> Compile<Ctx> for AttrSet<'_> { fn compile_attrset<'ir, Ctx: CodegenContext>(
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { stcs: &HashMap<'ir, SymId, (RawIrRef<'ir>, TextRange)>,
if !self.dyns.is_empty() { dyns: &[(RawIrRef<'ir>, RawIrRef<'ir>, TextRange)],
ctx: &Ctx,
buf: &mut CodeBuffer,
) {
if !dyns.is_empty() {
code!(buf, ctx; code!(buf, ctx;
"$ma(new Map([" "$ma(new Map(["
joined(self.stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(val, _))| { joined(stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(val, _))| {
let key = ctx.get_sym(sym); let key = ctx.get_sym(sym);
code!( code!(
buf, ctx; buf, ctx;
@@ -517,27 +534,27 @@ impl<Ctx: CodegenContext> Compile<Ctx> for AttrSet<'_> {
); );
}) })
"]),new Map([" "]),new Map(["
joined(self.stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(_, span))| { joined(stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(_, span))| {
code!(buf, ctx; "[" ctx.get_sym(sym) "," span "]"); code!(buf, ctx; "[" ctx.get_sym(sym) "," span "]");
}) })
"]),{dynKeys:[" "]),{dynKeys:["
joined(self.dyns.iter(), ",", |ctx: &Ctx, buf, (key, _, _)| { joined(dyns.iter(), ",", |ctx: &Ctx, buf, (key, _, _)| {
code!(buf, ctx; key); code!(buf, ctx; key);
}) })
"],dynVals:[" "],dynVals:["
joined(self.dyns.iter(), ",", |ctx: &Ctx, buf, (_, val, _)| { joined(dyns.iter(), ",", |ctx: &Ctx, buf, (_, val, _)| {
code!(buf, ctx; val); code!(buf, ctx; val);
}) })
"],dynSpans:[" "],dynSpans:["
joined(self.dyns.iter(), ",", |ctx: &Ctx, buf, (_, _, attr_span)| { joined(dyns.iter(), ",", |ctx: &Ctx, buf, (_, _, attr_span)| {
code!(buf, ctx; attr_span); code!(buf, ctx; attr_span);
}) })
"]})" "]})"
); );
} else if !self.stcs.is_empty() { } else if !stcs.is_empty() {
code!(buf, ctx; code!(buf, ctx;
"$ma(new Map([" "$ma(new Map(["
joined(self.stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(val, _))| { joined(stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(val, _))| {
let key = ctx.get_sym(sym); let key = ctx.get_sym(sym);
code!( code!(
buf, ctx; buf, ctx;
@@ -545,7 +562,7 @@ impl<Ctx: CodegenContext> Compile<Ctx> for AttrSet<'_> {
); );
}) })
"]),new Map([" "]),new Map(["
joined(self.stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(_, span))| { joined(stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(_, span))| {
code!(buf, ctx; "[" ctx.get_sym(sym) "," span "]"); code!(buf, ctx; "[" ctx.get_sym(sym) "," span "]");
}) })
"]))" "]))"
@@ -553,40 +570,44 @@ impl<Ctx: CodegenContext> Compile<Ctx> for AttrSet<'_> {
} else { } else {
code!(buf, ctx; "$e"); code!(buf, ctx; "$e");
} }
}
} }
impl<Ctx: CodegenContext> Compile<Ctx> for List<'_> { fn compile_list<Ctx: CodegenContext>(items: &[RawIrRef<'_>], ctx: &Ctx, buf: &mut CodeBuffer) {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
code!(buf, ctx; code!(buf, ctx;
"[" "["
joined(self.items.iter(), ",", |ctx: &Ctx, buf, item| { joined(items.iter(), ",", |ctx: &Ctx, buf, item| {
code!(buf, ctx; item); code!(buf, ctx; item);
}) })
"]" "]"
); );
}
} }
impl<Ctx: CodegenContext> Compile<Ctx> for ConcatStrings<'_> { fn compile_concat_strings<Ctx: CodegenContext>(
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { parts: &[RawIrRef<'_>],
force_string: bool,
ctx: &Ctx,
buf: &mut CodeBuffer,
) {
code!(buf, ctx; code!(buf, ctx;
"$cs([" "$cs(["
joined(self.parts.iter(), ",", |ctx: &Ctx, buf, part| { joined(parts.iter(), ",", |ctx: &Ctx, buf, part| {
code!(buf, ctx; part); code!(buf, ctx; part);
}) })
"]," self.force_string ")" "]," force_string ")"
); );
}
} }
impl<Ctx: CodegenContext> Compile<Ctx> for HasAttr<'_> { fn compile_has_attr<'ir, Ctx: CodegenContext>(
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { lhs: RawIrRef<'ir>,
rhs: &[Attr<RawIrRef<'ir>>],
ctx: &Ctx,
buf: &mut CodeBuffer,
) {
code!(buf, ctx; code!(buf, ctx;
"$h(" "$h("
self.lhs lhs
",[" ",["
joined(self.rhs.iter(), ",", |ctx: &Ctx, buf, attr| { joined(rhs.iter(), ",", |ctx: &Ctx, buf, attr| {
match attr { match attr {
Attr::Str(sym, _) => code!(buf, ctx; ctx.get_sym(*sym)), Attr::Str(sym, _) => code!(buf, ctx; ctx.get_sym(*sym)),
Attr::Dynamic(expr, _) => code!(buf, ctx; expr), Attr::Dynamic(expr, _) => code!(buf, ctx; expr),
@@ -594,5 +615,4 @@ impl<Ctx: CodegenContext> Compile<Ctx> for HasAttr<'_> {
}) })
"])" "])"
); );
}
} }

View File

@@ -1,18 +1,17 @@
use std::cell::UnsafeCell; use std::cell::UnsafeCell;
use std::hash::BuildHasher;
use std::path::Path; use std::path::Path;
use bumpalo::Bump; use bumpalo::Bump;
use hashbrown::{DefaultHashBuilder, HashMap, HashSet}; use ghost_cell::{GhostCell, GhostToken};
use hashbrown::{DefaultHashBuilder, HashMap, HashSet, HashTable};
use rnix::TextRange; use rnix::TextRange;
use string_interner::DefaultStringInterner; use string_interner::DefaultStringInterner;
use crate::codegen::{CodegenContext, compile}; use crate::codegen::{CodegenContext, compile};
use crate::downgrade::*; use crate::downgrade::*;
use crate::error::{Error, Result, Source}; use crate::error::{Error, Result, Source};
use crate::ir::{ use crate::ir::{ArgId, Ir, IrKey, IrRef, RawIrRef, SymId, ThunkId, ir_content_eq};
Arg, ArgId, Bool, Builtin, Ir, IrKey, IrRef, Null, ReplBinding, ScopedImportBinding, SymId,
Thunk, ThunkId, ToIr as _, WithLookup,
};
#[cfg(feature = "inspector")] #[cfg(feature = "inspector")]
use crate::runtime::inspector::InspectorServer; use crate::runtime::inspector::InspectorServer;
use crate::runtime::{Runtime, RuntimeContext}; use crate::runtime::{Runtime, RuntimeContext};
@@ -184,38 +183,36 @@ impl Context {
struct Ctx { struct Ctx {
symbols: DefaultStringInterner, symbols: DefaultStringInterner,
global: HashMap<SymId, Ir<'static>>, global: HashMap<SymId, Ir<'static, RawIrRef<'static>>>,
sources: Vec<Source>, sources: Vec<Source>,
store: DaemonStore, store: DaemonStore,
spans: UnsafeCell<Vec<(usize, TextRange)>>, spans: UnsafeCell<Vec<(usize, TextRange)>>,
thunk_count: usize, 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 { struct OwnedIr {
_bump: Bump, _bump: Bump,
ir: &'static Ir<'static>, ir: RawIrRef<'static>,
} }
impl OwnedIr { impl OwnedIr {
fn as_ref<'ir>(&'ir self) -> IrRef<'ir> { fn as_ref(&self) -> RawIrRef<'_> {
self.ir self.ir
} }
} }
impl Ctx { impl Ctx {
fn new() -> Result<Self> { fn new() -> Result<Self> {
use crate::ir::{Builtins, ToIr as _};
let mut symbols = DefaultStringInterner::new(); let mut symbols = DefaultStringInterner::new();
let mut global = HashMap::new(); let mut global = HashMap::new();
let builtins_sym = symbols.get_or_intern("builtins"); let builtins_sym = symbols.get_or_intern("builtins");
global.insert( global.insert(builtins_sym, Ir::Builtins);
builtins_sym,
Builtins {
span: TextRange::default(),
}
.to_ir(),
);
let free_globals = [ let free_globals = [
"abort", "abort",
@@ -239,38 +236,14 @@ impl Ctx {
"toString", "toString",
]; ];
let consts = [ let consts = [
( ("true", Ir::Bool(true)),
"true", ("false", Ir::Bool(false)),
Bool { ("null", Ir::Null),
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(),
),
]; ];
for name in free_globals { for name in free_globals {
let name = symbols.get_or_intern(name); let name = symbols.get_or_intern(name);
let value = Builtin { let value = Ir::Builtin(name);
inner: name,
span: rnix::TextRange::default(),
}
.to_ir();
global.insert(name, value); global.insert(name, value);
} }
for (name, value) in consts { 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, &'ctx mut self,
bump: &'ir Bump, bump: &'ir Bump,
extra_scope: Option<Scope<'ctx, 'ir>>, token: GhostToken<'id>,
) -> DowngradeCtx<'ctx, 'ir> { extra_scope: Option<Scope<'ctx>>,
) -> DowngradeCtx<'ctx, 'id, 'ir> {
let source = self.get_current_source(); let source = self.get_current_source();
DowngradeCtx::new( DowngradeCtx::new(
bump, bump,
token,
&mut self.symbols, &mut self.symbols,
&self.global, &self.global,
extra_scope, extra_scope,
@@ -325,7 +300,7 @@ impl Ctx {
fn downgrade<'ctx>( fn downgrade<'ctx>(
&'ctx mut self, &'ctx mut self,
source: Source, source: Source,
extra_scope: Option<Scope<'ctx, 'static>>, extra_scope: Option<Scope<'ctx>>,
) -> Result<OwnedIr> { ) -> Result<OwnedIr> {
tracing::debug!("Parsing Nix expression"); tracing::debug!("Parsing Nix expression");
@@ -340,17 +315,19 @@ impl Ctx {
.expr() .expr()
.ok_or_else(|| Error::parse_error("unexpected EOF".into()))?; .ok_or_else(|| Error::parse_error("unexpected EOF".into()))?;
let bump = Bump::new(); let bump = Bump::new();
GhostToken::new(|token| {
let ir = self let ir = self
.downgrade_ctx(&bump, extra_scope) .downgrade_ctx(&bump, token, extra_scope)
.downgrade_toplevel(expr)?; .downgrade_toplevel(expr)?;
let ir = unsafe { std::mem::transmute::<IrRef<'_>, IrRef<'static>>(ir) }; let ir = unsafe { std::mem::transmute::<RawIrRef<'_>, RawIrRef<'static>>(ir) };
Ok(OwnedIr { _bump: bump, ir }) Ok(OwnedIr { _bump: bump, ir })
})
} }
fn compile<'ctx>( fn compile<'ctx>(
&'ctx mut self, &'ctx mut self,
source: Source, source: Source,
extra_scope: Option<Scope<'ctx, 'static>>, extra_scope: Option<Scope<'ctx>>,
) -> Result<String> { ) -> Result<String> {
let root = self.downgrade(source, extra_scope)?; let root = self.downgrade(source, extra_scope)?;
tracing::debug!("Generating JavaScript code"); tracing::debug!("Generating JavaScript code");
@@ -426,98 +403,104 @@ impl RuntimeContext for Ctx {
} }
} }
enum Scope<'ctx, 'ir> { enum Scope<'ctx> {
Global(&'ctx HashMap<SymId, Ir<'static>>), Global(&'ctx HashMap<SymId, Ir<'static, RawIrRef<'static>>>),
Repl(&'ctx HashSet<SymId>), Repl(&'ctx HashSet<SymId>),
ScopedImport(HashSet<SymId>), ScopedImport(HashSet<SymId>),
Let(HashMap<SymId, ThunkId>), Let(HashMap<SymId, ThunkId>),
Param(SymId, IrRef<'ir>), Param(SymId, ArgId),
} }
struct ScopeGuard<'a, 'ctx, 'ir> { struct ScopeGuard<'a, 'ctx, 'id, 'ir> {
ctx: &'a mut DowngradeCtx<'ctx, 'ir>, ctx: &'a mut DowngradeCtx<'ctx, 'id, 'ir>,
} }
impl Drop for ScopeGuard<'_, '_, '_> { impl Drop for ScopeGuard<'_, '_, '_, '_> {
fn drop(&mut self) { fn drop(&mut self) {
self.ctx.scopes.pop(); self.ctx.scopes.pop();
} }
} }
impl<'ir, 'ctx> ScopeGuard<'_, 'ctx, 'ir> { impl<'id, 'ir, 'ctx> ScopeGuard<'_, 'ctx, 'id, 'ir> {
fn as_ctx(&mut self) -> &mut DowngradeCtx<'ctx, 'ir> { fn as_ctx(&mut self) -> &mut DowngradeCtx<'ctx, 'id, 'ir> {
self.ctx self.ctx
} }
} }
struct ThunkScope<'ir> { struct ThunkScope<'id, 'ir> {
bindings: bumpalo::collections::Vec<'ir, (ThunkId, IrRef<'ir>)>, bindings: bumpalo::collections::Vec<'ir, (ThunkId, IrRef<'id, 'ir>)>,
cache: HashMap<IrKey<'ir>, ThunkId>, cache: HashTable<(IrRef<'id, 'ir>, ThunkId)>,
hasher: DefaultHashBuilder, hasher: DefaultHashBuilder,
} }
impl<'ir> ThunkScope<'ir> { impl<'id, 'ir> ThunkScope<'id, 'ir> {
fn new_in(bump: &'ir Bump) -> Self { fn new_in(bump: &'ir Bump) -> Self {
Self { Self {
bindings: bumpalo::collections::Vec::new_in(bump), bindings: bumpalo::collections::Vec::new_in(bump),
cache: HashMap::new(), cache: HashTable::new(),
hasher: DefaultHashBuilder::default(), hasher: DefaultHashBuilder::default(),
} }
} }
fn lookup_cache(&self, key: IrKey<'ir>) -> Option<ThunkId> { fn lookup_cache(&self, key: IrRef<'id, 'ir>, token: &GhostToken<'id>) -> Option<ThunkId> {
self.cache.get(&key).copied() 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)); 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<Item = (ThunkId, IrRef<'ir>)>) { fn extend_bindings(&mut self, iter: impl IntoIterator<Item = (ThunkId, IrRef<'id, 'ir>)>) {
self.bindings.extend(iter); 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, bump: &'ir Bump,
token: GhostToken<'id>,
symbols: &'ctx mut DefaultStringInterner, symbols: &'ctx mut DefaultStringInterner,
source: Source, source: Source,
scopes: Vec<Scope<'ctx, 'ir>>, scopes: Vec<Scope<'ctx>>,
with_scope_count: usize, with_scope_count: usize,
arg_count: usize, arg_count: usize,
thunk_count: &'ctx mut usize, thunk_count: &'ctx mut usize,
thunk_scopes: Vec<ThunkScope<'ir>>, thunk_scopes: Vec<ThunkScope<'id, 'ir>>,
} }
fn should_thunk(ir: IrRef<'_>) -> bool { fn should_thunk<'id>(ir: IrRef<'id, '_>, token: &GhostToken<'id>) -> bool {
!matches!( !matches!(
ir, ir.borrow(token),
Ir::Builtin(_) Ir::Builtin(_)
| Ir::Builtins(_) | Ir::Builtins
| Ir::Int(_) | Ir::Int(_)
| Ir::Float(_) | Ir::Float(_)
| Ir::Bool(_) | Ir::Bool(_)
| Ir::Null(_) | Ir::Null
| Ir::Str(_) | Ir::Str(_)
| Ir::Thunk(_) | Ir::Thunk(_)
) )
} }
impl<'ctx, 'ir> DowngradeCtx<'ctx, 'ir> { impl<'ctx, 'id, 'ir> DowngradeCtx<'ctx, 'id, 'ir> {
fn new( fn new(
bump: &'ir Bump, bump: &'ir Bump,
token: GhostToken<'id>,
symbols: &'ctx mut DefaultStringInterner, symbols: &'ctx mut DefaultStringInterner,
global: &'ctx HashMap<SymId, Ir<'static>>, global: &'ctx HashMap<SymId, Ir<'static, RawIrRef<'static>>>,
extra_scope: Option<Scope<'ctx, 'ir>>, extra_scope: Option<Scope<'ctx>>,
thunk_count: &'ctx mut usize, thunk_count: &'ctx mut usize,
source: Source, source: Source,
) -> Self { ) -> Self {
Self { Self {
bump, bump,
token,
symbols, symbols,
source, source,
scopes: std::iter::once(Scope::Global(global)) 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> { impl<'ctx: 'ir, 'id, 'ir> DowngradeContext<'id, 'ir> for DowngradeCtx<'ctx, 'id, 'ir> {
fn new_expr(&self, expr: Ir<'ir>) -> IrRef<'ir> { fn new_expr(&self, expr: Ir<'ir, IrRef<'id, 'ir>>) -> IrRef<'id, 'ir> {
self.bump.alloc(expr) 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.arg_count += 1;
self.bump.alloc( ArgId(self.arg_count - 1)
Arg {
inner: ArgId(self.arg_count - 1),
span,
}
.to_ir(),
)
} }
fn maybe_thunk(&mut self, ir: IrRef<'ir>) -> IrRef<'ir> { fn maybe_thunk(&mut self, ir: IrRef<'id, 'ir>) -> IrRef<'id, 'ir> {
if should_thunk(ir) { if !should_thunk(ir, &self.token) {
let scope = self.thunk_scopes.last_mut().expect("no active cache scope"); return ir;
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 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 span = ir.span();
let id = ThunkId(*self.thunk_count); let id = ThunkId(*self.thunk_count);
*self.thunk_count += 1; *self.thunk_count = self.thunk_count.checked_add(1).expect("thunk id overflow");
scope.add_binding(id, ir); self.thunk_scopes
scope.add_cache(key, id); .last_mut()
self.new_expr(Thunk { inner: id, span }.to_ir()) .expect("no active cache scope")
} else { .add_binding(id, ir, &self.token);
ir IrRef::alloc(self.bump, Ir::Thunk(id))
}
} }
fn new_sym(&mut self, sym: String) -> SymId { 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() self.symbols.resolve(id).expect("no symbol found").into()
} }
fn lookup(&self, sym: SymId, span: TextRange) -> Result<IrRef<'ir>> { fn lookup(&self, sym: SymId, span: TextRange) -> Result<IrRef<'id, 'ir>> {
for scope in self.scopes.iter().rev() { for scope in self.scopes.iter().rev() {
match scope { match scope {
&Scope::Global(global_scope) => { &Scope::Global(global_scope) => {
if let Some(expr) = global_scope.get(&sym) { 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) => { &Scope::Repl(repl_bindings) => {
if repl_bindings.contains(&sym) { 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) => { Scope::ScopedImport(scoped_bindings) => {
if scoped_bindings.contains(&sym) { 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) => { Scope::Let(let_scope) => {
if let Some(&expr) = let_scope.get(&sym) { 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 { if param_sym == sym {
return Ok(expr); return Ok(self.new_expr(Ir::Arg(id)));
} }
} }
} }
} }
if self.with_scope_count > 0 { if self.with_scope_count > 0 {
Ok(self.new_expr(WithLookup { inner: sym, span }.to_ir())) Ok(self.new_expr(Ir::WithLookup(sym)))
} else { } else {
Err(Error::downgrade_error( Err(Error::downgrade_error(
format!("'{}' not found", self.get_sym(sym)), 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<F, R>(&mut self, keys: &[SymId], f: F) -> Result<R> fn with_let_scope<F, R>(&mut self, keys: &[SymId], f: F) -> Result<R>
where 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; let base = *self.thunk_count;
*self.thunk_count += keys.len(); *self.thunk_count = self
let iter = keys .thunk_count
.iter() .checked_add(keys.len())
.enumerate() .expect("thunk id overflow");
.map(|(offset, &key)| (key, ThunkId(base + offset))); let iter = keys.iter().enumerate().map(|(offset, &key)| {
(
key,
ThunkId(unsafe { base.checked_add(offset).unwrap_unchecked() }),
)
});
self.scopes.push(Scope::Let(iter.collect())); self.scopes.push(Scope::Let(iter.collect()));
let (vals, ret) = { let (vals, ret) = {
let mut guard = ScopeGuard { ctx: self }; let mut guard = ScopeGuard { ctx: self };
@@ -646,7 +635,7 @@ impl<'ctx: 'ir, 'ir> DowngradeContext<'ir> for DowngradeCtx<'ctx, 'ir> {
Ok(ret) Ok(ret)
} }
fn with_param_scope<F, R>(&mut self, param: SymId, arg: IrRef<'ir>, f: F) -> R fn with_param_scope<F, R>(&mut self, param: SymId, arg: ArgId, f: F) -> R
where where
F: FnOnce(&mut Self) -> R, F: FnOnce(&mut Self) -> R,
{ {
@@ -668,7 +657,10 @@ impl<'ctx: 'ir, 'ir> DowngradeContext<'ir> for DowngradeCtx<'ctx, 'ir> {
fn with_thunk_scope<F, R>( fn with_thunk_scope<F, R>(
&mut self, &mut self,
f: F, f: F,
) -> (R, bumpalo::collections::Vec<'ir, (ThunkId, IrRef<'ir>)>) ) -> (
R,
bumpalo::collections::Vec<'ir, (ThunkId, IrRef<'id, 'ir>)>,
)
where where
F: FnOnce(&mut Self) -> R, 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> { impl<'id, 'ir, 'ctx: 'ir> DowngradeCtx<'ctx, 'id, 'ir> {
fn downgrade_toplevel(mut self, root: rnix::ast::Expr) -> Result<IrRef<'ir>> { fn downgrade_toplevel(mut self, root: rnix::ast::Expr) -> Result<RawIrRef<'ir>> {
use crate::ir::TopLevel;
let body = root.downgrade(&mut self)?; let body = root.downgrade(&mut self)?;
let span = body.span();
let thunks = self let thunks = self
.thunk_scopes .thunk_scopes
.pop() .pop()
.expect("no thunk scope left???") .expect("no thunk scope left???")
.bindings; .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))
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1,54 +1,151 @@
use std::hash::{Hash, Hasher}; use std::{
hash::{Hash, Hasher},
ops::Deref,
};
use bumpalo::{Bump, boxed::Box, collections::Vec}; use bumpalo::{Bump, boxed::Box, collections::Vec};
use ghost_cell::{GhostCell, GhostToken};
use rnix::{TextRange, ast}; use rnix::{TextRange, ast};
use string_interner::symbol::SymbolU32; use string_interner::symbol::SymbolU32;
use nix_js_macros::ir;
pub type HashMap<'ir, K, V> = hashbrown::HashMap<K, V, hashbrown::DefaultHashBuilder, &'ir Bump>; pub type HashMap<'ir, K, V> = hashbrown::HashMap<K, V, hashbrown::DefaultHashBuilder, &'ir Bump>;
pub type IrRef<'ir> = &'ir Ir<'ir>; #[repr(transparent)]
ir! { #[derive(Clone, Copy)]
Ir<'ir>; 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), Int(i64),
Float(f64), Float(f64),
Bool(bool), Bool(bool),
Null, Null,
Str { inner: Box<'ir, String> }, Str(Box<'ir, String>),
AttrSet { stcs: HashMap<'ir, SymId, (IrRef<'ir>, TextRange)>, dyns: Vec<'ir, (IrRef<'ir>, IrRef<'ir>, TextRange)> }, AttrSet {
List { items: Vec<'ir, IrRef<'ir>> }, stcs: HashMap<'ir, SymId, (Ref, TextRange)>,
Path { expr: IrRef<'ir> }, dyns: Vec<'ir, (Ref, Ref, TextRange)>,
ConcatStrings { parts: Vec<'ir, IrRef<'ir>>, force_string: bool }, },
List {
items: Vec<'ir, Ref>,
},
Path(Ref),
ConcatStrings {
parts: Vec<'ir, Ref>,
force_string: bool,
},
// OPs // OPs
UnOp { rhs: IrRef<'ir>, kind: UnOpKind }, UnOp {
BinOp { lhs: IrRef<'ir>, rhs: IrRef<'ir>, kind: BinOpKind }, rhs: Ref,
HasAttr { lhs: IrRef<'ir>, rhs: Vec<'ir, Attr<'ir>> }, kind: UnOpKind,
Select { expr: IrRef<'ir>, attrpath: Vec<'ir, Attr<'ir>>, default: Option<IrRef<'ir>> }, },
BinOp {
lhs: Ref,
rhs: Ref,
kind: BinOpKind,
},
HasAttr {
lhs: Ref,
rhs: Vec<'ir, Attr<Ref>>,
},
Select {
expr: Ref,
attrpath: Vec<'ir, Attr<Ref>>,
default: Option<Ref>,
span: TextRange,
},
// Conditionals // Conditionals
If { cond: IrRef<'ir>, consq: IrRef<'ir>, alter: IrRef<'ir> }, If {
Assert { assertion: IrRef<'ir>, expr: IrRef<'ir>, assertion_raw: String }, 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), WithLookup(SymId),
// Function related // Function related
Func { body: IrRef<'ir>, param: Option<Param<'ir>>, arg: IrRef<'ir>, thunks: Vec<'ir, (ThunkId, IrRef<'ir>)> }, Func {
body: Ref,
param: Option<Param<'ir>>,
arg: ArgId,
thunks: Vec<'ir, (ThunkId, Ref)>,
},
Arg(ArgId), Arg(ArgId),
Call { func: IrRef<'ir>, arg: IrRef<'ir> }, Call {
func: Ref,
arg: Ref,
span: TextRange,
},
// Builtins // Builtins
Builtins, Builtins,
Builtin(SymId), Builtin(SymId),
// Misc // Misc
TopLevel { body: IrRef<'ir>, thunks: Vec<'ir, (ThunkId, IrRef<'ir>)> }, TopLevel {
body: Ref,
thunks: Vec<'ir, (ThunkId, Ref)>,
},
Thunk(ThunkId), Thunk(ThunkId),
CurPos, CurPos(TextRange),
ReplBinding(SymId), ReplBinding(SymId),
ScopedImportBinding(SymId), ScopedImportBinding(SymId),
} }
@@ -66,17 +163,17 @@ pub struct ArgId(pub usize);
/// Represents a key in an attribute path. /// Represents a key in an attribute path.
#[allow(unused)] #[allow(unused)]
#[derive(Debug)] #[derive(Debug)]
pub enum Attr<'ir> { pub enum Attr<Ref> {
/// A dynamic attribute key, which is an expression that must evaluate to a string. /// A dynamic attribute key, which is an expression that must evaluate to a string.
/// Example: `attrs.${key}` /// Example: `attrs.${key}`
Dynamic(IrRef<'ir>, TextRange), Dynamic(Ref, TextRange),
/// A static attribute key. /// A static attribute key.
/// Example: `attrs.key` /// Example: `attrs.key`
Str(SymId, TextRange), Str(SymId, TextRange),
} }
/// The kinds of binary operations supported in Nix. /// The kinds of binary operations supported in Nix.
#[derive(Clone, Debug, Hash, PartialEq, Eq)] #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub enum BinOpKind { pub enum BinOpKind {
// Arithmetic // Arithmetic
Add, Add,
@@ -133,7 +230,7 @@ impl From<ast::BinOpKind> for BinOpKind {
} }
/// The kinds of unary operations. /// The kinds of unary operations.
#[derive(Clone, Debug, Hash, PartialEq, Eq)] #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub enum UnOpKind { pub enum UnOpKind {
Neg, // Negation (`-`) Neg, // Negation (`-`)
Not, // Logical not (`!`) Not, // Logical not (`!`)
@@ -157,33 +254,41 @@ pub struct Param<'ir> {
} }
#[derive(Clone, Copy)] #[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<H: Hasher>(&self, state: &mut H) { fn hash<H: Hasher>(&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 { 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<IrRef<'id, '_>>,
token: &GhostToken<'id>,
state: &mut impl Hasher,
) {
core::mem::discriminant(attr).hash(state); core::mem::discriminant(attr).hash(state);
match attr { 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), Attr::Str(sym, _) => sym.hash(state),
} }
} }
fn attr_content_eq(a: &Attr<'_>, b: &Attr<'_>) -> bool { fn attr_content_eq<'id, 'ir>(
a: &Attr<IrRef<'id, 'ir>>,
b: &Attr<IrRef<'id, 'ir>>,
token: &GhostToken<'id>,
) -> bool {
match (a, b) { 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, (Attr::Str(a, _), Attr::Str(b, _)) => a == b,
_ => false, _ => false,
} }
@@ -215,230 +320,364 @@ fn param_content_eq(a: &Param<'_>, b: &Param<'_>) -> bool {
.all(|((a, _), (b, _))| a == b) .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); thunks.len().hash(state);
for (id, ir) in thunks { for &(id, ir) in thunks {
id.hash(state); 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.len() == b.len()
&& a.iter() && a.iter()
.zip(b.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); core::mem::discriminant(ir).hash(state);
match ir { match ir {
Ir::Int(x) => x.inner.hash(state), Ir::Int(x) => x.hash(state),
Ir::Float(x) => x.inner.to_bits().hash(state), Ir::Float(x) => x.to_bits().hash(state),
Ir::Bool(x) => x.inner.hash(state), Ir::Bool(x) => x.hash(state),
Ir::Null(_) => {} Ir::Null => {}
Ir::Str(x) => x.inner.hash(state), Ir::Str(x) => x.hash(state),
Ir::AttrSet(x) => { Ir::AttrSet { stcs, dyns } => {
x.stcs.len().hash(state); stcs.len().hash(state);
let mut combined: u64 = 0; 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(); let mut h = std::hash::DefaultHasher::new();
key.hash(&mut h); key.hash(&mut h);
ir_content_hash(val, &mut h); ir_content_hash(val, token, &mut h);
combined = combined.wrapping_add(h.finish()); combined = combined.wrapping_add(h.finish());
} }
combined.hash(state); combined.hash(state);
x.dyns.len().hash(state); dyns.len().hash(state);
for (k, v, _) in x.dyns.iter() { for &(k, v, _) in dyns.iter() {
ir_content_hash(k, state); ir_content_hash(k, token, state);
ir_content_hash(v, state); ir_content_hash(v, token, state);
} }
} }
Ir::List(x) => { Ir::List { items } => {
x.items.len().hash(state); items.len().hash(state);
for item in x.items.iter() { for &item in items.iter() {
ir_content_hash(item, state); ir_content_hash(item, token, state);
} }
} }
Ir::HasAttr(x) => { Ir::HasAttr { lhs, rhs } => {
ir_content_hash(x.lhs, state); ir_content_hash(*lhs, token, state);
x.rhs.len().hash(state); rhs.len().hash(state);
for attr in x.rhs.iter() { for attr in rhs.iter() {
attr_content_hash(attr, state); attr_content_hash(attr, token, state);
} }
} }
Ir::BinOp(x) => { &Ir::BinOp { lhs, rhs, kind } => {
ir_content_hash(x.lhs, state); ir_content_hash(lhs, token, state);
ir_content_hash(x.rhs, state); ir_content_hash(rhs, token, state);
x.kind.hash(state); kind.hash(state);
} }
Ir::UnOp(x) => { &Ir::UnOp { rhs, kind } => {
ir_content_hash(x.rhs, state); ir_content_hash(rhs, token, state);
x.kind.hash(state); kind.hash(state);
} }
Ir::Select(x) => { Ir::Select {
ir_content_hash(x.expr, state); expr,
x.attrpath.len().hash(state); attrpath,
for attr in x.attrpath.iter() { default,
attr_content_hash(attr, state); ..
} => {
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); default.is_some().hash(state);
if let Some(d) = x.default { if let Some(d) = default {
ir_content_hash(d, state); ir_content_hash(*d, token, state);
} }
} }
Ir::If(x) => { &Ir::If { cond, consq, alter } => {
ir_content_hash(x.cond, state); ir_content_hash(cond, token, state);
ir_content_hash(x.consq, state); ir_content_hash(consq, token, state);
ir_content_hash(x.alter, state); ir_content_hash(alter, token, state);
} }
Ir::Call(x) => { &Ir::Call { func, arg, .. } => {
ir_content_hash(x.func, state); ir_content_hash(func, token, state);
ir_content_hash(x.arg, state); ir_content_hash(arg, token, state);
} }
Ir::Assert(x) => { Ir::Assert {
ir_content_hash(x.assertion, state); assertion,
ir_content_hash(x.expr, state); expr,
x.assertion_raw.hash(state); assertion_raw,
..
} => {
ir_content_hash(*assertion, token, state);
ir_content_hash(*expr, token, state);
assertion_raw.hash(state);
} }
Ir::ConcatStrings(x) => { Ir::ConcatStrings {
x.force_string.hash(state); force_string,
x.parts.len().hash(state); parts,
for part in x.parts.iter() { } => {
ir_content_hash(part, state); 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::Path(expr) => ir_content_hash(expr, token, state),
Ir::Func(x) => { Ir::Func {
ir_content_hash(x.body, state); body,
ir_content_hash(x.arg, state); arg,
x.param.is_some().hash(state); param,
if let Some(p) = &x.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); param_content_hash(p, state);
} }
thunks_content_hash(&x.thunks, state); thunks_content_hash(thunks, token, state);
} }
Ir::TopLevel(x) => { Ir::TopLevel { body, thunks } => {
ir_content_hash(x.body, state); ir_content_hash(*body, token, state);
thunks_content_hash(&x.thunks, state); thunks_content_hash(thunks, token, state);
} }
Ir::Arg(x) => x.inner.hash(state), Ir::Arg(x) => x.hash(state),
Ir::Thunk(x) => x.inner.hash(state), Ir::Thunk(x) => x.hash(state),
Ir::Builtins(_) => {} Ir::Builtins => {}
Ir::Builtin(x) => x.inner.hash(state), Ir::Builtin(x) => x.hash(state),
Ir::CurPos(x) => x.span.hash(state), Ir::CurPos(x) => x.hash(state),
Ir::ReplBinding(x) => x.inner.hash(state), Ir::ReplBinding(x) => x.hash(state),
Ir::ScopedImportBinding(x) => x.inner.hash(state), Ir::ScopedImportBinding(x) => x.hash(state),
Ir::With(x) => { &Ir::With {
ir_content_hash(x.namespace, state); namespace,
ir_content_hash(x.body, state); body,
thunks_content_hash(&x.thunks, state); 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 { pub(crate) fn ir_content_eq<'id, 'ir>(
std::ptr::eq(a, b) a: IrRef<'id, 'ir>,
|| match (a, b) { b: IrRef<'id, 'ir>,
(Ir::Int(a), Ir::Int(b)) => a.inner == b.inner, token: &GhostToken<'id>,
(Ir::Float(a), Ir::Float(b)) => a.inner.to_bits() == b.inner.to_bits(), ) -> bool {
(Ir::Bool(a), Ir::Bool(b)) => a.inner == b.inner, std::ptr::eq(a.0, b.0)
(Ir::Null(_), Ir::Null(_)) => true, || match (a.borrow(token), b.borrow(token)) {
(Ir::Str(a), Ir::Str(b)) => *a.inner == *b.inner, (Ir::Int(a), Ir::Int(b)) => a == b,
(Ir::AttrSet(a), Ir::AttrSet(b)) => { (Ir::Float(a), Ir::Float(b)) => a.to_bits() == b.to_bits(),
a.stcs.len() == b.stcs.len() (Ir::Bool(a), Ir::Bool(b)) => a == b,
&& a.dyns.len() == b.dyns.len() (Ir::Null, Ir::Null) => true,
&& a.stcs.iter().all(|(&k, (v, _))| { (Ir::Str(a), Ir::Str(b)) => **a == **b,
b.stcs.get(&k).is_some_and(|(bv, _)| ir_content_eq(v, bv)) (
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() .iter()
.zip(b.dyns.iter()) .zip(b_dyns.iter())
.all(|((ak, av, _), (bk, bv, _))| { .all(|(&(ak, av, _), &(bk, bv, _))| {
ir_content_eq(ak, bk) && ir_content_eq(av, bv) ir_content_eq(ak, bk, token) && ir_content_eq(av, bv, token)
}) })
} }
(Ir::List(a), Ir::List(b)) => { (Ir::List { items: a }, Ir::List { items: b }) => {
a.items.len() == b.items.len() a.len() == b.len()
&& a.items && a.iter()
.iter() .zip(b.iter())
.zip(b.items.iter()) .all(|(&a, &b)| ir_content_eq(a, b, token))
.all(|(a, b)| ir_content_eq(a, b))
} }
(Ir::HasAttr(a), Ir::HasAttr(b)) => { (Ir::HasAttr { lhs: al, rhs: ar }, Ir::HasAttr { lhs: bl, rhs: br }) => {
ir_content_eq(a.lhs, b.lhs) ir_content_eq(*al, *bl, token)
&& a.rhs.len() == b.rhs.len() && ar.len() == br.len()
&& a.rhs && ar
.iter() .iter()
.zip(b.rhs.iter()) .zip(br.iter())
.all(|(a, b)| attr_content_eq(a, b)) .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::Select {
ir_content_eq(a.expr, b.expr) expr: ae,
&& a.attrpath.len() == b.attrpath.len() attrpath: aa,
&& a.attrpath default: ad,
..
},
Ir::Select {
expr: be,
attrpath: ba,
default: bd,
..
},
) => {
ir_content_eq(*ae, *be, token)
&& aa.len() == ba.len()
&& aa
.iter() .iter()
.zip(b.attrpath.iter()) .zip(ba.iter())
.all(|(a, b)| attr_content_eq(a, b)) .all(|(a, b)| attr_content_eq(a, b, token))
&& match (a.default, b.default) { && match (ad, bd) {
(Some(a), Some(b)) => ir_content_eq(a, b), (Some(a), Some(b)) => ir_content_eq(*a, *b, token),
(None, None) => true, (None, None) => true,
_ => false, _ => false,
} }
} }
(Ir::If(a), Ir::If(b)) => { (
ir_content_eq(a.cond, b.cond) &Ir::If {
&& ir_content_eq(a.consq, b.consq) cond: ac,
&& ir_content_eq(a.alter, b.alter) 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::Call {
} func: af, arg: aa, ..
(Ir::Assert(a), Ir::Assert(b)) => { },
a.assertion_raw == b.assertion_raw &Ir::Call {
&& ir_content_eq(a.assertion, b.assertion) func: bf, arg: ba, ..
&& ir_content_eq(a.expr, b.expr) },
} ) => ir_content_eq(af, bf, token) && ir_content_eq(aa, ba, token),
(Ir::ConcatStrings(a), Ir::ConcatStrings(b)) => { (
a.force_string == b.force_string Ir::Assert {
&& a.parts.len() == b.parts.len() assertion: aa,
&& a.parts 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() .iter()
.zip(b.parts.iter()) .zip(bp.iter())
.all(|(a, b)| ir_content_eq(a, b)) .all(|(&a, &b)| ir_content_eq(a, b, token))
} }
(Ir::Path(a), Ir::Path(b)) => ir_content_eq(a.expr, b.expr), (&Ir::Path(a), &Ir::Path(b)) => ir_content_eq(a, b, token),
(Ir::Func(a), Ir::Func(b)) => { (
ir_content_eq(a.body, b.body) Ir::Func {
&& ir_content_eq(a.arg, b.arg) body: ab,
&& match (&a.param, &b.param) { 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), (Some(a), Some(b)) => param_content_eq(a, b),
(None, None) => true, (None, None) => true,
_ => false, _ => 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::WithLookup(a), Ir::WithLookup(b)) => a == b,
(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,
_ => false, _ => false,
} }
} }