Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug] documentation / functionality to generate correct c++ code for generics when using serde-name #37

Open
johnperry-math opened this issue Nov 29, 2023 · 3 comments
Labels
bug Something isn't working

Comments

@johnperry-math
Copy link

🐛 Bug

If you need to generate C++ code for a generic, documentation needs more information on how to obtain correct code, or serde-generate needs to generate valid code for C++ generics when using recommended procedure

To reproduce

Extended from the example in serde-name, recommended in serde-reflection docs.

use std::path::PathBuf;

use serde::{Deserialize, Serialize};
use serde_generate::SourceInstaller;
use serde_name::{DeserializeNameAdapter, SerializeNameAdapter};
use serde_reflection::{FormatHolder, Samples, Tracer, TracerConfig};

#[derive(Serialize, Deserialize)]
#[serde(remote = "Foo")] // Generates Foo::(de)serialize instead of implementing Serde traits.
struct Foo<T> {
    data: T,
}

impl<'de, T> Deserialize<'de> for Foo<T>
where
    T: Deserialize<'de>,
{
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::de::Deserializer<'de>,
    {
        Foo::deserialize(DeserializeNameAdapter::new(
            deserializer,
            std::any::type_name::<Self>(),
        ))
    }
}

impl<T> Serialize for Foo<T>
where
    T: Serialize,
{
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::ser::Serializer,
    {
        Foo::serialize(
            self,
            SerializeNameAdapter::new(serializer, std::any::type_name::<Self>()),
        )
    }
}

fn main() {
    // Testing the Deserialize implementation
    assert!(serde_name::trace_name::<Foo<u64>>()
        .unwrap()
        .ends_with("Foo<u64>"));

    // Testing the Serialize implementation
    use serde_reflection::*;
    let mut tracer = Tracer::new(TracerConfig::default());
    let mut samples = Samples::new();
    let (mut ident, _) = tracer
        .trace_value(&mut samples, &Foo { data: 1u64 })
        .unwrap();
    ident.normalize().unwrap();
    assert!(matches!(ident, Format::TypeName(s) if s.ends_with("Foo<u64>")));
    let registry = tracer.registry().unwrap();
    let config = serde_generate::CodeGeneratorConfig::new("generic_to_cpp".to_string())
        .with_encodings(vec![serde_generate::Encoding::Bincode]);
    let installer = serde_generate::cpp::Installer::new(PathBuf::from("a_new_lib"));
    installer.install_module(&config, &registry).unwrap();
    installer.install_bincode_runtime().unwrap();
    installer.install_serde_runtime().unwrap();
}

Generated C++ code

#pragma once

#include "serde.hpp"
#include "bincode.hpp"

namespace generic_to_cpp {

    struct generic_to_cpp::Foo<u64> {
        uint64_t data;
// and more like this

😢

Expected C++ code

  • ideally, something like
#include "serde.hpp"
#include "bincode.hpp"

namespace generic_to_cpp {

    template <typename T>
    struct Foo{
        T data;
// etc.
}
  • Otherwise, an example that shows how to build on the example in serde-name to obtain valid C++ code for the instantiated type.

System information

Please complete the following information:

  • Serde Reflection version 0.3.6
  • Rust version 1.74.0

Additional context

I've also tried this at least with trace_type.

@johnperry-math johnperry-math added the bug Something isn't working label Nov 29, 2023
@ma2bd
Copy link
Contributor

ma2bd commented Feb 18, 2024

Hi @johnperry-math, Thanks for the report. You were almost there! What's missing is applying some form of name mangling on top of std::any::type_name::<Self>() in both places. Perhaps an existing crate like mangling would do it?

Explanation: The code of serde-generate currently assumes that the names in the registry look like typical Rust identifiers (without special characters). I think that's fair because support for generics is highly experimental. If there is enough interest, we could enforce this invariant and provide examples of work-around.

@johnperry-math
Copy link
Author

I'm sorry it's taken me a while to reply. I think I found a workaround at the time, and I didn't worry about it when you replied, and then it fell off my radar, and then when I did remember, I didn't quite follow what you're saying, and... well, here we are.

I took a look at the mangling crate, and I'm not sure how that's supposed to help. Am I supposed to:

  1. ...use a serde rename to indicate:
    • the desired C++ type? (template <typename T> Foo<T>) or
    • the compiler-mangled type? (not sure and 😨 to find out)
  2. ...write a custom macro to tell serde-reflection what to do?

Sorry if the reply is completely off-base.

@ma2bd
Copy link
Contributor

ma2bd commented Aug 9, 2024

Ok so we can see that your generate C++ code contains "Foo" in a position of an identifier so that won't work.

This string is the result of calling std::any::type_name::() in the rust code.

Instead you need to define a suitable function "mangling" and use mangling(std::any::type_name::())

You can use a rust crate called mangling (i think) or you can define the function yourself, for instance replacing < and > by _

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants