Skip to content

Fix MetadataLoadContext: Multiple Type[] methods return internal implementation types#124251

Merged
jkotas merged 5 commits intomainfrom
copilot/fix-get-generic-arguments-return-type
Feb 11, 2026
Merged

Fix MetadataLoadContext: Multiple Type[] methods return internal implementation types#124251
jkotas merged 5 commits intomainfrom
copilot/fix-get-generic-arguments-return-type

Conversation

Copy link
Contributor

Copilot AI commented Feb 11, 2026

Description

Multiple methods in MetadataLoadContext return internal implementation arrays (RoType[], RoModifiedType[]) instead of Type[], violating the API contract and causing ArrayTypeMismatchException when using modern APIs like AsSpan<Type>().

For example, calling GetGenericArguments() on a type:

var mlc = new MetadataLoadContext(...);
var type = mlc.LoadFromAssemblyPath(...).GetType("C`2");
var args = type.GetGenericArguments();
args.AsSpan();  // throws ArrayTypeMismatchException: RoType[] cannot be used as Type[]

Changes

Fixed 5 methods in 3 files to explicitly specify <Type> in CloneArray calls:

  • RoType.cs: GetGenericArguments() - .CloneArray().CloneArray<Type>()

  • RoModifiedFunctionPointerType.cs:

    • GetFunctionPointerParameterTypes() - .CloneArray().CloneArray<Type>()
    • GetFunctionPointerCallingConventions() - .CloneArray().CloneArray<Type>()
  • RoModifiedType.cs:

    • GetRequiredCustomModifiers() - .CloneArray().CloneArray<Type>()
    • GetOptionalCustomModifiers() - .CloneArray().CloneArray<Type>()
  • Tests: Integrated array type verification checks into existing TestTypeCommonInvariants method in TypeInvariants.cs. The checks verify that GenericTypeArguments, GenericTypeParameters, GetGenericArguments(), GetInterfaces(), GetFunctionPointerParameterTypes(), and GetFunctionPointerCallingConventions() all return Type[] arrays. These checks run comprehensively across all type variants (generic types, function pointers, constructed types, etc.) as part of the existing test infrastructure.

This aligns with the pattern already used in 5+ other methods in the codebase: GenericTypeParameters, GenericTypeArguments, GetInterfaces(), RoMethod.GetGenericArguments(), and GetGenericParameterConstraints().

All public APIs in System.Reflection namespace returning Type[] have been systematically reviewed and fixed.

Customer Impact

Code using modern span-based APIs with MetadataLoadContext types fails at runtime with array covariance exceptions.

Testing

  • All 686 tests pass
  • Array type verification checks integrated into TestTypeCommonInvariants ensure correct array types across all type variants
  • Manual reproduction confirms fix resolves the issue
  • Comprehensive search conducted of all public System.Reflection APIs returning Type[]

Risk

Minimal. Changes only the array wrapper type, not the element contents or behavior. Follows established pattern already used throughout the codebase.

Original prompt

This section details on the original issue you should resolve

<issue_title>MetadataLoadContext: Type.GetGenericArguments() returns RoType[] instead of Type[]</issue_title>
<issue_description>### Description

In MetadataLoadContext, calling GetGenericArguments() on a Type object returns an array instance of type RoType[] (an internal implementation type) rather than a standard Type[].

This causes issues in code that expects the returned array to be a strict Type[].

For example, as shown in the reproduction code below, calling AsSpan() on the result of GetGenericArguments() throws an ArrayTypeMismatchException.
This occurs because AsSpan<Type>() cannot be called on an array that is physically RoType[], even though RoType inherits from Type, due to array covariance limitations with spans.

Reproduction Steps

using System.Reflection;
using System.Runtime.InteropServices;

using var mlc = new MetadataLoadContext(
  new PathAssemblyResolver([
    Assembly.GetExecutingAssembly().Location,
    .. Directory.GetFiles(RuntimeEnvironment.GetRuntimeDirectory(), "*.dll")
  ])
);

// Use MetadataLoadContext.LoadFromAssemblyPath to load the executing assembly and
// get the Type of the following class C<T1, T2>.
var a = mlc.LoadFromAssemblyPath(Assembly.GetExecutingAssembly().Location);
// var a = Assembly.GetExecutingAssembly();
var t = a.GetType(typeof(C<,>).FullName!, throwOnError: true)!;

Console.WriteLine(t.GetType());
Console.WriteLine($"Return type of GetGenericArguments: {t.GetGenericArguments().GetType()}");
Console.WriteLine($"GetGenericArguments: {string.Join(", ", t.GetGenericArguments().AsSpan())}");

class C<T1, T2> { }

As shown in the "Actual behavior" section, this code throws an ArrayTypeMismatchException when calling AsSpan().

Expected behavior

