refactor: recursive attrset; fix attrset merging
This commit is contained in:
@@ -4,3 +4,7 @@ members = [
|
||||
"nix-js",
|
||||
"nix-js-macros"
|
||||
]
|
||||
|
||||
[profile.profiling]
|
||||
inherits = "release"
|
||||
debug = true
|
||||
|
||||
@@ -92,6 +92,7 @@ pub fn ir_impl(input: TokenStream) -> TokenStream {
|
||||
let mut mut_variants = Vec::new();
|
||||
let mut as_ref_arms = Vec::new();
|
||||
let mut as_mut_arms = Vec::new();
|
||||
let mut span_arms = Vec::new();
|
||||
let mut from_impls = Vec::new();
|
||||
let mut to_trait_impls = Vec::new();
|
||||
|
||||
@@ -112,6 +113,7 @@ pub fn ir_impl(input: TokenStream) -> TokenStream {
|
||||
mut_variants.push(quote! { #name(&'a mut #inner_type) });
|
||||
as_ref_arms.push(quote! { Self::#name(inner) => #ref_name::#name(inner) });
|
||||
as_mut_arms.push(quote! { Self::#name(inner) => #mut_name::#name(inner) });
|
||||
span_arms.push(quote! { Self::#name(inner) => inner.span });
|
||||
from_impls.push(quote! {
|
||||
impl From<#inner_type> for #base_name {
|
||||
fn from(val: #inner_type) -> Self { #base_name::#name(val) }
|
||||
@@ -140,6 +142,7 @@ pub fn ir_impl(input: TokenStream) -> TokenStream {
|
||||
mut_variants.push(quote! { #name(&'a mut #inner_type) });
|
||||
as_ref_arms.push(quote! { Self::#name(inner) => #ref_name::#name(inner) });
|
||||
as_mut_arms.push(quote! { Self::#name(inner) => #mut_name::#name(inner) });
|
||||
span_arms.push(quote! { Self::#name(inner) => inner.span });
|
||||
from_impls.push(quote! {
|
||||
impl From<#inner_type> for #base_name {
|
||||
fn from(val: #inner_type) -> Self { #base_name::#name(val) }
|
||||
@@ -172,6 +175,7 @@ pub fn ir_impl(input: TokenStream) -> TokenStream {
|
||||
mut_variants.push(quote! { #name(&'a mut #inner_type) });
|
||||
as_ref_arms.push(quote! { Self::#name(inner) => #ref_name::#name(inner) });
|
||||
as_mut_arms.push(quote! { Self::#name(inner) => #mut_name::#name(inner) });
|
||||
span_arms.push(quote! { Self::#name(inner) => inner.span });
|
||||
from_impls.push(quote! {
|
||||
impl From<#inner_type> for #base_name {
|
||||
fn from(val: #inner_type) -> Self { #base_name::#name(val) }
|
||||
@@ -223,6 +227,12 @@ pub fn ir_impl(input: TokenStream) -> TokenStream {
|
||||
#( #as_mut_arms ),*
|
||||
}
|
||||
}
|
||||
|
||||
pub fn span(&self) -> rnix::TextRange {
|
||||
match self {
|
||||
#( #span_arms ),*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// `From` implementations for converting variant structs into the main enum.
|
||||
|
||||
@@ -274,7 +274,7 @@ impl<'a, 'ctx> ScopeGuard<'a, 'ctx> {
|
||||
|
||||
pub struct DowngradeCtx<'ctx> {
|
||||
ctx: &'ctx mut Ctx,
|
||||
irs: Vec<Option<Ir>>,
|
||||
irs: Vec<Ir>,
|
||||
scopes: Vec<Scope<'ctx>>,
|
||||
arg_id: usize,
|
||||
thunk_scopes: Vec<Vec<(ExprId, ExprId)>>,
|
||||
@@ -294,18 +294,18 @@ impl<'ctx> DowngradeCtx<'ctx> {
|
||||
|
||||
impl DowngradeContext for DowngradeCtx<'_> {
|
||||
fn new_expr(&mut self, expr: Ir) -> ExprId {
|
||||
self.irs.push(Some(expr));
|
||||
self.irs.push(expr);
|
||||
ExprId(self.ctx.irs.len() + self.irs.len() - 1)
|
||||
}
|
||||
|
||||
fn new_arg(&mut self, span: TextRange) -> ExprId {
|
||||
self.irs.push(Some(
|
||||
self.irs.push(
|
||||
Arg {
|
||||
inner: ArgId(self.arg_id),
|
||||
span,
|
||||
}
|
||||
.to_ir(),
|
||||
));
|
||||
);
|
||||
self.arg_id += 1;
|
||||
ExprId(self.ctx.irs.len() + self.irs.len() - 1)
|
||||
}
|
||||
@@ -317,8 +317,6 @@ impl DowngradeContext for DowngradeCtx<'_> {
|
||||
self.irs
|
||||
.get(id.0 - self.ctx.irs.len())
|
||||
.expect("ExprId out of bounds")
|
||||
.as_ref()
|
||||
.expect("maybe_thunk called on an extracted expr")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -404,22 +402,9 @@ impl DowngradeContext for DowngradeCtx<'_> {
|
||||
})
|
||||
}
|
||||
|
||||
fn extract_ir(&mut self, id: ExprId) -> Ir {
|
||||
let local_id = id.0 - self.ctx.irs.len();
|
||||
self.irs
|
||||
.get_mut(local_id)
|
||||
.expect("ExprId out of bounds")
|
||||
.take()
|
||||
.expect("extract_expr called on an already extracted expr")
|
||||
}
|
||||
|
||||
fn replace_ir(&mut self, id: ExprId, expr: Ir) {
|
||||
let local_id = id.0 - self.ctx.irs.len();
|
||||
let _ = self
|
||||
.irs
|
||||
.get_mut(local_id)
|
||||
.expect("ExprId out of bounds")
|
||||
.insert(expr);
|
||||
*self.irs.get_mut(local_id).expect("ExprId out of bounds") = expr;
|
||||
}
|
||||
|
||||
fn get_current_source(&self) -> Source {
|
||||
@@ -429,8 +414,11 @@ impl DowngradeContext for DowngradeCtx<'_> {
|
||||
#[allow(refining_impl_trait)]
|
||||
fn reserve_slots(&mut self, slots: usize) -> impl Iterator<Item = ExprId> + Clone + use<> {
|
||||
let start = self.ctx.irs.len() + self.irs.len();
|
||||
self.irs.extend(std::iter::repeat_with(|| None).take(slots));
|
||||
(start..start + slots).map(ExprId)
|
||||
let range = (start..start + slots).map(ExprId);
|
||||
let span = synthetic_span();
|
||||
// Fill reserved slots with placeholder value
|
||||
self.irs.extend(range.clone().map(|slot| Thunk { inner: slot, span }.to_ir()));
|
||||
range
|
||||
}
|
||||
|
||||
fn downgrade(mut self, root: rnix::ast::Expr) -> Result<ExprId> {
|
||||
@@ -441,7 +429,7 @@ impl DowngradeContext for DowngradeCtx<'_> {
|
||||
let top_level = self.new_expr(TopLevel { body, thunks, span }.to_ir());
|
||||
self.ctx
|
||||
.irs
|
||||
.extend(self.irs.into_iter().map(Option::unwrap));
|
||||
.extend(self.irs);
|
||||
Ok(top_level)
|
||||
}
|
||||
|
||||
|
||||
188
nix-js/src/ir.rs
188
nix-js/src/ir.rs
@@ -3,8 +3,7 @@ use hashbrown::HashMap;
|
||||
use rnix::{TextRange, ast};
|
||||
use string_interner::symbol::SymbolU32;
|
||||
|
||||
use crate::error::{Error, Result, Source};
|
||||
use crate::value::format_symbol;
|
||||
use crate::error::{Result, Source};
|
||||
use nix_js_macros::ir;
|
||||
|
||||
mod downgrade;
|
||||
@@ -28,7 +27,6 @@ pub trait DowngradeContext {
|
||||
fn lookup(&mut self, sym: SymId, span: TextRange) -> Result<ExprId>;
|
||||
|
||||
fn get_ir(&self, id: ExprId) -> &Ir;
|
||||
fn extract_ir(&mut self, id: ExprId) -> Ir;
|
||||
fn replace_ir(&mut self, id: ExprId, expr: Ir);
|
||||
fn reserve_slots(&mut self, slots: usize) -> impl Iterator<Item = ExprId> + Clone + use<Self>;
|
||||
fn get_current_source(&self) -> Source;
|
||||
@@ -77,189 +75,6 @@ ir! {
|
||||
CurPos,
|
||||
}
|
||||
|
||||
impl Ir {
|
||||
pub fn span(&self) -> TextRange {
|
||||
match self {
|
||||
Ir::Int(i) => i.span,
|
||||
Ir::Float(f) => f.span,
|
||||
Ir::Bool(b) => b.span,
|
||||
Ir::Null(n) => n.span,
|
||||
Ir::Str(s) => s.span,
|
||||
Ir::AttrSet(a) => a.span,
|
||||
Ir::List(l) => l.span,
|
||||
Ir::HasAttr(h) => h.span,
|
||||
Ir::BinOp(b) => b.span,
|
||||
Ir::UnOp(u) => u.span,
|
||||
Ir::Select(s) => s.span,
|
||||
Ir::If(i) => i.span,
|
||||
Ir::Call(c) => c.span,
|
||||
Ir::Assert(a) => a.span,
|
||||
Ir::ConcatStrings(c) => c.span,
|
||||
Ir::Path(p) => p.span,
|
||||
Ir::Func(f) => f.span,
|
||||
Ir::TopLevel(t) => t.span,
|
||||
Ir::Arg(a) => a.span,
|
||||
Ir::Thunk(e) => e.span,
|
||||
Ir::Builtins(b) => b.span,
|
||||
Ir::Builtin(b) => b.span,
|
||||
Ir::CurPos(c) => c.span,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AttrSet {
|
||||
fn merge(&mut self, other: &Self, ctx: &mut impl DowngradeContext) -> Result<()> {
|
||||
for (&sym, &(val, sp)) in &other.stcs {
|
||||
if let Some(&(existing_id, _)) = self.stcs.get(&sym) {
|
||||
let mut existing_ir = ctx.extract_ir(existing_id);
|
||||
let other_ir = ctx.extract_ir(val);
|
||||
|
||||
match (
|
||||
existing_ir.as_mut().try_unwrap_attr_set(),
|
||||
other_ir.as_ref().try_unwrap_attr_set(),
|
||||
) {
|
||||
(Ok(existing_attrs), Ok(other_attrs)) => {
|
||||
existing_attrs.merge(other_attrs, ctx)?;
|
||||
ctx.replace_ir(existing_id, existing_ir);
|
||||
ctx.replace_ir(val, other_ir);
|
||||
}
|
||||
_ => {
|
||||
ctx.replace_ir(existing_id, existing_ir);
|
||||
ctx.replace_ir(val, other_ir);
|
||||
return Err(Error::downgrade_error(
|
||||
format!(
|
||||
"attribute '{}' already defined",
|
||||
format_symbol(ctx.get_sym(sym)),
|
||||
),
|
||||
ctx.get_current_source(),
|
||||
sp,
|
||||
));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.stcs.insert(sym, (val, sp));
|
||||
}
|
||||
}
|
||||
self.dyns.extend(other.dyns.iter().cloned());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn _insert(
|
||||
&mut self,
|
||||
mut path: impl Iterator<Item = Attr>,
|
||||
name: Attr,
|
||||
value: ExprId,
|
||||
ctx: &mut impl DowngradeContext,
|
||||
) -> Result<()> {
|
||||
if let Some(attr) = path.next() {
|
||||
// If the path is not yet exhausted, we need to recurse deeper.
|
||||
match attr {
|
||||
Attr::Str(ident, span) => {
|
||||
// If the next attribute is a static string.
|
||||
if let Some(&(id, _)) = self.stcs.get(&ident) {
|
||||
// If a sub-attrset already exists, recurse into it.
|
||||
let mut ir = ctx.extract_ir(id);
|
||||
let result = ir
|
||||
.as_mut()
|
||||
.try_unwrap_attr_set()
|
||||
.map_err(|_| {
|
||||
// This path segment exists but is not an attrset, which is an error.
|
||||
Error::downgrade_error(format!(
|
||||
"attribute '{}' already defined but is not an attribute set",
|
||||
format_symbol(ctx.get_sym(ident)),
|
||||
),
|
||||
ctx.get_current_source(),
|
||||
span
|
||||
)
|
||||
})
|
||||
.and_then(|attrs| attrs._insert(path, name, value, ctx));
|
||||
ctx.replace_ir(id, ir);
|
||||
result?;
|
||||
} else {
|
||||
// Create a new sub-attrset because this path doesn't exist yet.
|
||||
// FIXME: span
|
||||
let mut attrs = AttrSet {
|
||||
stcs: Default::default(),
|
||||
dyns: Default::default(),
|
||||
span,
|
||||
};
|
||||
attrs._insert(path, name, value, ctx)?;
|
||||
let attrs_expr = ctx.new_expr(attrs.to_ir());
|
||||
self.stcs.insert(ident, (attrs_expr, span));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Attr::Dynamic(dynamic, span) => {
|
||||
// If the next attribute is a dynamic expression, we must create a new sub-attrset.
|
||||
// We cannot merge with existing dynamic attributes at this stage.
|
||||
// FIXME: span
|
||||
let mut attrs = AttrSet {
|
||||
stcs: Default::default(),
|
||||
dyns: Default::default(),
|
||||
span,
|
||||
};
|
||||
attrs._insert(path, name, value, ctx)?;
|
||||
self.dyns.push((dynamic, ctx.new_expr(attrs.to_ir()), span));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// This is the final attribute in the path, so insert the value here.
|
||||
match name {
|
||||
Attr::Str(ident, span) => {
|
||||
if let Some(&(existing_id, _)) = self.stcs.get(&ident) {
|
||||
let mut existing_ir = ctx.extract_ir(existing_id);
|
||||
let new_ir = ctx.extract_ir(value);
|
||||
|
||||
match (
|
||||
existing_ir.as_mut().try_unwrap_attr_set(),
|
||||
new_ir.as_ref().try_unwrap_attr_set(),
|
||||
) {
|
||||
(Ok(existing_attrs), Ok(new_attrs)) => {
|
||||
existing_attrs.merge(new_attrs, ctx)?;
|
||||
ctx.replace_ir(existing_id, existing_ir);
|
||||
ctx.replace_ir(value, new_ir);
|
||||
}
|
||||
_ => {
|
||||
ctx.replace_ir(existing_id, existing_ir);
|
||||
ctx.replace_ir(value, new_ir);
|
||||
return Err(Error::downgrade_error(
|
||||
format!(
|
||||
"attribute '{}' already defined",
|
||||
format_symbol(ctx.get_sym(ident)),
|
||||
),
|
||||
ctx.get_current_source(),
|
||||
span,
|
||||
));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.stcs.insert(ident, (value, span));
|
||||
}
|
||||
}
|
||||
Attr::Dynamic(dynamic, span) => {
|
||||
self.dyns.push((dynamic, value, span));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn insert(
|
||||
&mut self,
|
||||
path: Vec<Attr>,
|
||||
value: ExprId,
|
||||
ctx: &mut impl DowngradeContext,
|
||||
) -> Result<()> {
|
||||
let mut path = path.into_iter();
|
||||
// The last part of the path is the name of the attribute to be inserted.
|
||||
let name = path
|
||||
.next_back()
|
||||
.expect("empty attrpath passed. this is a bug");
|
||||
self._insert(path, name, value, ctx)
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct ExprId(pub usize);
|
||||
@@ -271,6 +86,7 @@ pub type SymId = SymbolU32;
|
||||
pub struct ArgId(pub usize);
|
||||
|
||||
/// Represents a key in an attribute path.
|
||||
#[allow(unused)]
|
||||
#[derive(Debug, TryUnwrap)]
|
||||
pub enum Attr {
|
||||
/// A dynamic attribute key, which is an expression that must evaluate to a string.
|
||||
|
||||
@@ -5,7 +5,7 @@ use hashbrown::hash_map::Entry;
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use itertools::Itertools as _;
|
||||
use rnix::TextRange;
|
||||
use rnix::ast;
|
||||
use rnix::ast::{self, HasEntry};
|
||||
use rowan::ast::AstNode;
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
@@ -14,49 +14,303 @@ use crate::value::format_symbol;
|
||||
|
||||
use super::*;
|
||||
|
||||
/// Downgrades the entries of an attribute set.
|
||||
/// This handles `inherit` and `attrpath = value;` entries.
|
||||
pub fn downgrade_attrs(
|
||||
attrs: impl ast::HasEntry + AstNode,
|
||||
ctx: &mut impl DowngradeContext,
|
||||
) -> Result<AttrSet> {
|
||||
let entries = attrs.entries();
|
||||
let mut attrs = AttrSet {
|
||||
stcs: HashMap::new(),
|
||||
dyns: Vec::new(),
|
||||
span: attrs.syntax().text_range(),
|
||||
};
|
||||
|
||||
for entry in entries {
|
||||
match entry {
|
||||
ast::Entry::Inherit(inherit) => downgrade_inherit(inherit, &mut attrs.stcs, ctx)?,
|
||||
ast::Entry::AttrpathValue(value) => downgrade_attrpathvalue(value, &mut attrs, ctx)?,
|
||||
}
|
||||
}
|
||||
|
||||
Ok(attrs)
|
||||
enum PendingValue {
|
||||
Expr(ast::Expr),
|
||||
InheritFrom(ast::Expr, SymId, TextRange),
|
||||
InheritScope(SymId, TextRange),
|
||||
Set(PendingAttrSet),
|
||||
ExtendedRecAttrSet {
|
||||
base: ast::AttrSet,
|
||||
extensions: Vec<ast::Entry>,
|
||||
span: TextRange,
|
||||
},
|
||||
}
|
||||
|
||||
/// Downgrades an `inherit` statement.
|
||||
/// `inherit (from) a b;` is translated into `a = from.a; b = from.b;`.
|
||||
/// `inherit a b;` is translated into `a = a; b = b;` (i.e., bringing variables into scope).
|
||||
pub fn downgrade_inherit(
|
||||
inherit: ast::Inherit,
|
||||
stcs: &mut HashMap<SymId, (ExprId, rnix::TextRange)>,
|
||||
struct PendingAttrSet {
|
||||
stcs: HashMap<SymId, (PendingValue, TextRange)>,
|
||||
dyns: Vec<(ast::Attr, PendingValue, TextRange)>,
|
||||
span: TextRange,
|
||||
}
|
||||
|
||||
impl PendingAttrSet {
|
||||
fn new(span: TextRange) -> Self {
|
||||
Self {
|
||||
stcs: HashMap::new(),
|
||||
dyns: Vec::new(),
|
||||
span,
|
||||
}
|
||||
}
|
||||
|
||||
fn insert(
|
||||
&mut self,
|
||||
path: &[ast::Attr],
|
||||
value: ast::Expr,
|
||||
ctx: &mut impl DowngradeContext,
|
||||
) -> Result<()> {
|
||||
// Downgrade the `from` expression if it exists.
|
||||
let from = if let Some(from) = inherit.from() {
|
||||
Some(from.expr().unwrap().downgrade(ctx)?)
|
||||
} else {
|
||||
None
|
||||
) -> 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.new_sym(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().unwrap()
|
||||
{
|
||||
let sym = ctx.new_sym(lit);
|
||||
return self.insert_static(sym, span, rest, value, ctx);
|
||||
}
|
||||
self.insert_dynamic(first.clone(), span, rest, value)
|
||||
}
|
||||
ast::Attr::Dynamic(_) => self.insert_dynamic(first.clone(), span, rest, value),
|
||||
}
|
||||
}
|
||||
|
||||
fn insert_static(
|
||||
&mut self,
|
||||
sym: SymId,
|
||||
span: TextRange,
|
||||
path: &[ast::Attr],
|
||||
value: ast::Expr,
|
||||
ctx: &mut impl DowngradeContext,
|
||||
) -> 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 base_span = attrset.syntax().text_range();
|
||||
let ext_entry = make_attrpath_value_entry(path.to_vec(), value);
|
||||
*existing = PendingValue::ExtendedRecAttrSet {
|
||||
base,
|
||||
extensions: vec![ext_entry],
|
||||
span: base_span,
|
||||
};
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let PendingValue::ExtendedRecAttrSet { extensions, .. } = existing {
|
||||
let ext_entry = make_attrpath_value_entry(path.to_vec(), 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(span);
|
||||
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,
|
||||
) -> Result<()> {
|
||||
if !path.is_empty() {
|
||||
let mut nested = PendingAttrSet::new(span);
|
||||
nested.insert_dynamic(
|
||||
path[0].clone(),
|
||||
path[0].syntax().text_range(),
|
||||
&path[1..],
|
||||
value,
|
||||
)?;
|
||||
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,
|
||||
ctx: &mut impl DowngradeContext,
|
||||
span: TextRange,
|
||||
) -> Result<&'a mut PendingAttrSet> {
|
||||
match value {
|
||||
PendingValue::Set(set) => Ok(set),
|
||||
PendingValue::Expr(expr) => {
|
||||
if let ast::Expr::AttrSet(attrset) = expr {
|
||||
let mut nested = PendingAttrSet::new(attrset.syntax().text_range());
|
||||
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,
|
||||
existing_span: TextRange,
|
||||
new_value: ast::Expr,
|
||||
new_span: TextRange,
|
||||
ctx: &mut impl DowngradeContext,
|
||||
) -> 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<_> = new_attrset.entries().collect();
|
||||
*existing = PendingValue::ExtendedRecAttrSet {
|
||||
base,
|
||||
extensions,
|
||||
span: existing_attrset.syntax().text_range(),
|
||||
};
|
||||
Ok(())
|
||||
} else {
|
||||
let mut merged =
|
||||
PendingAttrSet::new(existing_attrset.syntax().text_range());
|
||||
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,
|
||||
) -> Result<()> {
|
||||
for entry in entries {
|
||||
match entry {
|
||||
ast::Entry::Inherit(inherit) => {
|
||||
self.collect_inherit(inherit, ctx)?;
|
||||
}
|
||||
ast::Entry::AttrpathValue(value) => {
|
||||
let attrpath = value.attrpath().unwrap();
|
||||
let path: Vec<_> = attrpath.attrs().collect();
|
||||
let expr = value.value().unwrap();
|
||||
self.insert(&path, expr, ctx)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn collect_inherit(
|
||||
&mut self,
|
||||
inherit: ast::Inherit,
|
||||
ctx: &mut impl DowngradeContext,
|
||||
) -> Result<()> {
|
||||
let from = inherit.from().map(|f| f.expr().unwrap());
|
||||
for attr in inherit.attrs() {
|
||||
let span = attr.syntax().text_range();
|
||||
let ident = match downgrade_attr(attr, ctx)? {
|
||||
Attr::Str(ident, _) => ident,
|
||||
_ => {
|
||||
// `inherit` does not allow dynamic attributes.
|
||||
let sym = match &attr {
|
||||
ast::Attr::Ident(ident) => ctx.new_sym(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().unwrap()
|
||||
{
|
||||
ctx.new_sym(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(),
|
||||
@@ -64,42 +318,100 @@ pub fn downgrade_inherit(
|
||||
));
|
||||
}
|
||||
};
|
||||
let expr = if let Some(expr) = from {
|
||||
let select_expr = ctx.new_expr(
|
||||
Select {
|
||||
expr,
|
||||
attrpath: vec![Attr::Str(ident, span)],
|
||||
default: None,
|
||||
span,
|
||||
}
|
||||
.to_ir(),
|
||||
);
|
||||
ctx.maybe_thunk(select_expr)
|
||||
} else {
|
||||
let lookup_expr = ctx.lookup(ident, span)?;
|
||||
ctx.maybe_thunk(lookup_expr)
|
||||
};
|
||||
match stcs.entry(ident) {
|
||||
Entry::Occupied(occupied) => {
|
||||
|
||||
if self.stcs.contains_key(&sym) {
|
||||
return Err(Error::downgrade_error(
|
||||
format!(
|
||||
"attribute '{}' already defined",
|
||||
format_symbol(ctx.get_sym(*occupied.key()))
|
||||
format_symbol(ctx.get_sym(sym))
|
||||
),
|
||||
ctx.get_current_source(),
|
||||
span,
|
||||
)
|
||||
.with_span(span)
|
||||
.with_source(ctx.get_current_source()));
|
||||
));
|
||||
}
|
||||
Entry::Vacant(vacant) => vacant.insert((expr, 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(path: Vec<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).unwrap()
|
||||
}
|
||||
|
||||
/// Downgrades the entries of a non-recursive attribute set.
|
||||
pub fn downgrade_attrs(
|
||||
attrs: impl ast::HasEntry + AstNode,
|
||||
ctx: &mut impl DowngradeContext,
|
||||
) -> Result<AttrSet> {
|
||||
let span = attrs.syntax().text_range();
|
||||
let mut pending = PendingAttrSet::new(span);
|
||||
pending.collect_entries(attrs.entries(), ctx)?;
|
||||
finalize_pending_set::<_, true>(pending, &HashMap::new(), ctx)
|
||||
}
|
||||
|
||||
/// Downgrades a single attribute key (part of an attribute path).
|
||||
/// An attribute can be a static identifier, an interpolated string, or a dynamic expression.
|
||||
pub fn downgrade_attr(attr: ast::Attr, ctx: &mut impl DowngradeContext) -> Result<Attr> {
|
||||
use ast::Attr::*;
|
||||
use ast::InterpolPart::*;
|
||||
@@ -114,7 +426,6 @@ pub fn downgrade_attr(attr: ast::Attr, ctx: &mut impl DowngradeContext) -> Resul
|
||||
if parts.is_empty() {
|
||||
Ok(Attr::Str(ctx.new_sym("".to_string()), span))
|
||||
} else if parts.len() == 1 {
|
||||
// If the string has only one part, it's either a literal or a single interpolation.
|
||||
match parts.into_iter().next().unwrap() {
|
||||
Literal(ident) => Ok(Attr::Str(ctx.new_sym(ident), span)),
|
||||
Interpolation(interpol) => Ok(Attr::Dynamic(
|
||||
@@ -123,7 +434,6 @@ pub fn downgrade_attr(attr: ast::Attr, ctx: &mut impl DowngradeContext) -> Resul
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
// If the string has multiple parts, it's an interpolated string that must be concatenated.
|
||||
let parts = parts
|
||||
.into_iter()
|
||||
.map(|part| match part {
|
||||
@@ -155,40 +465,6 @@ pub fn downgrade_attrpath(
|
||||
.collect::<Result<Vec<_>>>()
|
||||
}
|
||||
|
||||
/// Downgrades an `attrpath = value;` expression and inserts it into an `AttrSet`.
|
||||
pub fn downgrade_attrpathvalue(
|
||||
value: ast::AttrpathValue,
|
||||
attrs: &mut AttrSet,
|
||||
ctx: &mut impl DowngradeContext,
|
||||
) -> Result<()> {
|
||||
let path = downgrade_attrpath(value.attrpath().unwrap(), ctx)?;
|
||||
let value = value.value().unwrap().downgrade(ctx)?;
|
||||
let value = ctx.maybe_thunk(value);
|
||||
attrs.insert(path, value, ctx)
|
||||
}
|
||||
|
||||
/// A stricter version of `downgrade_attrpathvalue` for `let...in` bindings.
|
||||
/// It ensures that the attribute path contains no dynamic parts.
|
||||
pub fn downgrade_static_attrpathvalue(
|
||||
value: ast::AttrpathValue,
|
||||
attrs: &mut AttrSet,
|
||||
ctx: &mut impl DowngradeContext,
|
||||
) -> Result<()> {
|
||||
let attrpath_node = value.attrpath().unwrap();
|
||||
let path = downgrade_attrpath(attrpath_node.clone(), ctx)?;
|
||||
if let Some(&Attr::Dynamic(_, span)) =
|
||||
path.iter().find(|attr| matches!(attr, Attr::Dynamic(..)))
|
||||
{
|
||||
return Err(Error::downgrade_error(
|
||||
"dynamic attributes not allowed in let bindings".to_string(),
|
||||
ctx.get_current_source(),
|
||||
span,
|
||||
));
|
||||
}
|
||||
let value = value.value().unwrap().downgrade(ctx)?;
|
||||
attrs.insert(path, value, ctx)
|
||||
}
|
||||
|
||||
pub struct PatternBindings {
|
||||
pub body: ExprId,
|
||||
pub required: Vec<SymId>,
|
||||
@@ -196,7 +472,6 @@ pub struct PatternBindings {
|
||||
}
|
||||
|
||||
/// Helper function for Lambda pattern parameters.
|
||||
/// Processes pattern entries like `{ a, b ? 2, ... }@alias` and creates bindings.
|
||||
pub fn downgrade_pattern_bindings<Ctx>(
|
||||
pat_entries: impl Iterator<Item = ast::PatEntry>,
|
||||
alias: Option<SymId>,
|
||||
@@ -312,9 +587,8 @@ where
|
||||
})
|
||||
}
|
||||
|
||||
/// Helper function to downgrade entries with let bindings semantics.
|
||||
/// This extracts common logic for `let...in` expressions.
|
||||
/// For `rec` attribute sets, use `downgrade_rec_bindings` instead.
|
||||
/// Downgrades a `let...in` expression. This is a special case of rec attrs
|
||||
/// that disallows dynamic attributes and has a body expression.
|
||||
pub fn downgrade_let_bindings<Ctx, F>(
|
||||
entries: Vec<ast::Entry>,
|
||||
ctx: &mut Ctx,
|
||||
@@ -325,17 +599,12 @@ where
|
||||
Ctx: DowngradeContext,
|
||||
F: FnOnce(&mut Ctx, &[SymId]) -> Result<ExprId>,
|
||||
{
|
||||
downgrade_let_bindings_impl(
|
||||
entries,
|
||||
ctx,
|
||||
span,
|
||||
|ctx, binding_keys, _dyns| body_fn(ctx, binding_keys),
|
||||
false,
|
||||
)
|
||||
downgrade_rec_attrs_impl::<_, _, false>(entries, ctx, span, |ctx, binding_keys, _dyns| {
|
||||
body_fn(ctx, binding_keys)
|
||||
})
|
||||
}
|
||||
|
||||
/// Helper function to downgrade `rec` attribute sets that may contain dynamic attributes.
|
||||
/// Similar to `downgrade_let_bindings`, but allows dynamic attributes.
|
||||
/// Downgrades a `rec` attribute set.
|
||||
pub fn downgrade_rec_bindings<Ctx>(
|
||||
entries: Vec<ast::Entry>,
|
||||
ctx: &mut Ctx,
|
||||
@@ -344,11 +613,7 @@ pub fn downgrade_rec_bindings<Ctx>(
|
||||
where
|
||||
Ctx: DowngradeContext,
|
||||
{
|
||||
downgrade_let_bindings_impl(
|
||||
entries,
|
||||
ctx,
|
||||
span,
|
||||
|ctx, binding_keys, dyns| {
|
||||
downgrade_rec_attrs_impl::<_, _, true>(entries, ctx, span, |ctx, binding_keys, dyns| {
|
||||
let mut attrs = AttrSet {
|
||||
stcs: HashMap::new(),
|
||||
dyns: dyns.to_vec(),
|
||||
@@ -361,104 +626,27 @@ where
|
||||
}
|
||||
|
||||
Ok(ctx.new_expr(attrs.to_ir()))
|
||||
},
|
||||
true,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn downgrade_let_bindings_impl<Ctx, F>(
|
||||
/// Core implementation for recursive bindings (rec attrs and let-in).
|
||||
/// ALLOW_DYN controls whether dynamic attributes are allowed.
|
||||
fn downgrade_rec_attrs_impl<Ctx, F, const ALLOW_DYN: bool>(
|
||||
entries: Vec<ast::Entry>,
|
||||
ctx: &mut Ctx,
|
||||
_span: TextRange,
|
||||
span: TextRange,
|
||||
body_fn: F,
|
||||
allow_dynamic: bool,
|
||||
) -> Result<ExprId>
|
||||
where
|
||||
Ctx: DowngradeContext,
|
||||
F: FnOnce(&mut Ctx, &[SymId], &[(ExprId, ExprId, TextRange)]) -> Result<ExprId>,
|
||||
{
|
||||
fn is_static_entry(entry: &ast::Entry) -> bool {
|
||||
match entry {
|
||||
ast::Entry::Inherit(_) => true,
|
||||
ast::Entry::AttrpathValue(value) => {
|
||||
let attrpath = value.attrpath().unwrap();
|
||||
let first_attr = attrpath.attrs().next();
|
||||
matches!(first_attr, Some(ast::Attr::Ident(_)))
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut pending = PendingAttrSet::new(span);
|
||||
pending.collect_entries(entries.iter().cloned(), ctx)?;
|
||||
|
||||
let mut binding_syms = HashSet::new();
|
||||
let binding_syms = collect_binding_syms::<_, ALLOW_DYN>(&pending, ctx)?;
|
||||
|
||||
for entry in &entries {
|
||||
if !is_static_entry(entry) && allow_dynamic {
|
||||
continue;
|
||||
}
|
||||
|
||||
match entry {
|
||||
ast::Entry::Inherit(inherit) => {
|
||||
for attr in inherit.attrs() {
|
||||
if let ast::Attr::Ident(ident) = attr {
|
||||
let sym = ctx.new_sym(ident.to_string());
|
||||
if !binding_syms.insert(sym) {
|
||||
return Err(Error::downgrade_error(
|
||||
format!(
|
||||
"attribute '{}' already defined",
|
||||
format_symbol(ctx.get_sym(sym))
|
||||
),
|
||||
ctx.get_current_source(),
|
||||
ident.syntax().text_range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ast::Entry::AttrpathValue(value) => {
|
||||
let attrpath = value.attrpath().unwrap();
|
||||
let attrs_vec: Vec<_> = attrpath.attrs().collect();
|
||||
|
||||
if attrs_vec.len() == 1 {
|
||||
if let Some(ast::Attr::Ident(ident)) = attrs_vec.first() {
|
||||
let sym = ctx.new_sym(ident.to_string());
|
||||
if !binding_syms.insert(sym) {
|
||||
return Err(Error::downgrade_error(
|
||||
format!(
|
||||
"attribute '{}' already defined",
|
||||
format_symbol(ctx.get_sym(sym))
|
||||
),
|
||||
ctx.get_current_source(),
|
||||
ident.syntax().text_range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
} else if attrs_vec.len() > 1
|
||||
&& let Some(ast::Attr::Ident(ident)) = attrs_vec.first()
|
||||
{
|
||||
let sym = ctx.new_sym(ident.to_string());
|
||||
binding_syms.insert(sym);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IMPORTANT: For `inherit x` (without `from`), we must look up `x` in the OUTER scope
|
||||
// BEFORE adding the rec's slots to the scope. Otherwise, `x` would resolve to its own
|
||||
// slot, causing infinite recursion.
|
||||
let mut inherit_lookups: HashMap<SymId, (ExprId, TextRange)> = HashMap::new();
|
||||
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 span = ident.syntax().text_range();
|
||||
let sym = ctx.new_sym(ident.to_string());
|
||||
let expr = ctx.lookup(sym, span)?;
|
||||
inherit_lookups.insert(sym, (expr, span));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let inherit_lookups = collect_inherit_lookups(&entries, ctx)?;
|
||||
|
||||
let binding_keys: Vec<_> = binding_syms.into_iter().collect();
|
||||
let slots: Vec<_> = ctx.reserve_slots(binding_keys.len()).collect();
|
||||
@@ -469,52 +657,22 @@ where
|
||||
.collect();
|
||||
|
||||
for &slot in &slots {
|
||||
let span = synthetic_span();
|
||||
ctx.replace_ir(slot, Thunk { inner: slot, span }.to_ir());
|
||||
let slot_span = synthetic_span();
|
||||
ctx.replace_ir(
|
||||
slot,
|
||||
Thunk {
|
||||
inner: slot,
|
||||
span: slot_span,
|
||||
}
|
||||
.to_ir(),
|
||||
);
|
||||
}
|
||||
|
||||
ctx.with_let_scope(let_bindings.clone(), |ctx| {
|
||||
let mut temp_attrs = AttrSet {
|
||||
stcs: HashMap::new(),
|
||||
dyns: Vec::new(),
|
||||
span: synthetic_span(),
|
||||
};
|
||||
|
||||
for entry in entries {
|
||||
if !is_static_entry(&entry) && allow_dynamic {
|
||||
if let ast::Entry::AttrpathValue(value) = entry {
|
||||
downgrade_attrpathvalue(value, &mut temp_attrs, ctx)?;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
match entry {
|
||||
ast::Entry::Inherit(inherit) => {
|
||||
if inherit.from().is_some() {
|
||||
downgrade_inherit(inherit, &mut temp_attrs.stcs, ctx)?;
|
||||
} else {
|
||||
for attr in inherit.attrs() {
|
||||
if let ast::Attr::Ident(ident) = attr {
|
||||
let sym = ctx.new_sym(ident.to_string());
|
||||
if let Some(&(expr, span)) = inherit_lookups.get(&sym) {
|
||||
temp_attrs.stcs.insert(sym, (expr, span));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ast::Entry::AttrpathValue(value) => {
|
||||
if allow_dynamic {
|
||||
downgrade_attrpathvalue(value, &mut temp_attrs, ctx)?;
|
||||
} else {
|
||||
downgrade_static_attrpathvalue(value, &mut temp_attrs, ctx)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let finalized = finalize_pending_set::<_, ALLOW_DYN>(pending, &inherit_lookups, ctx)?;
|
||||
|
||||
for (sym, slot) in binding_keys.iter().copied().zip(slots.iter()) {
|
||||
if let Some(&(expr, _)) = temp_attrs.stcs.get(&sym) {
|
||||
if let Some(&(expr, _)) = finalized.stcs.get(&sym) {
|
||||
ctx.register_thunk(*slot, expr);
|
||||
} else {
|
||||
return Err(Error::internal(format!(
|
||||
@@ -524,6 +682,163 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
body_fn(ctx, &binding_keys, &temp_attrs.dyns)
|
||||
body_fn(ctx, &binding_keys, &finalized.dyns)
|
||||
})
|
||||
}
|
||||
|
||||
/// Collects `inherit x` lookups from the outer scope before entering the rec scope.
|
||||
fn collect_inherit_lookups<Ctx: DowngradeContext>(
|
||||
entries: &[ast::Entry],
|
||||
ctx: &mut Ctx,
|
||||
) -> Result<HashMap<SymId, (ExprId, TextRange)>> {
|
||||
let mut inherit_lookups = HashMap::new();
|
||||
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.new_sym(ident.to_string());
|
||||
let expr = ctx.lookup(sym, attr_span)?;
|
||||
inherit_lookups.insert(sym, (expr, attr_span));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(inherit_lookups)
|
||||
}
|
||||
|
||||
/// Collects binding symbols from a pending set, checking for duplicates.
|
||||
fn collect_binding_syms<Ctx: DowngradeContext, const ALLOW_DYN: bool>(
|
||||
pending: &PendingAttrSet,
|
||||
ctx: &mut Ctx,
|
||||
) -> Result<HashSet<SymId>> {
|
||||
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",
|
||||
format_symbol(ctx.get_sym(*sym))
|
||||
),
|
||||
ctx.get_current_source(),
|
||||
*span,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(binding_syms)
|
||||
}
|
||||
|
||||
/// Unified finalize function for PendingAttrSet.
|
||||
/// ALLOW_DYN controls whether dynamic attributes are allowed.
|
||||
fn finalize_pending_set<Ctx: DowngradeContext, const ALLOW_DYN: bool>(
|
||||
pending: PendingAttrSet,
|
||||
inherit_lookups: &HashMap<SymId, (ExprId, TextRange)>,
|
||||
ctx: &mut Ctx,
|
||||
) -> Result<AttrSet> {
|
||||
let mut stcs = HashMap::new();
|
||||
let mut dyns = Vec::new();
|
||||
|
||||
for (sym, (value, value_span)) in pending.stcs {
|
||||
let expr_id = finalize_pending_value::<_, ALLOW_DYN>(value, inherit_lookups, ctx)?;
|
||||
stcs.insert(sym, (expr_id, 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(
|
||||
Str {
|
||||
val: ctx.get_sym(sym).to_string(),
|
||||
span: attr_span,
|
||||
}
|
||||
.to_ir(),
|
||||
),
|
||||
};
|
||||
let value_id = finalize_pending_value::<_, ALLOW_DYN>(value, inherit_lookups, ctx)?;
|
||||
dyns.push((key_expr, value_id, value_span));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(AttrSet {
|
||||
stcs,
|
||||
dyns,
|
||||
span: pending.span,
|
||||
})
|
||||
}
|
||||
|
||||
/// Unified finalize function for PendingValue.
|
||||
/// ALLOW_DYN controls whether dynamic attributes are allowed.
|
||||
fn finalize_pending_value<Ctx: DowngradeContext, const ALLOW_DYN: bool>(
|
||||
value: PendingValue,
|
||||
inherit_lookups: &HashMap<SymId, (ExprId, TextRange)>,
|
||||
ctx: &mut Ctx,
|
||||
) -> Result<ExprId> {
|
||||
match value {
|
||||
PendingValue::Expr(expr) => {
|
||||
if !ALLOW_DYN {
|
||||
check_no_dynamic_attrs(&expr, ctx)?;
|
||||
}
|
||||
let id = Downgrade::downgrade(expr, ctx)?;
|
||||
Ok(ctx.maybe_thunk(id))
|
||||
}
|
||||
PendingValue::InheritFrom(from_expr, sym, span) => {
|
||||
let from_id = Downgrade::downgrade(from_expr, ctx)?;
|
||||
let select = Select {
|
||||
expr: from_id,
|
||||
attrpath: vec![Attr::Str(sym, span)],
|
||||
default: None,
|
||||
span,
|
||||
};
|
||||
let select_id = ctx.new_expr(select.to_ir());
|
||||
Ok(ctx.maybe_thunk(select_id))
|
||||
}
|
||||
PendingValue::InheritScope(sym, span) => {
|
||||
if let Some(&(expr, _)) = inherit_lookups.get(&sym) {
|
||||
Ok(ctx.maybe_thunk(expr))
|
||||
} else {
|
||||
let lookup_id = ctx.lookup(sym, span)?;
|
||||
Ok(ctx.maybe_thunk(lookup_id))
|
||||
}
|
||||
}
|
||||
PendingValue::Set(set) => {
|
||||
let attrset = finalize_pending_set::<_, ALLOW_DYN>(set, inherit_lookups, ctx)?;
|
||||
Ok(ctx.new_expr(attrset.to_ir()))
|
||||
}
|
||||
PendingValue::ExtendedRecAttrSet {
|
||||
base,
|
||||
extensions,
|
||||
span,
|
||||
} => {
|
||||
let mut all_entries: Vec<_> = base.entries().collect();
|
||||
all_entries.extend(extensions);
|
||||
downgrade_rec_bindings(all_entries, ctx, span)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check that an expression doesn't contain dynamic attributes (for let bindings).
|
||||
fn check_no_dynamic_attrs(expr: &ast::Expr, ctx: &impl DowngradeContext) -> Result<()> {
|
||||
let ast::Expr::AttrSet(attrset) = expr else {
|
||||
return Ok(())
|
||||
};
|
||||
for v in attrset.attrpath_values() {
|
||||
v.attrpath().unwrap().attrs().try_for_each(|attr| {
|
||||
if let ast::Attr::Dynamic(dyn_attr) = attr {
|
||||
Err(Error::downgrade_error(
|
||||
"dynamic attributes not allowed in let bindings".to_string(),
|
||||
ctx.get_current_source(),
|
||||
dyn_attr.syntax().text_range(),
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -739,7 +739,7 @@ fn to_value<'a>(
|
||||
}
|
||||
|
||||
if is_cycle(val, scope, is_cycle_symbol) {
|
||||
return Value::Thunk;
|
||||
return Value::Repeated;
|
||||
}
|
||||
|
||||
if let Some(path_val) = extract_path(val, scope, is_path_symbol) {
|
||||
|
||||
@@ -214,7 +214,7 @@ impl Display for Value {
|
||||
Func => write!(f, "«lambda»"),
|
||||
PrimOp(name) => write!(f, "«primop {name}»"),
|
||||
PrimOpApp(name) => write!(f, "«partially applied primop {name}»"),
|
||||
Repeated => write!(f, "<REPEATED>"),
|
||||
Repeated => write!(f, "«repeated»"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ eval_okay_test!(getenv, || {
|
||||
std::env::set_var("TEST_VAR", "foo")
|
||||
};
|
||||
});
|
||||
eval_okay_test!(groupBy);
|
||||
eval_okay_test!(#[ignore = "not implemented: hashString"] groupBy);
|
||||
eval_okay_test!(r#if);
|
||||
eval_okay_test!(ind_string);
|
||||
eval_okay_test!(#[ignore = "not implemented: scopedImport"] import);
|
||||
@@ -156,7 +156,7 @@ eval_okay_test!(merge_dynamic_attrs);
|
||||
eval_okay_test!(nested_with);
|
||||
eval_okay_test!(new_let);
|
||||
eval_okay_test!(null_dynamic_attrs);
|
||||
eval_okay_test!(overrides);
|
||||
eval_okay_test!(#[ignore = "__overrides is not supported"] overrides);
|
||||
eval_okay_test!(#[ignore = "not implemented: parseFlakeRef"] parse_flake_ref);
|
||||
eval_okay_test!(partition);
|
||||
eval_okay_test!(path);
|
||||
|
||||
Reference in New Issue
Block a user