Esempio n. 1
0
            // To convert a null-check to pattern-matching, we should make sure of a few things:
            //
            //      (1) The pattern variable may not be used before the point of declaration.
            //
            //          {
            //              var use = t;
            //              if (x is T t) {}
            //          }
            //
            //      (2) The pattern variable may not be used outside of the new scope which
            //          is determined by the parent statement.
            //
            //          {
            //              if (x is T t) {}
            //          }
            //
            //          var use = t;
            //
            //      (3) The pattern variable may not be used before assignment in opposite
            //          branches, if any.
            //
            //          {
            //              if (x is T t) {}
            //              var use = t;
            //          }
            //
            // We walk up the tree from the point of null-check and see if any of the above is violated.
            private bool CanSafelyConvertToPatternMatching()
            {
                // Keep track of whether the pattern variable is definitely assigned when false/true.
                // We start by the null-check itself, if it's compared with '==', the pattern variable
                // will be definitely assigned when false, because we wrap the is-operator in a !-operator.
                var defAssignedWhenTrue = _comparison.IsKind(SyntaxKind.NotEqualsExpression, SyntaxKind.IsExpression);

                foreach (var current in _comparison.Ancestors())
                {
                    // Checking for any conditional statement or expression that could possibly
                    // affect or determine the state of definite-assignment of the pattern variable.
                    switch (current.Kind())
                    {
                    case SyntaxKind.LogicalAndExpression when !defAssignedWhenTrue:
                    case SyntaxKind.LogicalOrExpression when defAssignedWhenTrue:
                        // Since the pattern variable is only definitely assigned if the pattern
                        // succeeded, in the following cases it would not be safe to use pattern-matching.
                        // For example:
                        //
                        //      if ((x = o as string) == null && SomeExpression)
                        //      if ((x = o as string) != null || SomeExpression)
                        //
                        // Here, x would never be definitely assigned if pattern-matching were used.
                        return(false);

                    case SyntaxKind.LogicalAndExpression:
                    case SyntaxKind.LogicalOrExpression:

                    // Parentheses and cast expressions do not contribute to the flow analysis.
                    case SyntaxKind.ParenthesizedExpression:
                    case SyntaxKind.CastExpression:

                    // Skip over declaration parts to get to the parenting statement
                    // which might be a for-statement or a local declaration statement.
                    case SyntaxKind.EqualsValueClause:
                    case SyntaxKind.VariableDeclarator:
                    case SyntaxKind.VariableDeclaration:
                        continue;

                    case SyntaxKind.LogicalNotExpression:
                        // The !-operator negates the definitive assignment state.
                        defAssignedWhenTrue = !defAssignedWhenTrue;
                        continue;

                    case SyntaxKind.ConditionalExpression:
                        var conditionalExpression = (ConditionalExpressionSyntax)current;
                        if (LocalFlowsIn(defAssignedWhenTrue
                                    ? conditionalExpression.WhenFalse
                                    : conditionalExpression.WhenTrue))
                        {
                            // In a conditional expression, the pattern variable
                            // would not be definitely assigned in the opposite branch.
                            return(false);
                        }

                        return(CheckExpression(conditionalExpression));

                    case SyntaxKind.ForStatement:
                        var forStatement = (ForStatementSyntax)current;
                        if (forStatement.Condition is null || !forStatement.Condition.Span.Contains(_comparison.Span))
                        {
                            // In a for-statement, only the condition expression
                            // can make this definitely assigned in the loop body.
                            return(false);
                        }

                        return(CheckLoop(forStatement, forStatement.Statement, defAssignedWhenTrue));

                    case SyntaxKind.WhileStatement:
                        var whileStatement = (WhileStatementSyntax)current;
                        return(CheckLoop(whileStatement, whileStatement.Statement, defAssignedWhenTrue));

                    case SyntaxKind.IfStatement:
                        var ifStatement       = (IfStatementSyntax)current;
                        var oppositeStatement = defAssignedWhenTrue
                                ? ifStatement.Else?.Statement
                                : ifStatement.Statement;

                        if (oppositeStatement != null)
                        {
                            var dataFlow = _semanticModel.AnalyzeRequiredDataFlow(oppositeStatement);
                            if (dataFlow.DataFlowsIn.Contains(_localSymbol))
                            {
                                // Access before assignment is not safe in the opposite branch
                                // as the variable is not definitely assigned at this point.
                                // For example:
                                //
                                //    if (o is string x) { }
                                //    else { Use(x); }
                                //
                                return(false);
                            }

                            if (dataFlow.AlwaysAssigned.Contains(_localSymbol))
                            {
                                // If the variable is always assigned here, we don't need to check
                                // subsequent statements as it's definitely assigned afterwards.
                                // For example:
                                //
                                //     if (o is string x) { }
                                //     else { x = null; }
                                //
                                return(true);
                            }
                        }

                        if (!defAssignedWhenTrue &&
                            !_semanticModel.AnalyzeRequiredControlFlow(ifStatement.Statement).EndPointIsReachable)
                        {
                            // Access before assignment here is only valid if we have a negative
                            // pattern-matching in an if-statement with an unreachable endpoint.
                            // For example:
                            //
                            //      if (!(o is string x)) {
                            //        return;
                            //      }
                            //
                            //      // The 'return' statement above ensures x is definitely assigned here
                            //      Console.WriteLine(x);
                            //
                            return(true);
                        }

                        return(CheckStatement(ifStatement));
                    }

                    switch (current)
                    {
                    case ExpressionSyntax expression:
                        // If we reached here, it means we have a sub-expression that
                        // does not guarantee definite assignment. We should make sure that
                        // the pattern variable is not used outside of the expression boundaries.
                        return(CheckExpression(expression));

                    case StatementSyntax statement:
                        // If we reached here, it means that the null-check is appeared in
                        // a statement. In that case, the variable would be actually in the
                        // scope in subsequent statements, but not definitely assigned.
                        // Therefore, we should ensure that there is no use before assignment.
                        return(CheckStatement(statement));
                    }

                    // Bail out for error cases and unhandled cases.
                    break;
                }

                return(false);
            }