witx_generate_raw/
lib.rs

1use heck::*;
2use std::io::{Read, Write};
3use std::mem;
4use std::path::Path;
5use std::process::{Command, Stdio};
6use witx::*;
7
8pub fn generate<P: AsRef<Path>>(witx_paths: &[P]) -> String {
9    let doc = witx::load(witx_paths).unwrap();
10
11    let mut raw = String::new();
12    raw.push_str(
13        "\
14// This file is automatically generated, DO NOT EDIT
15//
16// To regenerate this file run the `crates/witx-bindgen` command
17
18use core::mem::MaybeUninit;
19use core::fmt;
20",
21    );
22    for ty in doc.typenames() {
23        ty.render(&mut raw);
24        raw.push('\n');
25    }
26    for m in doc.modules() {
27        m.render(&mut raw);
28        raw.push('\n');
29    }
30    for c in doc.constants() {
31        rustdoc(&c.docs, &mut raw);
32        raw.push_str(&format!(
33            "pub const {}_{}: {} = {};\n",
34            c.ty.as_str().to_shouty_snake_case(),
35            c.name.as_str().to_shouty_snake_case(),
36            c.ty.as_str().to_camel_case(),
37            c.value
38        ));
39    }
40
41    let mut rustfmt = Command::new("rustfmt")
42        .stdin(Stdio::piped())
43        .stdout(Stdio::piped())
44        .spawn()
45        .unwrap();
46    rustfmt
47        .stdin
48        .take()
49        .unwrap()
50        .write_all(raw.as_bytes())
51        .unwrap();
52    let mut ret = String::new();
53    rustfmt
54        .stdout
55        .take()
56        .unwrap()
57        .read_to_string(&mut ret)
58        .unwrap();
59    let status = rustfmt.wait().unwrap();
60    assert!(status.success());
61    ret
62}
63
64trait Render {
65    fn render(&self, src: &mut String);
66}
67
68impl Render for NamedType {
69    fn render(&self, src: &mut String) {
70        let name = self.name.as_str();
71        match &self.tref {
72            TypeRef::Value(ty) => match &**ty {
73                Type::Record(s) => render_record(src, name, s),
74                Type::Handle(h) => render_handle(src, name, h),
75                Type::Variant(h) => render_variant(src, name, h),
76                Type::List { .. }
77                | Type::Pointer { .. }
78                | Type::ConstPointer { .. }
79                | Type::Builtin { .. } => render_alias(src, name, &self.tref),
80            },
81            TypeRef::Name(_nt) => render_alias(src, name, &self.tref),
82        }
83    }
84}
85
86fn render_record(src: &mut String, name: &str, s: &RecordDatatype) {
87    if let Some(repr) = s.bitflags_repr() {
88        src.push_str(&format!("pub type {} = ", name.to_camel_case()));
89        repr.render(src);
90        src.push(';');
91        for (i, member) in s.members.iter().enumerate() {
92            rustdoc(&member.docs, src);
93            src.push_str(&format!(
94                "pub const {}_{}: {} = 1 << {};\n",
95                name.to_shouty_snake_case(),
96                member.name.as_str().to_shouty_snake_case(),
97                name.to_camel_case(),
98                i,
99            ));
100        }
101        return;
102    }
103    src.push_str("#[repr(C)]\n");
104    if record_contains_union(s) {
105        // Unions can't automatically derive `Debug`.
106        src.push_str("#[derive(Copy, Clone)]\n");
107    } else {
108        src.push_str("#[derive(Copy, Clone, Debug)]\n");
109    }
110    src.push_str(&format!("pub struct {} {{\n", name.to_camel_case()));
111    for member in s.members.iter() {
112        rustdoc(&member.docs, src);
113        src.push_str("pub ");
114        member.name.render(src);
115        src.push_str(": ");
116        member.tref.render(src);
117        src.push_str(",\n");
118    }
119    src.push('}');
120}
121
122fn render_variant(src: &mut String, name: &str, v: &Variant) {
123    if v.cases.iter().all(|c| c.tref.is_none()) {
124        return render_enum_like_variant(src, name, v);
125    }
126    src.push_str("#[repr(C)]\n");
127    src.push_str("#[derive(Copy, Clone)]\n");
128    src.push_str(&format!("pub union {}Union {{\n", name.to_camel_case()));
129    for case in v.cases.iter() {
130        if let Some(ref tref) = case.tref {
131            rustdoc(&case.docs, src);
132            src.push_str("pub ");
133            case.name.render(src);
134            src.push_str(": ");
135            tref.render(src);
136            src.push_str(",\n");
137        }
138    }
139    src.push_str("}\n");
140    src.push_str("#[repr(C)]\n");
141    src.push_str("#[derive(Copy, Clone)]\n");
142    src.push_str(&format!("pub struct {} {{\n", name.to_camel_case()));
143    src.push_str("pub tag: ");
144    v.tag_repr.render(src);
145    src.push_str(",\n");
146    src.push_str(&format!("pub u: {}Union,\n", name.to_camel_case()));
147    src.push_str("}\n");
148}
149
150fn render_enum_like_variant(src: &mut String, name: &str, s: &Variant) {
151    src.push_str("#[repr(transparent)]\n");
152    src.push_str("#[derive(Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]\n");
153    src.push_str(&format!("pub struct {}(", name.to_camel_case()));
154    s.tag_repr.render(src);
155    src.push_str(");\n");
156    for (i, variant) in s.cases.iter().enumerate() {
157        rustdoc(&variant.docs, src);
158        src.push_str(&format!(
159            "pub const {}_{}: {ty} = {ty}({});\n",
160            name.to_shouty_snake_case(),
161            variant.name.as_str().to_shouty_snake_case(),
162            i,
163            ty = name.to_camel_case(),
164        ));
165    }
166    let camel_name = name.to_camel_case();
167
168    src.push_str("impl ");
169    src.push_str(&camel_name);
170    src.push_str("{\n");
171
172    src.push_str("pub const fn raw(&self) -> ");
173    s.tag_repr.render(src);
174    src.push_str("{ self.0 }\n\n");
175
176    src.push_str("pub fn name(&self) -> &'static str {\n");
177    src.push_str("match self.0 {");
178    for (i, variant) in s.cases.iter().enumerate() {
179        src.push_str(&i.to_string());
180        src.push_str(" => \"");
181        src.push_str(&variant.name.as_str().to_shouty_snake_case());
182        src.push_str("\",");
183    }
184    src.push_str("_ => unsafe { core::hint::unreachable_unchecked() },");
185    src.push_str("}\n");
186    src.push_str("}\n");
187
188    src.push_str("pub fn message(&self) -> &'static str {\n");
189    src.push_str("match self.0 {");
190    for (i, variant) in s.cases.iter().enumerate() {
191        src.push_str(&i.to_string());
192        src.push_str(" => \"");
193        src.push_str(variant.docs.trim());
194        src.push_str("\",");
195    }
196    src.push_str("_ => unsafe { core::hint::unreachable_unchecked() },");
197    src.push_str("}\n");
198    src.push_str("}\n");
199
200    src.push_str("}\n");
201
202    src.push_str("impl fmt::Debug for ");
203    src.push_str(&camel_name);
204    src.push_str("{\nfn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n");
205    src.push_str("f.debug_struct(\"");
206    src.push_str(&camel_name);
207    src.push_str("\")");
208    src.push_str(".field(\"code\", &self.0)");
209    src.push_str(".field(\"name\", &self.name())");
210    src.push_str(".field(\"message\", &self.message())");
211    src.push_str(".finish()");
212    src.push_str("}\n");
213    src.push_str("}\n");
214
215    // Auto-synthesize an implementation of the standard `Error` trait for
216    // error-looking types based on their name.
217    //
218    // TODO: should this perhaps be an attribute in the witx file?
219    if name.contains("errno") {
220        src.push_str("impl fmt::Display for ");
221        src.push_str(&camel_name);
222        src.push_str("{\nfn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n");
223        src.push_str("write!(f, \"{} (error {})\", self.name(), self.0)");
224        src.push_str("}\n");
225        src.push_str("}\n");
226        src.push('\n');
227        src.push_str("#[cfg(feature = \"std\")]\n");
228        src.push_str("extern crate std;\n");
229        src.push_str("#[cfg(feature = \"std\")]\n");
230        src.push_str("impl std::error::Error for ");
231        src.push_str(&camel_name);
232        src.push_str("{}\n");
233    }
234}
235
236impl Render for IntRepr {
237    fn render(&self, src: &mut String) {
238        match self {
239            IntRepr::U8 => src.push_str("u8"),
240            IntRepr::U16 => src.push_str("u16"),
241            IntRepr::U32 => src.push_str("u32"),
242            IntRepr::U64 => src.push_str("u64"),
243        }
244    }
245}
246
247fn render_alias(src: &mut String, name: &str, dest: &TypeRef) {
248    src.push_str(&format!("pub type {}", name.to_camel_case()));
249    if let Type::List(_) = &**dest.type_() {
250        src.push_str("<'a>");
251    }
252    src.push_str(" = ");
253
254    // Give `size` special treatment to translate it to `usize` in Rust instead of `u32`. Makes
255    // things a bit nicer for client libraries. We can remove this hack once WASI moves to a
256    // snapshot that uses BuiltinType::Size.
257    if name == "size" {
258        src.push_str("usize");
259    } else {
260        dest.render(src);
261    }
262    src.push(';');
263}
264
265impl Render for TypeRef {
266    fn render(&self, src: &mut String) {
267        match self {
268            TypeRef::Name(t) => {
269                src.push_str(&t.name.as_str().to_camel_case());
270                if let Type::List(_) = &**t.type_() {
271                    src.push_str("<'_>");
272                }
273            }
274            TypeRef::Value(v) => match &**v {
275                Type::Builtin(t) => t.render(src),
276                Type::List(t) => match &**t.type_() {
277                    Type::Builtin(BuiltinType::Char) => src.push_str("&str"),
278                    _ => {
279                        src.push_str("&'a [");
280                        t.render(src);
281                        src.push(']');
282                    }
283                },
284                Type::Pointer(t) => {
285                    src.push_str("*mut ");
286                    t.render(src);
287                }
288                Type::ConstPointer(t) => {
289                    src.push_str("*const ");
290                    t.render(src);
291                }
292                Type::Variant(v) if v.is_bool() => src.push_str("bool"),
293                Type::Variant(v) => match v.as_expected() {
294                    Some((ok, err)) => {
295                        src.push_str("Result<");
296                        match ok {
297                            Some(ty) => ty.render(src),
298                            None => src.push_str("()"),
299                        }
300                        src.push(',');
301                        match err {
302                            Some(ty) => ty.render(src),
303                            None => src.push_str("()"),
304                        }
305                        src.push('>');
306                    }
307                    None => {
308                        panic!("unsupported anonymous variant")
309                    }
310                },
311                Type::Record(r) if r.is_tuple() => {
312                    src.push('(');
313                    for member in r.members.iter() {
314                        member.tref.render(src);
315                        src.push(',');
316                    }
317                    src.push(')');
318                }
319                t => panic!("reference to anonymous {} not possible!", t.kind()),
320            },
321        }
322    }
323}
324
325impl Render for BuiltinType {
326    fn render(&self, src: &mut String) {
327        match self {
328            // A C `char` in Rust we just interpret always as `u8`. It's
329            // technically possible to use `std::os::raw::c_char` but that's
330            // overkill for the purposes that we'll be using this type for.
331            BuiltinType::U8 { lang_c_char: _ } => src.push_str("u8"),
332            BuiltinType::U16 => src.push_str("u16"),
333            BuiltinType::U32 {
334                lang_ptr_size: false,
335            } => src.push_str("u32"),
336            BuiltinType::U32 {
337                lang_ptr_size: true,
338            } => src.push_str("usize"),
339            BuiltinType::U64 => src.push_str("u64"),
340            BuiltinType::S8 => src.push_str("i8"),
341            BuiltinType::S16 => src.push_str("i16"),
342            BuiltinType::S32 => src.push_str("i32"),
343            BuiltinType::S64 => src.push_str("i64"),
344            BuiltinType::F32 => src.push_str("f32"),
345            BuiltinType::F64 => src.push_str("f64"),
346            BuiltinType::Char => src.push_str("char"),
347        }
348    }
349}
350
351impl Render for Module {
352    fn render(&self, src: &mut String) {
353        // wrapper functions
354        for f in self.funcs() {
355            render_highlevel(&f, &self.name, src);
356            src.push_str("\n\n");
357        }
358
359        // raw module
360        let rust_name = self.name.as_str().to_snake_case();
361        src.push_str("pub mod ");
362        src.push_str(&rust_name);
363        src.push_str("{\n");
364        src.push_str("#[link(wasm_import_module =\"");
365        src.push_str(self.name.as_str());
366        src.push_str("\")]\n");
367        src.push_str("extern \"C\" {\n");
368        for f in self.funcs() {
369            f.render(src);
370            src.push('\n');
371        }
372        src.push('}');
373        src.push('}');
374    }
375}
376
377fn render_highlevel(func: &InterfaceFunc, module: &Id, src: &mut String) {
378    let mut rust_name = String::new();
379    func.name.render(&mut rust_name);
380    let rust_name = rust_name.to_snake_case();
381    rustdoc(&func.docs, src);
382    rustdoc_params(&func.params, "Parameters", src);
383    rustdoc_params(&func.results, "Return", src);
384
385    // Render the function and its arguments, and note that the arguments here
386    // are the exact type name arguments as opposed to the pointer/length pair
387    // ones. These functions are unsafe because they work with integer file
388    // descriptors, which are effectively forgeable and danglable raw pointers
389    // into the file descriptor address space.
390    src.push_str("pub unsafe fn ");
391
392    // TODO workout how to handle wasi-ephemeral which introduces multiple
393    // WASI modules into the picture. For now, feature-gate it, and if we're
394    // compiling ephmeral bindings, prefix wrapper syscall with module name.
395    if cfg!(feature = "multi-module") {
396        src.push_str(&[module.as_str().to_snake_case().as_str(), &rust_name].join("_"));
397    } else {
398        src.push_str(to_rust_ident(&rust_name));
399    }
400
401    src.push('(');
402    for param in func.params.iter() {
403        param.name.render(src);
404        src.push_str(": ");
405        param.tref.render(src);
406        src.push(',');
407    }
408    src.push(')');
409
410    match func.results.len() {
411        0 => {}
412        1 => {
413            src.push_str(" -> ");
414            func.results[0].tref.render(src);
415        }
416        _ => {
417            src.push_str(" -> (");
418            for result in func.results.iter() {
419                result.tref.render(src);
420                src.push_str(", ");
421            }
422            src.push(')');
423        }
424    }
425    src.push('{');
426
427    func.call_wasm(
428        module,
429        &mut Rust {
430            src,
431            params: &func.params,
432            block_storage: Vec::new(),
433            blocks: Vec::new(),
434        },
435    );
436
437    src.push('}');
438}
439
440struct Rust<'a> {
441    src: &'a mut String,
442    params: &'a [InterfaceFuncParam],
443    block_storage: Vec<String>,
444    blocks: Vec<String>,
445}
446
447impl Bindgen for Rust<'_> {
448    type Operand = String;
449
450    fn push_block(&mut self) {
451        let prev = std::mem::take(self.src);
452        self.block_storage.push(prev);
453    }
454
455    fn finish_block(&mut self, operand: Option<String>) {
456        let to_restore = self.block_storage.pop().unwrap();
457        let src = mem::replace(self.src, to_restore);
458        match operand {
459            None => {
460                assert!(src.is_empty());
461                self.blocks.push("()".to_string());
462            }
463            Some(s) => {
464                if src.is_empty() {
465                    self.blocks.push(s);
466                } else {
467                    self.blocks.push(format!("{{ {}; {} }}", src, s));
468                }
469            }
470        }
471    }
472
473    fn allocate_space(&mut self, n: usize, ty: &witx::NamedType) {
474        self.src
475            .push_str(&format!("let mut rp{} = MaybeUninit::<", n));
476        self.src.push_str(&ty.name.as_str().to_camel_case());
477        self.src.push_str(">::uninit();");
478    }
479
480    fn emit(
481        &mut self,
482        inst: &Instruction<'_>,
483        operands: &mut Vec<String>,
484        results: &mut Vec<String>,
485    ) {
486        let mut top_as = |cvt: &str| {
487            let mut s = operands.pop().unwrap();
488            s.push_str(" as ");
489            s.push_str(cvt);
490            results.push(s);
491        };
492
493        match inst {
494            Instruction::GetArg { nth } => {
495                let mut s = String::new();
496                self.params[*nth].name.render(&mut s);
497                results.push(s);
498            }
499            Instruction::AddrOf => {
500                results.push(format!("&{} as *const _ as i32", operands[0]));
501            }
502            Instruction::I64FromBitflags { .. } | Instruction::I64FromU64 => top_as("i64"),
503            Instruction::I32FromPointer
504            | Instruction::I32FromConstPointer
505            | Instruction::I32FromHandle { .. }
506            | Instruction::I32FromUsize
507            | Instruction::I32FromChar
508            | Instruction::I32FromU8
509            | Instruction::I32FromS8
510            | Instruction::I32FromChar8
511            | Instruction::I32FromU16
512            | Instruction::I32FromS16
513            | Instruction::I32FromU32
514            | Instruction::I32FromBitflags { .. } => top_as("i32"),
515
516            Instruction::EnumLower { .. } => {
517                results.push(format!("{}.0 as i32", operands[0]));
518            }
519
520            Instruction::F32FromIf32
521            | Instruction::F64FromIf64
522            | Instruction::If32FromF32
523            | Instruction::If64FromF64
524            | Instruction::I64FromS64
525            | Instruction::I32FromS32 => {
526                results.push(operands.pop().unwrap());
527            }
528            Instruction::ListPointerLength => {
529                let list = operands.pop().unwrap();
530                results.push(format!("{}.as_ptr() as i32", list));
531                results.push(format!("{}.len() as i32", list));
532            }
533            Instruction::S8FromI32 => top_as("i8"),
534            Instruction::Char8FromI32 | Instruction::U8FromI32 => top_as("u8"),
535            Instruction::S16FromI32 => top_as("i16"),
536            Instruction::U16FromI32 => top_as("u16"),
537            Instruction::S32FromI32 => {}
538            Instruction::U32FromI32 => top_as("u32"),
539            Instruction::S64FromI64 => {}
540            Instruction::U64FromI64 => top_as("u64"),
541            Instruction::UsizeFromI32 => top_as("usize"),
542            Instruction::HandleFromI32 { .. } => top_as("u32"),
543            Instruction::PointerFromI32 { .. } => top_as("*mut _"),
544            Instruction::ConstPointerFromI32 { .. } => top_as("*const _"),
545            Instruction::BitflagsFromI32 { .. } => unimplemented!(),
546            Instruction::BitflagsFromI64 { .. } => unimplemented!(),
547
548            Instruction::ReturnPointerGet { n } => {
549                results.push(format!("rp{}.as_mut_ptr() as i32", n));
550            }
551
552            Instruction::Load { ty } => {
553                let mut s = format!("core::ptr::read({} as *const ", &operands[0]);
554                s.push_str(&ty.name.as_str().to_camel_case());
555                s.push(')');
556                results.push(s);
557            }
558
559            Instruction::ReuseReturn => {
560                results.push("ret".to_string());
561            }
562
563            Instruction::TupleLift { .. } => {
564                let value = format!("({})", operands.join(", "));
565                results.push(value);
566            }
567
568            Instruction::ResultLift => {
569                let err = self.blocks.pop().unwrap();
570                let ok = self.blocks.pop().unwrap();
571                let mut result = format!("match {} {{", operands[0]);
572                result.push_str("0 => Ok(");
573                result.push_str(&ok);
574                result.push_str("),");
575                result.push_str("_ => Err(");
576                result.push_str(&err);
577                result.push_str("),");
578                result.push('}');
579                results.push(result);
580            }
581
582            Instruction::EnumLift { ty } => {
583                let mut result = ty.name.as_str().to_camel_case();
584                result.push('(');
585                result.push_str(&operands[0]);
586                result.push_str(" as ");
587                match &**ty.type_() {
588                    Type::Variant(v) => v.tag_repr.render(&mut result),
589                    _ => unreachable!(),
590                }
591                result.push(')');
592                results.push(result);
593            }
594
595            Instruction::CharFromI32 => unimplemented!(),
596
597            Instruction::CallWasm {
598                module,
599                name,
600                params: _,
601                results: func_results,
602            } => {
603                assert!(func_results.len() < 2);
604                if !func_results.is_empty() {
605                    self.src.push_str("let ret = ");
606                    results.push("ret".to_string());
607                }
608                self.src.push_str(&module.to_snake_case());
609                self.src.push_str("::");
610                self.src.push_str(to_rust_ident(&name.to_snake_case()));
611                self.src.push('(');
612                self.src.push_str(&operands.join(", "));
613                self.src.push_str(");");
614            }
615
616            Instruction::Return { amt: 0 } => {}
617            Instruction::Return { amt: 1 } => {
618                self.src.push_str(&operands[0]);
619            }
620            Instruction::Return { .. } => {
621                self.src.push('(');
622                self.src.push_str(&operands.join(", "));
623                self.src.push(')');
624            }
625
626            Instruction::Store { .. }
627            | Instruction::ListFromPointerLength { .. }
628            | Instruction::CallInterface { .. }
629            | Instruction::ResultLower { .. }
630            | Instruction::TupleLower { .. }
631            | Instruction::VariantPayload => unimplemented!(),
632        }
633    }
634}
635
636impl Render for InterfaceFunc {
637    fn render(&self, src: &mut String) {
638        rustdoc(&self.docs, src);
639        if self.name.as_str() != self.name.as_str().to_snake_case() {
640            src.push_str("#[link_name = \"");
641            src.push_str(self.name.as_str());
642            src.push_str("\"]\n");
643        }
644        src.push_str("pub fn ");
645        let mut name = String::new();
646        self.name.render(&mut name);
647        src.push_str(to_rust_ident(&name.to_snake_case()));
648
649        let (params, results) = self.wasm_signature();
650        assert!(results.len() <= 1);
651        src.push('(');
652        for (i, param) in params.iter().enumerate() {
653            src.push_str(&format!("arg{}: ", i));
654            param.render(src);
655            src.push(',');
656        }
657        src.push(')');
658
659        if self.noreturn {
660            src.push_str(" -> !");
661        } else if let Some(result) = results.get(0) {
662            src.push_str(" -> ");
663            result.render(src);
664        }
665        src.push(';');
666    }
667}
668
669fn to_rust_ident(name: &str) -> &str {
670    match name {
671        "in" => "in_",
672        "type" => "type_",
673        "yield" => "yield_",
674        s => s,
675    }
676}
677
678impl Render for Id {
679    fn render(&self, src: &mut String) {
680        src.push_str(to_rust_ident(self.as_str()))
681    }
682}
683
684impl Render for WasmType {
685    fn render(&self, src: &mut String) {
686        match self {
687            WasmType::I32 => src.push_str("i32"),
688            WasmType::I64 => src.push_str("i64"),
689            WasmType::F32 => src.push_str("f32"),
690            WasmType::F64 => src.push_str("f64"),
691        }
692    }
693}
694
695fn render_handle(src: &mut String, name: &str, _h: &HandleDatatype) {
696    src.push_str(&format!("pub type {} = u32;", name.to_camel_case()));
697}
698
699fn rustdoc(docs: &str, dst: &mut String) {
700    if docs.trim().is_empty() {
701        return;
702    }
703    for line in docs.lines() {
704        dst.push_str("/// ");
705        dst.push_str(line);
706        dst.push('\n');
707    }
708}
709
710fn rustdoc_params(docs: &[InterfaceFuncParam], header: &str, dst: &mut String) {
711    let docs = docs
712        .iter()
713        .filter(|param| !param.docs.trim().is_empty())
714        .collect::<Vec<_>>();
715    if docs.is_empty() {
716        return;
717    }
718
719    dst.push_str("///\n");
720    dst.push_str("/// ## ");
721    dst.push_str(header);
722    dst.push('\n');
723    dst.push_str("///\n");
724
725    for param in docs {
726        for (i, line) in param.docs.lines().enumerate() {
727            dst.push_str("/// ");
728            // Currently wasi only has at most one return value, so there's no
729            // need to indent it or name it.
730            if header != "Return" {
731                if i == 0 {
732                    dst.push_str("* `");
733                    param.name.render(dst);
734                    dst.push_str("` - ");
735                } else {
736                    dst.push_str("  ");
737                }
738            }
739            dst.push_str(line);
740            dst.push('\n');
741        }
742    }
743}
744
745fn record_contains_union(s: &RecordDatatype) -> bool {
746    s.members
747        .iter()
748        .any(|member| type_contains_union(member.tref.type_()))
749}
750
751fn type_contains_union(ty: &Type) -> bool {
752    match ty {
753        Type::Variant(c) => c.cases.iter().any(|c| c.tref.is_some()),
754        Type::List(tref) => type_contains_union(tref.type_()),
755        Type::Record(st) => record_contains_union(st),
756        _ => false,
757    }
758}