AotReflection: capture assembly-level attributes#9007
Open
Evangelink wants to merge 5 commits into
Open
Conversation
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>
Today TestClassModelBuilder only enumerates members directly declared on the `[TestClass]` type. As soon as a fixture extends a base class, MSTest members (`[TestInitialize]`, `[TestCleanup]`, `[TestMethod]`, the `[TestContext]` property, ...) declared on the base disappear from the generated registry. This change makes the builder walk the inheritance chain (stopping at `System.Object`): * Methods and properties are folded from base types into the model. * Iteration is derived-first; an override or `new`-shadowed member with the same signature/name wins over the base declaration. The signature key includes ref-kinds so genuine overloads survive. * Attributes are collected across the `OverriddenMethod` / `OverriddenProperty` chain (deduped by attribute class FQN) so an `override` that does not re-apply `[TestMethod]` still sees the base attribute - matching the runtime `GetCustomAttributes(inherit: true)` semantics. * Accessibility is broadened to include `Protected` / `ProtectedOrInternal` / `ProtectedAndInternal` so abstract bases can expose their hooks to the emitted code (which lives in the consumer's assembly). * Constructors are NEVER inherited (only taken from the leaf type). Adds 9 new tests covering: inherited methods, multi-level inheritance, overridden virtual (attribute inheritance + de-dup), `new`-hidden methods, overload preservation, inherited properties, no inherited constructors, abstract-base fold-down, and not walking past `System.Object`. Part of microsoft#1837. Depends on microsoft#9004 (test project), microsoft#9005 (typeof nullable). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds an AssemblyAttributes property to the emitted MSTestReflectionMetadata registry containing all attributes declared with [assembly: ...] in the same compilation. The attribute payload is built via the existing AttributeApplicationModel pipeline (reused from class/method attribute emission), so adapters can iterate without calling Assembly.GetCustomAttributes at runtime. Part of microsoft#1837. Stacked on microsoft#9004, microsoft#9005, microsoft#9006. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR extends the MSTest.AotReflection.SourceGeneration proof-of-concept to capture assembly-level attributes ([assembly: ...]) during source generation, emitting them into the generated registry so runtime code can avoid Assembly.GetCustomAttributes(...) for attributes declared in the consumer assembly.
Changes:
- Add an assembly-metadata model and incremental-generator pipeline branch to capture
compilation.Assembly.GetAttributes()and flow it into emission. - Emit a new generated
AssemblyAttributesproperty (empty-array fast path when none are present). - Add/expand a dedicated unit test project with new tests validating assembly-attribute capture scenarios, and wire projects into solution filters.
Show a summary per file
| File | Description |
|---|---|
| TestFx.slnx | Adds the AotReflection generator and its unit tests to the full solution. |
| MSTest.slnf | Adds the AotReflection generator project to the MSTest solution filter. |
| test/UnitTests/MSTest.AotReflection.SourceGeneration.UnitTests/Program.cs | Test runner entry point for the new unit test project (MTP host + MSTest). |
| test/UnitTests/MSTest.AotReflection.SourceGeneration.UnitTests/MSTestReflectionMetadataGeneratorTests.cs | Adds new unit tests validating assembly-level attribute capture and emission behavior. |
| test/UnitTests/MSTest.AotReflection.SourceGeneration.UnitTests/MSTest.AotReflection.SourceGeneration.UnitTests.csproj | New unit test project wiring (Roslyn, AwesomeAssertions, project references). |
| src/Analyzers/MSTest.AotReflection.SourceGeneration/MSTest.AotReflection.SourceGeneration.csproj | Adds InternalsVisibleTo for the new unit test assembly. |
| src/Analyzers/MSTest.AotReflection.SourceGeneration/Model/TestClassModel.cs | Introduces AssemblyMetadataModel to represent captured assembly-level attributes. |
| src/Analyzers/MSTest.AotReflection.SourceGeneration/Generators/TestClassModelBuilder.cs | Updates model building logic (inheritance walk, signature keying, attribute collection changes). |
| src/Analyzers/MSTest.AotReflection.SourceGeneration/Generators/MSTestReflectionMetadataGenerator.cs | Adds an incremental pipeline branch to capture assembly attributes and combine with test-class models. |
| src/Analyzers/MSTest.AotReflection.SourceGeneration/Generators/MetadataRegistryEmitter.cs | Emits AssemblyAttributes into the generated registry, including empty-array optimization. |
Copilot's findings
- Files reviewed: 10/10 changed files
- Comments generated: 3
- IsAccessibleFromConsumer now excludes Protected / ProtectedAndInternal members since the generated invoker class is not a derived type. - BuildMethodSignatureKey now encodes the method's generic arity so overloads that differ only in arity (e.g. M() vs M<T>()) are not collapsed by the per-class dedup. - CollectInheritedAttributes now respects AttributeUsage(Inherited = false) on inherited levels and preserves every instance of attributes marked with AllowMultiple = true (e.g. [TestCategory]). - Adds regression tests covering all three behaviors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Member
Author
|
@copilot resolve the merge conflicts in this pull request |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Captures assembly-level attributes (
[assembly: ...]) in the newMSTest.AotReflection.SourceGenerationPoC so adapters never needAssembly.GetCustomAttributes(...)at runtime for attributes declared in theconsumer assembly.
Why
Assembly.GetCustomAttributesis one of the call sites flagged indocs/source-generator/design.md"Category-A fallback" list: the shipping
SourceGeneratedReflectionDataProviderhas an
AssemblyAttributesslot but nothing populates it today.MSTest reads several attributes at assembly scope —
[Parallelize],[ClassCleanupExecutionAttribute],[AssemblyInitialize]lookup, and anyuser-defined
[TestCategory]-style attributes targeting the assembly. Source-generatingthis list makes the discovery path trim-safe and NativeAOT-safe.
What's in the PR
AssemblyMetadataModel(EquatableArray<AttributeApplicationModel> Attributes)— value-equatable so the incremental pipeline can cache the assembly-attribute
branch independently of test-class changes.
CompilationProvider.Selectbranch runsBuildAttributes(compilation.Assembly.GetAttributes())and combines with theexisting
(AssemblyName, ImmutableArray<TestClassModel>)payload.Array.Empty<Attribute>()when nothing is declared at assembly scope.Generator_CapturesAssemblyLevelAttribute— single[assembly: Parallelize(Workers = 4, Scope = "Method")].Generator_AssemblyAttributes_IsEmptyArray_WhenNoneApplied— empty-array path.Generator_CapturesMultipleAssemblyAttributes_InDeclarationOrder— order preserved.Generator_AssemblyAttributes_AreEmittedEvenWithNoTestClasses— emit happens even when no[TestClass]exists.Validation
Stacking
Part of #1837 follow-ups. Stacked on:
MSTest.AotReflection.SourceGeneration.UnitTests.typeof(...)(CS8639 fix).This PR adds +179 / -4 LOC on top of #9006.
Out of scope
AssemblyAttributesinto the shippingSourceGeneratedReflectionDataProvider— that's PR-A8 in the plan.
TargetFrameworkAttribute).Roslyn's
IAssemblySymbol.GetAttributes()only returns user-declared ones fromsource/
InternalsVisibleTo-style consumers, so no filter is needed today.