File: src\Analyzers\Core\Analyzers\UseCollectionInitializer\AbstractObjectCreationExpressionAnalyzer.cs
Web Access
Project: src\src\CodeStyle\Core\Analyzers\Microsoft.CodeAnalysis.CodeStyle.csproj (Microsoft.CodeAnalysis.CodeStyle)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
 
namespace Microsoft.CodeAnalysis.UseCollectionInitializer;
 
internal abstract class AbstractObjectCreationExpressionAnalyzer<
    TExpressionSyntax,
    TStatementSyntax,
    TObjectCreationExpressionSyntax,
    TLocalDeclarationStatementSyntax,
    TVariableDeclaratorSyntax,
    TMatch,
    TAnalyzer> : IDisposable
    where TExpressionSyntax : SyntaxNode
    where TStatementSyntax : SyntaxNode
    where TObjectCreationExpressionSyntax : TExpressionSyntax
    where TLocalDeclarationStatementSyntax : TStatementSyntax
    where TVariableDeclaratorSyntax : SyntaxNode
    where TAnalyzer : AbstractObjectCreationExpressionAnalyzer<
        TExpressionSyntax,
        TStatementSyntax,
        TObjectCreationExpressionSyntax,
        TLocalDeclarationStatementSyntax,
        TVariableDeclaratorSyntax,
        TMatch,
        TAnalyzer>, new()
{
    public readonly record struct AnalysisResult(
        ImmutableArray<TMatch> PreMatches,
        ImmutableArray<TMatch> PostMatches,
        bool ChangesSemantics);
 
    protected UpdateExpressionState<TExpressionSyntax, TStatementSyntax> State;
 
    protected TObjectCreationExpressionSyntax _objectCreationExpression = null!;
 
    protected ISyntaxFacts SyntaxFacts => this.State.SyntaxFacts;
    protected SemanticModel SemanticModel => this.State.SemanticModel;
 
    protected abstract bool ShouldAnalyze(CancellationToken cancellationToken);
    protected abstract bool TryAddMatches(ArrayBuilder<TMatch> preMatches, ArrayBuilder<TMatch> postMatches, out bool changesSemantics, CancellationToken cancellationToken);
    protected abstract bool IsInitializerOfLocalDeclarationStatement(
        TLocalDeclarationStatementSyntax localDeclarationStatement, TObjectCreationExpressionSyntax rootExpression, [NotNullWhen(true)] out TVariableDeclaratorSyntax? variableDeclarator);
 
    private static readonly ObjectPool<TAnalyzer> s_pool = SharedPools.Default<TAnalyzer>();
 
    public static TAnalyzer Allocate()
        => s_pool.Allocate();
 
    public void Dispose()
    {
        this.Clear();
        s_pool.Free((TAnalyzer)this);
    }
 
    public void Initialize(
        UpdateExpressionState<TExpressionSyntax, TStatementSyntax> state,
        TObjectCreationExpressionSyntax objectCreationExpression)
    {
        State = state;
        _objectCreationExpression = objectCreationExpression;
    }
 
    protected virtual void Clear()
    {
        State = default;
        _objectCreationExpression = null!;
    }
 
    protected AnalysisResult AnalyzeWorker(CancellationToken cancellationToken)
    {
        if (!ShouldAnalyze(cancellationToken))
            return default;
 
        using var _1 = ArrayBuilder<TMatch>.GetInstance(out var preMatches);
        using var _2 = ArrayBuilder<TMatch>.GetInstance(out var postMatches);
        if (!TryAddMatches(preMatches, postMatches, out var mayChangeSemantics, cancellationToken))
            return default;
 
        return new(preMatches.ToImmutableAndClear(), postMatches.ToImmutableAndClear(), mayChangeSemantics);
    }
 
    protected UpdateExpressionState<TExpressionSyntax, TStatementSyntax> TryInitializeState(
        SemanticModel semanticModel,
        ISyntaxFacts syntaxFacts,
        TObjectCreationExpressionSyntax rootExpression,
        CancellationToken cancellationToken)
    {
        var statement = rootExpression.FirstAncestorOrSelf<TStatementSyntax>();
        if (statement != null)
        {
            var result =
                TryInitializeVariableDeclarationCase(semanticModel, syntaxFacts, rootExpression, statement, cancellationToken) ??
                TryInitializeAssignmentCase(semanticModel, syntaxFacts, rootExpression, statement, cancellationToken);
            if (result != null)
                return result.Value;
        }
 
        return new UpdateExpressionState<TExpressionSyntax, TStatementSyntax>(
            semanticModel, syntaxFacts, rootExpression, valuePattern: default, initializedSymbol: null);
    }
 
    private UpdateExpressionState<TExpressionSyntax, TStatementSyntax>? TryInitializeVariableDeclarationCase(
        SemanticModel semanticModel,
        ISyntaxFacts syntaxFacts,
        TObjectCreationExpressionSyntax rootExpression,
        TStatementSyntax containingStatement,
        CancellationToken cancellationToken)
    {
        if (containingStatement is not TLocalDeclarationStatementSyntax localDeclarationStatement)
            return null;
 
        if (!this.IsInitializerOfLocalDeclarationStatement(localDeclarationStatement, rootExpression, out var variableDeclarator))
            return null;
 
        var valuePattern = syntaxFacts.GetIdentifierOfVariableDeclarator(variableDeclarator);
        if (valuePattern == default || valuePattern.IsMissing)
            return null;
 
        var initializedSymbol = semanticModel.GetDeclaredSymbol(valuePattern.GetRequiredParent(), cancellationToken);
        if (initializedSymbol is not ILocalSymbol local)
            return null;
 
        // Not supported if we're creating a dynamic local.  The object we're instantiating may not have the members
        // that we're trying to access on the dynamic object.
        if (local.Type is IDynamicTypeSymbol)
            return null;
 
        return new(semanticModel, syntaxFacts, rootExpression, valuePattern, initializedSymbol);
    }
 
    private static UpdateExpressionState<TExpressionSyntax, TStatementSyntax>? TryInitializeAssignmentCase(
        SemanticModel semanticModel,
        ISyntaxFacts syntaxFacts,
        TObjectCreationExpressionSyntax rootExpression,
        TStatementSyntax containingStatement,
        CancellationToken cancellationToken)
    {
        if (!syntaxFacts.IsSimpleAssignmentStatement(containingStatement))
            return null;
 
        syntaxFacts.GetPartsOfAssignmentStatement(containingStatement,
            out var left, out var right);
        if (right != rootExpression)
            return null;
 
        var typeInfo = semanticModel.GetTypeInfo(left, cancellationToken);
        if (typeInfo.Type is IDynamicTypeSymbol || typeInfo.ConvertedType is IDynamicTypeSymbol)
        {
            // Not supported if we're initializing something dynamic.  The object we're instantiating
            // may not have the members that we're trying to access on the dynamic object.
            return null;
        }
 
        var initializedSymbol = semanticModel.GetSymbolInfo(left, cancellationToken).GetAnySymbol();
        return new(semanticModel, syntaxFacts, rootExpression, left, initializedSymbol);
    }
}