Skip to content

AVRO-4331: [csharp] Fix unreachable code in generated Get()/Put() - #3937

Open
GBNikola wants to merge 2 commits into
apache:mainfrom
GBNikola:AVRO-4331-csharp-unreachable-code
Open

GBNikola wants to merge 2 commits into
apache:mainfrom
GBNikola:AVRO-4331-csharp-unreachable-code

Conversation

@GBNikola

Copy link
Copy Markdown

What is the purpose of the change

Fixes AVRO-4331. Generated Get(int fieldPos) / Put(int fieldPos, object) build their
switch body via CodeSnippetExpression, which CodeDom wraps in a
CodeExpressionStatement and always suffixes with a ; when emitting C#. Since every
switch arm returns or throws, that trailing ; is an unreachable empty statement.
Roslyn/dotnet build never flags it, but IDE analyzers with fuller flow analysis
(ReSharper/Rider) do, forcing consumers to add per-file suppressions for all avrogen
output.

This swaps CodeSnippetExpression for CodeSnippetStatement, which emits the switch
block verbatim with no appended semicolon. Both types live in System.CodeDom and are
available on every target framework the C# SDK already supports
(netstandard2.0/netstandard2.1 for the library, net6.0-net8.0 for tests), so
there's no compatibility impact.

Verifying this change

This change added tests and can be verified as follows:

  • Added RecordGetAndPutSwitchesShouldNotEmitUnreachableStatement in CodeGenTest.cs,
    which generates a record and asserts the generated Get/Put switch has no stray ;
    after its closing brace. Fails without the fix, passes with it.

Documentation

  • Does this pull request introduce a new feature? no

@github-actions github-actions Bot added the C# label Aug 10, 2026
@GBNikola
GBNikola force-pushed the AVRO-4331-csharp-unreachable-code branch from 5c562c4 to ac1fb34 Compare August 10, 2026 13:06
@uros-b

uros-b commented Aug 11, 2026

Copy link
Copy Markdown
Member

LGTM, thank you @GBNikola!

@GBNikola

Copy link
Copy Markdown
Author

@uros-b who can merge this PR?

@GBNikola

GBNikola commented Sep 3, 2026

Copy link
Copy Markdown
Author

@RyanSkraba can you check it as well here please?

@zcsizmadia zcsizmadia left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @GBNikola, the change does what it says. I checked that the new test fails without the fix and passes with it.

One regression in the generated output, though: the switch line now lands at column 0. For the test's Sample record:

 		public virtual object Get(int fieldPos)
 		{
-			switch (fieldPos)
+switch (fieldPos)
 			{
 			case 0: return this.value;
 			default: throw new global::Avro.AvroRuntimeException("Bad index " + fieldPos + " in Get()");
-			};
+			}
 		}

Same for Put. The cause is how CodeDom's C# generator emits the two snippet types (CSharpCodeGenerator in dotnet/runtime):

  • A CodeSnippetExpression is wrapped in a CodeExpressionStatement, so the generator writes the current indent, then the snippet, then ;. That ; is the unreachable statement this PR removes. The indent in front of switch also came from the generator.
  • A CodeSnippetStatement is written with the indent deliberately reset to 0 ("Don't indent snippet statements, in order to preserve the column information from the original code"), followed by Output.WriteLine(e.Value).

Our snippet only indents itself partially. Every later line carries its own \t\t\t ("\t\t\tcase ", "\t\t\tdefault: ...", .Append("\t\t\t}")), but the builders start with a bare "switch (fieldPos)". Once the generator stops supplying the indent, only that first line loses it.

Since most users check the generated files in, this would show up as churn in their diffs. The fix is to seed both builders with the same indent the other lines use:

StringBuilder getFieldStmt = new StringBuilder("\t\t\tswitch (fieldPos)")
...
var putFieldStmt = new StringBuilder("\t\t\tswitch (fieldPos)")

It would be good to assert that in the test as well, e.g. StringAssert.Contains("\t\t\tswitch (fieldPos)", sampleCode).

Optional, for consistency: the protocol callback Request switch at CodeGen.cs:585 uses the same CodeSnippetExpression pattern. Its cases end in break;, so the trailing ; isn't unreachable there, but switching it to CodeSnippetStatement (with the same indent handling) would keep the generator uniform.

avrogen built the Get()/Put() switch body via CodeSnippetExpression,
which CodeDom wraps in a CodeExpressionStatement and always suffixes
with a ";". Since every switch arm returns or throws, that trailing
";" is unreachable. Roslyn stays quiet about it, but IDE analyzers
with fuller flow analysis (ReSharper/Rider) flag it, forcing
consumers to suppress CS0162 for all avrogen output.

Use CodeSnippetStatement instead, which emits the block verbatim
with no appended semicolon.
@GBNikola
GBNikola force-pushed the AVRO-4331-csharp-unreachable-code branch from ac1fb34 to 985e0ee Compare September 25, 2026 14:42

@zcsizmadia zcsizmadia left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @GBNikola, all three points are addressed. I checked locally: with CodeGen.cs reverted, the new tests fail. With only the indent removed, the indent assertions fail. The generated output now differs from main only by the removed ; in Get/Put/Request. LGTM.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants