difftreelog
refactor fancier builtin param type
in: master
5 files changed
bindings/jsonnet/src/native.rsdiffbeforeafterboth--- a/bindings/jsonnet/src/native.rs
+++ b/bindings/jsonnet/src/native.rs
@@ -1,5 +1,4 @@
use std::{
- borrow::Cow,
ffi::{c_void, CStr},
os::raw::{c_char, c_int},
};
@@ -82,7 +81,7 @@
let param = CStr::from_ptr(*raw_params)
.to_str()
.expect("param name is not utf-8");
- params.push(Cow::Owned(param.into()));
+ params.push(param.into());
raw_params = raw_params.offset(1);
}
crates/jrsonnet-evaluator/src/function/builtin.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/function/builtin.rs
+++ b/crates/jrsonnet-evaluator/src/function/builtin.rs
@@ -1,18 +1,56 @@
use std::{any::Any, borrow::Cow};
use jrsonnet_gcmodule::Trace;
+use jrsonnet_interner::IStr;
use super::{arglike::ArgsLike, parse::parse_builtin_call, CallLocation};
use crate::{error::Result, gc::TraceBox, tb, Context, Val};
-pub type BuiltinParamName = Cow<'static, str>;
+/// Can't have str | IStr, because constant BuiltinParam causes
+/// E0492: constant functions cannot refer to interior mutable data
+#[derive(Clone, Trace)]
+pub struct ParamName(Option<Cow<'static, str>>);
+impl ParamName {
+ pub const ANONYMOUS: Self = Self(None);
+ pub const fn new_static(name: &'static str) -> Self {
+ Self(Some(Cow::Borrowed(name)))
+ }
+ pub fn new_dynamic(name: String) -> Self {
+ Self(Some(Cow::Owned(name)))
+ }
+ pub fn as_str(&self) -> Option<&str> {
+ self.0.as_deref()
+ }
+ pub fn is_anonymous(&self) -> bool {
+ self.0.is_none()
+ }
+}
+impl PartialEq<IStr> for ParamName {
+ fn eq(&self, other: &IStr) -> bool {
+ match &self.0 {
+ Some(s) => s.as_bytes() == other.as_bytes(),
+ None => false,
+ }
+ }
+}
#[derive(Clone, Trace)]
pub struct BuiltinParam {
+ name: ParamName,
+ has_default: bool,
+}
+impl BuiltinParam {
+ pub const fn new(name: ParamName, has_default: bool) -> Self {
+ Self { name, has_default }
+ }
/// Parameter name for named call parsing
- pub name: Option<BuiltinParamName>,
+ pub fn name(&self) -> &ParamName {
+ &self.name
+ }
/// Is implementation allowed to return empty value
- pub has_default: bool,
+ pub fn has_default(&self) -> bool {
+ self.has_default
+ }
}
/// Description of function defined by native code
@@ -44,12 +82,12 @@
}
impl NativeCallback {
#[deprecated = "prefer using builtins directly, use this interface only for bindings"]
- pub fn new(params: Vec<Cow<'static, str>>, handler: impl NativeCallbackHandler) -> Self {
+ pub fn new(params: Vec<String>, handler: impl NativeCallbackHandler) -> Self {
Self {
params: params
.into_iter()
.map(|n| BuiltinParam {
- name: Some(n),
+ name: ParamName::new_dynamic(n.to_string()),
has_default: false,
})
.collect(),
crates/jrsonnet-evaluator/src/function/mod.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/function/mod.rs
+++ b/crates/jrsonnet-evaluator/src/function/mod.rs
@@ -8,7 +8,7 @@
use self::{
arglike::OptionalContext,
- builtin::{Builtin, StaticBuiltin},
+ builtin::{Builtin, BuiltinParam, ParamName, StaticBuiltin},
native::NativeDesc,
parse::{parse_default_function_call, parse_function_call},
};
@@ -113,17 +113,45 @@
}
}
+#[allow(clippy::unnecessary_wraps)]
+#[builtin]
+const fn builtin_id(x: Val) -> Val {
+ x
+}
+static ID: &builtin_id = &builtin_id {};
+
impl FuncVal {
pub fn builtin(builtin: impl Builtin) -> Self {
Self::Builtin(Cc::new(tb!(builtin)))
}
+
+ pub fn params(&self) -> Vec<BuiltinParam> {
+ match self {
+ Self::Id => ID.params().to_vec(),
+ Self::StaticBuiltin(i) => i.params().to_vec(),
+ Self::Builtin(i) => i.params().to_vec(),
+ Self::Normal(p) => p
+ .params
+ .iter()
+ .map(|p| {
+ BuiltinParam::new(
+ p.0.name()
+ .as_ref()
+ .map(IStr::to_string)
+ .map_or(ParamName::ANONYMOUS, ParamName::new_dynamic),
+ p.1.is_some(),
+ )
+ })
+ .collect(),
+ }
+ }
/// 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::StaticBuiltin(i) => i.params().iter().filter(|p| !p.has_default()).count(),
+ Self::Builtin(i) => i.params().iter().filter(|p| !p.has_default()).count(),
}
}
/// Function name, as defined in code.
@@ -146,16 +174,7 @@
tailstrict: bool,
) -> Result<Val> {
match self {
- Self::Id => {
- #[allow(clippy::unnecessary_wraps)]
- #[builtin]
- const fn builtin_id(x: Val) -> Val {
- x
- }
- static ID: &builtin_id = &builtin_id {};
-
- ID.call(call_ctx, loc, args)
- }
+ Self::Id => ID.call(call_ctx, loc, args),
Self::Normal(func) => {
let body_ctx = func.call_body_context(call_ctx, args, tailstrict)?;
evaluate(body_ctx, &func.body)
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::{ErrorKind::*, Result},11 evaluate_named,12 gc::GcHashMap,13 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(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(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(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}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::{ErrorKind::*, Result},11 evaluate_named,12 gc::GcHashMap,13 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(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_str().map(IStr::from), 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() == name)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 param.name() == name {206 found = true;207 }208 });209 if !found {210 throw!(FunctionParameterNotBoundInCall(211 param.name().as_str().map(IStr::from),212 params213 .iter()214 .map(|p| (p.name().as_str().map(IStr::from), p.has_default()))215 .collect()216 ));217 }218 }219 unreachable!();220 }221 }222 Ok(passed_args)223}224225/// Creates Context, which has all argument default values applied226/// and with unbound values causing error to be returned227pub fn parse_default_function_call(body_ctx: Context, params: &ParamsDesc) -> Result<Context> {228 #[derive(Trace)]229 struct DependsOnUnbound(IStr, ParamsDesc);230 impl ThunkValue for DependsOnUnbound {231 type Output = Val;232 fn get(self: Box<Self>) -> Result<Val> {233 Err(FunctionParameterNotBoundInCall(234 Some(self.0.clone()),235 self.1.iter().map(|p| (p.0.name(), p.1.is_some())).collect(),236 )237 .into())238 }239 }240241 let fctx = Context::new_future();242243 let mut bindings = GcHashMap::with_capacity(params.iter().map(|p| p.0.capacity_hint()).sum());244245 for param in params.iter() {246 if let Some(v) = ¶m.1 {247 destruct(248 ¶m.0.clone(),249 Thunk::new(EvaluateNamedThunk {250 ctx: fctx.clone(),251 name: param.0.name().unwrap_or_else(|| "<destruct>".into()),252 value: v.clone(),253 }),254 fctx.clone(),255 &mut bindings,256 )?;257 } else {258 destruct(259 ¶m.0,260 Thunk::new(DependsOnUnbound(261 param.0.name().unwrap_or_else(|| "<destruct>".into()),262 params.clone(),263 )),264 fctx.clone(),265 &mut bindings,266 )?;267 }268 }269270 Ok(body_ctx271 .extend(bindings, None, None, None)272 .into_future(fctx))273}tests/tests/common.rsdiffbeforeafterboth--- a/tests/tests/common.rs
+++ b/tests/tests/common.rs
@@ -1,5 +1,3 @@
-use std::borrow::Cow;
-
use jrsonnet_evaluator::{
error::Result,
function::{builtin, FuncVal},
@@ -66,22 +64,12 @@
FuncVal::StaticBuiltin(b) => b
.params()
.iter()
- .map(|p| {
- p.name
- .as_ref()
- .unwrap_or(&Cow::Borrowed("<unnamed>"))
- .to_string()
- })
+ .map(|p| p.name().as_str().unwrap_or(&"<unnamed>").to_string())
.collect(),
FuncVal::Builtin(b) => b
.params()
.iter()
- .map(|p| {
- p.name
- .as_ref()
- .unwrap_or(&Cow::Borrowed("<unnamed>"))
- .to_string()
- })
+ .map(|p| p.name().as_str().unwrap_or(&"<unnamed>").to_string())
.collect(),
}
}