1329 lines
47 KiB
Rust
1329 lines
47 KiB
Rust
use bumpalo::collections::{CollectIn, Vec};
|
|
use fix_error::{Error, Result, Source};
|
|
use fix_lang::{BuiltinId, Symbol};
|
|
use hashbrown::HashSet;
|
|
use hashbrown::hash_map::Entry;
|
|
use rnix::TextRange;
|
|
use rnix::ast::{self, AstToken, Expr, HasEntry};
|
|
use rowan::ast::AstNode;
|
|
|
|
use super::*;
|
|
|
|
trait Require<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>, T> {
|
|
fn require(self, ctx: &Ctx, span: TextRange) -> Result<T>;
|
|
}
|
|
|
|
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>, T> Require<'id, 'ir, Ctx, T> for Option<T> {
|
|
#[inline]
|
|
fn require(self, ctx: &Ctx, span: TextRange) -> Result<T> {
|
|
self.ok_or_else(|| {
|
|
Error::parse_error("invalid syntax".into())
|
|
.with_source(ctx.get_current_source())
|
|
.with_span(span)
|
|
})
|
|
}
|
|
}
|
|
|
|
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>, T, E: std::fmt::Display>
|
|
Require<'id, 'ir, Ctx, T> for std::result::Result<T, E>
|
|
{
|
|
#[inline]
|
|
fn require(self, ctx: &Ctx, span: TextRange) -> Result<T> {
|
|
self.map_err(|e| {
|
|
Error::parse_error(format!("invalid syntax: {e}"))
|
|
.with_source(ctx.get_current_source())
|
|
.with_span(span)
|
|
})
|
|
}
|
|
}
|
|
|
|
pub trait DowngradeContext<'id: 'ir, 'ir> {
|
|
fn new_expr(&self, expr: Ir<'ir, GhostRoRef<'id, 'ir>>) -> GhostRoIrRef<'id, 'ir>;
|
|
fn maybe_thunk(&mut self, ir: GhostRoIrRef<'id, 'ir>) -> GhostRoMaybeThunkRef<'id, 'ir>;
|
|
|
|
fn intern_string(&mut self, sym: impl AsRef<str>) -> StringId;
|
|
fn resolve_sym(&self, id: StringId) -> Symbol<'_>;
|
|
fn lookup(&mut self, sym: StringId, span: TextRange) -> Result<GhostRoMaybeThunkRef<'id, 'ir>>;
|
|
|
|
fn get_current_source(&self) -> Source;
|
|
|
|
fn with_param_scope<F, R>(&mut self, param: StringId, f: F) -> R
|
|
where
|
|
F: FnOnce(&mut Self) -> R;
|
|
fn with_let_scope<F, R>(&mut self, bindings: &[StringId], f: F) -> Result<R>
|
|
where
|
|
F: FnOnce(&mut Self) -> Result<(Vec<'ir, GhostRoMaybeThunkRef<'id, 'ir>>, R)>;
|
|
fn with_with_scope<F, R>(&mut self, namespace: GhostRoMaybeThunkRef<'id, 'ir>, f: F) -> R
|
|
where
|
|
F: FnOnce(&mut Self) -> R;
|
|
fn with_thunk_scope<F, R>(&mut self, f: F) -> (R, Vec<'ir, (ThunkId, GhostRoIrRef<'id, 'ir>)>)
|
|
where
|
|
F: FnOnce(&mut Self) -> R;
|
|
|
|
fn bump(&self) -> &'ir bumpalo::Bump;
|
|
}
|
|
|
|
pub trait Downgrade<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> {
|
|
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>>;
|
|
}
|
|
|
|
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for Expr {
|
|
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
|
use Expr::*;
|
|
match self {
|
|
Apply(apply) => apply.downgrade(ctx),
|
|
Assert(assert) => assert.downgrade(ctx),
|
|
Error(error) => {
|
|
let span = error.syntax().text_range();
|
|
Err(self::Error::downgrade_error(
|
|
error.to_string(),
|
|
ctx.get_current_source(),
|
|
span,
|
|
))
|
|
}
|
|
IfElse(ifelse) => ifelse.downgrade(ctx),
|
|
Select(select) => select.downgrade(ctx),
|
|
Str(str) => str.downgrade(ctx),
|
|
PathAbs(path) => path.downgrade(ctx),
|
|
PathRel(path) => path.downgrade(ctx),
|
|
PathHome(path) => path.downgrade(ctx),
|
|
PathSearch(path) => path.downgrade(ctx),
|
|
Literal(lit) => lit.downgrade(ctx),
|
|
Lambda(lambda) => lambda.downgrade(ctx),
|
|
LegacyLet(let_) => let_.downgrade(ctx),
|
|
LetIn(letin) => letin.downgrade(ctx),
|
|
List(list) => list.downgrade(ctx),
|
|
BinOp(op) => op.downgrade(ctx),
|
|
AttrSet(attrs) => attrs.downgrade(ctx),
|
|
UnaryOp(op) => op.downgrade(ctx),
|
|
Ident(ident) => ident.downgrade(ctx),
|
|
CurPos(curpos) => curpos.downgrade(ctx),
|
|
With(with) => with.downgrade(ctx),
|
|
HasAttr(has) => has.downgrade(ctx),
|
|
Paren(paren) => paren
|
|
.expr()
|
|
.require(ctx, paren.syntax().text_range())?
|
|
.downgrade(ctx),
|
|
Root(root) => root
|
|
.expr()
|
|
.require(ctx, root.syntax().text_range())?
|
|
.downgrade(ctx),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::Assert {
|
|
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
|
let span = self.syntax().text_range();
|
|
let assertion = self.condition().require(ctx, span)?;
|
|
let assertion_raw = assertion.to_string();
|
|
let assertion = assertion.downgrade(ctx)?;
|
|
let expr = self.body().require(ctx, span)?.downgrade(ctx)?;
|
|
Ok(ctx.new_expr(Ir::Assert {
|
|
assertion,
|
|
expr,
|
|
assertion_raw,
|
|
span,
|
|
}))
|
|
}
|
|
}
|
|
|
|
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::IfElse {
|
|
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
|
let span = self.syntax().text_range();
|
|
let cond = self.condition().require(ctx, span)?.downgrade(ctx)?;
|
|
let consq = self.body().require(ctx, span)?.downgrade(ctx)?;
|
|
let alter = self.else_body().require(ctx, span)?.downgrade(ctx)?;
|
|
Ok(ctx.new_expr(Ir::If { cond, consq, alter }))
|
|
}
|
|
}
|
|
|
|
macro_rules! path {
|
|
($ty:ident) => {
|
|
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::$ty {
|
|
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
|
downgrade_path(self.parts(), ctx)
|
|
}
|
|
}
|
|
};
|
|
}
|
|
path!(PathAbs);
|
|
path!(PathRel);
|
|
path!(PathHome);
|
|
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::PathSearch {
|
|
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
|
let span = self.syntax().text_range();
|
|
let path = {
|
|
let temp = self.content().require(ctx, span)?;
|
|
let text = temp.text();
|
|
let id = ctx.intern_string(&text[1..text.len() - 1]);
|
|
let expr = ctx.new_expr(Ir::Str(id));
|
|
ctx.maybe_thunk(expr)
|
|
};
|
|
// HACK: disgusting eww
|
|
let find_file = ctx.new_expr(Ir::Builtin(BuiltinId::FindFile));
|
|
let sym = ctx.intern_string("nixPath");
|
|
let nix_path = ctx.new_expr(Ir::BuiltinConst(sym));
|
|
let nix_path = ctx.maybe_thunk(nix_path);
|
|
let call = ctx.new_expr(Ir::Call {
|
|
func: find_file,
|
|
arg: nix_path,
|
|
span,
|
|
});
|
|
Ok(ctx.new_expr(Ir::Call {
|
|
func: call,
|
|
arg: path,
|
|
span,
|
|
}))
|
|
}
|
|
}
|
|
|
|
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::Str {
|
|
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
|
let normalized = self.normalized_parts();
|
|
let is_single_literal = normalized.len() == 1
|
|
&& matches!(normalized.first(), Some(ast::InterpolPart::Literal(_)));
|
|
|
|
let bump = ctx.bump();
|
|
let mut parts = normalized.into_iter().map(|part| match part {
|
|
ast::InterpolPart::Literal(lit) => {
|
|
let id = ctx.intern_string(lit);
|
|
Ok(ctx.new_expr(Ir::Str(id)))
|
|
}
|
|
ast::InterpolPart::Interpolation(interpol) => interpol
|
|
.expr()
|
|
.require(ctx, interpol.syntax().text_range())?
|
|
.downgrade(ctx),
|
|
});
|
|
|
|
Ok(if is_single_literal {
|
|
parts.next().expect("is_single_literal checked")?
|
|
} else {
|
|
let parts = parts.collect_in::<Result<_>>(bump)?;
|
|
ctx.new_expr(Ir::ConcatStrings {
|
|
parts,
|
|
force_string: true,
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::Literal {
|
|
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
|
let span = self.syntax().text_range();
|
|
let expr = match self.kind() {
|
|
ast::LiteralKind::Integer(int) => Ir::Int(int.value().require(ctx, span)?),
|
|
ast::LiteralKind::Float(float) => Ir::Float(float.value().require(ctx, span)?),
|
|
ast::LiteralKind::Uri(uri) => {
|
|
let id = ctx.intern_string(uri.syntax().text());
|
|
Ir::Str(id)
|
|
}
|
|
};
|
|
Ok(ctx.new_expr(expr))
|
|
}
|
|
}
|
|
|
|
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::Ident {
|
|
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
|
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| ctx.new_expr(Ir::MaybeThunk(thunk)))
|
|
}
|
|
}
|
|
|
|
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::CurPos {
|
|
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
|
fn byte_offset_to_line_col(content: &str, offset: usize) -> (u32, u32) {
|
|
let mut line = 1u32;
|
|
let mut col = 1u32;
|
|
|
|
for (idx, ch) in content.char_indices() {
|
|
if idx >= offset {
|
|
break;
|
|
}
|
|
if ch == '\n' {
|
|
line += 1;
|
|
col = 1;
|
|
} else {
|
|
col += 1;
|
|
}
|
|
}
|
|
|
|
(line, col)
|
|
}
|
|
|
|
let span = self.syntax().text_range();
|
|
let source = ctx.get_current_source();
|
|
let (line, column) = byte_offset_to_line_col(&source.src, span.start().into());
|
|
|
|
let file_sym = ctx.intern_string("file");
|
|
let line_sym = ctx.intern_string("line");
|
|
let column_sym = ctx.intern_string("column");
|
|
|
|
let file: GhostRoMaybeThunkRef = ctx
|
|
.bump()
|
|
.alloc(GhostCell::new(MaybeThunk::Str(ctx.intern_string(source.get_name()))).into());
|
|
let line = ctx
|
|
.bump()
|
|
.alloc(GhostCell::new(MaybeThunk::Int(i64::from(line))).into());
|
|
let column = ctx
|
|
.bump()
|
|
.alloc(GhostCell::new(MaybeThunk::Int(i64::from(column))).into());
|
|
|
|
let map = {
|
|
let mut map = HashMap::new_in(ctx.bump());
|
|
|
|
map.insert(file_sym, (file, TextRange::default()));
|
|
map.insert(line_sym, (line, TextRange::default()));
|
|
map.insert(column_sym, (column, TextRange::default()));
|
|
|
|
map
|
|
};
|
|
|
|
Ok(ctx.new_expr(Ir::AttrSet {
|
|
stcs: map,
|
|
dyns: Vec::new_in(ctx.bump()),
|
|
}))
|
|
}
|
|
}
|
|
|
|
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::AttrSet {
|
|
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
|
let rec = self.rec_token().is_some();
|
|
if !rec {
|
|
let attrs = downgrade_attrs(self, ctx)?;
|
|
return Ok(ctx.new_expr(Ir::AttrSet {
|
|
stcs: attrs.stcs,
|
|
dyns: attrs.dyns,
|
|
}));
|
|
}
|
|
|
|
let entries: Vec<'ir, _> = self.entries().collect_in(ctx.bump());
|
|
downgrade_rec_bindings(entries, ctx)
|
|
}
|
|
}
|
|
|
|
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::List {
|
|
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
|
let bump = ctx.bump();
|
|
let items = self
|
|
.items()
|
|
.map(|item| {
|
|
let id = item.downgrade(ctx)?;
|
|
Ok(ctx.maybe_thunk(id))
|
|
})
|
|
.collect_in::<Result<_>>(bump)?;
|
|
Ok(ctx.new_expr(Ir::List { items }))
|
|
}
|
|
}
|
|
|
|
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::BinOp {
|
|
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
|
use BinOpKind::*;
|
|
use ast::BinOpKind as Kind;
|
|
|
|
let span = self.syntax().text_range();
|
|
let lhs = self.lhs().require(ctx, span)?.downgrade(ctx)?;
|
|
let rhs = self.rhs().require(ctx, span)?.downgrade(ctx)?;
|
|
let kind = match self.operator().require(ctx, span)? {
|
|
Kind::Concat => Con,
|
|
Kind::Update => Upd,
|
|
Kind::Add => Add,
|
|
Kind::Sub => Sub,
|
|
Kind::Mul => Mul,
|
|
Kind::Div => Div,
|
|
Kind::And => And,
|
|
Kind::Equal => Eq,
|
|
Kind::Implication => Impl,
|
|
Kind::Less => Lt,
|
|
Kind::LessOrEq => Leq,
|
|
Kind::More => Gt,
|
|
Kind::MoreOrEq => Geq,
|
|
Kind::NotEqual => Neq,
|
|
Kind::Or => Or,
|
|
Kind::PipeLeft => {
|
|
let arg = ctx.maybe_thunk(rhs);
|
|
return Ok(ctx.new_expr(Ir::Call {
|
|
func: lhs,
|
|
arg,
|
|
span,
|
|
}));
|
|
}
|
|
Kind::PipeRight => {
|
|
let arg = ctx.maybe_thunk(lhs);
|
|
return Ok(ctx.new_expr(Ir::Call {
|
|
func: rhs,
|
|
arg,
|
|
span,
|
|
}));
|
|
}
|
|
};
|
|
Ok(ctx.new_expr(Ir::BinOp { lhs, rhs, kind }))
|
|
}
|
|
}
|
|
|
|
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::HasAttr {
|
|
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
|
let span = self.syntax().text_range();
|
|
let lhs = self.expr().require(ctx, span)?.downgrade(ctx)?;
|
|
let rhs = downgrade_attrpath(self.attrpath().require(ctx, span)?, ctx)?;
|
|
Ok(ctx.new_expr(Ir::HasAttr { lhs, rhs }))
|
|
}
|
|
}
|
|
|
|
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::UnaryOp {
|
|
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
|
let span = self.syntax().text_range();
|
|
let rhs = self.expr().require(ctx, span)?.downgrade(ctx)?;
|
|
let kind = self.operator().require(ctx, span)?.into();
|
|
Ok(ctx.new_expr(Ir::UnOp { rhs, kind }))
|
|
}
|
|
}
|
|
|
|
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::Select {
|
|
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
|
let span = self.syntax().text_range();
|
|
let expr = self.expr().require(ctx, span)?.downgrade(ctx)?;
|
|
let attrpath = downgrade_attrpath(self.attrpath().require(ctx, span)?, ctx)?;
|
|
let default = self
|
|
.default_expr()
|
|
.map(|expr| expr.downgrade(ctx))
|
|
.transpose()?;
|
|
let span = self.syntax().text_range();
|
|
Ok(ctx.new_expr(Ir::Select {
|
|
expr,
|
|
attrpath,
|
|
default,
|
|
span,
|
|
}))
|
|
}
|
|
}
|
|
|
|
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::LegacyLet {
|
|
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
|
let span = self.syntax().text_range();
|
|
let entries: Vec<'ir, _> = self.entries().collect_in(ctx.bump());
|
|
let attrset_expr = downgrade_let_bindings(entries, ctx, |ctx, binding_keys| {
|
|
let mut stcs = HashMap::new_in(ctx.bump());
|
|
let dyns = Vec::new_in(ctx.bump());
|
|
|
|
for sym in binding_keys {
|
|
let expr = ctx.lookup(*sym, rnix::TextRange::default())?;
|
|
stcs.insert(*sym, (expr, rnix::TextRange::default()));
|
|
}
|
|
|
|
Ok(ctx.new_expr(Ir::AttrSet { stcs, dyns }))
|
|
})?;
|
|
|
|
let body_sym = ctx.intern_string("body");
|
|
Ok(ctx.new_expr(Ir::Select {
|
|
expr: attrset_expr,
|
|
attrpath: Vec::from_iter_in(
|
|
[Attr::Str(body_sym, rnix::TextRange::default())],
|
|
ctx.bump(),
|
|
),
|
|
default: None,
|
|
span,
|
|
}))
|
|
}
|
|
}
|
|
|
|
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::LetIn {
|
|
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
|
let entries: Vec<'ir, _> = self.entries().collect_in(ctx.bump());
|
|
let span = self.syntax().text_range();
|
|
let body_expr = self.body().require(ctx, span)?;
|
|
|
|
downgrade_let_bindings(entries, ctx, |ctx, _binding_keys| body_expr.downgrade(ctx))
|
|
}
|
|
}
|
|
|
|
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::With {
|
|
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
|
let span = self.syntax().text_range();
|
|
let namespace = self.namespace().require(ctx, span)?.downgrade(ctx)?;
|
|
let namespace = ctx.maybe_thunk(namespace);
|
|
|
|
let body_expr = self.body().require(ctx, span)?;
|
|
ctx.with_with_scope(namespace, |ctx| body_expr.downgrade(ctx))
|
|
}
|
|
}
|
|
|
|
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::Lambda {
|
|
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
|
let span = self.syntax().text_range();
|
|
let raw_param = self.param().require(ctx, span)?;
|
|
let body_ast = self.body().require(ctx, span)?;
|
|
|
|
struct Ret<'id, 'ir> {
|
|
param: Option<Param<'ir>>,
|
|
body: GhostRoIrRef<'id, 'ir>,
|
|
}
|
|
|
|
let (ret, thunks) = ctx.with_thunk_scope(|ctx| {
|
|
let param;
|
|
let body;
|
|
|
|
match raw_param {
|
|
ast::Param::IdentParam(id) => {
|
|
let param_sym = ctx.intern_string(id.to_string());
|
|
param = None;
|
|
|
|
body = ctx.with_param_scope(param_sym, |ctx| body_ast.downgrade(ctx))?;
|
|
}
|
|
ast::Param::Pattern(pattern) => {
|
|
let alias = pattern
|
|
.pat_bind()
|
|
.map(|alias| {
|
|
let ident = alias.ident().require(ctx, alias.syntax().text_range())?;
|
|
Ok::<_, std::boxed::Box<Error>>(ctx.intern_string(ident.to_string()))
|
|
})
|
|
.transpose()?;
|
|
|
|
let ellipsis = pattern.ellipsis_token().is_some();
|
|
let pat_entries = pattern.pat_entries();
|
|
|
|
let PatternBindings {
|
|
body: inner_body,
|
|
required,
|
|
optional,
|
|
} = downgrade_pattern_bindings(pat_entries, alias, ctx, |ctx, _| {
|
|
body_ast.clone().downgrade(ctx)
|
|
})?;
|
|
|
|
param = Some(Param {
|
|
required,
|
|
optional,
|
|
ellipsis,
|
|
});
|
|
|
|
body = inner_body;
|
|
}
|
|
}
|
|
|
|
Result::Ok(Ret { param, body })
|
|
});
|
|
let Ret { param, body } = ret?;
|
|
|
|
Ok(ctx.new_expr(Ir::Func {
|
|
body,
|
|
param,
|
|
thunks,
|
|
}))
|
|
}
|
|
}
|
|
|
|
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::Apply {
|
|
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
|
let span = self.syntax().text_range();
|
|
let func = self.lambda().require(ctx, span)?.downgrade(ctx)?;
|
|
let arg = self.argument().require(ctx, span)?.downgrade(ctx)?;
|
|
let arg = ctx.maybe_thunk(arg);
|
|
let span = self.syntax().text_range();
|
|
Ok(ctx.new_expr(Ir::Call { func, arg, span }))
|
|
}
|
|
}
|
|
|
|
enum PendingValue<'ir> {
|
|
Expr(ast::Expr),
|
|
InheritFrom(ast::Expr, StringId, TextRange),
|
|
InheritScope(StringId, TextRange),
|
|
Set(PendingAttrSet<'ir>),
|
|
ExtendedRecAttrSet {
|
|
base: ast::AttrSet,
|
|
extensions: Vec<'ir, ast::Entry>,
|
|
},
|
|
}
|
|
|
|
struct PendingAttrSet<'ir> {
|
|
stcs: HashMap<'ir, StringId, (PendingValue<'ir>, TextRange)>,
|
|
dyns: Vec<'ir, (ast::Attr, PendingValue<'ir>, TextRange)>,
|
|
}
|
|
|
|
impl<'id: 'ir, 'ir> PendingAttrSet<'ir> {
|
|
fn new_in(bump: &'ir bumpalo::Bump) -> Self {
|
|
Self {
|
|
stcs: HashMap::new_in(bump),
|
|
dyns: Vec::new_in(bump),
|
|
}
|
|
}
|
|
|
|
fn insert(
|
|
&mut self,
|
|
path: &[ast::Attr],
|
|
value: ast::Expr,
|
|
ctx: &mut impl DowngradeContext<'id, 'ir>,
|
|
) -> Result<()> {
|
|
let first = path.first().expect("empty attrpath passed. this is a bug");
|
|
let rest = &path[1..];
|
|
let span = first.syntax().text_range();
|
|
|
|
match first {
|
|
ast::Attr::Ident(ident) => {
|
|
let sym = ctx.intern_string(ident.to_string());
|
|
self.insert_static(sym, span, rest, value, ctx)
|
|
}
|
|
ast::Attr::Str(string) => {
|
|
let parts = string.normalized_parts();
|
|
if parts.len() == 1
|
|
&& let ast::InterpolPart::Literal(lit) =
|
|
parts.into_iter().next().expect("len checked")
|
|
{
|
|
let sym = ctx.intern_string(lit);
|
|
return self.insert_static(sym, span, rest, value, ctx);
|
|
}
|
|
self.insert_dynamic(first.clone(), span, rest, value, ctx)
|
|
}
|
|
ast::Attr::Dynamic(_) => self.insert_dynamic(first.clone(), span, rest, value, ctx),
|
|
}
|
|
}
|
|
|
|
fn insert_static(
|
|
&mut self,
|
|
sym: StringId,
|
|
span: TextRange,
|
|
path: &[ast::Attr],
|
|
value: ast::Expr,
|
|
ctx: &mut impl DowngradeContext<'id, 'ir>,
|
|
) -> Result<()> {
|
|
if !path.is_empty() {
|
|
match self.stcs.entry(sym) {
|
|
Entry::Occupied(mut entry) => {
|
|
let (existing, existing_span) = entry.get_mut();
|
|
|
|
if let PendingValue::Expr(ast::Expr::AttrSet(attrset)) = existing
|
|
&& attrset.rec_token().is_some()
|
|
{
|
|
let base = attrset.clone();
|
|
let ext_entry = make_attrpath_value_entry(
|
|
Vec::from_iter_in(path.iter().cloned(), ctx.bump()),
|
|
value,
|
|
);
|
|
*existing = PendingValue::ExtendedRecAttrSet {
|
|
base,
|
|
extensions: Vec::from_iter_in([ext_entry], ctx.bump()),
|
|
};
|
|
return Ok(());
|
|
}
|
|
|
|
if let PendingValue::ExtendedRecAttrSet { extensions, .. } = existing {
|
|
let ext_entry = make_attrpath_value_entry(
|
|
Vec::from_iter_in(path.iter().cloned(), ctx.bump()),
|
|
value,
|
|
);
|
|
extensions.push(ext_entry);
|
|
return Ok(());
|
|
}
|
|
|
|
let nested = Self::ensure_pending_set(existing, ctx, *existing_span)?;
|
|
nested.insert(path, value, ctx)
|
|
}
|
|
Entry::Vacant(entry) => {
|
|
let mut nested = PendingAttrSet::new_in(ctx.bump());
|
|
nested.insert(path, value, ctx)?;
|
|
entry.insert((PendingValue::Set(nested), span));
|
|
Ok(())
|
|
}
|
|
}
|
|
} else {
|
|
match self.stcs.entry(sym) {
|
|
Entry::Occupied(mut entry) => {
|
|
let (existing, existing_span) = entry.get_mut();
|
|
Self::merge_value(existing, *existing_span, value, span, ctx)
|
|
}
|
|
Entry::Vacant(entry) => {
|
|
entry.insert((PendingValue::Expr(value), span));
|
|
Ok(())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn insert_dynamic(
|
|
&mut self,
|
|
attr: ast::Attr,
|
|
span: TextRange,
|
|
path: &[ast::Attr],
|
|
value: ast::Expr,
|
|
ctx: &mut impl DowngradeContext<'id, 'ir>,
|
|
) -> Result<()> {
|
|
if !path.is_empty() {
|
|
let mut nested = PendingAttrSet::new_in(ctx.bump());
|
|
nested.insert_dynamic(
|
|
path[0].clone(),
|
|
path[0].syntax().text_range(),
|
|
&path[1..],
|
|
value,
|
|
ctx,
|
|
)?;
|
|
self.dyns.push((attr, PendingValue::Set(nested), span));
|
|
} else {
|
|
self.dyns.push((attr, PendingValue::Expr(value), span));
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn ensure_pending_set<'a>(
|
|
value: &'a mut PendingValue<'ir>,
|
|
ctx: &mut impl DowngradeContext<'id, 'ir>,
|
|
span: TextRange,
|
|
) -> Result<&'a mut PendingAttrSet<'ir>> {
|
|
match value {
|
|
PendingValue::Set(set) => Ok(set),
|
|
PendingValue::Expr(expr) => {
|
|
if let ast::Expr::AttrSet(attrset) = expr {
|
|
let mut nested = PendingAttrSet::new_in(ctx.bump());
|
|
nested.collect_entries(attrset.entries(), ctx)?;
|
|
*value = PendingValue::Set(nested);
|
|
match value {
|
|
PendingValue::Set(set) => Ok(set),
|
|
_ => unreachable!(),
|
|
}
|
|
} else {
|
|
Err(Error::downgrade_error(
|
|
"attribute already defined but is not an attribute set".to_string(),
|
|
ctx.get_current_source(),
|
|
span,
|
|
))
|
|
}
|
|
}
|
|
PendingValue::ExtendedRecAttrSet { .. } => Err(Error::downgrade_error(
|
|
"cannot add nested attributes to rec attribute set with extensions".to_string(),
|
|
ctx.get_current_source(),
|
|
span,
|
|
)),
|
|
PendingValue::InheritFrom(..) | PendingValue::InheritScope(..) => {
|
|
Err(Error::downgrade_error(
|
|
"attribute already defined (inherited)".to_string(),
|
|
ctx.get_current_source(),
|
|
span,
|
|
))
|
|
}
|
|
}
|
|
}
|
|
|
|
fn merge_value(
|
|
existing: &mut PendingValue<'ir>,
|
|
existing_span: TextRange,
|
|
new_value: ast::Expr,
|
|
new_span: TextRange,
|
|
ctx: &mut impl DowngradeContext<'id, 'ir>,
|
|
) -> Result<()> {
|
|
match existing {
|
|
PendingValue::Set(existing_set) => {
|
|
if let ast::Expr::AttrSet(new_attrset) = new_value {
|
|
existing_set.collect_entries(new_attrset.entries(), ctx)
|
|
} else {
|
|
Err(Error::downgrade_error(
|
|
"attribute already defined as attribute set".to_string(),
|
|
ctx.get_current_source(),
|
|
new_span,
|
|
))
|
|
}
|
|
}
|
|
PendingValue::Expr(existing_expr) => {
|
|
if let ast::Expr::AttrSet(existing_attrset) = existing_expr {
|
|
if let ast::Expr::AttrSet(new_attrset) = new_value {
|
|
let is_rec = existing_attrset.rec_token().is_some();
|
|
if is_rec {
|
|
let base = existing_attrset.clone();
|
|
let extensions: Vec<'ir, _> =
|
|
new_attrset.entries().collect_in(ctx.bump());
|
|
*existing = PendingValue::ExtendedRecAttrSet { base, extensions };
|
|
Ok(())
|
|
} else {
|
|
let mut merged = PendingAttrSet::new_in(ctx.bump());
|
|
merged.collect_entries(existing_attrset.entries(), ctx)?;
|
|
merged.collect_entries(new_attrset.entries(), ctx)?;
|
|
*existing = PendingValue::Set(merged);
|
|
Ok(())
|
|
}
|
|
} else {
|
|
Err(Error::downgrade_error(
|
|
"attribute already defined as attribute set".to_string(),
|
|
ctx.get_current_source(),
|
|
new_span,
|
|
))
|
|
}
|
|
} else {
|
|
Err(Error::downgrade_error(
|
|
"attribute already defined".to_string(),
|
|
ctx.get_current_source(),
|
|
existing_span,
|
|
))
|
|
}
|
|
}
|
|
PendingValue::ExtendedRecAttrSet { extensions, .. } => {
|
|
if let ast::Expr::AttrSet(new_attrset) = new_value {
|
|
extensions.extend(new_attrset.entries());
|
|
Ok(())
|
|
} else {
|
|
Err(Error::downgrade_error(
|
|
"attribute already defined as attribute set".to_string(),
|
|
ctx.get_current_source(),
|
|
new_span,
|
|
))
|
|
}
|
|
}
|
|
PendingValue::InheritFrom(..) | PendingValue::InheritScope(..) => {
|
|
Err(Error::downgrade_error(
|
|
"attribute already defined (inherited)".to_string(),
|
|
ctx.get_current_source(),
|
|
existing_span,
|
|
))
|
|
}
|
|
}
|
|
}
|
|
|
|
fn collect_entries(
|
|
&mut self,
|
|
entries: impl Iterator<Item = ast::Entry>,
|
|
ctx: &mut impl DowngradeContext<'id, 'ir>,
|
|
) -> Result<()> {
|
|
for entry in entries {
|
|
match entry {
|
|
ast::Entry::Inherit(inherit) => {
|
|
self.collect_inherit(inherit, ctx)?;
|
|
}
|
|
ast::Entry::AttrpathValue(value) => {
|
|
let span = value.syntax().text_range();
|
|
let attrpath = value.attrpath().require(ctx, span)?;
|
|
let path: Vec<'ir, _> = attrpath.attrs().collect_in(ctx.bump());
|
|
let expr = value.value().require(ctx, span)?;
|
|
self.insert(&path, expr, ctx)?;
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn collect_inherit(
|
|
&mut self,
|
|
inherit: ast::Inherit,
|
|
ctx: &mut impl DowngradeContext<'id, 'ir>,
|
|
) -> Result<()> {
|
|
let from = inherit
|
|
.from()
|
|
.map(|f| {
|
|
let span = f.syntax().text_range();
|
|
f.expr().require(ctx, span)
|
|
})
|
|
.transpose()?;
|
|
for attr in inherit.attrs() {
|
|
let span = attr.syntax().text_range();
|
|
let sym = match &attr {
|
|
ast::Attr::Ident(ident) => ctx.intern_string(ident.to_string()),
|
|
ast::Attr::Str(s) => {
|
|
let parts = s.normalized_parts();
|
|
if parts.len() == 1
|
|
&& let ast::InterpolPart::Literal(lit) =
|
|
parts.into_iter().next().expect("len checked")
|
|
{
|
|
ctx.intern_string(lit)
|
|
} else {
|
|
return Err(Error::downgrade_error(
|
|
"dynamic attributes not allowed in inherit".to_string(),
|
|
ctx.get_current_source(),
|
|
span,
|
|
));
|
|
}
|
|
}
|
|
ast::Attr::Dynamic(_) => {
|
|
return Err(Error::downgrade_error(
|
|
"dynamic attributes not allowed in inherit".to_string(),
|
|
ctx.get_current_source(),
|
|
span,
|
|
));
|
|
}
|
|
};
|
|
|
|
if self.stcs.contains_key(&sym) {
|
|
return Err(Error::downgrade_error(
|
|
format!("attribute '{}' already defined", ctx.resolve_sym(sym)),
|
|
ctx.get_current_source(),
|
|
span,
|
|
));
|
|
}
|
|
|
|
let pending_value = if let Some(ref from_expr) = from {
|
|
PendingValue::InheritFrom(from_expr.clone(), sym, span)
|
|
} else {
|
|
PendingValue::InheritScope(sym, span)
|
|
};
|
|
|
|
self.stcs.insert(sym, (pending_value, span));
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
fn make_attrpath_value_entry<'ir>(path: Vec<'ir, ast::Attr>, value: ast::Expr) -> ast::Entry {
|
|
use rnix::{NixLanguage, SyntaxKind};
|
|
use rowan::{GreenNodeBuilder, Language, NodeOrToken};
|
|
|
|
let mut builder = GreenNodeBuilder::new();
|
|
builder.start_node(NixLanguage::kind_to_raw(SyntaxKind::NODE_ATTRPATH_VALUE));
|
|
|
|
builder.start_node(NixLanguage::kind_to_raw(SyntaxKind::NODE_ATTRPATH));
|
|
for attr in path {
|
|
fn add_node(builder: &mut GreenNodeBuilder, node: &rowan::SyntaxNode<NixLanguage>) {
|
|
for child in node.children_with_tokens() {
|
|
match child {
|
|
NodeOrToken::Node(n) => {
|
|
builder.start_node(NixLanguage::kind_to_raw(n.kind()));
|
|
add_node(builder, &n);
|
|
builder.finish_node();
|
|
}
|
|
NodeOrToken::Token(t) => {
|
|
builder.token(NixLanguage::kind_to_raw(t.kind()), t.text());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
builder.start_node(NixLanguage::kind_to_raw(attr.syntax().kind()));
|
|
add_node(&mut builder, attr.syntax());
|
|
builder.finish_node();
|
|
}
|
|
builder.finish_node();
|
|
|
|
builder.token(NixLanguage::kind_to_raw(SyntaxKind::TOKEN_ASSIGN), "=");
|
|
|
|
builder.start_node(NixLanguage::kind_to_raw(value.syntax().kind()));
|
|
fn add_node_value(builder: &mut GreenNodeBuilder, node: &rowan::SyntaxNode<NixLanguage>) {
|
|
for child in node.children_with_tokens() {
|
|
match child {
|
|
NodeOrToken::Node(n) => {
|
|
builder.start_node(NixLanguage::kind_to_raw(n.kind()));
|
|
add_node_value(builder, &n);
|
|
builder.finish_node();
|
|
}
|
|
NodeOrToken::Token(t) => {
|
|
builder.token(NixLanguage::kind_to_raw(t.kind()), t.text());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
add_node_value(&mut builder, value.syntax());
|
|
builder.finish_node();
|
|
|
|
builder.token(NixLanguage::kind_to_raw(SyntaxKind::TOKEN_SEMICOLON), ";");
|
|
|
|
builder.finish_node();
|
|
|
|
let green = builder.finish();
|
|
let node = rowan::SyntaxNode::<NixLanguage>::new_root(green);
|
|
ast::Entry::cast(node).expect("constructed valid Entry node")
|
|
}
|
|
|
|
struct FinalizedAttrSet<'id, 'ir> {
|
|
stcs: HashMap<'ir, StringId, (GhostRoMaybeThunkRef<'id, 'ir>, TextRange)>,
|
|
dyns: Vec<
|
|
'ir,
|
|
(
|
|
GhostRoIrRef<'id, 'ir>,
|
|
GhostRoMaybeThunkRef<'id, 'ir>,
|
|
TextRange,
|
|
),
|
|
>,
|
|
}
|
|
|
|
fn downgrade_attrs<'id, 'ir>(
|
|
attrs: impl ast::HasEntry + AstNode,
|
|
ctx: &mut impl DowngradeContext<'id, 'ir>,
|
|
) -> Result<FinalizedAttrSet<'id, 'ir>> {
|
|
let mut pending = PendingAttrSet::new_in(ctx.bump());
|
|
pending.collect_entries(attrs.entries(), ctx)?;
|
|
finalize_pending_set::<_, true>(pending, &HashMap::new_in(ctx.bump()), ctx)
|
|
}
|
|
|
|
fn downgrade_attr<'id, 'ir>(
|
|
attr: ast::Attr,
|
|
ctx: &mut impl DowngradeContext<'id, 'ir>,
|
|
) -> Result<Attr<GhostRoIrRef<'id, 'ir>>> {
|
|
use ast::Attr::*;
|
|
use ast::InterpolPart::*;
|
|
match attr {
|
|
Ident(ident) => Ok(Attr::Str(
|
|
ctx.intern_string(ident.to_string()),
|
|
ident.syntax().text_range(),
|
|
)),
|
|
Str(string) => {
|
|
let parts = string.normalized_parts();
|
|
let span = string.syntax().text_range();
|
|
if parts.is_empty() {
|
|
Ok(Attr::Str(ctx.intern_string(""), span))
|
|
} else if parts.len() == 1 {
|
|
match parts.into_iter().next().expect("len checked") {
|
|
Literal(ident) => Ok(Attr::Str(ctx.intern_string(ident), span)),
|
|
Interpolation(interpol) => Ok(Attr::Dynamic(
|
|
interpol
|
|
.expr()
|
|
.require(ctx, interpol.syntax().text_range())?
|
|
.downgrade(ctx)?,
|
|
span,
|
|
)),
|
|
}
|
|
} else {
|
|
let bump = ctx.bump();
|
|
let parts = parts
|
|
.into_iter()
|
|
.map(|part| match part {
|
|
Literal(lit) => {
|
|
let id = ctx.intern_string(lit);
|
|
Ok(ctx.new_expr(Ir::Str(id)))
|
|
}
|
|
Interpolation(interpol) => interpol
|
|
.expr()
|
|
.require(ctx, interpol.syntax().text_range())?
|
|
.downgrade(ctx),
|
|
})
|
|
.collect_in::<Result<Vec<'ir, _>>>(bump)?;
|
|
Ok(Attr::Dynamic(
|
|
ctx.new_expr(Ir::ConcatStrings {
|
|
parts,
|
|
force_string: true,
|
|
}),
|
|
span,
|
|
))
|
|
}
|
|
}
|
|
Dynamic(dynamic) => {
|
|
let span = dynamic.syntax().text_range();
|
|
Ok(Attr::Dynamic(
|
|
dynamic.expr().require(ctx, span)?.downgrade(ctx)?,
|
|
span,
|
|
))
|
|
}
|
|
}
|
|
}
|
|
|
|
fn downgrade_attrpath<'id, 'ir>(
|
|
attrpath: ast::Attrpath,
|
|
ctx: &mut impl DowngradeContext<'id, 'ir>,
|
|
) -> Result<Vec<'ir, Attr<GhostRoIrRef<'id, 'ir>>>> {
|
|
let bump = ctx.bump();
|
|
attrpath
|
|
.attrs()
|
|
.map(|attr| downgrade_attr(attr, ctx))
|
|
.collect_in::<Result<Vec<'ir, _>>>(bump)
|
|
}
|
|
|
|
struct PatternBindings<'id, 'ir> {
|
|
body: GhostRoIrRef<'id, 'ir>,
|
|
required: Vec<'ir, (StringId, TextRange)>,
|
|
optional: Vec<'ir, (StringId, TextRange)>,
|
|
}
|
|
|
|
fn downgrade_pattern_bindings<'id, 'ir, Ctx>(
|
|
pat_entries: impl Iterator<Item = ast::PatEntry>,
|
|
alias: Option<StringId>,
|
|
ctx: &mut Ctx,
|
|
body_fn: impl FnOnce(&mut Ctx, &[StringId]) -> Result<GhostRoIrRef<'id, 'ir>>,
|
|
) -> Result<PatternBindings<'id, 'ir>>
|
|
where
|
|
Ctx: DowngradeContext<'id, 'ir>,
|
|
{
|
|
struct Param {
|
|
sym: StringId,
|
|
sym_span: TextRange,
|
|
default: Option<ast::Expr>,
|
|
span: TextRange,
|
|
}
|
|
let mut seen_params = HashSet::new();
|
|
let bump = ctx.bump();
|
|
let mut params = Vec::new_in(bump);
|
|
let mut keys = Vec::new_in(bump);
|
|
for entry in pat_entries {
|
|
let ident = entry.ident().require(ctx, entry.syntax().text_range())?;
|
|
let sym_span = ident.syntax().text_range();
|
|
let sym = ctx.intern_string(ident.syntax().text().to_string());
|
|
let default = entry.default();
|
|
let span = entry.syntax().text_range();
|
|
|
|
if !seen_params.insert(sym) {
|
|
return Err(Error::downgrade_error(
|
|
format!("duplicate parameter '{}'", ctx.resolve_sym(sym)),
|
|
ctx.get_current_source(),
|
|
span,
|
|
));
|
|
}
|
|
params.push(Param {
|
|
sym,
|
|
sym_span,
|
|
default,
|
|
span,
|
|
});
|
|
keys.push(sym);
|
|
}
|
|
|
|
if let Some(alias_sym) = alias {
|
|
keys.push(alias_sym);
|
|
}
|
|
|
|
let mut required = Vec::new_in(bump);
|
|
let mut optional = Vec::new_in(bump);
|
|
for &Param {
|
|
sym,
|
|
sym_span,
|
|
ref default,
|
|
..
|
|
} in params.iter()
|
|
{
|
|
if default.is_none() {
|
|
required.push((sym, sym_span));
|
|
} else {
|
|
optional.push((sym, sym_span));
|
|
}
|
|
}
|
|
|
|
let arg = ctx.new_expr(Ir::Arg { layer: 0 });
|
|
let arg_thunk = ctx.maybe_thunk(arg);
|
|
ctx.with_let_scope(&keys, |ctx| {
|
|
let vals = params
|
|
.into_iter()
|
|
.map(|param| {
|
|
let Param {
|
|
sym,
|
|
sym_span,
|
|
default,
|
|
span,
|
|
} = param;
|
|
let default = default.map(|default| default.downgrade(ctx)).transpose()?;
|
|
|
|
let expr = ctx.new_expr(Ir::Select {
|
|
expr: arg,
|
|
attrpath: Vec::from_iter_in([Attr::Str(sym, sym_span)], bump),
|
|
default,
|
|
span,
|
|
});
|
|
Ok(ctx.maybe_thunk(expr))
|
|
})
|
|
.chain(alias.into_iter().map(|_| Ok(arg_thunk)))
|
|
.collect_in::<Result<_>>(bump)?;
|
|
|
|
let body = body_fn(ctx, &keys)?;
|
|
|
|
Ok((
|
|
vals,
|
|
PatternBindings {
|
|
body,
|
|
required,
|
|
optional,
|
|
},
|
|
))
|
|
})
|
|
}
|
|
|
|
fn downgrade_let_bindings<'id, 'ir, Ctx, F>(
|
|
entries: Vec<'ir, ast::Entry>,
|
|
ctx: &mut Ctx,
|
|
body_fn: F,
|
|
) -> Result<GhostRoIrRef<'id, 'ir>>
|
|
where
|
|
Ctx: DowngradeContext<'id, 'ir>,
|
|
F: FnOnce(&mut Ctx, &[StringId]) -> Result<GhostRoIrRef<'id, 'ir>>,
|
|
{
|
|
downgrade_rec_attrs_impl::<_, _, false>(entries, ctx, |ctx, binding_keys, _dyns| {
|
|
body_fn(ctx, binding_keys)
|
|
})
|
|
}
|
|
|
|
fn downgrade_rec_bindings<'id, 'ir, Ctx>(
|
|
entries: Vec<'ir, ast::Entry>,
|
|
ctx: &mut Ctx,
|
|
) -> Result<GhostRoIrRef<'id, 'ir>>
|
|
where
|
|
Ctx: DowngradeContext<'id, 'ir>,
|
|
{
|
|
downgrade_rec_attrs_impl::<_, _, true>(entries, ctx, |ctx, binding_keys, dyns| {
|
|
let mut stcs = HashMap::new_in(ctx.bump());
|
|
let dyns = Vec::from_iter_in(dyns.iter().cloned(), ctx.bump());
|
|
|
|
for sym in binding_keys {
|
|
let expr = ctx.lookup(*sym, rnix::TextRange::default())?;
|
|
stcs.insert(*sym, (expr, rnix::TextRange::default()));
|
|
}
|
|
|
|
Ok(ctx.new_expr(Ir::AttrSet { stcs, dyns }))
|
|
})
|
|
}
|
|
|
|
fn downgrade_rec_attrs_impl<'id, 'ir, Ctx, F, const ALLOW_DYN: bool>(
|
|
entries: Vec<'ir, ast::Entry>,
|
|
ctx: &mut Ctx,
|
|
body_fn: F,
|
|
) -> Result<GhostRoIrRef<'id, 'ir>>
|
|
where
|
|
Ctx: DowngradeContext<'id, 'ir>,
|
|
F: FnOnce(
|
|
&mut Ctx,
|
|
&[StringId],
|
|
&[(
|
|
GhostRoIrRef<'id, 'ir>,
|
|
GhostRoMaybeThunkRef<'id, 'ir>,
|
|
TextRange,
|
|
)],
|
|
) -> Result<GhostRoIrRef<'id, 'ir>>,
|
|
{
|
|
let mut pending = PendingAttrSet::new_in(ctx.bump());
|
|
pending.collect_entries(entries.iter().cloned(), ctx)?;
|
|
|
|
let binding_syms = collect_binding_syms::<_, ALLOW_DYN>(&pending, ctx)?;
|
|
let keys: Vec<'ir, _> = binding_syms.into_iter().collect_in(ctx.bump());
|
|
|
|
let inherit_lookups = collect_inherit_lookups(&entries, ctx)?;
|
|
|
|
ctx.with_let_scope(&keys, |ctx| {
|
|
let finalized = finalize_pending_set::<_, ALLOW_DYN>(pending, &inherit_lookups, ctx)?;
|
|
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);
|
|
}
|
|
temp
|
|
};
|
|
body_fn(ctx, &keys, &finalized.dyns).map(|body| (vals, body))
|
|
})
|
|
}
|
|
|
|
fn collect_inherit_lookups<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>>(
|
|
entries: &[ast::Entry],
|
|
ctx: &mut Ctx,
|
|
) -> Result<HashMap<'ir, StringId, (GhostRoMaybeThunkRef<'id, 'ir>, TextRange)>> {
|
|
let mut inherit_lookups = HashMap::new_in(ctx.bump());
|
|
for entry in entries {
|
|
if let ast::Entry::Inherit(inherit) = entry
|
|
&& inherit.from().is_none()
|
|
{
|
|
for attr in inherit.attrs() {
|
|
if let ast::Attr::Ident(ident) = attr {
|
|
let attr_span = ident.syntax().text_range();
|
|
let sym = ctx.intern_string(ident.to_string());
|
|
let expr = ctx.lookup(sym, attr_span)?;
|
|
inherit_lookups.insert(sym, (expr, attr_span));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Ok(inherit_lookups)
|
|
}
|
|
|
|
fn collect_binding_syms<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>, const ALLOW_DYN: bool>(
|
|
pending: &PendingAttrSet,
|
|
ctx: &mut Ctx,
|
|
) -> Result<HashSet<StringId>> {
|
|
let mut binding_syms = HashSet::new();
|
|
|
|
for (sym, (_, span)) in &pending.stcs {
|
|
if !binding_syms.insert(*sym) {
|
|
return Err(Error::downgrade_error(
|
|
format!("attribute '{}' already defined", ctx.resolve_sym(*sym)),
|
|
ctx.get_current_source(),
|
|
*span,
|
|
));
|
|
}
|
|
}
|
|
|
|
Ok(binding_syms)
|
|
}
|
|
|
|
fn finalize_pending_set<'id, 'ir, Ctx: DowngradeContext<'id, 'ir>, const ALLOW_DYN: bool>(
|
|
pending: PendingAttrSet,
|
|
inherit_lookups: &HashMap<StringId, (GhostRoMaybeThunkRef<'id, 'ir>, TextRange)>,
|
|
ctx: &mut Ctx,
|
|
) -> Result<FinalizedAttrSet<'id, 'ir>> {
|
|
let mut stcs = HashMap::new_in(ctx.bump());
|
|
let mut dyns = Vec::new_in(ctx.bump());
|
|
|
|
for (sym, (value, value_span)) in pending.stcs {
|
|
let expr = finalize_pending_value::<_, ALLOW_DYN>(value, inherit_lookups, ctx)?;
|
|
stcs.insert(sym, (ctx.maybe_thunk(expr), value_span));
|
|
}
|
|
|
|
if ALLOW_DYN {
|
|
for (attr, value, value_span) in pending.dyns {
|
|
let key_id = downgrade_attr(attr, ctx)?;
|
|
let key_expr = match key_id {
|
|
Attr::Dynamic(id, _) => id,
|
|
Attr::Str(sym, _attr_span) => ctx.new_expr(Ir::Str(sym)),
|
|
};
|
|
let value = finalize_pending_value::<_, ALLOW_DYN>(value, inherit_lookups, ctx)?;
|
|
dyns.push((key_expr, ctx.maybe_thunk(value), value_span));
|
|
}
|
|
}
|
|
|
|
Ok(FinalizedAttrSet { stcs, dyns })
|
|
}
|
|
|
|
fn finalize_pending_value<'id, 'ir, Ctx: DowngradeContext<'id, 'ir>, const ALLOW_DYN: bool>(
|
|
value: PendingValue,
|
|
inherit_lookups: &HashMap<StringId, (GhostRoMaybeThunkRef<'id, 'ir>, TextRange)>,
|
|
ctx: &mut Ctx,
|
|
) -> Result<GhostRoIrRef<'id, 'ir>> {
|
|
match value {
|
|
PendingValue::Expr(expr) => expr.downgrade(ctx),
|
|
PendingValue::InheritFrom(from_expr, sym, span) => {
|
|
let from = Downgrade::downgrade(from_expr, ctx)?;
|
|
Ok(ctx.new_expr(Ir::Select {
|
|
expr: from,
|
|
attrpath: Vec::from_iter_in([Attr::Str(sym, span)], ctx.bump()),
|
|
default: None,
|
|
span,
|
|
}))
|
|
}
|
|
PendingValue::InheritScope(sym, span) => {
|
|
if let Some(&(expr, _)) = inherit_lookups.get(&sym) {
|
|
Ok(ctx.new_expr(Ir::MaybeThunk(expr)))
|
|
} else {
|
|
ctx.lookup(sym, span)
|
|
.map(|val| ctx.new_expr(Ir::MaybeThunk(val)))
|
|
}
|
|
}
|
|
PendingValue::Set(set) => {
|
|
let attrset = finalize_pending_set::<_, ALLOW_DYN>(set, inherit_lookups, ctx)?;
|
|
Ok(ctx.new_expr(Ir::AttrSet {
|
|
stcs: attrset.stcs,
|
|
dyns: attrset.dyns,
|
|
}))
|
|
}
|
|
PendingValue::ExtendedRecAttrSet {
|
|
base, extensions, ..
|
|
} => {
|
|
let mut all_entries: Vec<'ir, _> = base.entries().collect_in(ctx.bump());
|
|
all_entries.extend(extensions);
|
|
downgrade_rec_bindings(all_entries, ctx)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn downgrade_path<'id, 'ir>(
|
|
parts: impl IntoIterator<Item = ast::InterpolPart<ast::PathContent>>,
|
|
ctx: &mut impl DowngradeContext<'id, 'ir>,
|
|
) -> Result<GhostRoIrRef<'id, 'ir>> {
|
|
let bump = ctx.bump();
|
|
let parts = parts
|
|
.into_iter()
|
|
.map(|part| match part {
|
|
ast::InterpolPart::Literal(lit) => {
|
|
let id = ctx.intern_string(lit.text());
|
|
Ok(ctx.new_expr(Ir::Str(id)))
|
|
}
|
|
ast::InterpolPart::Interpolation(interpol) => interpol
|
|
.expr()
|
|
.require(ctx, interpol.syntax().text_range())?
|
|
.downgrade(ctx),
|
|
})
|
|
.collect_in::<Result<Vec<'ir, _>>>(bump)?;
|
|
let expr = if parts.len() == 1 {
|
|
parts.into_iter().next().expect("length checked")
|
|
} else {
|
|
ctx.new_expr(Ir::ConcatStrings {
|
|
parts,
|
|
force_string: false,
|
|
})
|
|
};
|
|
Ok(ctx.new_expr(Ir::Path(expr)))
|
|
}
|