difftreelog
refactor cleaner toplevel value handling
in: trunk
1 file changed
crates/nix-eval/src/value.rsdiffbeforeafterboth71struct PathDisplay<'i>(&'i [Index]);71struct PathDisplay<'i>(&'i [Index]);72impl fmt::Display for PathDisplay<'_> {72impl fmt::Display for PathDisplay<'_> {73 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {73 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {74 if !matches!(self.0.first(), Some(Index::Var(_))) {75 write!(f, "<unknown>")?;76 }74 for i in self.0 {77 for i in self.0 {75 write!(f, "{i}")?;78 write!(f, "{i}")?;76 }79 }77 Ok(())80 Ok(())78 }81 }79}82}80struct ValueInner {83struct ValueInner {81 full_path: Option<Vec<Index>>,84 full_path: Vec<Index>,82 session: NixSession,85 session: NixSession,83 value: Option<u32>,86 value: u32,84}87}85#[derive(Clone)]88#[derive(Clone)]86pub struct Value(Arc<ValueInner>);89pub struct Value(Arc<ValueInner>);87impl Value {90impl Value {88 fn root(session: NixSession) -> Self {91 async fn new(session: NixSession, query: &str) -> Result<Self> {92 let vid = session.0.lock().await.execute_assign(query).await?;89 Self(Arc::new(ValueInner {93 Ok(Self(Arc::new(ValueInner {90 full_path: Some(vec![]),94 full_path: vec![],91 session,95 session,92 value: None,96 value: vid,93 }))97 })))94 }98 }99 /// Get a top-level binding.100 ///101 /// In flake repl session, every output is exposed as top-level binding.95 async fn new(session: NixSession, query: &str) -> Result<Self> {102 pub async fn binding(session: NixSession, query: &str) -> Result<Self> {103 // TODO: Verify that query is a valid variable name96 let vid = session.0.lock().await.execute_assign(query).await?;104 let vid = session.0.lock().await.execute_assign(query).await?;97 Ok(Self(Arc::new(ValueInner {105 Ok(Self(Arc::new(ValueInner {98 full_path: None,106 full_path: vec![Index::Var(query.to_owned())],99 session,107 session,100 value: Some(vid),108 value: vid,101 })))109 })))102 }110 }103 /// Get a top-level binding.104 ///105 /// In flake repl session, every output is exposed as top-level binding.106 pub async fn binding(session: NixSession, field: &str) -> Result<Self> {107 Self::root(session).select([Index::var(field)]).await108 }109 pub async fn select<'a>(&self, name: impl IntoIterator<Item = Index>) -> Result<Self> {111 pub async fn select(&self, name: impl IntoIterator<Item = Index>) -> Result<Self> {110 let mut used_fields = Vec::new();112 let mut used_fields = Vec::new();111 let mut name = name.into_iter();113 let name = name.into_iter();112114113 let mut full_path = self.0.full_path.clone();115 let mut full_path = self.0.full_path.clone();114 let mut query = if let Some(id) = self.0.value {116 let mut query = self.sess_field_name();115 format!("sess_field_{id}")116 } else {117 let first = name.next();118 if let Some(Index::Var(i)) = first {119 if let Some(full_path) = &mut full_path {120 full_path.push(Index::Var(i.clone()));121 }122 i.clone()123 } else {124 panic!("first path item should be variable, got {first:?}")125 }126 };127 for v in name {117 for v in name {128 if let Some(full_path) = &mut full_path {129 full_path.push(v.clone());118 full_path.push(v.clone());130 }131 match v {119 match v {132 Index::Var(_) => panic!("var item may only be first"),120 Index::Var(_) => panic!("var item may only be first"),133 Index::String(s) => {121 Index::String(s) => {144 let index = Value::new(self.0.session.clone(), &e.out).await?;132 let index = Value::new(self.0.session.clone(), &e.out).await?;145 used_fields.push(index.clone());133 used_fields.push(index.clone());146 query.push('.');134 query.push('.');147 let index = format!("${{sess_field_{}}}", index.0.value.expect("value"));135 let index = format!("${{sess_field_{}}}", index.0.value);148 query.push_str(&index);136 query.push_str(&index);149 }137 }150 Index::ExprApply(e) => {138 Index::ExprApply(e) => {151 let index = Value::new(self.0.session.clone(), &e.out).await?;139 let index = Value::new(self.0.session.clone(), &e.out).await?;152 used_fields.push(index.clone());140 used_fields.push(index.clone());153 query.push(' ');141 query.push(' ');154 let index = format!("sess_field_{}", index.0.value.expect("value"));142 let index = format!("sess_field_{}", index.0.value);155 query.push_str(&index);143 query.push_str(&index);156 query = format!("({query})");144 query = format!("({query})");157 }145 }158 Index::Pipe(v) => {146 Index::Pipe(v) => {159 let index = Value::new(self.0.session.clone(), &v.out).await?;147 let index = Value::new(self.0.session.clone(), &v.out).await?;160 used_fields.push(index.clone());148 used_fields.push(index.clone());161 let index = format!("sess_field_{}", index.0.value.expect("value"));149 let index = format!("sess_field_{}", index.0.value);162 query = format!("({index} {query})");150 query = format!("({index} {query})");163 }151 }164 Index::Merge(v) => {152 Index::Merge(v) => {165 let index = Value::new(self.0.session.clone(), &v.out).await?;153 let index = Value::new(self.0.session.clone(), &v.out).await?;166 used_fields.push(index.clone());154 used_fields.push(index.clone());167 let index = format!("sess_field_{}", index.0.value.expect("value"));155 let index = format!("sess_field_{}", index.0.value);168 query = format!("({query} // {index})");156 query = format!("({query} // {index})");169 }157 }170 }158 }182 Ok(Self(Arc::new(ValueInner {170 Ok(Self(Arc::new(ValueInner {183 full_path,171 full_path,184 session: self.0.session.clone(),172 session: self.0.session.clone(),185 value: Some(vid),173 value: vid,186 })))174 })))187 }175 }188 pub async fn as_json<V: DeserializeOwned>(&self) -> Result<V> {176 pub async fn as_json<V: DeserializeOwned>(&self) -> Result<V> {189 let id = self.0.value.expect("can't serialize root field");177 let query = self.sess_field_name();190 let query = format!("sess_field_{id}");191 self.0178 self.0192 .session179 .session193 .0180 .0199 }186 }200 #[allow(dead_code)]187 #[allow(dead_code)]201 pub async fn has_field(&self, name: &str) -> Result<bool> {188 pub async fn has_field(&self, name: &str) -> Result<bool> {202 let id = self.0.value.expect("can't list root fields");203 let key = nixlike::escape_string(name);189 let key = nixlike::escape_string(name);204 let query = format!("sess_field_{id} ? {key}");190 let query = format!("{} ? {key}", self.sess_field_name());205 self.0191 self.0206 .session192 .session207 .0193 .0212 .map_err(|e| e.context(self.attribute()))198 .map_err(|e| e.context(self.attribute()))213 }199 }214 pub async fn list_fields(&self) -> Result<Vec<String>> {200 pub async fn list_fields(&self) -> Result<Vec<String>> {215 let id = self.0.value.expect("can't list root fields");216 let query = format!("builtins.attrNames sess_field_{id}");201 let query = format!("builtins.attrNames {}", self.sess_field_name());217 self.0202 self.0218 .session203 .session219 .0204 .0224 .map_err(|e| e.context(self.attribute()))209 .map_err(|e| e.context(self.attribute()))225 }210 }226 pub async fn type_of(&self) -> Result<String> {211 pub async fn type_of(&self) -> Result<String> {227 let id = self.0.value.expect("can't list root fields");228 let query = format!("builtins.typeOf sess_field_{id}");212 let query = format!("builtins.typeOf {}", self.sess_field_name());229 self.0213 self.0230 .session214 .session231 .0215 .0240 let import = Self::new(self.0.session.clone(), "import").await?;224 let import = Self::new(self.0.session.clone(), "import").await?;241 Ok(nix_go!(self | import))225 Ok(nix_go!(self | import))242 }226 }227 fn sess_field_name(&self) -> String {228 format!("sess_field_{}", self.0.value)229 }243 pub async fn build_maybe_batch(230 pub async fn build_maybe_batch(244 &self,231 &self,245 batch: Option<NixBuildBatch>,232 batch: Option<NixBuildBatch>,251 }238 }252 }239 }253 pub async fn build(&self) -> Result<HashMap<String, PathBuf>> {240 pub async fn build(&self) -> Result<HashMap<String, PathBuf>> {254 let id = self.0.value.expect("can't use build on not-value");255 let query = format!(":b sess_field_{id}");241 let query = format!(":b {}", self.sess_field_name());256 let vid = self242 let vid = self257 .0243 .0258 .session244 .session284 }270 }285 /// Weakly convert string-like types (derivation/path/string) to string271 /// Weakly convert string-like types (derivation/path/string) to string286 pub async fn to_string_weak(&self) -> Result<String> {272 pub async fn to_string_weak(&self) -> Result<String> {287 let id = self.0.value.expect("can't use build on not-value");288 let query = format!("\"${{sess_field_{id}}}\"");273 let query = format!("\"${{{}}}\"", self.sess_field_name());289 let vid: String = self274 let vid: String = self290 .0275 .0291 .session276 .session298 }283 }299284300 fn attribute(&self) -> String {285 fn attribute(&self) -> String {301 if let Some(full_path) = &self.0.full_path {286 PathDisplay(&self.0.full_path).to_string()302 PathDisplay(full_path).to_string()303 } else {304 "<root>".to_owned()305 }306 }287 }307288308 pub(crate) fn session(&self) -> NixSession {289 pub(crate) fn session(&self) -> NixSession {309 self.0.session.clone()290 self.0.session.clone()310 }291 }311292312 pub(crate) fn session_field_id(&self) -> u32 {293 pub(crate) fn session_field_id(&self) -> u32 {313 self.0.value.expect("not root")294 self.0.value314 }295 }315}296}316impl Drop for ValueInner {297impl Drop for ValueInner {317 fn drop(&mut self) {298 fn drop(&mut self) {318 if let Some(id) = self.value {319 if let Ok(mut lock) = self.session.0.try_lock() {299 if let Ok(mut lock) = self.session.0.try_lock() {320 lock.free_list.push(id)300 lock.free_list.push(self.value)321 }301 }322 // Leaked302 // Leaked323 }324 }303 }325}304}326305