1use std::{2 cmp::Ordering,3 fmt::{Debug, Display},4 path::PathBuf,5};67use jrsonnet_gcmodule::Trace;8use jrsonnet_interner::IStr;9use jrsonnet_parser::{BinaryOpType, ExprLocation, LocExpr, Source, SourcePath, UnaryOpType};10use jrsonnet_types::ValType;11use thiserror::Error;1213use crate::{14 function::{builtin::ParamDefault, CallLocation},15 stdlib::format::FormatError,16 typed::TypeLocError,17 ObjValue,18};1920pub(crate) fn format_found(list: &[IStr], what: &str) -> String {21 if list.is_empty() {22 return String::new();23 }24 let mut out = String::new();25 out.push_str("\nThere is ");26 out.push_str(what);27 if list.len() > 1 {28 out.push('s');29 }30 out.push_str(" with similar name");31 if list.len() > 1 {32 out.push('s');33 }34 out.push_str(" present: ");35 for (i, v) in list.iter().enumerate() {36 if i != 0 {37 out.push_str(", ");38 }39 out.push_str(v as &str);40 }41 out42}4344fn format_signature(sig: &FunctionSignature) -> String {45 let mut out = String::new();46 out.push_str("\nFunction has the following signature: ");47 out.push('(');48 if sig.is_empty() {49 out.push_str("/*no arguments*/");50 } else {51 for (i, (name, default)) in sig.iter().enumerate() {52 if i != 0 {53 out.push_str(", ");54 }55 if let Some(name) = name {56 out.push_str(name);57 } else {58 out.push_str("<unnamed>");59 }60 match default {61 ParamDefault::None => {}62 ParamDefault::Exists => out.push_str(" = <default>"),63 ParamDefault::Literal(lit) => {64 out.push_str(" = ");65 out.push_str(lit);66 }67 }68 }69 }70 out.push(')');71 out72}7374const fn format_empty_str(str: &str) -> &str {75 if str.is_empty() {76 "\"\" (empty string)"77 } else {78 str79 }80}8182pub(crate) fn suggest_object_fields(v: &ObjValue, key: IStr) -> Vec<IStr> {83 let mut heap = Vec::new();84 for field in v.fields_ex(85 true,86 #[cfg(feature = "exp-preserve-order")]87 false,88 ) {89 let conf = strsim::jaro_winkler(field.as_str(), key.as_str());90 if conf < 0.8 {91 continue;92 }93 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!");9495 heap.push((conf, field));96 }97 heap.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(Ordering::Equal));98 heap.into_iter().map(|v| v.1).collect()99}100101type FunctionSignature = Vec<(Option<IStr>, ParamDefault)>;102103104#[allow(missing_docs)]105#[derive(Error, Debug, Clone, Trace)]106#[non_exhaustive]107pub enum ErrorKind {108 #[error("intrinsic not found: {0}")]109 IntrinsicNotFound(IStr),110111 #[error("operator {0} does not operate on type {1}")]112 UnaryOperatorDoesNotOperateOnType(UnaryOpType, ValType),113 #[error("binary operation {1} {0} {2} is not implemented")]114 BinaryOperatorDoesNotOperateOnValues(BinaryOpType, ValType, ValType),115116 #[error("no top level object in this context")]117 NoTopLevelObjectFound,118 #[error("self is only usable inside objects")]119 CantUseSelfOutsideOfObject,120 #[error("no super found")]121 NoSuperFound,122123 #[error("for loop can only iterate over arrays")]124 InComprehensionCanOnlyIterateOverArray,125126 #[error("array out of bounds: {0} is not within [0,{1})")]127 ArrayBoundsError(isize, usize),128 #[error("string out of bounds: {0} is not within [0,{1})")]129 StringBoundsError(usize, usize),130131 #[error("assert failed: {}", format_empty_str(.0))]132 AssertionFailed(IStr),133134 #[error("variable is not defined: {0}{}", format_found(.1, "variable"))]135 VariableIsNotDefined(IStr, Vec<IStr>),136 #[error("duplicate local var: {0}")]137 DuplicateLocalVar(IStr),138139 #[error("type mismatch: expected {}, got {2} {0}", .1.iter().map(|e| format!("{e}")).collect::<Vec<_>>().join(", "))]140 TypeMismatch(&'static str, Vec<ValType>, ValType),141 #[error("no such field: {}{}", format_empty_str(.0), format_found(.1, "field"))]142 NoSuchField(IStr, Vec<IStr>),143144 #[error("only functions can be called, got {0}")]145 OnlyFunctionsCanBeCalledGot(ValType),146 #[error("parameter {0} is not defined")]147 UnknownFunctionParameter(String),148 #[error("argument {0} is already bound")]149 BindingParameterASecondTime(IStr),150 #[error("too many args, function has {0}{}", format_signature(.1))]151 TooManyArgsFunctionHas(usize, FunctionSignature),152 #[error("function argument is not passed: {}{}", .0.as_ref().map_or("<unnamed>", IStr::as_str), format_signature(.1))]153 FunctionParameterNotBoundInCall(Option<IStr>, FunctionSignature),154155 #[error("external variable is not defined: {0}")]156 UndefinedExternalVariable(IStr),157158 #[error("field name should be string, got {0}")]159 FieldMustBeStringGot(ValType),160 #[error("duplicate field name: {}", format_empty_str(.0))]161 DuplicateFieldName(IStr),162163 #[error("attempted to index array with string {}", format_empty_str(.0))]164 AttemptedIndexAnArrayWithString(IStr),165 #[error("{0} index type should be {1}, got {2}")]166 ValueIndexMustBeTypeGot(ValType, ValType, ValType),167 #[error("cant index into {0}")]168 CantIndexInto(ValType),169 #[error("{0} is not indexable")]170 ValueIsNotIndexable(ValType),171172 #[error("super can't be used standalone")]173 StandaloneSuper,174175 #[error("can't resolve {1} from {0}")]176 ImportFileNotFound(SourcePath, String),177 #[error("can't resolve absolute {0}")]178 AbsoluteImportFileNotFound(PathBuf),179 #[error("resolved file not found: {:?}", .0)]180 ResolvedFileNotFound(SourcePath),181 #[error("can't import {0}: is a directory")]182 ImportIsADirectory(SourcePath),183 #[error("imported file is not valid utf-8: {0:?}")]184 ImportBadFileUtf8(SourcePath),185 #[error("import io error: {0}")]186 ImportIo(String),187 #[error("tried to import {1} from {0}, but imports are not supported")]188 ImportNotSupported(SourcePath, String),189 #[error("tried to import {0}, but absolute imports are not supported")]190 AbsoluteImportNotSupported(PathBuf),191 #[error("can't import from virtual file")]192 CantImportFromVirtualFile,193 #[error(194 "syntax error: {}",195 196 {.error.expected.tokens().find(|t| t.starts_with("!!!")).map_or_else(|| {197 format!(198 "expected {}, got {:?}",199 .error.expected,200 .path.code().chars().nth(error.location.offset)201 .map_or_else(|| "EOF".into(), |c| c.to_string())202 )203 }, |v| v[3..].into())}204 )]205 ImportSyntaxError {206 path: Source,207 #[trace(skip)]208 error: Box<jrsonnet_parser::ParseError>,209 },210211 #[error("runtime error: {}", format_empty_str(.0))]212 RuntimeError(IStr),213 #[error("stack overflow, try to reduce recursion, or set --max-stack to bigger value")]214 StackOverflow,215 #[error("infinite recursion detected")]216 InfiniteRecursionDetected,217 #[error("tried to index by fractional value")]218 FractionalIndex,219 #[error("attempted to divide by zero")]220 DivisionByZero,221222 #[error("string manifest output is not an string")]223 StringManifestOutputIsNotAString,224 #[error("stream manifest output is not an array")]225 StreamManifestOutputIsNotAArray,226 #[error("multi manifest output is not an object")]227 MultiManifestOutputIsNotAObject,228229 #[error("cant recurse stream manifest")]230 StreamManifestOutputCannotBeRecursed,231 #[error("stream manifest output cannot consist of raw strings")]232 StreamManifestCannotNestString,233234 #[error("{}", format_empty_str(.0))]235 ImportCallbackError(String),236 #[error("invalid unicode codepoint: {0}")]237 InvalidUnicodeCodepointGot(u32),238239 #[error("format error: {0}")]240 Format(#[from] FormatError),241 #[error("type error: {0}")]242 TypeError(TypeLocError),243244 #[cfg(feature = "anyhow-error")]245 #[error(transparent)]246 Other(#[trace(skip)] std::rc::Rc<anyhow::Error>),247}248249#[cfg(feature = "anyhow-error")]250impl From<anyhow::Error> for Error {251 fn from(e: anyhow::Error) -> Self {252 Self::new(ErrorKind::Other(std::rc::Rc::new(e)))253 }254}255256impl From<ErrorKind> for Error {257 fn from(e: ErrorKind) -> Self {258 Self::new(e)259 }260}261262263#[derive(Clone, Debug, Trace)]264pub struct StackTraceElement {265 266 267 pub location: Option<ExprLocation>,268 269 pub desc: String,270}271#[derive(Debug, Clone, Trace)]272pub struct StackTrace(pub Vec<StackTraceElement>);273274#[derive(Clone, Trace)]275pub struct Error(Box<(ErrorKind, StackTrace)>);276impl Error {277 pub fn new(e: ErrorKind) -> Self {278 Self(Box::new((e, StackTrace(vec![]))))279 }280281 pub const fn error(&self) -> &ErrorKind {282 &(self.0).0283 }284 pub fn error_mut(&mut self) -> &mut ErrorKind {285 &mut (self.0).0286 }287 pub const fn trace(&self) -> &StackTrace {288 &(self.0).1289 }290 pub fn trace_mut(&mut self) -> &mut StackTrace {291 &mut (self.0).1292 }293}294impl Display for Error {295 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {296 writeln!(f, "{}", self.0 .0)?;297 for el in &self.0 .1 .0 {298 write!(f, "\t{}", el.desc)?;299 if let Some(loc) = &el.location {300 write!(f, "at {}", loc.0 .0 .0)?;301 loc.0.map_source_locations(&[loc.1, loc.2]);302 }303 writeln!(f)?;304 }305 Ok(())306 }307}308impl Debug for Error {309 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {310 f.debug_tuple("LocError").field(&self.0).finish()311 }312}313impl std::error::Error for Error {}314315pub trait ErrorSource {316 fn to_location(self) -> Option<ExprLocation>;317}318impl ErrorSource for &LocExpr {319 fn to_location(self) -> Option<ExprLocation> {320 Some(self.1.clone())321 }322}323impl ErrorSource for &ExprLocation {324 fn to_location(self) -> Option<ExprLocation> {325 Some(self.clone())326 }327}328impl ErrorSource for CallLocation<'_> {329 fn to_location(self) -> Option<ExprLocation> {330 self.0.cloned()331 }332}333334pub type Result<V, E = Error> = std::result::Result<V, E>;335pub trait ResultExt: Sized {336 #[must_use]337 fn with_description<O: Into<String>>(self, msg: impl FnOnce() -> O) -> Self;338 #[must_use]339 fn description(self, msg: &str) -> Self {340 self.with_description(|| msg)341 }342343 #[must_use]344 fn with_description_src<O: Into<String>>(345 self,346 src: impl ErrorSource,347 msg: impl FnOnce() -> O,348 ) -> Self;349 #[must_use]350 fn description_src(self, src: impl ErrorSource, msg: &str) -> Self {351 self.with_description_src(src, || msg)352 }353}354impl<T> ResultExt for Result<T, Error> {355 fn with_description<O: Into<String>>(mut self, msg: impl FnOnce() -> O) -> Self {356 if let Err(e) = &mut self {357 let trace = e.trace_mut();358 trace.0.push(StackTraceElement {359 location: None,360 desc: msg().into(),361 });362 }363 self364 }365366 fn with_description_src<O: Into<String>>(367 mut self,368 src: impl ErrorSource,369 msg: impl FnOnce() -> O,370 ) -> Self {371 if let Err(e) = &mut self {372 let trace = e.trace_mut();373 trace.0.push(StackTraceElement {374 location: src.to_location(),375 desc: msg().into(),376 });377 }378 self379 }380}381382#[macro_export]383macro_rules! bail {384 ($w:ident$(::$i:ident)*$(($($tt:tt)*))?) => {385 return Err($w$(::$i)*$(($($tt)*))?.into())386 };387 ($w:ident$(::$i:ident)*$({$($tt:tt)*})?) => {388 return Err($w$(::$i)*$({$($tt)*})?.into())389 };390 ($l:literal$(, $($tt:tt)*)?) => {391 return Err($crate::error::ErrorKind::RuntimeError($crate::jrsonnet_macros::format_istr!($l$(, $($tt)*)?)).into())392 };393}394395#[macro_export]396macro_rules! runtime_error {397 ($l:literal$(, $($tt:tt)*)?) => {398 $crate::error::Error::from($crate::error::ErrorKind::RuntimeError($crate::jrsonnet_macros::format_istr!($l$(, $($tt)*)?)))399 };400}