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