// 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 disable using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeGeneration; using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.GenerateMember.GenerateParameterizedMember; internal abstract partial class AbstractGenerateParameterizedMemberService<TService, TSimpleNameSyntax, TExpressionSyntax, TInvocationExpressionSyntax> { internal abstract class AbstractInvocationInfo : SignatureInfo { protected abstract bool IsIdentifierName(); protected abstract ImmutableArray<ITypeParameterSymbol> GetCapturedTypeParameters(CancellationToken cancellationToken); protected abstract ImmutableArray<ITypeParameterSymbol> GenerateTypeParameters(CancellationToken cancellationToken); protected AbstractInvocationInfo(SemanticDocument document, State state) : base(document, state) { } protected override ImmutableArray<ITypeParameterSymbol> DetermineTypeParametersWorker( CancellationToken cancellationToken) { var typeParameters = ComputeTypeParameters(cancellationToken); return typeParameters.SelectAsArray(MassageTypeParameter); } private ImmutableArray<ITypeParameterSymbol> ComputeTypeParameters( CancellationToken cancellationToken) { if (IsIdentifierName()) { // If the user wrote something like Goo(x) then we still might want to generate // a generic method if the expression 'x' captured any method type variables. var capturedTypeParameters = GetCapturedTypeParameters(cancellationToken); var availableTypeParameters = State.TypeToGenerateIn.GetAllTypeParameters(); var result = capturedTypeParameters.Except<ITypeParameterSymbol>(availableTypeParameters, SymbolEqualityComparer.Default).ToImmutableArray(); return result; } else { return GenerateTypeParameters(cancellationToken); } } private ITypeParameterSymbol MassageTypeParameter( ITypeParameterSymbol typeParameter) { var constraints = typeParameter.ConstraintTypes.Where(ts => !ts.IsUnexpressibleTypeParameterConstraint()).ToList(); var classTypes = constraints.Where(ts => ts.TypeKind == TypeKind.Class).ToList(); var nonClassTypes = constraints.Where(ts => ts.TypeKind != TypeKind.Class).ToList(); classTypes = MergeClassTypes(classTypes); constraints = [.. classTypes, .. nonClassTypes]; if (constraints.SequenceEqual(typeParameter.ConstraintTypes)) { return typeParameter; } return CodeGenerationSymbolFactory.CreateTypeParameter( attributes: default, varianceKind: typeParameter.Variance, name: typeParameter.Name, constraintTypes: constraints.AsImmutable(), hasConstructorConstraint: typeParameter.HasConstructorConstraint, hasReferenceConstraint: typeParameter.HasReferenceTypeConstraint, hasValueConstraint: typeParameter.HasValueTypeConstraint, hasUnmanagedConstraint: typeParameter.HasUnmanagedTypeConstraint, hasNotNullConstraint: typeParameter.HasNotNullConstraint); } private List<ITypeSymbol> MergeClassTypes(List<ITypeSymbol> classTypes) { var compilation = Document.SemanticModel.Compilation; for (var i = classTypes.Count - 1; i >= 0; i--) { // For example, 'Attribute'. var type1 = classTypes[i]; for (var j = 0; j < classTypes.Count; j++) { if (j != i) { // For example 'GooAttribute'. var type2 = classTypes[j]; if (IsImplicitReferenceConversion(compilation, type2, type1)) { // If there's an implicit reference conversion (i.e. from // GooAttribute to Attribute), then we don't need Attribute as it's // implied by the second attribute; classTypes.RemoveAt(i); break; } } } } return classTypes; } protected abstract bool IsImplicitReferenceConversion(Compilation compilation, ITypeSymbol sourceType, ITypeSymbol targetType); } } |