git.delta.rocks / jrsonnet / refs/commits / bbe146ee2257

difftreelog

feat refer to subfields of objects in string formatting

Yaroslav Bolyukin2023-06-14parent: #aeb8b35.patch.diff
in: master
Upstream issue: https://github.com/google/jsonnet/pull/1011

3 files changed

modifiedcrates/jrsonnet-evaluator/src/error.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/error.rs
+++ b/crates/jrsonnet-evaluator/src/error.rs
@@ -1,6 +1,6 @@
 use std::{
 	fmt::{Debug, Display},
-	path::PathBuf,
+	path::PathBuf, cmp::Ordering,
 };
 
 use jrsonnet_gcmodule::Trace;
@@ -9,9 +9,9 @@
 use jrsonnet_types::ValType;
 use thiserror::Error;
 
-use crate::{function::CallLocation, stdlib::format::FormatError, typed::TypeLocError};
+use crate::{function::CallLocation, stdlib::format::FormatError, typed::TypeLocError, ObjValue};
 
-fn format_found(list: &[IStr], what: &str) -> String {
+pub(crate) fn format_found(list: &[IStr], what: &str) -> String {
 	if list.is_empty() {
 		return String::new();
 	}
@@ -68,6 +68,26 @@
 	}
 }
 
+pub(crate) fn suggest_object_fields(v: &ObjValue, key: IStr) -> Vec<IStr> {
+	let mut heap = Vec::new();
+	for field in v.fields_ex(
+		true,
+		#[cfg(feature = "exp-preserve-order")]
+		false,
+	) {
+		let conf = strsim::jaro_winkler(field.as_str(), key.as_str());
+		if conf < 0.8 {
+			continue;
+		}
+		if field.as_str() == key.as_str() {
+			panic!("looks like string pooling failure, please write any info regarding this crash to https://github.com/CertainLach/jrsonnet/issues/113, thanks!");
+		}
+		heap.push((conf, field));
+	}
+	heap.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(Ordering::Equal));
+	heap.into_iter().map(|v| v.1).collect()
+}
+
 type FunctionSignature = Vec<(Option<IStr>, bool)>;
 
 /// Possible errors
@@ -348,6 +368,9 @@
 	($w:ident$(::$i:ident)*$(($($tt:tt)*))?) => {
 		return Err($w$(::$i)*$(($($tt)*))?.into())
 	};
+	($w:ident$(::$i:ident)*$({$($tt:tt)*})?) => {
+		return Err($w$(::$i)*$({$($tt)*})?.into())
+	};
 	($l:literal) => {
 		return Err($crate::error::ErrorKind::RuntimeError($l.into()).into())
 	};
modifiedcrates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs
@@ -12,7 +12,7 @@
 use crate::{
 	arr::ArrValue,
 	destructure::evaluate_dest,
-	error::ErrorKind::*,
+	error::{ErrorKind::*, suggest_object_fields},
 	evaluate::operator::{evaluate_add_op, evaluate_binary_op_special, evaluate_unary_op},
 	function::{CallLocation, FuncDesc, FuncVal},
 	throw,
@@ -466,31 +466,10 @@
 				|| format!("field <{key}> access"),
 				|| match v.get(key.clone().into_flat()) {
 					Ok(Some(v)) => Ok(v),
-					#[cfg(not(feature = "friendly-errors"))]
-					Ok(None) => throw!(NoSuchField(key.clone(), vec![])),
-					#[cfg(feature = "friendly-errors")]
 					Ok(None) => {
-						let mut heap = Vec::new();
-						for field in v.fields_ex(
-							true,
-							#[cfg(feature = "exp-preserve-order")]
-							false,
-						) {
-							let conf = strsim::jaro_winkler(
-								&field as &str,
-								&key.clone().into_flat() as &str,
-							);
-							if conf < 0.8 {
-								continue;
-							}
-							heap.push((conf, field));
-						}
-						heap.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(Ordering::Equal));
+						let suggestions = suggest_object_fields(&v, key.clone().into_flat());
 
-						throw!(NoSuchField(
-							key.clone().into_flat(),
-							heap.into_iter().map(|(_, v)| v).collect()
-						))
+						throw!(NoSuchField(key.clone().into_flat(), suggestions))
 					}
 					Err(e) => Err(e),
 				},
modifiedcrates/jrsonnet-evaluator/src/stdlib/format.rsdiffbeforeafterboth
7use thiserror::Error;7use thiserror::Error;
88
9use crate::{error::ErrorKind::*, throw, typed::Typed, Error, ObjValue, Result, Val};9use crate::{
10 error::{format_found, suggest_object_fields, ErrorKind::*},
11 throw,
12 typed::Typed,
13 Error, ObjValue, Result, Val,
14};
1015
11#[derive(Debug, Clone, Error, Trace)]16#[derive(Debug, Clone, Error, Trace)]
25 #[error("no such format field: {0}")]30 #[error("no such format field: {0}")]
26 NoSuchFormatField(IStr),31 NoSuchFormatField(IStr),
32
33 #[error("expected subfield <{0}> to be an object, got {1} instead")]
34 SubfieldDidntYieldAnObject(IStr, ValType),
35 #[error("subfield not found: <[{full}]{current}>{}", format_found(.found, "subfield"))]
36 SubfieldNotFound {
37 current: IStr,
38 full: IStr,
39 found: Box<Vec<IStr>>,
40 },
27}41}
2842
29impl From<FormatError> for Error {43impl From<FormatError> for Error {
691 Ok(out)705 Ok(out)
692}706}
707
708fn get_dotted_field(obj: ObjValue, field: &str) -> Result<Val> {
709 let mut current = Val::Obj(obj);
710 let mut name_offset = 0;
711 for component in field.split('.') {
712 let end_offset = name_offset + component.len();
713 current = if let Val::Obj(obj) = current {
714 if let Some(value) = obj.get(component.into())? {
715 value
716 } else {
717 let current = &field[name_offset..end_offset];
718 let full = &field[..name_offset];
719 let found = Box::new(suggest_object_fields(&obj, current.into()));
720 throw!(SubfieldNotFound {
721 current: current.into(),
722 full: full.into(),
723 found,
724 })
725 }
726 } else {
727 // No underflow may happen, initially we always start with an object
728 let subfield = &field[..name_offset - 1];
729 throw!(SubfieldDidntYieldAnObject(
730 subfield.into(),
731 current.value_type()
732 ));
733 };
734 name_offset = end_offset + 1;
735 }
736 Ok(current)
737}
693738
694pub fn format_obj(str: &str, values: &ObjValue) -> Result<String> {739pub fn format_obj(str: &str, values: &ObjValue) -> Result<String> {
695 let codes = parse_codes(str)?;740 let codes = parse_codes(str)?;
726 if let Some(v) = values.get(f.clone())? {771 if let Some(v) = values.get(f.clone())? {
727 v772 v
728 } else {773 } else {
729 throw!(NoSuchFormatField(f));774 get_dotted_field(values.clone(), &f)?
730 }775 }
731 };776 };
732777