git.delta.rocks / jrsonnet / refs/commits / 1925b3a76ba9

difftreelog

style use let-else

Yaroslav Bolyukin2022-11-03parent: #a8de1de.patch.diff
in: master

7 files changed

modifiedcrates/jrsonnet-evaluator/src/evaluate/destructure.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate/destructure.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/destructure.rs
@@ -45,9 +45,8 @@
 
 				fn get(self: Box<Self>) -> Result<Self::Output> {
 					let v = self.parent.evaluate()?;
-					let arr = match v {
-						Val::Arr(a) => a,
-						_ => throw!("expected array"),
+					let Val::Arr(arr) = v else {
+						throw!("expected array");
 					};
 					if !self.has_rest {
 						if arr.len() != self.min_len {
@@ -176,9 +175,8 @@
 
 				fn get(self: Box<Self>) -> Result<Self::Output> {
 					let v = self.parent.evaluate()?;
-					let obj = match v {
-						Val::Obj(o) => o,
-						_ => throw!("expected object"),
+					let Val::Obj(obj) = v else {
+						throw!("expected object");
 					};
 					for field in &self.field_names {
 						if !obj.has_field_ex(field.clone(), true) {
modifiedcrates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs
@@ -162,9 +162,7 @@
 				}
 
 				let name = evaluate_field_name(ctx.clone(), name)?;
-				let name = if let Some(name) = name {
-					name
-				} else {
+				let Some(name) = name else {
 					continue;
 				};
 
modifiedcrates/jrsonnet-evaluator/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/lib.rs
+++ b/crates/jrsonnet-evaluator/src/lib.rs
@@ -344,10 +344,10 @@
 		let mut file_cache = self.file_cache();
 		let mut file = file_cache.raw_entry_mut().from_key(&path);
 
-		let file = match file {
-			RawEntryMut::Occupied(ref mut d) => d.get_mut(),
-			RawEntryMut::Vacant(_) => unreachable!("this file was just here!"),
+		let RawEntryMut::Occupied(file) = &mut file else {
+			unreachable!("this file was just here!")
 		};
+		let file = file.get_mut();
 		file.evaluating = false;
 		match res {
 			Ok(v) => {
modifiedcrates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/val.rs
+++ b/crates/jrsonnet-evaluator/src/val.rs
@@ -54,12 +54,8 @@
 			ThunkInner::Pending => return Err(InfiniteRecursionDetected.into()),
 			ThunkInner::Waiting(..) => (),
 		};
-		let value = if let ThunkInner::Waiting(value) =
-			std::mem::replace(&mut *self.0.borrow_mut(), ThunkInner::Pending)
-		{
-			value
-		} else {
-			unreachable!()
+		let ThunkInner::Waiting(value) = std::mem::replace(&mut *self.0.borrow_mut(), ThunkInner::Pending) else {
+			unreachable!();
 		};
 		let new_value = match value.0.get() {
 			Ok(v) => v,
@@ -668,9 +664,8 @@
 
 	/// Expects value to be object, outputs (key, manifested value) pairs
 	pub fn manifest_multi(&self, ty: &ManifestFormat) -> Result<Vec<(IStr, IStr)>> {
-		let obj = match self {
-			Self::Obj(obj) => obj,
-			_ => throw!(MultiManifestOutputIsNotAObject),
+		let Self::Obj(obj) = self else {
+			throw!(MultiManifestOutputIsNotAObject);
 		};
 		let keys = obj.fields(
 			#[cfg(feature = "exp-preserve-order")]
@@ -689,9 +684,8 @@
 
 	/// Expects value to be array, outputs manifested values
 	pub fn manifest_stream(&self, ty: &ManifestFormat) -> Result<Vec<IStr>> {
-		let arr = match self {
-			Self::Arr(a) => a,
-			_ => throw!(StreamManifestOutputIsNotAArray),
+		let Self::Arr(arr) = self else {
+			throw!(StreamManifestOutputIsNotAArray);
 		};
 		let mut out = Vec::with_capacity(arr.len());
 		for i in arr.iter() {
@@ -703,9 +697,8 @@
 	pub fn manifest(&self, ty: &ManifestFormat) -> Result<IStr> {
 		Ok(match ty {
 			ManifestFormat::YamlStream(format) => {
-				let arr = match self {
-					Self::Arr(a) => a,
-					_ => throw!(StreamManifestOutputIsNotAArray),
+				let Self::Arr(arr) = self else {
+					throw!(StreamManifestOutputIsNotAArray)
 				};
 				let mut out = String::new();
 
modifiedcrates/jrsonnet-macros/src/lib.rsdiffbeforeafterboth
before · crates/jrsonnet-macros/src/lib.rs
1use proc_macro2::TokenStream;2use quote::quote;3use syn::{4	parenthesized,5	parse::{Parse, ParseStream},6	parse_macro_input,7	punctuated::Punctuated,8	spanned::Spanned,9	token::{self, Comma},10	Attribute, DeriveInput, Error, FnArg, GenericArgument, Ident, ItemFn, LitStr, Pat, Path,11	PathArguments, Result, ReturnType, Token, Type,12};1314fn parse_attr<A: Parse, I>(attrs: &[Attribute], ident: I) -> Result<Option<A>>15where16	Ident: PartialEq<I>,17{18	let attrs = attrs19		.iter()20		.filter(|a| a.path.is_ident(&ident))21		.collect::<Vec<_>>();22	if attrs.len() > 1 {23		return Err(Error::new(24			attrs[1].span(),25			"this attribute may be specified only once",26		));27	} else if attrs.is_empty() {28		return Ok(None);29	}30	let attr = attrs[0];31	let attr = attr.parse_args::<A>()?;3233	Ok(Some(attr))34}3536fn path_is(path: &Path, needed: &str) -> bool {37	path.leading_colon.is_none()38		&& !path.segments.is_empty()39		&& path.segments.iter().last().unwrap().ident == needed40}4142fn type_is_path<'ty>(ty: &'ty Type, needed: &str) -> Option<&'ty PathArguments> {43	match ty {44		Type::Path(path) if path.qself.is_none() && path_is(&path.path, needed) => {45			let args = &path.path.segments.iter().last().unwrap().arguments;46			Some(args)47		}48		_ => None,49	}50}5152fn extract_type_from_option(ty: &Type) -> Result<Option<&Type>> {53	Ok(if let Some(args) = type_is_path(ty, "Option") {54		// It should have only on angle-bracketed param ("<String>"):55		let generic_arg = match args {56			PathArguments::AngleBracketed(params) => params.args.iter().next().unwrap(),57			_ => return Err(Error::new(args.span(), "missing option generic")),58		};59		// This argument must be a type:60		match generic_arg {61			GenericArgument::Type(ty) => Some(ty),62			_ => {63				return Err(Error::new(64					generic_arg.span(),65					"option generic should be a type",66				))67			}68		}69	} else {70		None71	})72}7374struct Field {75	name: Ident,76	_colon: Token![:],77	ty: Type,78}79impl Parse for Field {80	fn parse(input: ParseStream) -> syn::Result<Self> {81		Ok(Self {82			name: input.parse()?,83			_colon: input.parse()?,84			ty: input.parse()?,85		})86	}87}8889mod kw {90	syn::custom_keyword!(fields);91	syn::custom_keyword!(rename);92	syn::custom_keyword!(flatten);93	syn::custom_keyword!(ok);94}9596struct EmptyAttr;97impl Parse for EmptyAttr {98	fn parse(_input: ParseStream) -> Result<Self> {99		Ok(Self)100	}101}102103struct BuiltinAttrs {104	fields: Vec<Field>,105}106impl Parse for BuiltinAttrs {107	fn parse(input: ParseStream) -> syn::Result<Self> {108		if input.is_empty() {109			return Ok(Self { fields: Vec::new() });110		}111		input.parse::<kw::fields>()?;112		let fields;113		parenthesized!(fields in input);114		let p = Punctuated::<Field, Comma>::parse_terminated(&fields)?;115		Ok(Self {116			fields: p.into_iter().collect(),117		})118	}119}120121enum ArgInfo {122	Normal {123		ty: Box<Type>,124		is_option: bool,125		name: Option<String>,126		cfg_attrs: Vec<Attribute>,127		// ident: Ident,128	},129	Lazy {130		is_option: bool,131		name: Option<String>,132	},133	Context,134	Location,135	This,136}137138impl ArgInfo {139	fn parse(name: &str, arg: &FnArg) -> Result<Self> {140		let arg = match arg {141			FnArg::Receiver(_) => unreachable!(),142			FnArg::Typed(a) => a,143		};144		let ident = match &arg.pat as &Pat {145			Pat::Ident(i) => Some(i.ident.clone()),146			_ => None,147		};148		let ty = &arg.ty;149		if type_is_path(ty, "Context").is_some() {150			return Ok(Self::Context);151		} else if type_is_path(ty, "CallLocation").is_some() {152			return Ok(Self::Location);153		} else if type_is_path(ty, "Thunk").is_some() {154			return Ok(Self::Lazy {155				is_option: false,156				name: ident.map(|v| v.to_string()),157			});158		}159160		match ty as &Type {161			Type::Reference(r) if type_is_path(&r.elem, name).is_some() => return Ok(Self::This),162			_ => {}163		}164165		let (is_option, ty) = if let Some(ty) = extract_type_from_option(ty)? {166			if type_is_path(ty, "Thunk").is_some() {167				return Ok(Self::Lazy {168					is_option: true,169					name: ident.map(|v| v.to_string()),170				});171			}172173			(true, Box::new(ty.clone()))174		} else {175			(false, ty.clone())176		};177178		let cfg_attrs = arg179			.attrs180			.iter()181			.filter(|a| a.path.is_ident("cfg"))182			.cloned()183			.collect();184185		Ok(Self::Normal {186			ty,187			is_option,188			name: ident.map(|v| v.to_string()),189			cfg_attrs,190		})191	}192}193194#[proc_macro_attribute]195pub fn builtin(196	attr: proc_macro::TokenStream,197	item: proc_macro::TokenStream,198) -> proc_macro::TokenStream {199	let attr = parse_macro_input!(attr as BuiltinAttrs);200	let item: ItemFn = parse_macro_input!(item);201202	match builtin_inner(attr, item) {203		Ok(v) => v.into(),204		Err(e) => e.into_compile_error().into(),205	}206}207208fn builtin_inner(attr: BuiltinAttrs, fun: ItemFn) -> syn::Result<TokenStream> {209	let result = match fun.sig.output {210		ReturnType::Default => {211			return Err(Error::new(212				fun.sig.span(),213				"builtin should return something",214			))215		}216		ReturnType::Type(_, ref ty) => ty.clone(),217	};218	let result_inner = if let Some(args) = type_is_path(&result, "Result") {219		let generic_arg = match args {220			PathArguments::AngleBracketed(params) => params.args.iter().next().unwrap(),221			_ => return Err(Error::new(args.span(), "missing result generic")),222		};223		// This argument must be a type:224		match generic_arg {225			GenericArgument::Type(ty) => ty,226			_ => {227				return Err(Error::new(228					generic_arg.span(),229					"option generic should be a type",230				))231			}232		}233	} else {234		return Err(Error::new(result.span(), "return value should be result"));235	};236237	let name = fun.sig.ident.to_string();238	let args = fun239		.sig240		.inputs241		.iter()242		.map(|arg| ArgInfo::parse(&name, arg))243		.collect::<Result<Vec<_>>>()?;244245	let params_desc = args.iter().flat_map(|a| match a {246		ArgInfo::Normal {247			is_option,248			name,249			cfg_attrs,250			..251		} => {252			let name = name253				.as_ref()254				.map(|n| quote! {Some(std::borrow::Cow::Borrowed(#n))})255				.unwrap_or_else(|| quote! {None});256			Some(quote! {257				#(#cfg_attrs)*258				BuiltinParam {259					name: #name,260					has_default: #is_option,261				},262			})263		}264		ArgInfo::Lazy { is_option, name } => {265			let name = name266				.as_ref()267				.map(|n| quote! {Some(std::borrow::Cow::Borrowed(#n))})268				.unwrap_or_else(|| quote! {None});269			Some(quote! {270				BuiltinParam {271					name: #name,272					has_default: #is_option,273				},274			})275		}276		ArgInfo::Context => None,277		ArgInfo::Location => None,278		ArgInfo::This => None,279	});280281	let mut id = 0usize;282	let pass = args283		.iter()284		.map(|a| match a {285			ArgInfo::Normal { .. } | ArgInfo::Lazy { .. } => {286				let cid = id;287				id += 1;288				(quote! {#cid}, a)289			}290			ArgInfo::Context | ArgInfo::Location | ArgInfo::This => {291				(quote! {compile_error!("should not use id")}, a)292			}293		})294		.map(|(id, a)| match a {295			ArgInfo::Normal {296				ty,297				is_option,298				name,299				cfg_attrs,300			} => {301				let name = name.as_ref().map(|v| v.as_str()).unwrap_or("<unnamed>");302				let eval = quote! {jrsonnet_evaluator::State::push_description(303					|| format!("argument <{}> evaluation", #name),304					|| <#ty>::from_untyped(value.evaluate()?),305				)?};306				let value = if *is_option {307					quote! {if let Some(value) = &parsed[#id] {308						Some(#eval)309					} else {310						None311					},}312				} else {313					quote! {{314						let value = parsed[#id].as_ref().expect("args shape is checked");315						#eval316					},}317				};318				quote! {319					#(#cfg_attrs)*320					#value321				}322			}323			ArgInfo::Lazy { is_option, .. } => {324				if *is_option {325					quote! {if let Some(value) = &parsed[#id] {326						Some(value.clone())327					} else {328						None329					}}330				} else {331					quote! {332						parsed[#id].as_ref().expect("args shape is correct").clone(),333					}334				}335			}336			ArgInfo::Context => quote! {ctx.clone(),},337			ArgInfo::Location => quote! {location,},338			ArgInfo::This => quote! {self,},339		});340341	let fields = attr.fields.iter().map(|field| {342		let name = &field.name;343		let ty = &field.ty;344		quote! {345			pub #name: #ty,346		}347	});348349	let name = &fun.sig.ident;350	let vis = &fun.vis;351	let static_ext = if attr.fields.is_empty() {352		quote! {353			impl #name {354				pub const INST: &'static dyn StaticBuiltin = &#name {};355			}356			impl StaticBuiltin for #name {}357		}358	} else {359		quote! {}360	};361	let static_derive_copy = if attr.fields.is_empty() {362		quote! {, Copy}363	} else {364		quote! {}365	};366367	Ok(quote! {368		#fun369		#[doc(hidden)]370		#[allow(non_camel_case_types)]371		#[derive(Clone, jrsonnet_gcmodule::Trace #static_derive_copy)]372		#vis struct #name {373			#(#fields)*374		}375		const _: () = {376			use ::jrsonnet_evaluator::{377				State, Val,378				function::{builtin::{Builtin, StaticBuiltin, BuiltinParam}, CallLocation, ArgsLike, parse::parse_builtin_call},379				error::Result, Context, typed::Typed,380				parser::ExprLocation,381			};382			const PARAMS: &'static [BuiltinParam] = &[383				#(#params_desc)*384			];385386			#static_ext387			impl Builtin for #name388			where389				Self: 'static390			{391				fn name(&self) -> &str {392					stringify!(#name)393				}394				fn params(&self) -> &[BuiltinParam] {395					PARAMS396				}397				fn call(&self, ctx: Context, location: CallLocation, args: &dyn ArgsLike) -> Result<Val> {398					let parsed = parse_builtin_call(ctx.clone(), &PARAMS, args, false)?;399400					let result: #result = #name(#(#pass)*);401					let result = result?;402					<#result_inner>::into_untyped(result)403				}404			}405		};406	})407}408409#[derive(Default)]410struct TypedAttr {411	rename: Option<String>,412	flatten: bool,413	/// flatten(ok) strategy for flattened optionals414	/// field would be None in case of any parsing error (as in serde)415	flatten_ok: bool,416}417impl Parse for TypedAttr {418	fn parse(input: ParseStream) -> syn::Result<Self> {419		let mut out = Self::default();420		loop {421			let lookahead = input.lookahead1();422			if lookahead.peek(kw::rename) {423				input.parse::<kw::rename>()?;424				input.parse::<Token![=]>()?;425				let name = input.parse::<LitStr>()?;426				if out.rename.is_some() {427					return Err(Error::new(428						name.span(),429						"rename attribute may only be specified once",430					));431				}432				out.rename = Some(name.value());433			} else if lookahead.peek(kw::flatten) {434				input.parse::<kw::flatten>()?;435				out.flatten = true;436				if input.peek(token::Paren) {437					let content;438					parenthesized!(content in input);439					let lookahead = content.lookahead1();440					if lookahead.peek(kw::ok) {441						content.parse::<kw::ok>()?;442						out.flatten_ok = true;443					} else {444						return Err(lookahead.error());445					}446				}447			} else if input.is_empty() {448				break;449			} else {450				return Err(lookahead.error());451			}452			if input.peek(Token![,]) {453				input.parse::<Token![,]>()?;454			} else {455				break;456			}457		}458		// input.parse::<kw::rename>()?;459		// input.parse::<Token![=]>()?;460		// let rename = input.parse::<LitStr>()?.value();461		Ok(out)462	}463}464465struct TypedField {466	attr: TypedAttr,467	ident: Ident,468	ty: Type,469	is_option: bool,470}471impl TypedField {472	fn parse(field: &syn::Field) -> Result<Self> {473		let attr = parse_attr::<TypedAttr, _>(&field.attrs, "typed")?.unwrap_or_default();474		let ident = if let Some(ident) = field.ident.clone() {475			ident476		} else {477			return Err(Error::new(478				field.span(),479				"this field should appear in output object, but it has no visible name",480			));481		};482		let (is_option, ty) = if let Some(ty) = extract_type_from_option(&field.ty)? {483			(true, ty.clone())484		} else {485			(false, field.ty.clone())486		};487		if is_option && attr.flatten {488			if !attr.flatten_ok {489				return Err(Error::new(490					field.span(),491					"strategy should be set when flattening Option",492				));493			}494		} else if attr.flatten_ok {495			return Err(Error::new(496				field.span(),497				"flatten(ok) is only useable on optional fields",498			));499		}500501		Ok(Self {502			attr,503			ident,504			ty,505			is_option,506		})507	}508	/// None if this field is flattened in jsonnet output509	fn name(&self) -> Option<String> {510		if self.attr.flatten {511			return None;512		}513		Some(514			self.attr515				.rename516				.clone()517				.unwrap_or_else(|| self.ident.to_string()),518		)519	}520521	fn expand_field(&self) -> Option<TokenStream> {522		if self.is_option {523			return None;524		}525		let name = self.name()?;526		let ty = &self.ty;527		Some(quote! {528			(#name, <#ty>::TYPE)529		})530	}531	fn expand_parse(&self) -> TokenStream {532		let ident = &self.ident;533		let ty = &self.ty;534		if self.attr.flatten {535			// optional flatten is handled in same way as serde536			return if self.is_option {537				quote! {538					#ident: <#ty>::parse(&obj).ok(),539				}540			} else {541				quote! {542					#ident: <#ty>::parse(&obj)?,543				}544			};545		};546547		let name = self.name().unwrap();548		let value = if self.is_option {549			quote! {550				if let Some(value) = obj.get(#name.into())? {551					Some(<#ty>::from_untyped(value)?)552				} else {553					None554				}555			}556		} else {557			quote! {558				<#ty>::from_untyped(obj.get(#name.into())?.ok_or_else(|| Error::NoSuchField(#name.into(), vec![]))?)?559			}560		};561562		quote! {563			#ident: #value,564		}565	}566	fn expand_serialize(&self) -> Result<TokenStream> {567		let ident = &self.ident;568		let ty = &self.ty;569		Ok(if let Some(name) = self.name() {570			if self.is_option {571				quote! {572					if let Some(value) = self.#ident {573						out.member(#name.into()).value(<#ty>::into_untyped(value)?)?;574					}575				}576			} else {577				quote! {578					out.member(#name.into()).value(<#ty>::into_untyped(self.#ident)?)?;579				}580			}581		} else if self.is_option {582			quote! {583				if let Some(value) = self.#ident {584					value.serialize(out)?;585				}586			}587		} else {588			quote! {589				self.#ident.serialize(out)?;590			}591		})592	}593}594595#[proc_macro_derive(Typed, attributes(typed))]596pub fn derive_typed(item: proc_macro::TokenStream) -> proc_macro::TokenStream {597	let input = parse_macro_input!(item as DeriveInput);598599	match derive_typed_inner(input) {600		Ok(v) => v.into(),601		Err(e) => e.to_compile_error().into(),602	}603}604605fn derive_typed_inner(input: DeriveInput) -> Result<TokenStream> {606	let data = match &input.data {607		syn::Data::Struct(s) => s,608		_ => return Err(Error::new(input.span(), "only structs supported")),609	};610611	let ident = &input.ident;612	let fields = data613		.fields614		.iter()615		.map(TypedField::parse)616		.collect::<Result<Vec<_>>>()?;617618	let typed = {619		let fields = fields620			.iter()621			.flat_map(TypedField::expand_field)622			.collect::<Vec<_>>();623		let len = fields.len();624		quote! {625			const ITEMS: [(&'static str, &'static ComplexValType); #len] = [626				#(#fields,)*627			];628			impl Typed for #ident {629				const TYPE: &'static ComplexValType = &ComplexValType::ObjectRef(&ITEMS);630631				fn from_untyped(value: Val) -> Result<Self> {632					let obj = value.as_obj().expect("shape is correct");633					Self::parse(&obj)634				}635636				fn into_untyped(value: Self) -> Result<Val> {637					let mut out = ObjValueBuilder::new();638					value.serialize(&mut out)?;639					Ok(Val::Obj(out.build()))640				}641642			}643		}644	};645646	let fields_parse = fields.iter().map(TypedField::expand_parse);647	let fields_serialize = fields648		.iter()649		.map(TypedField::expand_serialize)650		.collect::<Result<Vec<_>>>()?;651652	Ok(quote! {653		const _: () = {654			use ::jrsonnet_evaluator::{655				typed::{ComplexValType, Typed, TypedObj, CheckType},656				Val, State,657				error::{LocError, Error, Result},658				ObjValueBuilder, ObjValue,659			};660661			#typed662663			impl TypedObj for #ident {664				fn serialize(self, out: &mut ObjValueBuilder) -> Result<(), LocError> {665					#(#fields_serialize)*666667					Ok(())668				}669				fn parse(obj: &ObjValue) -> Result<Self, LocError> {670					Ok(Self {671						#(#fields_parse)*672					})673				}674			}675		};676	})677}
modifiedcrates/jrsonnet-parser/src/source.rsdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/source.rs
+++ b/crates/jrsonnet-parser/src/source.rs
@@ -32,10 +32,8 @@
 			self.hash(&mut hasher)
 		}
 		fn dyn_eq(&self, other: &dyn $T) -> bool {
-			let other = if let Some(v) = other.as_any().downcast_ref::<Self>() {
-				v
-			} else {
-				return false;
+			let Some(other) = other.as_any().downcast_ref::<Self>() else {
+				return false
 			};
 			let this = <Self as $T>::as_any(self)
 				.downcast_ref::<Self>()
modifiedtests/tests/sanity.rsdiffbeforeafterboth
--- a/tests/tests/sanity.rs
+++ b/tests/tests/sanity.rs
@@ -22,17 +22,15 @@
 	s.with_stdlib();
 
 	{
-		let e = match s.evaluate_snippet("snip".to_owned(), "assert 1 == 2: 'fail'; null") {
-			Ok(_) => throw!("assertion should fail"),
-			Err(e) => e,
+		let Err(e) = s.evaluate_snippet("snip".to_owned(), "assert 1 == 2: 'fail'; null") else {
+			throw!("assertion should fail");
 		};
 		let e = s.stringify_err(&e);
 		ensure!(e.starts_with("assert failed: fail\n"));
 	}
 	{
-		let e = match s.evaluate_snippet("snip".to_owned(), "std.assertEqual(1, 2)") {
-			Ok(_) => throw!("assertion should fail"),
-			Err(e) => e,
+		let Err(e) = s.evaluate_snippet("snip".to_owned(), "std.assertEqual(1, 2)") else {
+			throw!("assertion should fail")
 		};
 		let e = s.stringify_err(&e);
 		ensure!(e.starts_with("runtime error: Assertion failed. 1 != 2"))