dotnet / roslyn

The Roslyn .NET compiler provides C# and Visual Basic languages with rich code analysis APIs.
https://docs.microsoft.com/dotnet/csharp/roslyn-sdk/
MIT License
18.98k stars 4.03k forks source link

Watch Window: <Method Type Argument> does not contain a definition for <member> #2188

Open dpoeschl opened 9 years ago

dpoeschl commented 9 years ago
  1. Enlist in Roslyn.sln and start debugging.
  2. Open a C# project and start an inline rename.
  3. Put a breakpoint in EventMap.RemoveEventHandler
  4. Complete the inline rename.
  5. You should hit the breakpoint.
  6. Add registries.array[0].handler to the watch window. It should evaluate correctly.
  7. Expand that node and right click Method (or any other child) and Add Watch

Expected: Watch evaluates correctly. Actual: error CS1061: 'TEventHandler' does not contain a definition for 'Method' and no extension method 'Method' accepting a first argument of type 'TEventHandler' could be found (are you missing a using directive or an assembly reference?)

dpoeschl commented 9 years ago

FYI @amcasey

amcasey commented 9 years ago

Reduced repro:

Run to the breakpoint and then evaluate t.x

class Program
{
    int x;

    static void Main(string[] args)
    {
        M(new Program());
    }

    static void M<T>(T t)
    {
        System.Diagnostics.Debugger.Break();
    }
}
amcasey commented 9 years ago

The same thing happens for type-level type parameters.

class Program
{
    int x;

    static void Main(string[] args)
    {
        C<Program>.M(new Program(), new Program());
    }
}

class C<T>
{
    public static void M<U>(T t, U u)
    {
        System.Diagnostics.Debugger.Break();
    }
}
amcasey commented 9 years ago

This looks like a simple fullname bug. We just have to catch to the runtime type because the un-cast expression is typed as a type parameter.

amcasey commented 9 years ago

There's a mismatch between the EC and the RP. In the EC the declared type is the type parameter. In the RP, the declared type is the type argument. Since the RP doesn't know about the type parameter, it doesn't insert the cast that the EC requires.

amcasey commented 9 years ago

If you manually add the a watch to the full name without the cast, the legacy EE also rejects it. Curiously though, it requires only a single cast, whereas Roslyn requires an intermediate cast to object (per the regular C# rules). Maybe the legacy EE binder is more generous than the regular legacy binder.

amcasey commented 9 years ago

My feeling is that the EC behavior is by design and the problem lies with the RP - specifically the full name. However, as things stand, I'm not sure the RP has enough information to insert the required casts.

amcasey commented 9 years ago

Consider the following method:

void M<U>(U u)
{
}

In the body of M, we’ll display a row in the Locals window for u. As far as I can tell, none of the values passed to IDkmClrResultProvider.GetResult indicate that the declared type of u was U. As a result, the full names of u's children (i.e. the members of the runtime type of u, Bag in the bug) generate C# expressions as though the declared type matched the runtime type (i.e. no cast is inserted). The expression compiler, however, has no knowledge of runtime types and considers u to be of type U. Obviously, U has no members, so dotting into u fails. The upshot is that you can see the children of u if you expand it, but you can’t add watches to them.

Dev12:

u – works u.x – “x is not a member of U” ((Bag)u).x – works (generated full name) ((Bag)(object)u).x – works

Roslyn:

u – works u.x – “x is not a member of U” (generated full name) ((Bag)u).x – “U cannot be converted to Bag” (i.e. same as during regular compilation) ((Bag)(object)u).x – works

This is unfortunate, but I don’t think the work of designing and implementing a new information channel between Concord and the ResultProvider is justified at this point in the cycle, especially since the row is visible as a child of its parent (or directly if you manually insert the casts).