|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Microsoft.CSharp.RuntimeBinder.Syntax;
namespace Microsoft.CSharp.RuntimeBinder.Semantics
{
internal sealed class ExpressionTreeRewriter : ExprVisitorBase
{
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
public static ExprBinOp Rewrite(ExprBoundLambda expr) => new ExpressionTreeRewriter().VisitBoundLambda(expr);
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
protected override Expr Dispatch(Expr expr)
{
Debug.Assert(expr != null);
Expr result = base.Dispatch(expr);
if (result == expr)
{
throw Error.InternalCompilerError();
}
return result;
}
/////////////////////////////////////////////////////////////////////////////////
// Statement types.
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
protected override Expr VisitASSIGNMENT(ExprAssignment assignment)
{
Debug.Assert(assignment != null);
// For assignments, we either have a member assignment or an indexed assignment.
//Debug.Assert(assignment.GetLHS().isPROP() || assignment.GetLHS().isFIELD() || assignment.GetLHS().isARRAYINDEX() || assignment.GetLHS().isLOCAL());
Expr lhs;
if (assignment.LHS is ExprProperty prop)
{
if (prop.OptionalArguments == null)
{
// Regular property.
lhs = Visit(prop);
}
else
{
// Indexed assignment. Here we need to find the instance of the object, create the
// PropInfo for the thing, and get the array of expressions that make up the index arguments.
//
// The LHS becomes Expression.Property(instance, indexerInfo, arguments).
Expr instance = Visit(prop.MemberGroup.OptionalObject);
Expr propInfo = ExprFactory.CreatePropertyInfo(prop.PropWithTypeSlot.Prop(), prop.PropWithTypeSlot.Ats);
Expr arguments = GenerateParamsArray(
GenerateArgsList(prop.OptionalArguments),
PredefinedType.PT_EXPRESSION);
lhs = GenerateCall(PREDEFMETH.PM_EXPRESSION_PROPERTY, instance, propInfo, arguments);
}
}
else
{
lhs = Visit(assignment.LHS);
}
Expr rhs = Visit(assignment.RHS);
return GenerateCall(PREDEFMETH.PM_EXPRESSION_ASSIGN, lhs, rhs);
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
protected override Expr VisitMULTIGET(ExprMultiGet pExpr)
{
return Visit(pExpr.OptionalMulti.Left);
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
protected override Expr VisitMULTI(ExprMulti pExpr)
{
Expr rhs = Visit(pExpr.Operator);
Expr lhs = Visit(pExpr.Left);
return GenerateCall(PREDEFMETH.PM_EXPRESSION_ASSIGN, lhs, rhs);
}
/////////////////////////////////////////////////////////////////////////////////
// Expression types.
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private ExprBinOp VisitBoundLambda(ExprBoundLambda anonmeth)
{
Debug.Assert(anonmeth != null);
MethodSymbol lambdaMethod = GetPreDefMethod(PREDEFMETH.PM_EXPRESSION_LAMBDA);
AggregateType delegateType = anonmeth.DelegateType;
TypeArray lambdaTypeParams = TypeArray.Allocate(delegateType);
AggregateType expressionType = SymbolLoader.GetPredefindType(PredefinedType.PT_EXPRESSION);
MethWithInst mwi = new MethWithInst(lambdaMethod, expressionType, lambdaTypeParams);
Expr createParameters = CreateWraps(anonmeth);
Debug.Assert(createParameters != null);
Debug.Assert(anonmeth.Expression != null);
Expr body = Visit(anonmeth.Expression);
Debug.Assert(anonmeth.ArgumentScope.nextChild == null);
Expr parameters = GenerateParamsArray(null, PredefinedType.PT_PARAMETEREXPRESSION);
Expr args = ExprFactory.CreateList(body, parameters);
CType typeRet = TypeManager.SubstType(mwi.Meth().RetType, mwi.GetType(), mwi.TypeArgs);
ExprMemberGroup pMemGroup = ExprFactory.CreateMemGroup(null, mwi);
ExprCall call = ExprFactory.CreateCall(0, typeRet, args, pMemGroup, mwi);
call.PredefinedMethod = PREDEFMETH.PM_EXPRESSION_LAMBDA;
return ExprFactory.CreateSequence(createParameters, call);
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
protected override Expr VisitCONSTANT(ExprConstant expr)
{
Debug.Assert(expr != null);
return GenerateConstant(expr);
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
protected override Expr VisitLOCAL(ExprLocal local)
{
Debug.Assert(local != null);
Debug.Assert(local.Local.wrap != null);
return local.Local.wrap;
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
protected override Expr VisitFIELD(ExprField expr)
{
Debug.Assert(expr != null);
Expr pObject;
if (expr.OptionalObject == null)
{
pObject = ExprFactory.CreateNull();
}
else
{
pObject = Visit(expr.OptionalObject);
}
ExprFieldInfo pFieldInfo = ExprFactory.CreateFieldInfo(expr.FieldWithType.Field(), expr.FieldWithType.GetType());
return GenerateCall(PREDEFMETH.PM_EXPRESSION_FIELD, pObject, pFieldInfo);
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
protected override Expr VisitUSERDEFINEDCONVERSION(ExprUserDefinedConversion expr)
{
Debug.Assert(expr != null);
return GenerateUserDefinedConversion(expr, expr.Argument);
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
protected override Expr VisitCAST(ExprCast pExpr)
{
Debug.Assert(pExpr != null);
Expr pArgument = pExpr.Argument;
// If we have generated an identity cast or reference cast to a base class
// we can omit the cast.
if (pArgument.Type == pExpr.Type ||
SymbolLoader.IsBaseClassOfClass(pArgument.Type, pExpr.Type) ||
CConversions.FImpRefConv(pArgument.Type, pExpr.Type))
{
return Visit(pArgument);
}
// If we have a cast to PredefinedType.PT_G_EXPRESSION and the thing that we're casting is
// a EXPRBOUNDLAMBDA that is an expression tree, then just visit the expression tree.
if (pExpr.Type != null &&
pExpr.Type.IsPredefType(PredefinedType.PT_G_EXPRESSION) &&
pArgument is ExprBoundLambda)
{
return Visit(pArgument);
}
Expr result = GenerateConversion(pArgument, pExpr.Type, pExpr.isChecked());
if ((pExpr.Flags & EXPRFLAG.EXF_UNBOXRUNTIME) != 0)
{
// Propagate the unbox flag to the call for the ExpressionTreeCallRewriter.
result.Flags |= EXPRFLAG.EXF_UNBOXRUNTIME;
}
return result;
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
protected override Expr VisitCONCAT(ExprConcat expr)
{
Debug.Assert(expr != null);
PREDEFMETH pdm;
if (expr.FirstArgument.Type.IsPredefType(PredefinedType.PT_STRING) && expr.SecondArgument.Type.IsPredefType(PredefinedType.PT_STRING))
{
pdm = PREDEFMETH.PM_STRING_CONCAT_STRING_2;
}
else
{
pdm = PREDEFMETH.PM_STRING_CONCAT_OBJECT_2;
}
Expr p1 = Visit(expr.FirstArgument);
Expr p2 = Visit(expr.SecondArgument);
MethodSymbol method = GetPreDefMethod(pdm);
Expr methodInfo = ExprFactory.CreateMethodInfo(method, SymbolLoader.GetPredefindType(PredefinedType.PT_STRING), null);
return GenerateCall(PREDEFMETH.PM_EXPRESSION_ADD_USER_DEFINED, p1, p2, methodInfo);
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
protected override Expr VisitBINOP(ExprBinOp expr)
{
Debug.Assert(expr != null);
if (expr.UserDefinedCallMethod != null)
{
return GenerateUserDefinedBinaryOperator(expr);
}
else
{
return GenerateBuiltInBinaryOperator(expr);
}
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
protected override Expr VisitUNARYOP(ExprUnaryOp pExpr)
{
Debug.Assert(pExpr != null);
if (pExpr.UserDefinedCallMethod != null)
{
return GenerateUserDefinedUnaryOperator(pExpr);
}
else
{
return GenerateBuiltInUnaryOperator(pExpr);
}
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
protected override Expr VisitARRAYINDEX(ExprArrayIndex pExpr)
{
Debug.Assert(pExpr != null);
Expr arr = Visit(pExpr.Array);
Expr args = GenerateIndexList(pExpr.Index);
if (args is ExprList)
{
Expr Params = GenerateParamsArray(args, PredefinedType.PT_EXPRESSION);
return GenerateCall(PREDEFMETH.PM_EXPRESSION_ARRAYINDEX2, arr, Params);
}
return GenerateCall(PREDEFMETH.PM_EXPRESSION_ARRAYINDEX, arr, args);
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
protected override Expr VisitCALL(ExprCall expr)
{
Debug.Assert(expr != null);
switch (expr.NullableCallLiftKind)
{
default:
break;
case NullableCallLiftKind.NullableIntermediateConversion:
case NullableCallLiftKind.NullableConversion:
case NullableCallLiftKind.NullableConversionConstructor:
return GenerateConversion(expr.OptionalArguments, expr.Type, expr.isChecked());
case NullableCallLiftKind.NotLiftedIntermediateConversion:
case NullableCallLiftKind.UserDefinedConversion:
return GenerateUserDefinedConversion(expr.OptionalArguments, expr.Type, expr.MethWithInst);
}
if (expr.MethWithInst.Meth().IsConstructor())
{
return GenerateConstructor(expr);
}
ExprMemberGroup memberGroup = expr.MemberGroup;
if (memberGroup.IsDelegate)
{
return GenerateDelegateInvoke(expr);
}
Expr pObject;
if (expr.MethWithInst.Meth().isStatic || expr.MemberGroup.OptionalObject == null)
{
pObject = ExprFactory.CreateNull();
}
else
{
pObject = expr.MemberGroup.OptionalObject;
// If we have, say, an int? which is the object of a call to ToString
// then we do NOT want to generate ((object)i).ToString() because that
// will convert a null-valued int? to a null object. Rather what we want
// to do is box it to a ValueType and call ValueType.ToString.
//
// To implement this we say that if the object of the call is an implicit boxing cast
// then just generate the object, not the cast. If the cast is explicit in the
// source code then it will be an EXPLICITCAST and we will visit it normally.
//
// It might be better to rewrite the expression tree API so that it
// can handle in the general case all implicit boxing conversions. Right now it
// requires that all arguments to a call that need to be boxed be explicitly boxed.
if (pObject != null && pObject is ExprCast cast && cast.IsBoxingCast)
{
pObject = cast.Argument;
}
pObject = Visit(pObject);
}
Expr methodInfo = ExprFactory.CreateMethodInfo(expr.MethWithInst);
Expr args = GenerateArgsList(expr.OptionalArguments);
Expr Params = GenerateParamsArray(args, PredefinedType.PT_EXPRESSION);
PREDEFMETH pdm = PREDEFMETH.PM_EXPRESSION_CALL;
Debug.Assert(!expr.MethWithInst.Meth().isVirtual || expr.MemberGroup.OptionalObject != null);
return GenerateCall(pdm, pObject, methodInfo, Params);
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
protected override Expr VisitPROP(ExprProperty expr)
{
Debug.Assert(expr != null);
Expr pObject;
if (expr.PropWithTypeSlot.Prop().isStatic || expr.MemberGroup.OptionalObject == null)
{
pObject = ExprFactory.CreateNull();
}
else
{
pObject = Visit(expr.MemberGroup.OptionalObject);
}
Expr propInfo = ExprFactory.CreatePropertyInfo(expr.PropWithTypeSlot.Prop(), expr.PropWithTypeSlot.GetType());
if (expr.OptionalArguments != null)
{
// It is an indexer property. Turn it into a virtual method call.
Expr args = GenerateArgsList(expr.OptionalArguments);
Expr Params = GenerateParamsArray(args, PredefinedType.PT_EXPRESSION);
return GenerateCall(PREDEFMETH.PM_EXPRESSION_PROPERTY, pObject, propInfo, Params);
}
return GenerateCall(PREDEFMETH.PM_EXPRESSION_PROPERTY, pObject, propInfo);
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
protected override Expr VisitARRINIT(ExprArrayInit expr)
{
Debug.Assert(expr != null);
// POSSIBLE ERROR: Multi-d should be an error?
Expr pTypeOf = CreateTypeOf(((ArrayType)expr.Type).ElementType);
Expr args = GenerateArgsList(expr.OptionalArguments);
Expr Params = GenerateParamsArray(args, PredefinedType.PT_EXPRESSION);
return GenerateCall(PREDEFMETH.PM_EXPRESSION_NEWARRAYINIT, pTypeOf, Params);
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
protected override Expr VisitZEROINIT(ExprZeroInit expr)
{
Debug.Assert(expr != null);
return GenerateConstant(expr);
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
protected override Expr VisitTYPEOF(ExprTypeOf expr)
{
Debug.Assert(expr != null);
return GenerateConstant(expr);
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private Expr GenerateDelegateInvoke(ExprCall expr)
{
Debug.Assert(expr != null);
ExprMemberGroup memberGroup = expr.MemberGroup;
Debug.Assert(memberGroup.IsDelegate);
Expr oldObject = memberGroup.OptionalObject;
Debug.Assert(oldObject != null);
Expr pObject = Visit(oldObject);
Expr args = GenerateArgsList(expr.OptionalArguments);
Expr Params = GenerateParamsArray(args, PredefinedType.PT_EXPRESSION);
return GenerateCall(PREDEFMETH.PM_EXPRESSION_INVOKE, pObject, Params);
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private Expr GenerateBuiltInBinaryOperator(ExprBinOp expr)
{
Debug.Assert(expr != null);
PREDEFMETH pdm = expr.Kind switch
{
ExpressionKind.LeftShirt => PREDEFMETH.PM_EXPRESSION_LEFTSHIFT,
ExpressionKind.RightShift => PREDEFMETH.PM_EXPRESSION_RIGHTSHIFT,
ExpressionKind.BitwiseExclusiveOr => PREDEFMETH.PM_EXPRESSION_EXCLUSIVEOR,
ExpressionKind.BitwiseOr => PREDEFMETH.PM_EXPRESSION_OR,
ExpressionKind.BitwiseAnd => PREDEFMETH.PM_EXPRESSION_AND,
ExpressionKind.LogicalAnd => PREDEFMETH.PM_EXPRESSION_ANDALSO,
ExpressionKind.LogicalOr => PREDEFMETH.PM_EXPRESSION_ORELSE,
ExpressionKind.StringEq => PREDEFMETH.PM_EXPRESSION_EQUAL,
ExpressionKind.Eq => PREDEFMETH.PM_EXPRESSION_EQUAL,
ExpressionKind.StringNotEq => PREDEFMETH.PM_EXPRESSION_NOTEQUAL,
ExpressionKind.NotEq => PREDEFMETH.PM_EXPRESSION_NOTEQUAL,
ExpressionKind.GreaterThanOrEqual => PREDEFMETH.PM_EXPRESSION_GREATERTHANOREQUAL,
ExpressionKind.LessThanOrEqual => PREDEFMETH.PM_EXPRESSION_LESSTHANOREQUAL,
ExpressionKind.LessThan => PREDEFMETH.PM_EXPRESSION_LESSTHAN,
ExpressionKind.GreaterThan => PREDEFMETH.PM_EXPRESSION_GREATERTHAN,
ExpressionKind.Modulo => PREDEFMETH.PM_EXPRESSION_MODULO,
ExpressionKind.Divide => PREDEFMETH.PM_EXPRESSION_DIVIDE,
ExpressionKind.Multiply => expr.isChecked() ? PREDEFMETH.PM_EXPRESSION_MULTIPLYCHECKED : PREDEFMETH.PM_EXPRESSION_MULTIPLY,
ExpressionKind.Subtract => expr.isChecked() ? PREDEFMETH.PM_EXPRESSION_SUBTRACTCHECKED : PREDEFMETH.PM_EXPRESSION_SUBTRACT,
ExpressionKind.Add => expr.isChecked() ? PREDEFMETH.PM_EXPRESSION_ADDCHECKED : PREDEFMETH.PM_EXPRESSION_ADD,
_ => throw Error.InternalCompilerError(),
};
Expr origL = expr.OptionalLeftChild;
Expr origR = expr.OptionalRightChild;
Debug.Assert(origL != null);
Debug.Assert(origR != null);
CType typeL = origL.Type;
CType typeR = origR.Type;
Expr newL = Visit(origL);
Expr newR = Visit(origR);
bool didEnumConversion = false;
CType convertL = null;
CType convertR = null;
if (typeL.IsEnumType)
{
// We have already inserted casts if not lifted, so we should never see an enum.
Debug.Assert(expr.IsLifted);
convertL = TypeManager.GetNullable(typeL.UnderlyingEnumType);
typeL = convertL;
didEnumConversion = true;
}
else if (typeL is NullableType nubL && nubL.UnderlyingType.IsEnumType)
{
Debug.Assert(expr.IsLifted);
convertL = TypeManager.GetNullable(nubL.UnderlyingType.UnderlyingEnumType);
typeL = convertL;
didEnumConversion = true;
}
if (typeR.IsEnumType)
{
Debug.Assert(expr.IsLifted);
convertR = TypeManager.GetNullable(typeR.UnderlyingEnumType);
typeR = convertR;
didEnumConversion = true;
}
else if (typeR is NullableType nubR && nubR.UnderlyingType.IsEnumType)
{
Debug.Assert(expr.IsLifted);
convertR = TypeManager.GetNullable(nubR.UnderlyingType.UnderlyingEnumType);
typeR = convertR;
didEnumConversion = true;
}
if (typeL is NullableType nubL2 && nubL2.UnderlyingType == typeR)
{
convertR = typeL;
}
if (typeR is NullableType nubR2 && nubR2.UnderlyingType == typeL)
{
convertL = typeR;
}
if (convertL != null)
{
newL = GenerateCall(PREDEFMETH.PM_EXPRESSION_CONVERT, newL, CreateTypeOf(convertL));
}
if (convertR != null)
{
newR = GenerateCall(PREDEFMETH.PM_EXPRESSION_CONVERT, newR, CreateTypeOf(convertR));
}
Expr call = GenerateCall(pdm, newL, newR);
if (didEnumConversion && expr.Type.StripNubs().IsEnumType)
{
call = GenerateCall(PREDEFMETH.PM_EXPRESSION_CONVERT, call, CreateTypeOf(expr.Type));
}
return call;
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private Expr GenerateBuiltInUnaryOperator(ExprUnaryOp expr)
{
Debug.Assert(expr != null);
PREDEFMETH pdm;
switch (expr.Kind)
{
case ExpressionKind.UnaryPlus:
return Visit(expr.Child);
case ExpressionKind.BitwiseNot: pdm = PREDEFMETH.PM_EXPRESSION_NOT; break;
case ExpressionKind.LogicalNot: pdm = PREDEFMETH.PM_EXPRESSION_NOT; break;
case ExpressionKind.Negate:
pdm = expr.isChecked() ? PREDEFMETH.PM_EXPRESSION_NEGATECHECKED : PREDEFMETH.PM_EXPRESSION_NEGATE;
break;
default:
throw Error.InternalCompilerError();
}
Expr origOp = expr.Child;
// Such operations are always already casts on operations on casts.
Debug.Assert(!(origOp.Type is NullableType nub) || !nub.UnderlyingType.IsEnumType);
return GenerateCall(pdm, Visit(origOp));
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private Expr GenerateUserDefinedBinaryOperator(ExprBinOp expr)
{
Debug.Assert(expr != null);
PREDEFMETH pdm;
switch (expr.Kind)
{
case ExpressionKind.LogicalOr: pdm = PREDEFMETH.PM_EXPRESSION_ORELSE_USER_DEFINED; break;
case ExpressionKind.LogicalAnd: pdm = PREDEFMETH.PM_EXPRESSION_ANDALSO_USER_DEFINED; break;
case ExpressionKind.LeftShirt: pdm = PREDEFMETH.PM_EXPRESSION_LEFTSHIFT_USER_DEFINED; break;
case ExpressionKind.RightShift: pdm = PREDEFMETH.PM_EXPRESSION_RIGHTSHIFT_USER_DEFINED; break;
case ExpressionKind.BitwiseExclusiveOr: pdm = PREDEFMETH.PM_EXPRESSION_EXCLUSIVEOR_USER_DEFINED; break;
case ExpressionKind.BitwiseOr: pdm = PREDEFMETH.PM_EXPRESSION_OR_USER_DEFINED; break;
case ExpressionKind.BitwiseAnd: pdm = PREDEFMETH.PM_EXPRESSION_AND_USER_DEFINED; break;
case ExpressionKind.Modulo: pdm = PREDEFMETH.PM_EXPRESSION_MODULO_USER_DEFINED; break;
case ExpressionKind.Divide: pdm = PREDEFMETH.PM_EXPRESSION_DIVIDE_USER_DEFINED; break;
case ExpressionKind.StringEq:
case ExpressionKind.StringNotEq:
case ExpressionKind.DelegateEq:
case ExpressionKind.DelegateNotEq:
case ExpressionKind.Eq:
case ExpressionKind.NotEq:
case ExpressionKind.GreaterThanOrEqual:
case ExpressionKind.GreaterThan:
case ExpressionKind.LessThanOrEqual:
case ExpressionKind.LessThan:
return GenerateUserDefinedComparisonOperator(expr);
case ExpressionKind.DelegateSubtract:
case ExpressionKind.Subtract:
pdm = expr.isChecked() ? PREDEFMETH.PM_EXPRESSION_SUBTRACTCHECKED_USER_DEFINED : PREDEFMETH.PM_EXPRESSION_SUBTRACT_USER_DEFINED;
break;
case ExpressionKind.DelegateAdd:
case ExpressionKind.Add:
pdm = expr.isChecked() ? PREDEFMETH.PM_EXPRESSION_ADDCHECKED_USER_DEFINED : PREDEFMETH.PM_EXPRESSION_ADD_USER_DEFINED;
break;
case ExpressionKind.Multiply:
pdm = expr.isChecked() ? PREDEFMETH.PM_EXPRESSION_MULTIPLYCHECKED_USER_DEFINED : PREDEFMETH.PM_EXPRESSION_MULTIPLY_USER_DEFINED;
break;
default:
throw Error.InternalCompilerError();
}
Expr p1 = expr.OptionalLeftChild;
Expr p2 = expr.OptionalRightChild;
Expr udcall = expr.OptionalUserDefinedCall;
if (udcall != null)
{
Debug.Assert(udcall.Kind == ExpressionKind.Call || udcall.Kind == ExpressionKind.UserLogicalOp);
if (udcall is ExprCall ascall)
{
ExprList args = (ExprList)ascall.OptionalArguments;
Debug.Assert(args.OptionalNextListNode.Kind != ExpressionKind.List);
p1 = args.OptionalElement;
p2 = args.OptionalNextListNode;
}
else
{
ExprUserLogicalOp userLogOp = udcall as ExprUserLogicalOp;
Debug.Assert(userLogOp != null);
ExprList args = (ExprList)userLogOp.OperatorCall.OptionalArguments;
Debug.Assert(args.OptionalNextListNode.Kind != ExpressionKind.List);
p1 = ((ExprWrap)args.OptionalElement).OptionalExpression;
p2 = args.OptionalNextListNode;
}
}
p1 = Visit(p1);
p2 = Visit(p2);
FixLiftedUserDefinedBinaryOperators(expr, ref p1, ref p2);
Expr methodInfo = ExprFactory.CreateMethodInfo(expr.UserDefinedCallMethod);
Expr call = GenerateCall(pdm, p1, p2, methodInfo);
// Delegate add/subtract generates a call to Combine/Remove, which returns System.Delegate,
// not the operand delegate CType. We must cast to the delegate CType.
if (expr.Kind == ExpressionKind.DelegateSubtract || expr.Kind == ExpressionKind.DelegateAdd)
{
Expr pTypeOf = CreateTypeOf(expr.Type);
return GenerateCall(PREDEFMETH.PM_EXPRESSION_CONVERT, call, pTypeOf);
}
return call;
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private Expr GenerateUserDefinedUnaryOperator(ExprUnaryOp expr)
{
Debug.Assert(expr != null);
PREDEFMETH pdm;
Expr arg = expr.Child;
ExprCall call = (ExprCall)expr.OptionalUserDefinedCall;
if (call != null)
{
// Use the actual argument of the call; it may contain user-defined
// conversions or be a bound lambda, and that will not be in the original
// argument stashed away in the left child of the operator.
arg = call.OptionalArguments;
}
Debug.Assert(arg != null && arg.Kind != ExpressionKind.List);
switch (expr.Kind)
{
case ExpressionKind.True:
case ExpressionKind.False:
return Visit(call);
case ExpressionKind.UnaryPlus:
pdm = PREDEFMETH.PM_EXPRESSION_UNARYPLUS_USER_DEFINED;
break;
case ExpressionKind.BitwiseNot: pdm = PREDEFMETH.PM_EXPRESSION_NOT_USER_DEFINED; break;
case ExpressionKind.LogicalNot: pdm = PREDEFMETH.PM_EXPRESSION_NOT_USER_DEFINED; break;
case ExpressionKind.DecimalNegate:
case ExpressionKind.Negate:
pdm = expr.isChecked() ? PREDEFMETH.PM_EXPRESSION_NEGATECHECKED_USER_DEFINED : PREDEFMETH.PM_EXPRESSION_NEGATE_USER_DEFINED;
break;
case ExpressionKind.Inc:
case ExpressionKind.Dec:
case ExpressionKind.DecimalInc:
case ExpressionKind.DecimalDec:
pdm = PREDEFMETH.PM_EXPRESSION_CALL;
break;
default:
throw Error.InternalCompilerError();
}
Expr op = Visit(arg);
Expr methodInfo = ExprFactory.CreateMethodInfo(expr.UserDefinedCallMethod);
if (expr.Kind == ExpressionKind.Inc || expr.Kind == ExpressionKind.Dec ||
expr.Kind == ExpressionKind.DecimalInc || expr.Kind == ExpressionKind.DecimalDec)
{
return GenerateCall(pdm, null, methodInfo, GenerateParamsArray(op, PredefinedType.PT_EXPRESSION));
}
return GenerateCall(pdm, op, methodInfo);
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private Expr GenerateUserDefinedComparisonOperator(ExprBinOp expr)
{
Debug.Assert(expr != null);
PREDEFMETH pdm = expr.Kind switch
{
ExpressionKind.StringEq => PREDEFMETH.PM_EXPRESSION_EQUAL_USER_DEFINED,
ExpressionKind.StringNotEq => PREDEFMETH.PM_EXPRESSION_NOTEQUAL_USER_DEFINED,
ExpressionKind.DelegateEq => PREDEFMETH.PM_EXPRESSION_EQUAL_USER_DEFINED,
ExpressionKind.DelegateNotEq => PREDEFMETH.PM_EXPRESSION_NOTEQUAL_USER_DEFINED,
ExpressionKind.Eq => PREDEFMETH.PM_EXPRESSION_EQUAL_USER_DEFINED,
ExpressionKind.NotEq => PREDEFMETH.PM_EXPRESSION_NOTEQUAL_USER_DEFINED,
ExpressionKind.LessThanOrEqual => PREDEFMETH.PM_EXPRESSION_LESSTHANOREQUAL_USER_DEFINED,
ExpressionKind.LessThan => PREDEFMETH.PM_EXPRESSION_LESSTHAN_USER_DEFINED,
ExpressionKind.GreaterThanOrEqual => PREDEFMETH.PM_EXPRESSION_GREATERTHANOREQUAL_USER_DEFINED,
ExpressionKind.GreaterThan => PREDEFMETH.PM_EXPRESSION_GREATERTHAN_USER_DEFINED,
_ => throw Error.InternalCompilerError(),
};
Expr p1 = expr.OptionalLeftChild;
Expr p2 = expr.OptionalRightChild;
if (expr.OptionalUserDefinedCall != null)
{
ExprCall udcall = (ExprCall)expr.OptionalUserDefinedCall;
ExprList args = (ExprList)udcall.OptionalArguments;
Debug.Assert(args.OptionalNextListNode.Kind != ExpressionKind.List);
p1 = args.OptionalElement;
p2 = args.OptionalNextListNode;
}
p1 = Visit(p1);
p2 = Visit(p2);
FixLiftedUserDefinedBinaryOperators(expr, ref p1, ref p2);
Expr lift = ExprFactory.CreateBoolConstant(false); // We never lift to null in C#.
Expr methodInfo = ExprFactory.CreateMethodInfo(expr.UserDefinedCallMethod);
return GenerateCall(pdm, p1, p2, lift, methodInfo);
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private Expr GenerateConversion(Expr arg, CType CType, bool bChecked) =>
GenerateConversionWithSource(Visit(arg), CType, bChecked || arg.isChecked());
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private static Expr GenerateConversionWithSource(Expr pTarget, CType pType, bool bChecked)
{
PREDEFMETH pdm = bChecked ? PREDEFMETH.PM_EXPRESSION_CONVERTCHECKED : PREDEFMETH.PM_EXPRESSION_CONVERT;
Expr pTypeOf = CreateTypeOf(pType);
return GenerateCall(pdm, pTarget, pTypeOf);
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private Expr GenerateValueAccessConversion(Expr pArgument)
{
Debug.Assert(pArgument != null);
CType pStrippedTypeOfArgument = pArgument.Type.StripNubs();
Expr pStrippedTypeExpr = CreateTypeOf(pStrippedTypeOfArgument);
return GenerateCall(PREDEFMETH.PM_EXPRESSION_CONVERT, Visit(pArgument), pStrippedTypeExpr);
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private Expr GenerateUserDefinedConversion(Expr arg, CType type, MethWithInst method)
{
Expr target = Visit(arg);
return GenerateUserDefinedConversion(arg, type, target, method);
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private static Expr GenerateUserDefinedConversion(Expr arg, CType CType, Expr target, MethWithInst method)
{
// The user-defined explicit conversion from enum? to decimal or decimal? requires
// that we convert the enum? to its nullable underlying CType.
if (isEnumToDecimalConversion(arg.Type, CType))
{
// Special case: If we have enum? to decimal? then we need to emit
// a conversion from enum? to its nullable underlying CType first.
// This is unfortunate; we ought to reorganize how conversions are
// represented in the Expr tree so that this is more transparent.
// converting an enum to its underlying CType never fails, so no need to check it.
CType underlyingType = arg.Type.StripNubs().UnderlyingEnumType;
CType nullableType = TypeManager.GetNullable(underlyingType);
Expr typeofNubEnum = CreateTypeOf(nullableType);
target = GenerateCall(PREDEFMETH.PM_EXPRESSION_CONVERT, target, typeofNubEnum);
}
// If the methodinfo does not return the target CType AND this is not a lifted conversion
// from one value CType to another, then we need to wrap the whole thing in another conversion,
// e.g. if we have a user-defined conversion from int to S? and we have (S)myint, then we need to generate
// Convert(Convert(myint, typeof(S?), op_implicit), typeof(S))
CType pMethodReturnType = TypeManager.SubstType(method.Meth().RetType,
method.GetType(), method.TypeArgs);
bool fDontLiftReturnType = (pMethodReturnType == CType || (IsNullableValueType(arg.Type) && IsNullableValueType(CType)));
Expr typeofInner = CreateTypeOf(fDontLiftReturnType ? CType : pMethodReturnType);
Expr methodInfo = ExprFactory.CreateMethodInfo(method);
PREDEFMETH pdmInner = arg.isChecked() ? PREDEFMETH.PM_EXPRESSION_CONVERTCHECKED_USER_DEFINED : PREDEFMETH.PM_EXPRESSION_CONVERT_USER_DEFINED;
Expr callUserDefinedConversion = GenerateCall(pdmInner, target, typeofInner, methodInfo);
if (fDontLiftReturnType)
{
return callUserDefinedConversion;
}
PREDEFMETH pdmOuter = arg.isChecked() ? PREDEFMETH.PM_EXPRESSION_CONVERTCHECKED : PREDEFMETH.PM_EXPRESSION_CONVERT;
Expr typeofOuter = CreateTypeOf(CType);
return GenerateCall(pdmOuter, callUserDefinedConversion, typeofOuter);
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private Expr GenerateUserDefinedConversion(ExprUserDefinedConversion pExpr, Expr pArgument)
{
Expr pCastCall = pExpr.UserDefinedCall;
Expr pCastArgument = pExpr.Argument;
Expr pConversionSource;
if (!isEnumToDecimalConversion(pArgument.Type, pExpr.Type)
&& IsNullableValueAccess(pCastArgument, pArgument))
{
// We have an implicit conversion of nullable CType to the value CType, generate a convert node for it.
pConversionSource = GenerateValueAccessConversion(pArgument);
}
else
{
ExprCall call = pCastCall as ExprCall;
Expr pUDConversion = call?.PConversions;
if (pUDConversion != null)
{
if (pUDConversion is ExprCall convCall)
{
Expr pUDConversionArgument = convCall.OptionalArguments;
if (IsNullableValueAccess(pUDConversionArgument, pArgument))
{
pConversionSource = GenerateValueAccessConversion(pArgument);
}
else
{
pConversionSource = Visit(pUDConversionArgument);
}
return GenerateConversionWithSource(pConversionSource, pCastCall.Type, call.isChecked());
}
// This can happen if we have a UD conversion from C to, say, int,
// and we have an explicit cast to decimal?. The conversion should
// then be bound as two chained user-defined conversions.
Debug.Assert(pUDConversion is ExprUserDefinedConversion);
// Just recurse.
return GenerateUserDefinedConversion((ExprUserDefinedConversion)pUDConversion, pArgument);
}
pConversionSource = Visit(pCastArgument);
}
return GenerateUserDefinedConversion(pCastArgument, pExpr.Type, pConversionSource, pExpr.UserDefinedCallMethod);
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private static Expr GenerateParameter(string name, CType CType)
{
SymbolLoader.GetPredefindType(PredefinedType.PT_STRING); // force an ensure state
ExprConstant nameString = ExprFactory.CreateStringConstant(name);
ExprTypeOf pTypeOf = CreateTypeOf(CType);
return GenerateCall(PREDEFMETH.PM_EXPRESSION_PARAMETER, pTypeOf, nameString);
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private static MethodSymbol GetPreDefMethod(PREDEFMETH pdm) => PredefinedMembers.GetMethod(pdm);
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private static ExprTypeOf CreateTypeOf(CType type) => ExprFactory.CreateTypeOf(type);
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private static Expr CreateWraps(ExprBoundLambda anonmeth)
{
Expr sequence = null;
for (Symbol sym = anonmeth.ArgumentScope.firstChild; sym != null; sym = sym.nextChild)
{
if (!(sym is LocalVariableSymbol local))
{
continue;
}
Debug.Assert(anonmeth.Expression != null);
Expr create = GenerateParameter(local.name.Text, local.GetType());
local.wrap = ExprFactory.CreateWrap(create);
Expr save = ExprFactory.CreateSave(local.wrap);
if (sequence == null)
{
sequence = save;
}
else
{
sequence = ExprFactory.CreateSequence(sequence, save);
}
}
return sequence;
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private Expr GenerateConstructor(ExprCall expr)
{
Debug.Assert(expr != null);
Debug.Assert(expr.MethWithInst.Meth().IsConstructor());
Expr constructorInfo = ExprFactory.CreateMethodInfo(expr.MethWithInst);
Expr args = GenerateArgsList(expr.OptionalArguments);
Expr Params = GenerateParamsArray(args, PredefinedType.PT_EXPRESSION);
return GenerateCall(PREDEFMETH.PM_EXPRESSION_NEW, constructorInfo, Params);
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private Expr GenerateArgsList(Expr oldArgs)
{
Expr newArgs = null;
Expr newArgsTail = newArgs;
for (ExpressionIterator it = new ExpressionIterator(oldArgs); !it.AtEnd(); it.MoveNext())
{
Expr oldArg = it.Current();
ExprFactory.AppendItemToList(Visit(oldArg), ref newArgs, ref newArgsTail);
}
return newArgs;
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private Expr GenerateIndexList(Expr oldIndices)
{
CType intType = SymbolLoader.GetPredefindType(PredefinedType.PT_INT);
Expr newIndices = null;
Expr newIndicesTail = newIndices;
for (ExpressionIterator it = new ExpressionIterator(oldIndices); !it.AtEnd(); it.MoveNext())
{
Expr newIndex = it.Current();
if (newIndex.Type != intType)
{
newIndex = ExprFactory.CreateCast(EXPRFLAG.EXF_INDEXEXPR, intType, newIndex);
newIndex.Flags |= EXPRFLAG.EXF_CHECKOVERFLOW;
}
Expr rewrittenIndex = Visit(newIndex);
ExprFactory.AppendItemToList(rewrittenIndex, ref newIndices, ref newIndicesTail);
}
return newIndices;
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private static Expr GenerateConstant(Expr expr)
{
EXPRFLAG flags = 0;
AggregateType pObject = SymbolLoader.GetPredefindType(PredefinedType.PT_OBJECT);
if (expr.Type is NullType)
{
ExprTypeOf pTypeOf = CreateTypeOf(pObject);
return GenerateCall(PREDEFMETH.PM_EXPRESSION_CONSTANT_OBJECT_TYPE, expr, pTypeOf);
}
AggregateType stringType = SymbolLoader.GetPredefindType(PredefinedType.PT_STRING);
if (expr.Type != stringType)
{
flags = EXPRFLAG.EXF_BOX;
}
ExprCast cast = ExprFactory.CreateCast(flags, pObject, expr);
ExprTypeOf pTypeOf2 = CreateTypeOf(expr.Type);
return GenerateCall(PREDEFMETH.PM_EXPRESSION_CONSTANT_OBJECT_TYPE, cast, pTypeOf2);
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private static ExprCall GenerateCall(PREDEFMETH pdm, Expr arg1)
{
MethodSymbol method = GetPreDefMethod(pdm);
// this should be enforced in an earlier pass and the transform pass should not
// be handling this error
if (method == null)
return null;
AggregateType expressionType = SymbolLoader.GetPredefindType(PredefinedType.PT_EXPRESSION);
MethWithInst mwi = new MethWithInst(method, expressionType);
ExprMemberGroup pMemGroup = ExprFactory.CreateMemGroup(null, mwi);
ExprCall call = ExprFactory.CreateCall(0, mwi.Meth().RetType, arg1, pMemGroup, mwi);
call.PredefinedMethod = pdm;
return call;
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private static ExprCall GenerateCall(PREDEFMETH pdm, Expr arg1, Expr arg2)
{
MethodSymbol method = GetPreDefMethod(pdm);
if (method == null)
return null;
AggregateType expressionType = SymbolLoader.GetPredefindType(PredefinedType.PT_EXPRESSION);
Expr args = ExprFactory.CreateList(arg1, arg2);
MethWithInst mwi = new MethWithInst(method, expressionType);
ExprMemberGroup pMemGroup = ExprFactory.CreateMemGroup(null, mwi);
ExprCall call = ExprFactory.CreateCall(0, mwi.Meth().RetType, args, pMemGroup, mwi);
call.PredefinedMethod = pdm;
return call;
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private static ExprCall GenerateCall(PREDEFMETH pdm, Expr arg1, Expr arg2, Expr arg3)
{
MethodSymbol method = GetPreDefMethod(pdm);
if (method == null)
return null;
AggregateType expressionType = SymbolLoader.GetPredefindType(PredefinedType.PT_EXPRESSION);
Expr args = ExprFactory.CreateList(arg1, arg2, arg3);
MethWithInst mwi = new MethWithInst(method, expressionType);
ExprMemberGroup pMemGroup = ExprFactory.CreateMemGroup(null, mwi);
ExprCall call = ExprFactory.CreateCall(0, mwi.Meth().RetType, args, pMemGroup, mwi);
call.PredefinedMethod = pdm;
return call;
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private static ExprCall GenerateCall(PREDEFMETH pdm, Expr arg1, Expr arg2, Expr arg3, Expr arg4)
{
MethodSymbol method = GetPreDefMethod(pdm);
if (method == null)
return null;
AggregateType expressionType = SymbolLoader.GetPredefindType(PredefinedType.PT_EXPRESSION);
Expr args = ExprFactory.CreateList(arg1, arg2, arg3, arg4);
MethWithInst mwi = new MethWithInst(method, expressionType);
ExprMemberGroup pMemGroup = ExprFactory.CreateMemGroup(null, mwi);
ExprCall call = ExprFactory.CreateCall(0, mwi.Meth().RetType, args, pMemGroup, mwi);
call.PredefinedMethod = pdm;
return call;
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private static ExprArrayInit GenerateParamsArray(Expr args, PredefinedType pt)
{
int parameterCount = ExpressionIterator.Count(args);
AggregateType paramsArrayElementType = SymbolLoader.GetPredefindType(pt);
ArrayType paramsArrayType = TypeManager.GetArray(paramsArrayElementType, 1, true);
ExprConstant paramsArrayArg = ExprFactory.CreateIntegerConstant(parameterCount);
return ExprFactory.CreateArrayInit(paramsArrayType, args, paramsArrayArg, new int[] { parameterCount });
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
private static void FixLiftedUserDefinedBinaryOperators(ExprBinOp expr, ref Expr pp1, ref Expr pp2)
{
// If we have lifted T1 op T2 to T1? op T2?, and we have an expression T1 op T2? or T1? op T2 then
// we need to ensure that the unlifted actual arguments are promoted to their nullable CType.
Debug.Assert(expr != null);
Debug.Assert(pp1 != null);
Debug.Assert(pp1 != null);
Debug.Assert(pp2 != null);
Debug.Assert(pp2 != null);
MethodSymbol method = expr.UserDefinedCallMethod.Meth();
Expr orig1 = expr.OptionalLeftChild;
Expr orig2 = expr.OptionalRightChild;
Debug.Assert(orig1 != null && orig2 != null);
Expr new1 = pp1;
Expr new2 = pp2;
CType fptype1 = method.Params[0];
CType fptype2 = method.Params[1];
CType aatype1 = orig1.Type;
CType aatype2 = orig2.Type;
// Is the operator even a candidate for lifting?
if (!(fptype1 is AggregateType fat1)
|| !fat1.OwningAggregate.IsValueType()
|| !(fptype2 is AggregateType fat2)
|| !fat2.OwningAggregate.IsValueType())
{
return;
}
CType nubfptype1 = TypeManager.GetNullable(fptype1);
CType nubfptype2 = TypeManager.GetNullable(fptype2);
// If we have null op X, or T1 op T2?, or T1 op null, lift first arg to T1?
if (aatype1 is NullType || aatype1 == fptype1 && (aatype2 == nubfptype2 || aatype2 is NullType))
{
new1 = GenerateCall(PREDEFMETH.PM_EXPRESSION_CONVERT, new1, CreateTypeOf(nubfptype1));
}
// If we have X op null, or T1? op T2, or null op T2, lift second arg to T2?
if (aatype2 is NullType || aatype2 == fptype2 && (aatype1 == nubfptype1 || aatype1 is NullType))
{
new2 = GenerateCall(PREDEFMETH.PM_EXPRESSION_CONVERT, new2, CreateTypeOf(nubfptype2));
}
pp1 = new1;
pp2 = new2;
}
private static bool IsNullableValueType(CType pType) =>
pType is NullableType && pType.StripNubs() is AggregateType agg && agg.OwningAggregate.IsValueType();
private static bool IsNullableValueAccess(Expr pExpr, Expr pObject)
{
Debug.Assert(pExpr != null);
return pExpr is ExprProperty prop && prop.MemberGroup.OptionalObject == pObject && pObject.Type is NullableType;
}
private static bool isEnumToDecimalConversion(CType argtype, CType desttype) =>
argtype.StripNubs().IsEnumType && desttype.StripNubs().IsPredefType(PredefinedType.PT_DECIMAL);
}
}
|