/// <summary> /// Reduces the expression node to a simpler expression. /// </summary> /// <returns>The reduced expression.</returns> public override Expression Reduce() { if (IsLifted) { if (Helpers.IsAlwaysNull(Operand)) { return(Expression.Default(Type)); } if (Helpers.IsNeverNull(Operand)) { // CONSIDER: Peek into Operand and try to extract non-null value. return (Expression.Convert( MakeFromEnd(Helpers.MakeNullableGetValueOrDefault(Operand)), Type )); } if (Operand is ParameterExpression i) { return(MakeLiftedFromEnd(i)); } else { var temp = Expression.Parameter(Operand.Type, "__t"); return (Expression.Block( new[] { temp }, Expression.Assign(temp, Operand), MakeLiftedFromEnd(temp) )); } } else { return(MakeFromEnd(Operand)); } Expression MakeLiftedFromEnd(ParameterExpression operand) => Expression.Condition( Helpers.MakeNullableHasValue(operand), Expression.Convert( MakeFromEnd(Helpers.MakeNullableGetValueOrDefault(operand)), Type ), Expression.Default(Type) ); Expression MakeFromEnd(Expression operand) { var method = Method ?? FromEnd; switch (method) { case MethodInfo m: switch (m.GetParametersCached().Length) { case 1: return(Expression.Call(m, operand)); case 2: return(Expression.Call(m, operand, Expression.Constant(true))); } break; case ConstructorInfo c: switch (c.GetParametersCached().Length) { case 1: return(Expression.New(c, operand)); case 2: return(Expression.New(c, operand, Expression.Constant(true))); } break; } throw ContractUtils.Unreachable; } }
private Expression ReduceNullable(SwitchAnalysis analysis) { var governingType = SwitchValue.Type; var governingTypeNonNull = governingType.GetNonNullableType(); var valueLocal = Expression.Parameter(governingType, "__value"); var vars = new[] { valueLocal }; var assignSwitchValue = Expression.Assign(valueLocal, SwitchValue); Expression body; // NB: The DLR switch expression does not optimize the nullable case into a switch table after a null check. // We can do this here instead, but we could consider moving this logic to the DLR too. if (analysis.NullCase != null) { if (analysis.IsNullLonely) { // We found a case with only a 'null' test value; we can lower to a null-check followed by a non-null switch, // and move the 'null' case to an else branch. var hasValue = Helpers.MakeNullableHasValue(valueLocal); var value = Helpers.MakeNullableGetValueOrDefault(valueLocal); var lowered = LowerSwitchStatement(analysis, governingTypeNonNull, hoistNull: true); var @switch = lowered.Make(value, lowered.DefaultCase); body = Expression.IfThenElse(hasValue, @switch, lowered.NullCase); } else { // We have a case with a 'null' test value but it has other non-null test values too; we can't lower to non- // null types. This should be the rare case. The DLR will further lower to if-then-else branches. var lowered = LowerSwitchStatement(analysis, governingType, hoistNull: false); body = lowered.Make(valueLocal, lowered.DefaultCase); } } else { // We have no 'null' test value whatsoever; we can lower to a null-check followed by a non-null switch. var hasValue = Helpers.MakeNullableHasValue(valueLocal); var value = Helpers.MakeNullableGetValueOrDefault(valueLocal); var lowered = LowerSwitchStatement(analysis, governingTypeNonNull, hoistNull: false); var defaultBody = lowered.DefaultCase; if (defaultBody != null) { var defaultLabel = Expression.Label("__default"); var @switch = lowered.Make(value, Expression.Goto(defaultLabel)); var defaultCase = Expression.Block(Expression.Label(defaultLabel), defaultBody); body = Expression.IfThenElse(hasValue, @switch, defaultCase); } else { var @switch = lowered.Make(value, null); body = Expression.IfThen(hasValue, @switch); } } // NB: Variable scope should not include the switch value. var exprs = new Expression[] { assignSwitchValue, WithVariableScope(body), Expression.Label(BreakLabel), }; return(Expression.Block(new TrueReadOnlyCollection <ParameterExpression>(vars), new TrueReadOnlyCollection <Expression>(exprs))); }
private Expression ReduceLifted() { // TODO: More optimizations based on IsAlwaysNull and IsNeverNull are possible. var temps = default(List <ParameterExpression>); var stmts = default(List <Expression>); var notNullCheck = default(Expression); var left = PrepareOperand(Left); var right = PrepareOperand(Right); var make = MakeRange(left, right); make = Expression.Convert(make, Type); if (notNullCheck != null) { make = Expression.Condition(notNullCheck, make, Expression.Default(Type)); } if (temps == null) { return(make); } else { stmts.Add(make); return(Expression.Block(temps, stmts)); } Expression PrepareOperand(Expression operand) { if (operand == null) { return(null); } if (!operand.Type.IsNullableType()) { return(operand); } temps ??= new List <ParameterExpression>(2); stmts ??= new List <Expression>(2); var temp = Expression.Parameter(operand.Type); temps.Add(temp); stmts.Add(Expression.Assign(temp, operand)); var hasValue = Helpers.MakeNullableHasValue(temp); if (notNullCheck == null) { notNullCheck = hasValue; } else { notNullCheck = Expression.AndAlso(notNullCheck, hasValue); } return(Helpers.MakeNullableGetValueOrDefault(temp)); } }
/// <summary> /// Reduces the expression node to a simpler expression. /// </summary> /// <returns>The reduced expression.</returns> public override Expression Reduce() { var operand = Operand; var temps = new List<ParameterExpression>(); var stmts = new List<Expression>(); // // NB: We can't check IsPure with the readOnly parameter set to true, because the user conversions may assign to a variable. // To mitigate this, we'd have to check all element conversions to make sure they don't assign to the variable, which is // very unlikely, though a hand-written conversion lambda could have such a side-effect. One could argue that a side- // effecting conversion that mutates the parent tuple yields undefined behavior. // if (!Helpers.IsPure(Operand)) { var operandVariable = Expression.Parameter(Operand.Type, "__t"); temps.Add(operandVariable); stmts.Add(Expression.Assign(operandVariable, Operand)); operand = operandVariable; } Expression res; if (operand.Type.IsNullableType()) { var nonNullOperand = Helpers.MakeNullableGetValue(operand); // NB: Use of Nullable<T>.Value to ensure proper exception is thrown if null. var nonNullOperandVariable = Expression.Parameter(nonNullOperand.Type, "__nonNull"); var args = GetConversions(nonNullOperandVariable); if (Type.IsNullableType()) { // // T? -> U? // // var t = operand; // // if (t.HasValue) // { // var v = t.Value; // return (U?)new U(...); // } // else // { // return default(U?); // } // var hasValueTest = Helpers.MakeNullableHasValue(operand); var nullValue = Expression.Default(Type); var convertedTuple = Expression.Convert(CSharpExpression.TupleLiteral(Type.GetNonNullableType(), args, argumentNames: null), Type); var nonNullValue = Expression.Block(new[] { nonNullOperandVariable }, Expression.Assign(nonNullOperandVariable, nonNullOperand), convertedTuple); res = Expression.Condition(hasValueTest, nonNullValue, nullValue); } else { // // T? -> U // // var t = operand; // var v = operand.Value; // NB: May throw // return new U(...); temps.Add(nonNullOperandVariable); stmts.Add(Expression.Assign(nonNullOperandVariable, nonNullOperand)); res = CSharpExpression.TupleLiteral(Type, args, argumentNames: null); } } else { var args = GetConversions(operand); var targetType = Type.GetNonNullableType(); var convertedTuple = CSharpExpression.TupleLiteral(targetType, args, argumentNames: null); if (Type.IsNullableType()) { // // T -> U? // // var t = operand; // return (U?)new U(...); res = Expression.Convert(convertedTuple, Type); } else { // // T -> U // // var t = operand; // return new U(...); // res = convertedTuple; } } stmts.Add(res); return Helpers.Comma(temps, stmts); List<Expression> GetConversions(Expression operand) { var n = ElementConversions.Count; var args = new List<Expression>(n); for (int i = 0; i < n; i++) { var conversion = ElementConversions[i]; var item = Helpers.GetTupleItemAccess(operand, i); var conversionParameter = conversion.Parameters[0]; if (conversion.Body is UnaryExpression { Operand: var unaryOperand } unary && unaryOperand == conversionParameter && IsConvert(unary.NodeType)) { args.Add(unary.Update(item)); }