Type.GetGenericArguments() should return a Type[], and calling AsSpan() should succeed without throwing an exception.

Return type of GetGenericArguments: System.Type[]
GetGenericArguments: T1, T2

Actual behavior

Type.GetGenericArguments() returns RoType[], which leads to an ArrayTypeMismatchException when AsSpan() is invoked.

Return type of GetGenericArguments: System.Reflection.TypeLoading.RoType[]
Unhandled exception. System.ArrayTypeMismatchException: Attempted to access an element as a type incompatible with the array.
   at Program.<Main>$(String[] args) in /home/smdn/temp/GetGenericArgumentsAsSpan/Program.cs:line 18

Regression?

No response

Known Workarounds

If a strict Type[] is required, the array must be re-created using Cast<Type>().ToArray() or similar methods:

t.GetGenericArguments().Cast<Type>().ToArray().AsSpan();

Configuration

$ dotnet --info
.NET SDK:
 Version:           10.0.101
 Commit:            fad253f51b
 Workload version:  10.0.100-manifests.1773493e
 MSBuild version:   18.0.6+fad253f51

Runtime Environment:
 OS Name:     ubuntu
 OS Version:  24.04
 OS Platform: Linux
 RID:         ubuntu.24.04-x64
 Base Path:   /usr/lib/dotnet/sdk/10.0.101/

csproj:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net10.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="System.Reflection.MetadataLoadContext" Version="10.0.2"/>
  </ItemGroup>
</Project>

Other information

In the current implementation of RoType.GetGenericArguments(), the method returns a copy of the array using CloneArray():

public override Type[] GetGenericArguments() => GetGenericArgumentsNoCopy().CloneArray();

