Skip to main content

wac_types/
checker.rs

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/// Represents the kind of subtyping check to perform.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12pub enum SubtypeCheck {
13    /// The type is a covariant check.
14    Covariant,
15    /// The type is a contravariant check.
16    Contravariant,
17}
18
19/// Implements a subtype checker.
20///
21/// Subtype checking is used to type check instantiation arguments.
22pub struct SubtypeChecker<'a> {
23    kinds: Vec<SubtypeCheck>,
24    cache: &'a mut HashSet<(ItemKind, ItemKind)>,
25}
26
27impl<'a> SubtypeChecker<'a> {
28    /// Creates a new subtype checker with the given cache.
29    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    /// Checks if `a` is a subtype of `b`.
44    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    /// Inverts the current subtype check being performed.
58    ///
59    /// Returns the previous subtype check.
60    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    /// Reverts to the previous check kind.
70    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            // For covariant checks, the supertype is the expected type
109            SubtypeCheck::Covariant => (b, bt, a, at),
110            // For contravariant checks, the subtype is the expected type
111            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        // Note: currently subtyping for functions is done in terms of equality
171        // rather than actual subtyping; the reason for this is that implementing
172        // runtimes don't yet support more complex subtyping rules.
173
174        if a.is_async != b.is_async {
175            let (expected, _, found, _) = self.expected_found(a, at, b, bt);
176            bail!(
177                "expected {} function, found {} function",
178                if expected.is_async { "async" } else { "sync" },
179                if found.is_async { "async" } else { "sync" },
180            );
181        }
182
183        if a.params.len() != b.params.len() {
184            let (expected, _, found, _) = self.expected_found(a, at, b, bt);
185            bail!(
186                "expected function with parameter count {expected}, found parameter count {found}",
187                expected = expected.params.len(),
188                found = found.params.len(),
189            );
190        }
191
192        for (i, ((an, a), (bn, b))) in a.params.iter().zip(b.params.iter()).enumerate() {
193            if an != bn {
194                let (expected, _, found, _) = self.expected_found(an, at, bn, bt);
195                bail!("expected function parameter {i} to be named `{expected}`, found name `{found}`");
196            }
197
198            self.value_type(*a, at, *b, bt)
199                .with_context(|| format!("mismatched type for function parameter `{bn}`"))?;
200        }
201
202        match (&a.result, &b.result) {
203            (None, None) => return Ok(()),
204            (Some(a), Some(b)) => {
205                return self
206                    .value_type(*a, at, *b, bt)
207                    .context("mismatched type for function result");
208            }
209            (None, _) | (Some(_), _) => {
210                // Handle the mismatch below
211            }
212        }
213
214        let (expected, _, found, _) = self.expected_found(a, at, b, bt);
215        match (&expected.result, &found.result) {
216            (Some(_), None) => {
217                bail!("expected function with a result, found function without a result")
218            }
219            (None, Some(_)) => {
220                bail!("expected function without a result, found function with a result")
221            }
222            (Some(_), Some(_)) | (None, None) => panic!("should already be handled"),
223        }
224    }
225
226    fn instance_exports(
227        &mut self,
228        a: &IndexMap<String, ItemKind>,
229        at: &Types,
230        b: &IndexMap<String, ItemKind>,
231        bt: &Types,
232    ) -> Result<()> {
233        // For instance type subtyping, all exports in the other
234        // instance type must be present in this instance type's
235        // exports (i.e. it can export *more* than what this instance
236        // type needs).
237        for (k, b) in b.iter() {
238            match a.get(k) {
239                Some(a) => {
240                    self.is_subtype(*a, at, *b, bt)
241                        .with_context(|| format!("mismatched type for export `{k}`"))?;
242                }
243                None => match self.kind() {
244                    SubtypeCheck::Covariant => {
245                        bail!(
246                            "instance is missing expected {kind} export `{k}`",
247                            kind = b.desc(bt)
248                        )
249                    }
250                    SubtypeCheck::Contravariant => {
251                        bail!(
252                            "instance has unexpected {kind} export `{k}`",
253                            kind = b.desc(bt)
254                        )
255                    }
256                },
257            }
258        }
259
260        Ok(())
261    }
262
263    fn interface(&mut self, a: InterfaceId, at: &Types, b: InterfaceId, bt: &Types) -> Result<()> {
264        if a == b {
265            return Ok(());
266        }
267
268        let a = &at[a];
269        let b = &bt[b];
270        self.instance_exports(&a.exports, at, &b.exports, bt)
271    }
272
273    fn world(&mut self, a: WorldId, at: &Types, b: WorldId, bt: &Types) -> Result<()> {
274        let a = &at[a];
275        let b = &bt[b];
276
277        // For component type subtyping, all exports in the other component
278        // type must be present in this component type's exports (i.e. it
279        // can export *more* than what this component type needs).
280        // However, for imports, the check is reversed (i.e. it is okay
281        // to import *less* than what this component type needs).
282        let prev = self.invert();
283        for (k, a) in a.imports.iter() {
284            match b.imports.get(k) {
285                Some(b) => {
286                    self.is_subtype(*b, bt, *a, at)
287                        .with_context(|| format!("mismatched type for import `{k}`"))?;
288                }
289                None => match prev {
290                    SubtypeCheck::Covariant => {
291                        bail!(
292                            "component is missing expected {kind} import `{k}`",
293                            kind = a.desc(at)
294                        )
295                    }
296                    SubtypeCheck::Contravariant => {
297                        bail!(
298                            "component has unexpected import {kind} `{k}`",
299                            kind = a.desc(at)
300                        )
301                    }
302                },
303            }
304        }
305
306        self.revert();
307
308        for (k, b) in b.exports.iter() {
309            match a.exports.get(k) {
310                Some(a) => {
311                    self.is_subtype(*a, at, *b, bt)
312                        .with_context(|| format!("mismatched type for export `{k}`"))?;
313                }
314                None => match self.kind() {
315                    SubtypeCheck::Covariant => {
316                        bail!(
317                            "component is missing expected {kind} export `{k}`",
318                            kind = b.desc(bt)
319                        )
320                    }
321                    SubtypeCheck::Contravariant => {
322                        bail!(
323                            "component has unexpected {kind} export `{k}`",
324                            kind = b.desc(bt)
325                        )
326                    }
327                },
328            }
329        }
330
331        Ok(())
332    }
333
334    fn module(&mut self, a: ModuleTypeId, at: &Types, b: ModuleTypeId, bt: &Types) -> Result<()> {
335        if a == b {
336            return Ok(());
337        }
338
339        let a = &at[a];
340        let b = &bt[b];
341
342        // For module type subtyping, all exports in the other module
343        // type must be present in expected module type's exports (i.e. it
344        // can export *more* than what is expected module type needs).
345        // However, for imports, the check is reversed (i.e. it is okay
346        // to import *less* than what this module type needs).
347        let prev = self.invert();
348        for (k, a) in a.imports.iter() {
349            match b.imports.get(k) {
350                Some(b) => {
351                    self.core_extern(b, bt, a, at).with_context(|| {
352                        format!("mismatched type for import `{m}::{n}`", m = k.0, n = k.1)
353                    })?;
354                }
355                None => match prev {
356                    SubtypeCheck::Covariant => bail!(
357                        "module is missing expected {a} import `{m}::{n}`",
358                        m = k.0,
359                        n = k.1
360                    ),
361                    SubtypeCheck::Contravariant => {
362                        bail!(
363                            "module has unexpected {a} import `{m}::{n}`",
364                            m = k.0,
365                            n = k.1
366                        )
367                    }
368                },
369            }
370        }
371
372        self.revert();
373
374        for (k, b) in b.exports.iter() {
375            match a.exports.get(k) {
376                Some(a) => {
377                    self.kinds.push(SubtypeCheck::Covariant);
378                    let r = self
379                        .core_extern(a, at, b, bt)
380                        .with_context(|| format!("mismatched type for export `{k}`"));
381                    self.kinds.pop();
382                    r?;
383                }
384                None => match self.kind() {
385                    SubtypeCheck::Covariant => {
386                        bail!("module is missing expected {b} export `{k}`")
387                    }
388                    SubtypeCheck::Contravariant => {
389                        bail!("module has unexpected {b} export `{k}`")
390                    }
391                },
392            }
393        }
394
395        Ok(())
396    }
397
398    pub(crate) fn core_extern(
399        &self,
400        a: &CoreExtern,
401        at: &Types,
402        b: &CoreExtern,
403        bt: &Types,
404    ) -> Result<()> {
405        macro_rules! limits_match {
406            ($ai:expr, $am:expr, $bi:expr, $bm:expr) => {{
407                $ai >= $bi
408                    && match ($am, $bm) {
409                        (Some(am), Some(bm)) => am <= bm,
410                        (None, Some(_)) => false,
411                        _ => true,
412                    }
413            }};
414        }
415
416        match (a, b) {
417            (CoreExtern::Func(a), CoreExtern::Func(b)) => self.core_func(a, at, b, bt),
418            (
419                CoreExtern::Table {
420                    element_type: ae,
421                    initial: ai,
422                    maximum: am,
423                    table64: a64,
424                    shared: ashared,
425                },
426                CoreExtern::Table {
427                    element_type: be,
428                    initial: bi,
429                    maximum: bm,
430                    table64: b64,
431                    shared: bshared,
432                },
433            ) => {
434                if ae != be {
435                    let (expected, _, found, _) = self.expected_found(ae, at, be, bt);
436                    bail!("expected table element type {expected}, found {found}");
437                }
438
439                if !limits_match!(ai, am, bi, bm) {
440                    bail!("mismatched table limits");
441                }
442
443                if a64 != b64 {
444                    bail!("mismatched table64 flag for tables");
445                }
446
447                if ashared != bshared {
448                    bail!("mismatched shared flag for tables");
449                }
450
451                Ok(())
452            }
453            (
454                CoreExtern::Memory {
455                    memory64: a64,
456                    shared: ashared,
457                    initial: ai,
458                    maximum: am,
459                    page_size_log2: apsl,
460                },
461                CoreExtern::Memory {
462                    memory64: b64,
463                    shared: bshared,
464                    initial: bi,
465                    maximum: bm,
466                    page_size_log2: bpsl,
467                },
468            ) => {
469                if ashared != bshared {
470                    bail!("mismatched shared flag for memories");
471                }
472
473                if a64 != b64 {
474                    bail!("mismatched memory64 flag for memories");
475                }
476
477                if !limits_match!(ai, am, bi, bm) {
478                    bail!("mismatched memory limits");
479                }
480
481                if apsl != bpsl {
482                    bail!("mismatched page_size_log2 for memories");
483                }
484
485                Ok(())
486            }
487            (
488                CoreExtern::Global {
489                    val_type: avt,
490                    mutable: am,
491                    shared: ashared,
492                },
493                CoreExtern::Global {
494                    val_type: bvt,
495                    mutable: bm,
496                    shared: bshared,
497                },
498            ) => {
499                if am != bm {
500                    bail!("mismatched mutable flag for globals");
501                }
502
503                if avt != bvt {
504                    let (expected, _, found, _) = self.expected_found(avt, at, bvt, bt);
505                    bail!("expected global type {expected}, found {found}");
506                }
507
508                if ashared != bshared {
509                    bail!("mismatched shared flag for globals");
510                }
511
512                Ok(())
513            }
514            (CoreExtern::Tag(a), CoreExtern::Tag(b)) => self.core_func(a, at, b, bt),
515
516            (CoreExtern::Func(_), _)
517            | (CoreExtern::Table { .. }, _)
518            | (CoreExtern::Memory { .. }, _)
519            | (CoreExtern::Global { .. }, _)
520            | (CoreExtern::Tag(_), _) => {
521                let (expected, _, found, _) = self.expected_found(a, at, b, bt);
522                bail!("expected {expected}, found {found}");
523            }
524        }
525    }
526
527    fn core_func(&self, a: &CoreFuncType, at: &Types, b: &CoreFuncType, bt: &Types) -> Result<()> {
528        if a != b {
529            let (expected, _, found, _) = self.expected_found(a, at, b, bt);
530            bail!("expected {expected}, found {found}");
531        }
532
533        Ok(())
534    }
535
536    fn value_type(&self, a: ValueType, at: &Types, b: ValueType, bt: &Types) -> Result<()> {
537        let a = at.resolve_value_type(a);
538        let b = bt.resolve_value_type(b);
539
540        match (a, b) {
541            (ValueType::Primitive(a), ValueType::Primitive(b)) => self.primitive(a, at, b, bt),
542            (ValueType::Defined(a), ValueType::Defined(b)) => self.defined_type(a, at, b, bt),
543            (ValueType::Borrow(a), ValueType::Borrow(b))
544            | (ValueType::Own(a), ValueType::Own(b)) => self.resource(a, at, b, bt),
545
546            (ValueType::Primitive(_), _)
547            | (ValueType::Defined(_), _)
548            | (ValueType::Borrow(_), _)
549            | (ValueType::Own(_), _) => {
550                let (expected, expected_types, found, found_types) =
551                    self.expected_found(&a, at, &b, bt);
552                bail!(
553                    "expected {expected}, found {found}",
554                    expected = expected.desc(expected_types),
555                    found = found.desc(found_types)
556                )
557            }
558        }
559    }
560
561    fn defined_type(
562        &self,
563        a: DefinedTypeId,
564        at: &Types,
565        b: DefinedTypeId,
566        bt: &Types,
567    ) -> std::result::Result<(), anyhow::Error> {
568        if a == b {
569            return Ok(());
570        }
571
572        let a = &at[a];
573        let b = &bt[b];
574        match (a, b) {
575            (DefinedType::Tuple(a), DefinedType::Tuple(b)) => self.tuple(a, at, b, bt),
576            (DefinedType::List(a), DefinedType::List(b)) => self
577                .value_type(*a, at, *b, bt)
578                .context("mismatched type for list element"),
579            (DefinedType::FixedSizeList(a, asize), DefinedType::FixedSizeList(b, bsize)) => {
580                if asize != bsize {
581                    bail!("mismatched size for fixed size list element");
582                }
583                self.value_type(*a, at, *b, bt)
584                    .context("mismatched type for fixed size list element")
585            }
586            (DefinedType::Future(a), DefinedType::Future(b)) => self
587                .payload(*a, at, *b, bt)
588                .context("mismatched type for future payload"),
589            (DefinedType::Stream(a), DefinedType::Stream(b)) => self
590                .payload(*a, at, *b, bt)
591                .context("mismatched type for stream payload"),
592            (DefinedType::Option(a), DefinedType::Option(b)) => self
593                .value_type(*a, at, *b, bt)
594                .context("mismatched type for option"),
595            (
596                DefinedType::Result {
597                    ok: a_ok,
598                    err: a_err,
599                },
600                DefinedType::Result {
601                    ok: b_ok,
602                    err: b_err,
603                },
604            ) => {
605                self.result("ok", a_ok, at, b_ok, bt)?;
606                self.result("err", a_err, at, b_err, bt)
607            }
608            (DefinedType::Variant(a), DefinedType::Variant(b)) => self.variant(a, at, b, bt),
609            (DefinedType::Record(a), DefinedType::Record(b)) => self.record(a, at, b, bt),
610            (DefinedType::Flags(a), DefinedType::Flags(b)) => self.flags(a, at, b, bt),
611            (DefinedType::Enum(a), DefinedType::Enum(b)) => self.enum_type(a, at, b, bt),
612            (DefinedType::Alias(_), _) | (_, DefinedType::Alias(_)) => {
613                panic!("aliases should have been resolved")
614            }
615
616            (DefinedType::Tuple(_), _)
617            | (DefinedType::List(_), _)
618            | (DefinedType::FixedSizeList(_, _), _)
619            | (DefinedType::Option(_), _)
620            | (DefinedType::Result { .. }, _)
621            | (DefinedType::Variant(_), _)
622            | (DefinedType::Record(_), _)
623            | (DefinedType::Flags(_), _)
624            | (DefinedType::Enum(_), _)
625            | (DefinedType::Stream(_), _)
626            | (DefinedType::Future(_), _) => {
627                let (expected, expected_types, found, found_types) =
628                    self.expected_found(a, at, b, bt);
629                bail!(
630                    "expected {expected}, found {found}",
631                    expected = expected.desc(expected_types),
632                    found = found.desc(found_types)
633                )
634            }
635        }
636    }
637
638    fn result(
639        &self,
640        desc: &str,
641        a: &Option<ValueType>,
642        at: &Types,
643        b: &Option<ValueType>,
644        bt: &Types,
645    ) -> Result<()> {
646        match (a, b) {
647            (None, None) => return Ok(()),
648            (Some(a), Some(b)) => {
649                return self
650                    .value_type(*a, at, *b, bt)
651                    .with_context(|| format!("mismatched type for result `{desc}`"))
652            }
653            (Some(_), None) | (None, Some(_)) => {
654                // Handle mismatch below
655            }
656        }
657
658        let (expected, _, found, _) = self.expected_found(a, at, b, bt);
659        match (expected, found) {
660            (Some(_), None) => bail!("expected an `{desc}` for result type"),
661            (None, Some(_)) => bail!("expected no `{desc}` for result type"),
662            (None, None) | (Some(_), Some(_)) => panic!("expected to be handled"),
663        }
664    }
665
666    fn enum_type(&self, a: &Enum, at: &Types, b: &Enum, bt: &Types) -> Result<()> {
667        if a.0.len() != b.0.len() {
668            let (expected, _, found, _) = self.expected_found(a, at, b, bt);
669            bail!(
670                "expected an enum type case count of {expected}, found a count of {found}",
671                expected = expected.0.len(),
672                found = found.0.len()
673            );
674        }
675
676        if let Some((index, (a, b))) =
677            a.0.iter()
678                .zip(b.0.iter())
679                .enumerate()
680                .find(|(_, (a, b))| a != b)
681        {
682            let (expected, _, found, _) = self.expected_found(a, at, b, bt);
683            bail!("expected enum case {index} to be named `{expected}`, found an enum case named `{found}`");
684        }
685
686        Ok(())
687    }
688
689    fn flags(&self, a: &Flags, at: &Types, b: &Flags, bt: &Types) -> Result<()> {
690        if a.0.len() != b.0.len() {
691            let (expected, _, found, _) = self.expected_found(a, at, b, bt);
692            bail!(
693                "expected a flags type flag count of {expected}, found a count of {found}",
694                expected = expected.0.len(),
695                found = found.0.len()
696            );
697        }
698
699        if let Some((index, (a, b))) =
700            a.0.iter()
701                .zip(b.0.iter())
702                .enumerate()
703                .find(|(_, (a, b))| a != b)
704        {
705            let (expected, _, found, _) = self.expected_found(a, at, b, bt);
706            bail!("expected flag {index} to be named `{expected}`, found a flag named `{found}`");
707        }
708
709        Ok(())
710    }
711
712    fn record(&self, a: &Record, at: &Types, b: &Record, bt: &Types) -> Result<()> {
713        if a.fields.len() != b.fields.len() {
714            let (expected, _, found, _) = self.expected_found(a, at, b, bt);
715            bail!(
716                "expected a record field count of {expected}, found a count of {found}",
717                expected = expected.fields.len(),
718                found = found.fields.len()
719            );
720        }
721
722        for (i, ((an, a), (bn, b))) in a.fields.iter().zip(b.fields.iter()).enumerate() {
723            if an != bn {
724                let (expected, _, found, _) = self.expected_found(an, at, bn, bt);
725                bail!("expected record field {i} to be named `{expected}`, found a field named `{found}`");
726            }
727
728            self.value_type(*a, at, *b, bt)
729                .with_context(|| format!("mismatched type for record field `{bn}`"))?;
730        }
731
732        Ok(())
733    }
734
735    fn variant(&self, a: &Variant, at: &Types, b: &Variant, bt: &Types) -> Result<()> {
736        if a.cases.len() != b.cases.len() {
737            let (expected, _, found, _) = self.expected_found(a, at, b, bt);
738            bail!(
739                "expected a variant case count of {expected}, found a count of {found}",
740                expected = expected.cases.len(),
741                found = found.cases.len()
742            );
743        }
744
745        for (i, ((an, a), (bn, b))) in a.cases.iter().zip(b.cases.iter()).enumerate() {
746            if an != bn {
747                let (expected, _, found, _) = self.expected_found(an, at, bn, bt);
748                bail!("expected variant case {i} to be named `{expected}`, found a case named `{found}`");
749            }
750
751            match (a, b) {
752                (None, None) => {}
753                (Some(a), Some(b)) => self
754                    .value_type(*a, at, *b, bt)
755                    .with_context(|| format!("mismatched type for variant case `{bn}`"))?,
756                _ => {
757                    let (expected, _, found, _) = self.expected_found(a, at, b, bt);
758                    match (expected, found) {
759                        (None, Some(_)) => {
760                            bail!("expected variant case `{bn}` to be untyped, found a typed case")
761                        }
762                        (Some(_), None) => {
763                            bail!("expected variant case `{bn}` to be typed, found an untyped case")
764                        }
765                        (None, None) | (Some(_), Some(_)) => panic!("expected to be handled"),
766                    }
767                }
768            }
769        }
770
771        Ok(())
772    }
773
774    fn tuple(&self, a: &Vec<ValueType>, at: &Types, b: &Vec<ValueType>, bt: &Types) -> Result<()> {
775        if a.len() != b.len() {
776            let (expected, _, found, _) = self.expected_found(a, at, b, bt);
777            bail!(
778                "expected a tuple of size {expected}, found a tuple of size {found}",
779                expected = expected.len(),
780                found = found.len()
781            );
782        }
783
784        for (i, (a, b)) in a.iter().zip(b.iter()).enumerate() {
785            self.value_type(*a, at, *b, bt)
786                .with_context(|| format!("mismatched type for tuple item {i}"))?;
787        }
788
789        Ok(())
790    }
791
792    fn payload(
793        &self,
794        a: Option<ValueType>,
795        at: &Types,
796        b: Option<ValueType>,
797        bt: &Types,
798    ) -> Result<()> {
799        match (a, b) {
800            (Some(a), Some(b)) => self.value_type(a, at, b, bt),
801            (None, None) => Ok(()),
802            (Some(_), None) => bail!("expected a type payload, found none"),
803            (None, Some(_)) => bail!("expected no type payload, found one"),
804        }
805    }
806
807    fn primitive(&self, a: PrimitiveType, at: &Types, b: PrimitiveType, bt: &Types) -> Result<()> {
808        // Note: currently subtyping for primitive types is done in terms of equality
809        // rather than actual subtyping; the reason for this is that implementing
810        // runtimes don't yet support more complex subtyping rules.
811        if a != b {
812            let (expected, _, found, _) = self.expected_found(&a, at, &b, bt);
813            bail!(
814                "expected {expected}, found {found}",
815                expected = expected.desc(),
816                found = found.desc()
817            );
818        }
819
820        Ok(())
821    }
822}
823
824#[cfg(test)]
825mod tests {
826    use super::*;
827    use crate::{CoreRefType, CoreType, HeapType};
828
829    fn check_core_extern(a: &CoreExtern, b: &CoreExtern) -> Result<()> {
830        let types = Types::default();
831        let mut cache = HashSet::new();
832        let checker = SubtypeChecker::new(&mut cache);
833        checker.core_extern(a, &types, b, &types)
834    }
835
836    fn base_table() -> CoreExtern {
837        CoreExtern::Table {
838            element_type: CoreRefType {
839                nullable: true,
840                heap_type: HeapType::Func,
841            },
842            initial: 1,
843            maximum: None,
844            table64: false,
845            shared: false,
846        }
847    }
848
849    #[test]
850    fn mismatched_table64_is_rejected() {
851        let a = base_table();
852        let mut b = base_table();
853        if let CoreExtern::Table { table64, .. } = &mut b {
854            *table64 = true;
855        }
856        assert!(
857            check_core_extern(&a, &b).is_err(),
858            "mismatched table64 should be rejected"
859        );
860    }
861
862    #[test]
863    fn mismatched_table_shared_is_rejected() {
864        let a = base_table();
865        let mut b = base_table();
866        if let CoreExtern::Table { shared, .. } = &mut b {
867            *shared = true;
868        }
869        assert!(
870            check_core_extern(&a, &b).is_err(),
871            "mismatched table shared should be rejected"
872        );
873    }
874
875    #[test]
876    fn mismatched_memory_page_size_log2_is_rejected() {
877        let a = CoreExtern::Memory {
878            memory64: false,
879            shared: false,
880            initial: 1,
881            maximum: None,
882            page_size_log2: Some(16),
883        };
884        let b = CoreExtern::Memory {
885            memory64: false,
886            shared: false,
887            initial: 1,
888            maximum: None,
889            page_size_log2: Some(14),
890        };
891        assert!(
892            check_core_extern(&a, &b).is_err(),
893            "mismatched page_size_log2 should be rejected"
894        );
895    }
896
897    #[test]
898    fn mismatched_global_shared_is_rejected() {
899        let a = CoreExtern::Global {
900            val_type: CoreType::I32,
901            mutable: false,
902            shared: false,
903        };
904        let b = CoreExtern::Global {
905            val_type: CoreType::I32,
906            mutable: false,
907            shared: true,
908        };
909        assert!(
910            check_core_extern(&a, &b).is_err(),
911            "mismatched global shared should be rejected"
912        );
913    }
914}