Select reflection member accessor based on IsDynamicCodeCompiled#473
Merged
eiriktsarpalis merged 6 commits intoJul 10, 2026
Merged
Conversation
Ports the fix from dotnet/runtime#130503 to PolyType's reflection shape provider. - The default for ReflectionTypeShapeProviderOptions.UseReflectionEmit now gates on RuntimeFeature.IsDynamicCodeCompiled instead of IsDynamicCodeSupported. On runtimes where dynamic code is supported but only interpreted (Mono interpreter, WebAssembly, iOS), emitted IL is itself interpreted and offers no throughput benefit while still pulling in the un-trimmable System.Reflection.Emit stack, so plain reflection accessors are now preferred there. - The reflection member accessor now propagates user-code exceptions unwrapped (via new InvokeNoWrapExceptions helpers) instead of wrapping them in a TargetInvocationException, matching the Reflection.Emit accessor's behavior. - Adds MemberAccessorExceptionTests guarding the unwrapped-exception behavior across the reflection, reflection-emit, and source-gen providers. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Extract the identical downlevel catch blocks in the two InvokeNoWrapExceptions overloads into a single RethrowInnerException helper (compiled only for non-.NET TFMs, since .NET uses BindingFlags.DoNotWrapExceptions natively). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
All of its call sites were converted to InvokeNoWrapExceptions, so the exception-wrapping Invoke extension is dead code; the full solution still compiles across all TFMs without it. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Remove the shared RethrowInnerException helper and inline the ExceptionDispatchInfo rethrow directly into each downlevel catch block, so the re-thrown user exception's stack trace doesn't gain an extra helper frame. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
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.
Ports the fix from dotnet/runtime#130503 to PolyType's reflection shape provider.
Motivation
The reflection provider chose its
Reflection.Emit-based member accessors wheneverRuntimeFeature.IsDynamicCodeSupportedwastrue. On runtimes where dynamic code is supported but only interpreted — the Mono interpreter, WebAssembly, and iOS — the emitted IL is itself interpreted, so it offers no throughput benefit over plain reflection while still pulling in the un-trimmableSystem.Reflection.Emitstack.RuntimeFeature.IsDynamicCodeCompiledis the correct signal for "emitting IL is actually worthwhile."Changes
Primary fix — accessor selection
ReflectionTypeShapeProviderOptions.UseReflectionEmitnow defaults toReflectionHelpers.IsDynamicCodeCompiledinstead ofIsDynamicCodeSupported, so interpreted-only runtimes fall back to plain reflection accessors.ReflectionHelpers.IsDynamicCodeCompiled(usesRuntimeFeature.IsDynamicCodeCompiledonNET, with a framework/reflection probe downlevel).IsDynamicCodeSupported(unchanged) — only the default selection moved.Coupled fix — exception propagation
TargetInvocationException. AddedInvokeNoWrapExceptionshelpers (BindingFlags.DoNotWrapExceptionsonNET;ExceptionDispatchInfo-rethrow downlevel) and routed the user-code invoke sites inReflectionMemberAccessorthrough them. This matches theReflection.Emitaccessor, which emits direct calls.Tests
MemberAccessorExceptionTestsasserting that getters, setters, default constructors, and parameterized constructors surface the original user exception (notTargetInvocationException) across the reflection, reflection-emit, and source-gen providers. Verified to fail without the coupled fix.make test-clr: 277919 total, 0 failed (Release; net8.0/net9.0/net10.0/net472; with coverage + crash/hang dumps).make test-aot: 29 total, 0 failed (NativeAOT publish + source-generated smoke test).