Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lambdas requiring casting not getting properly converted #14722

Closed
phil-scott-78 opened this issue Oct 25, 2016 · 1 comment · Fixed by #14755

Comments

@phil-scott-78
Copy link
Contributor

@phil-scott-78 phil-scott-78 commented Oct 25, 2016

Version Used:
Visual-Studio-15-Preview-5, current master (5980aca)

Steps to Reproduce:

This seems to only occur in specific scenarios that involve an implicit cast from one type to another in lambdas. Current release version on nuget will emit Expression.Convert while current version leaves it out resulting in runtime exceptions. Code to reproduce:

using System.Collections.Generic;
using System.Linq;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            var invoices = new List<Invoice>().AsQueryable();
            var oneTimeCharges = new List<OneTimeCharge>().AsQueryable();
            var otcCharges = invoices.Join(oneTimeCharges, inv => inv.InvoiceId, otc => otc.Invoice, (inv, otc) => inv.InvoiceId);
        }        
    }

    public class OneTimeCharge
    {
        public int OneTimeChargeId { get; set; }
        public int? Invoice { get; set; }
    }

    public class Invoice
    {

        public int InvoiceId { get; set; }
    }    
}

Note - Without AsQueryable this produces totally different IL so it is needed to trigger the bug.

Expected Behavior:

Here's what is generated and works as expected from current release version

// ConsoleApplication2.Program
private static void Main(string[] args)
{
    IQueryable<Invoice> invoices = new List<Invoice>().AsQueryable<Invoice>();
    IQueryable<OneTimeCharge> oneTimeCharges = new List<OneTimeCharge>().AsQueryable<OneTimeCharge>();
    IQueryable<Invoice> arg_EE_0 = invoices;
    IEnumerable<OneTimeCharge> arg_EE_1 = oneTimeCharges;
    ParameterExpression parameterExpression = Expression.Parameter(typeof(Invoice), "inv");
    Expression<Func<Invoice, int?>> arg_EE_2 = Expression.Lambda<Func<Invoice, int?>>(Expression.Convert(Expression.Property(parameterExpression, methodof(Invoice.get_InvoiceId())), typeof(int?)), new ParameterExpression[]
    {
        parameterExpression
    });
    parameterExpression = Expression.Parameter(typeof(OneTimeCharge), "otc");
    Expression<Func<OneTimeCharge, int?>> arg_EE_3 = Expression.Lambda<Func<OneTimeCharge, int?>>(Expression.Property(parameterExpression, methodof(OneTimeCharge.get_Invoice())), new ParameterExpression[]
    {
        parameterExpression
    });
    parameterExpression = Expression.Parameter(typeof(Invoice), "inv");
    ParameterExpression parameterExpression2 = Expression.Parameter(typeof(OneTimeCharge), "otc");
    IQueryable<int> otcCharges = arg_EE_0.Join(arg_EE_1, arg_EE_2, arg_EE_3, Expression.Lambda<Func<Invoice, OneTimeCharge, int>>(Expression.Property(parameterExpression, methodof(Invoice.get_InvoiceId())), new ParameterExpression[]
    {
        parameterExpression,
        parameterExpression2
    }));
}

Actual Behavior:

Here's what is generated in the current release version of Roslyn

// ConsoleApplication2.Program
private static void Main(string[] args)
{
    IQueryable<Invoice> invoices = new List<Invoice>().AsQueryable<Invoice>();
    IQueryable<OneTimeCharge> oneTimeCharges = new List<OneTimeCharge>().AsQueryable<OneTimeCharge>();
    IQueryable<Invoice> arg_DF_0 = invoices;
    IEnumerable<OneTimeCharge> arg_DF_1 = oneTimeCharges;
    ParameterExpression parameterExpression = Expression.Parameter(typeof(Invoice), "inv");
    Expression<Func<Invoice, int?>> arg_DF_2 = Expression.Lambda<Func<Invoice, int?>>(Expression.Property(parameterExpression, methodof(Invoice.get_InvoiceId())), new ParameterExpression[]
    {
        parameterExpression
    });
    parameterExpression = Expression.Parameter(typeof(OneTimeCharge), "otc");
    Expression<Func<OneTimeCharge, int?>> arg_DF_3 = Expression.Lambda<Func<OneTimeCharge, int?>>(Expression.Property(parameterExpression, methodof(OneTimeCharge.get_Invoice())), new ParameterExpression[]
    {
        parameterExpression
    });
    parameterExpression = Expression.Parameter(typeof(Invoice), "inv");
    ParameterExpression parameterExpression2 = Expression.Parameter(typeof(OneTimeCharge), "otc");
    IQueryable<int> otcCharges = arg_DF_0.Join(arg_DF_1, arg_DF_2, arg_DF_3, Expression.Lambda<Func<Invoice, OneTimeCharge, int>>(Expression.Property(parameterExpression, methodof(Invoice.get_InvoiceId())), new ParameterExpression[]
    {
        parameterExpression,
        parameterExpression2
    }));
}

This results in the following runtime exception

Unhandled Exception: System.ArgumentException: Expression of type 'System.Int32' cannot be used for return type 'System.Nullable`1[System.Int32]'
   at System.Linq.Expressions.Expression.ValidateLambdaArgs(Type delegateType, Expression& body, ReadOnlyCollection`1 parameters)
   at System.Linq.Expressions.Expression.Lambda[TDelegate](Expression body, String name, Boolean tailCall, IEnumerable`1 parameters)
   at System.Linq.Expressions.Expression.Lambda[TDelegate](Expression body, Boolean tailCall, IEnumerable`1 parameters)
   at System.Linq.Expressions.Expression.Lambda[TDelegate](Expression body, ParameterExpression[] parameters)
   at ConsoleApplication2.Program.Main(String[] args) in C:\Users\Phil\Documents\Visual Studio 15\Projects\ConsoleApplication2\ConsoleApplication2\Program.cs:line 12

Only difference is the missing Expression.Convert

@phil-scott-78

This comment has been minimized.

Copy link
Contributor Author

@phil-scott-78 phil-scott-78 commented Oct 26, 2016

Submitted PR (#14755) with a unit test that reproduces and a potential fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
2 participants
You can’t perform that action at this time.