1use std::rc::Rc;23use jrsonnet_gcmodule::{Cc, Trace};4use jrsonnet_interner::IStr;5use jrsonnet_ir::{6 ArgsDesc, AssertStmt, BinaryOpType, BindSpec, CompSpec, Expr, ExprParams, FieldMember,7 FieldName, ForSpecData, IfSpecData, ImportKind, LiteralType, ObjBody, ObjMembers, Spanned,8 function::ParamName,9};10use jrsonnet_types::ValType;1112use self::destructure::destruct;13use crate::{14 Context, ContextBuilder, Error, ObjValue, ObjValueBuilder, ObjectAssertion, Pending, Result,15 ResultExt, SupThis, Unbound, Val,16 arr::ArrValue,17 bail,18 destructure::evaluate_dest,19 error::{ErrorKind::*, suggest_object_fields},20 evaluate::operator::{evaluate_binary_op_special, evaluate_unary_op},21 function::{CallLocation, FuncDesc, FuncVal, PreparedFuncVal},22 in_frame,23 typed::{FromUntyped, IntoUntyped as _, Typed},24 val::{CachedUnbound, IndexableVal, StrValue, Thunk},25 with_state,26};27pub mod destructure;28pub mod operator;2930313233const RED_ZONE: usize = 100 * 1024; 34353637const STACK_PER_RECURSION: usize = 1024 * 1024; 38394041424344#[inline]45pub fn ensure_sufficient_stack<R>(f: impl FnOnce() -> R) -> R {46 stacker::maybe_grow(RED_ZONE, STACK_PER_RECURSION, f)47}4849pub fn evaluate_trivial(expr: &Expr) -> Option<Val> {50 fn is_trivial(expr: &Expr) -> bool {51 match expr {52 Expr::Str(_)53 | Expr::Num(_)54 | Expr::Literal(LiteralType::False | LiteralType::True | LiteralType::Null) => true,55 Expr::Arr(a) => a.iter().all(is_trivial),56 _ => false,57 }58 }59 Some(match expr {60 Expr::Str(s) => Val::string(s.clone()),61 Expr::Num(n) => Val::Num(*n),62 Expr::Literal(LiteralType::False) => Val::Bool(false),63 Expr::Literal(LiteralType::True) => Val::Bool(true),64 Expr::Literal(LiteralType::Null) => Val::Null,65 Expr::Arr(n) => {66 if n.iter().any(|e| !is_trivial(e)) {67 return None;68 }69 Val::Arr(70 n.iter()71 .map(evaluate_trivial)72 .map(|e| e.expect("checked trivial"))73 .collect(),74 )75 }76 _ => return None,77 })78}7980pub fn evaluate_method(ctx: Context, name: IStr, params: ExprParams, body: Rc<Expr>) -> Val {81 Val::Func(FuncVal::Normal(Cc::new(FuncDesc {82 name,83 ctx,84 params,85 body,86 })))87}8889pub fn evaluate_field_name(ctx: Context, field_name: &Spanned<FieldName>) -> Result<Option<IStr>> {90 Ok(match &field_name.value {91 FieldName::Fixed(n) => Some(n.clone()),92 FieldName::Dyn(expr) => in_frame(93 CallLocation::new(&field_name.span),94 || "evaluating field name".to_string(),95 || {96 let v = evaluate(ctx, expr)?;97 Ok(if matches!(v, Val::Null) {98 None99 } else {100 Some(IStr::from_untyped(v)?)101 })102 },103 )?,104 })105}106107pub fn evaluate_comp(108 ctx: Context,109 specs: &[CompSpec],110 mut guaranteed_reserve: usize,111 callback: &mut impl FnMut(Context, usize) -> Result<()>,112) -> Result<()> {113 match specs.first() {114 None => callback(ctx, guaranteed_reserve)?,115 Some(CompSpec::IfSpec(IfSpecData { cond, span: _ })) => {116 if bool::from_untyped(evaluate(ctx.clone(), cond)?)? {117 evaluate_comp(ctx, &specs[1..], 0, callback)?;118 }119 }120 Some(CompSpec::ForSpec(ForSpecData {121 destruct: into,122 over,123 })) => {124 match evaluate(ctx.clone(), over)? {125 Val::Arr(list) => {126 guaranteed_reserve = guaranteed_reserve.max(1) * list.len();127 for (i, item) in list.iter_lazy().enumerate() {128 let fctx = Pending::new();129 let mut ctx = ContextBuilder::extend_fast(ctx.clone());130 destruct(into, item, fctx.clone(), &mut ctx)?;131 let ctx = ctx.build().into_future(fctx);132133 let specs = &specs[1..];134 evaluate_comp(135 ctx,136 specs,137 if i == 0 || !specs.is_empty() {138 guaranteed_reserve139 } else {140 0141 },142 callback,143 )?;144 }145 }146 Val::Obj(obj) if cfg!(feature = "exp-object-iteration") => {147 let fields = obj.fields(148 149 #[cfg(feature = "exp-preserve-order")]150 false,151 );152 guaranteed_reserve = guaranteed_reserve.max(1) * fields.len();153 for (i, field) in fields.into_iter().enumerate() {154 let fctx = Pending::new();155 let mut ctx = ContextBuilder::extend_fast(ctx.clone());156 let obj = obj.clone();157 let value = Thunk::evaluated(Val::arr(vec![158 Thunk::evaluated(Val::string(field.clone())),159 obj.get_lazy(field).expect(160 "field exists, as field name was obtained from object.fields()",161 ),162 ]));163 destruct(into, value, fctx.clone(), &mut ctx)?;164 let ctx = ctx.build().into_future(fctx);165166 evaluate_comp(167 ctx,168 &specs[1..],169 if i == 0 || !specs.is_empty() {170 guaranteed_reserve171 } else {172 0173 },174 callback,175 )?;176 }177 }178 _ => bail!(InComprehensionCanOnlyIterateOverArray),179 }180 }181 }182 Ok(())183}184185fn evaluate_arr_comp(ctx: Context, expr: &Rc<Expr>, comp_specs: &[CompSpec]) -> Result<ArrValue> {186 let ctx = ctx.branch_point();187 'eager: {188 let mut out = Vec::new();189190 if evaluate_comp(ctx.clone(), comp_specs, 0, &mut |ctx, reserve| {191 if reserve != 0 {192 out.reserve(reserve);193 }194 out.push(evaluate(ctx, expr)?);195 Ok(())196 })197 .is_err()198 {199 break 'eager;200 }201202 return Ok(ArrValue::new(out));203 };204 let mut out = Vec::new();205 evaluate_comp(ctx, comp_specs, 0, &mut |ctx, reserve| {206 if reserve != 0 {207 out.reserve(reserve);208 }209 let expr = expr.clone();210 out.push(Thunk!(move || evaluate(ctx, &expr)));211 Ok(())212 })?;213 Ok(ArrValue::new(out))214}215216trait CloneableUnbound<T>: Unbound<Bound = T> + Clone {}217impl<V, T> CloneableUnbound<T> for V where V: Unbound<Bound = T> + Clone {}218219fn evaluate_object_locals(220 fctx: Context,221 locals: Rc<Vec<BindSpec>>,222) -> impl CloneableUnbound<Context> {223 #[derive(Trace, Clone)]224 struct UnboundLocals {225 fctx: Context,226 locals: Rc<Vec<BindSpec>>,227 }228 impl Unbound for UnboundLocals {229 type Bound = Context;230231 fn bind(&self, sup_this: SupThis) -> Result<Context> {232 let fctx = Context::new_future();233 let ctx = self.fctx.clone();234 let mut ctx = ContextBuilder::extend(ctx);235 for b in self.locals.iter() {236 evaluate_dest(b, fctx.clone(), &mut ctx)?;237 }238239 let ctx = ctx.build_sup_this(sup_this).into_future(fctx);240241 Ok(ctx)242 }243 }244245 UnboundLocals { fctx, locals }246}247248pub fn evaluate_field_member<B: Unbound<Bound = Context> + Clone>(249 builder: &mut ObjValueBuilder,250 ctx: Context,251 uctx: B,252 field: &FieldMember,253) -> Result<()> {254 let name = evaluate_field_name(ctx, &field.name)?;255 let Some(name) = name else {256 return Ok(());257 };258259 match field {260 FieldMember {261 plus,262 params: None,263 visibility,264 value,265 ..266 } => {267 #[derive(Trace)]268 struct UnboundValue<B: Trace> {269 uctx: B,270 value: Rc<Expr>,271 name: IStr,272 }273 impl<B: Unbound<Bound = Context>> Unbound for UnboundValue<B> {274 type Bound = Val;275 fn bind(&self, sup_this: SupThis) -> Result<Val> {276 evaluate_named(self.uctx.bind(sup_this)?, &self.value, self.name.clone())277 }278 }279280 builder281 .field(name.clone())282 .with_add(*plus)283 .with_visibility(*visibility)284 .with_location(field.name.span.clone())285 .bindable(UnboundValue {286 uctx,287 value: value.clone(),288 name,289 })?;290 }291 FieldMember {292 params: Some(params),293 visibility,294 value,295 ..296 } => {297 #[derive(Trace)]298 struct UnboundMethod<B: Trace> {299 uctx: B,300 value: Rc<Expr>,301 params: ExprParams,302 name: IStr,303 }304 impl<B: Unbound<Bound = Context>> Unbound for UnboundMethod<B> {305 type Bound = Val;306 fn bind(&self, sup_this: SupThis) -> Result<Val> {307 Ok(evaluate_method(308 self.uctx.bind(sup_this)?,309 self.name.clone(),310 self.params.clone(),311 self.value.clone(),312 ))313 }314 }315316 builder317 .field(name.clone())318 .with_visibility(*visibility)319 320 .bindable(UnboundMethod {321 uctx,322 value: value.clone(),323 params: params.clone(),324 name,325 })?;326 }327 }328 Ok(())329}330331#[derive(Trace, Clone)]332struct DirectUnbound(Context);333impl Unbound for DirectUnbound {334 type Bound = Context;335 fn bind(&self, sup_this: SupThis) -> Result<Context> {336 Ok(ContextBuilder::extend(self.0.clone()).build_sup_this(sup_this))337 }338}339340#[allow(clippy::too_many_lines)]341pub fn evaluate_member_list_object(342 super_obj: Option<ObjValue>,343 ctx: Context,344 members: &ObjMembers,345) -> Result<ObjValue> {346 #[derive(Trace)]347 struct ObjectAssert<B: Trace> {348 uctx: B,349 asserts: Rc<Vec<AssertStmt>>,350 }351 impl<B: Unbound<Bound = Context>> ObjectAssertion for ObjectAssert<B> {352 fn run(&self, sup_this: SupThis) -> Result<()> {353 let ctx = self.uctx.bind(sup_this)?;354 for assert in &*self.asserts {355 evaluate_assert(ctx.clone(), assert)?;356 }357 Ok(())358 }359 }360361 let mut builder = ObjValueBuilder::new();362 if let Some(super_obj) = super_obj {363 builder.with_super(super_obj);364 }365366 if members.locals.is_empty() {367 368 let uctx = DirectUnbound(ctx.clone());369 for field in &members.fields {370 evaluate_field_member(&mut builder, ctx.clone(), uctx.clone(), field)?;371 }372 if !members.asserts.is_empty() {373 builder.assert(ObjectAssert {374 uctx,375 asserts: members.asserts.clone(),376 });377 }378 } else {379 let locals = members.locals.clone();380 381 let uctx = CachedUnbound::new(evaluate_object_locals(ctx.clone(), locals));382 for field in &members.fields {383 evaluate_field_member(&mut builder, ctx.clone(), uctx.clone(), field)?;384 }385 if !members.asserts.is_empty() {386 builder.assert(ObjectAssert {387 uctx,388 asserts: members.asserts.clone(),389 });390 }391 }392393 Ok(builder.build())394}395396pub fn evaluate_object(397 super_obj: Option<ObjValue>,398 ctx: Context,399 object: &ObjBody,400) -> Result<ObjValue> {401 Ok(match object {402 ObjBody::MemberList(members) => evaluate_member_list_object(super_obj, ctx, members)?,403 ObjBody::ObjComp(obj) => {404 let mut builder = ObjValueBuilder::new();405 if let Some(super_obj) = super_obj {406 builder.with_super(super_obj);407 }408 let locals = obj.locals.clone();409 evaluate_comp(410 ctx.branch_point(),411 &obj.compspecs,412 0,413 &mut |ctx, reserve| {414 let uctx = evaluate_object_locals(ctx.clone(), locals.clone());415 builder.reserve_fields(reserve);416417 evaluate_field_member(&mut builder, ctx, uctx, &obj.field)418 },419 )?;420421 builder.build()422 }423 })424}425426pub fn evaluate_apply(427 ctx: Context,428 value: &Expr,429 args: &ArgsDesc,430 loc: CallLocation<'_>,431 tailstrict: bool,432) -> Result<Val> {433 let value = evaluate(ctx.clone(), value)?;434 Ok(match value {435 Val::Func(f) => {436 let name = f.name();437 let unnamed = args438 .unnamed439 .iter()440 .cloned()441 .map(|un| evaluate_thunk(ctx.clone(), un, tailstrict))442 .collect::<Result<Vec<_>>>()?;443 let named = args444 .values445 .iter()446 .cloned()447 .map(|un| evaluate_thunk(ctx.clone(), un, tailstrict))448 .collect::<Result<Vec<_>>>()?;449 let prepare = PreparedFuncVal::new(f, args.unnamed.len(), &args.names)450 .with_description_src(loc, || format!("function <{name}> call"))?;451 let body = || prepare.call(loc, &unnamed, &named);452 if tailstrict {453 body()?454 } else {455 in_frame(loc, || format!("function <{name}> call"), body)?456 }457 }458 v => bail!(OnlyFunctionsCanBeCalledGot(v.value_type())),459 })460}461462pub fn evaluate_assert(ctx: Context, assertion: &AssertStmt) -> Result<()> {463 let AssertStmt { assertion, message } = assertion;464 let assertion_result = in_frame(465 CallLocation::new(&assertion.span),466 || "assertion condition".to_owned(),467 || bool::from_untyped(evaluate(ctx.clone(), assertion)?),468 )?;469 if !assertion_result {470 in_frame(471 CallLocation::new(&assertion.span),472 || "assertion failure".to_owned(),473 || {474 if let Some(msg) = message {475 bail!(AssertionFailed(evaluate(ctx, msg)?.to_string()?));476 }477 bail!(AssertionFailed(Val::Null.to_string()?));478 },479 )?;480 }481 Ok(())482}483484pub fn evaluate_named_param(ctx: Context, expr: &Expr, name: ParamName) -> Result<Val> {485 match name {486 ParamName::Named(name) => evaluate_named(ctx, expr, name),487 ParamName::Unnamed => evaluate(ctx, expr),488 }489}490491pub fn evaluate_named(ctx: Context, expr: &Expr, name: IStr) -> Result<Val> {492 use Expr::*;493 Ok(match expr {494 Function(params, body) => evaluate_method(ctx, name, params.clone(), body.clone()),495 _ => evaluate(ctx, expr)?,496 })497}498499pub fn evaluate_thunk(ctx: Context, expr: Rc<Expr>, tailstrict: bool) -> Result<Thunk<Val>> {500 Ok(if tailstrict {501 Thunk::evaluated(evaluate(ctx, &expr)?)502 } else {503 Thunk!(move || { evaluate(ctx, &expr) })504 })505}506#[allow(clippy::too_many_lines)]507pub fn evaluate(ctx: Context, expr: &Expr) -> Result<Val> {508 use Expr::*;509510 Ok(match expr {511 Literal(LiteralType::This) => Val::Obj(ctx.try_this()?),512 Literal(LiteralType::Super) => Val::Obj(ctx.try_sup_this()?.standalone_super()?),513 Literal(LiteralType::Dollar) => Val::Obj(ctx.try_dollar()?),514 Literal(LiteralType::True) => Val::Bool(true),515 Literal(LiteralType::False) => Val::Bool(false),516 Literal(LiteralType::Null) => Val::Null,517 Str(v) => Val::string(v.clone()),518 Num(v) => Val::try_num(*v)?,519 520 521 522 523 524 525 BinaryOp(bin)526 if matches!(&bin.rhs, Expr::Literal(LiteralType::Super))527 && bin.op == BinaryOpType::In =>528 {529 let sup_this = ctx.try_sup_this()?;530 531 532 if !sup_this.has_super() {533 return Ok(Val::Bool(false));534 }535 let field = evaluate(ctx, &bin.lhs)?;536 Val::Bool(sup_this.field_in_super(field.to_string()?))537 }538 BinaryOp(bin) => evaluate_binary_op_special(ctx, &bin.lhs, bin.op, &bin.rhs)?,539 UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(ctx, v)?)?,540 Var(name) => in_frame(541 CallLocation::new(&name.span),542 || format!("local <{}> access", &**name),543 || ctx.binding((**name).clone())?.evaluate(),544 )?,545 Index { indexable, parts } => ensure_sufficient_stack(|| {546 let mut parts = parts.iter();547 let mut indexable = if matches!(&**indexable, Expr::Literal(LiteralType::Super)) {548 let part = parts.next().expect("at least part should exist");549 550 551 let sup_this = ctx.try_sup_this()?;552 if !sup_this.has_super() {553 #[cfg(feature = "exp-null-coaelse")]554 if part.null_coaelse {555 return Ok(Val::Null);556 }557 bail!(NoSuperFound)558 }559 let name = evaluate(ctx.clone(), &part.value)?;560561 let Val::Str(name) = name else {562 bail!(ValueIndexMustBeTypeGot(563 ValType::Obj,564 ValType::Str,565 name.value_type(),566 ))567 };568569 let name = name.into_flat();570 match sup_this571 .get_super(name.clone())572 .with_description_src(&part.span, || format!("field <{name}> access"))?573 {574 Some(v) => v,575 #[cfg(feature = "exp-null-coaelse")]576 None if part.null_coaelse => return Ok(Val::Null),577 None => {578 let suggestions = suggest_object_fields(579 &sup_this.standalone_super().expect("super exists"),580 name.clone(),581 );582583 bail!(NoSuchField(name, suggestions))584 }585 }586 } else {587 evaluate(ctx.clone(), indexable)?588 };589590 for part in parts {591 indexable = match (indexable, evaluate(ctx.clone(), &part.value)?) {592 (Val::Obj(v), Val::Str(key)) => match v593 .get(key.clone().into_flat())594 .with_description_src(&part.span, || format!("field <{key}> access"))?595 {596 Some(v) => v,597 #[cfg(feature = "exp-null-coaelse")]598 None if part.null_coaelse => return Ok(Val::Null),599 None => {600 let suggestions = suggest_object_fields(&v, key.into_flat());601602 return Err(Error::from(NoSuchField(603 key.clone().into_flat(),604 suggestions,605 )))606 .with_description_src(&part.span, || format!("field <{key}> access"));607 }608 },609 (Val::Obj(_), n) => bail!(ValueIndexMustBeTypeGot(610 ValType::Obj,611 ValType::Str,612 n.value_type(),613 )),614 (Val::Arr(v), Val::Num(n)) => {615 let n = n.get();616 if n.fract() > f64::EPSILON {617 bail!(FractionalIndex)618 }619 if n < 0.0 {620 #[expect(621 clippy::cast_possible_truncation,622 reason = "it would be truncated anyway"623 )]624 let n = n as isize;625 bail!(ArrayBoundsError(n, v.len()));626 }627 #[expect(628 clippy::cast_possible_truncation,629 clippy::cast_sign_loss,630 reason = "n is checked postive"631 )]632 v.get(n as usize)?633 .ok_or_else(|| ArrayBoundsError(n as isize, v.len()))?634 }635 (Val::Arr(_), Val::Str(n)) => {636 bail!(AttemptedIndexAnArrayWithString(n.into_flat()))637 }638 (Val::Arr(_), n) => bail!(ValueIndexMustBeTypeGot(639 ValType::Arr,640 ValType::Num,641 n.value_type(),642 )),643644 (Val::Str(s), Val::Num(n)) => Val::Str({645 let n = n.get();646 if n.fract() > f64::EPSILON {647 bail!(FractionalIndex)648 }649 if n < 0.0 {650 #[expect(651 clippy::cast_possible_truncation,652 reason = "it would be truncated anyway"653 )]654 let n = n as isize;655 bail!(ArrayBoundsError(n, s.into_flat().chars().count()));656 }657 #[expect(658 clippy::cast_sign_loss,659 clippy::cast_possible_truncation,660 reason = "n is positive, overflow will truncate as expected"661 )]662 let n = n as usize;663 let v: IStr = s664 .clone()665 .into_flat()666 .chars()667 .skip(n)668 .take(1)669 .collect::<String>()670 .into();671 if v.is_empty() {672 bail!(StringBoundsError(n, s.into_flat().chars().count()))673 }674 StrValue::Flat(v)675 }),676 (Val::Str(_), n) => bail!(ValueIndexMustBeTypeGot(677 ValType::Str,678 ValType::Num,679 n.value_type(),680 )),681 #[cfg(feature = "exp-null-coaelse")]682 (Val::Null, _) if part.null_coaelse => return Ok(Val::Null),683 (v, _) => bail!(CantIndexInto(v.value_type())),684 };685 }686 Ok(indexable)687 })?,688 LocalExpr(bindings, returned) => {689 let fctx = Context::new_future();690 let mut ctx = ContextBuilder::extend(ctx);691 for b in bindings {692 evaluate_dest(b, fctx.clone(), &mut ctx)?;693 }694 let ctx = ctx.build().into_future(fctx);695 evaluate(ctx, returned)?696 }697 Arr(items) => {698 if items.is_empty() {699 Val::arr(())700 } else {701 Val::Arr(ArrValue::expr(ctx, items.clone()))702 }703 }704 ArrComp(expr, comp_specs) => Val::Arr(evaluate_arr_comp(ctx, expr, comp_specs)?),705 Obj(body) => Val::Obj(evaluate_object(None, ctx, body)?),706 ObjExtend(a, b) => {707 let base = evaluate(ctx.clone(), a)?;708 match base {709 Val::Obj(base_obj) => Val::Obj(evaluate_object(Some(base_obj), ctx, b)?),710 _ => bail!("ObjExtend lhs should be an object value"),711 }712 }713 Apply(value, args, tailstrict) => ensure_sufficient_stack(|| {714 evaluate_apply(ctx, value, args, CallLocation::new(&args.span), *tailstrict)715 })?,716 Function(params, body) => {717 evaluate_method(ctx, "anonymous".into(), params.clone(), body.clone())718 }719 AssertExpr(assert) => {720 evaluate_assert(ctx.clone(), &assert.assert)?;721 evaluate(ctx, &assert.rest)?722 }723 ErrorStmt(s, e) => in_frame(724 CallLocation::new(s),725 || "error statement".to_owned(),726 || bail!(RuntimeError(evaluate(ctx, e)?.to_string()?,)),727 )?,728 IfElse(if_else) => {729 if in_frame(730 CallLocation::new(&if_else.cond.span),731 || "if condition".to_owned(),732 || bool::from_untyped(evaluate(ctx.clone(), &if_else.cond.cond)?),733 )? {734 evaluate(ctx, &if_else.cond_then)?735 } else {736 match &if_else.cond_else {737 Some(v) => evaluate(ctx, v)?,738 None => Val::Null,739 }740 }741 }742 Slice(slice) => {743 fn parse_idx<T: Typed + FromUntyped>(744 ctx: Context,745 expr: Option<&Spanned<Expr>>,746 desc: &'static str,747 ) -> Result<Option<T>> {748 if let Some(value) = expr {749 Ok(in_frame(750 CallLocation::new(&value.span),751 || format!("slice {desc}"),752 || <Option<T>>::from_untyped(evaluate(ctx, value)?),753 )?)754 } else {755 Ok(None)756 }757 }758759 let indexable = evaluate(ctx.clone(), &slice.value)?;760761 let start = parse_idx(ctx.clone(), slice.slice.start.as_ref(), "start")?;762 let end = parse_idx(ctx.clone(), slice.slice.end.as_ref(), "end")?;763 let step = parse_idx(ctx, slice.slice.step.as_ref(), "step")?;764765 IndexableVal::into_untyped(indexable.into_indexable()?.slice(start, end, step)?)?766 }767 Import(kind, path) => {768 let Expr::Str(path) = &**path else {769 bail!("computed imports are not supported")770 };771 with_state(|s| {772 let span = &kind.span;773 let resolved_path = s.resolve_from(span.0.source_path(), path)?;774 Ok(match &**kind {775 ImportKind::Normal => in_frame(776 CallLocation::new(span),777 || format!("import {:?}", path.clone()),778 || s.import_resolved(resolved_path),779 )?,780 ImportKind::Str => Val::string(s.import_resolved_str(resolved_path)?),781 ImportKind::Bin => Val::arr(s.import_resolved_bin(resolved_path)?),782 }) as Result<Val>783 })?784 }785 })786}