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::FixList(vt, _) => self.wf_value(p_, vt),
217            Value::Record(rfs) => anon_err.and(self.wf_record_fields(p_, rfs)),
218            Value::Variant(vcs) => anon_err.and(self.wf_variant_cases(p_, vcs)),
219            Value::Flags(ns) => anon_err.and(error_if_duplicates_by(
220                ns.iter(),
221                |&n| n.name,
222                |n| Error::DuplicateFlagsName(*n),
223            )),
224            Value::Enum(ns) => anon_err.and(error_if_duplicates_by(
225                ns.iter(),
226                |&n| n.name,
227                |n| Error::DuplicateEnumName(*n),
228            )),
229            Value::Option(vt) => self.wf_value(p_, vt),
230            Value::Tuple(vs) => vs
231                .iter()
232                .try_for_each(|vt: &'r Value<'a>| self.wf_value(p_, vt)),
233            Value::Result(vt1, vt2) => self
234                .wf_value_option(p_, vt1)
235                .and(self.wf_value_option(p_, vt2)),
236            Value::Own(h) => resource_err(h),
237            Value::Borrow(h) => {
238                if p.is_param {
239                    resource_err(h)
240                } else {
241                    Err(Error::BorrowOutsideParam)
242                }
243            }
244            Value::Var(tv, vt) => tv
245                .as_ref()
246                .map(|tv| self.wf_type_bound(p.dtp, self.var_bound(tv)))
247                .unwrap_or(Ok(()))
248                .and(self.wf_value(p.not_anon_export(), vt)),
249        }
250    }
251    fn wf_value_option<'r>(
252        &'r self,
253        p: ValueTypePosition,
254        vt: &'r Option<Value<'a>>,
255    ) -> Result<(), Error<'a>> {
256        vt.as_ref().map_or(Ok(()), |ty| self.wf_value(p, ty))
257    }
258    fn wf_func<'r>(&'r self, p: DefinedTypePosition, ft: &'r Func<'a>) -> Result<(), Error<'a>> {
259        let p_ = p.anon_export();
260        let param_pos = ValueTypePosition {
261            is_param: true,
262            dtp: p_,
263        };
264        let result_pos = ValueTypePosition {
265            is_param: false,
266            dtp: p_,
267        };
268        ft.params
269            .iter()
270            .try_for_each(|fp: &'r Param<'a>| self.wf_value(param_pos, &fp.ty))?;
271        match &ft.result {
272            Some(vt) => self.wf_value(result_pos, vt),
273            None => Ok(()),
274        }
275    }
276    fn wf_type_bound<'r>(
277        &'r self,
278        p: DefinedTypePosition,
279        tb: &'r TypeBound<'a>,
280    ) -> Result<(), Error<'a>> {
281        match tb {
282            TypeBound::SubResource => Ok(()),
283            TypeBound::Eq(dt) => self.wf_defined(p.not_anon_export(), dt),
284        }
285    }
286    fn wf_bounded_tyvar<'r>(
287        &'r self,
288        p: DefinedTypePosition,
289        btv: &'r BoundedTyvar<'a>,
290    ) -> Result<(), Error<'a>> {
291        match &btv.bound {
292            TypeBound::SubResource => Ok(()),
293            TypeBound::Eq(dt) => self.wf_defined(p, dt),
294        }
295    }
296
297    fn wf_handleable<'r>(
298        &'r self,
299        p: DefinedTypePosition,
300        ht: &'r Handleable,
301    ) -> Result<(), Error<'a>> {
302        match ht {
303            Handleable::Var(tv) => self.wf_type_bound(p, self.var_bound(tv)),
304            Handleable::Resource(rid) => {
305                if p.is_export {
306                    Err(Error::BareResourceExport)
307                } else {
308                    // Internal invariant: rtidx should always exist
309                    assert!((rid.id as usize) < self.rtypes.len());
310                    Ok(())
311                }
312            }
313        }
314    }
315    pub fn wf_defined<'r>(
316        &'r self,
317        p: DefinedTypePosition,
318        dt: &'r Defined<'a>,
319    ) -> Result<(), Error<'a>> {
320        match dt {
321            Defined::Handleable(ht) => self.wf_handleable(p, ht),
322            Defined::Value(vt) => self.wf_value(p.into(), vt),
323            Defined::Func(ft) => self.wf_func(p, ft),
324            Defined::Instance(it) => self.wf_qualified_instance(p, it),
325            Defined::Component(ct) => self.wf_component(p, ct),
326        }
327    }
328    fn wf_extern_desc<'r>(
329        &self,
330        p: DefinedTypePosition,
331        ed: &'r ExternDesc<'a>,
332    ) -> Result<(), Error<'a>> {
333        match ed {
334            ExternDesc::CoreModule(_) => Ok(()),
335            ExternDesc::Func(ft) => self.wf_func(p, ft),
336            ExternDesc::Type(dt) => self.wf_defined(p, dt),
337            ExternDesc::Instance(it) => self.wf_instance(p, it),
338            ExternDesc::Component(ct) => self.wf_component(p, ct),
339        }
340    }
341    fn wf_extern_decl<'r>(
342        &self,
343        p: DefinedTypePosition,
344        ed: &'r ExternDecl<'a>,
345    ) -> Result<(), Error<'a>> {
346        self.wf_extern_desc(p, &ed.desc)
347    }
348    fn wf_instance<'r>(
349        &self,
350        p: DefinedTypePosition,
351        it: &'r Instance<'a>,
352    ) -> Result<(), Error<'a>> {
353        error_if_duplicates_by(
354            it.exports.iter(),
355            |&ex| ex.kebab_name,
356            |ex| Error::DuplicateExternName(ex.kebab_name, false),
357        )?;
358        it.exports
359            .iter()
360            .try_for_each(|ed| self.wf_extern_decl(p, ed))
361    }
362    pub fn wf_qualified_instance<'r>(
363        &self,
364        p: DefinedTypePosition,
365        qit: &'r QualifiedInstance<'a>,
366    ) -> Result<(), Error<'a>> {
367        let mut ctx_ = self.clone();
368        let subst = ctx_.bound_to_evars(None, &qit.evars);
369        ctx_.evars
370            .iter()
371            .try_for_each(|(btv, _)| ctx_.wf_bounded_tyvar(p, btv))?;
372        let it = subst.instance(&qit.unqualified).not_void();
373        ctx_.wf_instance(p, &it)
374    }
375    pub fn wf_component<'r>(
376        &self,
377        p: DefinedTypePosition,
378        ct: &'r Component<'a>,
379    ) -> Result<(), Error<'a>> {
380        let mut ctx_ = self.clone();
381        let subst = ctx_.bound_to_uvars(None, &ct.uvars, false);
382        ctx_.uvars
383            .iter()
384            .try_for_each(|(btv, _)| ctx_.wf_bounded_tyvar(p, btv))?;
385        error_if_duplicates_by(
386            ct.imports.iter(),
387            |&im| im.kebab_name,
388            |im| Error::DuplicateExternName(im.kebab_name, true),
389        )?;
390        ct.imports
391            .iter()
392            .map(|ed| subst.extern_decl(ed).not_void())
393            .try_for_each(|ed| ctx_.wf_extern_decl(p, &ed))?;
394        let it = subst.qualified_instance(&ct.instance).not_void();
395        ctx_.wf_qualified_instance(p, &it)
396    }
397}