/* Yarn Spinner is licensed to you under the terms found in the file LICENSE.md. */ using Microsoft.CodeAnalysis; using System.Linq; #nullable enable static class SymbolExtensions { /// /// If the is a method symbol, returns if the method's return type is "awaitable", but not if it's . /// If the is a type symbol, returns if that type is "awaitable". /// An "awaitable" is any type that exposes a GetAwaiter method which returns a valid "awaiter". This GetAwaiter method may be an instance method or an extension method. /// public static bool IsAwaitableNonDynamic(this ISymbol symbol, SemanticModel semanticModel, int position) { IMethodSymbol? methodSymbol = symbol as IMethodSymbol; ITypeSymbol? typeSymbol = null; if (methodSymbol == null) { typeSymbol = symbol as ITypeSymbol; if (typeSymbol == null) { return false; } } else { if (methodSymbol.ReturnType == null) { return false; } } // otherwise: needs valid GetAwaiter var potentialGetAwaiters = semanticModel.LookupSymbols(position, container: typeSymbol ?? methodSymbol?.ReturnType.OriginalDefinition, name: WellKnownMemberNames.GetAwaiter, includeReducedExtensionMethods: true); var getAwaiters = potentialGetAwaiters.OfType().Where(x => !x.Parameters.Any()); return getAwaiters.Any(VerifyGetAwaiter); } private static bool VerifyGetAwaiter(IMethodSymbol getAwaiter) { var returnType = getAwaiter.ReturnType; if (returnType == null) { return false; } // bool IsCompleted { get } if (!returnType.GetMembers().OfType().Any(p => p.Name == WellKnownMemberNames.IsCompleted && p.Type.SpecialType == SpecialType.System_Boolean && p.GetMethod != null)) { return false; } var methods = returnType.GetMembers().OfType(); // NOTE: (vladres) The current version of C# Spec, ยง7.7.7.3 'Runtime evaluation of await expressions', requires that // NOTE: the interface method INotifyCompletion.OnCompleted or ICriticalNotifyCompletion.UnsafeOnCompleted is invoked // NOTE: (rather than any OnCompleted method conforming to a certain pattern). // NOTE: Should this code be updated to match the spec? // void OnCompleted(Action) // Actions are delegates, so we'll just check for delegates. if (!methods.Any(x => x.Name == WellKnownMemberNames.OnCompleted && x.ReturnsVoid && x.Parameters.Length == 1 && x.Parameters.First().Type.TypeKind == TypeKind.Delegate)) { return false; } // void GetResult() || T GetResult() return methods.Any(m => m.Name == WellKnownMemberNames.GetResult && !m.Parameters.Any()); } }