Skip to content

Forward missing Type members in System.Reflection.Context - #134371

Merged
jkotas merged 2 commits into
dotnet:mainfrom
caraioniurie47:issue-133816
Sep 21, 2026
Merged

jkotas merged 2 commits into
dotnet:mainfrom
caraioniurie47:issue-133816

Conversation

@caraioniurie47

Copy link
Copy Markdown
Contributor

Types mapped by a CustomReflectionContext answer some Type members from the base class instead of from the type they wrap, because DelegatingType does not override them. #133816 reported the four whose base throws NotSupportedException: Derived classes must provide an implementation.; enumerating the rest found more with the same cause.

Member Mapped type before Commit
IsByRefLike, GetEnumValuesAsUnderlyingType(), GetNullableUnderlyingType(), MakeFunctionPointerType(...) NotSupportedException 1
IsSZArray, IsTypeDefinition, IsConstructedGenericType (and IsVariableBoundArray, computed from IsSZArray) NotImplementedException 2
IsFunctionPointer, IsUnmanagedFunctionPointer false, including on the result of MakeFunctionPointerType 2
GetFunctionPointerReturnType(), GetFunctionPointerParameterTypes(), GetFunctionPointerCallingConventions() NotSupportedException 2
MemberType TypeInfo for a nested type 2

Every member in the table except GetNullableUnderlyingType and MakeFunctionPointerType, which are new in .NET 11, also reproduces with the released 10.0.12 package on .NET 10.0.12.

Several of these members came with PRs that overrode them in other Type subclasses but not in this library: #81006 added the function pointer members to TypeDelegator and MetadataLoadContext, #73057 added GetEnumValuesAsUnderlyingType to MetadataLoadContext, #126905 added GetNullableUnderlyingType to TypeDelegator, the Reflection.Emit builders and MetadataLoadContext, and #123819 added MakeFunctionPointerType to TypeBuilder and EnumBuilder.

DelegatingType now forwards each member in the table to the wrapped type, as it does GetArrayRank; TypeDelegator forwards IsByRefLike, GetNullableUnderlyingType and every commit 2 member except MemberType the same way. ProjectingType projects the Type results of GetNullableUnderlyingType, MakeFunctionPointerType and the three GetFunctionPointer* methods, and unprojects the parameter types passed to MakeFunctionPointerType, as it does for MakeGenericType.

Commit 2 covers members #133816 does not name, so it is separate and can be dropped if this should stay limited to the issue; MakeFunctionPointerType would then return a type whose IsFunctionPointer is false.

The sources are compiled for net11.0, net10.0 and netstandard2.1. GetNullableUnderlyingType and MakeFunctionPointerType are under #if NET11_0_OR_GREATER; GetEnumValuesAsUnderlyingType and the function pointer members are under #if NET; the rest need no condition. MetadataLoadContext's RoType.cs uses the same conditions for GetNullableUnderlyingType and GetEnumValuesAsUnderlyingType.

This changes the behaviour of every member in the table. I can draft a breaking-change note if one is needed.

Tests

New cases in ExtendedTypeTests.cs cover each member in the table and check that returned types are projected. Calling conventions are only reported on modified types, so that case maps the result of GetModifiedParameterType() for an unmanaged[Cdecl] parameter; this needs AllowUnsafeBlocks in the test project, as in System.Reflection.Tests and the MetadataLoadContext tests.

The new cases fail without the change, except MemberType on a top-level type, whose expected value equals the base default. Reverting only the ProjectingType changes fails the projection checks.

Run locally on Windows x64, on a clr+libs -rc checked build of this branch unless noted:

  • The library builds for all its target frameworks with no warnings.
  • System.Reflection.Context.Tests: all pass; also with Release libraries on a local Release runtime build, and on a local x86 checked runtime build.
  • System.ComponentModel.Composition.Registration.Tests (RegistrationBuilder derives from CustomReflectionContext): all pass in the same three configurations.
  • NativeAOT (/p:TestNativeAot=true): none failed; every new case passes.
  • Mono (mono+libs -rc checked, /p:RuntimeFlavor=Mono), with the JIT and with MONO_ENV_OPTIONS=--interpreter: both test projects pass; every new case runs there except the two tests described below.
  • The net10.0 and netstandard2.1 builds of the library, run on .NET 10.0.12 by a probe comparing mapped and unmapped values: the net10.0 build matches on every member in the table that .NET 10 has; the netstandard2.1 build matches on the members that surface has.

