ObjectMethodExecutorFSharpSupport.cs 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. // Copyright (c) .NET Foundation. All rights reserved.
  2. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  3. #nullable disable
  4. using System;
  5. using System.Linq;
  6. using System.Linq.Expressions;
  7. using System.Reflection;
  8. using System.Threading;
  9. using System.Threading.Tasks;
  10. namespace Microsoft.Extensions.Internal
  11. {
  12. /// <summary>
  13. /// Helper for detecting whether a given type is FSharpAsync`1, and if so, supplying
  14. /// an <see cref="Expression"/> for mapping instances of that type to a C# awaitable.
  15. /// </summary>
  16. /// <remarks>
  17. /// The main design goal here is to avoid taking a compile-time dependency on
  18. /// FSharp.Core.dll, because non-F# applications wouldn't use it. So all the references
  19. /// to FSharp types have to be constructed dynamically at runtime.
  20. /// </remarks>
  21. internal static class ObjectMethodExecutorFSharpSupport
  22. {
  23. private static object _fsharpValuesCacheLock = new object();
  24. private static Assembly _fsharpCoreAssembly;
  25. private static MethodInfo _fsharpAsyncStartAsTaskGenericMethod;
  26. private static PropertyInfo _fsharpOptionOfTaskCreationOptionsNoneProperty;
  27. private static PropertyInfo _fsharpOptionOfCancellationTokenNoneProperty;
  28. public static bool TryBuildCoercerFromFSharpAsyncToAwaitable(
  29. Type possibleFSharpAsyncType,
  30. out Expression coerceToAwaitableExpression,
  31. out Type awaitableType)
  32. {
  33. var methodReturnGenericType = possibleFSharpAsyncType.IsGenericType
  34. ? possibleFSharpAsyncType.GetGenericTypeDefinition()
  35. : null;
  36. if (!IsFSharpAsyncOpenGenericType(methodReturnGenericType))
  37. {
  38. coerceToAwaitableExpression = null;
  39. awaitableType = null;
  40. return false;
  41. }
  42. var awaiterResultType = possibleFSharpAsyncType.GetGenericArguments().Single();
  43. awaitableType = typeof(Task<>).MakeGenericType(awaiterResultType);
  44. // coerceToAwaitableExpression = (object fsharpAsync) =>
  45. // {
  46. // return (object)FSharpAsync.StartAsTask<TResult>(
  47. // (Microsoft.FSharp.Control.FSharpAsync<TResult>)fsharpAsync,
  48. // FSharpOption<TaskCreationOptions>.None,
  49. // FSharpOption<CancellationToken>.None);
  50. // };
  51. var startAsTaskClosedMethod = _fsharpAsyncStartAsTaskGenericMethod
  52. .MakeGenericMethod(awaiterResultType);
  53. var coerceToAwaitableParam = Expression.Parameter(typeof(object));
  54. coerceToAwaitableExpression = Expression.Lambda(
  55. Expression.Convert(
  56. Expression.Call(
  57. startAsTaskClosedMethod,
  58. Expression.Convert(coerceToAwaitableParam, possibleFSharpAsyncType),
  59. Expression.MakeMemberAccess(null, _fsharpOptionOfTaskCreationOptionsNoneProperty),
  60. Expression.MakeMemberAccess(null, _fsharpOptionOfCancellationTokenNoneProperty)),
  61. typeof(object)),
  62. coerceToAwaitableParam);
  63. return true;
  64. }
  65. private static bool IsFSharpAsyncOpenGenericType(Type possibleFSharpAsyncGenericType)
  66. {
  67. var typeFullName = possibleFSharpAsyncGenericType?.FullName;
  68. if (!string.Equals(typeFullName, "Microsoft.FSharp.Control.FSharpAsync`1", StringComparison.Ordinal))
  69. {
  70. return false;
  71. }
  72. lock (_fsharpValuesCacheLock)
  73. {
  74. if (_fsharpCoreAssembly != null)
  75. {
  76. // Since we've already found the real FSharpAsync.Core assembly, we just have
  77. // to check that the supplied FSharpAsync`1 type is the one from that assembly.
  78. return possibleFSharpAsyncGenericType.Assembly == _fsharpCoreAssembly;
  79. }
  80. else
  81. {
  82. // We'll keep trying to find the FSharp types/values each time any type called
  83. // FSharpAsync`1 is supplied.
  84. return TryPopulateFSharpValueCaches(possibleFSharpAsyncGenericType);
  85. }
  86. }
  87. }
  88. private static bool TryPopulateFSharpValueCaches(Type possibleFSharpAsyncGenericType)
  89. {
  90. var assembly = possibleFSharpAsyncGenericType.Assembly;
  91. var fsharpOptionType = assembly.GetType("Microsoft.FSharp.Core.FSharpOption`1");
  92. var fsharpAsyncType = assembly.GetType("Microsoft.FSharp.Control.FSharpAsync");
  93. if (fsharpOptionType == null || fsharpAsyncType == null)
  94. {
  95. return false;
  96. }
  97. // Get a reference to FSharpOption<TaskCreationOptions>.None
  98. var fsharpOptionOfTaskCreationOptionsType = fsharpOptionType
  99. .MakeGenericType(typeof(TaskCreationOptions));
  100. _fsharpOptionOfTaskCreationOptionsNoneProperty = fsharpOptionOfTaskCreationOptionsType
  101. .GetRuntimeProperty("None");
  102. // Get a reference to FSharpOption<CancellationToken>.None
  103. var fsharpOptionOfCancellationTokenType = fsharpOptionType
  104. .MakeGenericType(typeof(CancellationToken));
  105. _fsharpOptionOfCancellationTokenNoneProperty = fsharpOptionOfCancellationTokenType
  106. .GetRuntimeProperty("None");
  107. // Get a reference to FSharpAsync.StartAsTask<>
  108. var fsharpAsyncMethods = fsharpAsyncType
  109. .GetRuntimeMethods()
  110. .Where(m => m.Name.Equals("StartAsTask", StringComparison.Ordinal));
  111. foreach (var candidateMethodInfo in fsharpAsyncMethods)
  112. {
  113. var parameters = candidateMethodInfo.GetParameters();
  114. if (parameters.Length == 3
  115. && TypesHaveSameIdentity(parameters[0].ParameterType, possibleFSharpAsyncGenericType)
  116. && parameters[1].ParameterType == fsharpOptionOfTaskCreationOptionsType
  117. && parameters[2].ParameterType == fsharpOptionOfCancellationTokenType)
  118. {
  119. // This really does look like the correct method (and hence assembly).
  120. _fsharpAsyncStartAsTaskGenericMethod = candidateMethodInfo;
  121. _fsharpCoreAssembly = assembly;
  122. break;
  123. }
  124. }
  125. return _fsharpCoreAssembly != null;
  126. }
  127. private static bool TypesHaveSameIdentity(Type type1, Type type2)
  128. {
  129. return type1.Assembly == type2.Assembly
  130. && string.Equals(type1.Namespace, type2.Namespace, StringComparison.Ordinal)
  131. && string.Equals(type1.Name, type2.Name, StringComparison.Ordinal);
  132. }
  133. }
  134. }