refactor: recursive attrset; fix attrset merging
This commit is contained in:
@@ -4,3 +4,7 @@ members = [
|
|||||||
"nix-js",
|
"nix-js",
|
||||||
"nix-js-macros"
|
"nix-js-macros"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[profile.profiling]
|
||||||
|
inherits = "release"
|
||||||
|
debug = true
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
valgrind
|
valgrind
|
||||||
hyperfine
|
hyperfine
|
||||||
just
|
just
|
||||||
|
samply
|
||||||
|
|
||||||
nodejs
|
nodejs
|
||||||
nodePackages.npm
|
nodePackages.npm
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ pub fn ir_impl(input: TokenStream) -> TokenStream {
|
|||||||
let mut mut_variants = Vec::new();
|
let mut mut_variants = Vec::new();
|
||||||
let mut as_ref_arms = Vec::new();
|
let mut as_ref_arms = Vec::new();
|
||||||
let mut as_mut_arms = Vec::new();
|
let mut as_mut_arms = Vec::new();
|
||||||
|
let mut span_arms = Vec::new();
|
||||||
let mut from_impls = Vec::new();
|
let mut from_impls = Vec::new();
|
||||||
let mut to_trait_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) });
|
mut_variants.push(quote! { #name(&'a mut #inner_type) });
|
||||||
as_ref_arms.push(quote! { Self::#name(inner) => #ref_name::#name(inner) });
|
as_ref_arms.push(quote! { Self::#name(inner) => #ref_name::#name(inner) });
|
||||||
as_mut_arms.push(quote! { Self::#name(inner) => #mut_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! {
|
from_impls.push(quote! {
|
||||||
impl From<#inner_type> for #base_name {
|
impl From<#inner_type> for #base_name {
|
||||||
fn from(val: #inner_type) -> Self { #base_name::#name(val) }
|
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) });
|
mut_variants.push(quote! { #name(&'a mut #inner_type) });
|
||||||
as_ref_arms.push(quote! { Self::#name(inner) => #ref_name::#name(inner) });
|
as_ref_arms.push(quote! { Self::#name(inner) => #ref_name::#name(inner) });
|
||||||
as_mut_arms.push(quote! { Self::#name(inner) => #mut_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! {
|
from_impls.push(quote! {
|
||||||
impl From<#inner_type> for #base_name {
|
impl From<#inner_type> for #base_name {
|
||||||
fn from(val: #inner_type) -> Self { #base_name::#name(val) }
|
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) });
|
mut_variants.push(quote! { #name(&'a mut #inner_type) });
|
||||||
as_ref_arms.push(quote! { Self::#name(inner) => #ref_name::#name(inner) });
|
as_ref_arms.push(quote! { Self::#name(inner) => #ref_name::#name(inner) });
|
||||||
as_mut_arms.push(quote! { Self::#name(inner) => #mut_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! {
|
from_impls.push(quote! {
|
||||||
impl From<#inner_type> for #base_name {
|
impl From<#inner_type> for #base_name {
|
||||||
fn from(val: #inner_type) -> Self { #base_name::#name(val) }
|
fn from(val: #inner_type) -> Self { #base_name::#name(val) }
|
||||||
@@ -223,6 +227,12 @@ pub fn ir_impl(input: TokenStream) -> TokenStream {
|
|||||||
#( #as_mut_arms ),*
|
#( #as_mut_arms ),*
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn span(&self) -> rnix::TextRange {
|
||||||
|
match self {
|
||||||
|
#( #span_arms ),*
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// `From` implementations for converting variant structs into the main enum.
|
// `From` implementations for converting variant structs into the main enum.
|
||||||
|
|||||||
@@ -274,7 +274,7 @@ impl<'a, 'ctx> ScopeGuard<'a, 'ctx> {
|
|||||||
|
|
||||||
pub struct DowngradeCtx<'ctx> {
|
pub struct DowngradeCtx<'ctx> {
|
||||||
ctx: &'ctx mut Ctx,
|
ctx: &'ctx mut Ctx,
|
||||||
irs: Vec<Option<Ir>>,
|
irs: Vec<Ir>,
|
||||||
scopes: Vec<Scope<'ctx>>,
|
scopes: Vec<Scope<'ctx>>,
|
||||||
arg_id: usize,
|
arg_id: usize,
|
||||||
thunk_scopes: Vec<Vec<(ExprId, ExprId)>>,
|
thunk_scopes: Vec<Vec<(ExprId, ExprId)>>,
|
||||||
@@ -294,18 +294,18 @@ impl<'ctx> DowngradeCtx<'ctx> {
|
|||||||
|
|
||||||
impl DowngradeContext for DowngradeCtx<'_> {
|
impl DowngradeContext for DowngradeCtx<'_> {
|
||||||
fn new_expr(&mut self, expr: Ir) -> ExprId {
|
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)
|
ExprId(self.ctx.irs.len() + self.irs.len() - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_arg(&mut self, span: TextRange) -> ExprId {
|
fn new_arg(&mut self, span: TextRange) -> ExprId {
|
||||||
self.irs.push(Some(
|
self.irs.push(
|
||||||
Arg {
|
Arg {
|
||||||
inner: ArgId(self.arg_id),
|
inner: ArgId(self.arg_id),
|
||||||
span,
|
span,
|
||||||
}
|
}
|
||||||
.to_ir(),
|
.to_ir(),
|
||||||
));
|
);
|
||||||
self.arg_id += 1;
|
self.arg_id += 1;
|
||||||
ExprId(self.ctx.irs.len() + self.irs.len() - 1)
|
ExprId(self.ctx.irs.len() + self.irs.len() - 1)
|
||||||
}
|
}
|
||||||
@@ -317,8 +317,6 @@ impl DowngradeContext for DowngradeCtx<'_> {
|
|||||||
self.irs
|
self.irs
|
||||||
.get(id.0 - self.ctx.irs.len())
|
.get(id.0 - self.ctx.irs.len())
|
||||||
.expect("ExprId out of bounds")
|
.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) {
|
fn replace_ir(&mut self, id: ExprId, expr: Ir) {
|
||||||
let local_id = id.0 - self.ctx.irs.len();
|
let local_id = id.0 - self.ctx.irs.len();
|
||||||
let _ = self
|
*self.irs.get_mut(local_id).expect("ExprId out of bounds") = expr;
|
||||||
.irs
|
|
||||||
.get_mut(local_id)
|
|
||||||
.expect("ExprId out of bounds")
|
|
||||||
.insert(expr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_current_source(&self) -> Source {
|
fn get_current_source(&self) -> Source {
|
||||||
@@ -429,8 +414,11 @@ impl DowngradeContext for DowngradeCtx<'_> {
|
|||||||
#[allow(refining_impl_trait)]
|
#[allow(refining_impl_trait)]
|
||||||
fn reserve_slots(&mut self, slots: usize) -> impl Iterator<Item = ExprId> + Clone + use<> {
|
fn reserve_slots(&mut self, slots: usize) -> impl Iterator<Item = ExprId> + Clone + use<> {
|
||||||
let start = self.ctx.irs.len() + self.irs.len();
|
let start = self.ctx.irs.len() + self.irs.len();
|
||||||
self.irs.extend(std::iter::repeat_with(|| None).take(slots));
|
let range = (start..start + slots).map(ExprId);
|
||||||
(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> {
|
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());
|
let top_level = self.new_expr(TopLevel { body, thunks, span }.to_ir());
|
||||||
self.ctx
|
self.ctx
|
||||||
.irs
|
.irs
|
||||||
.extend(self.irs.into_iter().map(Option::unwrap));
|
.extend(self.irs);
|
||||||
Ok(top_level)
|
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 rnix::{TextRange, ast};
|
||||||
use string_interner::symbol::SymbolU32;
|
use string_interner::symbol::SymbolU32;
|
||||||
|
|
||||||
use crate::error::{Error, Result, Source};
|
use crate::error::{Result, Source};
|
||||||
use crate::value::format_symbol;
|
|
||||||
use nix_js_macros::ir;
|
use nix_js_macros::ir;
|
||||||
|
|
||||||
mod downgrade;
|
mod downgrade;
|
||||||
@@ -28,7 +27,6 @@ pub trait DowngradeContext {
|
|||||||
fn lookup(&mut self, sym: SymId, span: TextRange) -> Result<ExprId>;
|
fn lookup(&mut self, sym: SymId, span: TextRange) -> Result<ExprId>;
|
||||||
|
|
||||||
fn get_ir(&self, id: ExprId) -> &Ir;
|
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 replace_ir(&mut self, id: ExprId, expr: Ir);
|
||||||
fn reserve_slots(&mut self, slots: usize) -> impl Iterator<Item = ExprId> + Clone + use<Self>;
|
fn reserve_slots(&mut self, slots: usize) -> impl Iterator<Item = ExprId> + Clone + use<Self>;
|
||||||
fn get_current_source(&self) -> Source;
|
fn get_current_source(&self) -> Source;
|
||||||
@@ -77,189 +75,6 @@ ir! {
|
|||||||
CurPos,
|
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)]
|
#[repr(transparent)]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
pub struct ExprId(pub usize);
|
pub struct ExprId(pub usize);
|
||||||
@@ -271,6 +86,7 @@ pub type SymId = SymbolU32;
|
|||||||
pub struct ArgId(pub usize);
|
pub struct ArgId(pub usize);
|
||||||
|
|
||||||
/// Represents a key in an attribute path.
|
/// Represents a key in an attribute path.
|
||||||
|
#[allow(unused)]
|
||||||
#[derive(Debug, TryUnwrap)]
|
#[derive(Debug, TryUnwrap)]
|
||||||
pub enum Attr {
|
pub enum Attr {
|
||||||
/// A dynamic attribute key, which is an expression that must evaluate to a string.
|
/// A dynamic attribute key, which is an expression that must evaluate to a string.
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use hashbrown::hash_map::Entry;
|
|||||||
use hashbrown::{HashMap, HashSet};
|
use hashbrown::{HashMap, HashSet};
|
||||||
use itertools::Itertools as _;
|
use itertools::Itertools as _;
|
||||||
use rnix::TextRange;
|
use rnix::TextRange;
|
||||||
use rnix::ast;
|
use rnix::ast::{self, HasEntry};
|
||||||
use rowan::ast::AstNode;
|
use rowan::ast::AstNode;
|
||||||
|
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
@@ -14,92 +14,404 @@ use crate::value::format_symbol;
|
|||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// Downgrades the entries of an attribute set.
|
enum PendingValue {
|
||||||
/// This handles `inherit` and `attrpath = value;` entries.
|
Expr(ast::Expr),
|
||||||
pub fn downgrade_attrs(
|
InheritFrom(ast::Expr, SymId, TextRange),
|
||||||
attrs: impl ast::HasEntry + AstNode,
|
InheritScope(SymId, TextRange),
|
||||||
ctx: &mut impl DowngradeContext,
|
Set(PendingAttrSet),
|
||||||
) -> Result<AttrSet> {
|
ExtendedRecAttrSet {
|
||||||
let entries = attrs.entries();
|
base: ast::AttrSet,
|
||||||
let mut attrs = AttrSet {
|
extensions: Vec<ast::Entry>,
|
||||||
stcs: HashMap::new(),
|
span: TextRange,
|
||||||
dyns: Vec::new(),
|
},
|
||||||
span: attrs.syntax().text_range(),
|
}
|
||||||
};
|
|
||||||
|
|
||||||
for entry in entries {
|
struct PendingAttrSet {
|
||||||
match entry {
|
stcs: HashMap<SymId, (PendingValue, TextRange)>,
|
||||||
ast::Entry::Inherit(inherit) => downgrade_inherit(inherit, &mut attrs.stcs, ctx)?,
|
dyns: Vec<(ast::Attr, PendingValue, TextRange)>,
|
||||||
ast::Entry::AttrpathValue(value) => downgrade_attrpathvalue(value, &mut attrs, ctx)?,
|
span: TextRange,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PendingAttrSet {
|
||||||
|
fn new(span: TextRange) -> Self {
|
||||||
|
Self {
|
||||||
|
stcs: HashMap::new(),
|
||||||
|
dyns: Vec::new(),
|
||||||
|
span,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(attrs)
|
fn insert(
|
||||||
}
|
&mut self,
|
||||||
|
path: &[ast::Attr],
|
||||||
|
value: ast::Expr,
|
||||||
|
ctx: &mut impl DowngradeContext,
|
||||||
|
) -> Result<()> {
|
||||||
|
let first = path.first().expect("empty attrpath passed. this is a bug");
|
||||||
|
let rest = &path[1..];
|
||||||
|
let span = first.syntax().text_range();
|
||||||
|
|
||||||
/// Downgrades an `inherit` statement.
|
match first {
|
||||||
/// `inherit (from) a b;` is translated into `a = from.a; b = from.b;`.
|
ast::Attr::Ident(ident) => {
|
||||||
/// `inherit a b;` is translated into `a = a; b = b;` (i.e., bringing variables into scope).
|
let sym = ctx.new_sym(ident.to_string());
|
||||||
pub fn downgrade_inherit(
|
self.insert_static(sym, span, rest, value, ctx)
|
||||||
inherit: ast::Inherit,
|
}
|
||||||
stcs: &mut HashMap<SymId, (ExprId, rnix::TextRange)>,
|
ast::Attr::Str(string) => {
|
||||||
ctx: &mut impl DowngradeContext,
|
let parts = string.normalized_parts();
|
||||||
) -> Result<()> {
|
if parts.len() == 1
|
||||||
// Downgrade the `from` expression if it exists.
|
&& let ast::InterpolPart::Literal(lit) = parts.into_iter().next().unwrap()
|
||||||
let from = if let Some(from) = inherit.from() {
|
{
|
||||||
Some(from.expr().unwrap().downgrade(ctx)?)
|
let sym = ctx.new_sym(lit);
|
||||||
} else {
|
return self.insert_static(sym, span, rest, value, ctx);
|
||||||
None
|
}
|
||||||
};
|
self.insert_dynamic(first.clone(), span, rest, value)
|
||||||
for attr in inherit.attrs() {
|
}
|
||||||
let span = attr.syntax().text_range();
|
ast::Attr::Dynamic(_) => self.insert_dynamic(first.clone(), span, rest, value),
|
||||||
let ident = match downgrade_attr(attr, ctx)? {
|
}
|
||||||
Attr::Str(ident, _) => ident,
|
}
|
||||||
_ => {
|
|
||||||
// `inherit` does not allow dynamic attributes.
|
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 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(),
|
||||||
|
span,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.stcs.contains_key(&sym) {
|
||||||
return Err(Error::downgrade_error(
|
return Err(Error::downgrade_error(
|
||||||
"dynamic attributes not allowed in inherit".to_string(),
|
format!(
|
||||||
|
"attribute '{}' already defined",
|
||||||
|
format_symbol(ctx.get_sym(sym))
|
||||||
|
),
|
||||||
ctx.get_current_source(),
|
ctx.get_current_source(),
|
||||||
span,
|
span,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
};
|
|
||||||
let expr = if let Some(expr) = from {
|
let pending_value = if let Some(ref from_expr) = from {
|
||||||
let select_expr = ctx.new_expr(
|
PendingValue::InheritFrom(from_expr.clone(), sym, span)
|
||||||
Select {
|
} else {
|
||||||
expr,
|
PendingValue::InheritScope(sym, span)
|
||||||
attrpath: vec![Attr::Str(ident, span)],
|
};
|
||||||
default: None,
|
|
||||||
span,
|
self.stcs.insert(sym, (pending_value, span));
|
||||||
}
|
}
|
||||||
.to_ir(),
|
Ok(())
|
||||||
);
|
|
||||||
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) => {
|
|
||||||
return Err(Error::downgrade_error(
|
|
||||||
format!(
|
|
||||||
"attribute '{}' already defined",
|
|
||||||
format_symbol(ctx.get_sym(*occupied.key()))
|
|
||||||
),
|
|
||||||
ctx.get_current_source(),
|
|
||||||
span,
|
|
||||||
)
|
|
||||||
.with_span(span)
|
|
||||||
.with_source(ctx.get_current_source()));
|
|
||||||
}
|
|
||||||
Entry::Vacant(vacant) => vacant.insert((expr, 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).
|
/// 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> {
|
pub fn downgrade_attr(attr: ast::Attr, ctx: &mut impl DowngradeContext) -> Result<Attr> {
|
||||||
use ast::Attr::*;
|
use ast::Attr::*;
|
||||||
use ast::InterpolPart::*;
|
use ast::InterpolPart::*;
|
||||||
@@ -114,7 +426,6 @@ pub fn downgrade_attr(attr: ast::Attr, ctx: &mut impl DowngradeContext) -> Resul
|
|||||||
if parts.is_empty() {
|
if parts.is_empty() {
|
||||||
Ok(Attr::Str(ctx.new_sym("".to_string()), span))
|
Ok(Attr::Str(ctx.new_sym("".to_string()), span))
|
||||||
} else if parts.len() == 1 {
|
} 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() {
|
match parts.into_iter().next().unwrap() {
|
||||||
Literal(ident) => Ok(Attr::Str(ctx.new_sym(ident), span)),
|
Literal(ident) => Ok(Attr::Str(ctx.new_sym(ident), span)),
|
||||||
Interpolation(interpol) => Ok(Attr::Dynamic(
|
Interpolation(interpol) => Ok(Attr::Dynamic(
|
||||||
@@ -123,7 +434,6 @@ pub fn downgrade_attr(attr: ast::Attr, ctx: &mut impl DowngradeContext) -> Resul
|
|||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// If the string has multiple parts, it's an interpolated string that must be concatenated.
|
|
||||||
let parts = parts
|
let parts = parts
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|part| match part {
|
.map(|part| match part {
|
||||||
@@ -155,40 +465,6 @@ pub fn downgrade_attrpath(
|
|||||||
.collect::<Result<Vec<_>>>()
|
.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 struct PatternBindings {
|
||||||
pub body: ExprId,
|
pub body: ExprId,
|
||||||
pub required: Vec<SymId>,
|
pub required: Vec<SymId>,
|
||||||
@@ -196,7 +472,6 @@ pub struct PatternBindings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Helper function for Lambda pattern parameters.
|
/// Helper function for Lambda pattern parameters.
|
||||||
/// Processes pattern entries like `{ a, b ? 2, ... }@alias` and creates bindings.
|
|
||||||
pub fn downgrade_pattern_bindings<Ctx>(
|
pub fn downgrade_pattern_bindings<Ctx>(
|
||||||
pat_entries: impl Iterator<Item = ast::PatEntry>,
|
pat_entries: impl Iterator<Item = ast::PatEntry>,
|
||||||
alias: Option<SymId>,
|
alias: Option<SymId>,
|
||||||
@@ -312,9 +587,8 @@ where
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper function to downgrade entries with let bindings semantics.
|
/// Downgrades a `let...in` expression. This is a special case of rec attrs
|
||||||
/// This extracts common logic for `let...in` expressions.
|
/// that disallows dynamic attributes and has a body expression.
|
||||||
/// For `rec` attribute sets, use `downgrade_rec_bindings` instead.
|
|
||||||
pub fn downgrade_let_bindings<Ctx, F>(
|
pub fn downgrade_let_bindings<Ctx, F>(
|
||||||
entries: Vec<ast::Entry>,
|
entries: Vec<ast::Entry>,
|
||||||
ctx: &mut Ctx,
|
ctx: &mut Ctx,
|
||||||
@@ -325,17 +599,12 @@ where
|
|||||||
Ctx: DowngradeContext,
|
Ctx: DowngradeContext,
|
||||||
F: FnOnce(&mut Ctx, &[SymId]) -> Result<ExprId>,
|
F: FnOnce(&mut Ctx, &[SymId]) -> Result<ExprId>,
|
||||||
{
|
{
|
||||||
downgrade_let_bindings_impl(
|
downgrade_rec_attrs_impl::<_, _, false>(entries, ctx, span, |ctx, binding_keys, _dyns| {
|
||||||
entries,
|
body_fn(ctx, binding_keys)
|
||||||
ctx,
|
})
|
||||||
span,
|
|
||||||
|ctx, binding_keys, _dyns| body_fn(ctx, binding_keys),
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper function to downgrade `rec` attribute sets that may contain dynamic attributes.
|
/// Downgrades a `rec` attribute set.
|
||||||
/// Similar to `downgrade_let_bindings`, but allows dynamic attributes.
|
|
||||||
pub fn downgrade_rec_bindings<Ctx>(
|
pub fn downgrade_rec_bindings<Ctx>(
|
||||||
entries: Vec<ast::Entry>,
|
entries: Vec<ast::Entry>,
|
||||||
ctx: &mut Ctx,
|
ctx: &mut Ctx,
|
||||||
@@ -344,121 +613,40 @@ pub fn downgrade_rec_bindings<Ctx>(
|
|||||||
where
|
where
|
||||||
Ctx: DowngradeContext,
|
Ctx: DowngradeContext,
|
||||||
{
|
{
|
||||||
downgrade_let_bindings_impl(
|
downgrade_rec_attrs_impl::<_, _, true>(entries, ctx, span, |ctx, binding_keys, dyns| {
|
||||||
entries,
|
let mut attrs = AttrSet {
|
||||||
ctx,
|
stcs: HashMap::new(),
|
||||||
span,
|
dyns: dyns.to_vec(),
|
||||||
|ctx, binding_keys, dyns| {
|
span,
|
||||||
let mut attrs = AttrSet {
|
};
|
||||||
stcs: HashMap::new(),
|
|
||||||
dyns: dyns.to_vec(),
|
|
||||||
span,
|
|
||||||
};
|
|
||||||
|
|
||||||
for sym in binding_keys {
|
for sym in binding_keys {
|
||||||
let expr = ctx.lookup(*sym, synthetic_span())?;
|
let expr = ctx.lookup(*sym, synthetic_span())?;
|
||||||
attrs.stcs.insert(*sym, (expr, synthetic_span()));
|
attrs.stcs.insert(*sym, (expr, synthetic_span()));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(ctx.new_expr(attrs.to_ir()))
|
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>,
|
entries: Vec<ast::Entry>,
|
||||||
ctx: &mut Ctx,
|
ctx: &mut Ctx,
|
||||||
_span: TextRange,
|
span: TextRange,
|
||||||
body_fn: F,
|
body_fn: F,
|
||||||
allow_dynamic: bool,
|
|
||||||
) -> Result<ExprId>
|
) -> Result<ExprId>
|
||||||
where
|
where
|
||||||
Ctx: DowngradeContext,
|
Ctx: DowngradeContext,
|
||||||
F: FnOnce(&mut Ctx, &[SymId], &[(ExprId, ExprId, TextRange)]) -> Result<ExprId>,
|
F: FnOnce(&mut Ctx, &[SymId], &[(ExprId, ExprId, TextRange)]) -> Result<ExprId>,
|
||||||
{
|
{
|
||||||
fn is_static_entry(entry: &ast::Entry) -> bool {
|
let mut pending = PendingAttrSet::new(span);
|
||||||
match entry {
|
pending.collect_entries(entries.iter().cloned(), ctx)?;
|
||||||
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();
|
let binding_syms = collect_binding_syms::<_, ALLOW_DYN>(&pending, ctx)?;
|
||||||
|
|
||||||
for entry in &entries {
|
let inherit_lookups = collect_inherit_lookups(&entries, ctx)?;
|
||||||
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 binding_keys: Vec<_> = binding_syms.into_iter().collect();
|
let binding_keys: Vec<_> = binding_syms.into_iter().collect();
|
||||||
let slots: Vec<_> = ctx.reserve_slots(binding_keys.len()).collect();
|
let slots: Vec<_> = ctx.reserve_slots(binding_keys.len()).collect();
|
||||||
@@ -469,52 +657,22 @@ where
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
for &slot in &slots {
|
for &slot in &slots {
|
||||||
let span = synthetic_span();
|
let slot_span = synthetic_span();
|
||||||
ctx.replace_ir(slot, Thunk { inner: slot, span }.to_ir());
|
ctx.replace_ir(
|
||||||
|
slot,
|
||||||
|
Thunk {
|
||||||
|
inner: slot,
|
||||||
|
span: slot_span,
|
||||||
|
}
|
||||||
|
.to_ir(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.with_let_scope(let_bindings.clone(), |ctx| {
|
ctx.with_let_scope(let_bindings.clone(), |ctx| {
|
||||||
let mut temp_attrs = AttrSet {
|
let finalized = finalize_pending_set::<_, ALLOW_DYN>(pending, &inherit_lookups, ctx)?;
|
||||||
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)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (sym, slot) in binding_keys.iter().copied().zip(slots.iter()) {
|
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);
|
ctx.register_thunk(*slot, expr);
|
||||||
} else {
|
} else {
|
||||||
return Err(Error::internal(format!(
|
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) {
|
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) {
|
if let Some(path_val) = extract_path(val, scope, is_path_symbol) {
|
||||||
|
|||||||
@@ -214,7 +214,7 @@ impl Display for Value {
|
|||||||
Func => write!(f, "«lambda»"),
|
Func => write!(f, "«lambda»"),
|
||||||
PrimOp(name) => write!(f, "«primop {name}»"),
|
PrimOp(name) => write!(f, "«primop {name}»"),
|
||||||
PrimOpApp(name) => write!(f, "«partially applied 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")
|
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!(r#if);
|
||||||
eval_okay_test!(ind_string);
|
eval_okay_test!(ind_string);
|
||||||
eval_okay_test!(#[ignore = "not implemented: scopedImport"] import);
|
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!(nested_with);
|
||||||
eval_okay_test!(new_let);
|
eval_okay_test!(new_let);
|
||||||
eval_okay_test!(null_dynamic_attrs);
|
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!(#[ignore = "not implemented: parseFlakeRef"] parse_flake_ref);
|
||||||
eval_okay_test!(partition);
|
eval_okay_test!(partition);
|
||||||
eval_okay_test!(path);
|
eval_okay_test!(path);
|
||||||
|
|||||||
Reference in New Issue
Block a user