difftreelog
perf destruct capacity hints
in: master
2 files changed
crates/jrsonnet-evaluator/src/function/parse.rsdiffbeforeafterboth1use std::mem::replace;23use jrsonnet_gcmodule::Trace;4use jrsonnet_interner::IStr;5use jrsonnet_parser::{LocExpr, ParamsDesc};67use super::{arglike::ArgsLike, builtin::BuiltinParam};8use crate::{9 destructure::destruct,10 error::{Error::*, Result},11 evaluate_named,12 gc::GcHashMap,13 tb, throw,14 val::ThunkValue,15 Context, Pending, Thunk, Val,16};1718#[derive(Trace)]19struct EvaluateNamedThunk {20 ctx: Pending<Context>,21 name: IStr,22 value: LocExpr,23}2425impl ThunkValue for EvaluateNamedThunk {26 type Output = Val;27 fn get(self: Box<Self>) -> Result<Val> {28 evaluate_named(self.ctx.unwrap(), &self.value, self.name)29 }30}3132/// Creates correct [context](Context) for function body evaluation returning error on invalid call.33///34/// ## Parameters35/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)36/// * `body_ctx`: used for default parameter values' execution and for body execution (if set)37/// * `params`: function parameters' definition38/// * `args`: passed function arguments39/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily40pub fn parse_function_call(41 ctx: Context,42 body_ctx: Context,43 params: &ParamsDesc,44 args: &dyn ArgsLike,45 tailstrict: bool,46) -> Result<Context> {47 let mut passed_args = GcHashMap::with_capacity(params.len());48 if args.unnamed_len() > params.len() {49 throw!(TooManyArgsFunctionHas(50 params.len(),51 params.iter().map(|p| (p.0.name(), p.1.is_some())).collect()52 ))53 }5455 let mut filled_named = 0;56 let mut filled_positionals = 0;5758 args.unnamed_iter(ctx.clone(), tailstrict, &mut |id, arg| {59 let name = params[id].0.clone();60 destruct(61 &name,62 arg,63 Pending::new_filled(ctx.clone()),64 &mut passed_args,65 )?;66 filled_positionals += 1;67 Ok(())68 })?;6970 args.named_iter(ctx, tailstrict, &mut |name, value| {71 // FIXME: O(n) for arg existence check72 if !params.iter().any(|p| p.0.name().as_ref() == Some(name)) {73 throw!(UnknownFunctionParameter((name as &str).to_owned()));74 }75 if passed_args.insert(name.clone(), value).is_some() {76 throw!(BindingParameterASecondTime(name.clone()));77 }78 filled_named += 1;79 Ok(())80 })?;8182 if filled_named + filled_positionals < params.len() {83 // Some args are unset, but maybe we have defaults for them84 // Default values should be created in newly created context85 let fctx = Context::new_future();86 let mut defaults =87 GcHashMap::with_capacity(params.len() - filled_named - filled_positionals);8889 for (idx, param) in params.iter().enumerate().filter(|p| p.1 .1.is_some()) {90 if let Some(name) = param.0.name() {91 if passed_args.contains_key(&name) {92 continue;93 }94 } else if idx < filled_positionals {95 continue;96 }9798 destruct(99 ¶m.0,100 Thunk::new(tb!(EvaluateNamedThunk {101 ctx: fctx.clone(),102 name: param.0.name().unwrap_or_else(|| "<destruct>".into()),103 value: param.1.clone().expect("default exists"),104 })),105 fctx.clone(),106 &mut defaults,107 )?;108 if param.0.name().is_some() {109 filled_named += 1;110 } else {111 filled_positionals += 1;112 }113 }114115 // Some args still weren't filled116 if filled_named + filled_positionals != params.len() {117 for param in params.iter().skip(args.unnamed_len()) {118 let mut found = false;119 args.named_names(&mut |name| {120 if Some(name) == param.0.name().as_ref() {121 found = true;122 }123 });124 if !found {125 throw!(FunctionParameterNotBoundInCall(126 param.0.clone().name(),127 params.iter().map(|p| (p.0.name(), p.1.is_some())).collect()128 ));129 }130 }131 unreachable!();132 }133134 Ok(body_ctx135 .extend(passed_args, None, None, None)136 .extend(defaults, None, None, None)137 .into_future(fctx))138 } else {139 let body_ctx = body_ctx.extend(passed_args, None, None, None);140 Ok(body_ctx)141 }142}143144/// You shouldn't probally use this function, use `jrsonnet_macros::builtin` instead145///146/// ## Parameters147/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)148/// * `params`: function parameters' definition149/// * `args`: passed function arguments150/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily151pub fn parse_builtin_call(152 ctx: Context,153 params: &[BuiltinParam],154 args: &dyn ArgsLike,155 tailstrict: bool,156) -> Result<Vec<Option<Thunk<Val>>>> {157 let mut passed_args: Vec<Option<Thunk<Val>>> = vec![None; params.len()];158 if args.unnamed_len() > params.len() {159 throw!(TooManyArgsFunctionHas(160 params.len(),161 params162 .iter()163 .map(|p| (p.name.as_ref().map(|v| v.as_ref().into()), p.has_default))164 .collect()165 ))166 }167168 let mut filled_args = 0;169170 args.unnamed_iter(ctx.clone(), tailstrict, &mut |id, arg| {171 passed_args[id] = Some(arg);172 filled_args += 1;173 Ok(())174 })?;175176 args.named_iter(ctx, tailstrict, &mut |name, arg| {177 // FIXME: O(n) for arg existence check178 let id = params179 .iter()180 .position(|p| p.name.as_ref().map_or(false, |v| v as &str == name as &str))181 .ok_or_else(|| UnknownFunctionParameter((name as &str).to_owned()))?;182 if replace(&mut passed_args[id], Some(arg)).is_some() {183 throw!(BindingParameterASecondTime(name.clone()));184 }185 filled_args += 1;186 Ok(())187 })?;188189 if filled_args < params.len() {190 for (id, _) in params.iter().enumerate().filter(|(_, p)| p.has_default) {191 if passed_args[id].is_some() {192 continue;193 }194 filled_args += 1;195 }196197 // Some args still wasn't filled198 if filled_args != params.len() {199 for param in params.iter().skip(args.unnamed_len()) {200 let mut found = false;201 args.named_names(&mut |name| {202 if param203 .name204 .as_ref()205 .map_or(false, |v| v as &str == name as &str)206 {207 found = true;208 }209 });210 if !found {211 throw!(FunctionParameterNotBoundInCall(212 param.name.as_ref().map(|v| v.as_ref().into()),213 params214 .iter()215 .map(|p| (p.name.as_ref().map(|p| p.as_ref().into()), p.has_default))216 .collect()217 ));218 }219 }220 unreachable!();221 }222 }223 Ok(passed_args)224}225226/// Creates Context, which has all argument default values applied227/// and with unbound values causing error to be returned228pub fn parse_default_function_call(body_ctx: Context, params: &ParamsDesc) -> Result<Context> {229 #[derive(Trace)]230 struct DependsOnUnbound(IStr, ParamsDesc);231 impl ThunkValue for DependsOnUnbound {232 type Output = Val;233 fn get(self: Box<Self>) -> Result<Val> {234 Err(FunctionParameterNotBoundInCall(235 Some(self.0.clone()),236 self.1.iter().map(|p| (p.0.name(), p.1.is_some())).collect(),237 )238 .into())239 }240 }241242 let fctx = Context::new_future();243244 let mut bindings = GcHashMap::new();245246 for param in params.iter() {247 if let Some(v) = ¶m.1 {248 destruct(249 ¶m.0.clone(),250 Thunk::new(tb!(EvaluateNamedThunk {251 ctx: fctx.clone(),252 name: param.0.name().unwrap_or_else(|| "<destruct>".into()),253 value: v.clone(),254 })),255 fctx.clone(),256 &mut bindings,257 )?;258 } else {259 destruct(260 ¶m.0,261 Thunk::new(tb!(DependsOnUnbound(262 param.0.name().unwrap_or_else(|| "<destruct>".into()),263 params.clone()264 ))),265 fctx.clone(),266 &mut bindings,267 )?;268 }269 }270271 Ok(body_ctx272 .extend(bindings, None, None, None)273 .into_future(fctx))274}1use std::mem::replace;23use jrsonnet_gcmodule::Trace;4use jrsonnet_interner::IStr;5use jrsonnet_parser::{LocExpr, ParamsDesc};67use super::{arglike::ArgsLike, builtin::BuiltinParam};8use crate::{9 destructure::destruct,10 error::{Error::*, Result},11 evaluate_named,12 gc::GcHashMap,13 tb, throw,14 val::ThunkValue,15 Context, Pending, Thunk, Val,16};1718#[derive(Trace)]19struct EvaluateNamedThunk {20 ctx: Pending<Context>,21 name: IStr,22 value: LocExpr,23}2425impl ThunkValue for EvaluateNamedThunk {26 type Output = Val;27 fn get(self: Box<Self>) -> Result<Val> {28 evaluate_named(self.ctx.unwrap(), &self.value, self.name)29 }30}3132/// Creates correct [context](Context) for function body evaluation returning error on invalid call.33///34/// ## Parameters35/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)36/// * `body_ctx`: used for default parameter values' execution and for body execution (if set)37/// * `params`: function parameters' definition38/// * `args`: passed function arguments39/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily40pub fn parse_function_call(41 ctx: Context,42 body_ctx: Context,43 params: &ParamsDesc,44 args: &dyn ArgsLike,45 tailstrict: bool,46) -> Result<Context> {47 let mut passed_args =48 GcHashMap::with_capacity(params.iter().map(|p| p.0.capacity_hint()).sum());49 if args.unnamed_len() > params.len() {50 throw!(TooManyArgsFunctionHas(51 params.len(),52 params.iter().map(|p| (p.0.name(), p.1.is_some())).collect()53 ))54 }5556 let mut filled_named = 0;57 let mut filled_positionals = 0;5859 args.unnamed_iter(ctx.clone(), tailstrict, &mut |id, arg| {60 let name = params[id].0.clone();61 destruct(62 &name,63 arg,64 Pending::new_filled(ctx.clone()),65 &mut passed_args,66 )?;67 filled_positionals += 1;68 Ok(())69 })?;7071 args.named_iter(ctx, tailstrict, &mut |name, value| {72 // FIXME: O(n) for arg existence check73 if !params.iter().any(|p| p.0.name().as_ref() == Some(name)) {74 throw!(UnknownFunctionParameter((name as &str).to_owned()));75 }76 if passed_args.insert(name.clone(), value).is_some() {77 throw!(BindingParameterASecondTime(name.clone()));78 }79 filled_named += 1;80 Ok(())81 })?;8283 if filled_named + filled_positionals < params.len() {84 // Some args are unset, but maybe we have defaults for them85 // Default values should be created in newly created context86 let fctx = Context::new_future();87 let mut defaults = GcHashMap::with_capacity(88 params.iter().map(|p| p.0.capacity_hint()).sum::<usize>()89 - filled_named - filled_positionals,90 );9192 for (idx, param) in params.iter().enumerate().filter(|p| p.1 .1.is_some()) {93 if let Some(name) = param.0.name() {94 if passed_args.contains_key(&name) {95 continue;96 }97 } else if idx < filled_positionals {98 continue;99 }100101 destruct(102 ¶m.0,103 Thunk::new(tb!(EvaluateNamedThunk {104 ctx: fctx.clone(),105 name: param.0.name().unwrap_or_else(|| "<destruct>".into()),106 value: param.1.clone().expect("default exists"),107 })),108 fctx.clone(),109 &mut defaults,110 )?;111 if param.0.name().is_some() {112 filled_named += 1;113 } else {114 filled_positionals += 1;115 }116 }117118 // Some args still weren't filled119 if filled_named + filled_positionals != params.len() {120 for param in params.iter().skip(args.unnamed_len()) {121 let mut found = false;122 args.named_names(&mut |name| {123 if Some(name) == param.0.name().as_ref() {124 found = true;125 }126 });127 if !found {128 throw!(FunctionParameterNotBoundInCall(129 param.0.clone().name(),130 params.iter().map(|p| (p.0.name(), p.1.is_some())).collect()131 ));132 }133 }134 unreachable!();135 }136137 Ok(body_ctx138 .extend(passed_args, None, None, None)139 .extend(defaults, None, None, None)140 .into_future(fctx))141 } else {142 let body_ctx = body_ctx.extend(passed_args, None, None, None);143 Ok(body_ctx)144 }145}146147/// You shouldn't probally use this function, use `jrsonnet_macros::builtin` instead148///149/// ## Parameters150/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)151/// * `params`: function parameters' definition152/// * `args`: passed function arguments153/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily154pub fn parse_builtin_call(155 ctx: Context,156 params: &[BuiltinParam],157 args: &dyn ArgsLike,158 tailstrict: bool,159) -> Result<Vec<Option<Thunk<Val>>>> {160 let mut passed_args: Vec<Option<Thunk<Val>>> = vec![None; params.len()];161 if args.unnamed_len() > params.len() {162 throw!(TooManyArgsFunctionHas(163 params.len(),164 params165 .iter()166 .map(|p| (p.name.as_ref().map(|v| v.as_ref().into()), p.has_default))167 .collect()168 ))169 }170171 let mut filled_args = 0;172173 args.unnamed_iter(ctx.clone(), tailstrict, &mut |id, arg| {174 passed_args[id] = Some(arg);175 filled_args += 1;176 Ok(())177 })?;178179 args.named_iter(ctx, tailstrict, &mut |name, arg| {180 // FIXME: O(n) for arg existence check181 let id = params182 .iter()183 .position(|p| p.name.as_ref().map_or(false, |v| v as &str == name as &str))184 .ok_or_else(|| UnknownFunctionParameter((name as &str).to_owned()))?;185 if replace(&mut passed_args[id], Some(arg)).is_some() {186 throw!(BindingParameterASecondTime(name.clone()));187 }188 filled_args += 1;189 Ok(())190 })?;191192 if filled_args < params.len() {193 for (id, _) in params.iter().enumerate().filter(|(_, p)| p.has_default) {194 if passed_args[id].is_some() {195 continue;196 }197 filled_args += 1;198 }199200 // Some args still wasn't filled201 if filled_args != params.len() {202 for param in params.iter().skip(args.unnamed_len()) {203 let mut found = false;204 args.named_names(&mut |name| {205 if param206 .name207 .as_ref()208 .map_or(false, |v| v as &str == name as &str)209 {210 found = true;211 }212 });213 if !found {214 throw!(FunctionParameterNotBoundInCall(215 param.name.as_ref().map(|v| v.as_ref().into()),216 params217 .iter()218 .map(|p| (p.name.as_ref().map(|p| p.as_ref().into()), p.has_default))219 .collect()220 ));221 }222 }223 unreachable!();224 }225 }226 Ok(passed_args)227}228229/// Creates Context, which has all argument default values applied230/// and with unbound values causing error to be returned231pub fn parse_default_function_call(body_ctx: Context, params: &ParamsDesc) -> Result<Context> {232 #[derive(Trace)]233 struct DependsOnUnbound(IStr, ParamsDesc);234 impl ThunkValue for DependsOnUnbound {235 type Output = Val;236 fn get(self: Box<Self>) -> Result<Val> {237 Err(FunctionParameterNotBoundInCall(238 Some(self.0.clone()),239 self.1.iter().map(|p| (p.0.name(), p.1.is_some())).collect(),240 )241 .into())242 }243 }244245 let fctx = Context::new_future();246247 let mut bindings = GcHashMap::with_capacity(params.iter().map(|p| p.0.capacity_hint()).sum());248249 for param in params.iter() {250 if let Some(v) = ¶m.1 {251 destruct(252 ¶m.0.clone(),253 Thunk::new(tb!(EvaluateNamedThunk {254 ctx: fctx.clone(),255 name: param.0.name().unwrap_or_else(|| "<destruct>".into()),256 value: v.clone(),257 })),258 fctx.clone(),259 &mut bindings,260 )?;261 } else {262 destruct(263 ¶m.0,264 Thunk::new(tb!(DependsOnUnbound(265 param.0.name().unwrap_or_else(|| "<destruct>".into()),266 params.clone()267 ))),268 fctx.clone(),269 &mut bindings,270 )?;271 }272 }273274 Ok(body_ctx275 .extend(bindings, None, None, None)276 .into_future(fctx))277}crates/jrsonnet-parser/src/expr.rsdiffbeforeafterboth--- a/crates/jrsonnet-parser/src/expr.rs
+++ b/crates/jrsonnet-parser/src/expr.rs
@@ -228,6 +228,39 @@
_ => None,
}
}
+ pub fn capacity_hint(&self) -> usize {
+ #[cfg(feature = "exp-destruct")]
+ fn cap_rest(rest: &Option<DestructRest>) -> usize {
+ match rest {
+ Some(DestructRest::Keep(_)) => 1,
+ Some(DestructRest::Drop) => 0,
+ None => 0,
+ }
+ }
+ match self {
+ Self::Full(_) => 1,
+ #[cfg(feature = "exp-destruct")]
+ Self::Skip => 0,
+ #[cfg(feature = "exp-destruct")]
+ Self::Array { start, rest, end } => {
+ start.iter().map(Destruct::capacity_hint).sum::<usize>()
+ + end.iter().map(Destruct::capacity_hint).sum::<usize>()
+ + cap_rest(rest)
+ }
+ #[cfg(feature = "exp-destruct")]
+ Self::Object { fields, rest } => {
+ let mut out = 0;
+ for (_, into, _) in fields {
+ match into {
+ Some(v) => out += v.capacity_hint(),
+ // Field is destructured to default name
+ None => out += 1,
+ }
+ }
+ out + cap_rest(rest)
+ }
+ }
+ }
}
#[cfg_attr(feature = "structdump", derive(Codegen))]
@@ -244,6 +277,14 @@
value: LocExpr,
},
}
+impl BindSpec {
+ pub fn capacity_hint(&self) -> usize {
+ match self {
+ BindSpec::Field { into, .. } => into.capacity_hint(),
+ BindSpec::Function { .. } => 1,
+ }
+ }
+}
#[cfg_attr(feature = "structdump", derive(Codegen))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]