Ayende @ Rahien

It's a girl

Breaking the C# compiler, again

Take a look at the following code:

   1:  var docs = new dynamic[0];
   2:  var q = from doc in docs
   3:          where doc["@metadata"]["Raven-Entity-Name"] == "Cases"
   4:          where doc.AssociatedEntities != null
   5:          from entity in doc.AssociatedEntities
   6:          where entity.Tags != null // COMPILER ERROR HERE
   7:          from tag in entity.Tags
   8:          where tag.ReferencedAggregate != null
   9:          select new {tag.ReferencedAggregate.Id, doc.__document_id};        

It doesn’t compiles, complaining about an error in line 6 (The property '<>h__TransparentIdentifier0' cannot be used with type arguments).

Leaving aside the issue with the error itself, which is about as useful as C++ template errors, I have a strongly issue with this.

What do you think can fix this error? Well, if you remove line 8 (!), it just works:

   1:  var docs = new dynamic[0];
   2:  var q = from doc in docs
   3:          where doc["@metadata"]["Raven-Entity-Name"] == "Cases"
   4:          where doc.AssociatedEntities != null
   5:          from entity in doc.AssociatedEntities
   6:          where entity.Tags != null
   7:          from tag in entity.Tags
   8:          select new { tag.ReferencedAggregate.Id, doc.__document_id };

There are several things that I can’t figure out:

  • Why am I getting an error?
  • Why removing a line after the error fixes the error in the line before it.

What I suspect is that the error reporting (which in C# is usually damn good) is getting confused with the error reporting location.

To make things worse, this code will crash VS 2010 every single time. I am actually editing this in notepad!!!

Here is the set of extensions methods that I use here:

public static class LinqOnDynamic
{
    private static IEnumerable<dynamic> Select(this object self)
    {
        if (self == null)
            yield break;
        if (self is IEnumerable == false || self is string)
            throw new InvalidOperationException("Attempted to enumerate over " + self.GetType().Name);

        foreach (var item in ((IEnumerable) self))
        {
            yield return item;
        }
    }

    public static IEnumerable<dynamic> SelectMany(this object source,
                                                    Func<dynamic, int, IEnumerable<dynamic>> collectionSelector,
                                                    Func<dynamic, dynamic, dynamic> resultSelector)
    {
        return Enumerable.SelectMany(Select(source), collectionSelector, resultSelector);
    }

    public static IEnumerable<dynamic> SelectMany(this object source,
                                                    Func<dynamic, IEnumerable<dynamic>> collectionSelector,
                                                    Func<dynamic, dynamic, dynamic> resultSelector)
    {
        return Enumerable.SelectMany(Select(source), collectionSelector, resultSelector);
    }

    public static IEnumerable<dynamic> SelectMany(this object source,
                                                    Func<object, IEnumerable<dynamic>> selector)
    {
        return Select(source).SelectMany<object, object>(selector);
    }

    public static IEnumerable<dynamic> SelectMany(this object source,
                                                                    Func<object, int, IEnumerable<dynamic>> selector)
    {
        return Select(source).SelectMany<object, object>(selector);

    }
}

WTF?!

Tags:

Posted By: Ayende Rahien

Published at

Originally posted at

Comments

Steve Py
08/10/2010 12:51 AM by
Steve Py

Simple options that come to mind: What happens if you break that query down into separate queries?

Have you tried setting up to debug into the ,Net source? It might be easier to step through with the extension method syntax:

var q = docs.Where(doc => doc["@metadata"]["Raven-Entity-Name"] == "Cases" && doc.AssociatedEntities != null)

.SelectMany( doc => doc.AssociatedEntities)

.Where(entity => entity.Tags != null)

.SelectMany(entity => entity.Tags)

.Where(tag => tag.ReferencedAggregate != null)

.Select (tag => new {tag.ReferencedAggregate.Id, doc._documentid});

Should compile down to effectively the same operations, I just find the extension syntax more consistent to follow in code.

Dmitry
08/10/2010 12:52 AM by
Dmitry

Have you tried writing (retyping) the same code in another file. I had a couple of weird issues with whitespace that was a result of copying-and-pasting.

David Cuccia
08/10/2010 03:06 AM by
David Cuccia

Sounds like a good SO question.

FallenGameR
08/10/2010 07:57 AM by
FallenGameR

Try to attach Reflector Pro - it can step into Framework assemblies.

Ayende Rahien
08/10/2010 07:58 AM by
Ayende Rahien

FallenGameR,

The issue is in the compiler, not at runtime

FallenGameR
08/10/2010 08:06 AM by
FallenGameR

When I ran the query in the LinqPad, I get following error on line 5 (before your error):

5: from entity in doc.AssociatedEntities

An expression of type 'dynamic' is not allowed in a subsequent from clause in a query expression with source type 'System.Collections.Generic.IEnumerable <dynamic'. Type inference failed in the call to 'SelectMany'.

FallenGameR
08/10/2010 08:08 AM by
FallenGameR

And Studio 2010 says the same error as LinqPad.

What studio & target framework are you using?

FallenGameR
08/10/2010 08:10 AM by
FallenGameR

Stupid question.... =) dynamic is in 4.0 and 4.0 is in 2010.

