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
81 changes: 78 additions & 3 deletions cmd/gravity/src/codegen/imports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use crate::{
GoIdentifier, GoResult, GoType,
imports::{CONTEXT_CONTEXT, WAZERO_API_MODULE},
},
resolve_param_type, resolve_type, resolve_wasm_type,
resolve_nested_type, resolve_param_type, resolve_type, resolve_wasm_type,
};

/// Analyzer for imports - only does analysis, no code generation
Expand Down Expand Up @@ -164,7 +164,10 @@ impl<'a> ImportAnalyzer<'a> {
}

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 payload = case.ty.as_ref().map(|t| {
let position = format!("the payload of variant case `{}`", case.name);
resolve_nested_type(t, self.resolve, &position)
});
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"),
Expand All @@ -189,9 +192,10 @@ impl<'a> ImportAnalyzer<'a> {
.fields
.iter()
.map(|field| {
let position = format!("record field `{}`", field.name);
(
GoIdentifier::public(&field.name),
resolve_type(&field.ty, self.resolve),
resolve_nested_type(&field.ty, self.resolve, &position),
)
})
.collect(),
Expand Down Expand Up @@ -940,4 +944,75 @@ mod tests {
other => panic!("alias analyzed as: {other:?}"),
}
}

/// Analyzes a world that imports `types`, an interface declaring `decls`.
fn analyze_types(decls: &str) {
let fixture = Fixture::parse(&format!(
"package test:fixture;
interface types {{
{decls}
}}
world test-world {{
import types;
}}"
));
ImportAnalyzer::new(&fixture.resolve, fixture.world()).analyze();
}

/// Resolves the result type of the function the world exports as `f`.
fn resolve_export_result(wit_result: &str) {
let fixture = Fixture::parse(&format!(
"package test:fixture;
world test-world {{
export f: func() -> {wit_result};
}}"
));
let result = fixture.export("f").result.expect("f has a result");
crate::resolve_type(&result, &fixture.resolve);
}

#[test]
#[should_panic(expected = "implement a nested result in record field `r`")]
fn test_result_record_field_is_refused() {
analyze_types("record holder { r: result<u32, string> }");
}

#[test]
#[should_panic(expected = "implement a nested result in the payload of variant case `failed`")]
fn test_result_variant_payload_is_refused() {
analyze_types("variant outcome { done, failed(result<u32, string>) }");
}

#[test]
#[should_panic(expected = "implement a nested result in a list element")]
fn test_result_list_element_is_refused() {
resolve_export_result("list<result<u32, string>>");
}

#[test]
#[should_panic(expected = "implement a nested result in an option payload")]
fn test_result_option_payload_is_refused() {
resolve_export_result("option<result<u32, string>>");
}

#[test]
#[should_panic(expected = "implement a nested result in a result's ok type")]
fn test_result_ok_type_is_refused() {
resolve_export_result("result<result<u32, string>, string>");
}

/// `result<_, string>` is a single Go `error`, which a field could
/// declare, but gravity cannot lift or lower it there, so the nested
/// check refuses every result shape.
#[test]
#[should_panic(expected = "implement a nested result in record field `e`")]
fn test_error_only_result_record_field_is_refused() {
analyze_types("record holder { e: result<_, string> }");
}

/// A result in a function's own result is still supported.
#[test]
fn test_result_as_function_result_still_resolves() {
resolve_export_result("result<u32, string>");
}
}
41 changes: 35 additions & 6 deletions cmd/gravity/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,15 +149,21 @@ pub fn resolve_type(typ: &Type, resolve: &Resolve) -> GoType {
// single pointer composes in every position (param, return,
// record field, list element); the prior `(T, bool)`
// comma-ok shape didn't.
TypeDefKind::Option(value) => {
GoType::Pointer(Box::new(resolve_type(value, resolve)))
}
TypeDefKind::Option(value) => GoType::Pointer(Box::new(resolve_nested_type(
value,
resolve,
"an option payload",
))),

// Various results, including specialised ones.
TypeDefKind::Result(Result_ {
ok: Some(ok),
err: Some(Type::String),
}) => GoType::ValueOrError(Box::new(resolve_type(ok, resolve))),
}) => GoType::ValueOrError(Box::new(resolve_nested_type(
ok,
resolve,
"a result's ok type",
))),
TypeDefKind::Result(Result_ {
ok: Some(_),
err: Some(_),
Expand All @@ -167,7 +173,7 @@ pub fn resolve_type(typ: &Type, resolve: &Resolve) -> GoType {
TypeDefKind::Result(Result_ {
ok: Some(ok),
err: None,
}) => resolve_type(ok, resolve),
}) => resolve_nested_type(ok, resolve, "a result's ok type"),
TypeDefKind::Result(Result_ {
ok: None,
err: Some(Type::String),
Expand All @@ -181,7 +187,11 @@ pub fn resolve_type(typ: &Type, resolve: &Resolve) -> GoType {
err: None,
}) => GoType::Nothing,

TypeDefKind::List(inner) => GoType::Slice(Box::new(resolve_type(inner, resolve))),
TypeDefKind::List(inner) => GoType::Slice(Box::new(resolve_nested_type(
inner,
resolve,
"a list element",
))),
TypeDefKind::Future(_) => todo!("TODO(#4): implement future conversion"),
TypeDefKind::Stream(_) => todo!("TODO(#4): implement stream conversion"),
TypeDefKind::Type(_) => GoType::UserDefined(qualified_type_name(*id, resolve)),
Expand All @@ -195,6 +205,25 @@ pub fn resolve_type(typ: &Type, resolve: &Resolve) -> GoType {
}
}

/// Resolves a WIT type that sits inside another type, such as a record field
/// or a list element. `position` names where it sits, for the error message.
///
/// gravity represents a `result` with Go's multiple return values (`T,
/// error`, or `error` alone), which only a function's results can carry, so a
/// nested `result` is refused here rather than written out as Go that does not
/// compile.
pub fn resolve_nested_type(typ: &Type, resolve: &Resolve, position: &str) -> GoType {
if let Type::Id(id) = typ
&& matches!(
resolve.types[dealias(resolve, *id)].kind,
TypeDefKind::Result(_)
)
{
todo!("TODO(#4): implement a nested result in {position}");
}
resolve_type(typ, resolve)
}

/// Like [`resolve_type`], but downgrades a top-level Variant to
/// `interface{}` so existing call sites can keep passing the variant
/// payload through `any`-typed plumbing (rule config returns, generic
Expand Down
Loading