Implementation of conditional access operators - ?. and ?[]
Change Description: Implementation of conditional access operators ( ?. and ?[] ) The grammar roughly looks like: expr ::= | ... | expr ? tail tail ::= | tailopt . id tail ::= | tailopt .$ id <--- this is NYI in this change | tailopt [ expr-args ] | tail ( expr-args ) Note that the tail isn’t optional for invocation: there is no ?(…) operator. Interesting implementation points: == Parsing/syntax Syntax tree is right associative – it has the same topology as in the grammar. However I did not create special variants for all the kinds of syntax that could be produced by the parsing of the tail of accessors. I just use regular syntax nodes with exception of the topmost node that is the conditional access operator itself and the lower-leftmost node that textually starts the accessor tail. The root node looks like – ConditionalAccessExpression { ExpressionSyntax Expression; SyntaxToken OperatorToken; // always “?” ExpressionSyntax WhenNotNull; // by analogy with conditional expression which uses WhenTrue } WhenNotNull is an ExpressionSyntax that represent the sequence of postfix operations conditionally applied to the Expression. These are regular InvocationExpressions, MemberAccesses, ElementAcesses – whatever postfix operators we have. The only special part about WhenNotNull is that the low-leftmost expression child is one of 2 special “binding” nodes – MemberBindingExpression, ElementBindingExpression. Binding expressions are entities which can start the chain of WhenNotNull . Textually they look like - “ .SimpleName“ , “[argList]” or “$Name” (when $ accessor is supported). Note that there is no InvocationBinding, since we do not allow () to start the chain of applications – you cannot do “a?(arg)”. The binding expression syntaxes are basically versions of corresponding member/element/dollar access expressions without receivers. R?.a.b(x).c Would parse as ConditionalAccess / | \ R ? MemberAccess / | \ Invocation . c / \ MemberAccess ArgList{( x )} / | \ MemberBinding . b | \ . a The advantage of using regular syntax nodes on the right side is that semantical analysis “just works”. Naturally, because of the topology, the API cannot ask nonsensical questions like - what is the type of subexpression “R?.a.b” ? Since “R?.a.b” is not a subexpression, there is no way to ask such question. It is however possible to interrogate member accesses – they are just regular member accesses (in both syntax and bound trees) so they naturally know their types etc… == Binding Binding of the tree above is fairly simple – I only had to add binding support for the left-lowermost binding nodes. And what comes out is really just regular access expressions with placeholder receiver. I cannot refer to the actual receiver since we are not evaluating it again. In terms of schema - Bound tree actually needed even fewer changes than syntax tree – the bound tree for the above expression is just a regular bound tree. It is isomorphic to the syntax. The only new bound nodes are the root BoundConditionalAccess and the left-lowermost placeholder receiver – BoundConditionalReceiver. BoundConditionalAccess / \ <BoundExpr> BoundMemberAccess {c} / BoundCall {b} / \ BoundMemberAccess {a} List<Args>{ x } / BoundConditionalReceiver == Dataflow Dataflow considers BoundConditionalReceiver always assigned. However the whole WhenNotNull is reachable conditionally - i.e. assignedVariables state is forked when going into WhenNotNull. “receiver is a const” situation is handled trivially. That is similar to the ?? operator but somewhat inverted. == Emit There are two ways to emit this : - In complex cases (nullables, async, expr trees) we lower the conditional access into a ternary with consequence being rewritten as0is with just BoundConditionalReceiver replaced by a temp for the receiver value. - In simple case (all reference types), we do not lower the node, emit of BoundConditionalAccess dups the receiver (thus avoiding an IL temp) , checks it for null and conditionally executes the the access expression. BoundConditionalReceiver is handled by emit by not emitting anything since it represents receiver value and that is on the stack already. (changeset 1249636)
Showing
想要评论请 注册 或 登录