Skip to content

Commit 587c6cd

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 6815cf4 commit 587c6cd

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},
@@ -518,7 +518,7 @@ impl Bindgen for Func<'_> {
518518
}
519519
};
520520

521-
results.push(Operand::MultiValue((value.into(), err.into())));
521+
results.push(Operand::DoubleValue(value.into(), err.into()));
522522
}
523523
Instruction::ResultLift {
524524
result:
@@ -608,10 +608,10 @@ impl Bindgen for Func<'_> {
608608
results.push(Operand::SingleValue(err.into()));
609609
}
610610
GoType::ValueOrError(_) => {
611-
results.push(Operand::MultiValue((value.into(), err.into())));
611+
results.push(Operand::DoubleValue(value.into(), err.into()));
612612
}
613613
GoType::ValueOrOk(_) => {
614-
results.push(Operand::MultiValue((value.into(), ok.into())))
614+
results.push(Operand::DoubleValue(value.into(), ok.into()))
615615
}
616616
_ => todo!("TODO(#9): handle return type - {returns:?}"),
617617
}
@@ -756,7 +756,10 @@ impl Bindgen for Func<'_> {
756756
Operand::SingleValue(_) => panic!(
757757
"impossible: expected Operand::MultiValue but got Operand::SingleValue"
758758
),
759-
Operand::MultiValue(bindings) => bindings,
759+
Operand::DoubleValue(ok, err) => (ok, err),
760+
Operand::MultiValue(_) => panic!(
761+
"impossible: expected Operand::DoubleValue but got Operand::MultiValue"
762+
),
760763
};
761764
quote_in! { self.body =>
762765
$['\r']
@@ -816,7 +819,7 @@ impl Bindgen for Func<'_> {
816819
}
817820
};
818821

819-
results.push(Operand::MultiValue((result.into(), ok.into())));
822+
results.push(Operand::DoubleValue(result.into(), ok.into()));
820823
}
821824
Instruction::OptionLower {
822825
results: result_types,
@@ -871,7 +874,12 @@ impl Bindgen for Func<'_> {
871874
}
872875
};
873876
}
874-
Operand::MultiValue((value, ok)) => {
877+
Operand::MultiValue(_) => {
878+
panic!(
879+
"impossible: expected Operand::DoubleValue but got Operand::MultiValue"
880+
)
881+
}
882+
Operand::DoubleValue(value, ok) => {
875883
quote_in! { self.body =>
876884
$['\r']
877885
if $ok {
@@ -917,7 +925,7 @@ impl Bindgen for Func<'_> {
917925
$['\r']
918926
};
919927
match (&field_type, &op_clone) {
920-
(GoType::Pointer(inner_type), Operand::MultiValue((val, ok))) => {
928+
(GoType::Pointer(inner_type), Operand::DoubleValue(val, ok)) => {
921929
quote_in! { self.body =>
922930
$['\r']
923931
};
@@ -1486,8 +1494,73 @@ impl Bindgen for Func<'_> {
14861494
};
14871495
results.push(Operand::SingleValue(result.into()));
14881496
}
1489-
Instruction::TupleLower { .. } => todo!("implement instruction: {inst:?}"),
1490-
Instruction::TupleLift { .. } => todo!("implement instruction: {inst:?}"),
1497+
Instruction::TupleLower { tuple, .. } => {
1498+
let tmp = self.tmp();
1499+
let operand = &operands[0];
1500+
for (i, _) in tuple.types.iter().enumerate() {
1501+
let field = GoIdentifier::public(format!("f-{i}"));
1502+
let var = &GoIdentifier::local(format!("f-{tmp}-{i}"));
1503+
quote_in! { self.body =>
1504+
$['\r']
1505+
$var := $operand.$field
1506+
}
1507+
results.push(Operand::SingleValue(var.into()));
1508+
}
1509+
}
1510+
Instruction::TupleLift { tuple, ty } => {
1511+
if tuple.types.len() != operands.len() {
1512+
panic!(
1513+
"impossible: expected {} operands but got {}",
1514+
tuple.types.len(),
1515+
operands.len()
1516+
);
1517+
}
1518+
let tmp = self.tmp();
1519+
let value = &GoIdentifier::local(format!("value{tmp}"));
1520+
1521+
let mut ty_tokens = Tokens::new();
1522+
if let Some(ty) = resolve
1523+
.types
1524+
.get(ty.clone())
1525+
.expect("failed to find tuple type definition")
1526+
.name
1527+
.as_ref()
1528+
{
1529+
let ty_name = GoIdentifier::public(ty);
1530+
ty_name.format_into(&mut ty_tokens);
1531+
} else {
1532+
ty_tokens.append(static_literal("struct{"));
1533+
if let Some((last, typs)) = tuple.types.split_last() {
1534+
for (i, typ) in typs.iter().enumerate() {
1535+
let go_type = resolve_type(typ, resolve);
1536+
let field = GoIdentifier::public(format!("f-{i}"));
1537+
field.format_into(&mut ty_tokens);
1538+
ty_tokens.space();
1539+
go_type.format_into(&mut ty_tokens);
1540+
ty_tokens.append(static_literal(";"));
1541+
ty_tokens.space();
1542+
}
1543+
let field = GoIdentifier::public(format!("f-{}", typs.len()));
1544+
field.format_into(&mut ty_tokens);
1545+
let go_type = resolve_type(last, resolve);
1546+
ty_tokens.space();
1547+
ty_tokens.append(go_type);
1548+
}
1549+
ty_tokens.append(static_literal("}"));
1550+
}
1551+
quote_in! { self.body =>
1552+
$['\r']
1553+
var $value $ty_tokens
1554+
}
1555+
for (i, (operand, _)) in operands.iter().zip(&tuple.types).enumerate() {
1556+
let field = &GoIdentifier::public(format!("f-{i}"));
1557+
quote_in! { self.body =>
1558+
$['\r']
1559+
$value.$field = $operand
1560+
}
1561+
}
1562+
results.push(Operand::SingleValue(value.into()));
1563+
}
14911564
Instruction::FlagsLower { .. } => todo!("implement instruction: {inst:?}"),
14921565
Instruction::FlagsLift { .. } => todo!("implement instruction: {inst:?}"),
14931566
Instruction::VariantLift { .. } => {

cmd/gravity/src/codegen/imports.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,19 @@ impl<'a> ImportAnalyzer<'a> {
227227
TypeDefKind::Future(_) => todo!("TODO(#4): generate future type definition"),
228228
TypeDefKind::Stream(_) => todo!("TODO(#4): generate stream type definition"),
229229
TypeDefKind::Flags(_) => todo!("TODO(#4):generate flags type definition"),
230-
TypeDefKind::Tuple(_) => todo!("TODO(#4):generate tuple type definition"),
230+
TypeDefKind::Tuple(tuple) => TypeDefinition::Record {
231+
fields: tuple
232+
.types
233+
.iter()
234+
.enumerate()
235+
.map(|(i, t)| {
236+
(
237+
GoIdentifier::public(format!("f-{i}")),
238+
resolve_type(t, self.resolve),
239+
)
240+
})
241+
.collect(),
242+
},
231243
TypeDefKind::Resource => todo!("TODO(#5): implement resources"),
232244
TypeDefKind::Handle(_) => todo!("TODO(#5): implement resources"),
233245
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)