refactor: use GAT in enum Ir

This commit is contained in:
2026-05-01 20:18:00 +08:00
parent 0df38f374f
commit 5dd160cc43
7 changed files with 331 additions and 214 deletions
+15 -13
View File
@@ -39,12 +39,12 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>, T, E: std::fmt::Display>
}
pub trait DowngradeContext<'id: 'ir, 'ir> {
fn new_expr(&self, expr: Ir<'ir, IrRef<'id, 'ir>>) -> IrRef<'id, 'ir>;
fn maybe_thunk(&mut self, ir: IrRef<'id, 'ir>) -> MaybeThunk;
fn new_expr(&self, expr: Ir<'ir, GhostRef<'id, 'ir>>) -> IrRef<'id, 'ir>;
fn maybe_thunk(&mut self, ir: IrRef<'id, 'ir>) -> GhostMaybeThunkRef<'id, 'ir>;
fn intern_string(&mut self, sym: impl AsRef<str>) -> StringId;
fn resolve_sym(&self, id: StringId) -> Symbol<'_>;
fn lookup(&self, sym: StringId, span: TextRange) -> Result<MaybeThunk>;
fn lookup(&self, sym: StringId, span: TextRange) -> Result<GhostMaybeThunkRef<'id, 'ir>>;
fn get_current_source(&self) -> Source;
@@ -229,7 +229,8 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
let span = self.syntax().text_range();
let text = self.ident_token().require(ctx, span)?.to_string();
let sym = ctx.intern_string(text);
ctx.lookup(sym, span).map(|thunk| thunk.to_ir(ctx))
ctx.lookup(sym, span)
.map(|thunk| ctx.new_expr(Ir::MaybeThunk(thunk)))
}
}
@@ -835,8 +836,8 @@ fn make_attrpath_value_entry<'ir>(path: Vec<'ir, ast::Attr>, value: ast::Expr) -
}
struct FinalizedAttrSet<'id, 'ir> {
stcs: HashMap<'ir, StringId, (MaybeThunk, TextRange)>,
dyns: Vec<'ir, (IrRef<'id, 'ir>, MaybeThunk, TextRange)>,
stcs: HashMap<'ir, StringId, (GhostMaybeThunkRef<'id, 'ir>, TextRange)>,
dyns: Vec<'ir, (IrRef<'id, 'ir>, GhostMaybeThunkRef<'id, 'ir>, TextRange)>,
}
fn downgrade_attrs<'id, 'ir>(
@@ -1074,7 +1075,7 @@ where
F: FnOnce(
&mut Ctx,
&[StringId],
&[(IrRef<'id, 'ir>, MaybeThunk, TextRange)],
&[(IrRef<'id, 'ir>, GhostMaybeThunkRef<'id, 'ir>, TextRange)],
) -> Result<IrRef<'id, 'ir>>,
{
let mut pending = PendingAttrSet::new_in(ctx.bump());
@@ -1090,7 +1091,7 @@ where
let vals = {
let mut temp = Vec::with_capacity_in(keys.len(), ctx.bump());
for sym in &keys {
temp.push(finalized.stcs.get(sym).expect("WTF").0.to_ir(ctx));
temp.push(ctx.new_expr(Ir::MaybeThunk(finalized.stcs.get(sym).expect("WTF").0)));
}
temp
};
@@ -1101,7 +1102,7 @@ where
fn collect_inherit_lookups<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>>(
entries: &[ast::Entry],
ctx: &mut Ctx,
) -> Result<HashMap<'ir, StringId, (MaybeThunk, TextRange)>> {
) -> Result<HashMap<'ir, StringId, (GhostMaybeThunkRef<'id, 'ir>, TextRange)>> {
let mut inherit_lookups = HashMap::new_in(ctx.bump());
for entry in entries {
if let ast::Entry::Inherit(inherit) = entry
@@ -1141,7 +1142,7 @@ fn collect_binding_syms<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>, const AL
fn finalize_pending_set<'id, 'ir, Ctx: DowngradeContext<'id, 'ir>, const ALLOW_DYN: bool>(
pending: PendingAttrSet,
inherit_lookups: &HashMap<StringId, (MaybeThunk, TextRange)>,
inherit_lookups: &HashMap<StringId, (GhostMaybeThunkRef<'id, 'ir>, TextRange)>,
ctx: &mut Ctx,
) -> Result<FinalizedAttrSet<'id, 'ir>> {
let mut stcs = HashMap::new_in(ctx.bump());
@@ -1169,7 +1170,7 @@ fn finalize_pending_set<'id, 'ir, Ctx: DowngradeContext<'id, 'ir>, const ALLOW_D
fn finalize_pending_value<'id, 'ir, Ctx: DowngradeContext<'id, 'ir>, const ALLOW_DYN: bool>(
value: PendingValue,
inherit_lookups: &HashMap<StringId, (MaybeThunk, TextRange)>,
inherit_lookups: &HashMap<StringId, (GhostMaybeThunkRef<'id, 'ir>, TextRange)>,
ctx: &mut Ctx,
) -> Result<IrRef<'id, 'ir>> {
match value {
@@ -1185,9 +1186,10 @@ fn finalize_pending_value<'id, 'ir, Ctx: DowngradeContext<'id, 'ir>, const ALLOW
}
PendingValue::InheritScope(sym, span) => {
if let Some(&(expr, _)) = inherit_lookups.get(&sym) {
Ok(expr.to_ir(ctx))
Ok(ctx.new_expr(Ir::MaybeThunk(expr)))
} else {
ctx.lookup(sym, span).map(|val| val.to_ir(ctx))
ctx.lookup(sym, span)
.map(|val| ctx.new_expr(Ir::MaybeThunk(val)))
}
}
PendingValue::Set(set) => {
+80 -94
View File
@@ -1,5 +1,5 @@
use std::hash::Hash;
use std::ops::Deref;
use std::marker::PhantomData;
use bumpalo::Bump;
use bumpalo::collections::Vec;
@@ -10,56 +10,36 @@ use num_enum::TryFromPrimitive as _;
use rnix::{TextRange, ast};
use string_interner::DefaultStringInterner;
use crate::downgrade::DowngradeContext;
pub mod downgrade;
pub type HashMap<'ir, K, V> = hashbrown::HashMap<K, V, hashbrown::DefaultHashBuilder, &'ir Bump>;
#[repr(transparent)]
#[derive(Clone, Copy)]
pub struct IrRef<'id, 'ir>(&'ir GhostCell<'id, Ir<'ir, Self>>);
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)))
}
pub fn borrow<'a>(&'a self, token: &'a GhostToken<'id>) -> &'a Ir<'ir, Self> {
self.0.borrow(token)
}
pub type IrRef<'id, 'ir> = <GhostRef<'id, 'ir> as RefExt<'ir>>::IrRef;
pub type RawIrRef<'ir> = <RawRef<'ir> as RefExt<'ir>>::IrRef;
pub type GhostMaybeThunkRef<'id, 'ir> = <GhostRef<'id, 'ir> as RefExt<'ir>>::MaybeThunkRef;
impl<'id, 'ir> Ir<'ir, GhostRef<'id, '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) }
}
}
#[repr(transparent)]
#[derive(Clone, Copy, Debug)]
pub struct RawIrRef<'ir>(pub &'ir Ir<'ir, Self>);
impl<'ir> Deref for RawIrRef<'ir> {
type Target = Ir<'ir, RawIrRef<'ir>>;
fn deref(&self) -> &Self::Target {
self.0
pub fn freeze(this: IrRef<'id, 'ir>, _: GhostToken<'id>) -> RawIrRef<'ir> {
// SAFETY: The transmute is sound because:
// - `GhostCell<'id, T>` is `#[repr(transparent)]` over `T`, so
// `&'ir GhostCell<'id, T>` and `&'ir T` have identical layout.
// - `Ir<'ir, R>` is `#[repr(C)]`, and for every field that depends on
// `R`, instantiating `R = GhostRef<'id, 'ir>` vs `R = RawRef<'ir>`
// produces types of identical layout:
// - `R::IrRef` becomes `&'ir GhostCell<'id, Ir<…>>` vs `&'ir Ir<…>`
// - `R::MaybeThunkRef` becomes `&'ir GhostCell<'id, MaybeThunk>`
// vs `&'ir MaybeThunk`
// - `R::Ref<Ir<'ir, R>>` (used in `ConcatStrings::parts`) reduces
// to the same case as `R::IrRef`
// - Therefore `IrRef<'id, 'ir>` and `RawIrRef<'ir>` are both
// pointer-sized references with the same layout.
//
// Consuming the `GhostToken` guarantees no `borrow_mut` calls can
// occur afterwards, so the shared `&Ir` references reachable from a
// `RawIrRef<'ir>` can never alias with mutable references.
unsafe { std::mem::transmute::<IrRef<'id, 'ir>, RawIrRef<'ir>>(this) }
}
}
@@ -81,102 +61,108 @@ pub enum MaybeThunk {
WithLookup(StringId),
}
impl MaybeThunk {
fn to_ir<'id, 'ir>(self, ctx: &mut impl DowngradeContext<'id, 'ir>) -> IrRef<'id, 'ir> {
use MaybeThunk::*;
let ir = match self {
Int(x) => Ir::Int(x),
Float(x) => Ir::Float(x),
Bool(x) => Ir::Bool(x),
Null => Ir::Null,
Str(x) => Ir::Str(x),
Path(x) => Ir::Path(ctx.new_expr(Ir::Str(x))),
Thunk(x) => Ir::Thunk(x),
Arg { layer } => Ir::Arg { layer },
Builtin(x) => Ir::Builtin(x),
Builtins => Ir::Builtins,
ReplBinding(x) => Ir::ReplBinding(x),
ScopedImportBinding(x) => Ir::ScopedImportBinding(x),
WithLookup(x) => Ir::WithLookup(x),
};
ctx.new_expr(ir)
}
pub trait Ref<'ir> {
type Ref<T>
where
T: 'ir;
}
pub trait RefExt<'ir>: Ref<'ir> {
type Ir;
type IrRef;
type MaybeThunkRef;
}
impl<'ir, T: Ref<'ir> + 'ir> RefExt<'ir> for T {
type Ir = Ir<'ir, Self>;
type IrRef = Self::Ref<Self::Ir>;
type MaybeThunkRef = Self::Ref<MaybeThunk>;
}
pub struct GhostRef<'id, 'ir>(PhantomData<&'ir GhostCell<'id, ()>>);
pub struct RawRef<'ir>(PhantomData<&'ir ()>);
impl<'id, 'ir> Ref<'ir> for GhostRef<'id, 'ir> {
type Ref<T: 'ir> = &'ir GhostCell<'id, T>;
}
impl<'ir> Ref<'ir> for RawRef<'ir> {
type Ref<T: 'ir> = &'ir T;
}
#[repr(C)]
#[derive(Debug)]
pub enum Ir<'ir, Ref> {
pub enum Ir<'ir, R: RefExt<'ir> + ?Sized + 'ir> {
Int(i64),
Float(f64),
Bool(bool),
Null,
Str(StringId),
Path(Ref),
Path(R::IrRef),
AttrSet {
stcs: HashMap<'ir, StringId, (MaybeThunk, TextRange)>,
dyns: Vec<'ir, (Ref, MaybeThunk, TextRange)>,
stcs: HashMap<'ir, StringId, (R::MaybeThunkRef, TextRange)>,
dyns: Vec<'ir, (R::IrRef, R::MaybeThunkRef, TextRange)>,
},
List {
items: Vec<'ir, MaybeThunk>,
items: Vec<'ir, R::MaybeThunkRef>,
},
ConcatStrings {
parts: Vec<'ir, Ref>,
parts: Vec<'ir, R::Ref<Ir<'ir, R>>>,
force_string: bool,
},
// OPs
UnOp {
rhs: Ref,
rhs: R::IrRef,
kind: UnOpKind,
},
BinOp {
lhs: Ref,
rhs: Ref,
lhs: R::IrRef,
rhs: R::IrRef,
kind: BinOpKind,
},
HasAttr {
lhs: Ref,
rhs: Vec<'ir, Attr<Ref>>,
lhs: R::IrRef,
rhs: Vec<'ir, Attr<R::IrRef>>,
},
Select {
expr: Ref,
attrpath: Vec<'ir, Attr<Ref>>,
default: Option<Ref>,
expr: R::IrRef,
attrpath: Vec<'ir, Attr<R::IrRef>>,
default: Option<R::IrRef>,
span: TextRange,
},
// Conditionals
If {
cond: Ref,
consq: Ref,
alter: Ref,
cond: R::IrRef,
consq: R::IrRef,
alter: R::IrRef,
},
Assert {
assertion: Ref,
expr: Ref,
assertion: R::IrRef,
expr: R::IrRef,
assertion_raw: String,
span: TextRange,
},
With {
namespace: MaybeThunk,
body: Ref,
thunks: Vec<'ir, (ThunkId, Ref)>,
namespace: R::MaybeThunkRef,
body: R::IrRef,
thunks: Vec<'ir, (ThunkId, R::IrRef)>,
},
WithLookup(StringId),
// Function related
Func {
body: Ref,
body: R::IrRef,
param: Option<Param<'ir>>,
thunks: Vec<'ir, (ThunkId, Ref)>,
thunks: Vec<'ir, (ThunkId, R::IrRef)>,
},
Arg {
layer: u8,
},
Call {
func: Ref,
arg: MaybeThunk,
func: R::IrRef,
arg: R::MaybeThunkRef,
span: TextRange,
},
@@ -187,10 +173,10 @@ pub enum Ir<'ir, Ref> {
// Misc
TopLevel {
body: Ref,
thunks: Vec<'ir, (ThunkId, Ref)>,
body: R::IrRef,
thunks: Vec<'ir, (ThunkId, R::IrRef)>,
},
Thunk(ThunkId),
MaybeThunk(R::MaybeThunkRef),
CurPos(TextRange),
ReplBinding(StringId),
ScopedImportBinding(StringId),
@@ -299,7 +285,7 @@ pub struct Param<'ir> {
pub fn new_global_env(
strings: &mut DefaultStringInterner,
) -> hashbrown::HashMap<StringId, Ir<'static, RawIrRef<'static>>> {
) -> hashbrown::HashMap<StringId, Ir<'static, RawRef<'static>>> {
let mut global_env = hashbrown::HashMap::new();
let builtins_sym = StringId(strings.get_or_intern("builtins"));
global_env.insert(builtins_sym, Ir::Builtins);