fix: recursive attrs

This commit is contained in:
2026-01-30 17:03:31 +08:00
parent aee46b0b49
commit 9545b0fcae
5 changed files with 167 additions and 71 deletions

View File

@@ -141,10 +141,9 @@ export const all =
export const any =
(pred: NixValue) =>
(list: NixValue): boolean => {
// CppNix forces `pred` eagerly
const f = forceFunction(pred);
const forcedList = forceList(list);
if (forcedList.length) {
const f = forceFunction(pred);
return forcedList.some((e) => forceBool(f(e)));
}
return true;
// `false` when no element
return forcedList.some((e) => forceBool(f(e)));
};

View File

@@ -108,6 +108,42 @@ impl Ir {
}
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>,
@@ -171,15 +207,34 @@ impl AttrSet {
// This is the final attribute in the path, so insert the value here.
match name {
Attr::Str(ident, span) => {
if self.stcs.insert(ident, (value, span)).is_some() {
return Err(Error::downgrade_error(
format!(
"attribute '{}' already defined",
format_symbol(ctx.get_sym(ident)),
),
ctx.get_current_source(),
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) => {

View File

@@ -220,21 +220,7 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::AttrSet {
// rec { a = 1; b = a; } => let a = 1; b = a; in { inherit a b; }
let entries: Vec<_> = self.entries().collect();
downgrade_let_bindings(entries, ctx, span, |ctx, binding_keys| {
// Create plain attrset as body with inherit
let mut attrs = AttrSet {
stcs: HashMap::new(),
dyns: Vec::new(),
span,
};
for sym in binding_keys {
let expr = ctx.lookup(*sym, synthetic_span())?;
attrs.stcs.insert(*sym, (expr, synthetic_span()));
}
Ok(ctx.new_expr(attrs.to_ir()))
})
downgrade_rec_bindings(entries, ctx, span)
}
}
@@ -321,10 +307,9 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Select {
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::LegacyLet {
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
let span = self.syntax().text_range();
let bindings = downgrade_static_attrs(self, ctx)?;
let binding_keys: Vec<_> = bindings.keys().copied().collect();
let attrset_expr = ctx.with_let_scope(bindings, |ctx| {
let entries: Vec<_> = self.entries().collect();
let attrset_expr = downgrade_let_bindings(entries, ctx, span, |ctx, binding_keys| {
// Create plain attrset as body with inherit
let mut attrs = AttrSet {
stcs: HashMap::new(),
dyns: Vec::new(),
@@ -332,12 +317,11 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::LegacyLet {
};
for sym in binding_keys {
// FIXME: span
let expr = ctx.lookup(sym, synthetic_span())?;
attrs.stcs.insert(sym, (expr, synthetic_span()));
let expr = ctx.lookup(*sym, synthetic_span())?;
attrs.stcs.insert(*sym, (expr, synthetic_span()));
}
Result::Ok(ctx.new_expr(attrs.to_ir()))
Ok(ctx.new_expr(attrs.to_ir()))
})?;
let body_sym = ctx.new_sym("body".to_string());

View File

@@ -37,32 +37,6 @@ pub fn downgrade_attrs(
Ok(attrs)
}
/// Downgrades attribute set entries for a `let...in` expression.
/// This is a stricter version of `downgrade_attrs` that disallows dynamic attributes,
/// as `let` bindings must be statically known.
pub fn downgrade_static_attrs(
attrs: impl ast::HasEntry + AstNode,
ctx: &mut impl DowngradeContext,
) -> Result<HashMap<SymId, ExprId>> {
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_static_attrpathvalue(value, &mut attrs, ctx)?
}
}
}
Ok(attrs.stcs.into_iter().map(|(k, (v, _))| (k, v)).collect())
}
/// 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).
@@ -102,7 +76,8 @@ pub fn downgrade_inherit(
);
ctx.maybe_thunk(select_expr)
} else {
ctx.lookup(ident, span)?
let lookup_expr = ctx.lookup(ident, span)?;
ctx.maybe_thunk(lookup_expr)
};
match stcs.entry(ident) {
Entry::Occupied(occupied) => {
@@ -338,20 +313,88 @@ where
}
/// Helper function to downgrade entries with let bindings semantics.
/// This extracts common logic for both `rec` attribute sets and `let...in` expressions.
/// This extracts common logic for `let...in` expressions.
/// For `rec` attribute sets, use `downgrade_rec_bindings` instead.
pub fn downgrade_let_bindings<Ctx, F>(
entries: Vec<ast::Entry>,
ctx: &mut Ctx,
_span: TextRange,
span: TextRange,
body_fn: F,
) -> Result<ExprId>
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,
)
}
/// Helper function to downgrade `rec` attribute sets that may contain dynamic attributes.
/// Similar to `downgrade_let_bindings`, but allows dynamic attributes.
pub fn downgrade_rec_bindings<Ctx>(
entries: Vec<ast::Entry>,
ctx: &mut Ctx,
span: TextRange,
) -> Result<ExprId>
where
Ctx: DowngradeContext,
{
downgrade_let_bindings_impl(
entries,
ctx,
span,
|ctx, binding_keys, dyns| {
let mut attrs = AttrSet {
stcs: HashMap::new(),
dyns: dyns.to_vec(),
span,
};
for sym in binding_keys {
let expr = ctx.lookup(*sym, synthetic_span())?;
attrs.stcs.insert(*sym, (expr, synthetic_span()));
}
Ok(ctx.new_expr(attrs.to_ir()))
},
true,
)
}
fn downgrade_let_bindings_impl<Ctx, F>(
entries: Vec<ast::Entry>,
ctx: &mut Ctx,
_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 binding_syms = HashSet::new();
for entry in &entries {
if !is_static_entry(entry) && allow_dynamic {
continue;
}
match entry {
ast::Entry::Inherit(inherit) => {
for attr in inherit.attrs() {
@@ -438,13 +481,18 @@ where
};
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() {
// `inherit (from) x` - process normally, `from` may reference current scope
downgrade_inherit(inherit, &mut temp_attrs.stcs, ctx)?;
} else {
// `inherit x` - use pre-looked-up expressions from outer scope
for attr in inherit.attrs() {
if let ast::Attr::Ident(ident) = attr {
let sym = ctx.new_sym(ident.to_string());
@@ -456,7 +504,11 @@ where
}
}
ast::Entry::AttrpathValue(value) => {
downgrade_static_attrpathvalue(value, &mut temp_attrs, ctx)?;
if allow_dynamic {
downgrade_attrpathvalue(value, &mut temp_attrs, ctx)?;
} else {
downgrade_static_attrpathvalue(value, &mut temp_attrs, ctx)?;
}
}
}
}
@@ -472,6 +524,6 @@ where
}
}
body_fn(ctx, &binding_keys)
body_fn(ctx, &binding_keys, &temp_attrs.dyns)
})
}

View File

@@ -572,7 +572,13 @@ impl<Ctx: RuntimeContext> Runtime<Ctx> {
..Default::default()
});
let (is_thunk_symbol, primop_metadata_symbol, has_context_symbol, is_path_symbol, is_cycle_symbol) = {
let (
is_thunk_symbol,
primop_metadata_symbol,
has_context_symbol,
is_path_symbol,
is_cycle_symbol,
) = {
deno_core::scope!(scope, &mut js_runtime);
Self::get_symbols(scope)?
};