difftreelog
style fix clippy warnings
in: master
8 files changed
crates/jrsonnet-evaluator/src/arr/mod.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/arr/mod.rs
+++ b/crates/jrsonnet-evaluator/src/arr/mod.rs
@@ -1,7 +1,4 @@
-use std::{
- any::Any,
- num::{NonZeroU32, NonZeroUsize},
-};
+use std::{any::Any, num::NonZeroU32};
use jrsonnet_gcmodule::{Cc, Trace};
use jrsonnet_interner::IBytes;
crates/jrsonnet-evaluator/src/evaluate/destructure.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/evaluate/destructure.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/destructure.rs
@@ -110,7 +110,11 @@
fn get(self: Box<Self>) -> Result<Self::Output> {
let full = self.full.evaluate()?;
let to = full.len() - self.end;
- Ok(Val::Arr(full.slice(Some(self.start as i32), Some(to as i32), None)))
+ Ok(Val::Arr(full.slice(
+ Some(self.start as i32),
+ Some(to as i32),
+ None,
+ )))
}
}
crates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/val.rs
+++ b/crates/jrsonnet-evaluator/src/val.rs
@@ -2,7 +2,7 @@
cell::RefCell,
fmt::{self, Debug, Display},
mem::replace,
- num::{NonZeroU32, NonZeroUsize},
+ num::NonZeroU32,
rc::Rc,
};
crates/jrsonnet-macros/src/lib.rsdiffbeforeafterboth1use std::string::String;23use proc_macro2::TokenStream;4use quote::quote;5use syn::{6 parenthesized,7 parse::{Parse, ParseStream},8 parse_macro_input,9 punctuated::Punctuated,10 spanned::Spanned,11 token::{self, Comma},12 Attribute, DeriveInput, Error, Expr, FnArg, GenericArgument, Ident, ItemFn, LitStr, Pat, Path,13 PathArguments, Result, ReturnType, Token, Type,14};1516fn parse_attr<A: Parse, I>(attrs: &[Attribute], ident: I) -> Result<Option<A>>17where18 Ident: PartialEq<I>,19{20 let attrs = attrs21 .iter()22 .filter(|a| a.path().is_ident(&ident))23 .collect::<Vec<_>>();24 if attrs.len() > 1 {25 return Err(Error::new(26 attrs[1].span(),27 "this attribute may be specified only once",28 ));29 } else if attrs.is_empty() {30 return Ok(None);31 }32 let attr = attrs[0];33 let attr = attr.parse_args::<A>()?;3435 Ok(Some(attr))36}37fn remove_attr<I>(attrs: &mut Vec<Attribute>, ident: I)38where39 Ident: PartialEq<I>,40{41 attrs.retain(|a| !a.path().is_ident(&ident));42}4344fn path_is(path: &Path, needed: &str) -> bool {45 path.leading_colon.is_none()46 && !path.segments.is_empty()47 && path.segments.iter().last().unwrap().ident == needed48}4950fn type_is_path<'ty>(ty: &'ty Type, needed: &str) -> Option<&'ty PathArguments> {51 match ty {52 Type::Path(path) if path.qself.is_none() && path_is(&path.path, needed) => {53 let args = &path.path.segments.iter().last().unwrap().arguments;54 Some(args)55 }56 _ => None,57 }58}5960fn extract_type_from_option(ty: &Type) -> Result<Option<&Type>> {61 let Some(args) = type_is_path(ty, "Option") else {62 return Ok(None);63 };64 // It should have only on angle-bracketed param ("<String>"):65 let PathArguments::AngleBracketed(params) = args else {66 return Err(Error::new(args.span(), "missing option generic"));67 };68 let generic_arg = params.args.iter().next().unwrap();69 // This argument must be a type:70 let GenericArgument::Type(ty) = generic_arg else {71 return Err(Error::new(72 generic_arg.span(),73 "option generic should be a type",74 ));75 };76 Ok(Some(ty))77}7879struct Field {80 attrs: Vec<Attribute>,81 name: Ident,82 _colon: Token![:],83 ty: Type,84}85impl Parse for Field {86 fn parse(input: ParseStream) -> syn::Result<Self> {87 Ok(Self {88 attrs: input.call(Attribute::parse_outer)?,89 name: input.parse()?,90 _colon: input.parse()?,91 ty: input.parse()?,92 })93 }94}9596mod kw {97 syn::custom_keyword!(fields);98 syn::custom_keyword!(rename);99 syn::custom_keyword!(flatten);100 syn::custom_keyword!(add);101 syn::custom_keyword!(hide);102 syn::custom_keyword!(ok);103}104105struct EmptyAttr;106impl Parse for EmptyAttr {107 fn parse(_input: ParseStream) -> Result<Self> {108 Ok(Self)109 }110}111112struct BuiltinAttrs {113 fields: Vec<Field>,114}115impl Parse for BuiltinAttrs {116 fn parse(input: ParseStream) -> syn::Result<Self> {117 if input.is_empty() {118 return Ok(Self { fields: Vec::new() });119 }120 input.parse::<kw::fields>()?;121 let fields;122 parenthesized!(fields in input);123 let p = Punctuated::<Field, Comma>::parse_terminated(&fields)?;124 Ok(Self {125 fields: p.into_iter().collect(),126 })127 }128}129130enum Optionality {131 Required,132 Optional,133 Default(Expr),134}135impl Optionality {136 fn is_optional(&self) -> bool {137 !matches!(self, Self::Required)138 }139}140141enum ArgInfo {142 Normal {143 ty: Box<Type>,144 optionality: Optionality,145 name: Option<String>,146 cfg_attrs: Vec<Attribute>,147 },148 Lazy {149 is_option: bool,150 name: Option<String>,151 },152 Context,153 Location,154 This,155}156157impl ArgInfo {158 fn parse(name: &str, arg: &mut FnArg) -> Result<Self> {159 let FnArg::Typed(arg) = arg else {160 unreachable!()161 };162 let ident = match &arg.pat as &Pat {163 Pat::Ident(i) => Some(i.ident.clone()),164 _ => None,165 };166 let ty = &arg.ty;167 if type_is_path(ty, "Context").is_some() {168 return Ok(Self::Context);169 } else if type_is_path(ty, "CallLocation").is_some() {170 return Ok(Self::Location);171 } else if type_is_path(ty, "Thunk").is_some() {172 return Ok(Self::Lazy {173 is_option: false,174 name: ident.map(|v| v.to_string()),175 });176 }177178 match ty as &Type {179 Type::Reference(r) if type_is_path(&r.elem, name).is_some() => return Ok(Self::This),180 _ => {}181 }182183 let (optionality, ty) = if let Some(default) = parse_attr::<_, _>(&arg.attrs, "default")? {184 remove_attr(&mut arg.attrs, "default");185 (Optionality::Default(default), ty.clone())186 } else if let Some(ty) = extract_type_from_option(ty)? {187 if type_is_path(ty, "Thunk").is_some() {188 return Ok(Self::Lazy {189 is_option: true,190 name: ident.map(|v| v.to_string()),191 });192 }193194 (Optionality::Optional, Box::new(ty.clone()))195 } else {196 (Optionality::Required, ty.clone())197 };198199 let cfg_attrs = arg200 .attrs201 .iter()202 .filter(|a| a.path().is_ident("cfg"))203 .cloned()204 .collect();205206 Ok(Self::Normal {207 ty,208 optionality,209 name: ident.map(|v| v.to_string()),210 cfg_attrs,211 })212 }213}214215#[proc_macro_attribute]216pub fn builtin(217 attr: proc_macro::TokenStream,218 item: proc_macro::TokenStream,219) -> proc_macro::TokenStream {220 let attr = parse_macro_input!(attr as BuiltinAttrs);221 let item_fn = item.clone();222 let item_fn: ItemFn = parse_macro_input!(item_fn);223224 match builtin_inner(attr, item_fn) {225 Ok(v) => v.into(),226 Err(e) => e.into_compile_error().into(),227 }228}229230#[allow(clippy::too_many_lines)]231fn builtin_inner(attr: BuiltinAttrs, mut fun: ItemFn) -> syn::Result<TokenStream> {232 let ReturnType::Type(_, result) = &fun.sig.output else {233 return Err(Error::new(234 fun.sig.span(),235 "builtin should return something",236 ));237 };238239 let name = fun.sig.ident.to_string();240 let args = fun241 .sig242 .inputs243 .iter_mut()244 .map(|arg| ArgInfo::parse(&name, arg))245 .collect::<Result<Vec<_>>>()?;246247 let params_desc = args.iter().filter_map(|a| match a {248 ArgInfo::Normal {249 optionality,250 name,251 cfg_attrs,252 ..253 } => {254 let name = name255 .as_ref()256 .map_or_else(|| quote! {None}, |n| quote! {ParamName::new_static(#n)});257 let is_optional = optionality.is_optional();258 Some(quote! {259 #(#cfg_attrs)*260 BuiltinParam::new(#name, #is_optional),261 })262 }263 ArgInfo::Lazy { is_option, name } => {264 let name = name265 .as_ref()266 .map_or_else(|| quote! {None}, |n| quote! {ParamName::new_static(#n)});267 Some(quote! {268 BuiltinParam::new(#name, #is_option),269 })270 }271 ArgInfo::Context | ArgInfo::Location | ArgInfo::This => None,272 });273274 let mut id = 0usize;275 let pass = args276 .iter()277 .map(|a| match a {278 ArgInfo::Normal { .. } | ArgInfo::Lazy { .. } => {279 let cid = id;280 id += 1;281 (quote! {#cid}, a)282 }283 ArgInfo::Context | ArgInfo::Location | ArgInfo::This => {284 (quote! {compile_error!("should not use id")}, a)285 }286 })287 .map(|(id, a)| match a {288 ArgInfo::Normal {289 ty,290 optionality,291 name,292 cfg_attrs,293 } => {294 let name = name.as_ref().map_or("<unnamed>", String::as_str);295 let eval = quote! {jrsonnet_evaluator::State::push_description(296 || format!("argument <{}> evaluation", #name),297 || <#ty>::from_untyped(value.evaluate()?),298 )?};299 let value = match optionality {300 Optionality::Required => quote! {{301 let value = parsed[#id].as_ref().expect("args shape is checked");302 #eval303 },},304 Optionality::Optional => quote! {if let Some(value) = &parsed[#id] {305 Some(#eval)306 } else {307 None308 },},309 Optionality::Default(expr) => quote! {if let Some(value) = &parsed[#id] {310 #eval311 } else {312 let v: #ty = #expr;313 v314 },},315 };316 quote! {317 #(#cfg_attrs)*318 #value319 }320 }321 ArgInfo::Lazy { is_option, .. } => {322 if *is_option {323 quote! {if let Some(value) = &parsed[#id] {324 Some(value.clone())325 } else {326 None327 },}328 } else {329 quote! {330 parsed[#id].as_ref().expect("args shape is correct").clone(),331 }332 }333 }334 ArgInfo::Context => quote! {ctx.clone(),},335 ArgInfo::Location => quote! {location,},336 ArgInfo::This => quote! {self,},337 });338339 let fields = attr.fields.iter().map(|field| {340 let attrs = &field.attrs;341 let name = &field.name;342 let ty = &field.ty;343 quote! {344 #(#attrs)*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 #fun369370 #[doc(hidden)]371 #[allow(non_camel_case_types)]372 #[derive(Clone, jrsonnet_gcmodule::Trace #static_derive_copy)]373 #vis struct #name {374 #(#fields)*375 }376 const _: () = {377 use ::jrsonnet_evaluator::{378 State, Val,379 function::{builtin::{Builtin, StaticBuiltin, BuiltinParam, ParamName}, CallLocation, ArgsLike, parse::parse_builtin_call},380 Result, Context, typed::Typed,381 parser::ExprLocation,382 };383 const PARAMS: &'static [BuiltinParam] = &[384 #(#params_desc)*385 ];386387 #static_ext388 impl Builtin for #name389 where390 Self: 'static391 {392 fn name(&self) -> &str {393 stringify!(#name)394 }395 fn params(&self) -> &[BuiltinParam] {396 PARAMS397 }398 #[allow(unused_variables)]399 fn call(&self, ctx: Context, location: CallLocation, args: &dyn ArgsLike) -> Result<Val> {400 let parsed = parse_builtin_call(ctx.clone(), &PARAMS, args, false)?;401402 let result: #result = #name(#(#pass)*);403 <_ as Typed>::into_result(result)404 }405 fn as_any(&self) -> &dyn ::std::any::Any {406 self407 }408 }409 };410 })411}412413#[derive(Default)]414#[allow(clippy::struct_excessive_bools)]415struct TypedAttr {416 rename: Option<String>,417 flatten: bool,418 /// flatten(ok) strategy for flattened optionals419 /// field would be None in case of any parsing error (as in serde)420 flatten_ok: bool,421 // Should it be `field+:` instead of `field:`422 add: bool,423 // Should it be `field::` instead of `field:`424 hide: bool,425}426impl Parse for TypedAttr {427 fn parse(input: ParseStream) -> syn::Result<Self> {428 let mut out = Self::default();429 loop {430 let lookahead = input.lookahead1();431 if lookahead.peek(kw::rename) {432 input.parse::<kw::rename>()?;433 input.parse::<Token![=]>()?;434 let name = input.parse::<LitStr>()?;435 if out.rename.is_some() {436 return Err(Error::new(437 name.span(),438 "rename attribute may only be specified once",439 ));440 }441 out.rename = Some(name.value());442 } else if lookahead.peek(kw::flatten) {443 input.parse::<kw::flatten>()?;444 out.flatten = true;445 if input.peek(token::Paren) {446 let content;447 parenthesized!(content in input);448 let lookahead = content.lookahead1();449 if lookahead.peek(kw::ok) {450 content.parse::<kw::ok>()?;451 out.flatten_ok = true;452 } else {453 return Err(lookahead.error());454 }455 }456 } else if lookahead.peek(kw::add) {457 input.parse::<kw::add>()?;458 out.add = true;459 } else if lookahead.peek(kw::hide) {460 input.parse::<kw::hide>()?;461 out.hide = true;462 } else if input.is_empty() {463 break;464 } else {465 return Err(lookahead.error());466 }467 if input.peek(Token![,]) {468 input.parse::<Token![,]>()?;469 } else {470 break;471 }472 }473 Ok(out)474 }475}476477struct TypedField {478 attr: TypedAttr,479 ident: Ident,480 ty: Type,481 is_option: bool,482}483impl TypedField {484 fn parse(field: &syn::Field) -> Result<Self> {485 let attr = parse_attr::<TypedAttr, _>(&field.attrs, "typed")?.unwrap_or_default();486 let Some(ident) = field.ident.clone() else {487 return Err(Error::new(488 field.span(),489 "this field should appear in output object, but it has no visible name",490 ));491 };492 let (is_option, ty) = extract_type_from_option(&field.ty)?493 .map_or_else(|| (false, field.ty.clone()), |ty| (true, ty.clone()));494 if is_option && attr.flatten {495 if !attr.flatten_ok {496 return Err(Error::new(497 field.span(),498 "strategy should be set when flattening Option",499 ));500 }501 } else if attr.flatten_ok {502 return Err(Error::new(503 field.span(),504 "flatten(ok) is only useable on optional fields",505 ));506 }507508 Ok(Self {509 attr,510 ident,511 ty,512 is_option,513 })514 }515 /// None if this field is flattened in jsonnet output516 fn name(&self) -> Option<String> {517 if self.attr.flatten {518 return None;519 }520 Some(521 self.attr522 .rename523 .clone()524 .unwrap_or_else(|| self.ident.to_string()),525 )526 }527528 fn expand_field(&self) -> Option<TokenStream> {529 if self.is_option {530 return None;531 }532 let name = self.name()?;533 let ty = &self.ty;534 Some(quote! {535 (#name, <#ty as Typed>::TYPE)536 })537 }538 fn expand_parse(&self) -> TokenStream {539 let ident = &self.ident;540 let ty = &self.ty;541 if self.attr.flatten {542 // optional flatten is handled in same way as serde543 return if self.is_option {544 quote! {545 #ident: <#ty as TypedObj>::parse(&obj).ok(),546 }547 } else {548 quote! {549 #ident: <#ty as TypedObj>::parse(&obj)?,550 }551 };552 };553554 let name = self.name().unwrap();555 let value = if self.is_option {556 quote! {557 if let Some(value) = obj.get(#name.into())? {558 Some(<#ty as Typed>::from_untyped(value)?)559 } else {560 None561 }562 }563 } else {564 quote! {565 <#ty as Typed>::from_untyped(obj.get(#name.into())?.ok_or_else(|| ErrorKind::NoSuchField(#name.into(), vec![]))?)?566 }567 };568569 quote! {570 #ident: #value,571 }572 }573 fn expand_serialize(&self) -> TokenStream {574 let ident = &self.ident;575 let ty = &self.ty;576 self.name().map_or_else(577 || {578 if self.is_option {579 quote! {580 if let Some(value) = self.#ident {581 <#ty as TypedObj>::serialize(value, out)?;582 }583 }584 } else {585 quote! {586 <#ty as TypedObj>::serialize(self.#ident, out)?;587 }588 }589 },590 |name| {591 let hide = if self.attr.hide {592 quote! {.hide()}593 } else {594 quote! {}595 };596 let add = if self.attr.add {597 quote! {.add()}598 } else {599 quote! {}600 };601 if self.is_option {602 quote! {603 if let Some(value) = self.#ident {604 out.field(#name)605 #hide606 #add607 .try_value(<#ty as Typed>::into_untyped(value)?)?;608 }609 }610 } else {611 quote! {612 out.field(#name)613 #hide614 #add615 .try_value(<#ty as Typed>::into_untyped(self.#ident)?)?;616 }617 }618 },619 )620 }621}622623#[proc_macro_derive(Typed, attributes(typed))]624pub fn derive_typed(item: proc_macro::TokenStream) -> proc_macro::TokenStream {625 let input = parse_macro_input!(item as DeriveInput);626627 match derive_typed_inner(input) {628 Ok(v) => v.into(),629 Err(e) => e.to_compile_error().into(),630 }631}632633fn derive_typed_inner(input: DeriveInput) -> Result<TokenStream> {634 let syn::Data::Struct(data) = &input.data else {635 return Err(Error::new(input.span(), "only structs supported"));636 };637638 let ident = &input.ident;639 let fields = data640 .fields641 .iter()642 .map(TypedField::parse)643 .collect::<Result<Vec<_>>>()?;644645 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();646647 let typed = {648 let fields = fields649 .iter()650 .filter_map(TypedField::expand_field)651 .collect::<Vec<_>>();652 quote! {653 impl #impl_generics Typed for #ident #ty_generics #where_clause {654 const TYPE: &'static ComplexValType = &ComplexValType::ObjectRef(&[655 #(#fields,)*656 ]);657658 fn from_untyped(value: Val) -> JrResult<Self> {659 let obj = value.as_obj().expect("shape is correct");660 Self::parse(&obj)661 }662663 fn into_untyped(value: Self) -> JrResult<Val> {664 let mut out = ObjValueBuilder::new();665 value.serialize(&mut out)?;666 Ok(Val::Obj(out.build()))667 }668669 }670 }671 };672673 let fields_parse = fields.iter().map(TypedField::expand_parse);674 let fields_serialize = fields675 .iter()676 .map(TypedField::expand_serialize)677 .collect::<Vec<_>>();678679 Ok(quote! {680 const _: () = {681 use ::jrsonnet_evaluator::{682 typed::{ComplexValType, Typed, TypedObj, CheckType},683 Val, State,684 error::{ErrorKind, Result as JrResult},685 ObjValueBuilder, ObjValue,686 };687688 #typed689690 impl #impl_generics TypedObj for #ident #ty_generics #where_clause {691 fn serialize(self, out: &mut ObjValueBuilder) -> JrResult<()> {692 #(#fields_serialize)*693694 Ok(())695 }696 fn parse(obj: &ObjValue) -> JrResult<Self> {697 Ok(Self {698 #(#fields_parse)*699 })700 }701 }702 };703 })704}705706struct FormatInput {707 formatting: LitStr,708 arguments: Vec<Expr>,709}710impl Parse for FormatInput {711 fn parse(input: ParseStream) -> Result<Self> {712 let formatting = input.parse()?;713 let mut arguments = Vec::new();714715 while input.peek(Token![,]) {716 input.parse::<Token![,]>()?;717 if input.is_empty() {718 // Trailing comma719 break;720 }721 let expr = input.parse()?;722 arguments.push(expr);723 }724725 if !input.is_empty() {726 return Err(syn::Error::new(input.span(), "unexpected trailing input"));727 }728729 Ok(Self {730 formatting,731 arguments,732 })733 }734}735fn is_format_str(i: &str) -> bool {736 let mut is_plain = true;737 // -1 = {738 // +1 = }739 let mut is_bracket = 0i8;740 for ele in i.chars() {741 match ele {742 '{' if is_bracket == -1 => {743 is_bracket = 0;744 }745 '}' if is_bracket == -1 => {746 is_plain = false;747 break;748 }749 '}' if is_bracket == 1 => {750 is_bracket = 0;751 }752 '{' if is_bracket == 1 => {753 is_plain = false;754 break;755 }756 '{' => {757 is_bracket = -1;758 }759 '}' => {760 is_bracket = 1;761 }762 _ if is_bracket != 0 => {763 is_plain = false;764 break;765 }766 _ => {}767 }768 }769 !is_plain || is_bracket != 0770}771impl FormatInput {772 fn expand(self) -> TokenStream {773 let format = self.formatting;774 if is_format_str(&format.value()) {775 let args = self.arguments;776 quote! {777 ::jrsonnet_evaluator::IStr::from(format!(#format #(, #args)*))778 }779 } else {780 if let Some(first) = self.arguments.first() {781 return syn::Error::new(782 first.span(),783 "string has no formatting codes, it should not have the arguments",784 )785 .into_compile_error();786 }787 quote! {788 ::jrsonnet_evaluator::IStr::from(#format)789 }790 }791 }792}793794/// `IStr` formatting helper795///796/// Using `format!("literal with no codes").into()` is slower than just `"literal with no codes".into()`797/// This macro looks for formatting codes in the input string, and uses798/// `format!()` only when necessary799#[proc_macro]800pub fn format_istr(input: proc_macro::TokenStream) -> proc_macro::TokenStream {801 let input = parse_macro_input!(input as FormatInput);802 input.expand().into()803}1use std::string::String;23use proc_macro2::TokenStream;4use quote::quote;5use syn::{6 parenthesized,7 parse::{Parse, ParseStream},8 parse_macro_input,9 punctuated::Punctuated,10 spanned::Spanned,11 token::{self, Comma},12 Attribute, DeriveInput, Error, Expr, FnArg, GenericArgument, Ident, ItemFn, LitStr, Pat, Path,13 PathArguments, Result, ReturnType, Token, Type,14};1516fn parse_attr<A: Parse, I>(attrs: &[Attribute], ident: I) -> Result<Option<A>>17where18 Ident: PartialEq<I>,19{20 let attrs = attrs21 .iter()22 .filter(|a| a.path().is_ident(&ident))23 .collect::<Vec<_>>();24 if attrs.len() > 1 {25 return Err(Error::new(26 attrs[1].span(),27 "this attribute may be specified only once",28 ));29 } else if attrs.is_empty() {30 return Ok(None);31 }32 let attr = attrs[0];33 let attr = attr.parse_args::<A>()?;3435 Ok(Some(attr))36}37fn remove_attr<I>(attrs: &mut Vec<Attribute>, ident: I)38where39 Ident: PartialEq<I>,40{41 attrs.retain(|a| !a.path().is_ident(&ident));42}4344fn path_is(path: &Path, needed: &str) -> bool {45 path.leading_colon.is_none()46 && !path.segments.is_empty()47 && path.segments.iter().last().unwrap().ident == needed48}4950fn type_is_path<'ty>(ty: &'ty Type, needed: &str) -> Option<&'ty PathArguments> {51 match ty {52 Type::Path(path) if path.qself.is_none() && path_is(&path.path, needed) => {53 let args = &path.path.segments.iter().last().unwrap().arguments;54 Some(args)55 }56 _ => None,57 }58}5960fn extract_type_from_option(ty: &Type) -> Result<Option<&Type>> {61 let Some(args) = type_is_path(ty, "Option") else {62 return Ok(None);63 };64 // It should have only on angle-bracketed param ("<String>"):65 let PathArguments::AngleBracketed(params) = args else {66 return Err(Error::new(args.span(), "missing option generic"));67 };68 let generic_arg = params.args.iter().next().unwrap();69 // This argument must be a type:70 let GenericArgument::Type(ty) = generic_arg else {71 return Err(Error::new(72 generic_arg.span(),73 "option generic should be a type",74 ));75 };76 Ok(Some(ty))77}7879struct Field {80 attrs: Vec<Attribute>,81 name: Ident,82 _colon: Token![:],83 ty: Type,84}85impl Parse for Field {86 fn parse(input: ParseStream) -> syn::Result<Self> {87 Ok(Self {88 attrs: input.call(Attribute::parse_outer)?,89 name: input.parse()?,90 _colon: input.parse()?,91 ty: input.parse()?,92 })93 }94}9596mod kw {97 syn::custom_keyword!(fields);98 syn::custom_keyword!(rename);99 syn::custom_keyword!(flatten);100 syn::custom_keyword!(add);101 syn::custom_keyword!(hide);102 syn::custom_keyword!(ok);103}104105struct EmptyAttr;106impl Parse for EmptyAttr {107 fn parse(_input: ParseStream) -> Result<Self> {108 Ok(Self)109 }110}111112struct BuiltinAttrs {113 fields: Vec<Field>,114}115impl Parse for BuiltinAttrs {116 fn parse(input: ParseStream) -> syn::Result<Self> {117 if input.is_empty() {118 return Ok(Self { fields: Vec::new() });119 }120 input.parse::<kw::fields>()?;121 let fields;122 parenthesized!(fields in input);123 let p = Punctuated::<Field, Comma>::parse_terminated(&fields)?;124 Ok(Self {125 fields: p.into_iter().collect(),126 })127 }128}129130enum Optionality {131 Required,132 Optional,133 Default(Expr),134}135impl Optionality {136 fn is_optional(&self) -> bool {137 !matches!(self, Self::Required)138 }139}140141enum ArgInfo {142 Normal {143 ty: Box<Type>,144 optionality: Optionality,145 name: Option<String>,146 cfg_attrs: Vec<Attribute>,147 },148 Lazy {149 is_option: bool,150 name: Option<String>,151 },152 Context,153 Location,154 This,155}156157impl ArgInfo {158 fn parse(name: &str, arg: &mut FnArg) -> Result<Self> {159 let FnArg::Typed(arg) = arg else {160 unreachable!()161 };162 let ident = match &arg.pat as &Pat {163 Pat::Ident(i) => Some(i.ident.clone()),164 _ => None,165 };166 let ty = &arg.ty;167 if type_is_path(ty, "Context").is_some() {168 return Ok(Self::Context);169 } else if type_is_path(ty, "CallLocation").is_some() {170 return Ok(Self::Location);171 } else if type_is_path(ty, "Thunk").is_some() {172 return Ok(Self::Lazy {173 is_option: false,174 name: ident.map(|v| v.to_string()),175 });176 }177178 match ty as &Type {179 Type::Reference(r) if type_is_path(&r.elem, name).is_some() => return Ok(Self::This),180 _ => {}181 }182183 let (optionality, ty) = if let Some(default) = parse_attr::<_, _>(&arg.attrs, "default")? {184 remove_attr(&mut arg.attrs, "default");185 (Optionality::Default(default), ty.clone())186 } else if let Some(ty) = extract_type_from_option(ty)? {187 if type_is_path(ty, "Thunk").is_some() {188 return Ok(Self::Lazy {189 is_option: true,190 name: ident.map(|v| v.to_string()),191 });192 }193194 (Optionality::Optional, Box::new(ty.clone()))195 } else {196 (Optionality::Required, ty.clone())197 };198199 let cfg_attrs = arg200 .attrs201 .iter()202 .filter(|a| a.path().is_ident("cfg"))203 .cloned()204 .collect();205206 Ok(Self::Normal {207 ty,208 optionality,209 name: ident.map(|v| v.to_string()),210 cfg_attrs,211 })212 }213}214215#[proc_macro_attribute]216pub fn builtin(217 attr: proc_macro::TokenStream,218 item: proc_macro::TokenStream,219) -> proc_macro::TokenStream {220 let attr = parse_macro_input!(attr as BuiltinAttrs);221 let item_fn = parse_macro_input!(item as ItemFn);222223 match builtin_inner(attr, item_fn) {224 Ok(v) => v.into(),225 Err(e) => e.into_compile_error().into(),226 }227}228229#[allow(clippy::too_many_lines)]230fn builtin_inner(attr: BuiltinAttrs, mut fun: ItemFn) -> syn::Result<TokenStream> {231 let ReturnType::Type(_, result) = &fun.sig.output else {232 return Err(Error::new(233 fun.sig.span(),234 "builtin should return something",235 ));236 };237238 let name = fun.sig.ident.to_string();239 let args = fun240 .sig241 .inputs242 .iter_mut()243 .map(|arg| ArgInfo::parse(&name, arg))244 .collect::<Result<Vec<_>>>()?;245246 let params_desc = args.iter().filter_map(|a| match a {247 ArgInfo::Normal {248 optionality,249 name,250 cfg_attrs,251 ..252 } => {253 let name = name254 .as_ref()255 .map_or_else(|| quote! {None}, |n| quote! {ParamName::new_static(#n)});256 let is_optional = optionality.is_optional();257 Some(quote! {258 #(#cfg_attrs)*259 BuiltinParam::new(#name, #is_optional),260 })261 }262 ArgInfo::Lazy { is_option, name } => {263 let name = name264 .as_ref()265 .map_or_else(|| quote! {None}, |n| quote! {ParamName::new_static(#n)});266 Some(quote! {267 BuiltinParam::new(#name, #is_option),268 })269 }270 ArgInfo::Context | ArgInfo::Location | ArgInfo::This => None,271 });272273 let mut id = 0usize;274 let pass = args275 .iter()276 .map(|a| match a {277 ArgInfo::Normal { .. } | ArgInfo::Lazy { .. } => {278 let cid = id;279 id += 1;280 (quote! {#cid}, a)281 }282 ArgInfo::Context | ArgInfo::Location | ArgInfo::This => {283 (quote! {compile_error!("should not use id")}, a)284 }285 })286 .map(|(id, a)| match a {287 ArgInfo::Normal {288 ty,289 optionality,290 name,291 cfg_attrs,292 } => {293 let name = name.as_ref().map_or("<unnamed>", String::as_str);294 let eval = quote! {jrsonnet_evaluator::State::push_description(295 || format!("argument <{}> evaluation", #name),296 || <#ty>::from_untyped(value.evaluate()?),297 )?};298 let value = match optionality {299 Optionality::Required => quote! {{300 let value = parsed[#id].as_ref().expect("args shape is checked");301 #eval302 },},303 Optionality::Optional => quote! {if let Some(value) = &parsed[#id] {304 Some(#eval)305 } else {306 None307 },},308 Optionality::Default(expr) => quote! {if let Some(value) = &parsed[#id] {309 #eval310 } else {311 let v: #ty = #expr;312 v313 },},314 };315 quote! {316 #(#cfg_attrs)*317 #value318 }319 }320 ArgInfo::Lazy { is_option, .. } => {321 if *is_option {322 quote! {if let Some(value) = &parsed[#id] {323 Some(value.clone())324 } else {325 None326 },}327 } else {328 quote! {329 parsed[#id].as_ref().expect("args shape is correct").clone(),330 }331 }332 }333 ArgInfo::Context => quote! {ctx.clone(),},334 ArgInfo::Location => quote! {location,},335 ArgInfo::This => quote! {self,},336 });337338 let fields = attr.fields.iter().map(|field| {339 let attrs = &field.attrs;340 let name = &field.name;341 let ty = &field.ty;342 quote! {343 #(#attrs)*344 pub #name: #ty,345 }346 });347348 let name = &fun.sig.ident;349 let vis = &fun.vis;350 let static_ext = if attr.fields.is_empty() {351 quote! {352 impl #name {353 pub const INST: &'static dyn StaticBuiltin = &#name {};354 }355 impl StaticBuiltin for #name {}356 }357 } else {358 quote! {}359 };360 let static_derive_copy = if attr.fields.is_empty() {361 quote! {, Copy}362 } else {363 quote! {}364 };365366 Ok(quote! {367 #fun368369 #[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, ParamName}, CallLocation, ArgsLike, parse::parse_builtin_call},379 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 #[allow(unused_variables)]398 fn call(&self, ctx: Context, location: CallLocation, args: &dyn ArgsLike) -> Result<Val> {399 let parsed = parse_builtin_call(ctx.clone(), &PARAMS, args, false)?;400401 let result: #result = #name(#(#pass)*);402 <_ as Typed>::into_result(result)403 }404 fn as_any(&self) -> &dyn ::std::any::Any {405 self406 }407 }408 };409 })410}411412#[derive(Default)]413#[allow(clippy::struct_excessive_bools)]414struct TypedAttr {415 rename: Option<String>,416 flatten: bool,417 /// flatten(ok) strategy for flattened optionals418 /// field would be None in case of any parsing error (as in serde)419 flatten_ok: bool,420 // Should it be `field+:` instead of `field:`421 add: bool,422 // Should it be `field::` instead of `field:`423 hide: bool,424}425impl Parse for TypedAttr {426 fn parse(input: ParseStream) -> syn::Result<Self> {427 let mut out = Self::default();428 loop {429 let lookahead = input.lookahead1();430 if lookahead.peek(kw::rename) {431 input.parse::<kw::rename>()?;432 input.parse::<Token![=]>()?;433 let name = input.parse::<LitStr>()?;434 if out.rename.is_some() {435 return Err(Error::new(436 name.span(),437 "rename attribute may only be specified once",438 ));439 }440 out.rename = Some(name.value());441 } else if lookahead.peek(kw::flatten) {442 input.parse::<kw::flatten>()?;443 out.flatten = true;444 if input.peek(token::Paren) {445 let content;446 parenthesized!(content in input);447 let lookahead = content.lookahead1();448 if lookahead.peek(kw::ok) {449 content.parse::<kw::ok>()?;450 out.flatten_ok = true;451 } else {452 return Err(lookahead.error());453 }454 }455 } else if lookahead.peek(kw::add) {456 input.parse::<kw::add>()?;457 out.add = true;458 } else if lookahead.peek(kw::hide) {459 input.parse::<kw::hide>()?;460 out.hide = true;461 } else if input.is_empty() {462 break;463 } else {464 return Err(lookahead.error());465 }466 if input.peek(Token![,]) {467 input.parse::<Token![,]>()?;468 } else {469 break;470 }471 }472 Ok(out)473 }474}475476struct TypedField {477 attr: TypedAttr,478 ident: Ident,479 ty: Type,480 is_option: bool,481}482impl TypedField {483 fn parse(field: &syn::Field) -> Result<Self> {484 let attr = parse_attr::<TypedAttr, _>(&field.attrs, "typed")?.unwrap_or_default();485 let Some(ident) = field.ident.clone() else {486 return Err(Error::new(487 field.span(),488 "this field should appear in output object, but it has no visible name",489 ));490 };491 let (is_option, ty) = extract_type_from_option(&field.ty)?492 .map_or_else(|| (false, field.ty.clone()), |ty| (true, ty.clone()));493 if is_option && attr.flatten {494 if !attr.flatten_ok {495 return Err(Error::new(496 field.span(),497 "strategy should be set when flattening Option",498 ));499 }500 } else if attr.flatten_ok {501 return Err(Error::new(502 field.span(),503 "flatten(ok) is only useable on optional fields",504 ));505 }506507 Ok(Self {508 attr,509 ident,510 ty,511 is_option,512 })513 }514 /// None if this field is flattened in jsonnet output515 fn name(&self) -> Option<String> {516 if self.attr.flatten {517 return None;518 }519 Some(520 self.attr521 .rename522 .clone()523 .unwrap_or_else(|| self.ident.to_string()),524 )525 }526527 fn expand_field(&self) -> Option<TokenStream> {528 if self.is_option {529 return None;530 }531 let name = self.name()?;532 let ty = &self.ty;533 Some(quote! {534 (#name, <#ty as Typed>::TYPE)535 })536 }537 fn expand_parse(&self) -> TokenStream {538 let ident = &self.ident;539 let ty = &self.ty;540 if self.attr.flatten {541 // optional flatten is handled in same way as serde542 return if self.is_option {543 quote! {544 #ident: <#ty as TypedObj>::parse(&obj).ok(),545 }546 } else {547 quote! {548 #ident: <#ty as TypedObj>::parse(&obj)?,549 }550 };551 };552553 let name = self.name().unwrap();554 let value = if self.is_option {555 quote! {556 if let Some(value) = obj.get(#name.into())? {557 Some(<#ty as Typed>::from_untyped(value)?)558 } else {559 None560 }561 }562 } else {563 quote! {564 <#ty as Typed>::from_untyped(obj.get(#name.into())?.ok_or_else(|| ErrorKind::NoSuchField(#name.into(), vec![]))?)?565 }566 };567568 quote! {569 #ident: #value,570 }571 }572 fn expand_serialize(&self) -> TokenStream {573 let ident = &self.ident;574 let ty = &self.ty;575 self.name().map_or_else(576 || {577 if self.is_option {578 quote! {579 if let Some(value) = self.#ident {580 <#ty as TypedObj>::serialize(value, out)?;581 }582 }583 } else {584 quote! {585 <#ty as TypedObj>::serialize(self.#ident, out)?;586 }587 }588 },589 |name| {590 let hide = if self.attr.hide {591 quote! {.hide()}592 } else {593 quote! {}594 };595 let add = if self.attr.add {596 quote! {.add()}597 } else {598 quote! {}599 };600 if self.is_option {601 quote! {602 if let Some(value) = self.#ident {603 out.field(#name)604 #hide605 #add606 .try_value(<#ty as Typed>::into_untyped(value)?)?;607 }608 }609 } else {610 quote! {611 out.field(#name)612 #hide613 #add614 .try_value(<#ty as Typed>::into_untyped(self.#ident)?)?;615 }616 }617 },618 )619 }620}621622#[proc_macro_derive(Typed, attributes(typed))]623pub fn derive_typed(item: proc_macro::TokenStream) -> proc_macro::TokenStream {624 let input = parse_macro_input!(item as DeriveInput);625626 match derive_typed_inner(input) {627 Ok(v) => v.into(),628 Err(e) => e.to_compile_error().into(),629 }630}631632fn derive_typed_inner(input: DeriveInput) -> Result<TokenStream> {633 let syn::Data::Struct(data) = &input.data else {634 return Err(Error::new(input.span(), "only structs supported"));635 };636637 let ident = &input.ident;638 let fields = data639 .fields640 .iter()641 .map(TypedField::parse)642 .collect::<Result<Vec<_>>>()?;643644 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();645646 let typed = {647 let fields = fields648 .iter()649 .filter_map(TypedField::expand_field)650 .collect::<Vec<_>>();651 quote! {652 impl #impl_generics Typed for #ident #ty_generics #where_clause {653 const TYPE: &'static ComplexValType = &ComplexValType::ObjectRef(&[654 #(#fields,)*655 ]);656657 fn from_untyped(value: Val) -> JrResult<Self> {658 let obj = value.as_obj().expect("shape is correct");659 Self::parse(&obj)660 }661662 fn into_untyped(value: Self) -> JrResult<Val> {663 let mut out = ObjValueBuilder::new();664 value.serialize(&mut out)?;665 Ok(Val::Obj(out.build()))666 }667668 }669 }670 };671672 let fields_parse = fields.iter().map(TypedField::expand_parse);673 let fields_serialize = fields674 .iter()675 .map(TypedField::expand_serialize)676 .collect::<Vec<_>>();677678 Ok(quote! {679 const _: () = {680 use ::jrsonnet_evaluator::{681 typed::{ComplexValType, Typed, TypedObj, CheckType},682 Val, State,683 error::{ErrorKind, Result as JrResult},684 ObjValueBuilder, ObjValue,685 };686687 #typed688689 impl #impl_generics TypedObj for #ident #ty_generics #where_clause {690 fn serialize(self, out: &mut ObjValueBuilder) -> JrResult<()> {691 #(#fields_serialize)*692693 Ok(())694 }695 fn parse(obj: &ObjValue) -> JrResult<Self> {696 Ok(Self {697 #(#fields_parse)*698 })699 }700 }701 };702 })703}704705struct FormatInput {706 formatting: LitStr,707 arguments: Vec<Expr>,708}709impl Parse for FormatInput {710 fn parse(input: ParseStream) -> Result<Self> {711 let formatting = input.parse()?;712 let mut arguments = Vec::new();713714 while input.peek(Token![,]) {715 input.parse::<Token![,]>()?;716 if input.is_empty() {717 // Trailing comma718 break;719 }720 let expr = input.parse()?;721 arguments.push(expr);722 }723724 if !input.is_empty() {725 return Err(syn::Error::new(input.span(), "unexpected trailing input"));726 }727728 Ok(Self {729 formatting,730 arguments,731 })732 }733}734fn is_format_str(i: &str) -> bool {735 let mut is_plain = true;736 // -1 = {737 // +1 = }738 let mut is_bracket = 0i8;739 for ele in i.chars() {740 match ele {741 '{' if is_bracket == -1 => {742 is_bracket = 0;743 }744 '}' if is_bracket == -1 => {745 is_plain = false;746 break;747 }748 '}' if is_bracket == 1 => {749 is_bracket = 0;750 }751 '{' if is_bracket == 1 => {752 is_plain = false;753 break;754 }755 '{' => {756 is_bracket = -1;757 }758 '}' => {759 is_bracket = 1;760 }761 _ if is_bracket != 0 => {762 is_plain = false;763 break;764 }765 _ => {}766 }767 }768 !is_plain || is_bracket != 0769}770impl FormatInput {771 fn expand(self) -> TokenStream {772 let format = self.formatting;773 if is_format_str(&format.value()) {774 let args = self.arguments;775 quote! {776 ::jrsonnet_evaluator::IStr::from(format!(#format #(, #args)*))777 }778 } else {779 if let Some(first) = self.arguments.first() {780 return syn::Error::new(781 first.span(),782 "string has no formatting codes, it should not have the arguments",783 )784 .into_compile_error();785 }786 quote! {787 ::jrsonnet_evaluator::IStr::from(#format)788 }789 }790 }791}792793/// `IStr` formatting helper794///795/// Using `format!("literal with no codes").into()` is slower than just `"literal with no codes".into()`796/// This macro looks for formatting codes in the input string, and uses797/// `format!()` only when necessary798#[proc_macro]799pub fn format_istr(input: proc_macro::TokenStream) -> proc_macro::TokenStream {800 let input = parse_macro_input!(input as FormatInput);801 input.expand().into()802}crates/jrsonnet-stdlib/src/manifest/toml.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/manifest/toml.rs
+++ b/crates/jrsonnet-stdlib/src/manifest/toml.rs
@@ -4,7 +4,7 @@
bail,
manifest::{escape_string_json_buf, ManifestFormat},
val::ArrValue,
- IStr, ObjValue, Result, ResultExt, Val, State,
+ IStr, ObjValue, Result, ResultExt, State, Val,
};
pub struct TomlFormat<'s> {
crates/jrsonnet-stdlib/src/manifest/xml.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/manifest/xml.rs
+++ b/crates/jrsonnet-stdlib/src/manifest/xml.rs
@@ -1,9 +1,9 @@
use jrsonnet_evaluator::{
bail,
manifest::{ManifestFormat, ToStringFormat},
- typed::{ComplexValType, Either2, Either4, Typed, ValType},
- val::{ArrValue, IndexableVal},
- Either, ObjValue, Result, ResultExt, Val, State,
+ typed::{ComplexValType, Either2, Typed, ValType},
+ val::ArrValue,
+ Either, ObjValue, Result, ResultExt, State, Val,
};
pub struct XmlJsonmlFormat {
@@ -39,20 +39,20 @@
fn from_untyped(untyped: Val) -> Result<Self> {
let val = <Either![ArrValue, String]>::from_untyped(untyped)
- .with_description(|| format!("parsing JSONML value (an array or string)"))?;
+ .description("parsing JSONML value (an array or string)")?;
let arr = match val {
Either2::A(a) => a,
Either2::B(s) => return Ok(Self::String(s)),
};
- if arr.len() < 1 {
+ if arr.is_empty() {
bail!("JSONML value should have tag (array length should be >=1)");
};
let tag = String::from_untyped(
arr.get(0)
- .with_description(|| "getting JSONML tag")?
+ .description("getting JSONML tag")?
.expect("length checked"),
)
- .with_description(|| format!("parsing JSONML tag"))?;
+ .description("parsing JSONML tag")?;
let (has_attrs, attrs) = if arr.len() >= 2 {
let maybe_attrs = arr
@@ -71,7 +71,7 @@
tag,
attrs,
children: State::push_description(
- || format!("parsing children"),
+ || "parsing children".to_owned(),
|| {
Typed::from_untyped(Val::Arr(arr.slice(
Some(if has_attrs { 2 } else { 1 }),
@@ -100,7 +100,7 @@
} => {
let has_children = !children.is_empty();
buf.push('<');
- buf.push_str(&tag);
+ buf.push_str(tag);
attrs.run_assertions()?;
for (key, value) in attrs.iter(
// Not much sense to preserve order here
@@ -125,12 +125,12 @@
}
buf.push('>');
for child in children {
- manifest_jsonml(&child, buf, opts)?;
+ manifest_jsonml(child, buf, opts)?;
}
if has_children || opts.force_closing {
buf.push('<');
buf.push('/');
- buf.push_str(&tag);
+ buf.push_str(tag);
buf.push('>');
}
Ok(())
@@ -177,8 +177,8 @@
}
if !found {
// No match - no escapes required
- out.push_str(&str);
+ out.push_str(str);
return;
}
- out.push_str(&remaining);
+ out.push_str(remaining);
}
crates/jrsonnet-stdlib/src/misc.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/misc.rs
+++ b/crates/jrsonnet-stdlib/src/misc.rs
@@ -28,8 +28,7 @@
o: ObjValue,
f: IStr,
default: Option<Thunk<Val>>,
- #[default(true)]
- inc_hidden: bool,
+ #[default(true)] inc_hidden: bool,
) -> Result<Val> {
let do_default = move || {
let Some(default) = default else {
crates/jrsonnet-stdlib/src/objects.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/objects.rs
+++ b/crates/jrsonnet-stdlib/src/objects.rs
@@ -57,8 +57,7 @@
o: ObjValue,
include_hidden: bool,
- #[cfg(feature = "exp-preserve-order")]
- preserve_order: bool,
+ #[cfg(feature = "exp-preserve-order")] preserve_order: bool,
) -> ArrValue {
o.values_ex(
include_hidden,
@@ -101,8 +100,7 @@
o: ObjValue,
include_hidden: bool,
- #[cfg(feature = "exp-preserve-order")]
- preserve_order: bool,
+ #[cfg(feature = "exp-preserve-order")] preserve_order: bool,
) -> ArrValue {
o.key_values_ex(
include_hidden,