Skip to content

Add MSTest reflection source generator (issue #1837)#8586

Open
Evangelink wants to merge 2 commits into
mainfrom
dev/amauryleve/sourcegen-reflection-issue-1837
Open

Add MSTest reflection source generator (issue #1837)#8586
Evangelink wants to merge 2 commits into
mainfrom
dev/amauryleve/sourcegen-reflection-issue-1837

Conversation

@Evangelink
Copy link
Copy Markdown
Member

@Evangelink Evangelink commented May 25, 2026

Fixes part of #1837.

What

Introduces a new Roslyn IIncrementalGenerator (project MSTest.SourceGeneration, under src/Analyzers/) that discovers [TestClass] types at compile time and emits a [ModuleInitializer] registering a SourceGeneratedReflectionDataProvider for the user's assembly.

It also adds the runtime infrastructure in MSTestAdapter.PlatformServices (folder SourceGeneration/) to swap the IReflectionOperations and IFileOperations services for source-gen-backed implementations once metadata is registered.

This is the first step toward Native AOT support: when the generator runs, MSTest reads test metadata from compile-time-known data instead of doing reflection at runtime. The feature is opt-in — when no metadata is registered, the platform keeps using the existing reflection-based implementations.

Why

Issue #1837 tracks Native AOT support for MSTest. PR #8263 introduced the IReflectionOperations service abstraction as a prerequisite. This PR delivers the next building block by adding the source generator that will populate that abstraction without reflection.

Layout

  • Generator (new project): src/Analyzers/MSTest.SourceGeneration/ReflectionMetadataGenerator and its emitters/models/helpers, packaged as analyzers/dotnet/cs.
  • Runtime hook & shims: src/Adapter/MSTestAdapter.PlatformServices/SourceGeneration/ReflectionMetadataHook, SourceGeneratedReflectionDataProvider, CompositeSourceGeneratedReflectionDataProvider, SourceGeneratedReflectionOperations, SourceGeneratedFileOperations, SourceGeneratorToggle.
  • Hook surface on PlatformServiceProvider: a single internal SetSourceGeneratedOperations method used by ReflectionMetadataHook to swap providers.
  • Removed: the legacy src/Adapter/MSTest.Engine/ project and its unit tests, which the new design replaces. (This is what shows up in the solution-file diff.)
  • Unit tests: test/UnitTests/MSTest.SourceGeneration.UnitTests/.
  • PublicAPI.Unshipped.txt updated.

Scope (MVP)

  • Generator emits a per-assembly [ModuleInitializer] that calls ReflectionMetadataHook.SetMetadata.
  • Composite provider supports multiple test assemblies registering concurrently.
  • Unit tests cover the happy path ([TestClass] + [TestMethod]), static/abstract classes being skipped, empty-assembly emission, and a Roslyn-compiles-cleanly smoke test.

Known follow-ups (out of scope for this PR)

These are intentionally deferred to keep this PR reviewable:

  • Populate the rest of the data bag (AssemblyAttributes, TypeAttributes, TypeProperties, TypeMethodAttributes, TypeConstructorsInvoker, TypeMethodLocations). The MVP only emits the assembly name, type list, and per-type method list.
  • [ModuleInitializer] polyfill for netstandard2.0 / older TFM consumers.
  • MSBuild gating (opt-in property like EnableMSTestSourceGeneration).
  • NuGet packaging of the generator into the MSTest analyzer set.
  • Wire up coverage of DataRow / DynamicData / inherited attributes / base-class roll-up.
  • AssemblyInitialize / ClassInitialize / TestInitialize lifecycle.

Validation

Built locally with build.cmd -c Debug (0 warnings, 0 errors).

Notes for reviewers

  • Public API is intentionally minimal: only ReflectionMetadataHook.SetMetadata and the SourceGeneratedReflectionDataProvider shape are public, because the generated module initializer needs to call them.
  • SourceGeneratorToggle uses Interlocked.Exchange to ensure exactly-once swap and to make the toggle race-free if multiple assemblies' module initializers run.
  • ReflectionMetadataGenerator skips static and abstract types — they cannot be discovered as test classes.
  • IReflectionOperations.GetType(string) delegates straight to the fallback so callers cannot bind to a same-named type from the wrong assembly via the composite TypesByName lookup; the assembly-qualified GetType(Assembly, string) overload is the one that uses the source-generated data.
  • Happy to split this into smaller PRs (e.g., runtime infra first, then generator) if that's easier to review.

Copilot AI review requested due to automatic review settings May 25, 2026 22:31
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces an opt-in MSTest reflection metadata source generator (MSTestAdapter.PlatformServices.SourceGeneration) plus new runtime infrastructure in MSTestAdapter.PlatformServices to swap IReflectionOperations/IFileOperations to source-gen-backed implementations via a generated [ModuleInitializer]. This is intended as an early building block toward NativeAOT support (#1837).

Changes:

  • Add a new incremental generator project that discovers [TestClass]/[TestMethod] and emits a module initializer registering metadata.
  • Add runtime “source-generated” reflection/file operations and a public hook (ReflectionMetadataHook) for generated code to register metadata.
  • Add unit tests for the generator and wire new projects into TestFx.slnx and MSTest.slnf, plus update PublicAPI.Unshipped.txt.
Show a summary per file
File Description
TestFx.slnx Adds the new generator project + its unit test project to the full solution.
MSTest.slnf Adds the new generator project + its unit test project to the MSTest solution filter.
src/Adapter/MSTestAdapter.PlatformServices/PlatformServiceProvider.cs Adds internal hook to swap IReflectionOperations/IFileOperations.
src/Adapter/MSTestAdapter.PlatformServices/PublicAPI/PublicAPI.Unshipped.txt Declares new public API surface (ReflectionMetadataHook, SourceGeneratedReflectionDataProvider).
src/Adapter/MSTestAdapter.PlatformServices/SourceGeneration/*.cs Adds runtime hook + source-gen-backed reflection/file operations + toggle.
src/Adapter/MSTestAdapter.PlatformServices.SourceGeneration/* Adds generator models/helpers + incremental generator + emitter + banned symbols.
test/UnitTests/MSTestAdapter.PlatformServices.SourceGeneration.UnitTests/* Adds generator unit tests + test runner program + test project.

Copilot's findings

  • Files reviewed: 19/19 changed files
  • Comments generated: 14

@Evangelink
Copy link
Copy Markdown
Member Author

🔍 Build Failure Analysis

Summary — The build failed due to a missing interface method and enforced code style violations across multiple target frameworks.

Root cause 1: Missing interface method SetSourceGeneratedOperations

The code in ReflectionMetadataHook.cs calls SetSourceGeneratedOperations on an IPlatformServiceProvider interface reference, but this method is not declared in the interface — it only exists as an internal method on the concrete PlatformServiceProvider class (line 181).

Affected files / errors

Proposed fix

Cast PlatformServiceProvider.Instance to the concrete type before calling the internal method:

-        PlatformServiceProvider.Instance.SetSourceGeneratedOperations(reflectionOperations, fileOperations);
+        ((PlatformServiceProvider)PlatformServiceProvider.Instance).SetSourceGeneratedOperations(reflectionOperations, fileOperations);

Alternative fix (if you want to expose this as part of the interface contract):

Add the method to IPlatformServiceProvider.cs:

     ITestContext GetTestContext(ITestMethod? testMethod, string? testClassFullName, IDictionary<string, object?> properties, IMessageLogger messageLogger, UTF.UnitTestOutcome outcome);
+
+    /// <summary>
+    /// Swaps the cached reflection and file operations with source-generated implementations.
+    /// </summary>
+    void SetSourceGeneratedOperations(IReflectionOperations reflectionOperations, IFileOperations fileOperations);
 }

And change the PlatformServiceProvider implementation from internal to public.


Root cause 2: Code style violations (IDE0032 — Use auto property)

Four fields are declared with explicit backing fields but could be converted to auto-properties. The analyzer rule IDE0032 is being enforced as an error.

Affected files / errors

Proposed fix

Convert explicit backing fields to auto-properties. These are readonly fields that are only assigned once in the constructor, so they can use the newer auto-property syntax with initializers.


Root cause 3: Code style violations (IDE0046 — Simplify conditional expression)

Three locations have if statements that can be simplified to conditional expressions. The analyzer rule IDE0046 is being enforced as an error.

Affected files / errors

Proposed fix

Simplify if/return patterns to ternary expressions or null-coalescing operators where appropriate.


Build overview
  • Project: MSTestAdapter.PlatformServices.csproj
  • Target frameworks: net8.0, net9.0 (errors occur in both)
  • Configuration: Debug
  • Exit code: 1 (failure)
  • Total unique errors: 7 distinct issues (multiplied across TFMs = 14 total occurrences)

The build failed during the CoreCompile target when compiling the newly added SourceGeneration code.

All MSBuild errors (14 occurrences)
Code Project File:Line Message
CS1061 MSTestAdapter.PlatformServices ReflectionMetadataHook.cs:37 'IPlatformServiceProvider' does not contain a definition for 'SetSourceGeneratedOperations'... (net8.0)
CS1061 MSTestAdapter.PlatformServices ReflectionMetadataHook.cs:37 'IPlatformServiceProvider' does not contain a definition for 'SetSourceGeneratedOperations'... (net9.0)
IDE0032 MSTestAdapter.PlatformServices SourceGeneratorToggle.cs:13 Use auto property (net8.0)
IDE0032 MSTestAdapter.PlatformServices SourceGeneratorToggle.cs:13 Use auto property (net9.0)
IDE0032 MSTestAdapter.PlatformServices SourceGeneratedReflectionOperations.cs:15 Use auto property (net8.0)
IDE0032 MSTestAdapter.PlatformServices SourceGeneratedReflectionOperations.cs:15 Use auto property (net9.0)
IDE0032 MSTestAdapter.PlatformServices SourceGeneratedFileOperations.cs:16 Use auto property (net8.0)
IDE0032 MSTestAdapter.PlatformServices SourceGeneratedFileOperations.cs:16 Use auto property (net9.0)
IDE0046 MSTestAdapter.PlatformServices SourceGeneratedReflectionOperations.cs:26 'if' statement can be simplified (net8.0)
IDE0046 MSTestAdapter.PlatformServices SourceGeneratedReflectionOperations.cs:26 'if' statement can be simplified (net9.0)
IDE0046 MSTestAdapter.PlatformServices SourceGeneratedReflectionOperations.cs:257 'if' statement can be simplified (net8.0)
IDE0046 MSTestAdapter.PlatformServices SourceGeneratedReflectionOperations.cs:257 'if' statement can be simplified (net9.0)
IDE0046 MSTestAdapter.PlatformServices SourceGeneratedReflectionOperations.cs:291 'if' statement can be simplified (net8.0)
IDE0046 MSTestAdapter.PlatformServices SourceGeneratedReflectionOperations.cs:291 'if' statement can be simplified (net9.0)

🤖 Generated by the Build Failure Analysis workflow · commit 6887de2

Generated by Build Failure Analysis for issue #8586 · ● 1.3M ·

Copy link
Copy Markdown
Member Author

@Evangelink Evangelink left a comment

Choose a reason for hiding this comment

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

Generated by Build Failure Analysis for issue #8586 · ● 1.3M

@Evangelink Evangelink force-pushed the dev/amauryleve/sourcegen-reflection-issue-1837 branch from 6887de2 to 73050ee Compare May 28, 2026 16:46
Consolidates the experimental MSTest.Engine package into the existing MSTest.SourceGeneration package, addresses PR review comments, and adds correctness fixes for ref/out/in parameter signatures, inaccessible nested test classes, file-local test classes, open generic test classes, overridden method deduplication, and composite-provider null handling.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 28, 2026 17:03
@Evangelink Evangelink force-pushed the dev/amauryleve/sourcegen-reflection-issue-1837 branch from 73050ee to a670c54 Compare May 28, 2026 17:03
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 171/172 changed files
  • Comments generated: 2

Comment thread TestFx.slnx
- Delegate IReflectionOperations.GetType(string) to fallback so simple
  type names cannot bind to a same-named type from the wrong assembly
  via the composite TypesByName lookup (matches Type.GetType semantics).
- Simplify HasByRefParameter to a single LINQ Any() expression.
- Drop unused reassignment of GeneratorDriver in the generator test
  harness's RunGeneratorAndGetCompilation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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