Skip to main content

hyperlight_component_util/
wf.rs

1/*
2Copyright 2025 The Hyperlight Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15 */
16
17//! Component type well-formedness
18//!
19//! This is a pretty direct port of the relevant sections of the OCaml
20//! reference interpreter.
21use itertools::Itertools;
22
23use crate::etypes::{
24    BoundedTyvar, Component, Ctx, Defined, ExternDecl, ExternDesc, Func, Handleable, Instance,
25    Name, Param, QualifiedInstance, RecordField, TypeBound, Value, VariantCase,
26};
27use crate::substitute::{Substitution, Unvoidable};
28use crate::subtype;
29
30/// The various position metadata that affect what value types are
31/// well-formed
32#[derive(Clone, Copy)]
33struct ValueTypePosition {
34    /// Is this well-formedness check for a type that is part of the
35    /// parameter type of a function? (Borrows should be allowed)
36    is_param: bool,
37    dtp: DefinedTypePosition,
38}
39
40impl From<DefinedTypePosition> for ValueTypePosition {
41    fn from(p: DefinedTypePosition) -> ValueTypePosition {
42        ValueTypePosition {
43            is_param: false,
44            dtp: p,
45        }
46    }
47}
48impl ValueTypePosition {
49    fn not_anon_export(self) -> Self {
50        ValueTypePosition {
51            dtp: self.dtp.not_anon_export(),
52            ..self
53        }
54    }
55    fn anon_export(self) -> Self {
56        ValueTypePosition {
57            dtp: self.dtp.anon_export(),
58            ..self
59        }
60    }
61}
62
63/// The various position metadata that affect what defined types are
64/// well-formed
65#[derive(Clone, Copy)]
66pub struct DefinedTypePosition {
67    /// Is this well-formedness check for a type one that should be
68    /// exportable (e.g. one that is being
69    /// exported/imported/outer-aliased-through-an-outer-boundary)?
70    /// (Bare resource types should be disallowed)
71    is_export: bool,
72    /// Is this well-formedness check for a type that should be
73    /// allowed in an "unnamed" export (i.e. nested under some other
74    /// type constructor in an export)? (Record, variant, enum, and
75    /// flags types, which must always be named in exports due to WIT
76    /// constraints, should not be allowed).
77    is_anon_export: bool,
78}
79impl DefinedTypePosition {
80    pub fn internal() -> Self {
81        DefinedTypePosition {
82            is_export: false,
83            is_anon_export: false,
84        }
85    }
86    pub fn export() -> Self {
87        DefinedTypePosition {
88            is_export: true,
89            is_anon_export: false,
90        }
91    }
92    fn not_anon_export(self) -> Self {
93        DefinedTypePosition {
94            is_anon_export: false,
95            ..self
96        }
97    }
98    fn anon_export(self) -> Self {
99        DefinedTypePosition {
100            is_anon_export: true,
101            ..self
102        }
103    }
104}
105
106/// There are several ways in which a type may be ill-formed:
107#[derive(Debug)]
108#[allow(dead_code)]
109pub enum Error<'a> {
110    /// A component/instance exported a bare resource type not behind
111    /// a tyvar (and therefore not named)
112    BareResourceExport,
113    /// A component/instance exported certain complex value types not
114    /// behind a tyvar (and therefore not named)
115    BareComplexValTypeExport(Value<'a>),
116    /// A record has multiple fields with the same name
117    DuplicateRecordField(Name<'a>),
118    /// A variant has multiple cases with the same name
119    DuplicateVariantField(Name<'a>),
120    /// A flags has multiple flags with the same name
121    DuplicateFlagsName(Name<'a>),
122    /// An enum has multiple cases with the same name
123    DuplicateEnumName(Name<'a>),
124    /// An import/export has the same name as another; the boolean is
125    /// true if it is an import
126    DuplicateExternName(&'a str, bool),
127    /// A value type owns or borrows a type that is not a resource type
128    NotAResource(subtype::Error<'a>),
129    /// A borrow type exists somewhere other than a function parameter
130    BorrowOutsideParam,
131}
132
133fn error_if_duplicates_by<T, U: Eq + std::hash::Hash, E>(
134    i: impl Iterator<Item = T>,
135    f: impl FnMut(&T) -> U,
136    e: impl Fn(T) -> E,
137) -> Result<(), E> {
138    let mut duplicates = i.duplicates_by(f);
139    if let Some(x) = duplicates.next() {
140        Err(e(x))
141    } else {
142        Ok(())
143    }
144}
145
146/// # Well-formedness
147///
148/// Most of this is a very direct translation of the specification
149/// (Well-formedness subsections of section 3.4 Type Elaboration).
150impl<'p, 'a> Ctx<'p, 'a> {
151    fn wf_record_fields<'r>(
152        &'r self,
153        p: ValueTypePosition,
154        rfs: &'r [RecordField<'a>],
155    ) -> Result<(), Error<'a>> {
156        rfs.iter()
157            .try_for_each(|rf: &'r RecordField<'a>| self.wf_value(p, &rf.ty))?;
158        error_if_duplicates_by(
159            rfs.iter(),
160            |&rf| rf.name.name,
161            |rf| Error::DuplicateRecordField(rf.name),
162        )?;
163        Ok(())
164    }
165    fn wf_variant_cases<'r>(
166        &'r self,
167        p: ValueTypePosition,
168        vcs: &'r [VariantCase<'a>],
169    ) -> Result<(), Error<'a>> {
170        vcs.iter()
171            .try_for_each(|vc: &'r VariantCase<'a>| self.wf_value_option(p, &vc.ty))?;
172        error_if_duplicates_by(
173            vcs.iter(),
174            |&vc| vc.name.name,
175            |vc| Error::DuplicateVariantField(vc.name),
176        )?;
177        Ok(())
178    }
179    fn wf_value<'r>(&'r self, p: ValueTypePosition, vt: &'r Value<'a>) -> Result<(), Error<'a>> {
180        let anon_err: Result<(), Error<'a>> = if p.dtp.is_export && p.dtp.is_anon_export {
181            Err(Error::BareComplexValTypeExport(vt.clone()))
182        } else {
183            Ok(())
184        };
185        let p_ = p.anon_export();
186        let resource_err = |h| {
187            self.wf_handleable(p.dtp, h).and(
188                self.subtype_handleable_is_resource(h)
189                    .map_err(Error::NotAResource),
190            )
191        };
192        match vt {
193            Value::Bool => Ok(()),
194            Value::S(_) => Ok(()),
195            Value::U(_) => Ok(()),
196            Value::F(_) => Ok(()),
197            Value::Char => Ok(()),
198            Value::String => Ok(()),
199            Value::List(vt) => self.wf_value(p_, vt),
200            Value::FixList(vt, _) => self.wf_value(p_, vt),
201            Value::Record(rfs) => anon_err.and(self.wf_record_fields(p_, rfs)),
202            Value::Variant(vcs) => anon_err.and(self.wf_variant_cases(p_, vcs)),
203            Value::Flags(ns) => anon_err.and(error_if_duplicates_by(
204                ns.iter(),
205                |&n| n.name,
206                |n| Error::DuplicateFlagsName(*n),
207            )),
208            Value::Enum(ns) => anon_err.and(error_if_duplicates_by(
209                ns.iter(),
210                |&n| n.name,
211                |n| Error::DuplicateEnumName(*n),
212            )),
213            Value::Option(vt) => self.wf_value(p_, vt),
214            Value::Tuple(vs) => vs
215                .iter()
216                .try_for_each(|vt: &'r Value<'a>| self.wf_value(p_, vt)),
217            Value::Result(vt1, vt2) => self
218                .wf_value_option(p_, vt1)
219                .and(self.wf_value_option(p_, vt2)),
220            Value::Own(h) => resource_err(h),
221            Value::Borrow(h) => {
222                if p.is_param {
223                    resource_err(h)
224                } else {
225                    Err(Error::BorrowOutsideParam)
226                }
227            }
228            Value::Var(tv, vt) => tv
229                .as_ref()
230                .map(|tv| self.wf_type_bound(p.dtp, self.var_bound(tv)))
231                .unwrap_or(Ok(()))
232                .and(self.wf_value(p.not_anon_export(), vt)),
233        }
234    }
235    fn wf_value_option<'r>(
236        &'r self,
237        p: ValueTypePosition,
238        vt: &'r Option<Value<'a>>,
239    ) -> Result<(), Error<'a>> {
240        vt.as_ref().map_or(Ok(()), |ty| self.wf_value(p, ty))
241    }
242    fn wf_func<'r>(&'r self, p: DefinedTypePosition, ft: &'r Func<'a>) -> Result<(), Error<'a>> {
243        let p_ = p.anon_export();
244        let param_pos = ValueTypePosition {
245            is_param: true,
246            dtp: p_,
247        };
248        let result_pos = ValueTypePosition {
249            is_param: false,
250            dtp: p_,
251        };
252        ft.params
253            .iter()
254            .try_for_each(|fp: &'r Param<'a>| self.wf_value(param_pos, &fp.ty))?;
255        match &ft.result {
256            Some(vt) => self.wf_value(result_pos, vt),
257            None => Ok(()),
258        }
259    }
260    fn wf_type_bound<'r>(
261        &'r self,
262        p: DefinedTypePosition,
263        tb: &'r TypeBound<'a>,
264    ) -> Result<(), Error<'a>> {
265        match tb {
266            TypeBound::SubResource => Ok(()),
267            TypeBound::Eq(dt) => self.wf_defined(p.not_anon_export(), dt),
268        }
269    }
270    fn wf_bounded_tyvar<'r>(
271        &'r self,
272        p: DefinedTypePosition,
273        btv: &'r BoundedTyvar<'a>,
274    ) -> Result<(), Error<'a>> {
275        match &btv.bound {
276            TypeBound::SubResource => Ok(()),
277            TypeBound::Eq(dt) => self.wf_defined(p, dt),
278        }
279    }
280
281    fn wf_handleable<'r>(
282        &'r self,
283        p: DefinedTypePosition,
284        ht: &'r Handleable,
285    ) -> Result<(), Error<'a>> {
286        match ht {
287            Handleable::Var(tv) => self.wf_type_bound(p, self.var_bound(tv)),
288            Handleable::Resource(rid) => {
289                if p.is_export {
290                    Err(Error::BareResourceExport)
291                } else {
292                    // Internal invariant: rtidx should always exist
293                    assert!((rid.id as usize) < self.rtypes.len());
294                    Ok(())
295                }
296            }
297        }
298    }
299    pub fn wf_defined<'r>(
300        &'r self,
301        p: DefinedTypePosition,
302        dt: &'r Defined<'a>,
303    ) -> Result<(), Error<'a>> {
304        match dt {
305            Defined::Handleable(ht) => self.wf_handleable(p, ht),
306            Defined::Value(vt) => self.wf_value(p.into(), vt),
307            Defined::Func(ft) => self.wf_func(p, ft),
308            Defined::Instance(it) => self.wf_qualified_instance(p, it),
309            Defined::Component(ct) => self.wf_component(p, ct),
310        }
311    }
312    fn wf_extern_desc<'r>(
313        &self,
314        p: DefinedTypePosition,
315        ed: &'r ExternDesc<'a>,
316    ) -> Result<(), Error<'a>> {
317        match ed {
318            ExternDesc::CoreModule(_) => Ok(()),
319            ExternDesc::Func(ft) => self.wf_func(p, ft),
320            ExternDesc::Type(dt) => self.wf_defined(p, dt),
321            ExternDesc::Instance(it) => self.wf_instance(p, it),
322            ExternDesc::Component(ct) => self.wf_component(p, ct),
323        }
324    }
325    fn wf_extern_decl<'r>(
326        &self,
327        p: DefinedTypePosition,
328        ed: &'r ExternDecl<'a>,
329    ) -> Result<(), Error<'a>> {
330        self.wf_extern_desc(p, &ed.desc)
331    }
332    fn wf_instance<'r>(
333        &self,
334        p: DefinedTypePosition,
335        it: &'r Instance<'a>,
336    ) -> Result<(), Error<'a>> {
337        error_if_duplicates_by(
338            it.exports.iter(),
339            |&ex| ex.kebab_name,
340            |ex| Error::DuplicateExternName(ex.kebab_name, false),
341        )?;
342        it.exports
343            .iter()
344            .try_for_each(|ed| self.wf_extern_decl(p, ed))
345    }
346    pub fn wf_qualified_instance<'r>(
347        &self,
348        p: DefinedTypePosition,
349        qit: &'r QualifiedInstance<'a>,
350    ) -> Result<(), Error<'a>> {
351        let mut ctx_ = self.clone();
352        let subst = ctx_.bound_to_evars(None, &qit.evars);
353        ctx_.evars
354            .iter()
355            .try_for_each(|(btv, _)| ctx_.wf_bounded_tyvar(p, btv))?;
356        let it = subst.instance(&qit.unqualified).not_void();
357        ctx_.wf_instance(p, &it)
358    }
359    pub fn wf_component<'r>(
360        &self,
361        p: DefinedTypePosition,
362        ct: &'r Component<'a>,
363    ) -> Result<(), Error<'a>> {
364        let mut ctx_ = self.clone();
365        let subst = ctx_.bound_to_uvars(None, &ct.uvars, false);
366        ctx_.uvars
367            .iter()
368            .try_for_each(|(btv, _)| ctx_.wf_bounded_tyvar(p, btv))?;
369        error_if_duplicates_by(
370            ct.imports.iter(),
371            |&im| im.kebab_name,
372            |im| Error::DuplicateExternName(im.kebab_name, true),
373        )?;
374        ct.imports
375            .iter()
376            .map(|ed| subst.extern_decl(ed).not_void())
377            .try_for_each(|ed| ctx_.wf_extern_decl(p, &ed))?;
378        let it = subst.qualified_instance(&ct.instance).not_void();
379        ctx_.wf_qualified_instance(p, &it)
380    }
381}