Skip to content

Rust doc comments are copied into C# XML doc comments without XML-escaping (CS1570) #291

Description

@sandersaares

The C# backend copies Rust doc comments verbatim into the generated /// lines. C# treats /// content as XML, but Rust doc comments routinely contain < and > — most commonly generic types written inside Markdown code spans, e.g. `Counter<u64>`. The generated C# is therefore not well-formed XML, and any consumer that enables GenerateDocumentationFile fails to build with CS1570 ("XML comment has badly formed XML").

This forces downstream projects to post-process the generated Interop.cs to XML-escape angle brackets, which should not be necessary.

Environment

  • interoptopus 0.16.1
  • interoptopus_csharp 0.16.1
  • rustc 1.95.0 (the issue requires only Rust ≥ 1.93, the interoptopus MSRV)
  • .NET SDK 10.0.301 (C# LangVersion default; also reproduces on net8.0)
  • Windows 10.0.26200

Minimal Rust input

use interoptopus::ffi;
use interoptopus::function;
use interoptopus::inventory::RustInventory;

/// Discriminates the numeric value type an instrument was declared with.
///
/// OpenTelemetry's synchronous instruments are generic over their value type
/// (`Counter<u64>`, `UpDownCounter<i64>`, `Gauge<f64>`). The bridge converts
/// every sample value to `f64` for transport.
#[ffi]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum InstrumentValueType {
    U64 = 0,
    I64 = 1,
    F64 = 2,
}

/// Returns the default instrument value type.
///
/// A real bridge would inspect the live instrument; this stand-in always reports
/// the `Counter<u64>` case so the doc comment carries a generic in a code span.
#[ffi]
pub fn default_instrument_value_type() -> InstrumentValueType {
    InstrumentValueType::U64
}

pub fn ffi_inventory() -> RustInventory {
    RustInventory::new()
        .register(function!(default_instrument_value_type))
        .validate()
}

Generated C# output (the problem)

The backtick code spans are copied through unchanged, so <u64> etc. land directly in the XML doc comment:

/// Returns the default instrument value type.
///
/// A real bridge would inspect the live instrument; this stand-in always reports
/// the `Counter<u64>` case so the doc comment carries a generic in a code span.
[LibraryImport(NativeLib, EntryPoint = "default_instrument_value_type")]
public static partial InstrumentValueType default_instrument_value_type();

/// Discriminates the numeric value type an instrument was declared with.
///
/// OpenTelemetry's synchronous instruments are generic over their value type
/// (`Counter<u64>`, `UpDownCounter<i64>`, `Gauge<f64>`). The bridge converts
/// every sample value to `f64` for transport.
public partial struct InstrumentValueType { byte _variant; }

The backtick is Markdown, not XML, so the C# XML-doc parser sees <u64> as an opening element with no matching close tag.

Build error

With GenerateDocumentationFile enabled (and warnings treated as errors, which is common in production builds):

Interop.cs(54,1): error CS1570: XML comment has badly formed XML -- 'Expected an end tag for element 'u64'.'
Interop.cs(65,1): error CS1570: XML comment has badly formed XML -- 'Expected an end tag for element 'i64'.'
Interop.cs(65,1): error CS1570: XML comment has badly formed XML -- 'Expected an end tag for element 'u64'.'
Interop.cs(65,1): error CS1570: XML comment has badly formed XML -- 'Expected an end tag for element 'f64'.'

Expected behavior

The generated C# should always be well-formed XML in its /// comments. Because /// content is XML, any <, >, or & that originates from a Rust doc comment must be XML-escaped (&lt;, &gt;, &amp;) before it is emitted — independent of whether it sits inside a Markdown code span.

Markdown code spans (`code`) should be rendered as <c>code</c> with the inner text escaped, which would also render nicely in IDE tooltips. The minimum correct behavior, though, is to XML-escape free text copied from Rust docs so the output never breaks the C# build.

Metadata

Metadata

Assignees

No one assigned

    Labels

    backend-c#C# BackendcoreInteroptopus Core CrateenhancementMake existing things better.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions