1use std::{2 cmp::Ordering, convert::Infallible, fmt::{Debug, Display}, path::PathBuf3};45use jrsonnet_gcmodule::Trace;6use jrsonnet_interner::IStr;7use jrsonnet_parser::{BinaryOpType, ExprLocation, LocExpr, Source, SourcePath, UnaryOpType};8use jrsonnet_types::ValType;9use thiserror::Error;1011use crate::{12 function::{builtin::ParamDefault, CallLocation},13 stdlib::format::FormatError,14 typed::TypeLocError,15 val::ConvertNumValueError,16 ObjValue,17};1819pub(crate) fn format_found(list: &[IStr], what: &str) -> String {20 if list.is_empty() {21 return String::new();22 }23 let mut out = String::new();24 out.push_str("\nThere is ");25 out.push_str(what);26 if list.len() > 1 {27 out.push('s');28 }29 out.push_str(" with similar name");30 if list.len() > 1 {31 out.push('s');32 }33 out.push_str(" present: ");34 for (i, v) in list.iter().enumerate() {35 if i != 0 {36 out.push_str(", ");37 }38 out.push_str(v as &str);39 }40 out41}4243fn format_signature(sig: &FunctionSignature) -> String {44 let mut out = String::new();45 out.push_str("\nFunction has the following signature: ");46 out.push('(');47 if sig.is_empty() {48 out.push_str("/*no arguments*/");49 } else {50 for (i, (name, default)) in sig.iter().enumerate() {51 if i != 0 {52 out.push_str(", ");53 }54 if let Some(name) = name {55 out.push_str(name);56 } else {57 out.push_str("<unnamed>");58 }59 match default {60 ParamDefault::None => {}61 ParamDefault::Exists => out.push_str(" = <default>"),62 ParamDefault::Literal(lit) => {63 out.push_str(" = ");64 out.push_str(lit);65 }66 }67 }68 }69 out.push(')');70 out71}7273const fn format_empty_str(str: &str) -> &str {74 if str.is_empty() {75 "\"\" (empty string)"76 } else {77 str78 }79}8081pub(crate) fn suggest_object_fields(v: &ObjValue, key: IStr) -> Vec<IStr> {82 let mut heap = Vec::new();83 for field in v.fields_ex(84 true,85 #[cfg(feature = "exp-preserve-order")]86 false,87 ) {88 let conf = strsim::jaro_winkler(field.as_str(), key.as_str());89 if conf < 0.8 {90 continue;91 }92 assert!(field.as_str() != key.as_str(), "looks like string pooling failure, please write any info regarding this crash to https://github.com/CertainLach/jrsonnet/issues/113, thanks!");9394 heap.push((conf, field));95 }96 heap.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(Ordering::Equal));97 heap.into_iter().map(|v| v.1).collect()98}99100type FunctionSignature = Vec<(Option<IStr>, ParamDefault)>;101102103#[allow(missing_docs)]104#[derive(Error, Debug, Clone, Trace)]105#[non_exhaustive]106pub enum ErrorKind {107 #[error("intrinsic not found: {0}")]108 IntrinsicNotFound(IStr),109110 #[error("operator {0} does not operate on type {1}")]111 UnaryOperatorDoesNotOperateOnType(UnaryOpType, ValType),112 #[error("binary operation {1} {0} {2} is not implemented")]113 BinaryOperatorDoesNotOperateOnValues(BinaryOpType, ValType, ValType),114115 #[error("no top level object in this context")]116 NoTopLevelObjectFound,117 #[error("self is only usable inside objects")]118 CantUseSelfOutsideOfObject,119 #[error("no super found")]120 NoSuperFound,121122 #[error("for loop can only iterate over arrays")]123 InComprehensionCanOnlyIterateOverArray,124125 #[error("array out of bounds: {0} is not within [0,{1})")]126 ArrayBoundsError(isize, usize),127 #[error("string out of bounds: {0} is not within [0,{1})")]128 StringBoundsError(usize, usize),129130 #[error("assert failed: {}", format_empty_str(.0))]131 AssertionFailed(IStr),132133 #[error("variable is not defined: {0}{}", format_found(.1, "variable"))]134 VariableIsNotDefined(IStr, Vec<IStr>),135 #[error("duplicate local var: {0}")]136 DuplicateLocalVar(IStr),137138 #[error("type mismatch: expected {}, got {2} {0}", .1.iter().map(|e| format!("{e}")).collect::<Vec<_>>().join(", "))]139 TypeMismatch(&'static str, Vec<ValType>, ValType),140 #[error("no such field: {}{}", format_empty_str(.0), format_found(.1, "field"))]141 NoSuchField(IStr, Vec<IStr>),142143 #[error("only functions can be called, got {0}")]144 OnlyFunctionsCanBeCalledGot(ValType),145 #[error("parameter {0} is not defined")]146 UnknownFunctionParameter(String),147 #[error("argument {0} is already bound")]148 BindingParameterASecondTime(IStr),149 #[error("too many args, function has {0}{}", format_signature(.1))]150 TooManyArgsFunctionHas(usize, FunctionSignature),151 #[error("function argument is not passed: {}{}", .0.as_ref().map_or("<unnamed>", IStr::as_str), format_signature(.1))]152 FunctionParameterNotBoundInCall(Option<IStr>, FunctionSignature),153154 #[error("external variable is not defined: {0}")]155 UndefinedExternalVariable(IStr),156157 #[error("field name should be string, got {0}")]158 FieldMustBeStringGot(ValType),159 #[error("duplicate field name: {}", format_empty_str(.0))]160 DuplicateFieldName(IStr),161162 #[error("attempted to index array with string {}", format_empty_str(.0))]163 AttemptedIndexAnArrayWithString(IStr),164 #[error("{0} index type should be {1}, got {2}")]165 ValueIndexMustBeTypeGot(ValType, ValType, ValType),166 #[error("cant index into {0}")]167 CantIndexInto(ValType),168 #[error("{0} is not indexable")]169 ValueIsNotIndexable(ValType),170171 #[error("super can't be used standalone")]172 StandaloneSuper,173174 #[error("can't resolve {1} from {0}")]175 ImportFileNotFound(SourcePath, String),176 #[error("can't resolve absolute {0}")]177 AbsoluteImportFileNotFound(PathBuf),178 #[error("resolved file not found: {:?}", .0)]179 ResolvedFileNotFound(SourcePath),180 #[error("can't import {0}: is a directory")]181 ImportIsADirectory(SourcePath),182 #[error("imported file is not valid utf-8: {0:?}")]183 ImportBadFileUtf8(SourcePath),184 #[error("import io error: {0}")]185 ImportIo(String),186 #[error("tried to import {1} from {0}, but imports are not supported")]187 ImportNotSupported(SourcePath, String),188 #[error("tried to import {0}, but absolute imports are not supported")]189 AbsoluteImportNotSupported(PathBuf),190 #[error("can't import from virtual file")]191 CantImportFromVirtualFile,192 #[error(193 "syntax error: {}",194 195 {.error.expected.tokens().find(|t| t.starts_with("!!!")).map_or_else(|| {196 format!(197 "expected {}, got {:?}",198 .error.expected,199 .path.code().chars().nth(error.location.offset)200 .map_or_else(|| "EOF".into(), |c| c.to_string())201 )202 }, |v| v[3..].into())}203 )]204 ImportSyntaxError {205 path: Source,206 #[trace(skip)]207 error: Box<jrsonnet_parser::ParseError>,208 },209210 #[error("runtime error: {}", format_empty_str(.0))]211 RuntimeError(IStr),212 #[error("stack overflow, try to reduce recursion, or set --max-stack to bigger value")]213 StackOverflow,214 #[error("infinite recursion detected")]215 InfiniteRecursionDetected,216 #[error("tried to index by fractional value")]217 FractionalIndex,218 #[error("attempted to divide by zero")]219 DivisionByZero,220221 #[error("string manifest output is not an string")]222 StringManifestOutputIsNotAString,223 #[error("stream manifest output is not an array")]224 StreamManifestOutputIsNotAArray,225 #[error("multi manifest output is not an object")]226 MultiManifestOutputIsNotAObject,227228 #[error("cant recurse stream manifest")]229 StreamManifestOutputCannotBeRecursed,230 #[error("stream manifest output cannot consist of raw strings")]231 StreamManifestCannotNestString,232233 #[error("{}", format_empty_str(.0))]234 ImportCallbackError(String),235 #[error("invalid unicode codepoint: {0}")]236 InvalidUnicodeCodepointGot(u32),237238 #[error("convert num value: {0}")]239 ConvertNumValue(#[from] ConvertNumValueError),240241 #[error("format error: {0}")]242 Format(#[from] FormatError),243 #[error("type error: {0}")]244 TypeError(TypeLocError),245246 #[cfg(feature = "anyhow-error")]247 #[error(transparent)]248 Other(#[trace(skip)] std::rc::Rc<anyhow::Error>),249}250251#[cfg(feature = "anyhow-error")]252impl From<anyhow::Error> for Error {253 fn from(e: anyhow::Error) -> Self {254 Self::new(ErrorKind::Other(std::rc::Rc::new(e)))255 }256}257258impl From<ErrorKind> for Error {259 fn from(e: ErrorKind) -> Self {260 Self::new(e)261 }262}263264impl From<Infallible> for Error {265 fn from(_value: Infallible) -> Self {266 unreachable!()267 }268}269270271#[derive(Clone, Debug, Trace)]272pub struct StackTraceElement {273 274 275 pub location: Option<ExprLocation>,276 277 pub desc: String,278}279#[derive(Debug, Clone, Trace)]280pub struct StackTrace(pub Vec<StackTraceElement>);281282#[derive(Clone, Trace)]283pub struct Error(Box<(ErrorKind, StackTrace)>);284impl Error {285 pub fn new(e: ErrorKind) -> Self {286 Self(Box::new((e, StackTrace(vec![]))))287 }288289 pub const fn error(&self) -> &ErrorKind {290 &(self.0).0291 }292 pub fn error_mut(&mut self) -> &mut ErrorKind {293 &mut (self.0).0294 }295 pub const fn trace(&self) -> &StackTrace {296 &(self.0).1297 }298 pub fn trace_mut(&mut self) -> &mut StackTrace {299 &mut (self.0).1300 }301}302impl Display for Error {303 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {304 writeln!(f, "{}", self.0 .0)?;305 for el in &self.0 .1 .0 {306 write!(f, "\t{}", el.desc)?;307 if let Some(loc) = &el.location {308 write!(f, "at {}", loc.0 .0 .0)?;309 loc.0.map_source_locations(&[loc.1, loc.2]);310 }311 writeln!(f)?;312 }313 Ok(())314 }315}316impl Debug for Error {317 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {318 f.debug_tuple("LocError").field(&self.0).finish()319 }320}321impl std::error::Error for Error {}322323pub trait ErrorSource {324 fn to_location(self) -> Option<ExprLocation>;325}326impl ErrorSource for &LocExpr {327 fn to_location(self) -> Option<ExprLocation> {328 Some(self.1.clone())329 }330}331impl ErrorSource for &ExprLocation {332 fn to_location(self) -> Option<ExprLocation> {333 Some(self.clone())334 }335}336impl ErrorSource for CallLocation<'_> {337 fn to_location(self) -> Option<ExprLocation> {338 self.0.cloned()339 }340}341342pub type Result<V, E = Error> = std::result::Result<V, E>;343pub trait ResultExt: Sized {344 #[must_use]345 fn with_description<O: Into<String>>(self, msg: impl FnOnce() -> O) -> Self;346 #[must_use]347 fn description(self, msg: &str) -> Self {348 self.with_description(|| msg)349 }350351 #[must_use]352 fn with_description_src<O: Into<String>>(353 self,354 src: impl ErrorSource,355 msg: impl FnOnce() -> O,356 ) -> Self;357 #[must_use]358 fn description_src(self, src: impl ErrorSource, msg: &str) -> Self {359 self.with_description_src(src, || msg)360 }361}362impl<T> ResultExt for Result<T, Error> {363 fn with_description<O: Into<String>>(mut self, msg: impl FnOnce() -> O) -> Self {364 if let Err(e) = &mut self {365 let trace = e.trace_mut();366 trace.0.push(StackTraceElement {367 location: None,368 desc: msg().into(),369 });370 }371 self372 }373374 fn with_description_src<O: Into<String>>(375 mut self,376 src: impl ErrorSource,377 msg: impl FnOnce() -> O,378 ) -> Self {379 if let Err(e) = &mut self {380 let trace = e.trace_mut();381 trace.0.push(StackTraceElement {382 location: src.to_location(),383 desc: msg().into(),384 });385 }386 self387 }388}389390#[macro_export]391macro_rules! bail {392 ($w:ident$(::$i:ident)*$(($($tt:tt)*))?) => {393 return Err($w$(::$i)*$(($($tt)*))?.into())394 };395 ($w:ident$(::$i:ident)*$({$($tt:tt)*})?) => {396 return Err($w$(::$i)*$({$($tt)*})?.into())397 };398 ($l:literal$(, $($tt:tt)*)?) => {399 return Err($crate::error::ErrorKind::RuntimeError($crate::jrsonnet_macros::format_istr!($l$(, $($tt)*)?)).into())400 };401}402403#[macro_export]404macro_rules! runtime_error {405 ($l:literal$(, $($tt:tt)*)?) => {406 $crate::error::Error::from($crate::error::ErrorKind::RuntimeError($crate::jrsonnet_macros::format_istr!($l$(, $($tt)*)?)))407 };408}