From b915f23c48a12b02d025f56ebb5456b95fbb7af2 Mon Sep 17 00:00:00 2001 From: Yaroslav Bolyukin Date: Thu, 19 Mar 2026 04:01:30 +0000 Subject: [PATCH] refactor: prepared signatures in IR --- --- a/crates/jrsonnet-evaluator/src/async_import.rs +++ b/crates/jrsonnet-evaluator/src/async_import.rs @@ -3,9 +3,9 @@ use jrsonnet_gcmodule::Acyclic; use jrsonnet_parser::{ - ArgsDesc, AssertExpr, AssertStmt, BindSpec, CompSpec, Destruct, Expr, FieldMember, FieldName, - ForSpecData, IfElse, IfSpecData, ImportKind, ObjBody, Param, ParamsDesc, - ParserSettings, Slice, SliceDesc, Source, SourcePath, Spanned, + ArgsDesc, AssertExpr, AssertStmt, BindSpec, CompSpec, Destruct, Expr, ExprParam, ExprParams, + FieldMember, FieldName, ForSpecData, IfElse, IfSpecData, ImportKind, ObjBody, ParserSettings, + Slice, SliceDesc, Source, SourcePath, Spanned, }; use rustc_hash::FxHashMap; @@ -63,9 +63,9 @@ } } } - fn in_params(params: &ParamsDesc, out: &mut FoundImports) { - for Param(dest, default) in &*params.0 { - in_destruct(dest, out); + fn in_params(params: &ExprParams, out: &mut FoundImports) { + for ExprParam { destruct, default } in &*params.exprs { + in_destruct(destruct, out); if let Some(expr) = default { find_imports(expr, out); } --- a/crates/jrsonnet-evaluator/src/ctx.rs +++ b/crates/jrsonnet-evaluator/src/ctx.rs @@ -180,6 +180,12 @@ assert!(old.is_none(), "variable bound twice in single context call"); self } + pub fn binds(&mut self, bindings: FxHashMap>) -> &mut Self { + for (k, v) in bindings { + self.bind(k, v); + } + self + } pub fn build(self) -> Context { if let Some(parent) = self.extend { parent.extend_bindings(self.bindings) --- a/crates/jrsonnet-evaluator/src/error.rs +++ b/crates/jrsonnet-evaluator/src/error.rs @@ -1,7 +1,7 @@ use std::{ cmp::Ordering, convert::Infallible, - fmt::{Debug, Display}, + fmt::{self, Debug, Display}, }; use jrsonnet_gcmodule::{Acyclic, Trace}; @@ -11,7 +11,7 @@ use thiserror::Error; use crate::{ - function::{builtin::ParamDefault, CallLocation}, + function::{CallLocation, FunctionSignature, ParamDefault, ParamName}, stdlib::format::FormatError, typed::TypeLocError, val::ConvertNumValueError, @@ -43,37 +43,7 @@ out.push_str(", "); } out.push_str(v as &str); - } - out -} - -fn format_signature(sig: &FunctionSignature) -> String { - let mut out = String::new(); - out.push_str("\nFunction has the following signature: "); - out.push('('); - if sig.is_empty() { - out.push_str("/*no arguments*/"); - } else { - for (i, (name, default)) in sig.iter().enumerate() { - if i != 0 { - out.push_str(", "); - } - if let Some(name) = name { - out.push_str(name); - } else { - out.push_str(""); - } - match default { - ParamDefault::None => {} - ParamDefault::Exists => out.push_str(" = "), - ParamDefault::Literal(lit) => { - out.push_str(" = "); - out.push_str(lit); - } - } - } } - out.push(')'); out } @@ -103,8 +73,6 @@ heap.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(Ordering::Equal)); heap.into_iter().map(|v| v.1).collect() } - -type FunctionSignature = Vec<(Option, ParamDefault)>; /// Possible errors #[allow(missing_docs)] @@ -148,13 +116,13 @@ #[error("only functions can be called, got {0}")] OnlyFunctionsCanBeCalledGot(ValType), #[error("parameter {0} is not defined")] - UnknownFunctionParameter(String), + UnknownFunctionParameter(IStr), #[error("argument {0} is already bound")] BindingParameterASecondTime(IStr), - #[error("too many args, function has {0}{sig}", sig = format_signature(.1))] + #[error("too many args, function has {0}\nFunction has the following signature: {1}")] TooManyArgsFunctionHas(usize, FunctionSignature), - #[error("function argument is not passed: {}{}", .0.as_ref().map_or("", IStr::as_str), format_signature(.1))] - FunctionParameterNotBoundInCall(Option, FunctionSignature), + #[error("function argument is not passed: {0}\nFunction has the following signature: {1}")] + FunctionParameterNotBoundInCall(ParamName, FunctionSignature), #[error("external variable is not defined: {0}")] UndefinedExternalVariable(IStr), --- a/crates/jrsonnet-evaluator/src/evaluate/destructure.rs +++ b/crates/jrsonnet-evaluator/src/evaluate/destructure.rs @@ -170,7 +170,7 @@ let value = value.clone(); let data = { let fctx = fctx.clone(); - Thunk!(move || name.map_or_else( + Thunk!(move || name.0.map_or_else( || evaluate(fctx.unwrap(), &value), |name| evaluate_named(fctx.unwrap(), &value, name), )) --- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs +++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs @@ -3,15 +3,27 @@ use jrsonnet_gcmodule::{Cc, Trace}; use jrsonnet_interner::IStr; use jrsonnet_parser::{ - ArgsDesc, AssertStmt, BinaryOpType, BindSpec, CompSpec, Expr, FieldMember, FieldName, - ForSpecData, IfSpecData, ImportKind, LiteralType, ObjBody, ObjMembers, ParamsDesc, Spanned, + function::ParamName, ArgsDesc, AssertStmt, BinaryOpType, BindSpec, CompSpec, Expr, ExprParams, + FieldMember, FieldName, ForSpecData, IfSpecData, ImportKind, LiteralType, ObjBody, ObjMembers, + Spanned, }; use jrsonnet_types::ValType; use rustc_hash::FxHashMap; use self::destructure::destruct; use crate::{ - Context, Error, ObjValue, ObjValueBuilder, ObjectAssertion, Pending, Result, ResultExt, SupThis, Unbound, Val, arr::ArrValue, bail, destructure::evaluate_dest, error::{ErrorKind::*, suggest_object_fields}, evaluate::operator::{evaluate_add_op, evaluate_binary_op_special, evaluate_unary_op}, function::{CallLocation, FuncDesc, FuncVal, builtin::{ParamDefault, ParamName, ParamParse}}, gc::WithCapacityExt as _, in_frame, typed::Typed, val::{CachedUnbound, IndexableVal, NumValue, StrValue, Thunk}, with_state + arr::ArrValue, + bail, + destructure::evaluate_dest, + error::{suggest_object_fields, ErrorKind::*}, + evaluate::operator::{evaluate_add_op, evaluate_binary_op_special, evaluate_unary_op}, + function::{CallLocation, FuncDesc, FuncVal}, + gc::WithCapacityExt as _, + in_frame, + typed::Typed, + val::{CachedUnbound, IndexableVal, NumValue, StrValue, Thunk}, + with_state, Context, Error, ObjValue, ObjValueBuilder, ObjectAssertion, Pending, Result, + ResultExt, SupThis, Unbound, Val, }; pub mod destructure; pub mod operator; @@ -71,21 +83,12 @@ pub fn evaluate_method( ctx: Context, name: IStr, - params: ParamsDesc, + params: ExprParams, body: Rc>, ) -> Val { Val::Func(FuncVal::Normal(Cc::new(FuncDesc { name, ctx, - params_parse: params - .iter() - .map(|p| { - ParamParse::new( - p.0.name().map_or(ParamName::ANONYMOUS, ParamName::new), - ParamDefault::exists(p.1.is_some()), - ) - }) - .collect(), params, body, }))) @@ -125,7 +128,7 @@ Val::Arr(list) => { for item in list.iter_lazy() { let fctx = Pending::new(); - let mut new_bindings = FxHashMap::with_capacity(var.capacity_hint()); + let mut new_bindings = FxHashMap::with_capacity(var.binds_len()); destruct(var, item, fctx.clone(), &mut new_bindings)?; let ctx = ctx.clone().extend_bindings(new_bindings).into_future(fctx); @@ -178,7 +181,7 @@ fn bind(&self, sup_this: SupThis) -> Result { let fctx = Context::new_future(); let mut new_bindings = - FxHashMap::with_capacity(self.locals.iter().map(BindSpec::capacity_hint).sum()); + FxHashMap::with_capacity(self.locals.iter().map(BindSpec::binds_len).sum()); for b in self.locals.iter() { evaluate_dest(b, fctx.clone(), &mut new_bindings)?; } @@ -249,7 +252,7 @@ struct UnboundMethod { uctx: B, value: Rc>, - params: ParamsDesc, + params: ExprParams, name: IStr, } impl> Unbound for UnboundMethod { @@ -376,6 +379,13 @@ Ok(()) } +pub fn evaluate_named_param(ctx: Context, expr: &Spanned, name: ParamName) -> Result { + match name.0 { + Some(name) => evaluate_named(ctx, expr, name), + None => evaluate(ctx, expr), + } +} + pub fn evaluate_named(ctx: Context, expr: &Spanned, name: IStr) -> Result { use Expr::*; Ok(match &**expr { @@ -551,7 +561,7 @@ })?, LocalExpr(bindings, returned) => { let mut new_bindings: FxHashMap> = - FxHashMap::with_capacity(bindings.iter().map(BindSpec::capacity_hint).sum()); + FxHashMap::with_capacity(bindings.iter().map(BindSpec::binds_len).sum()); let fctx = Context::new_future(); for b in bindings { evaluate_dest(b, fctx.clone(), &mut new_bindings)?; --- a/crates/jrsonnet-evaluator/src/function/builtin.rs +++ b/crates/jrsonnet-evaluator/src/function/builtin.rs @@ -1,82 +1,25 @@ use std::any::Any; +use std::fmt; use jrsonnet_gcmodule::{cc_dyn, Acyclic, Trace, TraceBox}; use jrsonnet_interner::IStr; +use jrsonnet_parser::function::{FunctionSignature, ParamDefault, ParamName, ParamParse}; use super::{arglike::ArgsLike, parse::parse_builtin_call, CallLocation}; use crate::{Context, Result, Val}; -#[derive(Clone, Acyclic)] -pub struct ParamName(Option); -impl ParamName { - pub const ANONYMOUS: Self = Self(None); - pub fn new(name: IStr) -> Self { - Self(Some(name)) - } - pub fn as_str(&self) -> Option<&str> { - self.0.as_deref() - } - pub fn is_anonymous(&self) -> bool { - self.0.is_none() - } -} -impl PartialEq for ParamName { - fn eq(&self, other: &IStr) -> bool { - self.0 - .as_ref() - .map_or(false, |s| s.as_bytes() == other.as_bytes()) - } -} - -#[derive(Clone, Copy, Debug, Acyclic)] -pub enum ParamDefault { - None, - Exists, - Literal(&'static str), -} -impl ParamDefault { - pub const fn exists(is_exists: bool) -> Self { - if is_exists { - Self::Exists - } else { - Self::None - } - } -} - #[macro_export] macro_rules! params { (@name unnamed) => { ParamName::ANONYMOUS }; (@name named $name:literal) => { ParamName::new($crate::IStr::from($name)) }; ($($(#[$meta:meta])* [$kind:ident $(($lit:literal))? => $default:expr]),* $(,)?) => { thread_local! { - static PARAMS: [ParamParse; { const N: usize = <[u8]>::len(&[$($(#[$meta])* 0u8),*]); N }] = [ + static PARAMS: FunctionSignature = FunctionSignature::new([ $($(#[$meta])* ParamParse::new(params!(@name $kind $($lit)?), $default)),* - ]; + ].into()); } }; -} - -#[derive(Clone, Acyclic)] -pub struct ParamParse { - name: ParamName, - default: ParamDefault, } -impl ParamParse { - pub fn new(name: ParamName, default: ParamDefault) -> Self { - Self { name, default } - } - /// Parameter name for named call parsing - pub fn name(&self) -> &ParamName { - &self.name - } - pub fn default(&self) -> ParamDefault { - self.default - } - pub fn has_default(&self) -> bool { - !matches!(self.default, ParamDefault::None) - } -} cc_dyn!( #[derive(Clone)] @@ -89,7 +32,7 @@ self.0.name() } - fn params(&self) -> &[ParamParse] { + fn params(&self) -> FunctionSignature { self.0.params() } @@ -109,7 +52,7 @@ /// Function name to be used in stack traces fn name(&self) -> &str; /// Parameter names for named calls - fn params(&self) -> &[ParamParse]; + fn params(&self) -> FunctionSignature; /// Call the builtin fn call(&self, ctx: Context, loc: CallLocation<'_>, args: &dyn ArgsLike) -> Result; @@ -126,20 +69,19 @@ #[derive(Trace)] pub struct NativeCallback { - pub(crate) params: Vec, + pub(crate) params: FunctionSignature, handler: TraceBox, } impl NativeCallback { #[deprecated = "prefer using builtins directly, use this interface only for bindings"] pub fn new(params: Vec, handler: impl NativeCallbackHandler) -> Self { Self { - params: params - .into_iter() - .map(|n| ParamParse { - name: ParamName::new(n.into()), - default: ParamDefault::None, - }) - .collect(), + params: FunctionSignature::new( + params + .into_iter() + .map(|n| ParamParse::new(ParamName::new(n.into()), ParamDefault::None)) + .collect(), + ), handler: TraceBox(Box::new(handler)), } } @@ -152,12 +94,12 @@ "" } - fn params(&self) -> &[ParamParse] { - &self.params + fn params(&self) -> FunctionSignature { + self.params.clone() } fn call(&self, ctx: Context, _loc: CallLocation<'_>, args: &dyn ArgsLike) -> Result { - let args = parse_builtin_call(ctx, &self.params, args, true)?; + let args = parse_builtin_call(ctx, self.params.clone(), args, true)?; let args = args .into_iter() .map(|a| a.expect("legacy natives have no default params")) --- a/crates/jrsonnet-evaluator/src/function/mod.rs +++ b/crates/jrsonnet-evaluator/src/function/mod.rs @@ -5,23 +5,26 @@ use jrsonnet_gcmodule::{Cc, Trace}; use jrsonnet_interner::IStr; pub use jrsonnet_macros::builtin; -use jrsonnet_parser::{Destruct, Expr, ParamsDesc, Span, Spanned}; +use jrsonnet_parser::{Destruct, Expr, ExprParams, Span, Spanned}; use self::{ arglike::OptionalContext, - builtin::{Builtin, ParamParse, StaticBuiltin}, + builtin::{Builtin, StaticBuiltin}, native::NativeDesc, parse::{parse_default_function_call, parse_function_call}, }; use crate::{ - bail, error::ErrorKind::*, evaluate, evaluate_trivial, function::builtin::BuiltinFunc, Context, - ContextBuilder, Result, Thunk, Val, + bail, error::ErrorKind::*, evaluate, evaluate_trivial, function::builtin::BuiltinFunc, params, + Context, ContextBuilder, Result, Thunk, Val, }; pub mod arglike; pub mod builtin; pub mod native; pub mod parse; +pub mod prepared; + +pub use jrsonnet_parser::function::*; /// Function callsite location. /// Either from other jsonnet code, specified by expression location, or from native (without location). @@ -66,12 +69,9 @@ pub ctx: Context, /// Function parameter definition - pub params: ParamsDesc, + pub params: ExprParams, /// Function body pub body: Rc>, - - #[educe(PartialEq = false, Debug = false)] - pub(crate) params_parse: Vec, } impl FuncDesc { /// Create body context, but fill arguments without defaults with lazy error @@ -139,24 +139,18 @@ Self::StaticBuiltin(static_builtin) } - pub fn params(&self) -> &[ParamParse] { + pub fn params(&self) -> FunctionSignature { match self { Self::Id => ID.params(), Self::StaticBuiltin(i) => i.params(), Self::Builtin(i) => i.params(), - Self::Normal(p) => &p.params_parse, - Self::Thunk(_) => &[], + Self::Normal(p) => p.params.signature.clone(), + Self::Thunk(_) => FunctionSignature::empty(), } } /// Amount of non-default required arguments pub fn params_len(&self) -> usize { - match self { - Self::Id => 1, - Self::Normal(n) => n.params.iter().filter(|p| p.1.is_none()).count(), - Self::StaticBuiltin(i) => i.params().iter().filter(|p| !p.has_default()).count(), - Self::Builtin(i) => i.params().iter().filter(|p| !p.has_default()).count(), - Self::Thunk(_) => 0, - } + self.params().iter().filter(|p| !p.has_default()).count() } /// Function name, as defined in code. pub fn name(&self) -> IStr { @@ -185,8 +179,8 @@ evaluate(body_ctx, &func.body) } Self::Thunk(thunk) => { - if args.is_empty() { - bail!(TooManyArgsFunctionHas(0, vec![],)) + if !args.is_empty() { + bail!(TooManyArgsFunctionHas(0, FunctionSignature::empty())) } thunk.evaluate() } @@ -223,12 +217,13 @@ if desc.params.len() != 1 { return false; } - let param = &desc.params[0]; - if param.1.is_some() { + let param = &desc.params.exprs[0]; + if param.default.is_some() { return false; } + #[allow(clippy::infallible_destructuring_match)] - let id = match ¶m.0 { + let id = match ¶m.destruct { Destruct::Full(id) => id, #[cfg(feature = "exp-destruct")] _ => return false, --- a/crates/jrsonnet-evaluator/src/function/parse.rs +++ b/crates/jrsonnet-evaluator/src/function/parse.rs @@ -1,16 +1,15 @@ use std::mem::replace; use jrsonnet_interner::IStr; -use jrsonnet_parser::ParamsDesc; +use jrsonnet_parser::{function::FunctionSignature, ExprParams}; use rustc_hash::FxHashMap; -use super::{arglike::ArgsLike, builtin::ParamParse}; +use super::arglike::ArgsLike; use crate::{ bail, destructure::destruct, error::{ErrorKind::*, Result}, - evaluate_named, - function::builtin::ParamDefault, + evaluate_named, evaluate_named_param, gc::WithCapacityExt as _, Context, Pending, Thunk, Val, }; @@ -26,19 +25,15 @@ pub fn parse_function_call( ctx: Context, body_ctx: Context, - params: &ParamsDesc, + params: &ExprParams, args: &dyn ArgsLike, tailstrict: bool, ) -> Result { - let mut passed_args = - FxHashMap::with_capacity(params.iter().map(|p| p.0.capacity_hint()).sum()); - if args.unnamed_len() > params.len() { + let mut passed_args = FxHashMap::with_capacity(params.binds_len()); + if args.unnamed_len() > params.signature.len() { bail!(TooManyArgsFunctionHas( - params.len(), - params - .iter() - .map(|p| (p.0.name(), ParamDefault::exists(p.1.is_some()))) - .collect() + params.signature.len(), + params.signature.clone(), )) } @@ -46,9 +41,8 @@ let mut filled_positionals = 0; args.unnamed_iter(ctx.clone(), tailstrict, &mut |id, arg| { - let name = params[id].0.clone(); destruct( - &name, + ¶ms.exprs[id].destruct, arg, Pending::new_filled(ctx.clone()), &mut passed_args, @@ -59,8 +53,8 @@ args.named_iter(ctx, tailstrict, &mut |name, value| { // FIXME: O(n) for arg existence check - if !params.iter().any(|p| p.0.name().as_ref() == Some(name)) { - bail!(UnknownFunctionParameter((name as &str).to_owned())); + if !params.exprs.iter().any(|p| &p.destruct.name() == name) { + bail!(UnknownFunctionParameter(name.clone())); } if passed_args.insert(name.clone(), value).is_some() { bail!(BindingParameterASecondTime(name.clone())); @@ -73,14 +67,16 @@ // Some args are unset, but maybe we have defaults for them // Default values should be created in newly created context let fctx = Context::new_future(); - let mut defaults = FxHashMap::with_capacity( - params.iter().map(|p| p.0.capacity_hint()).sum::() - - filled_named - - filled_positionals, - ); + let mut defaults = + FxHashMap::with_capacity(params.binds_len() - filled_named - filled_positionals); - for (idx, param) in params.iter().enumerate().filter(|p| p.1 .1.is_some()) { - if let Some(name) = param.0.name() { + for (idx, into, default) in params + .exprs + .iter() + .enumerate() + .filter_map(|(i, p)| Some((i, &p.destruct, p.default.as_ref()?))) + { + if let Some(name) = into.name().0 { if passed_args.contains_key(&name) { continue; } @@ -89,17 +85,17 @@ } destruct( - ¶m.0, + &into, { let ctx = fctx.clone(); - let name = param.0.name().unwrap_or_else(|| "".into()); - let value = param.1.clone().expect("default exists"); - Thunk!(move || evaluate_named(ctx.unwrap(), &value, name)) + let name = into.name(); + let value = default.clone(); + Thunk!(move || evaluate_named_param(ctx.unwrap(), &value, name)) }, fctx.clone(), &mut defaults, )?; - if param.0.name().is_some() { + if !into.name().is_anonymous() { filled_named += 1; } else { filled_positionals += 1; @@ -108,20 +104,17 @@ // Some args still weren't filled if filled_named + filled_positionals != params.len() { - for param in params.iter().skip(args.unnamed_len()) { + for param in params.exprs.iter().skip(args.unnamed_len()) { let mut found = false; args.named_names(&mut |name| { - if Some(name) == param.0.name().as_ref() { + if ¶m.destruct.name() == name { found = true; } }); if !found { bail!(FunctionParameterNotBoundInCall( - param.0.clone().name(), - params - .iter() - .map(|p| (p.0.name(), ParamDefault::exists(p.1.is_some()))) - .collect() + param.destruct.name(), + params.signature.clone() )); } } @@ -147,19 +140,13 @@ /// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily pub fn parse_builtin_call( ctx: Context, - params: &[ParamParse], + params: FunctionSignature, args: &dyn ArgsLike, tailstrict: bool, ) -> Result>>> { let mut passed_args: Vec>> = vec![None; params.len()]; if args.unnamed_len() > params.len() { - bail!(TooManyArgsFunctionHas( - params.len(), - params - .iter() - .map(|p| (p.name().as_str().map(IStr::from), p.default())) - .collect() - )) + bail!(TooManyArgsFunctionHas(params.len(), params,)) } let mut filled_args = 0; @@ -175,7 +162,7 @@ let id = params .iter() .position(|p| p.name() == name) - .ok_or_else(|| UnknownFunctionParameter((name as &str).to_owned()))?; + .ok_or_else(|| UnknownFunctionParameter(name.clone()))?; if replace(&mut passed_args[id], Some(arg)).is_some() { bail!(BindingParameterASecondTime(name.clone())); } @@ -202,11 +189,8 @@ }); if !found { bail!(FunctionParameterNotBoundInCall( - param.name().as_str().map(IStr::from), - params - .iter() - .map(|p| (p.name().as_str().map(IStr::from), p.default())) - .collect() + param.name().clone(), + params, )); } } @@ -218,36 +202,33 @@ /// Creates Context, which has all argument default values applied /// and with unbound values causing error to be returned -pub fn parse_default_function_call(body_ctx: Context, params: &ParamsDesc) -> Result { +pub fn parse_default_function_call(body_ctx: Context, params: &ExprParams) -> Result { let fctx = Context::new_future(); - let mut bindings = FxHashMap::with_capacity(params.iter().map(|p| p.0.capacity_hint()).sum()); + let mut bindings = FxHashMap::with_capacity(params.binds_len()); - for param in params.iter() { - if let Some(v) = ¶m.1 { + for param in params.exprs.iter() { + if let Some(v) = ¶m.default { destruct( - ¶m.0.clone(), + ¶m.destruct.clone(), { let ctx = fctx.clone(); - let name = param.0.name().unwrap_or_else(|| "".into()); + let name = param.destruct.name(); let value = v.clone(); - Thunk!(move || evaluate_named(ctx.unwrap(), &value, name)) + Thunk!(move || evaluate_named_param(ctx.unwrap(), &value, name)) }, fctx.clone(), &mut bindings, )?; } else { destruct( - ¶m.0, + ¶m.destruct, { - let param_name = param.0.name().unwrap_or_else(|| "".into()); + let param_name = param.destruct.name(); let params = params.clone(); Thunk!(move || Err(FunctionParameterNotBoundInCall( - Some(param_name), - params - .iter() - .map(|p| (p.0.name(), ParamDefault::exists(p.1.is_some()))) - .collect(), + param_name, + params.signature.clone() ) .into())) }, --- /dev/null +++ b/crates/jrsonnet-evaluator/src/function/prepared.rs @@ -0,0 +1,165 @@ +use jrsonnet_parser::function::FunctionSignature; +use jrsonnet_parser::{ExprParams, IStr}; +use rustc_hash::{FxHashMap, FxHashSet}; + +use crate::destructure::destruct; +use crate::gc::WithCapacityExt; +use crate::val::ThunkValue as _; +use crate::{bail, error::ErrorKind::*, Result}; +use crate::{evaluate_named, evaluate_named_param, Context, ContextBuilder, Pending, Thunk, Val}; + +pub struct PreparedCall { + // Param, named input. + named: Vec<(usize, usize)>, + defaults: Vec, +} + +pub fn prepare_call( + params: FunctionSignature, + unnamed: usize, + named: &[IStr], +) -> Result { + if unnamed > params.len() { + bail!(TooManyArgsFunctionHas(params.len(), params)) + } + + let expected_defaults = params.len() - unnamed - named.len(); + let mut ops = PreparedCall { + named: Vec::with_capacity(named.len()), + defaults: Vec::with_capacity(expected_defaults), + }; + + // FIXME: bitmask + let mut passed: FxHashSet = (0..unnamed).collect(); + + for (input_id, name) in named.iter().enumerate() { + // FIXME: O(n) for arg existence check + let Some(param_idx) = params.iter().position(|p| p.name() == name) else { + bail!(UnknownFunctionParameter(name.clone())); + }; + if !passed.insert(param_idx) { + bail!(BindingParameterASecondTime(name.clone())); + } + ops.named.push((param_idx, input_id)); + } + + if named.len() + unnamed < params.len() { + let mut defaults = 0; + + for (param_id, param) in params + .iter() + .enumerate() + .skip(unnamed) + .filter(|p| p.1.has_default()) + { + // Skip already passed parameters + if !param.name().is_anonymous() && passed.contains(¶m_id) { + continue; + } + defaults += 1; + + ops.defaults.push(param_id); + } + + // Some args still weren't filled + if defaults != expected_defaults { + for param in params.iter().skip(unnamed) { + let mut found = false; + for name in named { + if param.name() == name { + found = true; + } + } + if !found { + bail!(FunctionParameterNotBoundInCall( + param.name().clone(), + params + )); + } + } + unreachable!(); + } + } + + Ok(ops) +} +pub fn parse_prepared_function_call( + body_ctx: Context, + prepared: &PreparedCall, + params: &ExprParams, + unnamed: &[Thunk], + named: &[Thunk], +) -> Result { + let mut passed_args = FxHashMap::with_capacity(params.binds_len()); + + let destruct_ctx = Pending::new(); + + for (param_idx, unnamed) in unnamed.iter().enumerate() { + destruct( + ¶ms.exprs[param_idx].destruct, + unnamed.clone(), + destruct_ctx.clone(), + &mut passed_args, + )?; + } + + for (param_idx, arg_idx) in prepared.named.iter().copied() { + destruct( + ¶ms.exprs[param_idx].destruct, + named[arg_idx].clone(), + destruct_ctx.clone(), + &mut passed_args, + )?; + } + + if prepared.defaults.is_empty() { + let body_ctx = body_ctx + .extend_bindings(passed_args) + .into_future(destruct_ctx); + Ok(body_ctx) + } else { + let fctx = Context::new_future(); + let mut defaults = FxHashMap::with_capacity(params.binds_len() - passed_args.len()); + for param_idx in prepared.defaults.iter().copied() { + // let param = params.0.rc_idx(param_idx); + destruct( + ¶ms.exprs[param_idx].destruct, + { + let ctx = fctx.clone(); + let params = params.clone(); + Thunk!(move || { + let param = ¶ms.exprs[param_idx]; + let name = param.destruct.name(); + let value = param.default.as_ref().expect("default exists"); + evaluate_named_param(ctx.unwrap(), value, name) + }) + }, + fctx.clone(), + &mut defaults, + )?; + } + + let mut ctx = ContextBuilder::extend(body_ctx); + ctx.binds(passed_args); + ctx.binds(defaults); + Ok(ctx.build().into_future(fctx).into_future(destruct_ctx)) + } +} +pub fn parse_prepared_builtin_call( + prepared: &PreparedCall, + params: FunctionSignature, + unnamed: &[Thunk], + named: &[Thunk], +) -> Result>>> { + let mut passed_args = vec![None; params.len()]; + + for (param_idx, unnamed) in unnamed.iter().enumerate() { + passed_args[param_idx] = Some(unnamed.clone()); + } + + for (param_idx, arg_idx) in prepared.named.iter().copied() { + passed_args[param_idx] = Some(named[arg_idx].clone()); + } + + Ok(passed_args) +} --- a/crates/jrsonnet-evaluator/src/stack.rs +++ b/crates/jrsonnet-evaluator/src/stack.rs @@ -20,6 +20,7 @@ type NightlyLocalKey = std::thread::LocalKey; #[cfg(nightly)] +#[macro_export] macro_rules! const_tls { (const $name:ident: $t:ty = $expr:expr;) => { #[thread_local] @@ -27,6 +28,7 @@ }; } #[cfg(not(nightly))] +#[macro_export] macro_rules! const_tls { (const $name:ident: $t:ty = $expr:expr;) => { thread_local! { --- a/crates/jrsonnet-macros/src/lib.rs +++ b/crates/jrsonnet-macros/src/lib.rs @@ -239,7 +239,9 @@ cfg_attrs, .. } => { - let name = name.as_ref().map_or_else(|| quote! {unnamed}, |n| quote! {named(#n)}); + let name = name + .as_ref() + .map_or_else(|| quote! {unnamed}, |n| quote! {named(#n)}); let default = match optionality { Optionality::Required => quote!(ParamDefault::None), Optionality::Optional => quote!(ParamDefault::Exists), @@ -251,7 +253,9 @@ }) } ArgInfo::Lazy { is_option, name } => { - let name = name.as_ref().map_or_else(|| quote! {unnamed}, |n| quote! {named(#n)}); + let name = name + .as_ref() + .map_or_else(|| quote! {unnamed}, |n| quote! {named(#n)}); Some(quote! { [#name => ParamDefault::exists(#is_option)], }) @@ -364,7 +368,7 @@ const _: () = { use ::jrsonnet_evaluator::{ State, Val, - function::{builtin::{Builtin, StaticBuiltin, ParamParse, ParamName, ParamDefault}, CallLocation, ArgsLike, parse::parse_builtin_call}, + function::{builtin::{Builtin, StaticBuiltin}, FunctionSignature, ParamParse, ParamName, ParamDefault, CallLocation, ArgsLike, parse::parse_builtin_call}, Result, Context, typed::Typed, parser::Span, params, }; @@ -380,11 +384,8 @@ fn name(&self) -> &str { stringify!(#name) } - fn params(&self) -> &[ParamParse] { - /// Safety: ParamParse contains IStr, which is thread-local, thus neither Send or Sync - /// The result of this transmute can not outlive the thread, thus 'static here is equivalent to the - /// nightly-only 'thread - PARAMS.with(|p| unsafe { std::mem::transmute::<&[ParamParse], &'static [ParamParse]>(p.as_slice()) }) + fn params(&self) -> FunctionSignature { + PARAMS.with(|p| p.clone()) } #[allow(unused_variables)] fn call(&self, ctx: Context, location: CallLocation, args: &dyn ArgsLike) -> Result { --- a/crates/jrsonnet-parser/src/expr.rs +++ b/crates/jrsonnet-parser/src/expr.rs @@ -7,7 +7,10 @@ use jrsonnet_gcmodule::Acyclic; use jrsonnet_interner::IStr; -use crate::source::Source; +use crate::{ + function::{FunctionSignature, ParamDefault, ParamName, ParamParse}, + source::Source, +}; #[derive(Debug, PartialEq, Acyclic)] pub enum FieldName { @@ -41,7 +44,7 @@ pub struct FieldMember { pub name: FieldName, pub plus: bool, - pub params: Option, + pub params: Option, pub visibility: Visibility, pub value: Rc>, } @@ -147,16 +150,41 @@ /// name, default value #[derive(Debug, PartialEq, Acyclic)] -pub struct Param(pub Destruct, pub Option>>); +pub struct ExprParam { + pub destruct: Destruct, + pub default: Option>>, +} /// Defined function parameters #[derive(Debug, Clone, PartialEq, Acyclic)] -pub struct ParamsDesc(pub Rc>); - -impl Deref for ParamsDesc { - type Target = Vec; - fn deref(&self) -> &Self::Target { - &self.0 +pub struct ExprParams { + pub exprs: Rc>, + pub signature: FunctionSignature, + binds_len: usize, +} +impl ExprParams { + pub fn len(&self) -> usize { + self.exprs.len() + } + pub fn binds_len(&self) -> usize { + self.binds_len + } + pub fn new(exprs: Vec) -> Self { + Self { + signature: FunctionSignature::new( + exprs + .iter() + .map(|p| { + ParamParse::new( + p.destruct.name(), + ParamDefault::exists(p.default.is_some()), + ) + }) + .collect(), + ), + binds_len: exprs.iter().map(|v| v.destruct.binds_len()).sum(), + exprs: Rc::new(exprs), + } } } @@ -198,14 +226,14 @@ } impl Destruct { /// Name of destructure, used for function parameter names - pub fn name(&self) -> Option { - match self { + pub fn name(&self) -> ParamName { + ParamName(match self { Self::Full(name) => Some(name.clone()), #[cfg(feature = "exp-destruct")] _ => None, - } + }) } - pub fn capacity_hint(&self) -> usize { + pub fn binds_len(&self) -> usize { #[cfg(feature = "exp-destruct")] fn cap_rest(rest: &Option) -> usize { match rest { @@ -220,8 +248,8 @@ Self::Skip => 0, #[cfg(feature = "exp-destruct")] Self::Array { start, rest, end } => { - start.iter().map(Destruct::capacity_hint).sum::() - + end.iter().map(Destruct::capacity_hint).sum::() + start.iter().map(Destruct::binds_len).sum::() + + end.iter().map(Destruct::binds_len).sum::() + cap_rest(rest) } #[cfg(feature = "exp-destruct")] @@ -248,14 +276,14 @@ }, Function { name: IStr, - params: ParamsDesc, + params: ExprParams, value: Rc>, }, } impl BindSpec { - pub fn capacity_hint(&self) -> usize { + pub fn binds_len(&self) -> usize { match self { - BindSpec::Field { into, .. } => into.capacity_hint(), + BindSpec::Field { into, .. } => into.binds_len(), BindSpec::Function { .. } => 1, } } @@ -396,7 +424,7 @@ parts: Vec, }, /// function(x) x - Function(ParamsDesc, Rc>), + Function(ExprParams, Rc>), /// if true == false then 1 else 2 IfElse(Box), Slice(Box), --- /dev/null +++ b/crates/jrsonnet-parser/src/function.rs @@ -0,0 +1,126 @@ +use std::fmt; +use std::ops::Deref; +use std::rc::Rc; + +use jrsonnet_gcmodule::Acyclic; +use jrsonnet_interner::IStr; + +#[derive(Clone, Acyclic, Debug, PartialEq, Eq)] +pub struct ParamName(pub Option); +impl ParamName { + pub const ANONYMOUS: Self = Self(None); + pub fn new(name: IStr) -> Self { + Self(Some(name)) + } + pub fn as_str(&self) -> Option<&str> { + self.0.as_deref() + } + pub fn is_anonymous(&self) -> bool { + self.0.is_none() + } +} +impl PartialEq for ParamName { + fn eq(&self, other: &IStr) -> bool { + self.0 + .as_ref() + .map_or(false, |s| s.as_bytes() == other.as_bytes()) + } +} + +impl fmt::Display for ParamName { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self.0 { + Some(v) => write!(f, "{v}"), + None => write!(f, ""), + } + } +} + +#[derive(Clone, Copy, Debug, Acyclic, PartialEq, Eq)] +pub enum ParamDefault { + None, + Exists, + Literal(&'static str), +} +impl ParamDefault { + pub const fn exists(is_exists: bool) -> Self { + if is_exists { + Self::Exists + } else { + Self::None + } + } +} +impl fmt::Display for ParamDefault { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ParamDefault::None => Ok(()), + ParamDefault::Exists => write!(f, " = "), + ParamDefault::Literal(lit) => write!(f, " = {lit}"), + } + } +} + +#[derive(Clone, Acyclic, Debug, PartialEq, Eq)] +pub struct ParamParse { + name: ParamName, + default: ParamDefault, +} +impl ParamParse { + pub fn new(name: ParamName, default: ParamDefault) -> Self { + Self { name, default } + } + /// Parameter name for named call parsing + pub fn name(&self) -> &ParamName { + &self.name + } + pub fn default(&self) -> ParamDefault { + self.default + } + pub fn has_default(&self) -> bool { + !matches!(self.default, ParamDefault::None) + } +} +impl fmt::Display for ParamParse { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}{}", self.name, self.default) + } +} + +#[derive(Debug, Clone, Acyclic, PartialEq, Eq)] +pub struct FunctionSignature(Rc<[ParamParse]>); +impl Deref for FunctionSignature { + type Target = [ParamParse]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +thread_local! { + static EMPTY_SIGNATURE: FunctionSignature = FunctionSignature::new([].into()); +} + +impl FunctionSignature { + pub fn new(v: Rc<[ParamParse]>) -> Self { + Self(v) + } + pub fn empty() -> Self { + EMPTY_SIGNATURE.with(|p| p.clone()) + } +} +impl fmt::Display for FunctionSignature { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.0.is_empty() { + return write!(f, "(/*no arguments*/)"); + } + write!(f, "(")?; + for (i, par) in self.0.iter().enumerate() { + if i != 0 { + write!(f, ", ")?; + } + write!(f, "{par}")?; + } + write!(f, ")") + } +} --- a/crates/jrsonnet-parser/src/lib.rs +++ b/crates/jrsonnet-parser/src/lib.rs @@ -7,9 +7,11 @@ pub use expr::*; pub use jrsonnet_interner::IStr; pub use peg; +pub mod function; mod location; mod source; mod unescape; + pub use location::CodeLocation; pub use source::{ Source, SourceDefaultIgnoreJpath, SourceDirectory, SourceFifo, SourceFile, SourcePath, @@ -68,10 +70,10 @@ rule keyword(id: &'static str) -> () = ##parse_string_literal(id) end_of_ident() - pub rule param(s: &ParserSettings) -> expr::Param = name:destruct(s) expr:(_ "=" _ expr:expr(s){expr})? { expr::Param(name, expr.map(Rc::new)) } - pub rule params(s: &ParserSettings) -> expr::ParamsDesc - = params:param(s) ** comma() comma()? { expr::ParamsDesc(Rc::new(params)) } - / { expr::ParamsDesc(Rc::new(Vec::new())) } + pub rule param(s: &ParserSettings) -> expr::ExprParam = destruct:destruct(s) expr:(_ "=" _ expr:expr(s){expr})? { expr::ExprParam { destruct, default: expr.map(Rc::new) } } + pub rule params(s: &ParserSettings) -> expr::ExprParams + = params:param(s) ** comma() comma()? { expr::ExprParams::new(params) } + / { expr::ExprParams::new(Vec::new()) } pub rule arg(s: &ParserSettings) -> (Option, Rc>) = name:(quiet! { (s:id() _ "=" !['='] _ {s})? } / expected!("")) expr:expr(s) {(name, Rc::new(expr))} --- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__default_param_before_nondefault.snap +++ b/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__default_param_before_nondefault.snap @@ -6,26 +6,47 @@ [ Function { name: "x", - params: ParamsDesc( - [ - Param( - Full( + params: ExprParams { + exprs: [ + ExprParam { + destruct: Full( "foo", ), - Some( + default: Some( Str( "foo", ) from virtual::14-19, ), - ), - Param( - Full( + }, + ExprParam { + destruct: Full( "bar", ), - None, - ), + default: None, + }, ], - ), + signature: FunctionSignature( + [ + ParamParse { + name: ParamName( + Some( + "foo", + ), + ), + default: Exists, + }, + ParamParse { + name: ParamName( + Some( + "bar", + ), + ), + default: None, + }, + ], + ), + binds_len: 2, + }, value: Literal( Null, ) from virtual::28-32, -- gitstuff