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 (<, >, &) 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.
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 enablesGenerateDocumentationFilefails to build with CS1570 ("XML comment has badly formed XML").This forces downstream projects to post-process the generated
Interop.csto XML-escape angle brackets, which should not be necessary.Environment
interoptopus0.16.1interoptopus_csharp0.16.11.95.0(the issue requires only Rust ≥ 1.93, the interoptopus MSRV)10.0.301(C#LangVersiondefault; also reproduces on net8.0)Minimal Rust input
Generated C# output (the problem)
The backtick code spans are copied through unchanged, so
<u64>etc. land directly in the XML doc comment: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
GenerateDocumentationFileenabled (and warnings treated as errors, which is common in production builds):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 (<,>,&) 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.