Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion cmd/gravity/src/codegen/exports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ impl<'a> ExportGenerator<'a> {
.params
.iter()
.map(
|Param { name, ty, .. }| match crate::resolve_type(ty, self.config.resolve) {
|Param { name, ty, .. }| match crate::resolve_param_type(ty, self.config.resolve) {
GoType::ValueOrOk(t) => (GoIdentifier::local(name), *t),
t => (GoIdentifier::local(name), t),
},
Expand Down
412 changes: 228 additions & 184 deletions cmd/gravity/src/codegen/func.rs

Large diffs are not rendered by default.

105 changes: 73 additions & 32 deletions cmd/gravity/src/codegen/imports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use genco::prelude::*;
use wit_bindgen_core::{
abi::{AbiVariant, LiftLower},
wit_parser::{
Function, InterfaceId, Param, Resolve, SizeAlign, Type, TypeDefKind, TypeId, World,
Case, Function, InterfaceId, Param, Resolve, SizeAlign, Type, TypeDefKind, TypeId, World,
WorldItem,
},
};
Expand All @@ -13,15 +13,15 @@ use crate::{
codegen::{
func::Func,
ir::{
AnalyzedFunction, AnalyzedImports, AnalyzedInterface, AnalyzedType, InterfaceMethod,
Parameter, TypeDefinition, WitReturn,
AnalyzedFunction, AnalyzedImports, AnalyzedInterface, AnalyzedType, CaseDispatch,
InterfaceMethod, Parameter, TypeDefinition, VariantCase, WitReturn,
},
},
go::{
imports::{CONTEXT_CONTEXT, WAZERO_API_MODULE},
GoIdentifier, GoResult, GoType,
},
resolve_type, resolve_wasm_type,
resolve_param_type, resolve_type, resolve_wasm_type,
};

/// Analyzer for imports - only does analysis, no code generation
Expand Down Expand Up @@ -120,7 +120,7 @@ impl<'a> ImportAnalyzer<'a> {
.iter()
.map(|Param { name, ty, .. }| Parameter {
name: GoIdentifier::private(name),
go_type: resolve_type(ty, self.resolve),
go_type: resolve_param_type(ty, self.resolve),
wit_type: *ty,
})
.collect();
Expand All @@ -141,25 +141,49 @@ impl<'a> ImportAnalyzer<'a> {

fn analyze_type(&self, type_id: TypeId) -> Option<AnalyzedType> {
let type_def = &self.resolve.types[type_id];
let type_name = type_def.name.as_ref().expect("type missing name");

let go_type_name = GoIdentifier::public(type_name);
let definition = self.analyze_type_definition(&type_def.kind);
let qualified = crate::qualified_type_name(type_id, self.resolve);
let go_type_name = GoIdentifier::public(&qualified);
// Variants live here (not in `analyze_type_definition`) because
// their case wrapper names need the qualified variant name.
let definition = match &type_def.kind {
TypeDefKind::Variant(variant) => Some(TypeDefinition::Variant {
cases: variant
.cases
.iter()
.map(|case| self.analyze_variant_case(&qualified, case))
.collect(),
}),
kind => self.analyze_type_definition(kind),
};

definition.map(|definition| AnalyzedType {
name: type_name.clone(),
name: qualified,
go_type_name,
definition,
})
}

/// Analyze a type definition and return an intermediate representation ready for
/// codegen.
///
/// Returns `None` if the kind is just a `TypeDefKind::Type(Type::Id)`, because this
/// is probably a reference to an imported type that we have already analyzed.
///
/// TODO: we should probably instead resolve and return type and dedup elsewhere.
fn analyze_variant_case(&self, variant_name: &str, case: &Case) -> VariantCase {
let payload = case.ty.as_ref().map(|t| resolve_type(t, self.resolve));
let dispatch = match crate::case_dispatch_kind(case, self.resolve) {
crate::CaseDispatchKind::DirectRecord => CaseDispatch::DirectRecord {
record_type: payload
.clone()
.expect("DirectRecord case has a payload"),
},
crate::CaseDispatchKind::Wrapped => CaseDispatch::Wrapped {
wrapper_name: GoIdentifier::public(format!("{variant_name}-{}", case.name)),
},
};
VariantCase {
name: case.name.clone(),
payload,
dispatch,
}
}

/// Analyze a type definition. Returns `None` for `Type::Id` aliases
/// that just re-export an already-analyzed type.
fn analyze_type_definition(&self, kind: &TypeDefKind) -> Option<TypeDefinition> {
Some(match kind {
TypeDefKind::Record(record) => TypeDefinition::Record {
Expand All @@ -177,18 +201,9 @@ impl<'a> ImportAnalyzer<'a> {
TypeDefKind::Enum(enum_def) => TypeDefinition::Enum {
cases: enum_def.cases.iter().map(|c| c.name.clone()).collect(),
},
TypeDefKind::Variant(variant) => TypeDefinition::Variant {
cases: variant
.cases
.iter()
.map(|case| {
(
case.name.clone(),
case.ty.as_ref().map(|t| resolve_type(t, self.resolve)),
)
})
.collect(),
},
TypeDefKind::Variant(_) => unreachable!(
"Variant analysis is handled in `analyze_type` where the qualified name is in scope"
),
TypeDefKind::Type(Type::Id(_)) => {
// TODO(#4): Only skip this if we have already generated the type
return None;
Expand Down Expand Up @@ -234,7 +249,7 @@ impl<'a> ImportAnalyzer<'a> {
.iter()
.map(|Param { name, ty, .. }| Parameter {
name: GoIdentifier::private(name),
go_type: resolve_type(ty, self.resolve),
go_type: resolve_param_type(ty, self.resolve),
wit_type: *ty,
})
.collect();
Expand Down Expand Up @@ -398,10 +413,33 @@ impl<'a> ImportCodeGenerator<'a> {
// Primitive type: $(typ.name)
}
}
TypeDefinition::Variant { .. } => {
TypeDefinition::Variant { cases } => {
let variant_interface = &typ.go_type_name;
let marker_method =
&GoIdentifier::private(format!("is-{}", &typ.name));
let case_definitions = cases.iter().map(|case| match &case.dispatch {
CaseDispatch::DirectRecord { record_type } => quote! {
$['\n']
func ($record_type) $marker_method() {}
},
CaseDispatch::Wrapped { wrapper_name } => {
let payload_field = case.payload.as_ref().map(|p| quote!(Value $p));
quote! {
$['\n']
type $wrapper_name struct {
$(if let Some(field) = payload_field => $field)
}
$['\n']
func ($wrapper_name) $marker_method() {}
}
}
});
quote_in! { *tokens =>
$['\n']
// Variant type: $(typ.name) (TODO: implement)
type $variant_interface interface {
$marker_method()
}
$(for def in case_definitions => $def)
}
}
}
Expand Down Expand Up @@ -1199,6 +1237,9 @@ mod tests {
assert_eq!(interface.types.len(), 1);

let analyzed_type = &interface.types[0];
// Interface-scoped types are qualified only when their bare name
// would collide with another concrete type in the same world. The
// test world's `foo` is unique, so it stays flat.
assert_eq!(analyzed_type.name, "foo");
println!("Analyzed type definition: {:?}", analyzed_type.definition);

Expand Down
29 changes: 25 additions & 4 deletions cmd/gravity/src/codegen/ir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,8 @@ pub struct AnalyzedType {
pub enum TypeDefinition {
/// A struct-like type with named fields
Record { fields: Vec<(GoIdentifier, GoType)> },
/// A union-like type with multiple cases, each optionally carrying data
Variant {
cases: Vec<(String, Option<GoType>)>,
},
/// A union-like type with multiple cases, each optionally carrying data.
Variant { cases: Vec<VariantCase> },
/// A simple enumeration with named constants
Enum { cases: Vec<String> },
/// A type alias that wraps another type
Expand All @@ -125,6 +123,29 @@ pub enum TypeDefinition {
Primitive,
}

#[derive(Debug, Clone)]
pub struct VariantCase {
pub name: String,
/// `None` for unit cases.
pub payload: Option<GoType>,
pub dispatch: CaseDispatch,
}

/// How a variant case is represented in Go. See `crate::CaseDispatchKind`
/// for the underlying decision; this enum carries the resolved Go names so
/// they aren't re-derived at emission time.
#[derive(Debug, Clone)]
pub enum CaseDispatch {
/// WIT shorthand `case(case)`: the payload record itself implements
/// the variant's marker interface, so callers construct
/// `MyRecord{...}` directly.
DirectRecord { record_type: GoType },
/// Dedicated `{VariantName}{CaseName}` wrapper struct with an optional
/// `Value` field. Callers construct `Wrapper{Value: payload}` (or
/// `Wrapper{}` for unit cases) and read the payload via `.Value`.
Wrapped { wrapper_name: GoIdentifier },
}

/// An analyzed WIT function.
#[derive(Debug, Clone)]
pub struct AnalyzedFunction {
Expand Down
18 changes: 14 additions & 4 deletions cmd/gravity/src/go/type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ pub enum GoType {
Slice(Box<GoType>),
/// Multi-return type (for functions returning arbitrary multiple values)
// MultiReturn(Vec<GoType>),
/// Pointer to another type. Used as the canonical Go representation of
/// `option<T>` so the same lowering composes in every position (params,
/// return values, record fields, list elements). `nil` is `none`,
/// `&value` is `some`.
Pointer(Box<GoType>),
/// User-defined type (records, enums, type aliases)
UserDefined(String),
/// Represents no value/void
Expand Down Expand Up @@ -93,6 +98,11 @@ impl GoType {
// Complex types need cleanup if their inner types do
GoType::ValueOrOk(inner) => inner.needs_cleanup(),

// Pointer (representing option<T>) needs cleanup only when its
// inner type does. Strings and slices behind a pointer still own
// memory the guest allocated.
GoType::Pointer(inner) => inner.needs_cleanup(),

// The inner type of `Err` is always a String so it requires cleanup
// TODO(#91): Store the error type to check both inner types.
GoType::ValueOrError(_) => true,
Expand Down Expand Up @@ -162,10 +172,10 @@ impl FormatInto<Go> for &GoType {
// GoType::MultiReturn(typs) => {
// tokens.append(quote!($(for typ in typs join (, ) => $typ)))
// }
// GoType::Pointer(typ) => {
// tokens.append(static_literal("*"));
// typ.as_ref().format_into(tokens);
// }
GoType::Pointer(typ) => {
tokens.append(static_literal("*"));
typ.as_ref().format_into(tokens);
}
GoType::UserDefined(name) => {
let id = GoIdentifier::public(name);
id.format_into(tokens)
Expand Down
Loading