File: src\Workspaces\SharedUtilitiesAndExtensions\Compiler\CSharp\Utilities\TypeStyle\CSharpUseExplicitTypeHelper.cs
Web Access
Project: src\src\Workspaces\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.Workspaces.csproj (Microsoft.CodeAnalysis.CSharp.Workspaces)
// 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.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.CodeStyle.TypeStyle;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Simplification;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp.Utilities;
 
internal sealed class CSharpUseExplicitTypeHelper : CSharpTypeStyleHelper
{
    public static CSharpUseExplicitTypeHelper Instance = new();
 
    private CSharpUseExplicitTypeHelper()
    {
    }
 
    protected override bool IsStylePreferred(in State state)
    {
        var stylePreferences = state.TypeStylePreference;
 
        if (state.IsInIntrinsicTypeContext)
        {
            return !stylePreferences.HasFlag(UseVarPreference.ForBuiltInTypes);
        }
        else if (state.IsTypeApparentInContext)
        {
            return !stylePreferences.HasFlag(UseVarPreference.WhenTypeIsApparent);
        }
        else
        {
            return !stylePreferences.HasFlag(UseVarPreference.Elsewhere);
        }
    }
 
    public override bool ShouldAnalyzeVariableDeclaration(VariableDeclarationSyntax variableDeclaration, CancellationToken cancellationToken)
    {
        if (!variableDeclaration.Type.StripRefIfNeeded().IsVar)
        {
            // If the type is not 'var', this analyze has no work to do
            return false;
        }
 
        // The base analyzer may impose further limitations
        return base.ShouldAnalyzeVariableDeclaration(variableDeclaration, cancellationToken);
    }
 
    protected override bool ShouldAnalyzeForEachStatement(ForEachStatementSyntax forEachStatement, SemanticModel semanticModel, CancellationToken cancellationToken)
    {
        if (!forEachStatement.Type.StripRefIfNeeded().IsVar)
        {
            // If the type is not 'var', this analyze has no work to do
            return false;
        }
 
        // The base analyzer may impose further limitations
        return base.ShouldAnalyzeForEachStatement(forEachStatement, semanticModel, cancellationToken);
    }
 
    internal override bool TryAnalyzeVariableDeclaration(
        TypeSyntax typeName, SemanticModel semanticModel,
        CSharpSimplifierOptions options, CancellationToken cancellationToken)
    {
        // var (x, y) = e;
        // foreach (var (x, y) in e) ...
        if (typeName.Parent is DeclarationExpressionSyntax declExpression &&
            declExpression.Designation.IsKind(SyntaxKind.ParenthesizedVariableDesignation))
        {
            return true;
        }
 
        // If it is currently not var, explicit typing exists, return. 
        // this also takes care of cases where var is mapped to a named type via an alias or a class declaration.
        if (!typeName.StripRefIfNeeded().IsTypeInferred(semanticModel))
        {
            return false;
        }
 
        if (typeName.Parent is VariableDeclarationSyntax variableDeclaration &&
            typeName.Parent.Parent is (kind: SyntaxKind.LocalDeclarationStatement or SyntaxKind.ForStatement or SyntaxKind.UsingStatement))
        {
            // check assignment for variable declarations.
            var variable = variableDeclaration.Variables.First();
            RoslynDebug.AssertNotNull(variable.Initializer);
            if (!AssignmentSupportsStylePreference(
                    variable.Identifier, typeName, variable.Initializer.Value,
                    semanticModel, options, cancellationToken))
            {
                return false;
            }
 
            // This error case is handled by a separate code fix (UseExplicitTypeForConst).
            if ((variableDeclaration.Parent as LocalDeclarationStatementSyntax)?.IsConst == true)
            {
                return false;
            }
        }
        else if (typeName.Parent is ForEachStatementSyntax foreachStatement &&
                 foreachStatement.Type == typeName)
        {
            if (!AssignmentSupportsStylePreference(
                    foreachStatement.Identifier, typeName, foreachStatement.Expression,
                    semanticModel, options, cancellationToken))
            {
                return false;
            }
        }
 
        return true;
    }
 
    protected override bool ShouldAnalyzeDeclarationExpression(DeclarationExpressionSyntax declaration, SemanticModel semanticModel, CancellationToken cancellationToken)
    {
        if (!declaration.Type.IsVar)
        {
            // If the type is not 'var', this analyze has no work to do
            return false;
        }
 
        // The base analyzer may impose further limitations
        return base.ShouldAnalyzeDeclarationExpression(declaration, semanticModel, cancellationToken);
    }
 
    /// <summary>
    /// Analyzes the assignment expression and rejects a given declaration if it is unsuitable for explicit typing.
    /// </summary>
    /// <returns>
    /// false, if explicit typing cannot be used.
    /// true, otherwise.
    /// </returns>
    protected override bool AssignmentSupportsStylePreference(
        SyntaxToken identifier,
        TypeSyntax typeName,
        ExpressionSyntax initializer,
        SemanticModel semanticModel,
        CSharpSimplifierOptions options,
        CancellationToken cancellationToken)
    {
        // is or contains an anonymous type
        // cases :
        //        var anon = new { Num = 1 };
        //        var enumerableOfAnons = from prod in products select new { prod.Color, prod.Price };
        var declaredType = semanticModel.GetTypeInfo(typeName.StripRefIfNeeded(), cancellationToken).Type;
        if (declaredType.ContainsAnonymousType())
        {
            return false;
        }
 
        // cannot find type if initializer resolves to an ErrorTypeSymbol
        var initializerTypeInfo = semanticModel.GetTypeInfo(initializer, cancellationToken);
        return !initializerTypeInfo.Type.IsErrorType();
    }
}