dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.27k stars 4.73k forks source link

Unhelpful error message for missing TypeBuilder.CreateType() call #101322

Open masonwheeler opened 6 months ago

masonwheeler commented 6 months ago

Description

Sub-issue of https://github.com/dotnet/runtime/issues/101298.

There is an error in this code, but the error message that gets raised in response does nothing to help with debugging the problem. (Also, the error that exists arguably shouldn't be an error, but that's a whole other issue.)

Reproduction Steps

using System.Reflection.Emit;
using System.Reflection;

namespace NestedTypeError
{
    internal class Program
    {
        static void Main(string[] args)
        {
            try
            {
                var type = BuildType();
                var method = type.GetMethod("Invalid")!;
                var result = method.Invoke(null, []);
                Console.WriteLine(result);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
        }

        private static Type BuildType()
        {
            AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new("MyAssembly"), AssemblyBuilderAccess.RunAndCollect);
            ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MyAssembly");
            var typeBuilder = moduleBuilder.DefineType("MyType", TypeAttributes.Public, typeof(ValueType));

            var nestedTypeBuilder = typeBuilder.DefineNestedType("InnerType", TypeAttributes.NestedPrivate);
            var ctor = nestedTypeBuilder.DefineDefaultConstructor(MethodAttributes.Public);

            var value = nestedTypeBuilder.DefineMethod("Value", MethodAttributes.Public, typeof(int), []);
            var gen = value.GetILGenerator();
            gen.Emit(OpCodes.Ldc_I4_1);
            gen.Emit(OpCodes.Ret);
            //nestedTypeBuilder.CreateType();

            var invalid = typeBuilder.DefineMethod("Invalid", MethodAttributes.Public | MethodAttributes.Static, typeof(int), []);
            var il = invalid.GetILGenerator();
            il.Emit(OpCodes.Newobj, ctor);
            il.Emit(OpCodes.Callvirt, value);
            il.Emit(OpCodes.Ret);

            return typeBuilder.CreateType();
        }
    }
}

Expected behavior

This will error out on the call to method.Invoke. A useful error message giving some clue as to the nature of the problem that would meaningfully aid in debugging it is expected.

Actual behavior

System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
 ---> System.TypeLoadException: Could not load type 'InnerType' from assembly 'MyAssembly, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'.
   at MyType.Invalid()
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
   at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)
   --- End of inner exception stack trace ---
   at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)
   at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
   at NestedTypeError.Program.Main(String[] args) in C:\Users\mason\source\repos\Repro2\NestedTypeError\NteProgram.cs:line 14

So the type InnerType cannot be loaded. OK. Why? What's wrong with it? In some cases, TypeLoadExeption gives a meaningful error message, but in this case, and several others I've run across, it does not.

The error here is that nestedTypeBuilder.CreateType() was never called. The error message really should reflect that.

(Also, should that even be necessary for a nested type? I can see decent arguments on both sides, but to me the .NET philosophy of "the pit of success" feels like the strongest argument. When I call typeBuilder.CreateType(), I intuitively expect it to create the entire type, with all of its declared members. Having it create "everything except for the nested types" feels inconsistent.)

Regression?

No response

Known Workarounds

No response

Configuration

.NET Core 8, Windows 10, x64.

Other information

No response

dotnet-policy-service[bot] commented 6 months ago

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