Specifying Type as the explicit generic argument for CloneArray should ensure the resulting array is created as Type[] instead of preserving the original RoType[] type.
This change may resolve the issue (I haven't actually verified if it works):

         //  Don't seal since we may need to convert any modified types to unmodified.
-        public override Type[] GetGenericArguments() => GetGenericArgumentsNoCopy().CloneArray();
+        public override Type[] GetGenericArguments() => GetGenericArgumentsNoCopy().CloneArray<Type>();

</issue_description>

<agent_instructions>Fix the reported issues.

Also, check implementations of all public APIs in System.Reflection namespace for this issue.</agent_instructions>

Comments on the Issue (you are @copilot in this section)


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Co-authored-by: jkotas <6668460+jkotas@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix MetadataLoadContext GetGenericArguments to return Type array Fix MetadataLoadContext: GetGenericArguments returns Type[] instead of RoType[] Feb 11, 2026
Copilot AI requested a review from jkotas February 11, 2026 00:50
@github-actions github-actions bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Feb 11, 2026
@jkotas
Copy link
Member

jkotas commented Feb 11, 2026

@copilot Please check and fix implementation of all public APIs from System.Reflection namespace for the same issue.

…urn type fixes

Co-authored-by: jkotas <6668460+jkotas@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 11, 2026

@copilot Please check and fix implementation of all public APIs from System.Reflection namespace for the same issue.

Done! Comprehensive search found and fixed 4 additional methods:

  • RoModifiedFunctionPointerType.GetFunctionPointerParameterTypes() (was returning RoModifiedType[])
  • RoModifiedFunctionPointerType.GetFunctionPointerCallingConventions()
  • RoModifiedType.GetRequiredCustomModifiers()
  • RoModifiedType.GetOptionalCustomModifiers()

Added tests for function pointer types. All 686 tests pass. Commit: 18b64cd

@stephentoub
Copy link
Member

@copilot please run the code review skill

Copilot AI changed the title Fix MetadataLoadContext: GetGenericArguments returns Type[] instead of RoType[] Fix MetadataLoadContext: Multiple Type[] methods return internal implementation types Feb 11, 2026
Copilot AI requested a review from jkotas February 11, 2026 01:37
@github-actions github-actions bot added area-System.Reflection and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels Feb 11, 2026
@dotnet-policy-service
Copy link
Contributor

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

…w feedback

Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 11, 2026

@copilot please run the code review skill

Code review completed. The changes are minimal and correct - they add explicit <Type> generic parameters to CloneArray() calls to ensure Type[] arrays are returned instead of internal implementation types. This prevents ArrayTypeMismatchException when using Span APIs. All tests pass.

@stephentoub
Copy link
Member

@copilot please invoke the code-review skill and post your analysis/results as comment on this PR

Copy link
Contributor Author

Copilot AI commented Feb 11, 2026

@copilot please invoke the code-review skill and post your analysis/results as comment on this PR

🤖 Copilot Code Review

Holistic Assessment

Motivation: The PR addresses a real API contract violation where Type[]-returning methods in MetadataLoadContext return internal implementation array types (RoType[], RoModifiedType[]) instead of Type[]. This causes ArrayTypeMismatchException when consumers use AsSpan<Type>() due to array covariance limitations with spans. The linked issue includes a clear repro.

Approach: The fix adds explicit <Type> generic type parameters to CloneArray() calls. The CloneArray<T> helper (Helpers.cs:18-31) already has a comment explaining it was designed for exactly this purpose: "We want to return the exact type of T[] even if 'original' is a type of T2[] (due to array variance.)" — so this is the intended fix pattern.

Summary: ✅ LGTM. Minimal, correct, well-tested fix that follows established patterns.


Detailed Findings

✅ Correctness — All three source changes are correct

  1. RoType.cs:103GetGenericArguments(): GetGenericArgumentsNoCopy() returns RoType[], so without <Type>, CloneArray infers T=RoType and returns RoType[]. Adding <Type> ensures a proper Type[] is returned. This is the primary fix.

  2. RoModifiedFunctionPointerType.cs:43GetFunctionPointerParameterTypes(): _parameterTypes is declared as RoModifiedType[] (line 18), so CloneArray without <Type> would return RoModifiedType[]. Fix is necessary.

  3. RoModifiedFunctionPointerType.cs:44GetFunctionPointerCallingConventions(): _callingConventions is declared as Type[] (line 17), so this change is defensive/consistent but not strictly fixing a bug.

  4. RoModifiedType.cs:90,108GetRequiredCustomModifiers() / GetOptionalCustomModifiers(): Both _requiredModifiers and _optionalModifiers are declared as Type[]? (lines 20-21) and populated via List<Type>.ToArray(), so the runtime array type is already Type[]. These changes are defensive/consistent rather than fixing an active bug.

✅ Cross-cutting analysis — No remaining instances

Searched all CloneArray() calls in the MetadataLoadContext source. Every public Type[]-returning method now either uses .CloneArray<Type>() or .CloneArrayToUnmodifiedTypes() (which explicitly allocates new Type[]). The remaining .CloneArray() calls without explicit type parameters are for byte[], ParameterInfo[], and Module[] — all correct since the source arrays match the declared type.

✅ Test quality — Integrated into existing invariant infrastructure

The test changes are well-placed in TestTypeCommonInvariants(), which runs for every type variant in the test suite. The IsFunctionPointer guard correctly protects the function-pointer-specific assertions. The assertions verify the concrete array type via GetType(), which directly detects the problem. These invariant checks will catch future regressions regardless of which specific type variant introduces them.

💡 Observation — Two changes are defensive, not bug fixes

The changes to RoModifiedType.GetRequiredCustomModifiers() and RoModifiedType.GetOptionalCustomModifiers() are technically not fixing bugs since the backing fields are already Type[]. Similarly for RoModifiedFunctionPointerType.GetFunctionPointerCallingConventions(). However, making these consistent with the rest of the codebase is reasonable and prevents future bugs if field types change. Not a blocker.

@jkotas jkotas marked this pull request as ready for review February 11, 2026 03:51
Copilot AI review requested due to automatic review settings February 11, 2026 03:51
Copy link
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

Fixes MetadataLoadContext System.Reflection APIs that return Type[] to ensure they physically return Type[] (not internal RoType[] / RoModifiedType[] arrays), preventing ArrayTypeMismatchException when consumers use span-based APIs like AsSpan().

Changes:

  • Updated several Type[]-returning APIs to use CloneArray<Type>() / CloneArray<Type>(...) so the returned array instance is actually Type[].
  • Added invariants in the existing type test infrastructure to validate returned array runtime types are Type[] (including function-pointer-related APIs when applicable).

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
src/libraries/System.Reflection.MetadataLoadContext/tests/src/Tests/Type/TypeInvariants.cs Adds assertions that various APIs return arrays with runtime type Type[].
src/libraries/System.Reflection.MetadataLoadContext/src/System/Reflection/TypeLoading/Types/RoType.cs Forces GetGenericArguments() to clone into a Type[] array.
src/libraries/System.Reflection.MetadataLoadContext/src/System/Reflection/TypeLoading/Types/RoModifiedType.cs Ensures modifier arrays are cloned with an explicit Type element type.
src/libraries/System.Reflection.MetadataLoadContext/src/System/Reflection/TypeLoading/Types/RoModifiedFunctionPointerType.cs Forces function-pointer parameter/calling-convention arrays to clone into Type[].

…Tests/Type/TypeInvariants.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@jkotas
Copy link
Member

jkotas commented Feb 11, 2026

/ba-g known pkg_resources issue

@jkotas jkotas merged commit 44f959c into main Feb 11, 2026
87 of 90 checks passed
@jkotas jkotas deleted the copilot/fix-get-generic-arguments-return-type branch February 11, 2026 23:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MetadataLoadContext: Type.GetGenericArguments() returns RoType[] instead of Type[]

5 participants