private static MethodCallCSharpExpression MakeAccess(ConditionalReceiver receiver, MethodInfo method, ReadOnlyCollection <ParameterAssignment> arguments) { if (method.IsStatic && method.IsDefined(typeof(ExtensionAttribute))) { var thisPar = method.GetParametersCached()[0]; var thisArg = CSharpExpression.Bind(thisPar, receiver); var newArgs = new ParameterAssignment[arguments.Count + 1]; newArgs[0] = thisArg; var i = 1; foreach (var arg in arguments) { newArgs[i++] = arg; } var newArguments = new TrueReadOnlyCollection <ParameterAssignment>(newArgs); return(CSharpExpression.Call(null, method, newArguments)); // TODO: call ctor directly } else { return(CSharpExpression.Call(receiver, method, arguments)); // TODO: call ctor directly } }
protected internal override Expression VisitConditionalReceiver(ConditionalReceiver node) { var id = _parent.MakeInstanceId(node); var res = new XElement(nameof(ConditionalReceiver), new XAttribute("Id", id), new XAttribute(nameof(node.Type), node.Type)); _nodes.Push(res); return(node); }
protected internal override Expression VisitConditionalReceiver(ConditionalReceiver node) { // TODO: We could check that we find the receiver exactly once. if (node == _receiver) { return(_nonNull); } return(base.VisitConditionalReceiver(node)); }
internal static void CheckConditionalAccess(Expression receiver, ConditionalReceiver nonNullReceiver, Expression whenNotNull) { RequiresCanRead(receiver, nameof(receiver)); RequiresNotNull(nonNullReceiver, nameof(nonNullReceiver)); RequiresCanRead(whenNotNull, nameof(whenNotNull)); var receiverType = receiver.Type; if (receiverType == typeof(void) || receiverType.IsByRef || (receiverType.IsValueType && !receiverType.IsNullableType())) { throw Error.InvalidConditionalReceiverExpressionType(receiverType); } var nonNullReceiverType = receiverType.GetNonNullReceiverType(); if (nonNullReceiverType != nonNullReceiver.Type) { throw Error.ConditionalReceiverTypeMismatch(receiverType, nonNullReceiverType); } }
internal override ConditionalAccessCSharpExpression <Expression> Rewrite(Expression receiver, ConditionalReceiver nonNullReceiver, Expression whenNotNull) => new ConditionalAccessCSharpExpression(receiver, nonNullReceiver, whenNotNull);
public SubstituteConditionalReceiver(ConditionalReceiver receiver, Expression nonNull) { _receiver = receiver; _nonNull = nonNull; }
internal override ConditionalAccessCSharpExpression <MemberExpression> Rewrite(Expression receiver, ConditionalReceiver nonNullReceiver, MemberExpression whenNotNull) { return(new ConditionalMemberCSharpExpression(receiver, nonNullReceiver, whenNotNull)); }
private ConditionalMemberCSharpExpression(Expression expression, ConditionalReceiver receiver, MemberExpression access) : base(expression, receiver, access) { }
internal override ConditionalAccessCSharpExpression <IndexCSharpExpression> Rewrite(Expression receiver, ConditionalReceiver nonNullReceiver, IndexCSharpExpression whenNotNull) { return(new MethodBased(receiver, nonNullReceiver, whenNotNull)); }
private MethodBased(Expression @object, ConditionalReceiver receiver, IndexCSharpExpression access) : base(@object, receiver, access) { }
private ConditionalArrayIndexCSharpExpression(Expression array, ConditionalReceiver receiver, IndexExpression access) : base(array, receiver, access) { }
private ConditionalArrayIndexCSharpExpression(Expression array, ConditionalReceiver receiver, ReadOnlyCollection <Expression> indexes) : this(array, receiver, MakeAccess(receiver, indexes)) { }
private XNode Visit(ConditionalReceiver node) { VisitConditionalReceiver(node); return(_nodes.Pop()); }
public ConditionalReceiverProxy(ConditionalReceiver node) { _node = node; }
protected internal virtual Expression VisitConditionalReceiver(ConditionalReceiver node) { return(node); }
private ConditionalMethodCallCSharpExpression(Expression expression, ConditionalReceiver receiver, MethodInfo method, ReadOnlyCollection <ParameterAssignment> arguments) : this(expression, receiver, MakeAccess(receiver, method, arguments)) { }
private MethodBased(Expression @object, ConditionalReceiver receiver, MethodInfo method, ReadOnlyCollection <ParameterAssignment> arguments) : this(@object, receiver, MakeAccess(receiver, method, arguments)) { _method = method; }
private static IndexExpression MakeAccess(ConditionalReceiver receiver, ReadOnlyCollection <Expression> indexes) { return(Expression.ArrayAccess(receiver, indexes)); // TODO: call ctor directly }
private static IndexCSharpExpression MakeAccess(ConditionalReceiver receiver, MethodInfo method, ReadOnlyCollection <ParameterAssignment> arguments) { return(CSharpExpression.Index(receiver, method, arguments)); // TODO: call ctor directly }
private ConditionalInvocationCSharpExpression(Expression expression, ConditionalReceiver receiver, ReadOnlyCollection <ParameterAssignment> arguments) : this(expression, receiver, MakeAccess(receiver, arguments)) { }
private ConditionalMemberCSharpExpression(Expression expression, ConditionalReceiver receiver, MemberInfo member) : this(expression, receiver, MakeAccess(receiver, member)) { }
private ConditionalInvocationCSharpExpression(Expression expression, ConditionalReceiver receiver, InvocationCSharpExpression access) : base(expression, receiver, access) { }
private static MemberExpression MakeAccess(ConditionalReceiver receiver, MemberInfo member) { return(Expression.MakeMemberAccess(receiver, member)); // TODO: call ctor directly }
private static InvocationCSharpExpression MakeAccess(ConditionalReceiver receiver, ReadOnlyCollection <ParameterAssignment> arguments) { return(CSharpExpression.Invoke(receiver, arguments)); // TODO: call ctor directly }
internal ConditionalAccessCSharpExpression(Expression receiver, ConditionalReceiver nonNullReceiver, Expression whenNotNull) : base(receiver, nonNullReceiver, whenNotNull) { }
internal override ConditionalAccessCSharpExpression <InvocationCSharpExpression> Rewrite(Expression receiver, ConditionalReceiver nonNullReceiver, InvocationCSharpExpression whenNotNull) { return(new ConditionalInvocationCSharpExpression(receiver, nonNullReceiver, whenNotNull)); }
private PropertyBased(Expression @object, ConditionalReceiver receiver, PropertyInfo indexer, ReadOnlyCollection <ParameterAssignment> arguments) : this(@object, receiver, MakeAccess(receiver, indexer, arguments)) { }
/// <summary> /// Creates a <see cref="ConditionalAccessCSharpExpression"/> representing a null-conditional access operation. /// </summary> /// <param name="receiver">The receiver to access conditionally.</param> /// <param name="nonNullReceiver">The non-null receiver used in the <paramref name="whenNotNull"/> expression.</param> /// <param name="whenNotNull">The operation to apply to the receiver when it's non-null.</param> /// <returns>A <see cref="Microsoft.CSharp.Expressions.ConditionalReceiver"/> that has the <see cref="CSharpNodeType" /> property equal to <see cref="CSharpExpressionType.ConditionalReceiver" /> and the <see cref="Expression.Type" /> property equal to the specified type.</returns> public static ConditionalAccessCSharpExpression ConditionalAccess(Expression receiver, ConditionalReceiver nonNullReceiver, Expression whenNotNull) { CheckConditionalAccess(receiver, nonNullReceiver, whenNotNull); // TODO: More elaborate checking of `whenNotNull` would be possible, in particular to check the following: // // - only supported operations can occur in the access expression // - the conditional receiver is used in an input position // - the conditional receiver is used exactly once // // The only drawback is that those checks will involve a visit to the access expression, which doesn't have a // constant cost. When those nodes are deeply nested, we'd be going over the same tree many times, unless we // relax the check for the conditional receiver usage count to be at least once. This would also happen during // a rewrite of the tree by means of a visitor, which causes factory invocations. // // Note that the expression compiler can still catch such violations during reduction by only reducing one // occurrence of each conditional receiver, therefore leaving any other occurrences unbound, which on its turn // causes a reduction error for the unbound conditional receiver (which is not a reducible node). // // What are the implications of not performing those checks? Nothing unsafe will happen but the node type will // effectively capture a superset of the possibilities provided by the C# language for the construct. E.g. // you could construct a null-conditional call where the conditional receiver is used for a parameter that's // different from the receiving instance (or the `this` parameter for an extension method): // // ConditionalAccess(bar, receiver, Call(foo, qux, { receiver })) == foo.qux(bar) iff bar != null // ^^^ ~~~~~~~~ // instance argument // // We could decide the cost of checking doesn't yield enough benefits given that it only applies to hand- // crafted expressions (i.e. the compiler will never emit a non-standard form). We'd still have to support // this behavior in future versions and consider it to have well-defined behavior, or document it as having // undefined behavior (with little predecents) from the get-go. // // Finally, note that we could do away with this problem by modeling the nodes differently without relying on // a node for the conditional receiver. To do so, we'd have to introduce expressions for argument lists that // don't include the receiver, which duplicates a lot of existing LINQ nodes. In such a design, the receiver // would be implicit and always correspond to the "left-most source" in the access expression. It comes at // the cost of not being able to reuse existing LINQ nodes which always have a bound receiver, but we could // piggyback on the work to introduce C#-specific nodes for e.g. ParameterAssignment to create nodes for e.g. // argument lists. Old LINQ nodes could be considered applications of more primitive partial nodes: // // Call(o, m, args) == Access(o, Call(m, args)) == o.m(args) // Member(o, p) == Access(o, Member(p)) == o.p // Index(o, i, args) == Access(o, Index(i, args)) == o.i[args] // Invoke(f, args) == Access(f, Invoke(args)) == o(args) // // In this setting, we could still allow further composition of the access expressions by passing a list of // access operations in lieu of the second operand to `Access`, e.g. // // Access(o, { Member(p), Call(m, args2) }) == o.p.m(args2) // // Substituting `Access` for `ConditionalAccess` would mean the following: // // ConditionalAccess(o, { Member(p), Call(m, args2) }) == o?.p.m(args2) // // Note that it makes for a much stranger tree representation (though arguably the current form is weird too) // with new partially applied `Member`, `Call`, `Index`, and `Invoke` constructs. Instead, we could nest them // as well, by only having one (inner-most, left-most) access-unbound node in the access expression: // // ConditionalAccess(o, Call(Member( p), m, args2)) // ^^ // hole // // With this form, the check is still expensive, because we need to check for exactly one unbound form in the // source chain of the access expression. // DESIGN: We could make instances of type ConditionalAccessCSharpExpression<TExpression> and/or specialized subtypes // if we decide to keep those. However, it may look strange if `whenNotNull` is not just a single access but // consists of a 'chain' of operations. Right now, the idea is to have the specialized subtypes merely as a // convenience when using factories by hand, but we could scrap it all and just stick with the primitive node. return(new ConditionalAccessCSharpExpression(receiver, nonNullReceiver, whenNotNull)); }
internal ConditionalIndexCSharpExpression(Expression expression, ConditionalReceiver receiver, IndexCSharpExpression access) : base(expression, receiver, access) { }
protected internal virtual Expression VisitConditionalReceiver(ConditionalReceiver node) { return node; }
internal override ConditionalAccessCSharpExpression <IndexCSharpExpression> Rewrite(Expression receiver, ConditionalReceiver nonNullReceiver, IndexCSharpExpression whenNotNull) => new PropertyBased(receiver, nonNullReceiver, whenNotNull);