File: src\roslyn\src\Analyzers\CSharp\Analyzers\UseImplicitObjectCreation\CSharpUseImplicitObjectCreationDiagnosticAnalyzer.cs
Web Access
Project: src\roslyn\src\CodeStyle\CSharp\Analyzers\Microsoft.CodeAnalysis.CSharp.CodeStyle.csproj (Microsoft.CodeAnalysis.CSharp.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.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Simplification;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp.Utilities;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Utilities;

namespace Microsoft.CodeAnalysis.CSharp.UseImplicitObjectCreation;

[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal sealed class CSharpUseImplicitObjectCreationDiagnosticAnalyzer()
    : AbstractBuiltInCodeStyleDiagnosticAnalyzer(
        IDEDiagnosticIds.UseImplicitObjectCreationDiagnosticId,
        EnforceOnBuildValues.UseImplicitObjectCreation,
        CSharpCodeStyleOptions.ImplicitObjectCreationWhenTypeIsApparent,
        new LocalizableResourceString(nameof(CSharpAnalyzersResources.Use_new), CSharpAnalyzersResources.ResourceManager, typeof(CSharpAnalyzersResources)),
        new LocalizableResourceString(nameof(CSharpAnalyzersResources.new_expression_can_be_simplified), CSharpAnalyzersResources.ResourceManager, typeof(CSharpAnalyzersResources)))
{
    public override DiagnosticAnalyzerCategory GetAnalyzerCategory()
        => DiagnosticAnalyzerCategory.SemanticSpanAnalysis;

    protected override void InitializeWorker(AnalysisContext context)
        => context.RegisterSyntaxNodeAction(AnalyzeSyntax, SyntaxKind.ObjectCreationExpression);

    private void AnalyzeSyntax(SyntaxNodeAnalysisContext context)
    {
        var cancellationToken = context.CancellationToken;
        var semanticModel = context.SemanticModel;
        var syntaxTree = context.Node.SyntaxTree;

        // Not available prior to C# 9.
        if (syntaxTree.Options.LanguageVersion() < LanguageVersion.CSharp9)
            return;

        var styleOption = context.GetCSharpAnalyzerOptions().ImplicitObjectCreationWhenTypeIsApparent;
        if (!styleOption.Value || ShouldSkipAnalysis(context, styleOption.Notification))
        {
            // Bail immediately if the user has disabled this feature.
            return;
        }

        var objectCreation = (ObjectCreationExpressionSyntax)context.Node;
        if (!Analyze(semanticModel, context.GetCSharpAnalyzerOptions().GetSimplifierOptions(), objectCreation, cancellationToken))
            return;

        context.ReportDiagnostic(DiagnosticHelper.Create(
            Descriptor,
            // Place the suggestion on the 'new' keyword.  This is both the earliest part of the object creation
            // expression, and it also matches the location we place the 'use collection expression' analyzer,
            // ensuring consistency between the two analyzers.
            objectCreation.NewKeyword.GetLocation(),
            styleOption.Notification,
            context.Options,
            [objectCreation.GetLocation()],
            properties: null));
    }

    public static bool Analyze(
        SemanticModel semanticModel,
        CSharpSimplifierOptions simplifierOptions,
        ObjectCreationExpressionSyntax objectCreation,
        CancellationToken cancellationToken)
    {
        // type is apparent if we the object creation location is closely tied (spatially) to the explicit type.  Specifically:
        //
        // 1. Variable declarations.    i.e. `List<int> list = new ...`.  Note: we will suppress ourselves if this
        //    is a field and the 'var' preferences would lead to preferring this as `var list = ...`
        // 2. Expression-bodied constructs with an explicit return type.  i.e. `List<int> Prop => new ...` or
        //    `List<int> GetValue(...) => ...` The latter doesn't necessarily have the object creation spatially next to
        //    the type.  However, the type is always in a very easy to ascertain location in C#, so it is treated as
        //    apparent. 
        // 3. Collection-like constructs where the type of the collection is itself explicit.  For example: `new
        //    List<C> { new() }` or `new C[] { new() }`.

        var isAsync = false;
        TypeSyntax? typeNode;

        if (objectCreation.Parent is EqualsValueClauseSyntax
            {
                Parent: VariableDeclaratorSyntax { Parent: VariableDeclarationSyntax { Type.IsVar: false } variableDeclaration }
            })
        {
            typeNode = variableDeclaration.Type;

            var helper = CSharpUseImplicitTypeHelper.Instance;
            if (helper.ShouldAnalyzeVariableDeclaration(variableDeclaration, cancellationToken))
            {
                // this is a case where the user would prefer 'var'.  don't offer to use an implicit object here.
                if (helper.AnalyzeTypeName(typeNode, semanticModel, simplifierOptions, cancellationToken).IsStylePreferred)
                    return false;
            }
        }
        else if (objectCreation.Parent.IsKind(SyntaxKind.ArrowExpressionClause))
        {
            (typeNode, isAsync) = objectCreation.Parent.Parent switch
            {
                LocalFunctionStatementSyntax localFunction => (localFunction.ReturnType, localFunction.Modifiers.Any(SyntaxKind.AsyncKeyword)),
                MethodDeclarationSyntax method => (method.ReturnType, method.Modifiers.Any(SyntaxKind.AsyncKeyword)),
                ConversionOperatorDeclarationSyntax conversion => (conversion.Type, false),
                OperatorDeclarationSyntax op => (op.ReturnType, false),
                BasePropertyDeclarationSyntax property => (property.Type, false),
                AccessorDeclarationSyntax(SyntaxKind.GetAccessorDeclaration) { Parent: AccessorListSyntax { Parent: BasePropertyDeclarationSyntax baseProperty } } => (baseProperty.Type, false),
                _ => default,
            };
        }
        else if (objectCreation.Parent is InitializerExpressionSyntax { Parent: ObjectCreationExpressionSyntax { Type: var collectionType } })
        {
            typeNode = collectionType switch
            {
                GenericNameSyntax { TypeArgumentList.Arguments: [{ } typeArgument] } => typeArgument,
                QualifiedNameSyntax { Right: GenericNameSyntax { TypeArgumentList.Arguments: [{ } typeArgument] } } => typeArgument,
                _ => null,
            };
        }
        else if (objectCreation.Parent is InitializerExpressionSyntax(kind: SyntaxKind.ArrayInitializerExpression)
        {
            Parent: EqualsValueClauseSyntax { Parent: VariableDeclaratorSyntax { Parent: VariableDeclarationSyntax arrayVariableDeclaration } }
        })
        {
            typeNode = arrayVariableDeclaration.Type is ArrayTypeSyntax arrayType ? arrayType.ElementType : null;
        }
        else if (objectCreation.Parent is InitializerExpressionSyntax { Parent: ArrayCreationExpressionSyntax { Type: var arrayCreationType } })
        {
            typeNode = arrayCreationType is ArrayTypeSyntax arrayType ? arrayType.ElementType : null;
        }
        else
        {
            // more cases can be added here if we discover more cases we think the type is readily apparent from context.
            return false;
        }

        if (typeNode == null)
            return false;

        // Only offer if the type being constructed is the exact same as the type being assigned into.  We don't
        // want to change semantics by trying to instantiate something else.
        var leftType = semanticModel.GetTypeInfo(typeNode, cancellationToken).Type;
        var rightType = semanticModel.GetTypeInfo(objectCreation, cancellationToken).Type;

        if (leftType is null || rightType is null)
            return false;

        // In an async context, `Task<T>` and `ValueTask<T>` are considered the same as `T` for purposes of determining
        // if the type is apparent.  So `new()` is valid for `async Task<Goo> M() { return new Goo(); }`.
        var compilation = semanticModel.Compilation;
        if (isAsync)
        {
            if (leftType.OriginalDefinition.Equals(compilation.TaskOfTType()) ||
                leftType.OriginalDefinition.Equals(compilation.ValueTaskOfTType()))
            {
                leftType = leftType.GetTypeArguments().Single();
            }
        }

        if (leftType.IsErrorType() || rightType.IsErrorType())
            return false;

        // `new T?()` cannot be simplified to `new()`.  Even if the contextual type is `T?`, `new()` will be
        // interpetted as `new T()` which is a change in semantics.
        if (rightType.IsNullable())
            return false;

        // The default SymbolEquivalenceComparer will ignore tuple name differences, which is advantageous here
        if (!SymbolEquivalenceComparer.Instance.Equals(leftType, rightType))
            return false;

        return true;
    }
}