<commit_before>﻿// 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.

#nullable enable

using System;
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeGeneration;
using Microsoft.CodeAnalysis.Completion;
using Microsoft.CodeAnalysis.Completion.Providers;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.CSharp.Completion.Providers
{
    [ExportCompletionProvider(nameof(ConversionCompletionProvider), LanguageNames.CSharp), Shared]
    [ExtensionOrder(After = nameof(SymbolCompletionProvider))]
    internal class ConversionCompletionProvider : OperatorIndexerCompletionProviderBase
    {
        private const string MinimalTypeNamePropertyName = "MinimalTypeName";
        private const string ContainerTypeNamePropertyName = "ContainerTypeName";

        [ImportingConstructor]
        [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
        public ConversionCompletionProvider()
        {
        }

        protected override int SortingGroupIndex => 2;

        protected override ImmutableArray<CompletionItem> GetCompletionItemsForTypeSymbol(ITypeSymbol container, bool isAccessedByConditionalAccess, ExpressionSyntax expression, SemanticModel semanticModel, int position, CancellationToken cancellationToken)
        {
            // Lifted nullable value types should be suggested if container is nullable or accessed via conditional access.
            // Container is nullable: int? id; id.$$ -> (byte?)
            var containerIsNullable = container.IsNullable();

            // Container is access via conditional access: System.Diagnostics.Process p; p?.Id.$$ -> (byte?)
            containerIsNullable = containerIsNullable || (isAccessedByConditionalAccess && container.IsValueType);

            container = container.RemoveNullableIfPresent();

            using var _ = ArrayBuilder<CompletionItem>.GetInstance(out var builder);

            AddUserDefinedConversionsOfType(builder, semanticModel, container, containerIsNullable, position);
            if (container is INamedTypeSymbol namedType)
            {
                AddBuiltInNumericConversions(builder, semanticModel, namedType, containerIsNullable, position);
                AddBuiltInEnumConversions(builder, semanticModel, namedType, expression, containerIsNullable, position, cancellationToken);
            }

            return builder.ToImmutable();
        }

        private void AddUserDefinedConversionsOfType(ArrayBuilder<CompletionItem> builder, SemanticModel semanticModel, ITypeSymbol container, bool containerIsNullable, int position)
        {
            var containerOrBaseType = (ITypeSymbol?)container;
            while (containerOrBaseType is not null)
            {
                var allMembers = containerOrBaseType.GetMembers();
                var allExplicitConversions = from m in allMembers.OfType<IMethodSymbol>()
                                             where
                                                 m.IsConversion() && // MethodKind.Conversion
                                                 m.Name == WellKnownMemberNames.ExplicitConversionName && // op_Explicit
                                                 m.Parameters.Length == 1 && // Malformed conversion operator may have more or less than one parameter
                                                 containerOrBaseType.Equals(m.Parameters[0].Type) // Convert from container type to other type
                                             let typeName = m.ReturnType.ToMinimalDisplayString(semanticModel, position)
                                             // Lifted conversion https://docs.microsoft.com/hu-hu/dotnet/csharp/language-reference/language-specification/conversions#lifted-conversion-operators
                                             select CreateSymbolCompletionItem(m, typeName, targetTypeIsNullable: containerIsNullable && m.ReturnType.IsStructType(), position);
                builder.AddRange(allExplicitConversions);

                // Base types are valid sources for user-defined conversions
                // https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/conversions#processing-of-user-defined-explicit-conversions
                // "Find the set of types, D, from which user-defined conversion operators will be considered. This set consists of S0 (if S0 is a class or struct), 
                // the base classes of S0 (if S0 is a class), T0 (if T0 is a class or struct), and the base classes of T0 (if T0 is a class)."
                // Note: We only look in the source (aka container), because target could be any type (in scope) of the compilation.
                containerOrBaseType = containerOrBaseType.BaseType;
            }
        }
<commit_msg>Re-factor AddUserDefinedConversionsOfType and use GetBaseTypesAndThis() instead of loop.<commit_after>﻿// 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.

#nullable enable

using System;
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeGeneration;
using Microsoft.CodeAnalysis.Completion;
using Microsoft.CodeAnalysis.Completion.Providers;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.CSharp.Completion.Providers
{
    [ExportCompletionProvider(nameof(ConversionCompletionProvider), LanguageNames.CSharp), Shared]
    [ExtensionOrder(After = nameof(SymbolCompletionProvider))]
    internal class ConversionCompletionProvider : OperatorIndexerCompletionProviderBase
    {
        private const string MinimalTypeNamePropertyName = "MinimalTypeName";
        private const string ContainerTypeNamePropertyName = "ContainerTypeName";

        [ImportingConstructor]
        [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
        public ConversionCompletionProvider()
        {
        }

        protected override int SortingGroupIndex => 2;

        protected override ImmutableArray<CompletionItem> GetCompletionItemsForTypeSymbol(ITypeSymbol container, bool isAccessedByConditionalAccess, ExpressionSyntax expression, SemanticModel semanticModel, int position, CancellationToken cancellationToken)
        {
            // Lifted nullable value types should be suggested if container is nullable or accessed via conditional access.
            // Container is nullable: int? id; id.$$ -> (byte?)
            var containerIsNullable = container.IsNullable();

            // Container is access via conditional access: System.Diagnostics.Process p; p?.Id.$$ -> (byte?)
            containerIsNullable = containerIsNullable || (isAccessedByConditionalAccess && container.IsValueType);

            container = container.RemoveNullableIfPresent();

            using var _ = ArrayBuilder<CompletionItem>.GetInstance(out var builder);

            AddUserDefinedConversionsOfType(builder, semanticModel, container, containerIsNullable, position);
            if (container is INamedTypeSymbol namedType)
            {
                AddBuiltInNumericConversions(builder, semanticModel, namedType, containerIsNullable, position);
                AddBuiltInEnumConversions(builder, semanticModel, namedType, expression, containerIsNullable, position, cancellationToken);
            }

            return builder.ToImmutable();
        }

        private void AddUserDefinedConversionsOfType(ArrayBuilder<CompletionItem> builder, SemanticModel semanticModel, ITypeSymbol container, bool containerIsNullable, int position)
        {
            // Base types are valid sources for user-defined conversions
            // https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/conversions#processing-of-user-defined-explicit-conversions
            // "Find the set of types, D, from which user-defined conversion operators will be considered. This set consists of S0 (if S0 is a class or struct), 
            // the base classes of S0 (if S0 is a class), T0 (if T0 is a class or struct), and the base classes of T0 (if T0 is a class)."
            // Note: We only look in the source (aka container), because target could be any type (in scope) of the compilation.
            // No need to check for accessibility: "An operator declaration must include both a public and a static modifier." https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/classes#operators
            var allExplicitConversions = from t in container.GetBaseTypesAndThis()
                                         from m in t.GetMembers(WellKnownMemberNames.ExplicitConversionName).OfType<IMethodSymbol>()
                                         where
                                             m.IsConversion() && // MethodKind.Conversion
                                             m.Parameters.Length == 1 && // Malformed conversion operator may have more or less than one parameter
                                             t.Equals(m.Parameters[0].Type) // Convert from container type to other type
                                         let typeName = m.ReturnType.ToMinimalDisplayString(semanticModel, position)
                                         // Lifted conversion https://docs.microsoft.com/hu-hu/dotnet/csharp/language-reference/language-specification/conversions#lifted-conversion-operators
                                         select CreateSymbolCompletionItem(m, typeName, targetTypeIsNullable: containerIsNullable && m.ReturnType.IsStructType(), position);
            builder.AddRange(allExplicitConversions);

        }

{'fraction_non_alphanumeric': 0.07148281754319347, 'fraction_numerical': 0.0020884754129485475, 'mean_word_length': 2.8519195612431445, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 17, 'https://': 5, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 32, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2567975', 'n_tokens_mistral': 2407, 'n_tokens_neox': 2331, 'n_words': 700}