File: ITypeSymbolExtensions.cs
Web Access
Project: ..\..\..\src\CodeStyle\CSharp\CodeFixes\Microsoft.CodeAnalysis.CSharp.CodeStyle.Fixes.csproj (Microsoft.CodeAnalysis.CSharp.CodeStyle.Fixes)
// 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.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Shared.Lightup;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Simplification;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp.Extensions
{
    internal static partial class ITypeSymbolExtensions
    {
        /// <paramref name="nameSyntax"><see langword="true"/> if only normal name-syntax nodes should be returned.
        /// <see langword="false"/> if special nodes (like predefined types) can be used.</paramref>
        public static ExpressionSyntax GenerateExpressionSyntax(this ITypeSymbol typeSymbol, bool nameSyntax = false)
            => typeSymbol.Accept(ExpressionSyntaxGeneratorVisitor.Create(nameSyntax))!.WithAdditionalAnnotations(Simplifier.Annotation);
 
        public static NameSyntax GenerateNameSyntax(this INamespaceOrTypeSymbol symbol, bool allowVar = true)
            => (NameSyntax)GenerateTypeSyntax(symbol, nameSyntax: true, allowVar: allowVar);
 
        public static TypeSyntax GenerateTypeSyntax(this INamespaceOrTypeSymbol symbol, bool allowVar = true)
            => GenerateTypeSyntax(symbol, nameSyntax: false, allowVar: allowVar);
 
        private static TypeSyntax GenerateTypeSyntax(
            INamespaceOrTypeSymbol symbol, bool nameSyntax, bool allowVar = true)
        {
            var type = symbol as ITypeSymbol;
            var containsAnonymousType = type != null && type.ContainsAnonymousType();
 
            if (containsAnonymousType && allowVar)
            {
                // something with an anonymous type can only be represented with 'var', regardless
                // of what the user's preferences might be.
                return SyntaxFactory.IdentifierName("var");
            }
 
            var syntax = containsAnonymousType
                ? TypeSyntaxGeneratorVisitor.CreateSystemObject()
                : symbol.Accept(TypeSyntaxGeneratorVisitor.Create(nameSyntax))!
                        .WithAdditionalAnnotations(Simplifier.Annotation);
 
            if (!allowVar)
                syntax = syntax.WithAdditionalAnnotations(DoNotAllowVarAnnotation.Annotation);
 
            if (type != null && type.IsReferenceType)
            {
                var additionalAnnotation = type.NullableAnnotation switch
                {
                    NullableAnnotation.None => NullableSyntaxAnnotationEx.Oblivious,
                    NullableAnnotation.Annotated => NullableSyntaxAnnotationEx.AnnotatedOrNotAnnotated,
                    NullableAnnotation.NotAnnotated => NullableSyntaxAnnotationEx.AnnotatedOrNotAnnotated,
                    _ => throw ExceptionUtilities.UnexpectedValue(type.NullableAnnotation),
                };
 
                if (additionalAnnotation is not null)
                    syntax = syntax.WithAdditionalAnnotations(additionalAnnotation);
            }
 
            return syntax;
        }
 
        public static TypeSyntax GenerateRefTypeSyntax(
            this INamespaceOrTypeSymbol symbol)
        {
            var underlyingType = GenerateTypeSyntax(symbol)
                .WithPrependedLeadingTrivia(SyntaxFactory.ElasticMarker)
                .WithAdditionalAnnotations(Simplifier.Annotation);
            var refKeyword = SyntaxFactory.Token(SyntaxKind.RefKeyword);
            return SyntaxFactory.RefType(refKeyword, underlyingType);
        }
 
        public static TypeSyntax GenerateRefReadOnlyTypeSyntax(
            this INamespaceOrTypeSymbol symbol)
        {
            var underlyingType = GenerateTypeSyntax(symbol)
                .WithPrependedLeadingTrivia(SyntaxFactory.ElasticMarker)
                .WithAdditionalAnnotations(Simplifier.Annotation);
            var refKeyword = SyntaxFactory.Token(SyntaxKind.RefKeyword);
            var readOnlyKeyword = SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword);
            return SyntaxFactory.RefType(refKeyword, readOnlyKeyword, underlyingType);
        }
 
        public static bool ContainingTypesOrSelfHasUnsafeKeyword(this ITypeSymbol containingType)
        {
            do
            {
                foreach (var reference in containingType.DeclaringSyntaxReferences)
                {
                    if (reference.GetSyntax().ChildTokens().Any(t => t.IsKind(SyntaxKind.UnsafeKeyword)))
                    {
                        return true;
                    }
                }
 
                containingType = containingType.ContainingType;
            }
            while (containingType != null);
            return false;
        }
 
        public static async Task<ISymbol?> FindApplicableAliasAsync(this ITypeSymbol type, int position, SemanticModel semanticModel, CancellationToken cancellationToken)
        {
            try
            {
                if (semanticModel.IsSpeculativeSemanticModel)
                {
                    position = semanticModel.OriginalPositionForSpeculation;
                    semanticModel = semanticModel.ParentModel;
                }
 
                var root = await semanticModel.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false);
 
                var applicableUsings = GetApplicableUsings(position, (CompilationUnitSyntax)root);
                foreach (var applicableUsing in applicableUsings)
                {
                    var alias = semanticModel.GetOriginalSemanticModel().GetDeclaredSymbol(applicableUsing, cancellationToken);
                    if (alias != null && Equals(alias.Target, type))
                    {
                        return alias;
                    }
                }
 
                return null;
            }
            catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken, ErrorSeverity.General))
            {
                throw ExceptionUtilities.Unreachable();
            }
        }
 
        private static IEnumerable<UsingDirectiveSyntax> GetApplicableUsings(int position, SyntaxNode root)
        {
            var namespaceUsings = root.FindToken(position).Parent!.GetAncestors<BaseNamespaceDeclarationSyntax>().SelectMany(n => n.Usings);
            var allUsings = root is CompilationUnitSyntax compilationUnit
                ? compilationUnit.Usings.Concat(namespaceUsings)
                : namespaceUsings;
            return allUsings.Where(u => u.Alias != null);
        }
 
        public static bool TryGetRecordPrimaryConstructor(this INamedTypeSymbol typeSymbol, [NotNullWhen(true)] out IMethodSymbol? primaryConstructor)
        {
            if (typeSymbol.TypeKind is TypeKind.Class or TypeKind.Struct)
            {
                Debug.Assert(typeSymbol.GetParameters().IsDefaultOrEmpty, "If GetParameters extension handles record, we can remove the handling here.");
 
                // A bit hacky to determine the parameters of primary constructor associated with a given record.
                // Simplifying is tracked by: https://github.com/dotnet/roslyn/issues/53092.
                // Note: When the issue is handled, we can remove the logic here and handle things in GetParameters extension. BUT
                // if GetParameters extension method gets updated to handle records, we need to test EVERY usage
                // of the extension method and make sure the change is applicable to all these usages.
 
                primaryConstructor = typeSymbol.InstanceConstructors.FirstOrDefault(
                    c => c.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax() is RecordDeclarationSyntax or ClassDeclarationSyntax or StructDeclarationSyntax);
                return primaryConstructor is not null;
            }
 
            primaryConstructor = null;
            return false;
        }
    }
}