1use ecow::EcoString;
2use num_bigint::BigInt;
3use thiserror::Error;
4
5use super::{BitArrayValue, CustomValue, ExternalValue, FunctionValue, Value};
6use crate::plan::{CustomType, ExternalType, FunctionType, TypeParameterId, ValueType};
7
8#[derive(Debug, Clone, PartialEq)]
9pub struct ListValue {
10 kind: ListValueKind,
11}
12
13#[derive(Debug, Error, Clone, PartialEq, Eq)]
14#[error("list value item type mismatch at index {index} (expected {expected:?}, got {actual:?})")]
15pub struct ListValueItemTypeMismatch {
16 pub index: usize,
17 pub expected: ValueType,
18 pub actual: ValueType,
19}
20
21#[derive(Debug, Clone, PartialEq)]
24pub(super) enum ListValueKind {
25 Parameter(TypeParameterId),
26 Int(Vec<BigInt>),
27 String(Vec<EcoString>),
28 BitArray(Vec<BitArrayValue>),
29 UtfCodepoint(Vec<char>),
30 Custom {
31 item_type: CustomType,
32 values: Vec<CustomValue>,
33 },
34 External {
35 item_type: ExternalType,
36 values: Vec<ExternalValue>,
37 },
38 Float(Vec<f64>),
39 Bool(Vec<bool>),
40 Nil(usize),
41 Tuple {
42 item_type: Vec<ValueType>,
43 values: Vec<Vec<Value>>,
44 },
45 List {
46 item_type: Box<ValueType>,
47 values: Vec<ListValue>,
48 },
49 Function {
50 item_type: FunctionType,
51 values: Vec<FunctionValue>,
52 },
53}
54
55impl ListValue {
56 pub fn int(values: Vec<BigInt>) -> Self {
57 Self {
58 kind: ListValueKind::Int(values),
59 }
60 }
61
62 pub fn string(values: Vec<EcoString>) -> Self {
63 Self {
64 kind: ListValueKind::String(values),
65 }
66 }
67
68 pub fn bit_array(values: Vec<BitArrayValue>) -> Self {
69 Self {
70 kind: ListValueKind::BitArray(values),
71 }
72 }
73
74 pub fn utf_codepoint(values: Vec<char>) -> Self {
75 Self {
76 kind: ListValueKind::UtfCodepoint(values),
77 }
78 }
79
80 pub fn try_custom(
81 item_type: CustomType,
82 values: Vec<CustomValue>,
83 ) -> Result<Self, ListValueItemTypeMismatch> {
84 let expected = ValueType::Custom(item_type.clone());
85 ensure_item_types(
86 &expected,
87 values
88 .iter()
89 .map(|value| ValueType::Custom(value.type_().clone())),
90 )?;
91 Ok(Self {
92 kind: ListValueKind::Custom { item_type, values },
93 })
94 }
95
96 pub(crate) fn from_evaluated_custom(item_type: CustomType, values: Vec<CustomValue>) -> Self {
97 Self {
98 kind: ListValueKind::Custom { item_type, values },
99 }
100 }
101
102 pub fn try_external(
103 item_type: ExternalType,
104 values: Vec<ExternalValue>,
105 ) -> Result<Self, ListValueItemTypeMismatch> {
106 let expected = ValueType::External(item_type.clone());
107 ensure_item_types(
108 &expected,
109 values
110 .iter()
111 .map(|value| ValueType::External(value.type_().clone())),
112 )?;
113 Ok(Self {
114 kind: ListValueKind::External { item_type, values },
115 })
116 }
117
118 pub(crate) fn from_evaluated_external(
119 item_type: ExternalType,
120 values: Vec<ExternalValue>,
121 ) -> Self {
122 Self {
123 kind: ListValueKind::External { item_type, values },
124 }
125 }
126
127 pub fn float(values: Vec<f64>) -> Self {
128 Self {
129 kind: ListValueKind::Float(values),
130 }
131 }
132
133 pub fn bool(values: Vec<bool>) -> Self {
134 Self {
135 kind: ListValueKind::Bool(values),
136 }
137 }
138
139 pub fn nil(len: usize) -> Self {
140 Self {
141 kind: ListValueKind::Nil(len),
142 }
143 }
144
145 pub fn try_tuple(
146 item_type: Vec<ValueType>,
147 values: Vec<Vec<Value>>,
148 ) -> Result<Self, ListValueItemTypeMismatch> {
149 let expected = ValueType::Tuple(item_type.clone());
150 ensure_item_types(
151 &expected,
152 values
153 .iter()
154 .map(|values| ValueType::Tuple(values.iter().map(Value::value_type).collect())),
155 )?;
156 Ok(Self {
157 kind: ListValueKind::Tuple { item_type, values },
158 })
159 }
160
161 pub(crate) fn from_evaluated_tuple(item_type: Vec<ValueType>, values: Vec<Vec<Value>>) -> Self {
162 Self {
163 kind: ListValueKind::Tuple { item_type, values },
164 }
165 }
166
167 pub fn try_list(
168 item_type: ValueType,
169 values: Vec<ListValue>,
170 ) -> Result<Self, ListValueItemTypeMismatch> {
171 let expected = ValueType::List(Box::new(item_type.clone()));
172 ensure_item_types(
173 &expected,
174 values
175 .iter()
176 .map(|value| ValueType::List(Box::new(value.item_type()))),
177 )?;
178 Ok(Self {
179 kind: ListValueKind::List {
180 item_type: Box::new(item_type),
181 values,
182 },
183 })
184 }
185
186 pub(crate) fn from_evaluated_list(item_type: ValueType, values: Vec<ListValue>) -> Self {
187 Self {
188 kind: ListValueKind::List {
189 item_type: Box::new(item_type),
190 values,
191 },
192 }
193 }
194
195 pub fn try_function(
196 item_type: FunctionType,
197 values: Vec<FunctionValue>,
198 ) -> Result<Self, ListValueItemTypeMismatch> {
199 let expected = ValueType::Function(Box::new(item_type.clone()));
200 ensure_item_types(
201 &expected,
202 values
203 .iter()
204 .map(|value| ValueType::Function(Box::new(value.type_()))),
205 )?;
206 Ok(Self {
207 kind: ListValueKind::Function { item_type, values },
208 })
209 }
210
211 pub(crate) fn from_evaluated_function(
212 item_type: FunctionType,
213 values: Vec<FunctionValue>,
214 ) -> Self {
215 Self {
216 kind: ListValueKind::Function { item_type, values },
217 }
218 }
219
220 pub fn empty(item_type: ValueType) -> Self {
221 match item_type {
222 ValueType::Parameter(parameter) => Self {
223 kind: ListValueKind::Parameter(parameter),
224 },
225 ValueType::Int => Self::int(Vec::new()),
226 ValueType::String => Self::string(Vec::new()),
227 ValueType::BitArray => Self::bit_array(Vec::new()),
228 ValueType::UtfCodepoint => Self::utf_codepoint(Vec::new()),
229 ValueType::Custom(item_type) => Self {
230 kind: ListValueKind::Custom {
231 item_type,
232 values: Vec::new(),
233 },
234 },
235 ValueType::External(item_type) => Self {
236 kind: ListValueKind::External {
237 item_type,
238 values: Vec::new(),
239 },
240 },
241 ValueType::Float => Self::float(Vec::new()),
242 ValueType::Bool => Self::bool(Vec::new()),
243 ValueType::Nil => Self::nil(0),
244 ValueType::Tuple(item_type) => Self {
245 kind: ListValueKind::Tuple {
246 item_type,
247 values: Vec::new(),
248 },
249 },
250 ValueType::List(item_type) => Self {
251 kind: ListValueKind::List {
252 item_type,
253 values: Vec::new(),
254 },
255 },
256 ValueType::Function(item_type) => Self {
257 kind: ListValueKind::Function {
258 item_type: *item_type,
259 values: Vec::new(),
260 },
261 },
262 }
263 }
264
265 pub fn item_type(&self) -> ValueType {
266 match &self.kind {
267 ListValueKind::Parameter(parameter) => ValueType::Parameter(*parameter),
268 ListValueKind::Int(_) => ValueType::Int,
269 ListValueKind::String(_) => ValueType::String,
270 ListValueKind::BitArray(_) => ValueType::BitArray,
271 ListValueKind::UtfCodepoint(_) => ValueType::UtfCodepoint,
272 ListValueKind::Custom { item_type, .. } => ValueType::Custom(item_type.clone()),
273 ListValueKind::External { item_type, .. } => ValueType::External(item_type.clone()),
274 ListValueKind::Float(_) => ValueType::Float,
275 ListValueKind::Bool(_) => ValueType::Bool,
276 ListValueKind::Nil(_) => ValueType::Nil,
277 ListValueKind::Tuple { item_type, .. } => ValueType::Tuple(item_type.clone()),
278 ListValueKind::List { item_type, .. } => ValueType::List(item_type.clone()),
279 ListValueKind::Function { item_type, .. } => {
280 ValueType::Function(Box::new(item_type.clone()))
281 }
282 }
283 }
284
285 pub fn len(&self) -> usize {
286 match &self.kind {
287 ListValueKind::Parameter(_) => 0,
288 ListValueKind::Int(values) => values.len(),
289 ListValueKind::String(values) => values.len(),
290 ListValueKind::BitArray(values) => values.len(),
291 ListValueKind::UtfCodepoint(values) => values.len(),
292 ListValueKind::Custom { values, .. } => values.len(),
293 ListValueKind::External { values, .. } => values.len(),
294 ListValueKind::Float(values) => values.len(),
295 ListValueKind::Bool(values) => values.len(),
296 ListValueKind::Nil(len) => *len,
297 ListValueKind::Tuple { values, .. } => values.len(),
298 ListValueKind::List { values, .. } => values.len(),
299 ListValueKind::Function { values, .. } => values.len(),
300 }
301 }
302
303 pub fn is_empty(&self) -> bool {
304 self.len() == 0
305 }
306
307 pub fn to_values(&self) -> Vec<Value> {
308 match &self.kind {
309 ListValueKind::Parameter(_) => Vec::new(),
310 ListValueKind::Int(values) => values.iter().cloned().map(Value::Int).collect(),
311 ListValueKind::String(values) => values.iter().cloned().map(Value::String).collect(),
312 ListValueKind::BitArray(values) => {
313 values.iter().cloned().map(Value::BitArray).collect()
314 }
315 ListValueKind::UtfCodepoint(values) => {
316 values.iter().copied().map(Value::UtfCodepoint).collect()
317 }
318 ListValueKind::Custom { values, .. } => {
319 values.iter().cloned().map(Value::Custom).collect()
320 }
321 ListValueKind::External { values, .. } => {
322 values.iter().cloned().map(Value::External).collect()
323 }
324 ListValueKind::Float(values) => values.iter().copied().map(Value::Float).collect(),
325 ListValueKind::Bool(values) => values.iter().copied().map(Value::Bool).collect(),
326 ListValueKind::Nil(len) => vec![Value::Nil; *len],
327 ListValueKind::Tuple { values, .. } => {
328 values.iter().cloned().map(Value::Tuple).collect()
329 }
330 ListValueKind::List { values, .. } => values.iter().cloned().map(Value::List).collect(),
331 ListValueKind::Function { values, .. } => {
332 values.iter().cloned().map(Value::Function).collect()
333 }
334 }
335 }
336
337 pub(super) fn kind(&self) -> &ListValueKind {
338 &self.kind
339 }
340}
341
342fn ensure_item_types(
343 expected: &ValueType,
344 actual: impl IntoIterator<Item = ValueType>,
345) -> Result<(), ListValueItemTypeMismatch> {
346 for (index, actual) in actual.into_iter().enumerate() {
347 if actual != *expected {
348 return Err(ListValueItemTypeMismatch {
349 index,
350 expected: expected.clone(),
351 actual,
352 });
353 }
354 }
355
356 Ok(())
357}
358
359#[cfg(test)]
360mod tests {
361 use super::{ListValue, ListValueItemTypeMismatch};
362 use crate::host::HostExternalStore;
363 use crate::plan::{
364 CustomType, CustomTypeName, ExternalType, ExternalTypeName, FunctionType, TypeParameterId,
365 ValueType,
366 };
367 use crate::runtime::{BitArrayValue, CustomValue, ExternalValue, FunctionValue, Value};
368
369 #[test]
370 fn list_value_operations_preserve_every_storage_family() {
371 let function = sample_function();
372 let function_type = function.type_();
373 let custom_type = sample_custom_type("Boxed");
374 let custom = sample_custom_value(custom_type.clone(), "Boxed");
375 let external_type = sample_external_type("Resource");
376 let first_external = sample_external_value(external_type.clone(), 1);
377 let second_external = sample_external_value(external_type.clone(), 2);
378 let cases = [
379 (
380 ListValue::int(vec![1.into(), 2.into()]),
381 ValueType::Int,
382 vec![Value::Int(1.into()), Value::Int(2.into())],
383 ),
384 (
385 ListValue::string(vec!["one".into(), "two".into()]),
386 ValueType::String,
387 vec![Value::String("one".into()), Value::String("two".into())],
388 ),
389 (
390 ListValue::bit_array(vec![
391 BitArrayValue::from_bytes(vec![1]),
392 BitArrayValue::from_bytes(vec![2]),
393 ]),
394 ValueType::BitArray,
395 vec![
396 Value::BitArray(BitArrayValue::from_bytes(vec![1])),
397 Value::BitArray(BitArrayValue::from_bytes(vec![2])),
398 ],
399 ),
400 (
401 ListValue::utf_codepoint(vec!['a', '\u{10ffff}']),
402 ValueType::UtfCodepoint,
403 vec![Value::UtfCodepoint('a'), Value::UtfCodepoint('\u{10ffff}')],
404 ),
405 (
406 ListValue::from_evaluated_custom(
407 custom_type.clone(),
408 vec![custom.clone(), custom.clone()],
409 ),
410 ValueType::Custom(custom_type.clone()),
411 vec![Value::Custom(custom.clone()), Value::Custom(custom.clone())],
412 ),
413 (
414 ListValue::from_evaluated_external(
415 external_type.clone(),
416 vec![first_external.clone(), second_external.clone()],
417 ),
418 ValueType::External(external_type.clone()),
419 vec![
420 Value::External(first_external),
421 Value::External(second_external),
422 ],
423 ),
424 (
425 ListValue::float(vec![1.5, 2.5]),
426 ValueType::Float,
427 vec![Value::Float(1.5), Value::Float(2.5)],
428 ),
429 (
430 ListValue::bool(vec![true, false]),
431 ValueType::Bool,
432 vec![Value::Bool(true), Value::Bool(false)],
433 ),
434 (
435 ListValue::nil(2),
436 ValueType::Nil,
437 vec![Value::Nil, Value::Nil],
438 ),
439 (
440 ListValue::from_evaluated_tuple(
441 vec![ValueType::Int],
442 vec![vec![Value::Int(1.into())], vec![Value::Int(2.into())]],
443 ),
444 ValueType::Tuple(vec![ValueType::Int]),
445 vec![
446 Value::Tuple(vec![Value::Int(1.into())]),
447 Value::Tuple(vec![Value::Int(2.into())]),
448 ],
449 ),
450 (
451 ListValue::from_evaluated_list(
452 ValueType::Int,
453 vec![
454 ListValue::int(vec![1.into()]),
455 ListValue::int(vec![2.into()]),
456 ],
457 ),
458 ValueType::List(Box::new(ValueType::Int)),
459 vec![
460 Value::List(ListValue::int(vec![1.into()])),
461 Value::List(ListValue::int(vec![2.into()])),
462 ],
463 ),
464 (
465 ListValue::from_evaluated_function(
466 function_type.clone(),
467 vec![function.clone(), function.clone()],
468 ),
469 ValueType::Function(Box::new(function_type.clone())),
470 vec![
471 Value::Function(function.clone()),
472 Value::Function(function.clone()),
473 ],
474 ),
475 ];
476
477 for (value, item_type, expected) in cases {
478 assert_eq!(value.item_type(), item_type);
479 assert_eq!(value.len(), 2);
480 assert!(!value.is_empty());
481 assert_eq!(value.to_values(), expected);
482 }
483
484 for item_type in [
485 ValueType::Parameter(TypeParameterId(0)),
486 ValueType::Int,
487 ValueType::String,
488 ValueType::BitArray,
489 ValueType::UtfCodepoint,
490 ValueType::Custom(custom_type),
491 ValueType::External(external_type),
492 ValueType::Float,
493 ValueType::Bool,
494 ValueType::Nil,
495 ValueType::Tuple(vec![ValueType::Int]),
496 ValueType::List(Box::new(ValueType::Int)),
497 ValueType::Function(Box::new(function_type)),
498 ] {
499 let value = ListValue::empty(item_type.clone());
500 assert_eq!(value.item_type(), item_type);
501 assert_eq!(value.len(), 0);
502 assert!(value.is_empty());
503 assert_eq!(value.to_values(), Vec::<Value>::new());
504 }
505 }
506
507 #[test]
508 fn checked_list_value_constructors_report_exact_item_mismatches() {
509 let expected_custom_type = sample_custom_type("Expected");
510 let actual_custom_type = sample_custom_type("Actual");
511 let expected_custom_value = sample_custom_value(expected_custom_type.clone(), "Expected");
512 let actual_custom_value = sample_custom_value(actual_custom_type.clone(), "Actual");
513 let expected_external_type = sample_external_type("ExpectedResource");
514 let actual_external_type = sample_external_type("ActualResource");
515 let expected_external_value = sample_external_value(expected_external_type.clone(), 1);
516 let actual_external_value = sample_external_value(actual_external_type.clone(), 2);
517
518 assert_eq!(
519 ListValue::try_custom(
520 expected_custom_type.clone(),
521 vec![expected_custom_value.clone(), actual_custom_value],
522 ),
523 Err(ListValueItemTypeMismatch {
524 index: 1,
525 expected: ValueType::Custom(expected_custom_type.clone()),
526 actual: ValueType::Custom(actual_custom_type),
527 }),
528 );
529 assert_eq!(
530 ListValue::try_tuple(
531 vec![ValueType::Int],
532 vec![
533 vec![Value::Int(1.into())],
534 vec![Value::String("wrong".into())],
535 ],
536 ),
537 Err(ListValueItemTypeMismatch {
538 index: 1,
539 expected: ValueType::Tuple(vec![ValueType::Int]),
540 actual: ValueType::Tuple(vec![ValueType::String]),
541 }),
542 );
543 assert_eq!(
544 ListValue::try_external(
545 expected_external_type.clone(),
546 vec![expected_external_value.clone(), actual_external_value],
547 ),
548 Err(ListValueItemTypeMismatch {
549 index: 1,
550 expected: ValueType::External(expected_external_type.clone()),
551 actual: ValueType::External(actual_external_type),
552 }),
553 );
554 assert_eq!(
555 ListValue::try_list(
556 ValueType::Int,
557 vec![ListValue::int(Vec::new()), ListValue::string(Vec::new())],
558 ),
559 Err(ListValueItemTypeMismatch {
560 index: 1,
561 expected: ValueType::List(Box::new(ValueType::Int)),
562 actual: ValueType::List(Box::new(ValueType::String)),
563 }),
564 );
565
566 let function = sample_function();
567 assert_eq!(
568 ListValue::try_function(
569 FunctionType::new(vec![ValueType::Int], ValueType::Int),
570 vec![function],
571 ),
572 Err(ListValueItemTypeMismatch {
573 index: 0,
574 expected: ValueType::Function(Box::new(FunctionType::new(
575 vec![ValueType::Int],
576 ValueType::Int,
577 ))),
578 actual: ValueType::Function(Box::new(FunctionType::new(
579 Vec::new(),
580 ValueType::Int,
581 ))),
582 }),
583 );
584
585 assert_eq!(
586 ListValue::try_custom(
587 expected_custom_type.clone(),
588 vec![expected_custom_value.clone()],
589 ),
590 Ok(ListValue::from_evaluated_custom(
591 expected_custom_type.clone(),
592 vec![expected_custom_value],
593 )),
594 );
595 assert_eq!(
596 ListValue::try_custom(expected_custom_type.clone(), Vec::new()),
597 Ok(ListValue::from_evaluated_custom(
598 expected_custom_type,
599 Vec::new(),
600 )),
601 );
602 assert_eq!(
603 ListValue::try_external(
604 expected_external_type.clone(),
605 vec![expected_external_value.clone()],
606 ),
607 Ok(ListValue::from_evaluated_external(
608 expected_external_type.clone(),
609 vec![expected_external_value],
610 )),
611 );
612 assert_eq!(
613 ListValue::try_external(expected_external_type.clone(), Vec::new()),
614 Ok(ListValue::from_evaluated_external(
615 expected_external_type,
616 Vec::new(),
617 )),
618 );
619 assert_eq!(
620 ListValue::try_tuple(vec![ValueType::Int], Vec::new()),
621 Ok(ListValue::from_evaluated_tuple(
622 vec![ValueType::Int],
623 Vec::new()
624 )),
625 );
626 assert_eq!(
627 ListValue::try_list(ValueType::Int, Vec::new()),
628 Ok(ListValue::from_evaluated_list(ValueType::Int, Vec::new())),
629 );
630 assert_eq!(
631 ListValue::try_function(FunctionType::new(Vec::new(), ValueType::Int), Vec::new()),
632 Ok(ListValue::from_evaluated_function(
633 FunctionType::new(Vec::new(), ValueType::Int),
634 Vec::new(),
635 )),
636 );
637 }
638
639 #[test]
640 fn checked_function_list_value_constructor_accepts_matching_non_empty_values() {
641 let function = sample_function();
642 let function_type = function.type_();
643
644 assert_eq!(
645 ListValue::try_function(function_type.clone(), vec![function.clone()]),
646 Ok(ListValue::from_evaluated_function(
647 function_type,
648 vec![function],
649 )),
650 );
651 }
652
653 fn sample_function() -> FunctionValue {
654 FunctionValue::new(
655 crate::plan::execution::function::RuntimeFunctionId::Core(
656 crate::plan::execution::function::CoreRuntimeFunctionId::Int(
657 crate::plan::execution::function::IntFunctionId(0),
658 ),
659 ),
660 Vec::new(),
661 FunctionType::new(Vec::new(), ValueType::Int),
662 )
663 }
664
665 fn sample_custom_type(name: &str) -> CustomType {
666 CustomType::new(
667 CustomTypeName::new("geam".into(), "main".into(), name.into()),
668 Vec::new(),
669 )
670 }
671
672 fn sample_custom_value(type_: CustomType, constructor_name: &str) -> CustomValue {
673 CustomValue::from_evaluated(type_, constructor_name.into(), 0, Vec::new())
674 }
675
676 fn sample_external_type(name: &str) -> ExternalType {
677 ExternalType::new(
678 ExternalTypeName::new("geam".into(), "main".into(), name.into()),
679 Vec::new(),
680 )
681 }
682
683 fn sample_external_value(type_: ExternalType, payload: usize) -> ExternalValue {
684 fn source_hash(
685 context: &crate::host::HostExternalHashing<'_>,
686 value: &crate::host::HostStoredValue<num_bigint::BigInt>,
687 ) -> u64 {
688 context.stored_value_hash(value)
689 }
690
691 fn inspect(
692 context: &crate::host::HostExternalInspection<'_>,
693 value: &crate::host::HostStoredValue<num_bigint::BigInt>,
694 ) -> ecow::EcoString {
695 context.inspect_stored_value(value)
696 }
697
698 let store = HostExternalStore::default();
699 let source_equal =
700 |context: &crate::host::HostExternalEquality<'_>,
701 left: &crate::host::HostStoredValue<num_bigint::BigInt>,
702 right: &crate::host::HostStoredValue<num_bigint::BigInt>| {
703 context.stored_values_equal(left, right)
704 };
705 let first = store.insert(
706 crate::host::HostStoredValue::new(crate::runtime::StoredRuntimeValue::test_int(
707 payload.into(),
708 )),
709 source_equal,
710 source_hash,
711 inspect,
712 );
713 let equal = store.insert(
714 crate::host::HostStoredValue::new(crate::runtime::StoredRuntimeValue::test_int(
715 payload.into(),
716 )),
717 source_equal,
718 source_hash,
719 inspect,
720 );
721 let stored_equal =
722 |left: &crate::runtime::StoredRuntimeValue,
723 right: &crate::runtime::StoredRuntimeValue| left.value() == right.value();
724 let equality = crate::host::HostExternalEquality::new(&stored_equal);
725 assert!(first.source_equal(&equality, &equal));
726 let stored_hash = |_: &crate::runtime::StoredRuntimeValue| payload as u64;
727 let stored_inspect =
728 |_: &crate::runtime::StoredRuntimeValue| format!("Resource({payload})").into();
729 assert_eq!(
730 first.source_hash(&crate::host::HostExternalHashing::new(&stored_hash)),
731 payload as u64,
732 );
733 let expected_inspection = format!("Resource({payload})");
734 assert_eq!(
735 first
736 .inspection(&crate::host::HostExternalInspection::new(&stored_inspect))
737 .as_str(),
738 expected_inspection,
739 );
740 ExternalValue::from_evaluated(type_, first, format!("Resource({payload})").into())
741 }
742}