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 variant case is marked as refining another case, but that
121    /// case does not exist
122    NonexistentVariantRefinement(u32),
123    /// A variant case is marked as refining another case, but its
124    /// associated value is not a subtype of the value of the refined
125    /// case
126    IncompatibleVariantRefinement(subtype::Error<'a>),
127    /// A flags has multiple flags with the same name
128    DuplicateFlagsName(Name<'a>),
129    /// An enum has multiple cases with the same name
130    DuplicateEnumName(Name<'a>),
131    /// An import/export has the same name as another; the boolean is
132    /// true if it is an import
133    DuplicateExternName(&'a str, bool),
134    /// A value type owns or borrows a type that is not a resource type
135    NotAResource(subtype::Error<'a>),
136    /// A borrow type exists somewhere other than a function parameter
137    BorrowOutsideParam,
138}
139
140fn error_if_duplicates_by<T, U: Eq + std::hash::Hash, E>(
141    i: impl Iterator<Item = T>,
142    f: impl FnMut(&T) -> U,
143    e: impl Fn(T) -> E,
144) -> Result<(), E> {
145    let mut duplicates = i.duplicates_by(f);
146    if let Some(x) = duplicates.next() {
147        Err(e(x))
148    } else {
149        Ok(())
150    }
151}
152
153/// # Well-formedness
154///
155/// Most of this is a very direct translation of the specification
156/// (Well-formedness subsections of section 3.4 Type Elaboration).
157impl<'p, 'a> Ctx<'p, 'a> {
158    fn wf_record_fields<'r>(
159        &'r self,
160        p: ValueTypePosition,
161        rfs: &'r [RecordField<'a>],
162    ) -> Result<(), Error<'a>> {
163        rfs.iter()
164            .try_for_each(|rf: &'r RecordField<'a>| self.wf_value(p, &rf.ty))?;
165        error_if_duplicates_by(
166            rfs.iter(),
167            |&rf| rf.name.name,
168            |rf| Error::DuplicateRecordField(rf.name),
169        )?;
170        Ok(())
171    }
172    fn wf_variant_cases<'r>(
173        &'r self,
174        p: ValueTypePosition,
175        vcs: &'r [VariantCase<'a>],
176    ) -> Result<(), Error<'a>> {
177        vcs.iter()
178            .try_for_each(|vc: &'r VariantCase<'a>| self.wf_value_option(p, &vc.ty))?;
179        error_if_duplicates_by(
180            vcs.iter(),
181            |&vc| vc.name.name,
182            |vc| Error::DuplicateVariantField(vc.name),
183        )?;
184        for vc in vcs {
185            if let Some(ri) = vc.refines {
186                let rvc = vcs
187                    .get(ri as usize)
188                    .ok_or(Error::NonexistentVariantRefinement(ri))?;
189                self.subtype_value_option(&vc.ty, &rvc.ty)
190                    .map_err(Error::IncompatibleVariantRefinement)?;
191            }
192        }
193        Ok(())
194    }
195    fn wf_value<'r>(&'r self, p: ValueTypePosition, vt: &'r Value<'a>) -> Result<(), Error<'a>> {
196        let anon_err: Result<(), Error<'a>> = if p.dtp.is_export && p.dtp.is_anon_export {
197            Err(Error::BareComplexValTypeExport(vt.clone()))
198        } else {
199            Ok(())
200        };
201        let p_ = p.anon_export();
202        let resource_err = |h| {
203            self.wf_handleable(p.dtp, h).and(
204                self.subtype_handleable_is_resource(h)
205                    .map_err(Error::NotAResource),
206            )
207        };
208        match vt {
209            Value::Bool => Ok(()),
210            Value::S(_) => Ok(()),
211            Value::U(_) => Ok(()),
212            Value::F(_) => Ok(()),
213            Value::Char => Ok(()),
214            Value::String => Ok(()),
215            Value::List(vt) => self.wf_value(p_, vt),
216            Value::Record(rfs) => anon_err.and(self.wf_record_fields(p_, rfs)),
217            Value::Variant(vcs) => anon_err.and(self.wf_variant_cases(p_, vcs)),
218            Value::Flags(ns) => anon_err.and(error_if_duplicates_by(
219                ns.iter(),
220                |&n| n.name,
221                |n| Error::DuplicateFlagsName(*n),
222            )),
223            Value::Enum(ns) => anon_err.and(error_if_duplicates_by(
224                ns.iter(),
225                |&n| n.name,
226                |n| Error::DuplicateEnumName(*n),
227            )),
228            Value::Option(vt) => self.wf_value(p_, vt),
229            Value::Tuple(vs) => vs
230                .iter()
231                .try_for_each(|vt: &'r Value<'a>| self.wf_value(p_, vt)),
232            Value::Result(vt1, vt2) => self
233                .wf_value_option(p_, vt1)
234                .and(self.wf_value_option(p_, vt2)),
235            Value::Own(h) => resource_err(h),
236            Value::Borrow(h) => {
237                if p.is_param {
238                    resource_err(h)
239                } else {
240                    Err(Error::BorrowOutsideParam)
241                }
242            }
243            Value::Var(tv, vt) => tv
244                .as_ref()
245                .map(|tv| self.wf_type_bound(p.dtp, self.var_bound(tv)))
246                .unwrap_or(Ok(()))
247                .and(self.wf_value(p.not_anon_export(), vt)),
248        }
249    }
250    fn wf_value_option<'r>(
251        &'r self,
252        p: ValueTypePosition,
253        vt: &'r Option<Value<'a>>,
254    ) -> Result<(), Error<'a>> {
255        vt.as_ref().map_or(Ok(()), |ty| self.wf_value(p, ty))
256    }
257    fn wf_func<'r>(&'r self, p: DefinedTypePosition, ft: &'r Func<'a>) -> Result<(), Error<'a>> {
258        let p_ = p.anon_export();
259        let param_pos = ValueTypePosition {
260            is_param: true,
261            dtp: p_,
262        };
263        let result_pos = ValueTypePosition {
264            is_param: false,
265            dtp: p_,
266        };
267        ft.params
268            .iter()
269            .try_for_each(|fp: &'r Param<'a>| self.wf_value(param_pos, &fp.ty))?;
270        match &ft.result {
271            crate::etypes::Result::Unnamed(vt) => self.wf_value(result_pos, vt),
272            crate::etypes::Result::Named(ps) => ps
273                .iter()
274                .try_for_each(|fp: &'r Param<'a>| self.wf_value(result_pos, &fp.ty)),
275        }
276    }
277    fn wf_type_bound<'r>(
278        &'r self,
279        p: DefinedTypePosition,
280        tb: &'r TypeBound<'a>,
281    ) -> Result<(), Error<'a>> {
282        match tb {
283            TypeBound::SubResource => Ok(()),
284            TypeBound::Eq(dt) => self.wf_defined(p.not_anon_export(), dt),
285        }
286    }
287    fn wf_bounded_tyvar<'r>(
288        &'r self,
289        p: DefinedTypePosition,
290        btv: &'r BoundedTyvar<'a>,
291    ) -> Result<(), Error<'a>> {
292        match &btv.bound {
293            TypeBound::SubResource => Ok(()),
294            TypeBound::Eq(dt) => self.wf_defined(p, dt),
295        }
296    }
297
298    fn wf_handleable<'r>(
299        &'r self,
300        p: DefinedTypePosition,
301        ht: &'r Handleable,
302    ) -> Result<(), Error<'a>> {
303        match ht {
304            Handleable::Var(tv) => self.wf_type_bound(p, self.var_bound(tv)),
305            Handleable::Resource(rid) => {
306                if p.is_export {
307                    Err(Error::BareResourceExport)
308                } else {
309                    // Internal invariant: rtidx should always exist
310                    assert!((rid.id as usize) < self.rtypes.len());
311                    Ok(())
312                }
313            }
314        }
315    }
316    pub fn wf_defined<'r>(
317        &'r self,
318        p: DefinedTypePosition,
319        dt: &'r Defined<'a>,
320    ) -> Result<(), Error<'a>> {
321        match dt {
322            Defined::Handleable(ht) => self.wf_handleable(p, ht),
323            Defined::Value(vt) => self.wf_value(p.into(), vt),
324            Defined::Func(ft) => self.wf_func(p, ft),
325            Defined::Instance(it) => self.wf_qualified_instance(p, it),
326            Defined::Component(ct) => self.wf_component(p, ct),
327        }
328    }
329    fn wf_extern_desc<'r>(
330        &self,
331        p: DefinedTypePosition,
332        ed: &'r ExternDesc<'a>,
333    ) -> Result<(), Error<'a>> {
334        match ed {
335            ExternDesc::CoreModule(_) => Ok(()),
336            ExternDesc::Func(ft) => self.wf_func(p, ft),
337            ExternDesc::Type(dt) => self.wf_defined(p, dt),
338            ExternDesc::Instance(it) => self.wf_instance(p, it),
339            ExternDesc::Component(ct) => self.wf_component(p, ct),
340        }
341    }
342    fn wf_extern_decl<'r>(
343        &self,
344        p: DefinedTypePosition,
345        ed: &'r ExternDecl<'a>,
346    ) -> Result<(), Error<'a>> {
347        self.wf_extern_desc(p, &ed.desc)
348    }
349    fn wf_instance<'r>(
350        &self,
351        p: DefinedTypePosition,
352        it: &'r Instance<'a>,
353    ) -> Result<(), Error<'a>> {
354        error_if_duplicates_by(
355            it.exports.iter(),
356            |&ex| ex.kebab_name,
357            |ex| Error::DuplicateExternName(ex.kebab_name, false),
358        )?;
359        it.exports
360            .iter()
361            .try_for_each(|ed| self.wf_extern_decl(p, ed))
362    }
363    pub fn wf_qualified_instance<'r>(
364        &self,
365        p: DefinedTypePosition,
366        qit: &'r QualifiedInstance<'a>,
367    ) -> Result<(), Error<'a>> {
368        let mut ctx_ = self.clone();
369        let subst = ctx_.bound_to_evars(None, &qit.evars);
370        ctx_.evars
371            .iter()
372            .try_for_each(|(btv, _)| ctx_.wf_bounded_tyvar(p, btv))?;
373        let it = subst.instance(&qit.unqualified).not_void();
374        ctx_.wf_instance(p, &it)
375    }
376    pub fn wf_component<'r>(
377        &self,
378        p: DefinedTypePosition,
379        ct: &'r Component<'a>,
380    ) -> Result<(), Error<'a>> {
381        let mut ctx_ = self.clone();
382        let subst = ctx_.bound_to_uvars(None, &ct.uvars, false);
383        ctx_.uvars
384            .iter()
385            .try_for_each(|(btv, _)| ctx_.wf_bounded_tyvar(p, btv))?;
386        error_if_duplicates_by(
387            ct.imports.iter(),
388            |&im| im.kebab_name,
389            |im| Error::DuplicateExternName(im.kebab_name, true),
390        )?;
391        ct.imports
392            .iter()
393            .map(|ed| subst.extern_decl(ed).not_void())
394            .try_for_each(|ed| ctx_.wf_extern_decl(p, &ed))?;
395        let it = subst.qualified_instance(&ct.instance).not_void();
396        ctx_.wf_qualified_instance(p, &it)
397    }
398}