1use crate::{
2 CoreExtern, CoreFuncType, DefinedType, DefinedTypeId, Enum, Flags, FuncTypeId, InterfaceId,
3 ItemKind, ModuleTypeId, PrimitiveType, Record, ResourceId, Type, Types, ValueType, Variant,
4 WorldId,
5};
6use anyhow::{bail, Context, Result};
7use indexmap::IndexMap;
8use std::collections::HashSet;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12pub enum SubtypeCheck {
13 Covariant,
15 Contravariant,
17}
18
19pub struct SubtypeChecker<'a> {
23 kinds: Vec<SubtypeCheck>,
24 cache: &'a mut HashSet<(ItemKind, ItemKind)>,
25}
26
27impl<'a> SubtypeChecker<'a> {
28 pub fn new(cache: &'a mut HashSet<(ItemKind, ItemKind)>) -> Self {
30 Self {
31 kinds: Default::default(),
32 cache,
33 }
34 }
35
36 fn kind(&self) -> SubtypeCheck {
37 self.kinds
38 .last()
39 .copied()
40 .unwrap_or(SubtypeCheck::Covariant)
41 }
42
43 pub fn is_subtype(&mut self, a: ItemKind, at: &Types, b: ItemKind, bt: &Types) -> Result<()> {
45 if self.cache.contains(&(a, b)) {
46 return Ok(());
47 }
48
49 let result = self.is_subtype_(a, at, b, bt);
50 if result.is_ok() {
51 self.cache.insert((a, b));
52 }
53
54 result
55 }
56
57 pub fn invert(&mut self) -> SubtypeCheck {
61 let prev = self.kind();
62 self.kinds.push(match prev {
63 SubtypeCheck::Covariant => SubtypeCheck::Contravariant,
64 SubtypeCheck::Contravariant => SubtypeCheck::Covariant,
65 });
66 prev
67 }
68
69 pub fn revert(&mut self) {
71 self.kinds.pop().expect("mismatched stack");
72 }
73
74 fn is_subtype_(&mut self, a: ItemKind, at: &Types, b: ItemKind, bt: &Types) -> Result<()> {
75 match (a, b) {
76 (ItemKind::Type(a), ItemKind::Type(b)) => self.ty(a, at, b, bt),
77 (ItemKind::Func(a), ItemKind::Func(b)) => self.func(a, at, b, bt),
78 (ItemKind::Instance(a), ItemKind::Instance(b)) => self.interface(a, at, b, bt),
79 (ItemKind::Component(a), ItemKind::Component(b)) => self.world(a, at, b, bt),
80 (ItemKind::Module(a), ItemKind::Module(b)) => self.module(a, at, b, bt),
81 (ItemKind::Value(a), ItemKind::Value(b)) => self.value_type(a, at, b, bt),
82
83 (ItemKind::Type(_), _)
84 | (ItemKind::Func(_), _)
85 | (ItemKind::Instance(_), _)
86 | (ItemKind::Component(_), _)
87 | (ItemKind::Module(_), _)
88 | (ItemKind::Value(_), _) => {
89 let (expected, expected_types, found, found_types) =
90 self.expected_found(&a, at, &b, bt);
91 bail!(
92 "expected {expected}, found {found}",
93 expected = expected.desc(expected_types),
94 found = found.desc(found_types)
95 )
96 }
97 }
98 }
99
100 fn expected_found<'b, T>(
101 &self,
102 a: &'b T,
103 at: &'b Types,
104 b: &'b T,
105 bt: &'b Types,
106 ) -> (&'b T, &'b Types, &'b T, &'b Types) {
107 match self.kind() {
108 SubtypeCheck::Covariant => (b, bt, a, at),
110 SubtypeCheck::Contravariant => (a, at, b, bt),
112 }
113 }
114
115 fn resource(&self, a: ResourceId, at: &Types, b: ResourceId, bt: &Types) -> Result<()> {
116 if a == b {
117 return Ok(());
118 }
119
120 let a = &at[at.resolve_resource(a)];
121 let b = &bt[bt.resolve_resource(b)];
122 if a.name != b.name {
123 let (expected, _, found, _) = self.expected_found(a, at, b, bt);
124
125 bail!(
126 "expected resource `{expected}`, found resource `{found}`",
127 expected = expected.name,
128 found = found.name
129 );
130 }
131
132 Ok(())
133 }
134
135 fn ty(&mut self, a: Type, at: &Types, b: Type, bt: &Types) -> Result<()> {
136 match (a, b) {
137 (Type::Resource(a), Type::Resource(b)) => self.resource(a, at, b, bt),
138 (Type::Func(a), Type::Func(b)) => self.func(a, at, b, bt),
139 (Type::Value(a), Type::Value(b)) => self.value_type(a, at, b, bt),
140 (Type::Interface(a), Type::Interface(b)) => self.interface(a, at, b, bt),
141 (Type::World(a), Type::World(b)) => self.world(a, at, b, bt),
142 (Type::Module(a), Type::Module(b)) => self.module(a, at, b, bt),
143
144 (Type::Func(_), _)
145 | (Type::Resource(_), _)
146 | (Type::Value(_), _)
147 | (Type::Interface(_), _)
148 | (Type::World(_), _)
149 | (Type::Module(_), _) => {
150 let (expected, expected_types, found, found_types) =
151 self.expected_found(&a, at, &b, bt);
152
153 bail!(
154 "expected {expected}, found {found}",
155 expected = expected.desc(expected_types),
156 found = found.desc(found_types)
157 )
158 }
159 }
160 }
161
162 fn func(&self, a: FuncTypeId, at: &Types, b: FuncTypeId, bt: &Types) -> Result<()> {
163 if a == b {
164 return Ok(());
165 }
166
167 let a = &at[a];
168 let b = &bt[b];
169
170 if a.params.len() != b.params.len() {
175 let (expected, _, found, _) = self.expected_found(a, at, b, bt);
176 bail!(
177 "expected function with parameter count {expected}, found parameter count {found}",
178 expected = expected.params.len(),
179 found = found.params.len(),
180 );
181 }
182
183 for (i, ((an, a), (bn, b))) in a.params.iter().zip(b.params.iter()).enumerate() {
184 if an != bn {
185 let (expected, _, found, _) = self.expected_found(an, at, bn, bt);
186 bail!("expected function parameter {i} to be named `{expected}`, found name `{found}`");
187 }
188
189 self.value_type(*a, at, *b, bt)
190 .with_context(|| format!("mismatched type for function parameter `{bn}`"))?;
191 }
192
193 match (&a.result, &b.result) {
194 (None, None) => return Ok(()),
195 (Some(a), Some(b)) => {
196 return self
197 .value_type(*a, at, *b, bt)
198 .context("mismatched type for function result");
199 }
200 (None, _) | (Some(_), _) => {
201 }
203 }
204
205 let (expected, _, found, _) = self.expected_found(a, at, b, bt);
206 match (&expected.result, &found.result) {
207 (Some(_), None) => {
208 bail!("expected function with a result, found function without a result")
209 }
210 (None, Some(_)) => {
211 bail!("expected function without a result, found function with a result")
212 }
213 (Some(_), Some(_)) | (None, None) => panic!("should already be handled"),
214 }
215 }
216
217 fn instance_exports(
218 &mut self,
219 a: &IndexMap<String, ItemKind>,
220 at: &Types,
221 b: &IndexMap<String, ItemKind>,
222 bt: &Types,
223 ) -> Result<()> {
224 for (k, b) in b.iter() {
229 match a.get(k) {
230 Some(a) => {
231 self.is_subtype(*a, at, *b, bt)
232 .with_context(|| format!("mismatched type for export `{k}`"))?;
233 }
234 None => match self.kind() {
235 SubtypeCheck::Covariant => {
236 bail!(
237 "instance is missing expected {kind} export `{k}`",
238 kind = b.desc(bt)
239 )
240 }
241 SubtypeCheck::Contravariant => {
242 bail!(
243 "instance has unexpected {kind} export `{k}`",
244 kind = b.desc(bt)
245 )
246 }
247 },
248 }
249 }
250
251 Ok(())
252 }
253
254 fn interface(&mut self, a: InterfaceId, at: &Types, b: InterfaceId, bt: &Types) -> Result<()> {
255 if a == b {
256 return Ok(());
257 }
258
259 let a = &at[a];
260 let b = &bt[b];
261 self.instance_exports(&a.exports, at, &b.exports, bt)
262 }
263
264 fn world(&mut self, a: WorldId, at: &Types, b: WorldId, bt: &Types) -> Result<()> {
265 let a = &at[a];
266 let b = &bt[b];
267
268 let prev = self.invert();
274 for (k, a) in a.imports.iter() {
275 match b.imports.get(k) {
276 Some(b) => {
277 self.is_subtype(*b, bt, *a, at)
278 .with_context(|| format!("mismatched type for import `{k}`"))?;
279 }
280 None => match prev {
281 SubtypeCheck::Covariant => {
282 bail!(
283 "component is missing expected {kind} import `{k}`",
284 kind = a.desc(at)
285 )
286 }
287 SubtypeCheck::Contravariant => {
288 bail!(
289 "component has unexpected import {kind} `{k}`",
290 kind = a.desc(at)
291 )
292 }
293 },
294 }
295 }
296
297 self.revert();
298
299 for (k, b) in b.exports.iter() {
300 match a.exports.get(k) {
301 Some(a) => {
302 self.is_subtype(*a, at, *b, bt)
303 .with_context(|| format!("mismatched type for export `{k}`"))?;
304 }
305 None => match self.kind() {
306 SubtypeCheck::Covariant => {
307 bail!(
308 "component is missing expected {kind} export `{k}`",
309 kind = b.desc(bt)
310 )
311 }
312 SubtypeCheck::Contravariant => {
313 bail!(
314 "component has unexpected {kind} export `{k}`",
315 kind = b.desc(bt)
316 )
317 }
318 },
319 }
320 }
321
322 Ok(())
323 }
324
325 fn module(&mut self, a: ModuleTypeId, at: &Types, b: ModuleTypeId, bt: &Types) -> Result<()> {
326 if a == b {
327 return Ok(());
328 }
329
330 let a = &at[a];
331 let b = &bt[b];
332
333 let prev = self.invert();
339 for (k, a) in a.imports.iter() {
340 match b.imports.get(k) {
341 Some(b) => {
342 self.core_extern(b, bt, a, at).with_context(|| {
343 format!("mismatched type for import `{m}::{n}`", m = k.0, n = k.1)
344 })?;
345 }
346 None => match prev {
347 SubtypeCheck::Covariant => bail!(
348 "module is missing expected {a} import `{m}::{n}`",
349 m = k.0,
350 n = k.1
351 ),
352 SubtypeCheck::Contravariant => {
353 bail!(
354 "module has unexpected {a} import `{m}::{n}`",
355 m = k.0,
356 n = k.1
357 )
358 }
359 },
360 }
361 }
362
363 self.revert();
364
365 for (k, b) in b.exports.iter() {
366 match a.exports.get(k) {
367 Some(a) => {
368 self.kinds.push(SubtypeCheck::Covariant);
369 let r = self
370 .core_extern(a, at, b, bt)
371 .with_context(|| format!("mismatched type for export `{k}`"));
372 self.kinds.pop();
373 r?;
374 }
375 None => match self.kind() {
376 SubtypeCheck::Covariant => {
377 bail!("module is missing expected {b} export `{k}`")
378 }
379 SubtypeCheck::Contravariant => {
380 bail!("module has unexpected {b} export `{k}`")
381 }
382 },
383 }
384 }
385
386 Ok(())
387 }
388
389 pub(crate) fn core_extern(
390 &self,
391 a: &CoreExtern,
392 at: &Types,
393 b: &CoreExtern,
394 bt: &Types,
395 ) -> Result<()> {
396 macro_rules! limits_match {
397 ($ai:expr, $am:expr, $bi:expr, $bm:expr) => {{
398 $ai >= $bi
399 && match ($am, $bm) {
400 (Some(am), Some(bm)) => am <= bm,
401 (None, Some(_)) => false,
402 _ => true,
403 }
404 }};
405 }
406
407 match (a, b) {
408 (CoreExtern::Func(a), CoreExtern::Func(b)) => self.core_func(a, at, b, bt),
409 (
410 CoreExtern::Table {
411 element_type: ae,
412 initial: ai,
413 maximum: am,
414 table64: _a64,
415 shared: _ashared,
416 },
417 CoreExtern::Table {
418 element_type: be,
419 initial: bi,
420 maximum: bm,
421 table64: _b64,
422 shared: _bshared,
423 },
424 ) => {
425 if ae != be {
426 let (expected, _, found, _) = self.expected_found(ae, at, be, bt);
427 bail!("expected table element type {expected}, found {found}");
428 }
429
430 if !limits_match!(ai, am, bi, bm) {
431 bail!("mismatched table limits");
432 }
433
434 Ok(())
435 }
436 (
437 CoreExtern::Memory {
438 memory64: a64,
439 shared: ashared,
440 initial: ai,
441 maximum: am,
442 page_size_log2: _apsl,
443 },
444 CoreExtern::Memory {
445 memory64: b64,
446 shared: bshared,
447 initial: bi,
448 maximum: bm,
449 page_size_log2: _bpsl,
450 },
451 ) => {
452 if ashared != bshared {
453 bail!("mismatched shared flag for memories");
454 }
455
456 if a64 != b64 {
457 bail!("mismatched memory64 flag for memories");
458 }
459
460 if !limits_match!(ai, am, bi, bm) {
461 bail!("mismatched memory limits");
462 }
463
464 Ok(())
465 }
466 (
467 CoreExtern::Global {
468 val_type: avt,
469 mutable: am,
470 shared: _ashared,
471 },
472 CoreExtern::Global {
473 val_type: bvt,
474 mutable: bm,
475 shared: _bshared,
476 },
477 ) => {
478 if am != bm {
479 bail!("mismatched mutable flag for globals");
480 }
481
482 if avt != bvt {
483 let (expected, _, found, _) = self.expected_found(avt, at, bvt, bt);
484 bail!("expected global type {expected}, found {found}");
485 }
486
487 Ok(())
488 }
489 (CoreExtern::Tag(a), CoreExtern::Tag(b)) => self.core_func(a, at, b, bt),
490
491 (CoreExtern::Func(_), _)
492 | (CoreExtern::Table { .. }, _)
493 | (CoreExtern::Memory { .. }, _)
494 | (CoreExtern::Global { .. }, _)
495 | (CoreExtern::Tag(_), _) => {
496 let (expected, _, found, _) = self.expected_found(a, at, b, bt);
497 bail!("expected {expected}, found {found}");
498 }
499 }
500 }
501
502 fn core_func(&self, a: &CoreFuncType, at: &Types, b: &CoreFuncType, bt: &Types) -> Result<()> {
503 if a != b {
504 let (expected, _, found, _) = self.expected_found(a, at, b, bt);
505 bail!("expected {expected}, found {found}");
506 }
507
508 Ok(())
509 }
510
511 fn value_type(&self, a: ValueType, at: &Types, b: ValueType, bt: &Types) -> Result<()> {
512 let a = at.resolve_value_type(a);
513 let b = bt.resolve_value_type(b);
514
515 match (a, b) {
516 (ValueType::Primitive(a), ValueType::Primitive(b)) => self.primitive(a, at, b, bt),
517 (ValueType::Defined(a), ValueType::Defined(b)) => self.defined_type(a, at, b, bt),
518 (ValueType::Borrow(a), ValueType::Borrow(b))
519 | (ValueType::Own(a), ValueType::Own(b)) => self.resource(a, at, b, bt),
520
521 (ValueType::Primitive(_), _)
522 | (ValueType::Defined(_), _)
523 | (ValueType::Borrow(_), _)
524 | (ValueType::Own(_), _) => {
525 let (expected, expected_types, found, found_types) =
526 self.expected_found(&a, at, &b, bt);
527 bail!(
528 "expected {expected}, found {found}",
529 expected = expected.desc(expected_types),
530 found = found.desc(found_types)
531 )
532 }
533 }
534 }
535
536 fn defined_type(
537 &self,
538 a: DefinedTypeId,
539 at: &Types,
540 b: DefinedTypeId,
541 bt: &Types,
542 ) -> std::result::Result<(), anyhow::Error> {
543 if a == b {
544 return Ok(());
545 }
546
547 let a = &at[a];
548 let b = &bt[b];
549 match (a, b) {
550 (DefinedType::Tuple(a), DefinedType::Tuple(b)) => self.tuple(a, at, b, bt),
551 (DefinedType::List(a), DefinedType::List(b)) => self
552 .value_type(*a, at, *b, bt)
553 .context("mismatched type for list element"),
554 (DefinedType::Future(a), DefinedType::Future(b)) => self
555 .payload(*a, at, *b, bt)
556 .context("mismatched type for future payload"),
557 (DefinedType::Stream(a), DefinedType::Stream(b)) => self
558 .payload(*a, at, *b, bt)
559 .context("mismatched type for stream payload"),
560 (DefinedType::Option(a), DefinedType::Option(b)) => self
561 .value_type(*a, at, *b, bt)
562 .context("mismatched type for option"),
563 (
564 DefinedType::Result {
565 ok: a_ok,
566 err: a_err,
567 },
568 DefinedType::Result {
569 ok: b_ok,
570 err: b_err,
571 },
572 ) => {
573 self.result("ok", a_ok, at, b_ok, bt)?;
574 self.result("err", a_err, at, b_err, bt)
575 }
576 (DefinedType::Variant(a), DefinedType::Variant(b)) => self.variant(a, at, b, bt),
577 (DefinedType::Record(a), DefinedType::Record(b)) => self.record(a, at, b, bt),
578 (DefinedType::Flags(a), DefinedType::Flags(b)) => self.flags(a, at, b, bt),
579 (DefinedType::Enum(a), DefinedType::Enum(b)) => self.enum_type(a, at, b, bt),
580 (DefinedType::Alias(_), _) | (_, DefinedType::Alias(_)) => {
581 panic!("aliases should have been resolved")
582 }
583
584 (DefinedType::Tuple(_), _)
585 | (DefinedType::List(_), _)
586 | (DefinedType::Option(_), _)
587 | (DefinedType::Result { .. }, _)
588 | (DefinedType::Variant(_), _)
589 | (DefinedType::Record(_), _)
590 | (DefinedType::Flags(_), _)
591 | (DefinedType::Enum(_), _)
592 | (DefinedType::Stream(_), _)
593 | (DefinedType::Future(_), _) => {
594 let (expected, expected_types, found, found_types) =
595 self.expected_found(a, at, b, bt);
596 bail!(
597 "expected {expected}, found {found}",
598 expected = expected.desc(expected_types),
599 found = found.desc(found_types)
600 )
601 }
602 }
603 }
604
605 fn result(
606 &self,
607 desc: &str,
608 a: &Option<ValueType>,
609 at: &Types,
610 b: &Option<ValueType>,
611 bt: &Types,
612 ) -> Result<()> {
613 match (a, b) {
614 (None, None) => return Ok(()),
615 (Some(a), Some(b)) => {
616 return self
617 .value_type(*a, at, *b, bt)
618 .with_context(|| format!("mismatched type for result `{desc}`"))
619 }
620 (Some(_), None) | (None, Some(_)) => {
621 }
623 }
624
625 let (expected, _, found, _) = self.expected_found(a, at, b, bt);
626 match (expected, found) {
627 (Some(_), None) => bail!("expected an `{desc}` for result type"),
628 (None, Some(_)) => bail!("expected no `{desc}` for result type"),
629 (None, None) | (Some(_), Some(_)) => panic!("expected to be handled"),
630 }
631 }
632
633 fn enum_type(&self, a: &Enum, at: &Types, b: &Enum, bt: &Types) -> Result<()> {
634 if a.0.len() != b.0.len() {
635 let (expected, _, found, _) = self.expected_found(a, at, b, bt);
636 bail!(
637 "expected an enum type case count of {expected}, found a count of {found}",
638 expected = expected.0.len(),
639 found = found.0.len()
640 );
641 }
642
643 if let Some((index, (a, b))) =
644 a.0.iter()
645 .zip(b.0.iter())
646 .enumerate()
647 .find(|(_, (a, b))| a != b)
648 {
649 let (expected, _, found, _) = self.expected_found(a, at, b, bt);
650 bail!("expected enum case {index} to be named `{expected}`, found an enum case named `{found}`");
651 }
652
653 Ok(())
654 }
655
656 fn flags(&self, a: &Flags, at: &Types, b: &Flags, bt: &Types) -> Result<()> {
657 if a.0.len() != b.0.len() {
658 let (expected, _, found, _) = self.expected_found(a, at, b, bt);
659 bail!(
660 "expected a flags type flag count of {expected}, found a count of {found}",
661 expected = expected.0.len(),
662 found = found.0.len()
663 );
664 }
665
666 if let Some((index, (a, b))) =
667 a.0.iter()
668 .zip(b.0.iter())
669 .enumerate()
670 .find(|(_, (a, b))| a != b)
671 {
672 let (expected, _, found, _) = self.expected_found(a, at, b, bt);
673 bail!("expected flag {index} to be named `{expected}`, found a flag named `{found}`");
674 }
675
676 Ok(())
677 }
678
679 fn record(&self, a: &Record, at: &Types, b: &Record, bt: &Types) -> Result<()> {
680 if a.fields.len() != b.fields.len() {
681 let (expected, _, found, _) = self.expected_found(a, at, b, bt);
682 bail!(
683 "expected a record field count of {expected}, found a count of {found}",
684 expected = expected.fields.len(),
685 found = found.fields.len()
686 );
687 }
688
689 for (i, ((an, a), (bn, b))) in a.fields.iter().zip(b.fields.iter()).enumerate() {
690 if an != bn {
691 let (expected, _, found, _) = self.expected_found(an, at, bn, bt);
692 bail!("expected record field {i} to be named `{expected}`, found a field named `{found}`");
693 }
694
695 self.value_type(*a, at, *b, bt)
696 .with_context(|| format!("mismatched type for record field `{bn}`"))?;
697 }
698
699 Ok(())
700 }
701
702 fn variant(&self, a: &Variant, at: &Types, b: &Variant, bt: &Types) -> Result<()> {
703 if a.cases.len() != b.cases.len() {
704 let (expected, _, found, _) = self.expected_found(a, at, b, bt);
705 bail!(
706 "expected a variant case count of {expected}, found a count of {found}",
707 expected = expected.cases.len(),
708 found = found.cases.len()
709 );
710 }
711
712 for (i, ((an, a), (bn, b))) in a.cases.iter().zip(b.cases.iter()).enumerate() {
713 if an != bn {
714 let (expected, _, found, _) = self.expected_found(an, at, bn, bt);
715 bail!("expected variant case {i} to be named `{expected}`, found a case named `{found}`");
716 }
717
718 match (a, b) {
719 (None, None) => {}
720 (Some(a), Some(b)) => self
721 .value_type(*a, at, *b, bt)
722 .with_context(|| format!("mismatched type for variant case `{bn}`"))?,
723 _ => {
724 let (expected, _, found, _) = self.expected_found(a, at, b, bt);
725 match (expected, found) {
726 (None, Some(_)) => {
727 bail!("expected variant case `{bn}` to be untyped, found a typed case")
728 }
729 (Some(_), None) => {
730 bail!("expected variant case `{bn}` to be typed, found an untyped case")
731 }
732 (None, None) | (Some(_), Some(_)) => panic!("expected to be handled"),
733 }
734 }
735 }
736 }
737
738 Ok(())
739 }
740
741 fn tuple(&self, a: &Vec<ValueType>, at: &Types, b: &Vec<ValueType>, bt: &Types) -> Result<()> {
742 if a.len() != b.len() {
743 let (expected, _, found, _) = self.expected_found(a, at, b, bt);
744 bail!(
745 "expected a tuple of size {expected}, found a tuple of size {found}",
746 expected = expected.len(),
747 found = found.len()
748 );
749 }
750
751 for (i, (a, b)) in a.iter().zip(b.iter()).enumerate() {
752 self.value_type(*a, at, *b, bt)
753 .with_context(|| format!("mismatched type for tuple item {i}"))?;
754 }
755
756 Ok(())
757 }
758
759 fn payload(
760 &self,
761 a: Option<ValueType>,
762 at: &Types,
763 b: Option<ValueType>,
764 bt: &Types,
765 ) -> Result<()> {
766 match (a, b) {
767 (Some(a), Some(b)) => self.value_type(a, at, b, bt),
768 (None, None) => Ok(()),
769 (Some(_), None) => bail!("expected a type payload, found none"),
770 (None, Some(_)) => bail!("expected no type payload, found one"),
771 }
772 }
773
774 fn primitive(&self, a: PrimitiveType, at: &Types, b: PrimitiveType, bt: &Types) -> Result<()> {
775 if a != b {
779 let (expected, _, found, _) = self.expected_found(&a, at, &b, bt);
780 bail!(
781 "expected {expected}, found {found}",
782 expected = expected.desc(),
783 found = found.desc()
784 );
785 }
786
787 Ok(())
788 }
789}