Anyway, the code you provided doesn't crush on my side.

D. P. Bullington
08/10/2010 12:47 PM by
D. P. Bullington

@David Cuccia I hope that was in reference to:

"Ayende is not your helpdesk"

It goes both ways.

:)

Tom Pester
08/10/2010 01:12 PM by
Tom Pester

The compiler rewrites the syntactic sugar that are query expression to (extension) method calls. This is an early pass that the compiler does to get to plain vanilla c# statements.

I think the parser gets seriously confused by your line 8 probably in comination with previous build up state.

What happens if your rewrite the query expression to plain method calls?

Scubastard
08/10/2010 02:15 PM by
Scubastard

I think I saw a Rob Conery post where he details issues with dynamic C# classes/properties not being able to be resolved with extension methods. Sounds like thats related to whats happening here and as a result your linq extension methods can no longer be found... thats just a wild guess though.

tobi
08/10/2010 05:16 PM by
tobi

Obviously a compiler bug as the transparent identifier class names should never be visible. On stack overflow you can find other C# compiler bugs and even bugs in the Jitter. I personally have only hit one single compiler bug in C# at all. Cant complain about Microsofts quality standards at all (in which product or lib that you are using for five years have you found <= 1 bug?).

Elroy
08/10/2010 05:40 PM by
Elroy

My day-job includes developing a compiler and I can so well relate to this scenario. But, of course, we're talking about VS here. You would obviously expect a lot from the compiler.

13xforever
08/10/2010 06:24 PM by
13xforever

It seems like MS have a lot to fix in their c# 4.0 compiler that is dealing with dynamics.

'Cause as simple code as this will crash the compiler:

using System;

using System.Collections.Generic;

namespace ExcelToRavenDbExporter

{

using TConverter = Dictionary

<string,>

;

internal class Program

{

    private static void Main()

    {

    }

}

}

configurator
08/10/2010 07:34 PM by
configurator

Interesting, the lambda-syntax fails here too.

This doesn't compile:

var q = docs.SelectMany(doc => doc.AssociatedEntities);

Neither does this:

var q2 = docs.SelectMany((dynamic doc) => doc.AssociatedEntities);

Both with the same error: 'object' does not contain a definition for 'AssociatedEntities' and no extension method 'AssociatedEntities' accepting a first argument of type 'object' could be found (are you missing a using directive or an assembly reference?)

However this works:

var q = docs.Where(doc => doc.AssociatedEntities != null);

So you can absolutely use dynamic as lambda parameters. Sometimes.

Bob
08/11/2010 06:48 AM by
Bob

MAkes sure project target is set to .net 4.0?

humpbacked lout
08/12/2010 06:40 AM by
humpbacked lout

I just think you're idiot. That's why it keeps crashing. Reading your posts reveal complete lameness and idiocy. You're doing things the hard way and you're thinking the universe spins around you. That said - you're idiot.

Nexus
08/12/2010 01:07 PM by
Nexus

"I just think you're idiot. That's why it keeps crashing. Reading your posts reveal complete lameness and idiocy. You're doing things the hard way and you're thinking the universe spins around you. That said - you're idiot. "

--> humpbacked lout : Maybe you should rethink that statement with the following in mind: If you don't understand maybe you're idiot, and stop reading this blog.

Comments have been closed on this topic.