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",
|
"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",
|
||||||
|
|||||||
@@ -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"]
|
||||||
|
|||||||
@@ -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
|
}) = ¶m
|
||||||
{
|
{
|
||||||
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<'_> {
|
|||||||
})
|
})
|
||||||
"])"
|
"])"
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
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 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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user