private void PreAnalyzeFunctionData(FunctionData functionData, SemanticModel semanticModel) { var methodSymbol = functionData.Symbol; if (functionData.Conversion == MethodConversion.Ignore) { return; } functionData.Conversion = _configuration.GetMethodConversion(methodSymbol); // TODO: validate conversion if (functionData.Conversion == MethodConversion.Ignore) { functionData.Ignore(IgnoreReason.MethodConversion, true); return; } functionData.ForceAsync = functionData.Conversion.HasFlag(MethodConversion.ToAsync); var newType = functionData.TypeData.IsNewType; // Here we want to log only ignored methods that were explicitly set to async void IgnoreOrCopy(IgnoreReason reason) { if (newType) { functionData.Copy(); } else { if (functionData.ForceAsync) { reason = reason.WithSeverity(DiagnosticSeverity.Warning); } functionData.Ignore(reason); } } if (methodSymbol.IsAsync || methodSymbol.Name.EndsWith("Async")) { IgnoreOrCopy(IgnoreReason.AlreadyAsync); return; } if ( methodSymbol.MethodKind != MethodKind.Ordinary && methodSymbol.MethodKind != MethodKind.ExplicitInterfaceImplementation && methodSymbol.MethodKind != MethodKind.PropertyGet && methodSymbol.MethodKind != MethodKind.PropertySet) { IgnoreOrCopy(IgnoreReason.NotSupported($"Unsupported method kind {methodSymbol.MethodKind}")); return; } if (methodSymbol.Parameters.Any(o => o.RefKind == RefKind.Out)) { IgnoreOrCopy(IgnoreReason.OutParameters); return; } FillFunctionLocks(functionData, semanticModel); if (!(functionData is MethodOrAccessorData methodData)) { return; } // Override user configuration if the method is set to be copied on a partial type conversion if (methodData.Conversion == MethodConversion.Copy && !newType) { methodData.AddDiagnostic( "Method cannot be copied, when the containing type conversion is not set to be a new type. " + $"Override the method conversion to '{MethodConversion.Unknown}'", DiagnosticSeverity.Warning); methodData.Conversion = MethodConversion.Unknown; } // Check if explicitly implements external interfaces if (methodSymbol.MethodKind == MethodKind.ExplicitInterfaceImplementation) { foreach (var interfaceMember in methodSymbol.ExplicitInterfaceImplementations) { // Check if the interface member has an async counterpart var asyncConterparts = FillRelatedAsyncMethods(methodData, interfaceMember); if (methodSymbol.ContainingAssembly.Name != interfaceMember.ContainingAssembly.Name) { methodData.ExternalRelatedMethods.TryAdd(interfaceMember); if (!asyncConterparts.Any()) { IgnoreOrCopy(IgnoreReason.ExplicitImplementsExternalMethodWithoutAsync(interfaceMember)); return; } } else { methodData.ImplementedInterfaces.TryAdd(interfaceMember); } // For new types we need to copy all interface members if (newType) { methodData.SoftCopy(); } } } // Check if the method is overriding an external method var overridenMethod = methodSymbol.OverriddenMethod; while (overridenMethod != null) { // Check if the member has an async counterpart that is not implemented in the current type (missing member) var asyncConterparts = FillRelatedAsyncMethods(methodData, overridenMethod); if (methodSymbol.ContainingAssembly.Name != overridenMethod.ContainingAssembly.Name) { methodData.ExternalRelatedMethods.TryAdd(overridenMethod); if (!asyncConterparts.Any()) { IgnoreOrCopy(IgnoreReason.OverridesExternalMethodWithoutAsync(overridenMethod)); return; } } else { methodData.OverridenMethods.TryAdd(overridenMethod); } if (overridenMethod.OverriddenMethod != null) { overridenMethod = overridenMethod.OverriddenMethod; } else { break; } } methodData.BaseOverriddenMethod = overridenMethod; // Check if the method is implementing an external interface, if true skip as we cannot modify externals // FindImplementationForInterfaceMember will find the first implementation method starting from the deepest base class var type = methodSymbol.ContainingType; var interfaceMembers = type.AllInterfaces .SelectMany( o => o.GetMembers(methodSymbol.Name) .Where( m => { // Find out if the method implements the interface member or an override // method that implements it var impl = type.FindImplementationForInterfaceMember(m); return(methodSymbol.Equals(impl) || methodData.OverridenMethods.Any(ov => ov.Equals(impl))); } )) .OfType <IMethodSymbol>() .ToList(); foreach (var interfaceMember in interfaceMembers) { // Check if the member has an async counterpart that is not implemented in the current type (missing member) var asyncConterparts = FillRelatedAsyncMethods(methodData, interfaceMember); if (methodSymbol.ContainingAssembly.Name != interfaceMember.ContainingAssembly.Name) { // Check if there is an internal interface member that hides this method if (interfaceMembers.Where(o => o.ContainingAssembly.Name == methodSymbol.ContainingAssembly.Name) .Any(o => o.GetHiddenMethods().Any(hm => hm.Equals(interfaceMember)))) { continue; } methodData.ExternalRelatedMethods.TryAdd(interfaceMember); if (!asyncConterparts.Any()) { IgnoreOrCopy(IgnoreReason.ImplementsExternalMethodWithoutAsync(interfaceMember)); return; } } else { methodData.ImplementedInterfaces.TryAdd(interfaceMember); } // For new types we need to copy all interface member if (newType) { methodData.SoftCopy(); } } // Verify if there is already an async counterpart for this method //TODO: this is not correct when generating methods with a cancellation token as here we do not know // if the generated method will have the cancellation token parameter or not var asyncCounterparts = GetAsyncCounterparts(methodSymbol.OriginalDefinition, _preAnalyzeSearchOptions).ToList(); if (asyncCounterparts.Any()) { if (!_preAnalyzeSearchOptions.HasFlag(AsyncCounterpartsSearchOptions.HasCancellationToken) && asyncCounterparts.Count > 1) { throw new InvalidOperationException($"Method {methodSymbol} has more than one async counterpart"); } // We shall get a maximum of two async counterparts when the HasCancellationToken flag is used if (_preAnalyzeSearchOptions.HasFlag(AsyncCounterpartsSearchOptions.HasCancellationToken) && asyncCounterparts.Count > 2) { throw new InvalidOperationException($"Method {methodSymbol} has more than two async counterparts"); } foreach (var asyncCounterpart in asyncCounterparts) { // Check if the async counterpart has a cancellation token if (asyncCounterpart.Parameters.Length > methodSymbol.Parameters.Length) { methodData.AsyncCounterpartWithTokenSymbol = asyncCounterpart; } else { methodData.AsyncCounterpartSymbol = asyncCounterpart; } methodData.IgnoreAsyncCounterpart = _configuration.IgnoreAsyncCounterpartsPredicates.Any(p => p(asyncCounterpart)); } // TODO: define a better logic // We should not ignore if none of the async counterparts is in the sync method type. if (asyncCounterparts.Any(o => o.ContainingType.Equals(methodSymbol.ContainingType)) /*(_configuration.UseCancellationTokens && asyncCounterparts.Count == 2) || * (!_configuration.UseCancellationTokens && asyncCounterparts.Count == 1)*/ ) { IgnoreOrCopy(IgnoreReason.AsyncCounterpartExists); methodData.CancellationTokenRequired = methodData.AsyncCounterpartWithTokenSymbol != null; return; } } // Create an override async method if any of the related async methods is virtual or abstract // We need to do this here so that the method body will get scanned for async counterparts if (methodData.RelatedAsyncMethods.Any(o => o.IsVirtual || o.IsAbstract) && _configuration.CanScanForMissingAsyncMembers?.Invoke(methodData.TypeData.Symbol) == true) { if (!methodData.Conversion.HasAnyFlag(MethodConversion.ToAsync, MethodConversion.Smart)) { methodData.Smart(); } if (newType) { methodData.SoftCopy(); } // We have to generate the cancellation token parameter if the async member has more parameters that the sync counterpart var asyncMember = methodData.RelatedAsyncMethods .Where(o => o.IsVirtual || o.IsAbstract) .FirstOrDefault(o => o.Parameters.Length > methodData.Symbol.Parameters.Length); if (asyncMember != null) { methodData.CancellationTokenRequired = true; // We suppose that the cancellation token is the last parameter methodData.MethodCancellationToken = asyncMember.Parameters.Last().HasExplicitDefaultValue ? MethodCancellationToken.Optional : MethodCancellationToken.Required; } } }