--- a/crates/nix-eval/src/value.rs +++ b/crates/nix-eval/src/value.rs @@ -71,6 +71,9 @@ struct PathDisplay<'i>(&'i [Index]); impl fmt::Display for PathDisplay<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if !matches!(self.0.first(), Some(Index::Var(_))) { + write!(f, "")?; + } for i in self.0 { write!(f, "{i}")?; } @@ -78,56 +81,41 @@ } } struct ValueInner { - full_path: Option>, + full_path: Vec, session: NixSession, - value: Option, + value: u32, } #[derive(Clone)] pub struct Value(Arc); impl Value { - fn root(session: NixSession) -> Self { - Self(Arc::new(ValueInner { - full_path: Some(vec![]), - session, - value: None, - })) - } async fn new(session: NixSession, query: &str) -> Result { let vid = session.0.lock().await.execute_assign(query).await?; Ok(Self(Arc::new(ValueInner { - full_path: None, + full_path: vec![], session, - value: Some(vid), + value: vid, }))) } /// Get a top-level binding. /// /// In flake repl session, every output is exposed as top-level binding. - pub async fn binding(session: NixSession, field: &str) -> Result { - Self::root(session).select([Index::var(field)]).await + pub async fn binding(session: NixSession, query: &str) -> Result { + // TODO: Verify that query is a valid variable name + let vid = session.0.lock().await.execute_assign(query).await?; + Ok(Self(Arc::new(ValueInner { + full_path: vec![Index::Var(query.to_owned())], + session, + value: vid, + }))) } - pub async fn select<'a>(&self, name: impl IntoIterator) -> Result { + pub async fn select(&self, name: impl IntoIterator) -> Result { let mut used_fields = Vec::new(); - let mut name = name.into_iter(); + let name = name.into_iter(); let mut full_path = self.0.full_path.clone(); - let mut query = if let Some(id) = self.0.value { - format!("sess_field_{id}") - } else { - let first = name.next(); - if let Some(Index::Var(i)) = first { - if let Some(full_path) = &mut full_path { - full_path.push(Index::Var(i.clone())); - } - i.clone() - } else { - panic!("first path item should be variable, got {first:?}") - } - }; + let mut query = self.sess_field_name(); for v in name { - if let Some(full_path) = &mut full_path { - full_path.push(v.clone()); - } + full_path.push(v.clone()); match v { Index::Var(_) => panic!("var item may only be first"), Index::String(s) => { @@ -144,27 +132,27 @@ let index = Value::new(self.0.session.clone(), &e.out).await?; used_fields.push(index.clone()); query.push('.'); - let index = format!("${{sess_field_{}}}", index.0.value.expect("value")); + let index = format!("${{sess_field_{}}}", index.0.value); query.push_str(&index); } Index::ExprApply(e) => { let index = Value::new(self.0.session.clone(), &e.out).await?; used_fields.push(index.clone()); query.push(' '); - let index = format!("sess_field_{}", index.0.value.expect("value")); + let index = format!("sess_field_{}", index.0.value); query.push_str(&index); query = format!("({query})"); } Index::Pipe(v) => { let index = Value::new(self.0.session.clone(), &v.out).await?; used_fields.push(index.clone()); - let index = format!("sess_field_{}", index.0.value.expect("value")); + let index = format!("sess_field_{}", index.0.value); query = format!("({index} {query})"); } Index::Merge(v) => { let index = Value::new(self.0.session.clone(), &v.out).await?; used_fields.push(index.clone()); - let index = format!("sess_field_{}", index.0.value.expect("value")); + let index = format!("sess_field_{}", index.0.value); query = format!("({query} // {index})"); } } @@ -182,12 +170,11 @@ Ok(Self(Arc::new(ValueInner { full_path, session: self.0.session.clone(), - value: Some(vid), + value: vid, }))) } pub async fn as_json(&self) -> Result { - let id = self.0.value.expect("can't serialize root field"); - let query = format!("sess_field_{id}"); + let query = self.sess_field_name(); self.0 .session .0 @@ -199,9 +186,8 @@ } #[allow(dead_code)] pub async fn has_field(&self, name: &str) -> Result { - let id = self.0.value.expect("can't list root fields"); let key = nixlike::escape_string(name); - let query = format!("sess_field_{id} ? {key}"); + let query = format!("{} ? {key}", self.sess_field_name()); self.0 .session .0 @@ -212,8 +198,7 @@ .map_err(|e| e.context(self.attribute())) } pub async fn list_fields(&self) -> Result> { - let id = self.0.value.expect("can't list root fields"); - let query = format!("builtins.attrNames sess_field_{id}"); + let query = format!("builtins.attrNames {}", self.sess_field_name()); self.0 .session .0 @@ -224,8 +209,7 @@ .map_err(|e| e.context(self.attribute())) } pub async fn type_of(&self) -> Result { - let id = self.0.value.expect("can't list root fields"); - let query = format!("builtins.typeOf sess_field_{id}"); + let query = format!("builtins.typeOf {}", self.sess_field_name()); self.0 .session .0 @@ -240,6 +224,9 @@ let import = Self::new(self.0.session.clone(), "import").await?; Ok(nix_go!(self | import)) } + fn sess_field_name(&self) -> String { + format!("sess_field_{}", self.0.value) + } pub async fn build_maybe_batch( &self, batch: Option, @@ -251,8 +238,7 @@ } } pub async fn build(&self) -> Result> { - let id = self.0.value.expect("can't use build on not-value"); - let query = format!(":b sess_field_{id}"); + let query = format!(":b {}", self.sess_field_name()); let vid = self .0 .session @@ -284,8 +270,7 @@ } /// Weakly convert string-like types (derivation/path/string) to string pub async fn to_string_weak(&self) -> Result { - let id = self.0.value.expect("can't use build on not-value"); - let query = format!("\"${{sess_field_{id}}}\""); + let query = format!("\"${{{}}}\"", self.sess_field_name()); let vid: String = self .0 .session @@ -298,11 +283,7 @@ } fn attribute(&self) -> String { - if let Some(full_path) = &self.0.full_path { - PathDisplay(full_path).to_string() - } else { - "".to_owned() - } + PathDisplay(&self.0.full_path).to_string() } pub(crate) fn session(&self) -> NixSession { @@ -310,16 +291,14 @@ } pub(crate) fn session_field_id(&self) -> u32 { - self.0.value.expect("not root") + self.0.value } } impl Drop for ValueInner { fn drop(&mut self) { - if let Some(id) = self.value { - if let Ok(mut lock) = self.session.0.try_lock() { - lock.free_list.push(id) - } - // Leaked + if let Ok(mut lock) = self.session.0.try_lock() { + lock.free_list.push(self.value) } + // Leaked } }