/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Analyzers/Core/CodeFixes/GenerateParameterizedMember/AbstractGenerateParameterizedMemberService.SignatureInfo.cs
276 строк
13 KB
copilot-swe-agent[bot]
Fix async modifier on abstract methods
10 окт 2025, 14:32
10 окт 2025, 14:32
dfe91cd
Код
Авторство
О чём код?
// 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; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeGeneration; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.GenerateMember.GenerateParameterizedMember; internal abstract partial class AbstractGenerateParameterizedMemberService<TService, TSimpleNameSyntax, TExpressionSyntax, TInvocationExpressionSyntax> { internal abstract class SignatureInfo( SemanticDocument document, State state) { protected readonly SemanticDocument Document = document; protected readonly State State = state; private ImmutableArray<ITypeParameterSymbol> _typeParameters; private IDictionary<ITypeSymbol, ITypeParameterSymbol> _typeArgumentToTypeParameterMap; public ImmutableArray<ITypeParameterSymbol> DetermineTypeParameters(CancellationToken cancellationToken) { return _typeParameters.IsDefault ? (_typeParameters = DetermineTypeParametersWorker(cancellationToken)) : _typeParameters; } protected abstract ImmutableArray<ITypeParameterSymbol> DetermineTypeParametersWorker(CancellationToken cancellationToken); protected abstract RefKind DetermineRefKind(CancellationToken cancellationToken); public ValueTask<ITypeSymbol> DetermineReturnTypeAsync(CancellationToken cancellationToken) { var type = DetermineReturnTypeWorker(cancellationToken); if (State.IsInConditionalAccessExpression) { type = type.RemoveNullableIfPresent(); } return FixTypeAsync(type, cancellationToken); } protected abstract ImmutableArray<ITypeSymbol> DetermineTypeArguments(CancellationToken cancellationToken); protected abstract ITypeSymbol DetermineReturnTypeWorker(CancellationToken cancellationToken); protected abstract ImmutableArray<RefKind> DetermineParameterModifiers(CancellationToken cancellationToken); protected abstract ImmutableArray<ITypeSymbol> DetermineParameterTypes(CancellationToken cancellationToken); protected abstract ImmutableArray<bool> DetermineParameterOptionality(CancellationToken cancellationToken); protected abstract ImmutableArray<ParameterName> DetermineParameterNames(CancellationToken cancellationToken); internal async ValueTask<IPropertySymbol> GeneratePropertyAsync( SyntaxGenerator factory, bool isAbstract, bool includeSetter, CancellationToken cancellationToken) { var accessibility = DetermineAccessibility(isAbstract); var getMethod = CodeGenerationSymbolFactory.CreateAccessorSymbol( attributes: default, accessibility: accessibility, statements: GenerateStatements(factory, isAbstract)); var setMethod = includeSetter ? getMethod : null; return CodeGenerationSymbolFactory.CreatePropertySymbol( attributes: default, accessibility: accessibility, modifiers: DeclarationModifiers.None.WithIsStatic(State.IsStatic).WithIsAbstract(isAbstract), type: await DetermineReturnTypeAsync(cancellationToken).ConfigureAwait(false), refKind: DetermineRefKind(cancellationToken), explicitInterfaceImplementations: default, name: State.IdentifierToken.ValueText, parameters: await DetermineParametersAsync(cancellationToken).ConfigureAwait(false), getMethod: getMethod, setMethod: setMethod); } public async ValueTask<IMethodSymbol> GenerateMethodAsync( SyntaxGenerator factory, bool isAbstract, CancellationToken cancellationToken) { var parameters = await DetermineParametersAsync(cancellationToken).ConfigureAwait(false); var returnType = await DetermineReturnTypeAsync(cancellationToken).ConfigureAwait(false); var isUnsafe = false; if (!State.IsContainedInUnsafeType) { isUnsafe = returnType.RequiresUnsafeModifier() || parameters.Any(static p => p.Type.RequiresUnsafeModifier()); } var knownTypes = new KnownTaskTypes(Document.SemanticModel.Compilation); var method = CodeGenerationSymbolFactory.CreateMethodSymbol( attributes: default, accessibility: DetermineAccessibility(isAbstract), modifiers: DeclarationModifiers.None .WithIsStatic(State.IsStatic).WithIsAbstract(isAbstract).WithIsUnsafe(isUnsafe).WithAsync(!isAbstract && knownTypes.IsTaskLike(returnType)), returnType: returnType, refKind: DetermineRefKind(cancellationToken), explicitInterfaceImplementations: default, name: State.IdentifierToken.ValueText, typeParameters: DetermineTypeParameters(cancellationToken), parameters: parameters, statements: GenerateStatements(factory, isAbstract), handlesExpressions: default, returnTypeAttributes: default, methodKind: State.MethodKind); // Ensure no conflicts between type parameter names and parameter names. var syntaxFacts = Document.Project.Solution.GetLanguageService<ISyntaxFactsService>(State.TypeToGenerateIn.Language); var equalityComparer = syntaxFacts.StringComparer; var reservedParameterNames = DetermineParameterNames(cancellationToken) .Select(p => p.BestNameForParameter) .ToSet(equalityComparer); var newTypeParameterNames = NameGenerator.EnsureUniqueness( method.TypeParameters.SelectAsArray(t => t.Name), n => !reservedParameterNames.Contains(n)); return method.RenameTypeParameters(newTypeParameterNames); } private async ValueTask<ITypeSymbol> FixTypeAsync( ITypeSymbol typeSymbol, CancellationToken cancellationToken) { // A type can't refer to a type parameter that isn't available in the type we're // eventually generating into. var availableMethodTypeParameters = DetermineTypeParameters(cancellationToken); var availableTypeParameters = State.TypeToGenerateIn.GetAllTypeParameters(); var compilation = Document.SemanticModel.Compilation; var allTypeParameters = availableMethodTypeParameters.Concat(availableTypeParameters); var availableTypeParameterNames = allTypeParameters.Select(t => t.Name).ToSet(); var typeArgumentToTypeParameterMap = GetTypeArgumentToTypeParameterMap(cancellationToken); typeSymbol = typeSymbol.RemoveAnonymousTypes(compilation); typeSymbol = await ReplaceTypeParametersBasedOnTypeConstraintsAsync( Document.Project, typeSymbol, compilation, availableTypeParameterNames, cancellationToken).ConfigureAwait(false); return typeSymbol.RemoveUnavailableTypeParameters(compilation, allTypeParameters) .RemoveUnnamedErrorTypes(compilation) .SubstituteTypes(typeArgumentToTypeParameterMap, new TypeGenerator()); } private IDictionary<ITypeSymbol, ITypeParameterSymbol> GetTypeArgumentToTypeParameterMap( CancellationToken cancellationToken) { return _typeArgumentToTypeParameterMap ??= CreateTypeArgumentToTypeParameterMap(cancellationToken); } private IDictionary<ITypeSymbol, ITypeParameterSymbol> CreateTypeArgumentToTypeParameterMap( CancellationToken cancellationToken) { var typeArguments = DetermineTypeArguments(cancellationToken); var typeParameters = DetermineTypeParameters(cancellationToken); // We use a nullability-ignoring comparer because top-level and nested nullability won't matter. If we are looking to replace // IEnumerable<string> with T, we want to replace IEnumerable<string?> whenever it appears in an argument or return type, partly because // there's no way to represent something like T-with-only-the-inner-thing-nullable. We could leave the entire argument as is, but we're suspecting // this is closer to the user's desire, even if it might require some tweaking after the fact. var result = new Dictionary<ITypeSymbol, ITypeParameterSymbol>(SymbolEqualityComparer.Default); for (var i = 0; i < typeArguments.Length; i++) { if (typeArguments[i] != null) { result[typeArguments[i]] = typeParameters[i]; } } return result; } private ImmutableArray<SyntaxNode> GenerateStatements( SyntaxGenerator factory, bool isAbstract) { var throwStatement = CodeGenerationHelpers.GenerateThrowStatement(factory, Document, "System.NotImplementedException"); return isAbstract || State.TypeToGenerateIn.TypeKind == TypeKind.Interface || throwStatement == null ? default : [throwStatement]; } private async ValueTask<ImmutableArray<IParameterSymbol>> DetermineParametersAsync(CancellationToken cancellationToken) { var modifiers = DetermineParameterModifiers(cancellationToken); var types = await SpecializedTasks.WhenAll(DetermineParameterTypes(cancellationToken).Select(t => FixTypeAsync(t, cancellationToken))).ConfigureAwait(false); var optionality = DetermineParameterOptionality(cancellationToken); var names = DetermineParameterNames(cancellationToken); var result = new FixedSizeArrayBuilder<IParameterSymbol>(modifiers.Length); for (var i = 0; i < modifiers.Length; i++) { result.Add(CodeGenerationSymbolFactory.CreateParameterSymbol( attributes: default, refKind: modifiers[i], isParams: false, isOptional: optionality[i], type: types[i], name: names[i].BestNameForParameter)); } return result.MoveToImmutable(); } private Accessibility DetermineAccessibility(bool isAbstract) { var containingType = State.ContainingType; // If we're generating into an interface, then we don't use any modifiers. if (State.TypeToGenerateIn.TypeKind != TypeKind.Interface) { // Otherwise, figure out what accessibility modifier to use and optionally // mark it as static. if (containingType.IsContainedWithin(State.TypeToGenerateIn)) { return isAbstract ? Accessibility.Protected : Accessibility.Private; } else if (DerivesFrom(containingType) && State.IsStatic) { // NOTE(cyrusn): We only generate protected in the case of statics. Consider // the case where we're generating into one of our base types. i.e.: // // class B : A { void Goo() { A a; a.Goo(); } // // In this case we can *not* mark the method as protected. 'B' can only // access protected members of 'A' through an instance of 'B' (or a subclass // of B). It can not access protected members through an instance of the // superclass. In this case we need to make the method public or internal. // // However, this does not apply if the method will be static. i.e. // // class B : A { void Goo() { A.Goo(); } // // B can access the protected statics of A, and so we generate 'Goo' as // protected. // TODO: Code coverage return Accessibility.Protected; } else if (containingType.ContainingAssembly.IsSameAssemblyOrHasFriendAccessTo(State.TypeToGenerateIn.ContainingAssembly)) { return Accessibility.Internal; } else { // TODO: Code coverage return Accessibility.Public; } } return Accessibility.NotApplicable; } private bool DerivesFrom(INamedTypeSymbol containingType) { return containingType.GetBaseTypes().Select(t => t.OriginalDefinition) .OfType<INamedTypeSymbol>() .Contains(State.TypeToGenerateIn); } } }