use std::fmt::Write as _;
use formalang::ast::PrimitiveType;
use formalang::ir::{IrEnum, IrFunction, IrModule, IrStruct, ResolvedType};
use thiserror::Error;
use wit_parser::Resolve;
use crate::compound::Compound;
use crate::ident::kebab_case;
use crate::survey::PublicSurface;
use crate::types::TypeMapError;
pub const PACKAGE: &str = "formawasm:generated";
pub const WORLD_NAME: &str = "component";
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum WitEmitError {
#[error(transparent)]
TypeMap(#[from] TypeMapError),
#[error("parameter '{param}' on function '{function}' has type Never")]
NeverParam {
function: String,
param: String,
},
#[error("parameter '{param}' on function '{function}' is missing a type annotation")]
MissingParamType {
function: String,
param: String,
},
#[error("PublicSurface references function index {index} but module has only {len}")]
ExportOutOfRange {
index: u32,
len: usize,
},
#[error("emitted WIT failed to parse: {reason}")]
Invalid {
reason: String,
},
}
pub fn emit_wit(module: &IrModule, surface: &PublicSurface) -> Result<String, WitEmitError> {
let mut out = String::new();
writeln!(out, "package {PACKAGE};").map_err(invalid_format)?;
writeln!(out).map_err(invalid_format)?;
let type_names = write_types_interface(&mut out, module, surface)?;
writeln!(out, "world {WORLD_NAME} {{").map_err(invalid_format)?;
if !type_names.is_empty() {
write!(out, " use types.{{").map_err(invalid_format)?;
for (i, n) in type_names.iter().enumerate() {
if i > 0 {
out.push_str(", ");
}
out.push_str(n);
}
writeln!(out, "}};").map_err(invalid_format)?;
}
for &fid in &surface.imports {
let f = module
.functions
.get(fid.0 as usize)
.ok_or(WitEmitError::ExportOutOfRange {
index: fid.0,
len: module.functions.len(),
})?;
write_import(&mut out, f, module)?;
}
write_extern_impl_imports(&mut out, module)?;
let mut emitted_export_names: std::collections::HashSet<String> =
std::collections::HashSet::new();
for &fid in &surface.exports {
let f = module
.functions
.get(fid.0 as usize)
.ok_or(WitEmitError::ExportOutOfRange {
index: fid.0,
len: module.functions.len(),
})?;
if !function_signature_crosses_boundary(f, module) {
continue;
}
let name = kebab_case(&f.name);
if !emitted_export_names.insert(name) {
continue;
}
write_export(&mut out, f, module)?;
}
writeln!(out, "}}").map_err(invalid_format)?;
parse_round_trip(&out)?;
Ok(out)
}
fn write_types_interface(
out: &mut String,
module: &IrModule,
surface: &PublicSurface,
) -> Result<Vec<String>, WitEmitError> {
let mut type_names: Vec<String> = Vec::new();
let any_types = !surface.exported_structs.is_empty() || !surface.exported_enums.is_empty();
if !any_types {
return Ok(type_names);
}
writeln!(out, "interface types {{").map_err(invalid_format)?;
for &sid in &surface.exported_structs {
let s = module
.structs
.get(sid.0 as usize)
.ok_or(WitEmitError::ExportOutOfRange {
index: sid.0,
len: module.structs.len(),
})?;
write_record(out, s, module)?;
type_names.push(kebab_case(&s.name));
}
for &eid in &surface.exported_enums {
let e = module
.enums
.get(eid.0 as usize)
.ok_or(WitEmitError::ExportOutOfRange {
index: eid.0,
len: module.enums.len(),
})?;
write_variant(out, e, module)?;
type_names.push(kebab_case(&e.name));
}
writeln!(out, "}}").map_err(invalid_format)?;
writeln!(out).map_err(invalid_format)?;
Ok(type_names)
}
fn write_extern_impl_imports(out: &mut String, module: &IrModule) -> Result<(), WitEmitError> {
for imp in &module.impls {
if !imp.is_extern {
continue;
}
let is_prelude_target = match imp.target {
formalang::ir::ImplTarget::Struct(sid) => {
Some(sid) == module.prelude_array_id()
|| Some(sid) == module.prelude_dictionary_id()
|| Some(sid) == module.prelude_range_id()
}
formalang::ir::ImplTarget::Enum(eid) => Some(eid) == module.prelude_optional_id(),
formalang::ir::ImplTarget::Primitive(_) => true,
};
if is_prelude_target {
continue;
}
let target_name: Option<&str> = match &imp.target {
formalang::ir::ImplTarget::Struct(sid) => {
module.structs.get(sid.0 as usize).map(|s| s.name.as_str())
}
formalang::ir::ImplTarget::Enum(eid) => {
module.enums.get(eid.0 as usize).map(|e| e.name.as_str())
}
formalang::ir::ImplTarget::Primitive(_) => None,
};
let Some(target_name) = target_name else {
continue;
};
for m in &imp.functions {
write_extern_method_import(out, target_name, m, module, imp.target)?;
}
}
Ok(())
}
fn write_record(out: &mut String, s: &IrStruct, module: &IrModule) -> Result<(), WitEmitError> {
writeln!(out, " record {} {{", kebab_case(&s.name)).map_err(invalid_format)?;
for f in &s.fields {
let wit_ty = resolved_wit_type(&f.ty, module)?.ok_or_else(|| WitEmitError::NeverParam {
function: format!("record {}", s.name),
param: f.name.clone(),
})?;
writeln!(out, " {}: {wit_ty},", kebab_case(&f.name)).map_err(invalid_format)?;
}
writeln!(out, " }}").map_err(invalid_format)?;
Ok(())
}
fn write_variant(out: &mut String, e: &IrEnum, module: &IrModule) -> Result<(), WitEmitError> {
writeln!(out, " variant {} {{", kebab_case(&e.name)).map_err(invalid_format)?;
for v in &e.variants {
let arm_name = kebab_case(&v.name);
if v.fields.is_empty() {
writeln!(out, " {arm_name},").map_err(invalid_format)?;
continue;
}
if v.fields.len() == 1 {
let f = v.fields.first().ok_or_else(|| WitEmitError::NeverParam {
function: format!("variant {}", e.name),
param: "(0-th field)".to_owned(),
})?;
let wit_ty = variant_field_wit_type(&e.name, f, module)?;
writeln!(out, " {arm_name}({wit_ty}),").map_err(invalid_format)?;
continue;
}
let mut tuple = String::from("tuple<");
for (i, f) in v.fields.iter().enumerate() {
if i > 0 {
tuple.push_str(", ");
}
let wit_ty = variant_field_wit_type(&e.name, f, module)?;
tuple.push_str(&wit_ty);
}
tuple.push('>');
writeln!(out, " {arm_name}({tuple}),").map_err(invalid_format)?;
}
writeln!(out, " }}").map_err(invalid_format)?;
Ok(())
}
fn variant_field_wit_type(
enum_name: &str,
f: &formalang::ir::IrField,
module: &IrModule,
) -> Result<String, WitEmitError> {
resolved_wit_type(&f.ty, module)?.ok_or_else(|| WitEmitError::NeverParam {
function: format!("variant {enum_name}"),
param: f.name.clone(),
})
}
fn write_export(out: &mut String, f: &IrFunction, module: &IrModule) -> Result<(), WitEmitError> {
write_world_func(out, f, "export", module)
}
fn write_import(out: &mut String, f: &IrFunction, module: &IrModule) -> Result<(), WitEmitError> {
write_world_func(out, f, "import", module)
}
fn write_extern_method_import(
out: &mut String,
target_name: &str,
m: &IrFunction,
module: &IrModule,
target: formalang::ir::ImplTarget,
) -> Result<(), WitEmitError> {
let import_name = format!("{}-{}", kebab_case(target_name), kebab_case(&m.name));
let self_ty: Option<String> = match target {
formalang::ir::ImplTarget::Struct(sid) => {
let Some(s) = module.structs.get(sid.0 as usize) else {
return Ok(()); };
if s.fields.is_empty() {
None
} else {
Some(kebab_case(&s.name))
}
}
formalang::ir::ImplTarget::Enum(eid) => module
.enums
.get(eid.0 as usize)
.map(|e| kebab_case(&e.name)),
formalang::ir::ImplTarget::Primitive(_) => return Ok(()),
};
write!(out, " import {import_name}: func(").map_err(invalid_format)?;
let mut wrote_param = if let Some(ty_name) = &self_ty {
write!(out, "self: {ty_name}").map_err(invalid_format)?;
true
} else {
false
};
for p in m.params.iter().skip(1) {
if wrote_param {
out.push_str(", ");
}
let ty =
p.ty.as_ref()
.ok_or_else(|| WitEmitError::MissingParamType {
function: import_name.clone(),
param: p.name.clone(),
})?;
let wit_ty = resolved_wit_type(ty, module)?.ok_or_else(|| WitEmitError::NeverParam {
function: import_name.clone(),
param: p.name.clone(),
})?;
write!(out, "{}: {wit_ty}", kebab_case(&p.name)).map_err(invalid_format)?;
wrote_param = true;
}
write!(out, ")").map_err(invalid_format)?;
if let Some(ret) = m.return_type.as_ref()
&& let Some(wit_ty) = resolved_wit_type(ret, module)?
{
write!(out, " -> {wit_ty}").map_err(invalid_format)?;
}
writeln!(out, ";").map_err(invalid_format)?;
Ok(())
}
fn write_world_func(
out: &mut String,
f: &IrFunction,
kind: &str,
module: &IrModule,
) -> Result<(), WitEmitError> {
write!(out, " {kind} {}: func(", kebab_case(&f.name)).map_err(invalid_format)?;
for (i, param) in f.params.iter().enumerate() {
if i > 0 {
out.push_str(", ");
}
let ty = param
.ty
.as_ref()
.ok_or_else(|| WitEmitError::MissingParamType {
function: f.name.clone(),
param: param.name.clone(),
})?;
let wit_ty = resolved_wit_type(ty, module)?.ok_or_else(|| WitEmitError::NeverParam {
function: f.name.clone(),
param: param.name.clone(),
})?;
write!(out, "{}: {wit_ty}", kebab_case(¶m.name)).map_err(invalid_format)?;
}
write!(out, ")").map_err(invalid_format)?;
if let Some(ret) = f.return_type.as_ref()
&& let Some(wit_ty) = resolved_wit_type(ret, module)?
{
write!(out, " -> {wit_ty}").map_err(invalid_format)?;
}
writeln!(out, ";").map_err(invalid_format)?;
Ok(())
}
fn resolved_wit_type(ty: &ResolvedType, module: &IrModule) -> Result<Option<String>, WitEmitError> {
if let Some(name) = resolved_compound_wit_type(ty, module)? {
return Ok(Some(name));
}
match ty {
ResolvedType::Primitive(p) => primitive_wit_type(*p),
ResolvedType::Struct(id) => resolved_named_struct_wit(*id, module),
ResolvedType::Enum(id) => resolved_named_enum_wit(*id, module),
ResolvedType::Tuple(fields) => {
let mut out = String::from("tuple<");
for (i, (_name, inner)) in fields.iter().enumerate() {
if i > 0 {
out.push_str(", ");
}
let inner_wit = resolved_wit_type(inner, module)?.ok_or_else(|| {
WitEmitError::TypeMap(TypeMapError::NotYetSupported {
kind: format!("Never inside tuple at position {i}"),
})
})?;
out.push_str(&inner_wit);
}
out.push('>');
Ok(Some(out))
}
ResolvedType::Trait(_)
| ResolvedType::Generic { .. }
| ResolvedType::TypeParam(_)
| ResolvedType::External { .. }
| ResolvedType::Closure { .. }
| ResolvedType::Error => Err(WitEmitError::TypeMap(TypeMapError::NotYetSupported {
kind: variant_tag(ty, module),
})),
}
}
fn resolved_compound_wit_type(
ty: &ResolvedType,
module: &IrModule,
) -> Result<Option<String>, WitEmitError> {
match Compound::of(ty, module) {
Compound::Array(elem) => {
let inner = resolved_wit_type(elem, module)?.ok_or_else(|| {
WitEmitError::TypeMap(TypeMapError::NotYetSupported {
kind: "Array<Never>".to_owned(),
})
})?;
Ok(Some(format!("list<{inner}>")))
}
Compound::Optional(inner) => {
let inner_name = resolved_wit_type(inner, module)?.ok_or_else(|| {
WitEmitError::TypeMap(TypeMapError::NotYetSupported {
kind: "Optional<Never>".to_owned(),
})
})?;
Ok(Some(format!("option<{inner_name}>")))
}
Compound::Dictionary { key, value } => {
let key_name = resolved_wit_type(key, module)?.ok_or_else(|| {
WitEmitError::TypeMap(TypeMapError::NotYetSupported {
kind: "Dictionary<Never, _>".to_owned(),
})
})?;
let value_name = resolved_wit_type(value, module)?.ok_or_else(|| {
WitEmitError::TypeMap(TypeMapError::NotYetSupported {
kind: "Dictionary<_, Never>".to_owned(),
})
})?;
Ok(Some(format!("list<tuple<{key_name}, {value_name}>>")))
}
Compound::Range(_) | Compound::None => Ok(None),
}
}
fn resolved_named_struct_wit(
id: formalang::ir::StructId,
module: &IrModule,
) -> Result<Option<String>, WitEmitError> {
let s = module.get_struct(id).ok_or_else(|| {
WitEmitError::TypeMap(TypeMapError::NotYetSupported {
kind: format!("Struct(out-of-range #{})", id.0),
})
})?;
if s.fields.is_empty() {
return Err(WitEmitError::TypeMap(TypeMapError::NotYetSupported {
kind: format!(
"Struct({}) is empty; wit-component rejects empty records, so the surrounding function cannot cross the WIT boundary",
s.name
),
}));
}
if !matches!(s.visibility, formalang::ast::Visibility::Public) {
return Err(WitEmitError::TypeMap(TypeMapError::NotYetSupported {
kind: format!(
"Struct({}) is private; private types can't cross the WIT boundary",
s.name
),
}));
}
Ok(Some(kebab_case(&s.name)))
}
fn resolved_named_enum_wit(
id: formalang::ir::EnumId,
module: &IrModule,
) -> Result<Option<String>, WitEmitError> {
let e = module.get_enum(id).ok_or_else(|| {
WitEmitError::TypeMap(TypeMapError::NotYetSupported {
kind: format!("Enum(out-of-range #{})", id.0),
})
})?;
if !matches!(e.visibility, formalang::ast::Visibility::Public) {
return Err(WitEmitError::TypeMap(TypeMapError::NotYetSupported {
kind: format!(
"Enum({}) is private; private types can't cross the WIT boundary",
e.name
),
}));
}
Ok(Some(kebab_case(&e.name)))
}
fn primitive_wit_type(p: PrimitiveType) -> Result<Option<String>, WitEmitError> {
match p {
PrimitiveType::I32 => Ok(Some("s32".to_owned())),
PrimitiveType::I64 => Ok(Some("s64".to_owned())),
PrimitiveType::F32 => Ok(Some("f32".to_owned())),
PrimitiveType::F64 => Ok(Some("f64".to_owned())),
PrimitiveType::Boolean => Ok(Some("bool".to_owned())),
PrimitiveType::Never => Ok(None),
PrimitiveType::String | PrimitiveType::Path | PrimitiveType::Regex => {
Ok(Some("string".to_owned()))
}
_ => Err(WitEmitError::TypeMap(TypeMapError::NotYetSupported {
kind: format!("{p:?}"),
})),
}
}
fn function_signature_crosses_boundary(f: &IrFunction, module: &IrModule) -> bool {
for p in &f.params {
let Some(ty) = p.ty.as_ref() else {
return false;
};
if resolved_wit_type(ty, module).is_err() {
return false;
}
}
if let Some(ret) = f.return_type.as_ref()
&& resolved_wit_type(ret, module).is_err()
{
return false;
}
true
}
fn variant_tag(ty: &ResolvedType, module: &IrModule) -> String {
match Compound::of(ty, module) {
Compound::Array(_) => return "Array<T>".to_owned(),
Compound::Range(_) => return "Range<T>".to_owned(),
Compound::Optional(_) => return "Optional<T>".to_owned(),
Compound::Dictionary { .. } => return "Dictionary<K, V>".to_owned(),
Compound::None => {}
}
match ty {
ResolvedType::Primitive(p) => format!("{p:?}"),
ResolvedType::Struct(_) => "Struct".to_owned(),
ResolvedType::Trait(_) => "Trait".to_owned(),
ResolvedType::Enum(_) => "Enum".to_owned(),
ResolvedType::Tuple(_) => "Tuple".to_owned(),
ResolvedType::Generic { .. } => "Generic".to_owned(),
ResolvedType::TypeParam(name) => format!("TypeParam({name})"),
ResolvedType::External { name, .. } => format!(
"External({name}) — should have been inlined by upstream MonomorphisePass; reaching the backend means an upstream invariant violation"
),
ResolvedType::Closure { .. } => "Closure".to_owned(),
ResolvedType::Error => "Error".to_owned(),
}
}
fn parse_round_trip(wit: &str) -> Result<(), WitEmitError> {
let mut resolve = Resolve::default();
let pkg = resolve
.push_str("formawasm-generated.wit", wit)
.map_err(|e| WitEmitError::Invalid {
reason: format!("{e:#}"),
})?;
resolve
.select_world(&[pkg], Some(WORLD_NAME))
.map_err(|e| WitEmitError::Invalid {
reason: format!("{e:#}"),
})?;
Ok(())
}
fn invalid_format(e: std::fmt::Error) -> WitEmitError {
WitEmitError::Invalid {
reason: e.to_string(),
}
}