difftreelog
feat treat trivial identity function as so
in: master
1 file changed
crates/jrsonnet-evaluator/src/function/mod.rsdiffbeforeafterboth1use std::fmt::Debug;23pub use arglike::{ArgLike, ArgsLike, TlaArg};4use jrsonnet_gcmodule::{Cc, Trace};5use jrsonnet_interner::IStr;6pub use jrsonnet_macros::builtin;7use jrsonnet_parser::{ExprLocation, LocExpr, ParamsDesc};89use self::{10 builtin::{Builtin, StaticBuiltin},11 native::NativeDesc,12 parse::{parse_default_function_call, parse_function_call},13};14use crate::{evaluate, gc::TraceBox, typed::Any, Context, Result, State, Val};1516pub mod arglike;17pub mod builtin;18pub mod native;19pub mod parse;2021/// Function callsite location.22/// Either from other jsonnet code, specified by expression location, or from native (without location).23#[derive(Clone, Copy)]24pub struct CallLocation<'l>(pub Option<&'l ExprLocation>);25impl<'l> CallLocation<'l> {26 /// Construct new location for calls coming from specified jsonnet expression location.27 pub const fn new(loc: &'l ExprLocation) -> Self {28 Self(Some(loc))29 }30}31impl CallLocation<'static> {32 /// Construct new location for calls coming from native code.33 pub const fn native() -> Self {34 Self(None)35 }36}3738/// Represents Jsonnet function defined in code.39#[derive(Debug, PartialEq, Trace)]40pub struct FuncDesc {41 /// # Example42 ///43 /// In expressions like this, deducted to `a`, unspecified otherwise.44 /// ```jsonnet45 /// local a = function() ...46 /// local a() ...47 /// { a: function() ... }48 /// { a() = ... }49 /// ```50 pub name: IStr,51 /// Context, in which this function was evaluated.52 ///53 /// # Example54 /// In55 /// ```jsonnet56 /// local a = 2;57 /// function() ...58 /// ```59 /// context will contain `a`.60 pub ctx: Context,6162 /// Function parameter definition63 pub params: ParamsDesc,64 /// Function body65 pub body: LocExpr,66}67impl FuncDesc {68 /// Create body context, but fill arguments without defaults with lazy error69 pub fn default_body_context(&self) -> Result<Context> {70 parse_default_function_call(self.ctx.clone(), &self.params)71 }7273 /// Create context, with which body code will run74 pub fn call_body_context(75 &self,76 s: State,77 call_ctx: Context,78 args: &dyn ArgsLike,79 tailstrict: bool,80 ) -> Result<Context> {81 parse_function_call(82 s,83 call_ctx,84 self.ctx.clone(),85 &self.params,86 args,87 tailstrict,88 )89 }90}9192/// Represents a Jsonnet function value, including plain functions and user-provided builtins.93#[allow(clippy::module_name_repetitions)]94#[derive(Trace, Clone)]95pub enum FuncVal {96 /// Identity function, kept this way for comparsions.97 Id,98 /// Plain function implemented in jsonnet.99 Normal(Cc<FuncDesc>),100 /// Standard library function.101 StaticBuiltin(#[trace(skip)] &'static dyn StaticBuiltin),102 /// User-provided function.103 Builtin(Cc<TraceBox<dyn Builtin>>),104}105106impl Debug for FuncVal {107 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {108 match self {109 Self::Id => f.debug_tuple("Id").finish(),110 Self::Normal(arg0) => f.debug_tuple("Normal").field(arg0).finish(),111 Self::StaticBuiltin(arg0) => {112 f.debug_tuple("StaticBuiltin").field(&arg0.name()).finish()113 }114 Self::Builtin(arg0) => f.debug_tuple("Builtin").field(&arg0.name()).finish(),115 }116 }117}118119impl FuncVal {120 /// Amount of non-default required arguments121 pub fn params_len(&self) -> usize {122 match self {123 Self::Id => 1,124 Self::Normal(n) => n.params.iter().filter(|p| p.1.is_none()).count(),125 Self::StaticBuiltin(i) => i.params().iter().filter(|p| !p.has_default).count(),126 Self::Builtin(i) => i.params().iter().filter(|p| !p.has_default).count(),127 }128 }129 /// Function name, as defined in code.130 pub fn name(&self) -> IStr {131 match self {132 Self::Id => "id".into(),133 Self::Normal(normal) => normal.name.clone(),134 Self::StaticBuiltin(builtin) => builtin.name().into(),135 Self::Builtin(builtin) => builtin.name().into(),136 }137 }138 /// Call function using arguments evaluated in specified `call_ctx` [`Context`].139 ///140 /// If `tailstrict` is specified - then arguments will be evaluated before being passed to function body.141 pub fn evaluate(142 &self,143 s: State,144 call_ctx: Context,145 loc: CallLocation<'_>,146 args: &dyn ArgsLike,147 tailstrict: bool,148 ) -> Result<Val> {149 match self {150 Self::Id => {151 #[allow(clippy::unnecessary_wraps)]152 #[builtin]153 const fn builtin_id(v: Any) -> Result<Any> {154 Ok(v)155 }156 static ID: &builtin_id = &builtin_id {};157158 ID.call(s, call_ctx, loc, args)159 }160 Self::Normal(func) => {161 let body_ctx = func.call_body_context(s.clone(), call_ctx, args, tailstrict)?;162 evaluate(s, body_ctx, &func.body)163 }164 Self::StaticBuiltin(b) => b.call(s, call_ctx, loc, args),165 Self::Builtin(b) => b.call(s, call_ctx, loc, args),166 }167 }168 /// Helper method, which calls [`Self::evaluate`] with sensible defaults for native code.169 pub fn evaluate_simple(&self, s: State, args: &dyn ArgsLike) -> Result<Val> {170 self.evaluate(s, Context::default(), CallLocation::native(), args, true)171 }172 /// Convert jsonnet function to plain `Fn` value.173 pub fn into_native<D: NativeDesc>(self) -> D::Value {174 D::into_native(self)175 }176177 /// Is this function an indentity function.178 ///179 /// Currently only works for builtin `std.id`, aka `Self::Id` value, `function(x) x` defined by jsonnet will not count as identity.180 pub const fn is_identity(&self) -> bool {181 matches!(self, Self::Id)182 }183 /// Identity function value.184 pub const fn identity() -> Self {185 Self::Id186 }187}1use std::fmt::Debug;23pub use arglike::{ArgLike, ArgsLike, TlaArg};4use jrsonnet_gcmodule::{Cc, Trace};5use jrsonnet_interner::IStr;6pub use jrsonnet_macros::builtin;7use jrsonnet_parser::{Destruct, Expr, ExprLocation, LocExpr, ParamsDesc};89use self::{10 builtin::{Builtin, StaticBuiltin},11 native::NativeDesc,12 parse::{parse_default_function_call, parse_function_call},13};14use crate::{evaluate, gc::TraceBox, typed::Any, Context, Result, State, Val};1516pub mod arglike;17pub mod builtin;18pub mod native;19pub mod parse;2021/// Function callsite location.22/// Either from other jsonnet code, specified by expression location, or from native (without location).23#[derive(Clone, Copy)]24pub struct CallLocation<'l>(pub Option<&'l ExprLocation>);25impl<'l> CallLocation<'l> {26 /// Construct new location for calls coming from specified jsonnet expression location.27 pub const fn new(loc: &'l ExprLocation) -> Self {28 Self(Some(loc))29 }30}31impl CallLocation<'static> {32 /// Construct new location for calls coming from native code.33 pub const fn native() -> Self {34 Self(None)35 }36}3738/// Represents Jsonnet function defined in code.39#[derive(Debug, PartialEq, Trace)]40pub struct FuncDesc {41 /// # Example42 ///43 /// In expressions like this, deducted to `a`, unspecified otherwise.44 /// ```jsonnet45 /// local a = function() ...46 /// local a() ...47 /// { a: function() ... }48 /// { a() = ... }49 /// ```50 pub name: IStr,51 /// Context, in which this function was evaluated.52 ///53 /// # Example54 /// In55 /// ```jsonnet56 /// local a = 2;57 /// function() ...58 /// ```59 /// context will contain `a`.60 pub ctx: Context,6162 /// Function parameter definition63 pub params: ParamsDesc,64 /// Function body65 pub body: LocExpr,66}67impl FuncDesc {68 /// Create body context, but fill arguments without defaults with lazy error69 pub fn default_body_context(&self) -> Result<Context> {70 parse_default_function_call(self.ctx.clone(), &self.params)71 }7273 /// Create context, with which body code will run74 pub fn call_body_context(75 &self,76 s: State,77 call_ctx: Context,78 args: &dyn ArgsLike,79 tailstrict: bool,80 ) -> Result<Context> {81 parse_function_call(82 s,83 call_ctx,84 self.ctx.clone(),85 &self.params,86 args,87 tailstrict,88 )89 }90}9192/// Represents a Jsonnet function value, including plain functions and user-provided builtins.93#[allow(clippy::module_name_repetitions)]94#[derive(Trace, Clone)]95pub enum FuncVal {96 /// Identity function, kept this way for comparsions.97 Id,98 /// Plain function implemented in jsonnet.99 Normal(Cc<FuncDesc>),100 /// Standard library function.101 StaticBuiltin(#[trace(skip)] &'static dyn StaticBuiltin),102 /// User-provided function.103 Builtin(Cc<TraceBox<dyn Builtin>>),104}105106impl Debug for FuncVal {107 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {108 match self {109 Self::Id => f.debug_tuple("Id").finish(),110 Self::Normal(arg0) => f.debug_tuple("Normal").field(arg0).finish(),111 Self::StaticBuiltin(arg0) => {112 f.debug_tuple("StaticBuiltin").field(&arg0.name()).finish()113 }114 Self::Builtin(arg0) => f.debug_tuple("Builtin").field(&arg0.name()).finish(),115 }116 }117}118119impl FuncVal {120 /// Amount of non-default required arguments121 pub fn params_len(&self) -> usize {122 match self {123 Self::Id => 1,124 Self::Normal(n) => n.params.iter().filter(|p| p.1.is_none()).count(),125 Self::StaticBuiltin(i) => i.params().iter().filter(|p| !p.has_default).count(),126 Self::Builtin(i) => i.params().iter().filter(|p| !p.has_default).count(),127 }128 }129 /// Function name, as defined in code.130 pub fn name(&self) -> IStr {131 match self {132 Self::Id => "id".into(),133 Self::Normal(normal) => normal.name.clone(),134 Self::StaticBuiltin(builtin) => builtin.name().into(),135 Self::Builtin(builtin) => builtin.name().into(),136 }137 }138 /// Call function using arguments evaluated in specified `call_ctx` [`Context`].139 ///140 /// If `tailstrict` is specified - then arguments will be evaluated before being passed to function body.141 pub fn evaluate(142 &self,143 s: State,144 call_ctx: Context,145 loc: CallLocation<'_>,146 args: &dyn ArgsLike,147 tailstrict: bool,148 ) -> Result<Val> {149 match self {150 Self::Id => {151 #[allow(clippy::unnecessary_wraps)]152 #[builtin]153 const fn builtin_id(v: Any) -> Result<Any> {154 Ok(v)155 }156 static ID: &builtin_id = &builtin_id {};157158 ID.call(s, call_ctx, loc, args)159 }160 Self::Normal(func) => {161 let body_ctx = func.call_body_context(s.clone(), call_ctx, args, tailstrict)?;162 evaluate(s, body_ctx, &func.body)163 }164 Self::StaticBuiltin(b) => b.call(s, call_ctx, loc, args),165 Self::Builtin(b) => b.call(s, call_ctx, loc, args),166 }167 }168 /// Helper method, which calls [`Self::evaluate`] with sensible defaults for native code.169 pub fn evaluate_simple(&self, s: State, args: &dyn ArgsLike) -> Result<Val> {170 self.evaluate(s, Context::default(), CallLocation::native(), args, true)171 }172 /// Convert jsonnet function to plain `Fn` value.173 pub fn into_native<D: NativeDesc>(self) -> D::Value {174 D::into_native(self)175 }176177 /// Is this function an indentity function.178 ///179 /// Currently only works for builtin `std.id`, aka `Self::Id` value, and `function(x) x`.180 ///181 /// This function should only be used for optimization, not for the conditional logic, i.e code should work with syntetic identity function too182 pub fn is_identity(&self) -> bool {183 if matches!(self, Self::Id) {184 return true;185 }186187 match self {188 Self::Id => true,189 Self::Normal(desc) => {190 if desc.params.len() != 1 {191 return false;192 }193 let param = &desc.params[0];194 if param.1.is_some() {195 return false;196 }197 #[allow(clippy::infallible_destructuring_match)]198 let id = match ¶m.0 {199 Destruct::Full(id) => id,200 #[cfg(feature = "exp-destruct")]201 _ => return false,202 };203 &desc.body.0 as &Expr == &Expr::Var(id.clone())204 }205 _ => false,206 }207 }208 /// Identity function value.209 pub const fn identity() -> Self {210 Self::Id211 }212}