Beispiel #1
0
        private static bool OutArgumentCanBecomeOutVariableOrCanBeDiscarded(SemanticModel semanticModel, ArgumentSyntax outArgument, bool outArgumentCanBeDiscarded)
        {
            var enclosingStatement    = outArgument.LastAncestorOrSelf <StatementSyntax>(); // E.g. method body block.
            var outArgumentIdentifier = (IdentifierNameSyntax)outArgument.Expression;
            var outVariableName       = outArgumentIdentifier.Identifier.ValueText;

            // 1. The enclosing expression (method/constructor call) must correspond to the TEnclosingExpressionSyntax type.
            var enclosingExpression = GetEnclosingExpression();

            if (enclosingExpression == null)
            {
                return(false);
            }

            // 2. The out argument must be a local variable.
            var variableDeclarator = GetLocalVariableDeclaratorForOutArgument();

            if (variableDeclarator == null)
            {
                return(false);
            }

            // 3. If the local variable is initialized within the declaration, it means that it is used.
            if (variableDeclarator.Initializer != null)
            {
                return(false);
            }


            // 4. The local variable must not be used before it is passed to the method/constructor as an out argument.
            //    Also, if it is a requirement that the out argument can be discarded, it must not be used
            //    anywhere in code after the out argument.

            var localVariableSymbol = semanticModel.GetSymbolInfo(outArgumentIdentifier).Symbol;

            if (localVariableSymbol == null)
            {
                return(false);
            }
            var localVariableTextSpan = variableDeclarator.Identifier.Span;
            var outArgumentTextSpan   = outArgumentIdentifier.Span;

            // Find all the usages of the local variable within the enclosing e.g. method body block
            // that are different then its declaration and the usage in the out variable itself.
            // Those usage then appear either between the variable declaration and the out argument
            // or after the out argument.
            var usagesOfTheLocalVariable = enclosingStatement
                                           .DescendantNodes()
                                           .OfType <IdentifierNameSyntax>()
                                           .Where(identifier =>
                                                  identifier.Identifier.ValueText == outVariableName &&
                                                  identifier.Span != localVariableTextSpan && // It is not the declaration of the local variable itself.
                                                  identifier != outArgumentIdentifier &&      // It is not the out argument itself.
                                                  semanticModel.GetSymbolInfo(identifier).Symbol?.Equals(localVariableSymbol) == true
                                                  )
                                           .ToList();

            var(numberOfUsagesBeforeOutArgument, numberOfUsagesAfterOutArgument) = usagesOfTheLocalVariable
                                                                                   .CountMany
                                                                                   (
                identifier => identifier.Identifier.Span.IsBetween(localVariableTextSpan, outArgumentTextSpan),
                identifier => identifier.Identifier.Span.IsAfter(outArgumentTextSpan)
                                                                                   );

            var localVariableCouldBecomeOutVariableOrDiscarded =
                numberOfUsagesBeforeOutArgument == 0 &&
                (
                    outArgumentCanBeDiscarded && numberOfUsagesAfterOutArgument == 0
                    ||
                    !outArgumentCanBeDiscarded && numberOfUsagesAfterOutArgument > 0
                );

            if (!localVariableCouldBecomeOutVariableOrDiscarded)
            {
                return(false);
            }

            // 5. If turned into out variable, the local variable identifier still has the
            //    scope that contains all of the usages of that identifier that originally
            //    appear in the code after the out variable declaration.

            // If it can be discarded there is no usages after the out argument.
            // Thus, there cannot be any issues with the scope.
            if (outArgumentCanBeDiscarded && numberOfUsagesAfterOutArgument == 0)
            {
                return(true);
            }

            var outVariableScope = GetOutVariableScope();

            // If we have reached here, all the usages of the local variable are actually
            // after the out argument. We have to check that they are all in the scope
            // of the out variable.
            return(usagesOfTheLocalVariable
                   .All(identifier => outVariableScope.IsAnyAncestorOfOrSelf(identifier, enclosingStatement.Parent)));

            // The enclosing expression can be only a method or constructor call.
            // We have to additionally check for the case where we have them nested ;-)
            ExpressionSyntax GetEnclosingExpression()
            {
                var result = outArgument.FirstAncestorOrSelf <TEnclosingExpressionSyntax>();

                if (result == null)
                {
                    return(null);
                }

                // Check the possibly nested calls. E.g. Method(out j, new Constructor(out i));
                var potentialOtherEnclosingExpression = typeof(TEnclosingExpressionSyntax) == typeof(InvocationExpressionSyntax)
                                                        ? (ExpressionSyntax)outArgument.FirstAncestorOrSelf <ObjectCreationExpressionSyntax>()
                                                        : outArgument.FirstAncestorOrSelf <InvocationExpressionSyntax>();

                if (potentialOtherEnclosingExpression == null)
                {
                    // No nesting, just return the result.
                    return(result);
                }

                // Nesting. Check that the invocation of the type TEnclosingExpressionSyntax comes first.
                return(potentialOtherEnclosingExpression.IsAncestorOfOrSelf(result, enclosingStatement)
                    ? result
                    : null);
            }

            // Why an array here and not a single StatementSyntax node?
            // We have rare cases like e.g. for loop incrementors
            // where we do not have a single syntax node that would enclose
            // the scope but rather a list of mutually "disconnected" nodes.
            SyntaxNode[] GetOutVariableScope()
            {
                var potentialIfStatement = GetPotentialEnclosingIfStatement();

                if (potentialIfStatement != null)
                {
                    return new SyntaxNode[] { potentialIfStatement.FirstAncestorOrSelf <BlockSyntax>() }
                }
                ;

                var potentialSwitchStatement = GetPotentialEnclosingSwitchStatement();

                if (potentialSwitchStatement != null)
                {
                    return new SyntaxNode[] { potentialSwitchStatement.FirstAncestorOrSelf <BlockSyntax>() }
                }
                ;

                var potentialSwitchSection = GetPotentialEnclosingSwitchSection();

                if (potentialSwitchSection != null)
                {
                    return new SyntaxNode[] { (SwitchStatementSyntax)potentialSwitchSection.Parent }
                }
                ;

                var potentialWhileStatement = GetPotentialEnclosingWhileStatement();

                if (potentialWhileStatement != null)
                {
                    return new SyntaxNode[] { potentialWhileStatement }
                }
                ;

                // If the method/constructor invocation is within the while part of
                // a do-while statement the declared variable will not be visible anywhere
                // else but within that while part. Also, if we are checking that that method/constructor
                // invocation as a candidate to have an out variable, we know already that the
                // out variable is not used anywhere else in the do-while body.
                // In other words, the returned do-while statement will not contain any access
                // to the local variable that we want to turn into out variable.
                // Also, if the out variable is declared in the while part of the do-while loop,
                // it will not be visible after the while loop.
                // In other words, its new scope will be just the while part which is effectively
                // a kind of a "empty" scope.
                // Also, we ignore the usages like Method(out x, out x);
                var potentialDoStatement = GetPotentialEnclosingDoStatement();

                if (potentialDoStatement != null)
                {
                    return(new SyntaxNode[0]);
                }

                var potentialForStatement = GetPotentialEnclosingForStatement();

                if (potentialForStatement != null)
                {
                    return new SyntaxNode[] { potentialForStatement }
                }
                ;

                var potentialForIncrementors = GetPotentialEnclosingForIncrementors();

                if (potentialForIncrementors != default)
                {
                    return(potentialForIncrementors.Cast <SyntaxNode>().ToArray());
                }

                var potentialForEachStatement = GetPotentialEnclosingForEachStatement();

                if (potentialForEachStatement != null)
                {
                    return new SyntaxNode[] { potentialForEachStatement }
                }
                ;

                var potentialAnonymousFunction = GetPotentialEnclosingAnonymousFunction();

                if (potentialAnonymousFunction != null)
                {
                    return new SyntaxNode[] { potentialAnonymousFunction.Body }
                }
                ;

                // If it is nothing of the above, the method/constructor is called within some
                // "regular" block.
                return(new SyntaxNode[] { enclosingExpression.FirstAncestorOrSelf <BlockSyntax>() });

                AnonymousFunctionExpressionSyntax GetPotentialEnclosingAnonymousFunction()
                {
                    var anonymousFunction = enclosingExpression.FirstAncestorOrSelf <AnonymousFunctionExpressionSyntax>();

                    if (anonymousFunction == null)
                    {
                        return(null);
                    }

                    // Method/constructor invocation is within an anonymous delegate or lambda expression.
                    // But we have to make sure that it is not within a separate block within that
                    // anonymous delegate or lambda expression.
                    var block = enclosingExpression.FirstAncestorOrSelf <BlockSyntax>();

                    // Why enclosingStatement.Parent?
                    // Anonymous functions in general do not have to have a block.
                    // So the above search for the first enclosing block could return
                    // the enclosingStatement itself.
                    // So in general case we have to step one step out of it.
                    return(anonymousFunction.IsAncestorOfOrSelf(block, enclosingStatement.Parent)
                        ? null
                        : anonymousFunction);
                }

                ForEachStatementSyntax GetPotentialEnclosingForEachStatement()
                {
                    var forEachStatement = enclosingExpression.FirstAncestorOrSelf <ForEachStatementSyntax>();

                    if (forEachStatement == null)
                    {
                        return(null);
                    }

                    return(forEachStatement.Expression.IsAncestorOfOrSelf(enclosingExpression, forEachStatement)
                        ? forEachStatement
                        : null);
                }

                SeparatedSyntaxList <ExpressionSyntax> GetPotentialEnclosingForIncrementors()
                {
                    // Is the method/constructor invocation a part of incrementors of a for loop?
                    var forStatement = enclosingExpression.FirstAncestorOrSelf <ForStatementSyntax>();

                    if (forStatement == null)
                    {
                        return(default);