diff --git a/nix-js/src/codegen.rs b/nix-js/src/codegen.rs index 13103a3..42505ad 100644 --- a/nix-js/src/codegen.rs +++ b/nix-js/src/codegen.rs @@ -2,6 +2,7 @@ use std::fmt::{self, Write as _}; use std::path::Path; use crate::ir::*; +use crate::value::Symbol; pub(crate) struct CodeBuffer { buf: String, @@ -83,7 +84,7 @@ fn joined {{ + ($buf:expr, $ctx:expr; $($item:expr)*) => {{ $( ($item).compile($ctx, $buf); )* @@ -99,8 +100,8 @@ macro_rules! code { write!($buf, $fmt, $($arg)*).unwrap() }; - ($buf:expr, $lit:literal) => { - $buf.push_str($lit) + ($buf:expr, $fmt:literal) => { + write!($buf, $fmt).unwrap() }; } @@ -206,7 +207,7 @@ impl Compile for rnix::TextRange { pub(crate) trait CodegenContext { fn get_ir(&self, id: ExprId) -> &Ir; - fn get_sym(&self, id: SymId) -> &str; + fn get_sym(&self, id: SymId) -> Symbol<'_>; fn get_current_dir(&self) -> &Path; fn get_store_dir(&self) -> &str; fn get_current_source_id(&self) -> usize; @@ -219,6 +220,12 @@ impl Compile for ExprId { } } +impl Compile for Symbol<'_> { + fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { + quoted(self).compile(ctx, buf); + } +} + impl Compile for Ir { fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { match self { @@ -260,8 +267,8 @@ impl Compile for Ir { } &Ir::Builtin(Builtin { inner: name, .. }) => { code!(buf, ctx; - "Nix.builtins[", - quoted(ctx.get_sym(name)), + "Nix.builtins[" + ctx.get_sym(name) "]" ); } @@ -277,37 +284,37 @@ impl Compile for Ir { let assertion_span = assertion_ir.span(); code!(buf, ctx; - "Nix.assert(Nix.withContext(\"while evaluating the condition of the assert statement\",", - assertion_span, - ",()=>(", - assertion_ir, - ")),", - ctx.get_ir(expr), - ",", - quoted(assertion_raw), - ",", - assert_span, + "Nix.assert(Nix.withContext(\"while evaluating the condition of the assert statement\"," + assertion_span + ",()=>(" + assertion_ir + "))," + ctx.get_ir(expr) + "," + quoted(assertion_raw) + "," + assert_span ")" ); } Ir::CurPos(cur_pos) => { code!(buf, ctx; - "Nix.mkPos(", - cur_pos.span, + "Nix.mkPos(" + cur_pos.span ")" ); } &Ir::ReplBinding(ReplBinding { inner: name, .. }) => { code!(buf, ctx; "Nix.getReplBinding(" - quoted(ctx.get_sym(name)) + ctx.get_sym(name) ")" ); } &Ir::ScopedImportBinding(ScopedImportBinding { inner: name, .. }) => { code!(buf, ctx; "__scope[" - quoted(ctx.get_sym(name)) + ctx.get_sym(name) "]" ); } @@ -397,22 +404,10 @@ impl Compile for BinOp { ); } PipeL => { - code!(buf, ctx; - "Nix.call(", - rhs, - ",", - lhs, - ")" - ); + code!(buf, ctx; "Nix.call(" rhs "," lhs ")"); } PipeR => { - code!(buf, ctx; - "Nix.call(", - lhs, - ",", - rhs, - ")" - ); + code!(buf, ctx; "Nix.call(" lhs "," rhs ")"); } } } @@ -421,20 +416,13 @@ impl Compile for BinOp { impl Compile for UnOp { fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { use UnOpKind::*; + let rhs = ctx.get_ir(self.rhs); match self.kind { Neg => { - code!(buf, ctx; - "Nix.op.sub(0n,", - ctx.get_ir(self.rhs), - ")" - ); + code!(buf, ctx; "Nix.op.sub(0n," rhs ")"); } Not => { - code!(buf, ctx; - "Nix.op.bnot(", - ctx.get_ir(self.rhs), - ")" - ); + code!(buf, ctx; "Nix.op.bnot(" ctx.get_ir(self.rhs) ")"); } } } @@ -454,32 +442,33 @@ impl Compile for Func { { code!(buf, "Nix.mkFunction(arg{}=>", id); if has_thunks { - code!(buf, ctx; "{", &self.thunks, "return ", self.body, "}"); + code!(buf, ctx; "{" self.thunks "return " self.body "}"); } else { - code!(buf, ctx; "(", self.body, ")"); + code!(buf, ctx; "(" self.body ")"); } code!(buf, ctx; ",[" joined(required.iter(), ",", |ctx: &Ctx, buf, &(sym, _)| { - code!(buf, ctx; quoted(ctx.get_sym(sym))); + code!(buf, ctx; ctx.get_sym(sym)); }) "],[" joined(optional.iter(), ",", |ctx: &Ctx, buf, &(sym, _)| { - code!(buf, ctx; quoted(ctx.get_sym(sym))); + code!(buf, ctx; ctx.get_sym(sym)); }) "],{" joined(required.iter().chain(optional.iter()), ",", |ctx: &Ctx, buf, &(sym, span)| { - code!(buf, ctx; quoted(ctx.get_sym(sym)), ":", span); + code!(buf, ctx; ctx.get_sym(sym) ":" span); }) "}," + ellipsis + ")" ); - code!(buf, "{})", ellipsis); } else { code!(buf, "arg{}=>", id); if has_thunks { - code!(buf, ctx; "{", &self.thunks, "return ", self.body, "}"); + code!(buf, ctx; "{" self.thunks "return " self.body "}"); } else { - code!(buf, ctx; "(", self.body, ")"); + code!(buf, ctx; "(" self.body ")"); } } } @@ -488,12 +477,12 @@ impl Compile for Func { impl Compile for Call { fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { code!(buf, ctx; - "Nix.call(", - ctx.get_ir(self.func), - ",", - ctx.get_ir(self.arg), - ",", - self.span, + "Nix.call(" + ctx.get_ir(self.func) + "," + ctx.get_ir(self.arg) + "," + self.span ")" ); } @@ -527,11 +516,8 @@ impl Compile for TopLevel { if self.thunks.is_empty() { ctx.get_ir(self.body).compile(ctx, buf); } else { - code!(buf, "(()=>{"); - code!(buf, ctx; &self.thunks); - code!(buf, "return "); - ctx.get_ir(self.body).compile(ctx, buf); - code!(buf, "})()"); + let body = ctx.get_ir(self.body); + code!(buf, ctx; "(()=>{" self.thunks "return " body "})()"); } } } @@ -545,7 +531,7 @@ impl Compile for Select { ",[" joined(self.attrpath.iter(), ",", |ctx: &Ctx, buf, attr| { match attr { - Attr::Str(sym, _) => code!(buf, ctx; quoted(ctx.get_sym(*sym))), + Attr::Str(sym, _) => code!(buf, ctx; ctx.get_sym(*sym)), Attr::Dynamic(expr_id, _) => code!(buf, ctx; ctx.get_ir(*expr_id)), } }) @@ -562,7 +548,7 @@ impl Compile for Select { ",[" joined(self.attrpath.iter(), ",", |ctx: &Ctx, buf, attr| { match attr { - Attr::Str(sym, _) => code!(buf, ctx; quoted(ctx.get_sym(*sym))), + Attr::Str(sym, _) => code!(buf, ctx; ctx.get_sym(*sym)), Attr::Dynamic(expr_id, _) => code!(buf, ctx; ctx.get_ir(*expr_id)), } }) @@ -585,12 +571,12 @@ impl Compile for AttrSet { code!( buf, ctx; - quoted(key) ":Nix.withContext(\"while evaluating the attribute '" escaped(key) "'\"," val.span() ",()=>(" val "))" + key ":Nix.withContext(\"while evaluating the attribute '" escaped(&key) "'\"," val.span() ",()=>(" val "))" ); }) "},{" joined(self.stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(_, span))| { - code!(buf, ctx; quoted(ctx.get_sym(sym)) ":" span); + code!(buf, ctx; ctx.get_sym(sym) ":" span); }) "},{dynKeys:[" joined(self.dyns.iter(), ",", |ctx: &Ctx, buf, (key, _, _)| { @@ -619,12 +605,12 @@ impl Compile for AttrSet { code!( buf, ctx; - quoted(key) ":Nix.withContext(\"while evaluating the attribute '" escaped(key) "'\"," val.span() ",()=>(" val "))" + key ":Nix.withContext(\"while evaluating the attribute '" escaped(&key) "'\"," val.span() ",()=>(" val "))" ); }) "},{" joined(self.stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(_, span))| { - code!(buf, ctx; quoted(ctx.get_sym(sym)) ":" span); + code!(buf, ctx; ctx.get_sym(sym) ":" span); }) "})" ); @@ -674,7 +660,7 @@ impl Compile for HasAttr { ",[" joined(self.rhs.iter(), ",", |ctx: &Ctx, buf, attr| { match attr { - Attr::Str(sym, _) => code!(buf, ctx; quoted(ctx.get_sym(*sym))), + Attr::Str(sym, _) => code!(buf, ctx; ctx.get_sym(*sym)), Attr::Dynamic(expr_id, _) => code!(buf, ctx; ctx.get_ir(*expr_id)), } }) diff --git a/nix-js/src/context.rs b/nix-js/src/context.rs index e09905c..415b2f4 100644 --- a/nix-js/src/context.rs +++ b/nix-js/src/context.rs @@ -7,14 +7,15 @@ use rnix::TextRange; use string_interner::DefaultStringInterner; use crate::codegen::{CodegenContext, compile}; +use crate::downgrade::*; use crate::error::{Error, Result, Source}; use crate::ir::{ - Arg, ArgId, Bool, Builtin, Downgrade as _, DowngradeContext, ExprId, Ir, Null, ReplBinding, - ScopedImportBinding, SymId, Thunk, ToIr as _, + Arg, ArgId, Bool, Builtin, ExprId, Ir, Null, ReplBinding, ScopedImportBinding, SymId, Thunk, + ToIr as _, }; use crate::runtime::{Runtime, RuntimeContext}; use crate::store::{Store, StoreBackend, StoreConfig}; -use crate::value::Value; +use crate::value::{Symbol, Value}; pub struct Context { ctx: Ctx, @@ -54,7 +55,8 @@ impl Context { let code = self.ctx.compile(source, Some(Scope::Repl(scope)))?; tracing::debug!("Executing JavaScript"); - self.runtime.eval(format!("Nix.forceShallow({})", code), &mut self.ctx) + self.runtime + .eval(format!("Nix.forceShallow({})", code), &mut self.ctx) } pub fn compile(&mut self, source: Source) -> Result { @@ -65,7 +67,12 @@ impl Context { self.ctx.get_store_dir() } - pub fn add_binding<'a>(&'a mut self, name: &str, expr: &str, scope: &'a mut HashSet) -> Result { + pub fn add_binding<'a>( + &'a mut self, + name: &str, + expr: &str, + scope: &'a mut HashSet, + ) -> Result { let source = Source::new_repl(expr.to_string())?; let code = self.ctx.compile(source, Some(Scope::Repl(scope)))?; @@ -186,10 +193,7 @@ impl Ctx { }) } - fn downgrade_ctx<'a>( - &'a mut self, - extra_scope: Option>, - ) -> DowngradeCtx<'a> { + fn downgrade_ctx<'a>(&'a mut self, extra_scope: Option>) -> DowngradeCtx<'a> { let global_ref = unsafe { self.global.as_ref() }; DowngradeCtx::new(self, global_ref, extra_scope) } @@ -236,11 +240,7 @@ impl Ctx { Ok(code) } - pub(crate) fn compile_scoped( - &mut self, - source: Source, - scope: Vec, - ) -> Result { + pub(crate) fn compile_scoped(&mut self, source: Source, scope: Vec) -> Result { use crate::codegen::compile_scoped; tracing::debug!("Parsing Nix expression for scoped import"); @@ -262,7 +262,9 @@ impl Ctx { ); #[allow(clippy::unwrap_used)] - let root = self.downgrade_ctx(Some(scope)).downgrade(root.tree().expr().unwrap())?; + let root = self + .downgrade_ctx(Some(scope)) + .downgrade(root.tree().expr().unwrap())?; tracing::debug!("Generating JavaScript code for scoped import"); let code = compile_scoped(self.get_ir(root), self); @@ -275,8 +277,11 @@ impl CodegenContext for Ctx { fn get_ir(&self, id: ExprId) -> &Ir { self.irs.get(id.0).expect("ExprId out of bounds") } - fn get_sym(&self, id: SymId) -> &str { - self.symbols.resolve(id).expect("SymId out of bounds") + fn get_sym(&self, id: SymId) -> Symbol<'_> { + self.symbols + .resolve(id) + .expect("SymId out of bounds") + .into() } fn get_current_dir(&self) -> &std::path::Path { self.get_current_dir() @@ -420,7 +425,7 @@ impl DowngradeContext for DowngradeCtx<'_> { self.ctx.symbols.get_or_intern(sym) } - fn get_sym(&self, id: SymId) -> &str { + fn get_sym(&self, id: SymId) -> Symbol<'_> { self.ctx.get_sym(id) } diff --git a/nix-js/src/ir/utils.rs b/nix-js/src/downgrade.rs similarity index 62% rename from nix-js/src/ir/utils.rs rename to nix-js/src/downgrade.rs index a149e43..42020d8 100644 --- a/nix-js/src/ir/utils.rs +++ b/nix-js/src/downgrade.rs @@ -5,14 +5,488 @@ use hashbrown::hash_map::Entry; use hashbrown::{HashMap, HashSet}; use itertools::Itertools as _; use rnix::TextRange; -use rnix::ast::{self, HasEntry}; +use rnix::ast::{self, AstToken, Expr, HasEntry}; use rowan::ast::AstNode; -use crate::error::{Error, Result}; -use crate::ir::{Attr, AttrSet, ConcatStrings, ExprId, Select, Str, SymId}; -use crate::value::format_symbol; +use crate::error::{Error, Result, Source}; +use crate::ir::*; +use crate::value::Symbol; -use super::*; +pub trait DowngradeContext { + fn downgrade(self, expr: rnix::ast::Expr) -> Result; + + fn new_expr(&mut self, expr: Ir) -> ExprId; + fn new_arg(&mut self, span: TextRange) -> ExprId; + fn maybe_thunk(&mut self, id: ExprId) -> ExprId; + + fn new_sym(&mut self, sym: String) -> SymId; + fn get_sym(&self, id: SymId) -> Symbol<'_>; + fn lookup(&mut self, sym: SymId, span: TextRange) -> Result; + + fn get_ir(&self, id: ExprId) -> &Ir; + fn replace_ir(&mut self, id: ExprId, expr: Ir); + fn reserve_slots(&mut self, slots: usize) -> impl Iterator + Clone + use; + fn get_current_source(&self) -> Source; + + fn with_param_scope(&mut self, param: SymId, arg: ExprId, f: F) -> R + where + F: FnOnce(&mut Self) -> R; + fn with_let_scope(&mut self, bindings: HashMap, f: F) -> R + where + F: FnOnce(&mut Self) -> R; + fn with_with_scope(&mut self, namespace: ExprId, f: F) -> R + where + F: FnOnce(&mut Self) -> R; + + fn push_thunk_scope(&mut self); + fn pop_thunk_scope(&mut self) -> Vec<(ExprId, ExprId)>; + fn register_thunk(&mut self, slot: ExprId, inner: ExprId); +} + +pub trait Downgrade { + fn downgrade(self, ctx: &mut Ctx) -> Result; +} + +impl Downgrade for Expr { + fn downgrade(self, ctx: &mut Ctx) -> Result { + use Expr::*; + match self { + Apply(apply) => apply.downgrade(ctx), + Assert(assert) => assert.downgrade(ctx), + Error(error) => { + let span = error.syntax().text_range(); + Err(self::Error::downgrade_error( + error.to_string(), + ctx.get_current_source(), + span, + )) + } + IfElse(ifelse) => ifelse.downgrade(ctx), + Select(select) => select.downgrade(ctx), + Str(str) => str.downgrade(ctx), + Path(path) => path.downgrade(ctx), + Literal(lit) => lit.downgrade(ctx), + Lambda(lambda) => lambda.downgrade(ctx), + LegacyLet(let_) => let_.downgrade(ctx), + LetIn(letin) => letin.downgrade(ctx), + List(list) => list.downgrade(ctx), + BinOp(op) => op.downgrade(ctx), + AttrSet(attrs) => attrs.downgrade(ctx), + UnaryOp(op) => op.downgrade(ctx), + Ident(ident) => ident.downgrade(ctx), + With(with) => with.downgrade(ctx), + HasAttr(has) => has.downgrade(ctx), + Paren(paren) => paren.expr().unwrap().downgrade(ctx), + Root(root) => root.expr().unwrap().downgrade(ctx), + } + } +} + +impl Downgrade for ast::Assert { + fn downgrade(self, ctx: &mut Ctx) -> Result { + let assertion = self.condition().unwrap(); + let assertion_raw = assertion.to_string(); + let assertion = assertion.downgrade(ctx)?; + let expr = self.body().unwrap().downgrade(ctx)?; + let span = self.syntax().text_range(); + Ok(ctx.new_expr( + Assert { + assertion, + expr, + assertion_raw, + span, + } + .to_ir(), + )) + } +} + +impl Downgrade for ast::IfElse { + fn downgrade(self, ctx: &mut Ctx) -> Result { + let cond = self.condition().unwrap().downgrade(ctx)?; + let consq = self.body().unwrap().downgrade(ctx)?; + let alter = self.else_body().unwrap().downgrade(ctx)?; + let span = self.syntax().text_range(); + Ok(ctx.new_expr( + If { + cond, + consq, + alter, + span, + } + .to_ir(), + )) + } +} + +impl Downgrade for ast::Path { + fn downgrade(self, ctx: &mut Ctx) -> Result { + let span = self.syntax().text_range(); + let parts = self + .parts() + .map(|part| match part { + ast::InterpolPart::Literal(lit) => Ok(ctx.new_expr( + Str { + val: lit.to_string(), + span: lit.syntax().text_range(), + } + .to_ir(), + )), + ast::InterpolPart::Interpolation(interpol) => { + interpol.expr().unwrap().downgrade(ctx) + } + }) + .collect::>>()?; + + let expr = if parts.len() == 1 { + let part = parts.into_iter().next().unwrap(); + if let &Ir::Str(Str { ref val, span }) = ctx.get_ir(part) + && let Some(path) = val.strip_prefix("<").map(|path| &path[..path.len() - 1]) + { + ctx.replace_ir( + part, + Str { + val: path.to_string(), + span, + } + .to_ir(), + ); + let sym = ctx.new_sym("findFile".into()); + let find_file = ctx.new_expr(Builtin { inner: sym, span }.to_ir()); + let sym = ctx.new_sym("nixPath".into()); + let nix_path = ctx.new_expr(Builtin { inner: sym, span }.to_ir()); + let call = ctx.new_expr( + Call { + func: find_file, + arg: nix_path, + span, + } + .to_ir(), + ); + return Ok(ctx.new_expr( + Call { + func: call, + arg: part, + span, + } + .to_ir(), + )); + } else { + part + } + } else { + ctx.new_expr( + ConcatStrings { + parts, + span, + force_string: false, + } + .to_ir(), + ) + }; + Ok(ctx.new_expr(Path { expr, span }.to_ir())) + } +} + +impl Downgrade for ast::Str { + fn downgrade(self, ctx: &mut Ctx) -> Result { + let span = self.syntax().text_range(); + let normalized = self.normalized_parts(); + let is_single_literal = normalized.len() == 1 + && matches!(normalized.first(), Some(ast::InterpolPart::Literal(_))); + + let parts = normalized + .into_iter() + .map(|part| match part { + ast::InterpolPart::Literal(lit) => Ok(ctx.new_expr(Str { val: lit, span }.to_ir())), + ast::InterpolPart::Interpolation(interpol) => { + let inner = interpol.expr().unwrap().downgrade(ctx)?; + Ok(ctx.maybe_thunk(inner)) + } + }) + .collect::>>()?; + + Ok(if is_single_literal { + parts.into_iter().next().unwrap() + } else { + ctx.new_expr( + ConcatStrings { + parts, + span, + force_string: true, + } + .to_ir(), + ) + }) + } +} + +impl Downgrade for ast::Literal { + fn downgrade(self, ctx: &mut Ctx) -> Result { + let span = self.syntax().text_range(); + Ok(ctx.new_expr(match self.kind() { + ast::LiteralKind::Integer(int) => Int { + inner: int.value().unwrap(), + span, + } + .to_ir(), + ast::LiteralKind::Float(float) => Float { + inner: float.value().unwrap(), + span, + } + .to_ir(), + ast::LiteralKind::Uri(uri) => Str { + val: uri.to_string(), + span, + } + .to_ir(), + })) + } +} + +impl Downgrade for ast::Ident { + fn downgrade(self, ctx: &mut Ctx) -> Result { + let text = self.ident_token().unwrap().to_string(); + let span = self.syntax().text_range(); + + if text == "__curPos" { + return Ok(ctx.new_expr(CurPos { span }.to_ir())); + } + + let sym = ctx.new_sym(text); + ctx.lookup(sym, span) + } +} + +impl Downgrade for ast::AttrSet { + fn downgrade(self, ctx: &mut Ctx) -> Result { + let rec = self.rec_token().is_some(); + let span = self.syntax().text_range(); + + if !rec { + let attrs = downgrade_attrs(self, ctx)?; + return Ok(ctx.new_expr(attrs.to_ir())); + } + + // rec { a = 1; b = a; } => let a = 1; b = a; in { inherit a b; } + let entries: Vec<_> = self.entries().collect(); + downgrade_rec_bindings(entries, ctx, span) + } +} + +/// Downgrades a list. +impl Downgrade for ast::List { + fn downgrade(self, ctx: &mut Ctx) -> Result { + let items = self + .items() + .map(|item| { + let id = item.downgrade(ctx)?; + Ok(ctx.maybe_thunk(id)) + }) + .collect::>()?; + let span = self.syntax().text_range(); + Ok(ctx.new_expr(List { items, span }.to_ir())) + } +} + +/// Downgrades a binary operation. +impl Downgrade for ast::BinOp { + fn downgrade(self, ctx: &mut Ctx) -> Result { + let lhs = self.lhs().unwrap().downgrade(ctx)?; + let rhs = self.rhs().unwrap().downgrade(ctx)?; + let kind = self.operator().unwrap().into(); + let span = self.syntax().text_range(); + Ok(ctx.new_expr( + BinOp { + lhs, + rhs, + kind, + span, + } + .to_ir(), + )) + } +} + +/// Downgrades a "has attribute" (`?`) expression. +impl Downgrade for ast::HasAttr { + fn downgrade(self, ctx: &mut Ctx) -> Result { + let lhs = self.expr().unwrap().downgrade(ctx)?; + let rhs = downgrade_attrpath(self.attrpath().unwrap(), ctx)?; + let span = self.syntax().text_range(); + Ok(ctx.new_expr(HasAttr { lhs, rhs, span }.to_ir())) + } +} + +/// Downgrades a unary operation. +impl Downgrade for ast::UnaryOp { + fn downgrade(self, ctx: &mut Ctx) -> Result { + let rhs = self.expr().unwrap().downgrade(ctx)?; + let kind = self.operator().unwrap().into(); + let span = self.syntax().text_range(); + Ok(ctx.new_expr(UnOp { rhs, kind, span }.to_ir())) + } +} + +/// Downgrades an attribute selection (`.`). +impl Downgrade for ast::Select { + fn downgrade(self, ctx: &mut Ctx) -> Result { + let expr = self.expr().unwrap().downgrade(ctx)?; + let attrpath = downgrade_attrpath(self.attrpath().unwrap(), ctx)?; + let default = if let Some(default) = self.default_expr() { + let default_expr = default.downgrade(ctx)?; + Some(ctx.maybe_thunk(default_expr)) + } else { + None + }; + let span = self.syntax().text_range(); + Ok(ctx.new_expr( + Select { + expr, + attrpath, + default, + span, + } + .to_ir(), + )) + } +} + +/// Downgrades a `legacy let`, which is essentially a recursive attribute set. +/// The body of the `let` is accessed via `let.body`. +impl Downgrade for ast::LegacyLet { + fn downgrade(self, ctx: &mut Ctx) -> Result { + let span = self.syntax().text_range(); + 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(), + span, + }; + + for sym in binding_keys { + let expr = ctx.lookup(*sym, rnix::TextRange::default())?; + attrs.stcs.insert(*sym, (expr, rnix::TextRange::default())); + } + + Ok(ctx.new_expr(attrs.to_ir())) + })?; + + let body_sym = ctx.new_sym("body".to_string()); + let select = Select { + expr: attrset_expr, + attrpath: vec![Attr::Str(body_sym, rnix::TextRange::default())], + default: None, + span, + }; + + Ok(ctx.new_expr(select.to_ir())) + } +} + +/// Downgrades a `let ... in ...` expression. +impl Downgrade for ast::LetIn { + fn downgrade(self, ctx: &mut Ctx) -> Result { + let entries: Vec<_> = self.entries().collect(); + let body_expr = self.body().unwrap(); + let span = self.syntax().text_range(); + + downgrade_let_bindings(entries, ctx, span, |ctx, _binding_keys| { + body_expr.downgrade(ctx) + }) + } +} + +/// Downgrades a `with` expression. +impl Downgrade for ast::With { + fn downgrade(self, ctx: &mut Ctx) -> Result { + // with namespace; expr + let namespace = self.namespace().unwrap().downgrade(ctx)?; + + // Downgrade body in With scope + let expr = ctx.with_with_scope(namespace, |ctx| self.body().unwrap().downgrade(ctx))?; + + Ok(expr) + } +} + +/// Downgrades a lambda (function) expression. +/// This involves desugaring pattern-matching arguments into `let` bindings. +impl Downgrade for ast::Lambda { + fn downgrade(self, ctx: &mut Ctx) -> Result { + let raw_param = self.param().unwrap(); + let arg = ctx.new_arg(raw_param.syntax().text_range()); + + ctx.push_thunk_scope(); + + let param; + let body; + + match raw_param { + ast::Param::IdentParam(id) => { + // Simple case: `x: body` + let param_sym = ctx.new_sym(id.to_string()); + param = None; + + // Downgrade body in Param scope + body = ctx + .with_param_scope(param_sym, arg, |ctx| self.body().unwrap().downgrade(ctx))?; + } + ast::Param::Pattern(pattern) => { + let alias = pattern + .pat_bind() + .map(|alias| ctx.new_sym(alias.ident().unwrap().to_string())); + + let ellipsis = pattern.ellipsis_token().is_some(); + let pat_entries = pattern.pat_entries(); + + let PatternBindings { + body: inner_body, + required, + optional, + } = downgrade_pattern_bindings(pat_entries, alias, arg, ctx, |ctx, _| { + self.body().unwrap().downgrade(ctx) + })?; + + param = Some(Param { + required, + optional, + ellipsis, + }); + + body = inner_body; + } + } + + let thunks = ctx.pop_thunk_scope(); + let span = self.syntax().text_range(); + Ok(ctx.new_expr( + Func { + body, + param, + arg, + thunks, + span, + } + .to_ir(), + )) + } +} + +/// Downgrades a function application. +/// In Nix, function application is left-associative, so `f a b` should be parsed as `((f a) b)`. +/// Each Apply node represents a single function call with one argument. +impl Downgrade for ast::Apply { + fn downgrade(self, ctx: &mut Ctx) -> Result { + let func = self.lambda().unwrap().downgrade(ctx)?; + let arg = self.argument().unwrap().downgrade(ctx)?; + let arg = ctx.maybe_thunk(arg); + let span = self.syntax().text_range(); + Ok(ctx.new_expr(Call { func, arg, span }.to_ir())) + } +} enum PendingValue { Expr(ast::Expr), @@ -321,10 +795,7 @@ impl PendingAttrSet { if self.stcs.contains_key(&sym) { return Err(Error::downgrade_error( - format!( - "attribute '{}' already defined", - format_symbol(ctx.get_sym(sym)) - ), + format!("attribute '{}' already defined", ctx.get_sym(sym)), ctx.get_current_source(), span, )); @@ -401,7 +872,7 @@ fn make_attrpath_value_entry(path: Vec, value: ast::Expr) -> ast::Ent } /// Downgrades the entries of a non-recursive attribute set. -pub fn downgrade_attrs( +fn downgrade_attrs( attrs: impl ast::HasEntry + AstNode, ctx: &mut impl DowngradeContext, ) -> Result { @@ -412,7 +883,7 @@ pub fn downgrade_attrs( } /// Downgrades a single attribute key (part of an attribute path). -pub fn downgrade_attr(attr: ast::Attr, ctx: &mut impl DowngradeContext) -> Result { +fn downgrade_attr(attr: ast::Attr, ctx: &mut impl DowngradeContext) -> Result { use ast::Attr::*; use ast::InterpolPart::*; match attr { @@ -462,7 +933,7 @@ pub fn downgrade_attr(attr: ast::Attr, ctx: &mut impl DowngradeContext) -> Resul } /// Downgrades an attribute path (e.g., `a.b."${c}".d`) into a `Vec`. -pub fn downgrade_attrpath( +fn downgrade_attrpath( attrpath: ast::Attrpath, ctx: &mut impl DowngradeContext, ) -> Result> { @@ -472,14 +943,14 @@ pub fn downgrade_attrpath( .collect::>>() } -pub struct PatternBindings { - pub body: ExprId, - pub required: Vec<(SymId, TextRange)>, - pub optional: Vec<(SymId, TextRange)>, +struct PatternBindings { + body: ExprId, + required: Vec<(SymId, TextRange)>, + optional: Vec<(SymId, TextRange)>, } /// Helper function for Lambda pattern parameters. -pub fn downgrade_pattern_bindings( +fn downgrade_pattern_bindings( pat_entries: impl Iterator, alias: Option, arg: ExprId, @@ -507,7 +978,7 @@ where if !seen_params.insert(sym) { return Err(Error::downgrade_error( - format!("duplicate parameter '{}'", format_symbol(ctx.get_sym(sym))), + format!("duplicate parameter '{}'", ctx.get_sym(sym)), ctx.get_current_source(), span, )); @@ -603,7 +1074,7 @@ where /// 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( +fn downgrade_let_bindings( entries: Vec, ctx: &mut Ctx, span: TextRange, @@ -619,7 +1090,7 @@ where } /// Downgrades a `rec` attribute set. -pub fn downgrade_rec_bindings( +fn downgrade_rec_bindings( entries: Vec, ctx: &mut Ctx, span: TextRange, @@ -691,7 +1162,7 @@ where } else { return Err(Error::internal(format!( "binding '{}' not found", - format_symbol(ctx.get_sym(sym)) + ctx.get_sym(sym) ))); } } @@ -733,10 +1204,7 @@ fn collect_binding_syms( 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)) - ), + format!("attribute '{}' already defined", ctx.get_sym(*sym)), ctx.get_current_source(), *span, )); diff --git a/nix-js/src/ir.rs b/nix-js/src/ir.rs index 7574a00..30b48cf 100644 --- a/nix-js/src/ir.rs +++ b/nix-js/src/ir.rs @@ -3,47 +3,8 @@ use hashbrown::HashMap; use rnix::{TextRange, ast}; use string_interner::symbol::SymbolU32; -use crate::error::{Result, Source}; use nix_js_macros::ir; -mod downgrade; -mod utils; - -use utils::*; - -pub use downgrade::Downgrade; - -pub trait DowngradeContext { - fn downgrade(self, expr: rnix::ast::Expr) -> Result; - - fn new_expr(&mut self, expr: Ir) -> ExprId; - fn new_arg(&mut self, span: TextRange) -> ExprId; - fn maybe_thunk(&mut self, id: ExprId) -> ExprId; - - fn new_sym(&mut self, sym: String) -> SymId; - fn get_sym(&self, id: SymId) -> &str; - fn lookup(&mut self, sym: SymId, span: TextRange) -> Result; - - fn get_ir(&self, id: ExprId) -> &Ir; - fn replace_ir(&mut self, id: ExprId, expr: Ir); - fn reserve_slots(&mut self, slots: usize) -> impl Iterator + Clone + use; - fn get_current_source(&self) -> Source; - - fn with_param_scope(&mut self, param: SymId, arg: ExprId, f: F) -> R - where - F: FnOnce(&mut Self) -> R; - fn with_let_scope(&mut self, bindings: HashMap, f: F) -> R - where - F: FnOnce(&mut Self) -> R; - fn with_with_scope(&mut self, namespace: ExprId, f: F) -> R - where - F: FnOnce(&mut Self) -> R; - - fn push_thunk_scope(&mut self); - fn pop_thunk_scope(&mut self) -> Vec<(ExprId, ExprId)>; - fn register_thunk(&mut self, slot: ExprId, inner: ExprId); -} - ir! { Ir, diff --git a/nix-js/src/ir/downgrade.rs b/nix-js/src/ir/downgrade.rs deleted file mode 100644 index 6301305..0000000 --- a/nix-js/src/ir/downgrade.rs +++ /dev/null @@ -1,453 +0,0 @@ -// Assume no parse error -#![allow(clippy::unwrap_used)] - -use rnix::ast::{self, AstToken, Expr, HasEntry}; -use rowan::ast::AstNode; - -use super::*; -use crate::error::{Error, Result}; - -pub trait Downgrade { - fn downgrade(self, ctx: &mut Ctx) -> Result; -} - -impl Downgrade for Expr { - fn downgrade(self, ctx: &mut Ctx) -> Result { - use Expr::*; - match self { - Apply(apply) => apply.downgrade(ctx), - Assert(assert) => assert.downgrade(ctx), - Error(error) => { - let span = error.syntax().text_range(); - Err(self::Error::downgrade_error( - error.to_string(), - ctx.get_current_source(), - span, - )) - } - IfElse(ifelse) => ifelse.downgrade(ctx), - Select(select) => select.downgrade(ctx), - Str(str) => str.downgrade(ctx), - Path(path) => path.downgrade(ctx), - Literal(lit) => lit.downgrade(ctx), - Lambda(lambda) => lambda.downgrade(ctx), - LegacyLet(let_) => let_.downgrade(ctx), - LetIn(letin) => letin.downgrade(ctx), - List(list) => list.downgrade(ctx), - BinOp(op) => op.downgrade(ctx), - AttrSet(attrs) => attrs.downgrade(ctx), - UnaryOp(op) => op.downgrade(ctx), - Ident(ident) => ident.downgrade(ctx), - With(with) => with.downgrade(ctx), - HasAttr(has) => has.downgrade(ctx), - Paren(paren) => paren.expr().unwrap().downgrade(ctx), - Root(root) => root.expr().unwrap().downgrade(ctx), - } - } -} - -impl Downgrade for ast::Assert { - fn downgrade(self, ctx: &mut Ctx) -> Result { - let assertion = self.condition().unwrap(); - let assertion_raw = assertion.to_string(); - let assertion = assertion.downgrade(ctx)?; - let expr = self.body().unwrap().downgrade(ctx)?; - let span = self.syntax().text_range(); - Ok(ctx.new_expr( - Assert { - assertion, - expr, - assertion_raw, - span, - } - .to_ir(), - )) - } -} - -impl Downgrade for ast::IfElse { - fn downgrade(self, ctx: &mut Ctx) -> Result { - let cond = self.condition().unwrap().downgrade(ctx)?; - let consq = self.body().unwrap().downgrade(ctx)?; - let alter = self.else_body().unwrap().downgrade(ctx)?; - let span = self.syntax().text_range(); - Ok(ctx.new_expr( - If { - cond, - consq, - alter, - span, - } - .to_ir(), - )) - } -} - -impl Downgrade for ast::Path { - fn downgrade(self, ctx: &mut Ctx) -> Result { - let span = self.syntax().text_range(); - let parts = self - .parts() - .map(|part| match part { - ast::InterpolPart::Literal(lit) => Ok(ctx.new_expr( - Str { - val: lit.to_string(), - span: lit.syntax().text_range(), - } - .to_ir(), - )), - ast::InterpolPart::Interpolation(interpol) => { - interpol.expr().unwrap().downgrade(ctx) - } - }) - .collect::>>()?; - - let expr = if parts.len() == 1 { - let part = parts.into_iter().next().unwrap(); - if let &Ir::Str(Str { ref val, span }) = ctx.get_ir(part) - && let Some(path) = val.strip_prefix("<").map(|path| &path[..path.len() - 1]) - { - ctx.replace_ir( - part, - Str { - val: path.to_string(), - span, - } - .to_ir(), - ); - let sym = ctx.new_sym("findFile".into()); - let find_file = ctx.new_expr(Builtin { inner: sym, span }.to_ir()); - let sym = ctx.new_sym("nixPath".into()); - let nix_path = ctx.new_expr(Builtin { inner: sym, span }.to_ir()); - let call = ctx.new_expr( - Call { - func: find_file, - arg: nix_path, - span, - } - .to_ir(), - ); - return Ok(ctx.new_expr( - Call { - func: call, - arg: part, - span, - } - .to_ir(), - )); - } else { - part - } - } else { - ctx.new_expr( - ConcatStrings { - parts, - span, - force_string: false, - } - .to_ir(), - ) - }; - Ok(ctx.new_expr(Path { expr, span }.to_ir())) - } -} - -impl Downgrade for ast::Str { - fn downgrade(self, ctx: &mut Ctx) -> Result { - let span = self.syntax().text_range(); - let normalized = self.normalized_parts(); - let is_single_literal = normalized.len() == 1 - && matches!(normalized.first(), Some(ast::InterpolPart::Literal(_))); - - let parts = normalized - .into_iter() - .map(|part| match part { - ast::InterpolPart::Literal(lit) => Ok(ctx.new_expr(Str { val: lit, span }.to_ir())), - ast::InterpolPart::Interpolation(interpol) => { - let inner = interpol.expr().unwrap().downgrade(ctx)?; - Ok(ctx.maybe_thunk(inner)) - } - }) - .collect::>>()?; - - Ok(if is_single_literal { - parts.into_iter().next().unwrap() - } else { - ctx.new_expr( - ConcatStrings { - parts, - span, - force_string: true, - } - .to_ir(), - ) - }) - } -} - -impl Downgrade for ast::Literal { - fn downgrade(self, ctx: &mut Ctx) -> Result { - let span = self.syntax().text_range(); - Ok(ctx.new_expr(match self.kind() { - ast::LiteralKind::Integer(int) => Int { - inner: int.value().unwrap(), - span, - } - .to_ir(), - ast::LiteralKind::Float(float) => Float { - inner: float.value().unwrap(), - span, - } - .to_ir(), - ast::LiteralKind::Uri(uri) => Str { - val: uri.to_string(), - span, - } - .to_ir(), - })) - } -} - -impl Downgrade for ast::Ident { - fn downgrade(self, ctx: &mut Ctx) -> Result { - let text = self.ident_token().unwrap().to_string(); - let span = self.syntax().text_range(); - - if text == "__curPos" { - return Ok(ctx.new_expr(CurPos { span }.to_ir())); - } - - let sym = ctx.new_sym(text); - ctx.lookup(sym, span) - } -} - -impl Downgrade for ast::AttrSet { - fn downgrade(self, ctx: &mut Ctx) -> Result { - let rec = self.rec_token().is_some(); - let span = self.syntax().text_range(); - - if !rec { - let attrs = downgrade_attrs(self, ctx)?; - return Ok(ctx.new_expr(attrs.to_ir())); - } - - // rec { a = 1; b = a; } => let a = 1; b = a; in { inherit a b; } - let entries: Vec<_> = self.entries().collect(); - downgrade_rec_bindings(entries, ctx, span) - } -} - -/// Downgrades a list. -impl Downgrade for ast::List { - fn downgrade(self, ctx: &mut Ctx) -> Result { - let items = self - .items() - .map(|item| { - let id = item.downgrade(ctx)?; - Ok(ctx.maybe_thunk(id)) - }) - .collect::>()?; - let span = self.syntax().text_range(); - Ok(ctx.new_expr(List { items, span }.to_ir())) - } -} - -/// Downgrades a binary operation. -impl Downgrade for ast::BinOp { - fn downgrade(self, ctx: &mut Ctx) -> Result { - let lhs = self.lhs().unwrap().downgrade(ctx)?; - let rhs = self.rhs().unwrap().downgrade(ctx)?; - let kind = self.operator().unwrap().into(); - let span = self.syntax().text_range(); - Ok(ctx.new_expr( - BinOp { - lhs, - rhs, - kind, - span, - } - .to_ir(), - )) - } -} - -/// Downgrades a "has attribute" (`?`) expression. -impl Downgrade for ast::HasAttr { - fn downgrade(self, ctx: &mut Ctx) -> Result { - let lhs = self.expr().unwrap().downgrade(ctx)?; - let rhs = downgrade_attrpath(self.attrpath().unwrap(), ctx)?; - let span = self.syntax().text_range(); - Ok(ctx.new_expr(HasAttr { lhs, rhs, span }.to_ir())) - } -} - -/// Downgrades a unary operation. -impl Downgrade for ast::UnaryOp { - fn downgrade(self, ctx: &mut Ctx) -> Result { - let rhs = self.expr().unwrap().downgrade(ctx)?; - let kind = self.operator().unwrap().into(); - let span = self.syntax().text_range(); - Ok(ctx.new_expr(UnOp { rhs, kind, span }.to_ir())) - } -} - -/// Downgrades an attribute selection (`.`). -impl Downgrade for ast::Select { - fn downgrade(self, ctx: &mut Ctx) -> Result { - let expr = self.expr().unwrap().downgrade(ctx)?; - let attrpath = downgrade_attrpath(self.attrpath().unwrap(), ctx)?; - let default = if let Some(default) = self.default_expr() { - let default_expr = default.downgrade(ctx)?; - Some(ctx.maybe_thunk(default_expr)) - } else { - None - }; - let span = self.syntax().text_range(); - Ok(ctx.new_expr( - Select { - expr, - attrpath, - default, - span, - } - .to_ir(), - )) - } -} - -/// Downgrades a `legacy let`, which is essentially a recursive attribute set. -/// The body of the `let` is accessed via `let.body`. -impl Downgrade for ast::LegacyLet { - fn downgrade(self, ctx: &mut Ctx) -> Result { - let span = self.syntax().text_range(); - 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(), - span, - }; - - for sym in binding_keys { - let expr = ctx.lookup(*sym, rnix::TextRange::default())?; - attrs.stcs.insert(*sym, (expr, rnix::TextRange::default())); - } - - Ok(ctx.new_expr(attrs.to_ir())) - })?; - - let body_sym = ctx.new_sym("body".to_string()); - let select = Select { - expr: attrset_expr, - attrpath: vec![Attr::Str(body_sym, rnix::TextRange::default())], - default: None, - span, - }; - - Ok(ctx.new_expr(select.to_ir())) - } -} - -/// Downgrades a `let ... in ...` expression. -impl Downgrade for ast::LetIn { - fn downgrade(self, ctx: &mut Ctx) -> Result { - let entries: Vec<_> = self.entries().collect(); - let body_expr = self.body().unwrap(); - let span = self.syntax().text_range(); - - downgrade_let_bindings(entries, ctx, span, |ctx, _binding_keys| { - body_expr.downgrade(ctx) - }) - } -} - -/// Downgrades a `with` expression. -impl Downgrade for ast::With { - fn downgrade(self, ctx: &mut Ctx) -> Result { - // with namespace; expr - let namespace = self.namespace().unwrap().downgrade(ctx)?; - - // Downgrade body in With scope - let expr = ctx.with_with_scope(namespace, |ctx| self.body().unwrap().downgrade(ctx))?; - - Ok(expr) - } -} - -/// Downgrades a lambda (function) expression. -/// This involves desugaring pattern-matching arguments into `let` bindings. -impl Downgrade for ast::Lambda { - fn downgrade(self, ctx: &mut Ctx) -> Result { - let raw_param = self.param().unwrap(); - let arg = ctx.new_arg(raw_param.syntax().text_range()); - - ctx.push_thunk_scope(); - - let param; - let body; - - match raw_param { - ast::Param::IdentParam(id) => { - // Simple case: `x: body` - let param_sym = ctx.new_sym(id.to_string()); - param = None; - - // Downgrade body in Param scope - body = ctx - .with_param_scope(param_sym, arg, |ctx| self.body().unwrap().downgrade(ctx))?; - } - ast::Param::Pattern(pattern) => { - let alias = pattern - .pat_bind() - .map(|alias| ctx.new_sym(alias.ident().unwrap().to_string())); - - let ellipsis = pattern.ellipsis_token().is_some(); - let pat_entries = pattern.pat_entries(); - - let PatternBindings { - body: inner_body, - required, - optional, - } = downgrade_pattern_bindings(pat_entries, alias, arg, ctx, |ctx, _| { - self.body().unwrap().downgrade(ctx) - })?; - - param = Some(Param { - required, - optional, - ellipsis, - }); - - body = inner_body; - } - } - - let thunks = ctx.pop_thunk_scope(); - let span = self.syntax().text_range(); - Ok(ctx.new_expr( - Func { - body, - param, - arg, - thunks, - span, - } - .to_ir(), - )) - } -} - -/// Downgrades a function application. -/// In Nix, function application is left-associative, so `f a b` should be parsed as `((f a) b)`. -/// Each Apply node represents a single function call with one argument. -impl Downgrade for ast::Apply { - fn downgrade(self, ctx: &mut Ctx) -> Result { - let func = self.lambda().unwrap().downgrade(ctx)?; - let arg = self.argument().unwrap().downgrade(ctx)?; - let arg = ctx.maybe_thunk(arg); - let span = self.syntax().text_range(); - Ok(ctx.new_expr(Call { func, arg, span }.to_ir())) - } -} diff --git a/nix-js/src/lib.rs b/nix-js/src/lib.rs index 2f729c1..c627c50 100644 --- a/nix-js/src/lib.rs +++ b/nix-js/src/lib.rs @@ -6,6 +6,7 @@ pub mod logging; pub mod value; mod codegen; +mod downgrade; mod fetcher; mod ir; mod nar; diff --git a/nix-js/src/runtime.rs b/nix-js/src/runtime.rs index 9e22c10..496cca3 100644 --- a/nix-js/src/runtime.rs +++ b/nix-js/src/runtime.rs @@ -332,7 +332,7 @@ fn to_value<'a>( let val = val.get(scope, key).expect("infallible operation"); let key = key.to_rust_string_lossy(scope); ( - Symbol::new(key), + Symbol::from(key), to_value( val, scope, diff --git a/nix-js/src/value.rs b/nix-js/src/value.rs index 9165704..ad63573 100644 --- a/nix-js/src/value.rs +++ b/nix-js/src/value.rs @@ -10,11 +10,19 @@ use derive_more::{Constructor, IsVariant, Unwrap}; /// Represents a Nix symbol, which is used as a key in attribute sets. #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Constructor)] -pub struct Symbol(String); +pub struct Symbol<'a>(Cow<'a, str>); -impl> From for Symbol { - fn from(value: T) -> Self { - Symbol(value.into()) +pub type StaticSymbol = Symbol<'static>; + +impl From for Symbol<'_> { + fn from(value: String) -> Self { + Symbol(Cow::Owned(value)) + } +} + +impl<'a> From<&'a str> for Symbol<'a> { + fn from(value: &'a str) -> Self { + Symbol(Cow::Borrowed(value)) } } @@ -28,7 +36,7 @@ pub fn format_symbol<'a>(sym: impl Into>) -> Cow<'a, str> { } } -impl Display for Symbol { +impl Display for Symbol<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { if self.normal() { write!(f, "{}", self.0) @@ -38,7 +46,7 @@ impl Display for Symbol { } } -impl Symbol { +impl Symbol<'_> { const NORMAL_REGEX: ere::Regex<1> = ere::compile_regex!("^[a-zA-Z_][a-zA-Z0-9_'-]*$"); /// Checks if the symbol is a "normal" identifier that doesn't require quotes. fn normal(&self) -> bool { @@ -46,45 +54,33 @@ impl Symbol { } } -impl Deref for Symbol { +impl Deref for Symbol<'_> { type Target = str; fn deref(&self) -> &Self::Target { &self.0 } } -impl Symbol { - /// Consumes the `Symbol`, returning its inner `String`. - pub fn into_inner(self) -> String { - self.0 - } - - /// Returns a reference to the inner `String`. - pub fn as_inner(&self) -> &String { - &self.0 - } -} - /// Represents a Nix attribute set, which is a map from symbols to values. #[derive(Constructor, Default, Clone, PartialEq)] pub struct AttrSet { - data: BTreeMap, + data: BTreeMap, } impl AttrSet { /// Gets a value by key (string or Symbol). - pub fn get(&self, key: impl Into) -> Option<&Value> { + pub fn get<'a, 'sym: 'a>(&'a self, key: impl Into>) -> Option<&'a Value> { self.data.get(&key.into()) } /// Checks if a key exists in the attribute set. - pub fn contains_key(&self, key: impl Into) -> bool { + pub fn contains_key<'a, 'sym: 'a>(&'a self, key: impl Into>) -> bool { self.data.contains_key(&key.into()) } } impl Deref for AttrSet { - type Target = BTreeMap; + type Target = BTreeMap; fn deref(&self) -> &Self::Target { &self.data }