The two tests that call MakeFunctionPointerType carry the same [ActiveIssue("https://github.com/dotnet/runtime/issues/124149", TestRuntimes.Mono)] as the MakeFunctionPointerType tests in System.Runtime.Tests' TypeTests.cs, since Mono does not implement it.

Not in this PR

  • IsCollectible returns the MemberInfo default, true. Forwarding it would make TypeDescriptor keep mapped types alive, since the caches added in Use AssemblyLoadContext-aware caches in TypeDescriptor to support unloading of assemblies cached by TypeDescriptor  #114619 hold only collectible keys weakly: on a local build of main, a mapped type passed to TypeDescriptor.GetProperties was collected after a full GC with the 10.0.12 package, and stayed alive with IsCollectible forwarded.
  • HasSameMetadataDefinitionAs throws NotImplementedException on mapped types and on every kind of mapped member, and GetMemberWithSameMetadataDefinitionAs with it.
  • GetMethod overloads taking genericParameterCount throw NotSupportedException. CustomType.GetMethodImpl merges in methods the context adds, so this is not a forward.
  • A modified type (from GetModifiedFieldType()) loses its custom modifiers before the context sees it: it is not a TypeInfo, so GetTypeInfo() wraps it in a TypeDelegator, which does not forward GetRequiredCustomModifiers or GetOptionalCustomModifiers.

Resolves #133816

Note

AI-generated, written at my direction and reviewed by me before posting. The results above are from this branch's library and tests built locally on Windows x64 and run on the runtimes named; the enumeration of members, the members under "Not in this PR" and the 10.0.12 comparison were checked with throwaway tests and probes, not included here.

caraioniurie47 and others added 2 commits September 21, 2026 22:58
Types mapped by CustomReflectionContext threw NotSupportedException
("Derived classes must provide an implementation") from IsByRefLike,
GetEnumValuesAsUnderlyingType, GetNullableUnderlyingType and
MakeFunctionPointerType: DelegatingType did not override them.

DelegatingType now forwards all four to the wrapped type, as it does
GetArrayRank. ProjectingType projects the Type results of
GetNullableUnderlyingType and MakeFunctionPointerType, and unprojects
the parameter types of the latter, as it does for MakeGenericType.
The overrides are conditioned on the target frameworks that have the
members.

Fix dotnet#133816

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Types mapped by CustomReflectionContext also answered other Type
members from the base class instead of the wrapped type:
IsSZArray, IsTypeDefinition and IsConstructedGenericType threw
NotImplementedException; IsFunctionPointer and
IsUnmanagedFunctionPointer returned false and the three
GetFunctionPointer* methods threw NotSupportedException, including on
the result of MakeFunctionPointerType; MemberType returned TypeInfo
for a nested type.

DelegatingType now forwards these to the wrapped type, and
ProjectingType projects the types returned by the GetFunctionPointer*
methods.

Contributes to dotnet#133816

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@dotnet-policy-service dotnet-policy-service Bot added the community-contribution Indicates that the PR has been added by a community member label Sep 21, 2026
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @steveisok, @dotnet/area-system-reflection
See info in area-owners.md if you want to be subscribed.

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.

Copilot review overview

🟡 Changes recommended

The observable behavioral changes require a breaking-change note or link before approval.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 Low severity

Open (1)
What changed in this PR

Forwards previously omitted Type members through CustomReflectionContext wrappers while preserving projected type identities.

Changes:

  • Adds forwarding and projection for type-shape, nullable, enum, and function-pointer APIs.
  • Adds regression coverage for forwarded values and projection behavior.
  • Enables unsafe test declarations for function-pointer signatures.
File Description
DelegatingType.cs Forwards missing Type members.
ProjectingType.cs Projects returned types and unprojects inputs.
ExtendedTypeTests.cs Tests all newly forwarded behavior.
System.Reflection.Context.Tests.csproj Enables unsafe function-pointer tests.

@jkotas
jkotas merged commit 7b968fc into dotnet:main Sep 21, 2026
90 of 92 checks passed
@dotnet-milestone-bot dotnet-milestone-bot Bot added this to the 12.0-preview1 milestone Sep 22, 2026
@caraioniurie47
caraioniurie47 deleted the issue-133816 branch September 22, 2026 06:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-System.Reflection community-contribution Indicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

System.Reflection.Context: types mapped by CustomReflectionContext throw NotSupportedException from IsByRefLike and three other Type members

3 participants