hyperlight-component-util 0.15.0

Shared implementation for the procedural macros that generate Hyperlight host and guest bindings from component types
Documentation
/*
Copyright 2025 The Hyperlight Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
 */

use itertools::Itertools;

use crate::etypes::{
    Component, Ctx, Defined, Func, Handleable, Name, QualifiedInstance, ResourceId, TypeBound,
    Tyvar, Value,
};
use crate::tv::ResolvedTyvar;

/// The various ways in which a value can fail to be a subtype of another
#[derive(Debug)]
#[allow(dead_code)]
pub enum Error<'r> {
    /// An unnamed value that was expected was missing (e.g. in a
    /// tuple or variant case)
    MissingValue(Value<'r>),
    /// A record field that was expected was missing
    MissingRecordField(Name<'r>),
    /// A variant case that was expected was missing
    MissingVariantCase(Name<'r>),
    /// A value type was present, but incompatible with its expected type
    MismatchedValue(Value<'r>, Value<'r>),
    /// A defined type was present, but incompatible with its expected type
    MismatchedDefined(Box<Defined<'r>>, Box<Defined<'r>>),
    /// A resource was present, but was not the same resource as was expected
    MismatchedResources(ResourceId, ResourceId),
    /// A type variable could not be resolved to be the same as the
    /// expected one
    MismatchedVars(Tyvar, Tyvar),
    /// A resource was expected but a non-resource tyvar was found, or
    /// vice versa
    MismatchedResourceVar(Tyvar, ResourceId),
    /// A handle was taken to something that wasn't a
    /// resource. Strictly speaking, this might be a well-formedness
    /// error on one side or the other rather than a subtyping error
    NotResource(Handleable),
}

/// # Subtyping
///
/// Most of this is a very direct translation of the subset of the
/// OCaml reference interpreter that we need here. Most of the bits
/// with variables and instantiation that require being quite careful
/// are not involved here, since during the elaboration that we are
/// doing we never need to fully subtype entire component types, which
/// makes this quite a bit simpler.
impl<'p, 'a> Ctx<'p, 'a> {
    pub fn subtype_value<'r>(
        &self,
        vt1: &'r Value<'a>,
        vt2: &'r Value<'a>,
    ) -> Result<(), Error<'a>> {
        use Value::*;
        use itertools::EitherOrBoth::*;
        match (vt1, vt2) {
            (Bool, Bool) => Ok(()),
            (S(w1), S(w2)) if w1 == w2 => Ok(()),
            (U(w1), U(w2)) if w1 == w2 => Ok(()),
            (F(w1), F(w2)) if w1 == w2 => Ok(()),
            (Char, Char) => Ok(()),
            (String, String) => Ok(()),
            (List(vt1), List(vt2)) => self.subtype_value(vt1, vt2),
            (Record(rfs1), Record(rfs2)) => {
                for rf2 in rfs2.iter() {
                    match rfs1.iter().find(|rf| rf2.name.name == rf.name.name) {
                        None => return Err(Error::MissingRecordField(rf2.name)),
                        Some(rf1) => self.subtype_value(&rf1.ty, &rf2.ty)?,
                    }
                }
                Ok(())
            }
            (Tuple(vts1), Tuple(vts2)) => {
                vts1.iter()
                    .zip_longest(vts2.iter())
                    .try_for_each(|vs| match vs {
                        Both(vt1, vt2) => self.subtype_value(vt1, vt2),
                        Left(_) => Ok(()),
                        Right(vt2) => Err(Error::MissingValue(vt2.clone())),
                    })
            }
            (Flags(ns1), Flags(ns2)) => ns2
                .iter()
                .find(|n2| !ns1.iter().any(|n| n.name == n2.name))
                .map_or(Ok(()), |n| Err(Error::MissingRecordField(*n))),
            (Variant(vcs1), Variant(vcs2)) => {
                for vc1 in vcs1.iter() {
                    match vcs2.iter().find(|vc| vc1.name.name == vc.name.name) {
                        None => return Err(Error::MissingVariantCase(vc1.name)),
                        Some(vc2) => self.subtype_value_option(&vc1.ty, &vc2.ty)?,
                    }
                }
                Ok(())
            }
            (Enum(ns1), Enum(ns2)) => ns1
                .iter()
                .find(|n1| !ns2.iter().any(|n| n.name == n1.name))
                .map_or(Ok(()), |n| Err(Error::MissingVariantCase(*n))),
            (Option(vt1), Option(vt2)) => self.subtype_value(vt1, vt2),
            (Result(vt11, vt12), Result(vt21, vt22)) => self
                .subtype_value_option(vt11, vt21)
                .and(self.subtype_value_option(vt12, vt22)),
            (Own(ht1), Own(ht2)) | (Borrow(ht1), Borrow(ht2)) => {
                self.subtype_handleable_is_resource(ht1)?;
                self.subtype_handleable_is_resource(ht2)?;
                self.subtype_handleable(ht1, ht2)
            }
            (Var(_, vt1), vt2) => self.subtype_value(vt1, vt2),
            (vt1, Var(_, vt2)) => self.subtype_value(vt1, vt2),
            _ => Err(Error::MismatchedValue(vt1.clone(), vt2.clone())),
        }
    }
    pub fn subtype_value_option<'r>(
        &self,
        vt1: &'r Option<Value<'a>>,
        vt2: &'r Option<Value<'a>>,
    ) -> Result<(), Error<'a>> {
        match (vt1, vt2) {
            (None, None) => Ok(()),
            (None, Some(vt2)) => Err(Error::MissingValue(vt2.clone())),
            (Some(_), None) => Ok(()),
            (Some(vt1), Some(vt2)) => self.subtype_value(vt1, vt2),
        }
    }
    pub fn subtype_var_var<'r>(&self, v1: &'r Tyvar, v2: &'r Tyvar) -> Result<(), Error<'a>> {
        match (self.resolve_tyvar(v1), self.resolve_tyvar(v2)) {
            (ResolvedTyvar::Definite(dt1), ResolvedTyvar::Definite(dt2)) => {
                self.subtype_defined(&dt1, &dt2)
            }
            (ResolvedTyvar::E(o1, i1, _), ResolvedTyvar::E(o2, i2, _)) if o1 == o2 && i1 == i2 => {
                Ok(())
            }
            (ResolvedTyvar::U(o1, i1, _), ResolvedTyvar::U(o2, i2, _)) if o1 == o2 && i1 == i2 => {
                Ok(())
            }
            (ResolvedTyvar::Bound(_), _) | (_, ResolvedTyvar::Bound(_)) => {
                panic!("internal invariant violation: stray bvar in subtype_var_var")
            }
            _ => Err(Error::MismatchedVars(v1.clone(), v2.clone())),
        }
    }
    pub fn subtype_var_resource<'r>(
        &self,
        v1: &'r Tyvar,
        rid2: &'r ResourceId,
    ) -> Result<(), Error<'a>> {
        match self.resolve_tyvar(v1) {
            ResolvedTyvar::Definite(Defined::Handleable(Handleable::Resource(rid1)))
                if rid1 == *rid2 =>
            {
                Ok(())
            }
            _ => Err(Error::MismatchedResourceVar(v1.clone(), *rid2)),
        }
    }
    pub fn subtype_resource_var<'r>(
        &self,
        rid1: &'r ResourceId,
        v2: &'r Tyvar,
    ) -> Result<(), Error<'a>> {
        match self.resolve_tyvar(v2) {
            ResolvedTyvar::Definite(Defined::Handleable(Handleable::Resource(rid2)))
                if *rid1 == rid2 =>
            {
                Ok(())
            }
            _ => Err(Error::MismatchedResourceVar(v2.clone(), *rid1)),
        }
    }
    pub fn subtype_handleable<'r>(
        &self,
        ht1: &'r Handleable,
        ht2: &'r Handleable,
    ) -> Result<(), Error<'a>> {
        match (ht1, ht2) {
            (Handleable::Var(v1), Handleable::Var(v2)) => self.subtype_var_var(v1, v2),
            (Handleable::Var(v1), Handleable::Resource(rid2)) => {
                self.subtype_var_resource(v1, rid2)
            }
            (Handleable::Resource(rid1), Handleable::Var(v2)) => {
                self.subtype_resource_var(rid1, v2)
            }
            (Handleable::Resource(rid1), Handleable::Resource(rid2)) => {
                if rid1 == rid2 {
                    Ok(())
                } else {
                    Err(Error::MismatchedResources(*rid1, *rid2))
                }
            }
        }
    }
    pub fn subtype_func<'r>(
        &self,
        _ft1: &'r Func<'a>,
        _ft2: &'r Func<'a>,
    ) -> Result<(), Error<'a>> {
        panic!("func <: func should be impossible to encounter during type elaboration")
    }
    pub fn subtype_qualified_instance<'r>(
        &self,
        _qi1: &'r QualifiedInstance<'a>,
        _qi2: &'r QualifiedInstance<'a>,
    ) -> Result<(), Error<'a>> {
        panic!("qinstance <: qinstance should be impossible to encounter during type elaboration")
    }
    pub fn subtype_component<'r>(
        &self,
        _ct1: &'r Component<'a>,
        _ct2: &'r Component<'a>,
    ) -> Result<(), Error<'a>> {
        panic!("component <: component should be impossible to encounter during type elaboration")
    }
    pub fn subtype_defined<'r>(
        &self,
        dt1: &'r Defined<'a>,
        dt2: &'r Defined<'a>,
    ) -> Result<(), Error<'a>> {
        match (dt1, dt2) {
            (Defined::Handleable(ht1), Defined::Handleable(ht2)) => {
                self.subtype_handleable(ht1, ht2)
            }
            (Defined::Value(vt1), Defined::Value(vt2)) => self.subtype_value(vt1, vt2),
            (Defined::Func(ft1), Defined::Func(ft2)) => self.subtype_func(ft1, ft2),
            (Defined::Instance(it1), Defined::Instance(it2)) => {
                self.subtype_qualified_instance(it1, it2)
            }
            (Defined::Component(ct1), Defined::Component(ct2)) => self.subtype_component(ct1, ct2),
            _ => Err(Error::MismatchedDefined(
                Box::new(dt1.clone()),
                Box::new(dt2.clone()),
            )),
        }
    }
    pub fn subtype_handleable_is_resource<'r>(&self, ht: &'r Handleable) -> Result<(), Error<'a>> {
        match ht {
            Handleable::Resource(_) => Ok(()),
            Handleable::Var(tv) => match self.resolve_tyvar(tv) {
                ResolvedTyvar::Definite(Defined::Handleable(Handleable::Resource(_))) => Ok(()),
                ResolvedTyvar::E(_, _, TypeBound::SubResource) => Ok(()),
                ResolvedTyvar::U(_, _, TypeBound::SubResource) => Ok(()),
                _ => Err(Error::NotResource(ht.clone())),
            },
        }
    }
}