static private InvalidCompoundAssignmentWithOperands ( object p0, object p1, object p2 ) : |
||
p0 | object | |
p1 | object | |
p2 | object | |
return |
private static void ValidateCustomBinaryAssign(CSharpExpressionType binaryType, Expression left, Expression right, ref MethodInfo method, LambdaExpression leftConversion, LambdaExpression finalConversion) { var leftType = left.Type; var rightType = right.Type; if (leftConversion != null) { leftType = ValidateConversion(binaryType, leftType, leftConversion); } // NB: Just leverage LINQ to do the dirty work to check everything. Note that this assumes that the // left conversion performed widening if that's required for the underlying operation. However, // the use of FunctionalOp below will widen the right operand to match the left operand's type, // which is what we do during Reduce as well (see remarks in FunctionalOp). This could produce // mysterious error messages. (TODO: Review what's most appropriate here.) // // NB: We can't have it check the final conversion, because it doesn't allow these without the use of // a custom method, so we check that ourselves further down. var leftDummy = Expression.Parameter(leftType, "__left"); var rightDummy = Expression.Parameter(rightType, "__right"); var functionalOp = FunctionalOp(binaryType, leftDummy, rightDummy, method); if (method == null) { method = functionalOp.Method; } var resultType = functionalOp.Type; if (finalConversion != null) { resultType = ValidateConversion(binaryType, resultType, finalConversion); } if (!TypeUtils.AreEquivalent(resultType, left.Type)) { throw Error.InvalidCompoundAssignmentWithOperands(binaryType, left.Type, right.Type); } }
private static AssignBinaryCSharpExpression MakeBinaryAssignCore(CSharpExpressionType binaryType, Expression left, Expression right, MethodInfo method, LambdaExpression finalConversion, LambdaExpression leftConversion) { Helpers.RequiresCanWrite(left, nameof(left)); RequiresCanRead(right, nameof(right)); // NB: We could return a BinaryExpression in case the lhs is not one of our index nodes, but it'd change // the return type to Expression which isn't nice to consume. Also, the Update method would either // have to change to return Expression or we should have an AssignBinary node to hold a Binary node // underneath it. This said, a specialized layout for the case where the custom node trivially wraps // a LINQ node could be useful (just make Left virtual). if (binaryType != CSharpExpressionType.Assign) { var leftType = left.Type; var rightType = right.Type; if (leftType == typeof(string)) { if (method == null) { if (binaryType == CSharpExpressionType.AddAssign || binaryType == CSharpExpressionType.AddAssignChecked) { if (rightType == typeof(string)) { method = typeof(string).GetMethod(nameof(string.Concat), new[] { typeof(string), typeof(string) }); } else { method = typeof(string).GetMethod(nameof(string.Concat), new[] { typeof(string), typeof(object) }); if (!TypeUtils1.AreReferenceAssignable(typeof(object), rightType)) { // DESIGN: Should our factory do this our just reject the input? right = Expression.Convert(right, typeof(object)); } } } else { throw Error.InvalidCompoundAssignment(binaryType, typeof(string)); } } } else if (typeof(MulticastDelegate).IsAssignableFrom(leftType)) { if (leftType == typeof(MulticastDelegate)) { throw Error.InvalidCompoundAssignmentWithOperands(binaryType, leftType, rightType); } // NB: This checks for assignment with variance checks in mind, e.g. // // Action<string> s = ...; // Action<object> o = ...; // s += o; if (!TypeUtils1.AreReferenceAssignable(leftType, rightType)) { throw Error.InvalidCompoundAssignmentWithOperands(binaryType, leftType, rightType); } if (method == null) { if (binaryType == CSharpExpressionType.AddAssign || binaryType == CSharpExpressionType.AddAssignChecked) { method = typeof(Delegate).GetMethod(nameof(Delegate.Combine), new[] { typeof(Delegate), typeof(Delegate) }); } else if (binaryType == CSharpExpressionType.SubtractAssign || binaryType == CSharpExpressionType.SubtractAssignChecked) { method = typeof(Delegate).GetMethod(nameof(Delegate.Remove), new[] { typeof(Delegate), typeof(Delegate) }); } else { throw Error.InvalidCompoundAssignment(binaryType, leftType); } if (finalConversion == null) { var resultParameter = Expression.Parameter(typeof(Delegate), "__result"); var convertResult = Expression.Convert(resultParameter, leftType); finalConversion = Expression.Lambda(convertResult, resultParameter); } } } else if (IsCSharpSpecificCompoundNumeric(leftType)) { // NB: If any of these are passed, we'll assume the types all line up. The call to // the ValidateCustomBinaryAssign method below will check that's indeed the case. if (method == null && leftConversion == null && finalConversion == null) { var isChecked = IsCheckedBinary(binaryType); var isNullabeLeftType = leftType.IsNullableType(); var nonNullLeftType = leftType.GetNonNullableType(); var intermediateType = nonNullLeftType.IsEnum ? nonNullLeftType.GetEnumUnderlyingType() : typeof(int); var leftParameter = Expression.Parameter(leftType, "__left"); var convertType = isNullabeLeftType ? typeof(Nullable <>).MakeGenericType(intermediateType) : intermediateType; var convertLeft = isChecked ? Expression.ConvertChecked(leftParameter, convertType) : Expression.Convert(leftParameter, convertType); leftConversion = Expression.Lambda(convertLeft, leftParameter); var resultParameter = Expression.Parameter(convertType, "__result"); var convertResult = isChecked ? Expression.ConvertChecked(resultParameter, leftType) : Expression.Convert(resultParameter, leftType); finalConversion = Expression.Lambda(convertResult, resultParameter); if (rightType != convertType) { // DESIGN: Should our factory do this or just reject the input? On the one hand, // C# allows e.g. byte += byte, so if this is a C#-specific API it may be // reasonable for the user to expect such a tree can be built. On the // other hand, it's very unlike the expression tree API to insert nodes // on behalf of the user in the factories. Note that Roslyn often models // conversions as properties on a node using a `Conversion` objects // which would be handy to keep the shape from the tree isomorphic to the // bound nodes in the compiler. Note though that the RHS of a compound // assignment doesn't have such a conversion and the compiler will insert // a convert node in this case, so this is really just a convenience in // our factory method to mimic that behavior. right = Expression.Convert(right, convertType); } } } } return(AssignBinaryCSharpExpression.Make(binaryType, left, right, method, leftConversion, finalConversion)); }