refactor: use GhostCell to provide interior mutability in Ir
This commit is contained in:
7
Cargo.lock
generated
7
Cargo.lock
generated
@@ -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",
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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<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);
|
||||
|
||||
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) {
|
||||
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<Ctx: CodegenContext> Compile<Ctx> 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<Ctx: CodegenContext> Compile<Ctx> for Ir<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: CodegenContext> Compile<Ctx> 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<Ctx: CodegenContext> Compile<Ctx> 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<Ctx: CodegenContext> Compile<Ctx> 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<Ctx: CodegenContext> Compile<Ctx> 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<Ctx: CodegenContext> Compile<Ctx> 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<Param<'ir>>,
|
||||
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<Ctx> for [(ThunkId, IrRef<'ir>)] {
|
||||
impl<'ir, Ctx: CodegenContext> Compile<Ctx> 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<Ctx> for [(ThunkId, IrRef<'ir>)] {
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: CodegenContext> Compile<Ctx> 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<Ctx: CodegenContext> Compile<Ctx> 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<Ctx: CodegenContext> Compile<Ctx> 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<Ctx: CodegenContext> Compile<Ctx> 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<Ctx: CodegenContext> Compile<Ctx> for List<'_> {
|
||||
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
|
||||
fn compile_select<'ir, Ctx: CodegenContext>(
|
||||
expr: RawIrRef<'ir>,
|
||||
attrpath: &[Attr<RawIrRef<'ir>>],
|
||||
default: Option<RawIrRef<'ir>>,
|
||||
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<Ctx: CodegenContext> Compile<Ctx> 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<Ctx: CodegenContext> Compile<Ctx> 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<Ctx: CodegenContext>(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<Ctx: CodegenContext>(
|
||||
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<RawIrRef<'ir>>],
|
||||
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),
|
||||
}
|
||||
})
|
||||
"])"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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<SymId, Ir<'static>>,
|
||||
global: HashMap<SymId, Ir<'static, RawIrRef<'static>>>,
|
||||
sources: Vec<Source>,
|
||||
store: DaemonStore,
|
||||
spans: UnsafeCell<Vec<(usize, TextRange)>>,
|
||||
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<Self> {
|
||||
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<Scope<'ctx, 'ir>>,
|
||||
) -> DowngradeCtx<'ctx, 'ir> {
|
||||
token: GhostToken<'id>,
|
||||
extra_scope: Option<Scope<'ctx>>,
|
||||
) -> 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<Scope<'ctx, 'static>>,
|
||||
extra_scope: Option<Scope<'ctx>>,
|
||||
) -> Result<OwnedIr> {
|
||||
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<'_>, 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<'_>, RawIrRef<'static>>(ir) };
|
||||
Ok(OwnedIr { _bump: bump, ir })
|
||||
})
|
||||
}
|
||||
|
||||
fn compile<'ctx>(
|
||||
&'ctx mut self,
|
||||
source: Source,
|
||||
extra_scope: Option<Scope<'ctx, 'static>>,
|
||||
extra_scope: Option<Scope<'ctx>>,
|
||||
) -> Result<String> {
|
||||
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<SymId, Ir<'static>>),
|
||||
enum Scope<'ctx> {
|
||||
Global(&'ctx HashMap<SymId, Ir<'static, RawIrRef<'static>>>),
|
||||
Repl(&'ctx HashSet<SymId>),
|
||||
ScopedImport(HashSet<SymId>),
|
||||
Let(HashMap<SymId, ThunkId>),
|
||||
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<IrKey<'ir>, 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<ThunkId> {
|
||||
self.cache.get(&key).copied()
|
||||
fn lookup_cache(&self, key: IrRef<'id, 'ir>, token: &GhostToken<'id>) -> Option<ThunkId> {
|
||||
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<Item = (ThunkId, IrRef<'ir>)>) {
|
||||
fn extend_bindings(&mut self, iter: impl IntoIterator<Item = (ThunkId, IrRef<'id, 'ir>)>) {
|
||||
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<Scope<'ctx, 'ir>>,
|
||||
scopes: Vec<Scope<'ctx>>,
|
||||
with_scope_count: usize,
|
||||
arg_count: 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!(
|
||||
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<SymId, Ir<'static>>,
|
||||
extra_scope: Option<Scope<'ctx, 'ir>>,
|
||||
global: &'ctx HashMap<SymId, Ir<'static, RawIrRef<'static>>>,
|
||||
extra_scope: Option<Scope<'ctx>>,
|
||||
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<IrRef<'ir>> {
|
||||
fn lookup(&self, sym: SymId, span: TextRange) -> Result<IrRef<'id, 'ir>> {
|
||||
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<F, R>(&mut self, keys: &[SymId], f: F) -> Result<R>
|
||||
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<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
|
||||
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>(
|
||||
&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<IrRef<'ir>> {
|
||||
use crate::ir::TopLevel;
|
||||
impl<'id, 'ir, 'ctx: 'ir> DowngradeCtx<'ctx, 'id, 'ir> {
|
||||
fn downgrade_toplevel(mut self, root: rnix::ast::Expr) -> Result<RawIrRef<'ir>> {
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
633
nix-js/src/ir.rs
633
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<K, V, hashbrown::DefaultHashBuilder, &'ir Bump>;
|
||||
|
||||
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<IrRef<'ir>> },
|
||||
UnOp {
|
||||
rhs: Ref,
|
||||
kind: UnOpKind,
|
||||
},
|
||||
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
|
||||
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<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),
|
||||
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<Ref> {
|
||||
/// 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<ast::BinOpKind> 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<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 {
|
||||
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);
|
||||
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<IrRef<'id, 'ir>>,
|
||||
b: &Attr<IrRef<'id, 'ir>>,
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user