From 1fd5e5b3e6df869c6b48f8ce3b60078311a4a9ea Mon Sep 17 00:00:00 2001 From: Michael Williamson Date: Sun, 9 Aug 2026 10:27:45 +0100 Subject: Support generic native types in rust-transient-0 --- .../java/org/zwobble/example/Transient0Tests.java | 23 +++++ examples/15-transient-0/output/rust/src/data.rs | 36 ++++++- .../output/rust/src/gen/data/transient_0.rs | 62 +++++++++++- examples/15-transient-0/output/rust/src/lib.rs | 23 ++++- .../output/generators/rust/RustGenerator.java | 6 ++ .../rusttransient0/RustTransient0Generator.java | 105 ++++++++++++++++----- 6 files changed, 225 insertions(+), 30 deletions(-) diff --git a/examples/15-transient-0/output/java/src/test/java/org/zwobble/example/Transient0Tests.java b/examples/15-transient-0/output/java/src/test/java/org/zwobble/example/Transient0Tests.java index 528d43c..ba43aed 100644 --- a/examples/15-transient-0/output/java/src/test/java/org/zwobble/example/Transient0Tests.java +++ b/examples/15-transient-0/output/java/src/test/java/org/zwobble/example/Transient0Tests.java @@ -15,12 +15,14 @@ import java.util.Optional; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.zwobble.example.types.data.EnumWithVariants; +import org.zwobble.example.types.data.GenericNative; import org.zwobble.example.types.data.Native; import org.zwobble.example.types.data.StructSingleton; import org.zwobble.example.types.data.StructWithBool; import org.zwobble.example.types.data.StructWithBox; import org.zwobble.example.types.data.StructWithDifferentSharedTypes; import org.zwobble.example.types.data.StructWithEnum; +import org.zwobble.example.types.data.StructWithGenericNative; import org.zwobble.example.types.data.StructWithInt8; import org.zwobble.example.types.data.StructWithInt32; import org.zwobble.example.types.data.StructWithInt64; @@ -273,6 +275,27 @@ public class Transient0Tests { ); } + @Test + public void structWithGenericNative() throws IOException { + var value = new StructWithGenericNative( + new GenericNative(10, "x1"), + new GenericNative( + Optional.of("x2"), + new GenericNative( + Optional.of("x3"), + new GenericNative("x4", "x5") + ) + ) + ); + + assertRoundTripEncoding( + "StructWithGenericNative", + value, + HobgoblinTransient0Data::encodeStructWithGenericNative, + HobgoblinTransient0Data::decodeStructWithGenericNative + ); + } + @Test public void structWithBox() throws IOException { var value = new StructWithBox( diff --git a/examples/15-transient-0/output/rust/src/data.rs b/examples/15-transient-0/output/rust/src/data.rs index 2cf748b..cc0eb13 100644 --- a/examples/15-transient-0/output/rust/src/data.rs +++ b/examples/15-transient-0/output/rust/src/data.rs @@ -17,7 +17,7 @@ pub mod transient_0 { use ::std::any::Any; use ::std::sync::Arc; use ::std::vec::Vec; - use super::Native; + use super::{GenericNative, Native}; include!("./gen/data/transient_0.rs"); @@ -39,4 +39,38 @@ pub mod transient_0 { let b = crate::transient_0::decode_int_32(reader, shared_values)?; Ok(Native { a, b }) } + + type Encode = fn( + &T, + &mut TWrite, + &mut ::std::collections::HashMap::, + ) -> std::io::Result<()>; + + type Decode = fn( + &mut TRead, + &mut Vec>, + ) -> std::io::Result; + + pub fn encode_generic_native( + value: &GenericNative, + writer: &mut TWrite, + shared_values: &mut ::std::collections::HashMap::, + encode_a: Encode, + encode_b: Encode, + ) -> std::io::Result<()> { + encode_a(&value.a, writer, shared_values)?; + encode_b(&value.b, writer, shared_values)?; + Ok(()) + } + + pub fn decode_generic_native( + reader: &mut TRead, + shared_values: &mut Vec>, + decode_a: Decode, + decode_b: Decode, + ) -> std::io::Result> { + let a = decode_a(reader, shared_values)?; + let b = decode_b(reader, shared_values)?; + Ok(GenericNative { a, b }) + } } diff --git a/examples/15-transient-0/output/rust/src/gen/data/transient_0.rs b/examples/15-transient-0/output/rust/src/gen/data/transient_0.rs index 383a564..b72964c 100644 --- a/examples/15-transient-0/output/rust/src/gen/data/transient_0.rs +++ b/examples/15-transient-0/output/rust/src/gen/data/transient_0.rs @@ -275,14 +275,68 @@ pub fn decode_struct_with_native(reader: &mut impl std::io::Read, shared_values: } pub fn encode_struct_with_generic_native(value: &crate::data::StructWithGenericNative, writer: &mut impl std::io::Write, shared_values: &mut ::std::collections::HashMap::<::core::primitive::usize, ::core::primitive::i64>) -> ::std::io::Result::<()> { - todo!(); - todo!(); + crate::data::transient_0::encode_generic_native(&value.a, writer, shared_values, |value, writer, shared_values| { + crate::transient_0::encode_int_32(value, writer, shared_values)?; + ::std::io::Result::Ok(()) + }, |value, writer, shared_values| { + crate::transient_0::encode_string(value, writer, shared_values)?; + ::std::io::Result::Ok(()) + })?; + crate::data::transient_0::encode_generic_native(&value.b, writer, shared_values, |value, writer, shared_values| { + match value { + ::std::option::Option::Some(value) => { + crate::transient_0::encode_bool(&true, writer, shared_values)?; + crate::transient_0::encode_string(&value, writer, shared_values)?; + }, + ::std::option::Option::None => { + crate::transient_0::encode_bool(&false, writer, shared_values)?; + }, + }; + ::std::io::Result::Ok(()) + }, |value, writer, shared_values| { + crate::data::transient_0::encode_generic_native(value, writer, shared_values, |value, writer, shared_values| { + match value { + ::std::option::Option::Some(value) => { + crate::transient_0::encode_bool(&true, writer, shared_values)?; + crate::transient_0::encode_string(&value, writer, shared_values)?; + }, + ::std::option::Option::None => { + crate::transient_0::encode_bool(&false, writer, shared_values)?; + }, + }; + ::std::io::Result::Ok(()) + }, |value, writer, shared_values| { + crate::data::transient_0::encode_generic_native(value, writer, shared_values, |value, writer, shared_values| { + crate::transient_0::encode_string(value, writer, shared_values)?; + ::std::io::Result::Ok(()) + }, |value, writer, shared_values| { + crate::transient_0::encode_string(value, writer, shared_values)?; + ::std::io::Result::Ok(()) + })?; + ::std::io::Result::Ok(()) + })?; + ::std::io::Result::Ok(()) + })?; ::std::io::Result::Ok(()) } pub fn decode_struct_with_generic_native(reader: &mut impl std::io::Read, shared_values: &mut ::std::vec::Vec::<::std::sync::Arc::>) -> ::std::io::Result:: { - let a = todo!(); - let b = todo!(); + let a = crate::data::transient_0::decode_generic_native(reader, shared_values, |reader, shared_values| ::std::io::Result::Ok(crate::transient_0::decode_int_32(reader, shared_values)?), |reader, shared_values| ::std::io::Result::Ok(crate::transient_0::decode_string(reader, shared_values)?))?; + let b = crate::data::transient_0::decode_generic_native(reader, shared_values, |reader, shared_values| ::std::io::Result::Ok({ + let is_some = crate::transient_0::decode_bool(reader, shared_values)?; + if is_some { + ::std::option::Option::Some(crate::transient_0::decode_string(reader, shared_values)?) + } else { + ::std::option::Option::None + } + }), |reader, shared_values| ::std::io::Result::Ok(crate::data::transient_0::decode_generic_native(reader, shared_values, |reader, shared_values| ::std::io::Result::Ok({ + let is_some = crate::transient_0::decode_bool(reader, shared_values)?; + if is_some { + ::std::option::Option::Some(crate::transient_0::decode_string(reader, shared_values)?) + } else { + ::std::option::Option::None + } + }), |reader, shared_values| ::std::io::Result::Ok(crate::data::transient_0::decode_generic_native(reader, shared_values, |reader, shared_values| ::std::io::Result::Ok(crate::transient_0::decode_string(reader, shared_values)?), |reader, shared_values| ::std::io::Result::Ok(crate::transient_0::decode_string(reader, shared_values)?))?))?))?; ::std::io::Result::Ok(crate::data::StructWithGenericNative { a: a, b: b }) } diff --git a/examples/15-transient-0/output/rust/src/lib.rs b/examples/15-transient-0/output/rust/src/lib.rs index 22ecdb4..a51afff 100644 --- a/examples/15-transient-0/output/rust/src/lib.rs +++ b/examples/15-transient-0/output/rust/src/lib.rs @@ -8,7 +8,7 @@ mod test { use std::path::PathBuf; use std::sync::Arc; use std::collections::HashMap; - use super::data::{EnumWithVariants, Native, StructSingleton, StructWithBool, StructWithBox, StructWithDifferentSharedTypes, StructWithEnum, StructWithInt8, StructWithInt32, StructWithInt64, StructWithList, StructWithListOfShared, StructWithNative, StructWithOption, StructWithSharedSumAndVariant, StructWithString, StructWithStruct, StructWithSum, SumWithBoxedVariants, SumWithVariants, VariantOne, VariantTwo }; + use super::data::{EnumWithVariants, GenericNative, Native, StructSingleton, StructWithBool, StructWithBox, StructWithDifferentSharedTypes, StructWithEnum, StructWithGenericNative, StructWithInt8, StructWithInt32, StructWithInt64, StructWithList, StructWithListOfShared, StructWithNative, StructWithOption, StructWithSharedSumAndVariant, StructWithString, StructWithStruct, StructWithSum, SumWithBoxedVariants, SumWithVariants, VariantOne, VariantTwo }; #[test] fn struct_singleton() { @@ -242,6 +242,27 @@ mod test { ); } + #[test] + fn struct_with_generic_native() { + let value = StructWithGenericNative { + a: GenericNative { a: 10, b: "x1".to_string() }, + b: GenericNative { + a: Some("x2".to_string()), + b: GenericNative { + a: Some("x3".to_string()), + b: GenericNative { a: "x4".to_string(), b: "x5".to_string() }, + }, + }, + }; + + assert_round_trip_encoding( + "StructWithGenericNative", + value, + super::data::transient_0::encode_struct_with_generic_native, + super::data::transient_0::decode_struct_with_generic_native, + ); + } + #[test] fn struct_with_box() { let value = StructWithBox { diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/output/generators/rust/RustGenerator.java b/src/main/java/org/zwobble/hobgoblin/compiler/output/generators/rust/RustGenerator.java index ec10107..602c804 100644 --- a/src/main/java/org/zwobble/hobgoblin/compiler/output/generators/rust/RustGenerator.java +++ b/src/main/java/org/zwobble/hobgoblin/compiler/output/generators/rust/RustGenerator.java @@ -119,4 +119,10 @@ public class RustGenerator { public RustIdentifier generateVariantName(SumVariant variant) { return generateTypeName(variant.valueType().name()); } + + public static List functionParamsToClosureParams(List params) { + return params.stream() + .map(param -> new RustIdentifierPattern(param.name())) + .toList(); + } } diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/output/generators/rusttransient0/RustTransient0Generator.java b/src/main/java/org/zwobble/hobgoblin/compiler/output/generators/rusttransient0/RustTransient0Generator.java index dfca311..9d959bd 100644 --- a/src/main/java/org/zwobble/hobgoblin/compiler/output/generators/rusttransient0/RustTransient0Generator.java +++ b/src/main/java/org/zwobble/hobgoblin/compiler/output/generators/rusttransient0/RustTransient0Generator.java @@ -10,6 +10,7 @@ import org.zwobble.hobgoblin.compiler.output.lang.rust.RustTypes; import org.zwobble.hobgoblin.compiler.output.lang.rust.ast.*; import org.zwobble.hobgoblin.compiler.typechecker.TypesInfo; import org.zwobble.hobgoblin.compiler.types.*; +import org.zwobble.hobgoblin.compiler.util.Lists; import org.zwobble.json5.reader.Json5ObjectReader; import java.io.IOException; @@ -527,16 +528,7 @@ public class RustTransient0Generator implements Generator { return new RustFunction( Optional.of(RustVisibility.PUB), encodeMethodName(type), - List.of( - new RustFunctionParam(VALUE_NAME, new RustSharedReferenceType(rustType)), - new RustFunctionParam(WRITER_NAME, new RustMutableReferenceType(new RustImplTraitType(RustPath.of("std", "io", "Write")))), - new RustFunctionParam( - SHARED_VALUES_NAME, - new RustMutableReferenceType( - RustTypes.hashMap(RustTypes.USIZE, RustPath.primitive("i64")) - ) - ) - ), + generateEncodeParams(rustType), Optional.of( RustTypes.ioResult(RustTypes.UNIT) ), @@ -552,6 +544,19 @@ public class RustTransient0Generator implements Generator { ); } + private static List generateEncodeParams(RustPath rustType) { + return List.of( + new RustFunctionParam(VALUE_NAME, new RustSharedReferenceType(rustType)), + new RustFunctionParam(WRITER_NAME, new RustMutableReferenceType(new RustImplTraitType(RustPath.of("std", "io", "Write")))), + new RustFunctionParam( + SHARED_VALUES_NAME, + new RustMutableReferenceType( + RustTypes.hashMap(RustTypes.USIZE, RustPath.primitive("i64")) + ) + ) + ); + } + private RustItem generateDecodeFunction( Type type, RustBlockExpression body @@ -561,12 +566,7 @@ public class RustTransient0Generator implements Generator { return new RustFunction( Optional.of(RustVisibility.PUB), decodeMethodName(type), - List.of( - new RustFunctionParam(READER_NAME, new RustMutableReferenceType(new RustImplTraitType(RustPath.of("std", "io", "Read")))), - new RustFunctionParam(SHARED_VALUES_NAME, new RustMutableReferenceType( - RustTypes.vec(RustTypes.arc(RustTypes.dyn(RustTypes.ANY, RustTypes.SYNC, RustTypes.SEND))) - )) - ), + generateDecodeParams(), Optional.of(RustTypes.ioResult(rustType)), Optional.of(new RustBlockExpression( body.statements(), @@ -575,6 +575,17 @@ public class RustTransient0Generator implements Generator { ); } + private static List generateDecodeParams() { + return List.of( + new RustFunctionParam(READER_NAME, new RustMutableReferenceType(new RustImplTraitType(RustPath.of("std", "io", "Read")))), + new RustFunctionParam( + SHARED_VALUES_NAME, new RustMutableReferenceType( + RustTypes.vec(RustTypes.arc(RustTypes.dyn(RustTypes.ANY, RustTypes.SYNC, RustTypes.SEND))) + ) + ) + ); + } + private RustStatement generateEncode(RustExpression value, Type type) { return switch (type) { case ConstructedNativeType constructedNativeType -> { @@ -587,12 +598,30 @@ public class RustTransient0Generator implements Generator { } else if (constructedNativeType.constructor().equals(NativeTypes.SHARED)) { yield generateEncodeShared(value, constructedNativeType.args().getFirst()); } else { - yield new RustExpressionStatement(generateTodo()); + yield generateEncode( + value, + constructedNativeType.namespaceName(), + type, + constructedNativeType.args().stream() + .map(typeArg -> new RustClosureExpression( + RustGenerator.functionParamsToClosureParams(generateEncodeParams( + this.rustGenerator.generateRustTypeExpression(typeArg) + )), + new RustBlockExpression( + List.of(generateEncode(RustPath.of(VALUE_NAME), typeArg)), + Optional.of(new RustCallExpression( + RustTypes.IO_RESULT_OK, + List.of(new RustTupleExpression(List.of())) + )) + ) + )) + .toList() + ); } } case SimpleType simpleType -> { - yield generateEncode(value, simpleType.namespaceName(), type); + yield generateEncode(value, simpleType.namespaceName(), type, List.of()); } case TypeLevelValueType _ -> { @@ -617,12 +646,25 @@ public class RustTransient0Generator implements Generator { } else if (constructedNativeType.constructor().equals(NativeTypes.SHARED)) { yield generateDecodeShared(constructedNativeType.args().getFirst()); } else { - yield generateTodo(); + yield generateDecode( + constructedNativeType.namespaceName(), + constructedNativeType, + constructedNativeType.args().stream() + .map(typeArg -> new RustClosureExpression( + RustGenerator.functionParamsToClosureParams(generateDecodeParams()), + new RustCallExpression( + RustTypes.IO_RESULT_OK, + List.of(generateDecode(typeArg)) + ) + )) + .toList() + ); } } case SimpleType simpleType -> { - yield generateDecode(simpleType.namespaceName(), type); + NamespaceName typeNamespaceName = simpleType.namespaceName(); + yield generateDecode(typeNamespaceName, type, List.of()); } case TypeLevelValueType _ -> { @@ -635,7 +677,12 @@ public class RustTransient0Generator implements Generator { }; } - private RustStatement generateEncode(RustExpression value, NamespaceName typeNamespaceName, Type type) { + private RustStatement generateEncode( + RustExpression value, + NamespaceName typeNamespaceName, + Type type, + List encodeFunctions + ) { var rustEncodeFunctionPathSegments = new ArrayList<>( this.generateTransient0ModuleName(typeNamespaceName) ); @@ -646,11 +693,18 @@ public class RustTransient0Generator implements Generator { return new RustExpressionStatement(new RustTryPropagationExpression(new RustCallExpression( rustEncodeFunctionPath, - List.of(value, RustPath.of(WRITER_NAME), RustPath.of(SHARED_VALUES_NAME)) + Lists.concat(List.of( + List.of(value, RustPath.of(WRITER_NAME), RustPath.of(SHARED_VALUES_NAME)), + encodeFunctions + )) ))); } - private RustExpression generateDecode(NamespaceName typeNamespaceName, Type type) { + private RustExpression generateDecode( + NamespaceName typeNamespaceName, + Type type, + List decodeFunctions + ) { var rustDecodeFunctionPathSegments = new ArrayList<>( this.generateTransient0ModuleName(typeNamespaceName) ); @@ -661,7 +715,10 @@ public class RustTransient0Generator implements Generator { return new RustTryPropagationExpression(new RustCallExpression( rustDecodeFunctionPath, - List.of(RustPath.of(READER_NAME), RustPath.of(SHARED_VALUES_NAME)) + Lists.concat(List.of( + List.of(RustPath.of(READER_NAME), RustPath.of(SHARED_VALUES_NAME)), + decodeFunctions + )) )); } -- cgit v1.2.3