stellar/xdrgen

View on GitHub
lib/xdrgen/generators/rust.rb

Summary

Maintainability
F
1 wk
Test Coverage
module Xdrgen
  module Generators

    class Rust < Xdrgen::Generators::Base

      def generate
        $stderr.puts "warn: rust generator is experimental"

        @already_rendered = []
        path = "#{@namespace}.rs"
        out = @output.open(path)

        @types = build_type_list(@top)
        @type_field_types = build_type_field_types(@top)

        render_top_matter(out)
        render_lib(out)
        render_definitions(out, @top)
        render_enum_of_all_types(out, @types)
        out.break
      end

      private

      def build_type_list(node)
        types = Set.new
        ingest_node = lambda do |n|
          case n
          when AST::Definitions::Struct, AST::Definitions::Enum, AST::Definitions::Union, AST::Definitions::Typedef
            types << name(n)
          end
          n.definitions.each{ |nn| ingest_node.call(nn) } if n.respond_to?(:definitions)
          n.nested_definitions.each{ |nn| ingest_node.call(nn) } if n.respond_to?(:nested_definitions)
        end
        ingest_node.call(node)
        types
      end

      def build_type_field_types(node)
        types = Hash.new { |h, k| h[k] = [] }
        ingest_node = lambda do |n|
          n.definitions.each{ |nn| ingest_node.call(nn) } if n.respond_to?(:definitions)
          n.nested_definitions.each{ |nn| ingest_node.call(nn) } if n.respond_to?(:nested_definitions)
          case n
          when AST::Definitions::Struct
            n.members.each do |m|
              types[name(n)] << base_reference(m.declaration.type)
            end
          when AST::Definitions::Union ;
            union_cases(n) do |_, arm|
              types[name(n)] << base_reference(arm.type) unless arm.void?
            end
          end
        end
        ingest_node.call(node)
        types
      end

      # Determines if 'type' is referenced directly or indirectly by 'type_with_fields'.
      # Used to determine if 'type_with_fields' has a recursive relationship to 'type'.
      def is_type_in_type_field_types(type_with_fields, type, seen = [])
        return false if seen.include?(type_with_fields)
        seen << type_with_fields
        @type_field_types[type_with_fields].any? do |field_type|
          if field_type == type
            true
          else
            is_type_in_type_field_types(field_type, type, seen)
          end
        end
      end

      def render_top_matter(out)
        out.puts <<-EOS.strip_heredoc
          // Module #{@namepsace} is generated from:
          //  #{@output.relative_source_paths.join("\n//  ")}
        EOS
        out.break
        out.puts "#![allow(clippy::missing_errors_doc, clippy::unreadable_literal)]"
        out.break
        source_paths_sha256_hashes = @output.relative_source_path_sha256_hashes
        out.puts <<-EOS.strip_heredoc
          /// `XDR_FILES_SHA256` is a list of pairs of source files and their SHA256 hashes.
          pub const XDR_FILES_SHA256: [(&str, &str); #{source_paths_sha256_hashes.count}] = [
            #{source_paths_sha256_hashes.map(){ |path, hash| %{("#{path}", "#{hash}")} }.join(",\n")}
          ];
        EOS
        out.break
      end

      def render_lib(out)
        lib = IO.read(__dir__ + "/rust/src/types.rs")
        out.puts(lib)
        out.break
      end

      def render_enum_of_all_types(out, types)
        out.puts <<-EOS.strip_heredoc
        #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
        #[cfg_attr(
          all(feature = "serde", feature = "alloc"),
          derive(serde::Serialize, serde::Deserialize),
          serde(rename_all = "snake_case")
        )]
        pub enum TypeVariant {
            #{types.map { |t| "#{t}," }.join("\n")}
        }

        impl TypeVariant {
            pub const VARIANTS: [TypeVariant; #{types.count}] = [ #{types.map { |t| "TypeVariant::#{t}," }.join("\n")} ];
            pub const VARIANTS_STR: [&'static str; #{types.count}] = [ #{types.map { |t| "\"#{t}\"," }.join("\n")} ];

            #[must_use]
            #[allow(clippy::too_many_lines)]
            pub const fn name(&self) -> &'static str {
                match self {
                    #{types.map { |t| "Self::#{t} => \"#{t}\"," }.join("\n")}
                }
            }

            #[must_use]
            #[allow(clippy::too_many_lines)]
            pub const fn variants() -> [TypeVariant; #{types.count}] {
                Self::VARIANTS
            }
        }

        impl Name for TypeVariant {
            #[must_use]
            fn name(&self) -> &'static str {
                Self::name(self)
            }
        }

        impl Variants<TypeVariant> for TypeVariant {
            fn variants() -> slice::Iter<'static, TypeVariant> {
                Self::VARIANTS.iter()
            }
        }

        impl core::str::FromStr for TypeVariant {
            type Err = Error;
            #[allow(clippy::too_many_lines)]
            fn from_str(s: &str) -> Result<Self> {
                match s {
                    #{types.map { |t| "\"#{t}\" => Ok(Self::#{t})," }.join("\n")}
                    _ => Err(Error::Invalid),
                }
            }
        }

        #[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
        #[cfg_attr(
          all(feature = "serde", feature = "alloc"),
          derive(serde::Serialize, serde::Deserialize),
          serde(rename_all = "snake_case"),
          serde(untagged),
        )]
        pub enum Type {
            #{types.map { |t| "#{t}(Box<#{t}>)," }.join("\n")}
        }

        impl Type {
            pub const VARIANTS: [TypeVariant; #{types.count}] = [ #{types.map { |t| "TypeVariant::#{t}," }.join("\n")} ];
            pub const VARIANTS_STR: [&'static str; #{types.count}] = [ #{types.map { |t| "\"#{t}\"," }.join("\n")} ];

            #[cfg(feature = "std")]
            #[allow(clippy::too_many_lines)]
            pub fn read_xdr<R: Read>(v: TypeVariant, r: &mut Limited<R>) -> Result<Self> {
                match v {
                    #{types.map { |t| "TypeVariant::#{t} => r.with_limited_depth(|r| Ok(Self::#{t}(Box::new(#{t}::read_xdr(r)?))))," }.join("\n")}
                }
            }

            #[cfg(feature = "base64")]
            pub fn read_xdr_base64<R: Read>(v: TypeVariant, r: &mut Limited<R>) -> Result<Self> {
                let mut dec = Limited::new(base64::read::DecoderReader::new(&mut r.inner, base64::STANDARD), r.limits.clone());
                let t = Self::read_xdr(v, &mut dec)?;
                Ok(t)
            }

            #[cfg(feature = "std")]
            pub fn read_xdr_to_end<R: Read>(v: TypeVariant, r: &mut Limited<R>) -> Result<Self> {
                let s = Self::read_xdr(v, r)?;
                // Check that any further reads, such as this read of one byte, read no
                // data, indicating EOF. If a byte is read the data is invalid.
                if r.read(&mut [0u8; 1])? == 0 {
                    Ok(s)
                } else {
                    Err(Error::Invalid)
                }
            }

            #[cfg(feature = "base64")]
            pub fn read_xdr_base64_to_end<R: Read>(v: TypeVariant, r: &mut Limited<R>) -> Result<Self> {
                let mut dec = Limited::new(base64::read::DecoderReader::new(&mut r.inner, base64::STANDARD), r.limits.clone());
                let t = Self::read_xdr_to_end(v, &mut dec)?;
                Ok(t)
            }

            #[cfg(feature = "std")]
            #[allow(clippy::too_many_lines)]
            pub fn read_xdr_iter<R: Read>(v: TypeVariant, r: &mut Limited<R>) -> Box<dyn Iterator<Item=Result<Self>> + '_> {
                match v {
                    #{types.map { |t| "TypeVariant::#{t} => Box::new(ReadXdrIter::<_, #{t}>::new(&mut r.inner, r.limits.clone()).map(|r| r.map(|t| Self::#{t}(Box::new(t)))))," }.join("\n")}
                }
            }

            #[cfg(feature = "std")]
            #[allow(clippy::too_many_lines)]
            pub fn read_xdr_framed_iter<R: Read>(v: TypeVariant, r: &mut Limited<R>) -> Box<dyn Iterator<Item=Result<Self>> + '_> {
                match v {
                    #{types.map { |t| "TypeVariant::#{t} => Box::new(ReadXdrIter::<_, Frame<#{t}>>::new(&mut r.inner, r.limits.clone()).map(|r| r.map(|t| Self::#{t}(Box::new(t.0)))))," }.join("\n")}
                }
            }

            #[cfg(feature = "base64")]
            #[allow(clippy::too_many_lines)]
            pub fn read_xdr_base64_iter<R: Read>(v: TypeVariant, r: &mut Limited<R>) -> Box<dyn Iterator<Item=Result<Self>> + '_> {
                let dec = base64::read::DecoderReader::new(&mut r.inner, base64::STANDARD);
                match v {
                    #{types.map { |t| "TypeVariant::#{t} => Box::new(ReadXdrIter::<_, #{t}>::new(dec, r.limits.clone()).map(|r| r.map(|t| Self::#{t}(Box::new(t)))))," }.join("\n")}
                }
            }

            #[cfg(feature = "std")]
            pub fn from_xdr<B: AsRef<[u8]>>(v: TypeVariant, bytes: B, limits: Limits) -> Result<Self> {
                let mut cursor = Limited::new(Cursor::new(bytes.as_ref()), limits);
                let t = Self::read_xdr_to_end(v, &mut cursor)?;
                Ok(t)
            }

            #[cfg(feature = "base64")]
            pub fn from_xdr_base64(v: TypeVariant, b64: impl AsRef<[u8]>, limits: Limits) -> Result<Self> {
                let mut b64_reader = Cursor::new(b64);
                let mut dec = Limited::new(base64::read::DecoderReader::new(&mut b64_reader, base64::STANDARD), limits);
                let t = Self::read_xdr_to_end(v, &mut dec)?;
                Ok(t)
            }

            #[cfg(all(feature = "std", feature = "serde_json"))]
            #[allow(clippy::too_many_lines)]
            pub fn read_json(v: TypeVariant, r: impl Read) -> Result<Self> {
                match v {
                    #{types.map { |t| "TypeVariant::#{t} => Ok(Self::#{t}(Box::new(serde_json::from_reader(r)?)))," }.join("\n")}
                }
            }

            #[cfg(feature = "alloc")]
            #[must_use]
            #[allow(clippy::too_many_lines)]
            pub fn value(&self) -> &dyn core::any::Any {
                #[allow(clippy::match_same_arms)]
                match self {
                    #{types.map { |t| "Self::#{t}(ref v) => v.as_ref()," }.join("\n")}
                }
            }

            #[must_use]
            #[allow(clippy::too_many_lines)]
            pub const fn name(&self) -> &'static str {
                match self {
                    #{types.map { |t| "Self::#{t}(_) => \"#{t}\"," }.join("\n")}
                }
            }

            #[must_use]
            #[allow(clippy::too_many_lines)]
            pub const fn variants() -> [TypeVariant; #{types.count}] {
                Self::VARIANTS
            }

            #[must_use]
            #[allow(clippy::too_many_lines)]
            pub const fn variant(&self) -> TypeVariant {
                match self {
                    #{types.map { |t| "Self::#{t}(_) => TypeVariant::#{t}," }.join("\n")}
                }
            }
        }

        impl Name for Type {
            #[must_use]
            fn name(&self) -> &'static str {
                Self::name(self)
            }
        }

        impl Variants<TypeVariant> for Type {
            fn variants() -> slice::Iter<'static, TypeVariant> {
                Self::VARIANTS.iter()
            }
        }

        impl WriteXdr for Type {
            #[cfg(feature = "std")]
            #[allow(clippy::too_many_lines)]
            fn write_xdr<W: Write>(&self, w: &mut Limited<W>) -> Result<()> {
                match self {
                    #{types.map { |t| "Self::#{t}(v) => v.write_xdr(w)," }.join("\n")}
                }
            }
        }
        EOS
        out.break
      end

      def render_definitions(out, node)
        node.definitions.each{|n| render_definition out, n }
        node.namespaces.each{|n| render_definitions out, n }
      end

      def render_definition(out, defn)
        if @already_rendered.include? name(defn)

          unless defn.is_a?(AST::Definitions::Namespace)
            $stderr.puts "warn: #{name(defn)} is defined twice.  skipping"
          end

          return
        end

        render_nested_definitions(out, defn)
        render_source_comment(out, defn)

        @already_rendered << name(defn)

        case defn
        when AST::Definitions::Struct ;
          render_struct out, defn
        when AST::Definitions::Enum ;
          render_enum out, defn
        when AST::Definitions::Union ;
          render_union out, defn
        when AST::Definitions::Typedef ;
          render_typedef out, defn
        when AST::Definitions::Const ;
          render_const out, defn
        end
      end

      def render_nested_definitions(out, defn)
        return unless defn.respond_to? :nested_definitions
        defn.nested_definitions.each{|ndefn| render_definition out, ndefn}
      end

      def render_source_comment(out, defn)
        return if defn.is_a?(AST::Definitions::Namespace)

        out.puts <<-EOS.strip_heredoc
          /// #{name defn} is an XDR #{defn.class.name.demodulize} defines as:
          ///
          /// ```text
        EOS

        out.puts "/// " + defn.text_value.split("\n").join("\n/// ")

        out.puts <<-EOS.strip_heredoc
          /// ```
          ///
        EOS
      end

      def render_struct(out, struct)
        out.puts "#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]"
        out.puts %{#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]}
        if @options[:rust_types_custom_str_impl].include?(name struct)
          out.puts %{#[cfg_attr(all(feature = "serde", feature = "alloc"), derive(serde_with::SerializeDisplay, serde_with::DeserializeFromStr))]}
        else
          out.puts %{#[cfg_attr(all(feature = "serde", feature = "alloc"), derive(serde::Serialize, serde::Deserialize), serde(rename_all = "snake_case"))]}
        end
        out.puts "pub struct #{name struct} {"
        out.indent do
          struct.members.each do |m|
            out.puts "pub #{field_name m}: #{reference(struct, m.declaration.type)},"
          end
        end
        out.puts "}"
        out.puts ""
        out.puts <<-EOS.strip_heredoc
        impl ReadXdr for #{name struct} {
            #[cfg(feature = "std")]
            fn read_xdr<R: Read>(r: &mut Limited<R>) -> Result<Self> {
                r.with_limited_depth(|r| {
                    Ok(Self{
                      #{struct.members.map do |m|
                        "#{field_name(m)}: #{reference_to_call(struct, m.declaration.type)}::read_xdr(r)?,"
                      end.join("\n")}
                    })
                })
            }
        }

        impl WriteXdr for #{name struct} {
            #[cfg(feature = "std")]
            fn write_xdr<W: Write>(&self, w: &mut Limited<W>) -> Result<()> {
                w.with_limited_depth(|w| {
                    #{struct.members.map do |m|
                      "self.#{field_name(m)}.write_xdr(w)?;"
                    end.join("\n")}
                    Ok(())
                })
            }
        }
        EOS
        out.break
      end

      def render_enum(out, enum)
        out.puts "// enum"
        out.puts "#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]"
        out.puts %{#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]}
        if @options[:rust_types_custom_str_impl].include?(name enum)
          out.puts %{#[cfg_attr(all(feature = "serde", feature = "alloc"), derive(serde_with::SerializeDisplay, serde_with::DeserializeFromStr))]}
        else
          out.puts %{#[cfg_attr(all(feature = "serde", feature = "alloc"), derive(serde::Serialize, serde::Deserialize), serde(rename_all = "snake_case"))]}
        end
        out.puts "#[repr(i32)]"
        out.puts "pub enum #{name enum} {"
        out.indent do
          enum.members.each do |m|
            out.puts "#{name m} = #{m.value},"
          end
        end
        out.puts '}'
        out.puts ""
        out.puts <<-EOS.strip_heredoc
        impl #{name enum} {
            pub const VARIANTS: [#{name enum}; #{enum.members.count}] = [ #{enum.members.map { |m| "#{name enum}::#{name m}," }.join("\n")} ];
            pub const VARIANTS_STR: [&'static str; #{enum.members.count}] = [ #{enum.members.map { |m| "\"#{name m}\"," }.join("\n")} ];

            #[must_use]
            pub const fn name(&self) -> &'static str {
                match self {
                    #{enum.members.map do |m|
                      "Self::#{name m} => \"#{name m}\","
                    end.join("\n")}
                }
            }

            #[must_use]
            pub const fn variants() -> [#{name enum}; #{enum.members.count}] {
                Self::VARIANTS
            }
        }

        impl Name for #{name enum} {
            #[must_use]
            fn name(&self) -> &'static str {
                Self::name(self)
            }
        }

        impl Variants<#{name enum}> for #{name enum} {
            fn variants() -> slice::Iter<'static, #{name enum}> {
                Self::VARIANTS.iter()
            }
        }

        impl Enum for #{name enum} {}

        impl fmt::Display for #{name enum} {
            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
                f.write_str(self.name())
            }
        }

        impl TryFrom<i32> for #{name enum} {
            type Error = Error;

            fn try_from(i: i32) -> Result<Self> {
                let e = match i {
                    #{enum.members.map do |m| "#{m.value} => #{name enum}::#{name m}," end.join("\n")}
                    #[allow(unreachable_patterns)]
                    _ => return Err(Error::Invalid),
                };
                Ok(e)
            }
        }

        impl From<#{name enum}> for i32 {
            #[must_use]
            fn from(e: #{name enum}) -> Self {
                e as Self
            }
        }

        impl ReadXdr for #{name enum} {
            #[cfg(feature = "std")]
            fn read_xdr<R: Read>(r: &mut Limited<R>) -> Result<Self> {
                r.with_limited_depth(|r| {
                    let e = i32::read_xdr(r)?;
                    let v: Self = e.try_into()?;
                    Ok(v)
                })
            }
        }

        impl WriteXdr for #{name enum} {
            #[cfg(feature = "std")]
            fn write_xdr<W: Write>(&self, w: &mut Limited<W>) -> Result<()> {
                w.with_limited_depth(|w| {
                    let i: i32 = (*self).into();
                    i.write_xdr(w)
                })
            }
        }
        EOS
        out.break
      end

      def union_is_idents(union)
        union.normal_arms.first&.cases.first&.value.is_a?(AST::Identifier)
      end

      def union_cases(union)
        results = []
        union.normal_arms.each do |arm|
          arm.cases.each do |kase|
              if kase.value.is_a?(AST::Identifier)
                case_name = kase.name_short.underscore.camelize
                value = nil
              else
                case_name = "V#{kase.value.value}"
                value = kase.value.value
              end
              results << yield(case_name, arm, value)
          end
        end
        results
      end

      def render_union(out, union)
        if union.default_arm.present?
          $stderr.puts "warn: union #{name union} includes default arms and default arms are not supported in the rust generator"
        end
        discriminant_type = reference(nil, union.discriminant.type)
        discriminant_type_builtin = is_builtin_type(union.discriminant.type) || (is_builtin_type(union.discriminant.type.resolved_type.type) if union.discriminant.type.respond_to?(:resolved_type) && AST::Definitions::Typedef === union.discriminant.type.resolved_type)
        out.puts "// union with discriminant #{discriminant_type}"
        out.puts "#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]"
        out.puts %{#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]}
        if @options[:rust_types_custom_str_impl].include?(name union)
          out.puts %{#[cfg_attr(all(feature = "serde", feature = "alloc"), derive(serde_with::SerializeDisplay, serde_with::DeserializeFromStr))]}
        else
          out.puts %{#[cfg_attr(all(feature = "serde", feature = "alloc"), derive(serde::Serialize, serde::Deserialize), serde(rename_all = "snake_case"))]}
        end
        out.puts "#[allow(clippy::large_enum_variant)]"
        out.puts "pub enum #{name union} {"
        union_case_count = 0
        out.indent do
          union_cases(union) do |case_name, arm|
            union_case_count += 1
            out.puts arm.void? ? "#{case_name}#{"(())" unless arm.void?}," : "#{case_name}(#{reference(union, arm.type)}),"
          end
        end
        out.puts '}'
        out.puts ""
        out.puts <<-EOS.strip_heredoc
        impl #{name union} {
            pub const VARIANTS: [#{discriminant_type}; #{union_case_count}] = [
                #{union_cases(union) do |case_name, arm, value|
                  value.nil?                ? "#{discriminant_type}::#{case_name}," :
                  discriminant_type_builtin ? "#{value}," :
                                              "#{discriminant_type}(#{value}),"
                end.join("\n")}
            ];
            pub const VARIANTS_STR: [&'static str; #{union_case_count}] = [
                #{union_cases(union) do |case_name, arm, value|
                  "\"#{case_name}\","
                end.join("\n")}
            ];

            #[must_use]
            pub const fn name(&self) -> &'static str {
                match self {
                    #{union_cases(union) do |case_name, arm|
                      "Self::#{case_name}#{"(_)" unless arm.void?} => \"#{case_name}\","
                    end.join("\n")}
                }
            }

            #[must_use]
            pub const fn discriminant(&self) -> #{discriminant_type} {
                #[allow(clippy::match_same_arms)]
                match self {
                    #{union_cases(union) do |case_name, arm, value|
                      "Self::#{case_name}#{"(_)" unless arm.void?} => #{
                        value.nil?                ? "#{discriminant_type}::#{case_name}" :
                        discriminant_type_builtin ? "#{value}" :
                                                    "#{discriminant_type}(#{value})"
                      },"
                    end.join("\n")}
                }
            }

            #[must_use]
            pub const fn variants() -> [#{discriminant_type}; #{union_case_count}] {
                Self::VARIANTS
            }
        }

        impl Name for #{name union} {
            #[must_use]
            fn name(&self) -> &'static str {
                Self::name(self)
            }
        }

        impl Discriminant<#{discriminant_type}> for #{name union} {
            #[must_use]
            fn discriminant(&self) -> #{discriminant_type} {
                Self::discriminant(self)
            }
        }

        impl Variants<#{discriminant_type}> for #{name union} {
            fn variants() -> slice::Iter<'static, #{discriminant_type}> {
                Self::VARIANTS.iter()
            }
        }

        impl Union<#{discriminant_type}> for #{name union} {}

        impl ReadXdr for #{name union} {
            #[cfg(feature = "std")]
            fn read_xdr<R: Read>(r: &mut Limited<R>) -> Result<Self> {
                r.with_limited_depth(|r| {
                    let dv: #{discriminant_type} = <#{discriminant_type} as ReadXdr>::read_xdr(r)?;
                    #[allow(clippy::match_same_arms, clippy::match_wildcard_for_single_variants)]
                    let v = match dv {
                        #{union_cases(union) do |case_name, arm, value|
                          "#{
                            value.nil? ? "#{discriminant_type}::#{case_name}" : "#{value}"
                          } => #{
                            arm.void? ? "Self::#{case_name}" : "Self::#{case_name}(#{reference_to_call(union, arm.type)}::read_xdr(r)?)"
                          },"
                        end.join("\n")}
                        #[allow(unreachable_patterns)]
                        _ => return Err(Error::Invalid),
                    };
                    Ok(v)
                })
            }
        }

        impl WriteXdr for #{name union} {
            #[cfg(feature = "std")]
            fn write_xdr<W: Write>(&self, w: &mut Limited<W>) -> Result<()> {
                w.with_limited_depth(|w| {
                    self.discriminant().write_xdr(w)?;
                    #[allow(clippy::match_same_arms)]
                    match self {
                        #{union_cases(union) do |case_name, arm, value|
                          if arm.void?
                            "Self::#{case_name} => ().write_xdr(w)?,"
                          else
                            "Self::#{case_name}(v) => v.write_xdr(w)?,"
                          end
                        end.join("\n")}
                    };
                    Ok(())
                })
            }
        }
        EOS
        out.break
      end

      def render_typedef(out, typedef)
        if is_builtin_type(typedef.type)
          out.puts "pub type #{name typedef} = #{reference(typedef, typedef.type)};"
        else
          out.puts "#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]"
          out.puts %{#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]}
          out.puts "#[derive(Default)]" if is_var_array_type(typedef.type)
          if is_fixed_array_opaque(typedef.type) || @options[:rust_types_custom_str_impl].include?(name typedef)
            out.puts %{#[cfg_attr(all(feature = "serde", feature = "alloc"), derive(serde_with::SerializeDisplay, serde_with::DeserializeFromStr))]}
          else
            out.puts %{#[cfg_attr(all(feature = "serde", feature = "alloc"), derive(serde::Serialize, serde::Deserialize), serde(rename_all = "snake_case"))]}
          end
          if !is_fixed_array_opaque(typedef.type)
            out.puts "#[derive(Debug)]"
          end
          out.puts "pub struct #{name typedef}(pub #{reference(typedef, typedef.type)});"
          out.puts ""
          if is_fixed_array_opaque(typedef.type)
          out.puts <<-EOS.strip_heredoc
          impl core::fmt::Debug for #{name typedef} {
            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
                let v = &self.0;
                write!(f, "#{name typedef}(")?;
                for b in v {
                    write!(f, "{b:02x}")?;
                }
                write!(f, ")")?;
                Ok(())
            }
          }
          EOS
          end
          if is_fixed_array_opaque(typedef.type) && !@options[:rust_types_custom_str_impl].include?(name typedef)
          out.puts <<-EOS.strip_heredoc
          impl core::fmt::Display for #{name typedef} {
            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
                let v = &self.0;
                for b in v {
                    write!(f, "{b:02x}")?;
                }
                Ok(())
            }
          }

          #[cfg(feature = "alloc")]
          impl core::str::FromStr for #{name typedef} {
            type Err = Error;
            fn from_str(s: &str) -> core::result::Result<Self, Self::Err> {
                hex::decode(s).map_err(|_| Error::InvalidHex)?.try_into()
            }
          }
          EOS
          end
          out.puts <<-EOS.strip_heredoc
          impl From<#{name typedef}> for #{reference(typedef, typedef.type)} {
              #[must_use]
              fn from(x: #{name typedef}) -> Self {
                  x.0
              }
          }

          impl From<#{reference(typedef, typedef.type)}> for #{name typedef} {
              #[must_use]
              fn from(x: #{reference(typedef, typedef.type)}) -> Self {
                  #{name typedef}(x)
              }
          }

          impl AsRef<#{reference(typedef, typedef.type)}> for #{name typedef} {
              #[must_use]
              fn as_ref(&self) -> &#{reference(typedef, typedef.type)} {
                  &self.0
              }
          }

          impl ReadXdr for #{name typedef} {
              #[cfg(feature = "std")]
              fn read_xdr<R: Read>(r: &mut Limited<R>) -> Result<Self> {
                  r.with_limited_depth(|r| {
                      let i = #{reference_to_call(typedef, typedef.type)}::read_xdr(r)?;
                      let v = #{name typedef}(i);
                      Ok(v)
                  })
              }
          }

          impl WriteXdr for #{name typedef} {
              #[cfg(feature = "std")]
              fn write_xdr<W: Write>(&self, w: &mut Limited<W>) -> Result<()> {
                  w.with_limited_depth(|w|{ self.0.write_xdr(w) })
              }
          }
          EOS
          if is_fixed_array_type(typedef.type)
            out.break
            out.puts <<-EOS.strip_heredoc
            impl #{name typedef} {
                #[must_use]
                pub fn as_slice(&self) -> &[#{element_type_for_vec(typedef.type)}] {
                    &self.0
                }
            }

            #[cfg(feature = "alloc")]
            impl TryFrom<Vec<#{element_type_for_vec(typedef.type)}>> for #{name typedef} {
                type Error = Error;
                fn try_from(x: Vec<#{element_type_for_vec(typedef.type)}>) -> Result<Self> {
                    x.as_slice().try_into()
                }
            }

            #[cfg(feature = "alloc")]
            impl TryFrom<&Vec<#{element_type_for_vec(typedef.type)}>> for #{name typedef} {
                type Error = Error;
                fn try_from(x: &Vec<#{element_type_for_vec(typedef.type)}>) -> Result<Self> {
                    x.as_slice().try_into()
                }
            }

            impl TryFrom<&[#{element_type_for_vec(typedef.type)}]> for #{name typedef} {
                type Error = Error;
                fn try_from(x: &[#{element_type_for_vec(typedef.type)}]) -> Result<Self> {
                    Ok(#{name typedef}(x.try_into()?))
                }
            }

            impl AsRef<[#{element_type_for_vec(typedef.type)}]> for #{name typedef} {
                #[must_use]
                fn as_ref(&self) -> &[#{element_type_for_vec(typedef.type)}] {
                    &self.0
                }
            }
            EOS
          end
          if is_var_array_type(typedef.type)
            out.break
            out.puts <<-EOS.strip_heredoc
            impl Deref for #{name typedef} {
              type Target = #{reference(typedef, typedef.type)};
              fn deref(&self) -> &Self::Target {
                  &self.0
              }
            }

            impl From<#{name typedef}> for Vec<#{element_type_for_vec(typedef.type)}> {
                #[must_use]
                fn from(x: #{name typedef}) -> Self {
                    x.0.0
                }
            }

            impl TryFrom<Vec<#{element_type_for_vec(typedef.type)}>> for #{name typedef} {
                type Error = Error;
                fn try_from(x: Vec<#{element_type_for_vec(typedef.type)}>) -> Result<Self> {
                    Ok(#{name typedef}(x.try_into()?))
                }
            }

            #[cfg(feature = "alloc")]
            impl TryFrom<&Vec<#{element_type_for_vec(typedef.type)}>> for #{name typedef} {
                type Error = Error;
                fn try_from(x: &Vec<#{element_type_for_vec(typedef.type)}>) -> Result<Self> {
                    Ok(#{name typedef}(x.try_into()?))
                }
            }

            impl AsRef<Vec<#{element_type_for_vec(typedef.type)}>> for #{name typedef} {
                #[must_use]
                fn as_ref(&self) -> &Vec<#{element_type_for_vec(typedef.type)}> {
                    &self.0.0
                }
            }

            impl AsRef<[#{element_type_for_vec(typedef.type)}]> for #{name typedef} {
                #[cfg(feature = "alloc")]
                #[must_use]
                fn as_ref(&self) -> &[#{element_type_for_vec(typedef.type)}] {
                    &self.0.0
                }
                #[cfg(not(feature = "alloc"))]
                #[must_use]
                fn as_ref(&self) -> &[#{element_type_for_vec(typedef.type)}] {
                    self.0.0
                }
            }
            EOS
          end
        end
        out.break
      end

      def render_const(out, const)
        out.puts "pub const #{name(const).underscore.upcase}: u64 = #{const.value};"
        out.break
      end

      def is_builtin_type(type)
        [
          AST::Typespecs::Bool,
          AST::Typespecs::Double, AST::Typespecs::Float,
          AST::Typespecs::UnsignedHyper, AST::Typespecs::UnsignedInt,
          AST::Typespecs::Hyper, AST::Typespecs::Int,
        ].any? { |t| t === type }
      end

      def is_fixed_array_opaque(type)
        (AST::Typespecs::Opaque === type && type.fixed?)
      end

      def is_fixed_array_type(type)
        (AST::Typespecs::Opaque === type && type.fixed?) ||
        (type.sub_type == :array)
      end

      def is_var_array_type(type)
        (AST::Typespecs::Opaque === type && !type.fixed?) ||
        (AST::Typespecs::String === type) ||
        (type.sub_type == :var_array)
      end

      def base_reference(type)
        case type
        when AST::Typespecs::Bool
          'bool'
        when AST::Typespecs::Double
          $stderr.puts "warn: rust generator has not implemented f64 support"
          'f64'
        when AST::Typespecs::Float
          $stderr.puts "warn: rust generator has not implemented f64 support"
          'f32'
        when AST::Typespecs::UnsignedHyper
          'u64'
        when AST::Typespecs::UnsignedInt
          'u32'
        when AST::Typespecs::Hyper
          'i64'
        when AST::Typespecs::Int
          'i32'
        when AST::Typespecs::Quadruple
          raise 'no quadruple support for rust'
        when AST::Typespecs::String
          if !type.decl.resolved_size.nil?
            "StringM::<#{type.decl.resolved_size}>"
          else
            "StringM"
          end
        when AST::Typespecs::Opaque
          if type.fixed?
            "[u8; #{type.size}]"
          elsif !type.decl.resolved_size.nil?
            "BytesM::<#{type.decl.resolved_size}>"
          else
            "BytesM"
          end
        when AST::Typespecs::Simple, AST::Definitions::Base, AST::Concerns::NestedDefinition
          if type.respond_to?(:resolved_type) && AST::Definitions::Typedef === type.resolved_type && is_builtin_type(type.resolved_type.type)
            base_reference(type.resolved_type.type)
          else
            name type
          end
        else
          raise "Unknown reference type: #{type.class.name}, #{type.class.ancestors}"
        end
      end

      def reference(parent, type)
        base_ref = base_reference type

        parent_name = name(parent) if parent
        cyclic = is_type_in_type_field_types(base_ref, parent_name)

        case type.sub_type
        when :simple
          if cyclic
            "Box<#{base_ref}>"
          else
            base_ref
          end
        when :optional
          if cyclic
            "Option<Box<#{base_ref}>>"
          else
            "Option<#{base_ref}>"
          end
        when :array
          is_named, size = type.array_size
          size = name @top.find_definition(size) if is_named
          "[#{base_ref}; #{size}]"
        when :var_array
          if !type.decl.resolved_size.nil?
            "VecM::<#{base_ref}, #{type.decl.resolved_size}>"
          else
            "VecM::<#{base_ref}>"
          end
        else
          raise "Unknown sub_type: #{type.sub_type}"
        end
      end

      def element_type_for_vec(type)
        case type
        when AST::Typespecs::String
          "u8"
        when AST::Typespecs::Opaque
          "u8"
        when AST::Typespecs::Simple, AST::Definitions::Base, AST::Concerns::NestedDefinition
          if type.respond_to?(:resolved_type) && AST::Definitions::Typedef === type.resolved_type && is_builtin_type(type.resolved_type.type)
            base_reference(type.resolved_type.type)
          else
            name type
          end
        else
          raise "Unknown element type for vec: #{type.class.name}, #{type.class.ancestors}"
        end
      end

      def base_reference_to_call(type)
        case type
        when AST::Typespecs::String
          if !type.decl.resolved_size.nil?
            "StringM::<#{type.decl.resolved_size}>"
          else
            "StringM"
          end
        when AST::Typespecs::Opaque
          if type.fixed?
            "[u8; #{type.size}]"
          elsif !type.decl.resolved_size.nil?
            "BytesM::<#{type.decl.resolved_size}>"
          else
            "BytesM"
          end
        when AST::Typespecs::Simple, AST::Definitions::Base, AST::Concerns::NestedDefinition
          if type.respond_to?(:resolved_type) && AST::Definitions::Typedef === type.resolved_type && is_builtin_type(type.resolved_type.type)
            base_reference_to_call(type.resolved_type.type)
          else
            base_reference(type)
          end
        else
          base_reference(type)
        end
      end

      def reference_to_call(parent, type)
        base_ref = base_reference_to_call(type)

        parent_name = name(parent) if parent
        cyclic = is_type_in_type_field_types(base_ref, parent_name)

        ref = case type.sub_type
        when :simple
          if cyclic
            "Box<#{base_ref}>"
          else
            base_ref
          end
        when :optional
          if cyclic
            "Option::<Box<#{base_ref}>>"
          else
            "Option::<#{base_ref}>"
          end
        when :array
          is_named, size = type.array_size
          size = name @top.find_definition(size) if is_named
          "[#{base_ref}; #{size}]"
        when :var_array
          if !type.decl.resolved_size.nil?
            "VecM::<#{base_ref}, #{type.decl.resolved_size}>"
          else
            "VecM::<#{base_ref}>"
          end
        else
          raise "Unknown sub_type: #{type.sub_type}"
        end

        if ref.starts_with?("[") && ref.ends_with?("]")
          "<#{ref}>"
        elsif ref.starts_with?("Box<") && ref.ends_with?(">")
          "Box::#{ref.delete_prefix("Box")}"
        else
          ref
        end
      end

      def name(named)
        parent = name named.parent_defn if named.is_a?(AST::Concerns::NestedDefinition)

        base = if named.respond_to?(:name_short)
          named.name_short
        elsif named.respond_to?(:name)
          named.name
        else
          named.text_value
        end
        base = escape_name(base)
        "#{parent}#{base.underscore.camelize}"
      end

      def field_name(named)
        escape_name named.name.underscore
      end

      def escape_name(name)
        case name
        when 'type' then 'type_'
        when 'Error' then 'SError'
        else name
        end
      end

    end
  end
end