Skip to content

Commit cf16cfc

Browse files
committed
feat: add support for tuples
Includes type generation and instructions for tuple lowering/lifting, as well as basic tests. Supports both anonymous tuples and type aliases.
1 parent 55c9f56 commit cf16cfc

File tree

11 files changed

+256
-39
lines changed

11 files changed

+256
-39
lines changed

Cargo.lock

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmd/gravity/src/codegen/func.rs

Lines changed: 83 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::mem;
22

3-
use genco::prelude::*;
3+
use genco::{prelude::*, tokens::static_literal};
44
use wit_bindgen_core::{
55
abi::{Bindgen, Instruction},
66
wit_parser::{Alignment, ArchitectureSize, Resolve, Result_, SizeAlign, Type, TypeDefKind},
@@ -516,7 +516,7 @@ impl Bindgen for Func<'_> {
516516
}
517517
};
518518

519-
results.push(Operand::MultiValue((value.into(), err.into())));
519+
results.push(Operand::DoubleValue(value.into(), err.into()));
520520
}
521521
Instruction::ResultLift {
522522
result:
@@ -606,10 +606,10 @@ impl Bindgen for Func<'_> {
606606
results.push(Operand::SingleValue(err.into()));
607607
}
608608
GoType::ValueOrError(_) => {
609-
results.push(Operand::MultiValue((value.into(), err.into())));
609+
results.push(Operand::DoubleValue(value.into(), err.into()));
610610
}
611611
GoType::ValueOrOk(_) => {
612-
results.push(Operand::MultiValue((value.into(), ok.into())))
612+
results.push(Operand::DoubleValue(value.into(), ok.into()))
613613
}
614614
_ => todo!("TODO(#9): handle return type - {returns:?}"),
615615
}
@@ -754,7 +754,10 @@ impl Bindgen for Func<'_> {
754754
Operand::SingleValue(_) => panic!(
755755
"impossible: expected Operand::MultiValue but got Operand::SingleValue"
756756
),
757-
Operand::MultiValue(bindings) => bindings,
757+
Operand::DoubleValue(ok, err) => (ok, err),
758+
Operand::MultiValue(_) => panic!(
759+
"impossible: expected Operand::DoubleValue but got Operand::MultiValue"
760+
),
758761
};
759762
quote_in! { self.body =>
760763
$['\r']
@@ -814,7 +817,7 @@ impl Bindgen for Func<'_> {
814817
}
815818
};
816819

817-
results.push(Operand::MultiValue((result.into(), ok.into())));
820+
results.push(Operand::DoubleValue(result.into(), ok.into()));
818821
}
819822
Instruction::OptionLower {
820823
results: result_types,
@@ -869,7 +872,12 @@ impl Bindgen for Func<'_> {
869872
}
870873
};
871874
}
872-
Operand::MultiValue((value, ok)) => {
875+
Operand::MultiValue(_) => {
876+
panic!(
877+
"impossible: expected Operand::DoubleValue but got Operand::MultiValue"
878+
)
879+
}
880+
Operand::DoubleValue(value, ok) => {
873881
quote_in! { self.body =>
874882
$['\r']
875883
if $ok {
@@ -915,7 +923,7 @@ impl Bindgen for Func<'_> {
915923
$['\r']
916924
};
917925
match (&field_type, &op_clone) {
918-
(GoType::Pointer(inner_type), Operand::MultiValue((val, ok))) => {
926+
(GoType::Pointer(inner_type), Operand::DoubleValue(val, ok)) => {
919927
quote_in! { self.body =>
920928
$['\r']
921929
};
@@ -1484,8 +1492,73 @@ impl Bindgen for Func<'_> {
14841492
};
14851493
results.push(Operand::SingleValue(result.into()));
14861494
}
1487-
Instruction::TupleLower { .. } => todo!("implement instruction: {inst:?}"),
1488-
Instruction::TupleLift { .. } => todo!("implement instruction: {inst:?}"),
1495+
Instruction::TupleLower { tuple, .. } => {
1496+
let tmp = self.tmp();
1497+
let operand = &operands[0];
1498+
for (i, _) in tuple.types.iter().enumerate() {
1499+
let field = GoIdentifier::public(format!("f-{i}"));
1500+
let var = &GoIdentifier::local(format!("f-{tmp}-{i}"));
1501+
quote_in! { self.body =>
1502+
$['\r']
1503+
$var := $operand.$field
1504+
}
1505+
results.push(Operand::SingleValue(var.into()));
1506+
}
1507+
}
1508+
Instruction::TupleLift { tuple, ty } => {
1509+
if tuple.types.len() != operands.len() {
1510+
panic!(
1511+
"impossible: expected {} operands but got {}",
1512+
tuple.types.len(),
1513+
operands.len()
1514+
);
1515+
}
1516+
let tmp = self.tmp();
1517+
let value = &GoIdentifier::local(format!("value{tmp}"));
1518+
1519+
let mut ty_tokens = Tokens::new();
1520+
if let Some(ty) = resolve
1521+
.types
1522+
.get(ty.clone())
1523+
.expect("failed to find tuple type definition")
1524+
.name
1525+
.as_ref()
1526+
{
1527+
let ty_name = GoIdentifier::public(ty);
1528+
ty_name.format_into(&mut ty_tokens);
1529+
} else {
1530+
ty_tokens.append(static_literal("struct{"));
1531+
if let Some((last, typs)) = tuple.types.split_last() {
1532+
for (i, typ) in typs.iter().enumerate() {
1533+
let go_type = resolve_type(typ, resolve);
1534+
let field = GoIdentifier::public(format!("f-{i}"));
1535+
field.format_into(&mut ty_tokens);
1536+
ty_tokens.space();
1537+
go_type.format_into(&mut ty_tokens);
1538+
ty_tokens.append(static_literal(";"));
1539+
ty_tokens.space();
1540+
}
1541+
let field = GoIdentifier::public(format!("f-{}", typs.len()));
1542+
field.format_into(&mut ty_tokens);
1543+
let go_type = resolve_type(last, resolve);
1544+
ty_tokens.space();
1545+
ty_tokens.append(go_type);
1546+
}
1547+
ty_tokens.append(static_literal("}"));
1548+
}
1549+
quote_in! { self.body =>
1550+
$['\r']
1551+
var $value $ty_tokens
1552+
}
1553+
for (i, (operand, _)) in operands.iter().zip(&tuple.types).enumerate() {
1554+
let field = &GoIdentifier::public(format!("f-{i}"));
1555+
quote_in! { self.body =>
1556+
$['\r']
1557+
$value.$field = $operand
1558+
}
1559+
}
1560+
results.push(Operand::SingleValue(value.into()));
1561+
}
14891562
Instruction::FlagsLower { .. } => todo!("implement instruction: {inst:?}"),
14901563
Instruction::FlagsLift { .. } => todo!("implement instruction: {inst:?}"),
14911564
Instruction::VariantLift { .. } => {

cmd/gravity/src/codegen/imports.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,19 @@ impl<'a> ImportAnalyzer<'a> {
230230
TypeDefKind::Future(_) => todo!("TODO(#4): generate future type definition"),
231231
TypeDefKind::Stream(_) => todo!("TODO(#4): generate stream type definition"),
232232
TypeDefKind::Flags(_) => todo!("TODO(#4):generate flags type definition"),
233-
TypeDefKind::Tuple(_) => todo!("TODO(#4):generate tuple type definition"),
233+
TypeDefKind::Tuple(tuple) => TypeDefinition::Record {
234+
fields: tuple
235+
.types
236+
.iter()
237+
.enumerate()
238+
.map(|(i, t)| {
239+
(
240+
GoIdentifier::public(format!("f-{i}")),
241+
resolve_type(t, self.resolve),
242+
)
243+
})
244+
.collect(),
245+
},
234246
TypeDefKind::Resource => todo!("TODO(#5): implement resources"),
235247
TypeDefKind::Handle(_) => todo!("TODO(#5): implement resources"),
236248
TypeDefKind::Unknown => panic!("cannot generate Unknown type"),

cmd/gravity/src/go/operand.rs

Lines changed: 32 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,25 +13,12 @@ pub enum Operand {
1313
Literal(String),
1414
/// A single variable or expression
1515
SingleValue(String),
16-
/// A tuple of two values (for multi-value returns)
17-
MultiValue((String, String)),
18-
}
19-
20-
impl Operand {
21-
/// Returns the primary value of the operand.
16+
/// A tuple of two values (shortcut for for multi-value returns).
2217
///
23-
/// For single values and literals, returns the value itself.
24-
/// For multi-value tuples, returns the first value.
25-
///
26-
/// # Returns
27-
/// A string representation of the primary value.
28-
pub fn as_string(&self) -> String {
29-
match self {
30-
Operand::Literal(s) => s.clone(),
31-
Operand::SingleValue(s) => s.clone(),
32-
Operand::MultiValue((s1, _)) => s1.clone(),
33-
}
34-
}
18+
/// This is used when returning `val, ok` or `result, err` from Go functions.
19+
DoubleValue(String, String),
20+
/// A tuple of two or more values (for tuples)
21+
MultiValue(Vec<String>),
3522
}
3623

3724
// Implement genco's FormatInto for Operand so it can be used in quote! macros
@@ -40,11 +27,21 @@ impl FormatInto<Go> for &Operand {
4027
match self {
4128
Operand::Literal(val) => tokens.append(ItemStr::from(val)),
4229
Operand::SingleValue(val) => tokens.append(ItemStr::from(val)),
43-
Operand::MultiValue((val1, val2)) => {
44-
tokens.append(ItemStr::from(val1));
30+
Operand::DoubleValue(val, ok) => {
31+
tokens.append(ItemStr::from(val));
4532
tokens.append(static_literal(","));
4633
tokens.space();
47-
tokens.append(ItemStr::from(val2));
34+
tokens.append(ItemStr::from(ok));
35+
}
36+
Operand::MultiValue(vals) => {
37+
if let Some((last, vals)) = vals.split_last() {
38+
for val in vals.iter() {
39+
tokens.append(val);
40+
tokens.append(static_literal(","));
41+
tokens.space();
42+
}
43+
tokens.append(last);
44+
}
4845
}
4946
}
5047
}
@@ -86,10 +83,22 @@ mod tests {
8683
}
8784

8885
#[test]
89-
fn test_operand_multi_value() {
90-
let op = Operand::MultiValue(("val1".to_string(), "val2".to_string()));
86+
fn test_operand_double_value() {
87+
let op = Operand::DoubleValue("val1".to_string(), "val2".to_string());
9188
let mut tokens = Tokens::<Go>::new();
9289
op.format_into(&mut tokens);
9390
assert_eq!(tokens.to_string().unwrap(), "val1, val2");
9491
}
92+
93+
#[test]
94+
fn test_operand_multi_value() {
95+
let op = Operand::MultiValue(vec![
96+
"val1".to_string(),
97+
"val2".to_string(),
98+
"val3".to_string(),
99+
]);
100+
let mut tokens = Tokens::<Go>::new();
101+
op.format_into(&mut tokens);
102+
assert_eq!(tokens.to_string().unwrap(), "val1, val2, val3");
103+
}
95104
}

cmd/gravity/src/go/type.rs

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ pub enum GoType {
4545
/// Slice/array of another type
4646
Slice(Box<GoType>),
4747
/// Multi-return type (for functions returning arbitrary multiple values)
48-
// MultiReturn(Vec<GoType>),
48+
MultiReturn(Vec<GoType>),
4949
/// User-defined type (records, enums, type aliases)
5050
UserDefined(String),
5151
/// Represents no value/void
@@ -123,6 +123,9 @@ impl GoType {
123123

124124
// A pointer probably needs cleanup, not sure?
125125
GoType::Pointer(_) => true,
126+
127+
// Multi-return types need cleanup if their inner types do
128+
GoType::MultiReturn(inner) => inner.iter().any(|t| t.needs_cleanup()),
126129
}
127130
}
128131
}
@@ -160,9 +163,24 @@ impl FormatInto<Go> for &GoType {
160163
tokens.append(static_literal("[]"));
161164
typ.as_ref().format_into(tokens);
162165
}
163-
// GoType::MultiReturn(typs) => {
164-
// tokens.append(quote!($(for typ in typs join (, ) => $typ)))
165-
// }
166+
GoType::MultiReturn(typs) => {
167+
tokens.append(static_literal("struct{"));
168+
if let Some((last, typs)) = typs.split_last() {
169+
for (i, typ) in typs.iter().enumerate() {
170+
let field = GoIdentifier::public(format!("f-{i}"));
171+
field.format_into(tokens);
172+
tokens.space();
173+
typ.format_into(tokens);
174+
tokens.append(static_literal(";"));
175+
tokens.space();
176+
}
177+
let field = GoIdentifier::public(format!("f-{}", typs.len()));
178+
field.format_into(tokens);
179+
tokens.space();
180+
tokens.append(last);
181+
}
182+
tokens.append(static_literal("}"));
183+
}
166184
GoType::Pointer(typ) => {
167185
tokens.append(static_literal("*"));
168186
typ.as_ref().format_into(tokens);

cmd/gravity/src/lib.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,13 @@ pub fn resolve_type(typ: &Type, resolve: &Resolve) -> GoType {
6666
TypeDefKind::Resource => todo!("TODO(#5): implement resources"),
6767
TypeDefKind::Handle(_) => todo!("TODO(#5): implement resources"),
6868
TypeDefKind::Flags(_) => todo!("TODO(#4): implement flag conversion"),
69-
TypeDefKind::Tuple(_) => todo!("TODO(#4): implement tuple conversion"),
69+
TypeDefKind::Tuple(tuple) => GoType::MultiReturn(
70+
tuple
71+
.types
72+
.iter()
73+
.map(|t| resolve_type(t, resolve))
74+
.collect(),
75+
),
7076
TypeDefKind::Variant(_) => GoType::UserDefined(
7177
name.clone()
7278
.clone()

examples/generate.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ package examples
55
//go:generate cargo build -p example-iface-method-returns-string --target wasm32-unknown-unknown --release
66
//go:generate cargo build -p example-instructions --target wasm32-unknown-unknown --release
77
//go:generate sh -c "RUSTFLAGS='--cfg getrandom_backend=\"custom\"' cargo build -q -p example-outlier --target wasm32-unknown-unknown --release"
8+
//go:generate cargo build -p example-tuples --target wasm32-unknown-unknown --release
89

910
//go:generate cargo run --bin gravity -- --world basic --output ./basic/basic.go ../target/wasm32-unknown-unknown/release/example_basic.wasm
1011
//go:generate cargo run --bin gravity -- --world records --output ./records/records.go ../target/wasm32-unknown-unknown/release/example_records.wasm
1112
//go:generate cargo run --bin gravity -- --world example --output ./iface-method-returns-string/example.go ../target/wasm32-unknown-unknown/release/example_iface_method_returns_string.wasm
1213
//go:generate cargo run --bin gravity -- --world instructions --output ./instructions/bindings.go ../target/wasm32-unknown-unknown/release/example_instructions.wasm
1314
//go:generate cargo run --bin gravity -- --world outlier --output ./outlier/outlier.go ../target/wasm32-unknown-unknown/release/example_outlier.wasm
15+
//go:generate cargo run --bin gravity -- --world tuples --output ./tuples/tuples.go ../target/wasm32-unknown-unknown/release/example_tuples.wasm

examples/tuples/Cargo.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[package]
2+
name = "example-tuples"
3+
version = "0.0.2"
4+
edition = "2024"
5+
6+
[lib]
7+
crate-type = ["cdylib"]
8+
9+
[dependencies]
10+
wit-bindgen = "=0.46.0"
11+
wit-component = "=0.239.0"

0 commit comments

Comments
 (0)