git.delta.rocks / jrsonnet / refs/commits / 186ae516608e

difftreelog

fix type errors are acyclic

slzzmqtoYaroslav Bolyukin2026-03-23parent: #976458d.patch.diff
in: master

1 file changed

modifiedcrates/jrsonnet-evaluator/src/typed/mod.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/typed/mod.rs
1use std::{fmt::Display, rc::Rc};23pub(crate) mod conversions;4pub use conversions::*;5use jrsonnet_gcmodule::Trace;6pub use jrsonnet_types::{ComplexValType, ValType};7use thiserror::Error;89use crate::{10	error::{Error, ErrorKind, Result},11	in_description_frame, Val,12};1314#[derive(Debug, Error, Clone, Trace)]15pub enum TypeError {16	#[error("expected {0}, got {1}")]17	ExpectedGot(ComplexValType, ValType),18	#[error("missing property {0} from {1}")]19	MissingProperty(#[trace(skip)] Rc<str>, ComplexValType),20	#[error("every failed from {0}:\n{1}")]21	UnionFailed(ComplexValType, TypeLocErrorList),22	#[error(23		"number out of bounds: {0} not in {start}..{end}",24		start = .1.map(|v|v.to_string()).unwrap_or_default(),25		end = .2.map(|v|v.to_string()).unwrap_or_default(),26	)]27	BoundsFailed(f64, Option<f64>, Option<f64>),28}29impl From<TypeError> for Error {30	fn from(e: TypeError) -> Self {31		ErrorKind::TypeError(e.into()).into()32	}33}3435#[derive(Debug, Clone, Trace)]36pub struct TypeLocError(Box<TypeError>, ValuePathStack);37impl From<TypeError> for TypeLocError {38	fn from(e: TypeError) -> Self {39		Self(Box::new(e), ValuePathStack(Vec::new()))40	}41}42impl From<TypeLocError> for Error {43	fn from(e: TypeLocError) -> Self {44		ErrorKind::TypeError(e).into()45	}46}47impl Display for TypeLocError {48	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {49		write!(f, "{}", self.0)?;50		if !(self.1).0.is_empty() {51			write!(f, " at {}", self.1)?;52		}53		Ok(())54	}55}5657#[derive(Debug, Clone, Trace)]58pub struct TypeLocErrorList(Vec<TypeLocError>);59impl Display for TypeLocErrorList {60	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {61		use std::fmt::Write;62		let mut out = String::new();63		for (i, err) in self.0.iter().enumerate() {64			if i != 0 {65				writeln!(f)?;66			}67			out.clear();68			write!(out, "{err}")?;6970			for (i, line) in out.lines().enumerate() {71				if line.trim().is_empty() {72					continue;73				}74				if i == 0 {75					write!(f, "  - ")?;76				} else {77					writeln!(f)?;78					write!(f, "    ")?;79				}80				write!(f, "{line}")?;81			}82		}83		Ok(())84	}85}8687fn push_type_description(88	error_reason: impl Fn() -> String,89	path: impl Fn() -> ValuePathItem,90	item: impl Fn() -> Result<()>,91) -> Result<()> {92	in_description_frame(error_reason, || match item() {93		Ok(()) => Ok(()),94		Err(mut e) => {95			if let ErrorKind::TypeError(e) = &mut e.error_mut() {96				(e.1).0.push(path());97			}98			Err(e)99		}100	})101}102103// TODO: check_fast for fast path of union type checking104pub trait CheckType {105	fn check(&self, value: &Val) -> Result<()>;106}107108impl CheckType for ValType {109	fn check(&self, value: &Val) -> Result<()> {110		let got = value.value_type();111		if got != *self {112			let loc_error: TypeLocError = TypeError::ExpectedGot((*self).into(), got).into();113			return Err(loc_error.into());114		}115		Ok(())116	}117}118119#[derive(Clone, Debug, Trace)]120enum ValuePathItem {121	Field(#[trace(skip)] Rc<str>),122	Index(u64),123}124impl Display for ValuePathItem {125	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {126		match self {127			Self::Field(name) => write!(f, ".{name:?}")?,128			Self::Index(idx) => write!(f, "[{idx}]")?,129		}130		Ok(())131	}132}133134#[derive(Clone, Debug, Trace)]135struct ValuePathStack(Vec<ValuePathItem>);136impl Display for ValuePathStack {137	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {138		write!(f, "self")?;139		for elem in self.0.iter().rev() {140			write!(f, "{elem}")?;141		}142		Ok(())143	}144}145146impl CheckType for ComplexValType {147	#[allow(clippy::too_many_lines)]148	fn check(&self, value: &Val) -> Result<()> {149		match self {150			Self::Any => Ok(()),151			Self::Simple(t) => t.check(value),152			Self::Char => match value {153				Val::Str(s) if s.len() == 1 || s.clone().into_flat().chars().count() == 1 => Ok(()),154				v => Err(TypeError::ExpectedGot(self.clone(), v.value_type()).into()),155			},156			Self::BoundedNumber(from, to) => {157				if let Val::Num(n) = value {158					let n = n.get();159					if from.map(|from| from > n).unwrap_or(false)160						|| to.map(|to| to < n).unwrap_or(false)161					{162						return Err(TypeError::BoundsFailed(n, *from, *to).into());163					}164					Ok(())165				} else {166					Err(TypeError::ExpectedGot(self.clone(), value.value_type()).into())167				}168			}169			Self::Array(elem_type) => match value {170				Val::Arr(a) => {171					for (i, item) in a.iter().enumerate() {172						push_type_description(173							|| format!("array index {i}"),174							|| ValuePathItem::Index(i as u64),175							|| elem_type.check(&item.clone()?),176						)?;177					}178					Ok(())179				}180				v => Err(TypeError::ExpectedGot(self.clone(), v.value_type()).into()),181			},182			Self::ArrayRef(elem_type) => match value {183				Val::Arr(a) => {184					for (i, item) in a.iter().enumerate() {185						push_type_description(186							|| format!("array index {i}"),187							|| ValuePathItem::Index(i as u64),188							|| elem_type.check(&item.clone()?),189						)?;190					}191					Ok(())192				}193				v => Err(TypeError::ExpectedGot(self.clone(), v.value_type()).into()),194			},195			Self::AttrsOf(a) => match value {196				Val::Obj(o) => {197					for (_key, value) in o.iter(198						#[cfg(feature = "exp-preserve-order")]199						false,200					) {201						let value = value?;202						a.check(&value)?;203					}204					Ok(())205				}206				v => Err(TypeError::ExpectedGot(self.clone(), v.value_type()).into()),207			},208			Self::ObjectRef(elems) => match value {209				Val::Obj(obj) => {210					for (k, v) in *elems {211						if let Some(got_v) = obj.get((*k).into())? {212							push_type_description(213								|| format!("property {k}"),214								|| ValuePathItem::Field((*k).into()),215								|| v.check(&got_v),216							)?;217						} else {218							return Err(219								TypeError::MissingProperty((*k).into(), self.clone()).into()220							);221						}222					}223					Ok(())224				}225				v => Err(TypeError::ExpectedGot(self.clone(), v.value_type()).into()),226			},227			Self::Union(types) => {228				let mut errors = Vec::new();229				for ty in types {230					match ty.check(value) {231						Ok(()) => {232							return Ok(());233						}234						Err(e) => match e.error() {235							ErrorKind::TypeError(e) => errors.push(e.clone()),236							_ => return Err(e),237						},238					}239				}240				Err(TypeError::UnionFailed(self.clone(), TypeLocErrorList(errors)).into())241			}242			Self::UnionRef(types) => {243				let mut errors = Vec::new();244				for ty in *types {245					match ty.check(value) {246						Ok(()) => {247							return Ok(());248						}249						Err(e) => match e.error() {250							ErrorKind::TypeError(e) => errors.push(e.clone()),251							_ => return Err(e),252						},253					}254				}255				Err(TypeError::UnionFailed(self.clone(), TypeLocErrorList(errors)).into())256			}257			Self::Sum(types) => {258				for ty in types {259					ty.check(value)?;260				}261				Ok(())262			}263			Self::SumRef(types) => {264				for ty in *types {265					ty.check(value)?;266				}267				Ok(())268			}269			Self::Lazy(_lazy) => Ok(()),270		}271	}272}
after · crates/jrsonnet-evaluator/src/typed/mod.rs
1use std::{fmt::Display, rc::Rc};23pub(crate) mod conversions;4pub use conversions::*;5use jrsonnet_gcmodule::Acyclic;6pub use jrsonnet_types::{ComplexValType, ValType};7use thiserror::Error;89use crate::{10	error::{Error, ErrorKind, Result},11	in_description_frame, Val,12};1314#[derive(Debug, Error, Clone, Acyclic)]15pub enum TypeError {16	#[error("expected {0}, got {1}")]17	ExpectedGot(ComplexValType, ValType),18	#[error("missing property {0} from {1}")]19	MissingProperty(Rc<str>, ComplexValType),20	#[error("every failed from {0}:\n{1}")]21	UnionFailed(ComplexValType, TypeLocErrorList),22	#[error(23		"number out of bounds: {0} not in {start}..{end}",24		start = .1.map(|v|v.to_string()).unwrap_or_default(),25		end = .2.map(|v|v.to_string()).unwrap_or_default(),26	)]27	BoundsFailed(f64, Option<f64>, Option<f64>),28}29impl From<TypeError> for Error {30	fn from(e: TypeError) -> Self {31		ErrorKind::TypeError(e.into()).into()32	}33}3435#[derive(Debug, Clone, Acyclic)]36pub struct TypeLocError(Box<TypeError>, ValuePathStack);37impl From<TypeError> for TypeLocError {38	fn from(e: TypeError) -> Self {39		Self(Box::new(e), ValuePathStack(Vec::new()))40	}41}42impl From<TypeLocError> for Error {43	fn from(e: TypeLocError) -> Self {44		ErrorKind::TypeError(e).into()45	}46}47impl Display for TypeLocError {48	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {49		write!(f, "{}", self.0)?;50		if !(self.1).0.is_empty() {51			write!(f, " at {}", self.1)?;52		}53		Ok(())54	}55}5657#[derive(Debug, Clone, Acyclic)]58pub struct TypeLocErrorList(Vec<TypeLocError>);59impl Display for TypeLocErrorList {60	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {61		use std::fmt::Write;62		let mut out = String::new();63		for (i, err) in self.0.iter().enumerate() {64			if i != 0 {65				writeln!(f)?;66			}67			out.clear();68			write!(out, "{err}")?;6970			for (i, line) in out.lines().enumerate() {71				if line.trim().is_empty() {72					continue;73				}74				if i == 0 {75					write!(f, "  - ")?;76				} else {77					writeln!(f)?;78					write!(f, "    ")?;79				}80				write!(f, "{line}")?;81			}82		}83		Ok(())84	}85}8687fn push_type_description(88	error_reason: impl Fn() -> String,89	path: impl Fn() -> ValuePathItem,90	item: impl Fn() -> Result<()>,91) -> Result<()> {92	in_description_frame(error_reason, || match item() {93		Ok(()) => Ok(()),94		Err(mut e) => {95			if let ErrorKind::TypeError(e) = &mut e.error_mut() {96				(e.1).0.push(path());97			}98			Err(e)99		}100	})101}102103// TODO: check_fast for fast path of union type checking104pub trait CheckType {105	fn check(&self, value: &Val) -> Result<()>;106}107108impl CheckType for ValType {109	fn check(&self, value: &Val) -> Result<()> {110		let got = value.value_type();111		if got != *self {112			let loc_error: TypeLocError = TypeError::ExpectedGot((*self).into(), got).into();113			return Err(loc_error.into());114		}115		Ok(())116	}117}118119#[derive(Clone, Debug, Acyclic)]120enum ValuePathItem {121	Field(Rc<str>),122	Index(u64),123}124impl Display for ValuePathItem {125	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {126		match self {127			Self::Field(name) => write!(f, ".{name:?}")?,128			Self::Index(idx) => write!(f, "[{idx}]")?,129		}130		Ok(())131	}132}133134#[derive(Clone, Debug, Acyclic)]135struct ValuePathStack(Vec<ValuePathItem>);136impl Display for ValuePathStack {137	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {138		write!(f, "self")?;139		for elem in self.0.iter().rev() {140			write!(f, "{elem}")?;141		}142		Ok(())143	}144}145146impl CheckType for ComplexValType {147	#[allow(clippy::too_many_lines)]148	fn check(&self, value: &Val) -> Result<()> {149		match self {150			Self::Any => Ok(()),151			Self::Simple(t) => t.check(value),152			Self::Char => match value {153				Val::Str(s) if s.len() == 1 || s.clone().into_flat().chars().count() == 1 => Ok(()),154				v => Err(TypeError::ExpectedGot(self.clone(), v.value_type()).into()),155			},156			Self::BoundedNumber(from, to) => {157				if let Val::Num(n) = value {158					let n = n.get();159					if from.map(|from| from > n).unwrap_or(false)160						|| to.map(|to| to < n).unwrap_or(false)161					{162						return Err(TypeError::BoundsFailed(n, *from, *to).into());163					}164					Ok(())165				} else {166					Err(TypeError::ExpectedGot(self.clone(), value.value_type()).into())167				}168			}169			Self::Array(elem_type) => match value {170				Val::Arr(a) => {171					for (i, item) in a.iter().enumerate() {172						push_type_description(173							|| format!("array index {i}"),174							|| ValuePathItem::Index(i as u64),175							|| elem_type.check(&item.clone()?),176						)?;177					}178					Ok(())179				}180				v => Err(TypeError::ExpectedGot(self.clone(), v.value_type()).into()),181			},182			Self::ArrayRef(elem_type) => match value {183				Val::Arr(a) => {184					for (i, item) in a.iter().enumerate() {185						push_type_description(186							|| format!("array index {i}"),187							|| ValuePathItem::Index(i as u64),188							|| elem_type.check(&item.clone()?),189						)?;190					}191					Ok(())192				}193				v => Err(TypeError::ExpectedGot(self.clone(), v.value_type()).into()),194			},195			Self::AttrsOf(a) => match value {196				Val::Obj(o) => {197					for (_key, value) in o.iter(198						#[cfg(feature = "exp-preserve-order")]199						false,200					) {201						let value = value?;202						a.check(&value)?;203					}204					Ok(())205				}206				v => Err(TypeError::ExpectedGot(self.clone(), v.value_type()).into()),207			},208			Self::ObjectRef(elems) => match value {209				Val::Obj(obj) => {210					for (k, v) in *elems {211						if let Some(got_v) = obj.get((*k).into())? {212							push_type_description(213								|| format!("property {k}"),214								|| ValuePathItem::Field((*k).into()),215								|| v.check(&got_v),216							)?;217						} else {218							return Err(219								TypeError::MissingProperty((*k).into(), self.clone()).into()220							);221						}222					}223					Ok(())224				}225				v => Err(TypeError::ExpectedGot(self.clone(), v.value_type()).into()),226			},227			Self::Union(types) => {228				let mut errors = Vec::new();229				for ty in types {230					match ty.check(value) {231						Ok(()) => {232							return Ok(());233						}234						Err(e) => match e.error() {235							ErrorKind::TypeError(e) => errors.push(e.clone()),236							_ => return Err(e),237						},238					}239				}240				Err(TypeError::UnionFailed(self.clone(), TypeLocErrorList(errors)).into())241			}242			Self::UnionRef(types) => {243				let mut errors = Vec::new();244				for ty in *types {245					match ty.check(value) {246						Ok(()) => {247							return Ok(());248						}249						Err(e) => match e.error() {250							ErrorKind::TypeError(e) => errors.push(e.clone()),251							_ => return Err(e),252						},253					}254				}255				Err(TypeError::UnionFailed(self.clone(), TypeLocErrorList(errors)).into())256			}257			Self::Sum(types) => {258				for ty in types {259					ty.check(value)?;260				}261				Ok(())262			}263			Self::SumRef(types) => {264				for ty in *types {265					ty.check(value)?;266				}267				Ok(())268			}269			Self::Lazy(_lazy) => Ok(()),270		}271	}272}