Skip to content

AotReflection: strip nullable annotation from emitted typeof(...) (CS8639)#9005

Open
Evangelink wants to merge 2 commits into
microsoft:mainfrom
Evangelink:dev/amauryleve/aot-reflection-srcgen-typeof-nullable
Open

AotReflection: strip nullable annotation from emitted typeof(...) (CS8639)#9005
Evangelink wants to merge 2 commits into
microsoft:mainfrom
Evangelink:dev/amauryleve/aot-reflection-srcgen-typeof-nullable

Conversation

@Evangelink

Copy link
Copy Markdown
Member

Part of #1837. Depends on #9004 (introduces the test project used here).

Why

While building the AotReflection unit-test project in #9004, the new compile-clean snapshot test surfaced a real bug in the PoC: the generated source produced CS8639: The typeof operator cannot be used on a nullable reference type.

Root cause

TestClassModelBuilder.FullyQualifiedFormat was built with SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier. The resulting FQN string was then fed into two very different emission sites:

  • Casts like (global::Sample.TestContext?)value! — where the ? is harmless (the value! already suppresses the nullability warning).
  • typeof(...) expressions — where ? on a reference type is invalid C# (CS8639).

Fix

Drop IncludeNullableReferenceTypeModifier from FullyQualifiedFormat. This:

  • Strips ? from reference-type FQNs everywhere (no behavioural impact on casts).
  • Preserves ? on nullable value types (int?, DateTime?) because those are rendered via UseSpecialTypes / generics options, which are unaffected.

The property-setter snapshot assertion is updated to match ((global::Sample.TestContext)value! instead of (global::Sample.TestContext?)value!).

Regression test

New Generator_StripsNullableAnnotation_FromTypeofExpressions test:

  • Uses a class with a TestContext? property, a string? parameter, and an int? parameter.
  • Compiles the generated registry with the Roslyn C# compiler.
  • Asserts text shape: typeof(global::Sample.TestContext), typeof(string), typeof(int?); and the absence of typeof(global::Sample.TestContext?) / typeof(string?).
  • Asserts there are zero compile errors in the emitted code.

Validation

dotnet build + dotnet run for MSTest.AotReflection.SourceGeneration.UnitTests: 14/14 passing, 0 warnings.

Amaury Levé and others added 2 commits June 10, 2026 14:42
Adds a focused unit-test project for the AotReflection source generator PoC introduced in microsoft#8574. The PoC had no test coverage until now.

Coverage highlights (13 tests):

- Support types emission (TestClassReflectionInfo, TestMethodReflectionInfo, TestPropertyReflectionInfo, TestConstructorReflectionInfo).

- Registry emission shape and namespace (MSTest.SourceGenerated).

- Empty registry when no [TestClass] is present.

- Skipping of static / abstract / open-generic test classes.

- Constructor invoker, parameter types / names, async return shape.

- Class-level attribute capture; property getter & setter delegate text.

- Compile-clean snapshot (catches CS errors the generator may introduce).

- Incrementality: support-types step is cached when input is unchanged.

Also:

- Adds MSTest.AotReflection.SourceGeneration to TestFx.slnx and MSTest.slnf (missing since microsoft#8574).

- Adds [InternalsVisibleTo] for the new test project (generator class is internal sealed).

Part of microsoft#1837.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…8639)

The PoC's TestClassModelBuilder built its fully-qualified type names with SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier, then fed the resulting FQN into both casts (where '?' is harmless and merely cosmetic, since the emitted setter already uses 'value!') and 'typeof(...)' expressions (where '?' on a reference type is invalid C# and produces CS8639).

Removing the flag fixes 'typeof(string?)' / 'typeof(MyRef?)' while preserving 'typeof(int?)' (nullable value types are rendered via UseSpecialTypes, which is unaffected).

Adds a focused regression test that runs the Roslyn compiler over the generated source and asserts both the textual shape ('typeof(global::Sample.TestContext)', 'typeof(string)', 'typeof(int?)') and the absence of any compile errors.

Discovered while building tests for microsoft#8574; depends on microsoft#9004 for the test infrastructure.

Part of microsoft#1837.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 10, 2026 13:06

Copilot AI 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.

Pull request overview

Fixes a C# source-generation bug in the MSTest AOT reflection PoC where nullable reference annotations (?) were being emitted inside typeof(...), causing CS8639 during compilation. This supports the broader NativeAOT work tracked in #1837 and adds a focused unit-test project to prevent regressions.

Changes:

  • Adjust TestClassModelBuilder symbol display formatting to stop including nullable reference modifiers in fully-qualified type names, preventing typeof(SomeRefType?) emission.
  • Add a new unit-test project (MSTest.AotReflection.SourceGeneration.UnitTests) with a regression test that compiles the generated output and asserts the corrected typeof(...) shapes.
  • Wire the generator and its unit-test project into TestFx.slnx and MSTest.slnf for discoverability/build coverage.
Show a summary per file
File Description
TestFx.slnx Adds the AOT reflection generator + its new unit-test project to the main solution.
MSTest.slnf Includes the AOT reflection generator project in the MSTest solution filter.
test/UnitTests/MSTest.AotReflection.SourceGeneration.UnitTests/Program.cs Test host entry point for running the new unit-test project under MTP.
test/UnitTests/MSTest.AotReflection.SourceGeneration.UnitTests/MSTestReflectionMetadataGeneratorTests.cs Adds baseline tests plus the new regression test ensuring nullable annotations are stripped from typeof(...) and the emitted code compiles without errors.
test/UnitTests/MSTest.AotReflection.SourceGeneration.UnitTests/MSTest.AotReflection.SourceGeneration.UnitTests.csproj New unit-test project referencing the generator and Roslyn for compile-clean snapshot validation.
src/Analyzers/MSTest.AotReflection.SourceGeneration/MSTest.AotReflection.SourceGeneration.csproj Grants test project access to the internal generator via InternalsVisibleTo.
src/Analyzers/MSTest.AotReflection.SourceGeneration/Generators/TestClassModelBuilder.cs Removes IncludeNullableReferenceTypeModifier from the fully-qualified display format to avoid emitting ? on reference types in typeof(...).

Copilot's findings

  • Files reviewed: 7/7 changed files
  • Comments generated: 0

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants