1
0

Program.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657
  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the Apache 2.0 License.
  3. // See the LICENSE file in the project root for more information.
  4. using System;
  5. using System.Collections.Generic;
  6. using System.Linq;
  7. using System.Linq.Expressions;
  8. using System.Reflection;
  9. using System.Runtime.CompilerServices;
  10. using System.Threading;
  11. using System.Threading.Tasks;
  12. namespace ApiCompare
  13. {
  14. class Program
  15. {
  16. private static readonly Type asyncInterfaceType = typeof(IAsyncEnumerable<>);
  17. private static readonly Type syncInterfaceType = typeof(IEnumerable<>);
  18. private static readonly Type asyncOrderedInterfaceType = typeof(IOrderedAsyncEnumerable<>);
  19. private static readonly Type syncOrderedInterfaceType = typeof(IOrderedEnumerable<>);
  20. private static readonly TypeSubstitutor subst = new TypeSubstitutor(new Dictionary<Type, Type>
  21. {
  22. { asyncInterfaceType, syncInterfaceType },
  23. { asyncOrderedInterfaceType, syncOrderedInterfaceType },
  24. { typeof(IAsyncGrouping<,>), typeof(IGrouping<,>) },
  25. });
  26. static void Main()
  27. {
  28. var asyncOperatorsType = typeof(AsyncEnumerable);
  29. var syncOperatorsType = typeof(Enumerable);
  30. Compare(syncOperatorsType, asyncOperatorsType);
  31. }
  32. static void Compare(Type syncOperatorsType, Type asyncOperatorsType)
  33. {
  34. var syncOperators = GetQueryOperators(new[] { syncInterfaceType, syncOrderedInterfaceType}, syncOperatorsType);
  35. var asyncOperators = GetQueryOperators(new[] { asyncInterfaceType, asyncOrderedInterfaceType }, asyncOperatorsType);
  36. CompareFactories(syncOperators.Factories, asyncOperators.Factories);
  37. CompareQueryOperators(syncOperators.QueryOperators, asyncOperators.QueryOperators);
  38. CompareAggregates(syncOperators.Aggregates, asyncOperators.Aggregates);
  39. }
  40. static void CompareFactories(ILookup<string, MethodInfo> syncFactories, ILookup<string, MethodInfo> asyncFactories)
  41. {
  42. CompareSets(syncFactories, asyncFactories, CompareFactoryOverloads);
  43. }
  44. static void CompareFactoryOverloads(string name, IEnumerable<MethodInfo> syncMethods, IEnumerable<MethodInfo> asyncMethods)
  45. {
  46. var sync = GetSignatures(syncMethods).ToArray();
  47. var async = GetRewrittenSignatures(asyncMethods).ToArray();
  48. //
  49. // Ensure that async is a superset of sync.
  50. //
  51. var notInAsync = sync.Except(async);
  52. if (notInAsync.Any())
  53. {
  54. foreach (var signature in notInAsync)
  55. {
  56. Console.WriteLine("MISSING " + ToString(signature.Method));
  57. }
  58. }
  59. //
  60. // Check for excess overloads.
  61. //
  62. var notInSync = async.Except(sync);
  63. if (notInSync.Any())
  64. {
  65. foreach (var signature in notInSync)
  66. {
  67. Console.WriteLine("EXCESS " + ToString(signature.Method));
  68. }
  69. }
  70. }
  71. static void CompareQueryOperators(ILookup<string, MethodInfo> syncOperators, ILookup<string, MethodInfo> asyncOperators)
  72. {
  73. CompareSets(syncOperators, asyncOperators, CompareQueryOperatorsOverloads);
  74. }
  75. static void CompareQueryOperatorsOverloads(string name, IEnumerable<MethodInfo> syncMethods, IEnumerable<MethodInfo> asyncMethods)
  76. {
  77. var sync = GetSignatures(syncMethods).ToArray();
  78. var async = GetRewrittenSignatures(asyncMethods).ToArray();
  79. //
  80. // Ensure that async is a superset of sync.
  81. //
  82. var notInAsync = sync.Except(async);
  83. if (notInAsync.Any())
  84. {
  85. foreach (var signature in notInAsync)
  86. {
  87. Console.WriteLine("MISSING " + ToString(signature.Method));
  88. }
  89. }
  90. //
  91. // Find Task-based overloads.
  92. //
  93. var taskBasedSignatures = new List<Signature>();
  94. foreach (var signature in sync)
  95. {
  96. if (signature.ParameterTypes.Any(IsFuncOrActionType))
  97. {
  98. taskBasedSignatures.Add(GetAsyncVariant(signature));
  99. }
  100. }
  101. if (taskBasedSignatures.Count > 0)
  102. {
  103. var notInAsyncTaskBased = taskBasedSignatures.Except(async);
  104. if (notInAsyncTaskBased.Any())
  105. {
  106. foreach (var signature in notInAsyncTaskBased)
  107. {
  108. Console.WriteLine("MISSING " + name + " :: " + signature);
  109. }
  110. }
  111. }
  112. //
  113. // Excess overloads that are neither carbon copies of sync nor Task-based variants of sync.
  114. //
  115. var notInSync = async.Except(sync.Union(taskBasedSignatures));
  116. if (notInSync.Any())
  117. {
  118. foreach (var signature in notInSync)
  119. {
  120. Console.WriteLine("EXCESS " + ToString(signature.Method));
  121. }
  122. }
  123. }
  124. static void CompareAggregates(ILookup<string, MethodInfo> syncAggregates, ILookup<string, MethodInfo> asyncAggregates)
  125. {
  126. CompareSets(syncAggregates, asyncAggregates, CompareAggregateOverloads);
  127. }
  128. static void CompareAggregateOverloads(string name, IEnumerable<MethodInfo> syncMethods, IEnumerable<MethodInfo> asyncMethods)
  129. {
  130. var sync = GetSignatures(syncMethods).Select(GetAsyncAggregateSignature).ToArray();
  131. var async = GetRewrittenSignatures(asyncMethods).ToArray();
  132. //
  133. // Ensure that async is a superset of sync.
  134. //
  135. var notInAsync = sync.Except(async);
  136. if (notInAsync.Any())
  137. {
  138. foreach (var signature in notInAsync)
  139. {
  140. Console.WriteLine("MISSING " + ToString(signature.Method));
  141. }
  142. }
  143. //
  144. // Find Task-based overloads.
  145. //
  146. var taskBasedSignatures = new List<Signature>();
  147. foreach (var signature in sync)
  148. {
  149. if (signature.ParameterTypes.Any(IsFuncOrActionType))
  150. {
  151. taskBasedSignatures.Add(GetAsyncVariant(signature));
  152. }
  153. }
  154. if (taskBasedSignatures.Count > 0)
  155. {
  156. var notInAsyncTaskBased = taskBasedSignatures.Except(async);
  157. if (notInAsyncTaskBased.Any())
  158. {
  159. foreach (var signature in notInAsyncTaskBased)
  160. {
  161. Console.WriteLine("MISSING " + name + " :: " + signature);
  162. }
  163. }
  164. }
  165. //
  166. // Check for overloads with CancellationToken.
  167. //
  168. var withCancellationToken = new List<Signature>();
  169. foreach (var signature in sync)
  170. {
  171. withCancellationToken.Add(AppendCancellationToken(signature));
  172. }
  173. foreach (var signature in taskBasedSignatures)
  174. {
  175. withCancellationToken.Add(AppendCancellationToken(signature));
  176. }
  177. var notInAsyncWithCancellationToken = withCancellationToken.Except(async);
  178. if (notInAsyncWithCancellationToken.Any())
  179. {
  180. foreach (var signature in notInAsyncWithCancellationToken)
  181. {
  182. Console.WriteLine("MISSING " + name + " :: " + signature);
  183. }
  184. }
  185. //
  186. // Excess overloads that are neither carbon copies of sync nor Task-based variants of sync.
  187. //
  188. var notInSync = async.Except(sync.Union(taskBasedSignatures).Union(withCancellationToken));
  189. if (notInSync.Any())
  190. {
  191. foreach (var signature in notInSync)
  192. {
  193. Console.WriteLine("EXCESS " + ToString(signature.Method));
  194. }
  195. }
  196. }
  197. private static bool IsFuncOrActionType(Type type)
  198. {
  199. if (type.IsConstructedGenericType)
  200. {
  201. var defName = type.GetGenericTypeDefinition().Name;
  202. return defName.StartsWith("Func`") || defName.StartsWith("Action`");
  203. }
  204. if (type == typeof(Action))
  205. {
  206. return true;
  207. }
  208. return false;
  209. }
  210. private static Signature GetAsyncVariant(Signature signature)
  211. {
  212. return new Signature
  213. {
  214. ParameterTypes = signature.ParameterTypes.Select(GetAsyncVariant).ToArray(),
  215. ReturnType = signature.ReturnType
  216. };
  217. }
  218. private static Signature AppendCancellationToken(Signature signature)
  219. {
  220. return new Signature
  221. {
  222. ParameterTypes = signature.ParameterTypes.Concat(new[] { typeof(CancellationToken) }).ToArray(),
  223. ReturnType = signature.ReturnType
  224. };
  225. }
  226. private static Type GetAsyncVariant(Type type)
  227. {
  228. if (IsFuncOrActionType(type))
  229. {
  230. if (type == typeof(Action))
  231. {
  232. return typeof(Func<Task>);
  233. }
  234. else
  235. {
  236. var args = type.GetGenericArguments();
  237. var defName = type.GetGenericTypeDefinition().Name;
  238. if (defName.StartsWith("Func`"))
  239. {
  240. var ret = typeof(Task<>).MakeGenericType(args.Last());
  241. return Expression.GetFuncType(args.SkipLast(1).Append(ret).ToArray());
  242. }
  243. else
  244. {
  245. return Expression.GetFuncType(args.Append(typeof(Task)).ToArray());
  246. }
  247. }
  248. }
  249. return type;
  250. }
  251. static void CompareSets(ILookup<string, MethodInfo> sync, ILookup<string, MethodInfo> async, Action<string, IEnumerable<MethodInfo>, IEnumerable<MethodInfo>> compareCore)
  252. {
  253. var syncNames = sync.Select(g => g.Key).ToArray();
  254. var asyncNames = async.Select(g => g.Key).ToArray();
  255. //
  256. // Analyze that async is a superset of sync.
  257. //
  258. var notInAsync = syncNames.Except(asyncNames);
  259. foreach (var n in notInAsync)
  260. {
  261. foreach (var o in sync[n])
  262. {
  263. Console.WriteLine("MISSING " + ToString(o));
  264. }
  265. }
  266. //
  267. // Need to find the same overloads.
  268. //
  269. var inBoth = syncNames.Intersect(asyncNames);
  270. foreach (var n in inBoth)
  271. {
  272. var s = sync[n];
  273. var a = async[n];
  274. compareCore(n, s, a);
  275. }
  276. //
  277. // Report excessive API surface.
  278. //
  279. var onlyInAsync = asyncNames.Except(syncNames);
  280. foreach (var n in onlyInAsync)
  281. {
  282. foreach (var o in async[n])
  283. {
  284. Console.WriteLine("EXCESS " + ToString(o));
  285. }
  286. }
  287. }
  288. static Operators GetQueryOperators(Type[] interfaceTypes, Type operatorsType)
  289. {
  290. //
  291. // Get all the static methods.
  292. //
  293. var methods = operatorsType.GetMethods(BindingFlags.Public | BindingFlags.Static);
  294. //
  295. // Get extension methods. These can be either operators or aggregates.
  296. //
  297. var extensionMethods = methods.Where(m => m.IsDefined(typeof(ExtensionAttribute))).ToArray();
  298. //
  299. // Static methods that aren't extension methods can be factories.
  300. //
  301. var factories = methods.Except(extensionMethods).Where(m => m.ReturnType.IsConstructedGenericType && interfaceTypes.Contains(m.ReturnType.GetGenericTypeDefinition())).ToArray();
  302. //
  303. // Extension methods that return the interface type are operators.
  304. //
  305. var queryOperators = extensionMethods.Where(m => m.ReturnType.IsConstructedGenericType && interfaceTypes.Contains(m.ReturnType.GetGenericTypeDefinition())).ToArray();
  306. //
  307. // Extension methods that return another type are aggregates.
  308. //
  309. var aggregates = extensionMethods.Except(queryOperators).ToArray();
  310. //
  311. // Return operators.
  312. //
  313. return new Operators
  314. {
  315. Factories = factories.ToLookup(m => m.Name, m => m),
  316. QueryOperators = queryOperators.ToLookup(m => m.Name, m => m),
  317. Aggregates = aggregates.ToLookup(m => m.Name, m => m),
  318. };
  319. }
  320. static IEnumerable<Signature> GetSignatures(IEnumerable<MethodInfo> methods)
  321. {
  322. return methods.Select(m => GetSignature(m));
  323. }
  324. static IEnumerable<Signature> GetRewrittenSignatures(IEnumerable<MethodInfo> methods)
  325. {
  326. return GetSignatures(methods).Select(s => RewriteSignature(s));
  327. }
  328. static Signature GetSignature(MethodInfo method)
  329. {
  330. if (method.IsGenericMethodDefinition)
  331. {
  332. var newArgs = method.GetGenericArguments().Select((t, i) => Wildcards[i]).ToArray();
  333. method = method.MakeGenericMethod(newArgs);
  334. }
  335. return new Signature
  336. {
  337. Method = method,
  338. ReturnType = method.ReturnType,
  339. ParameterTypes = method.GetParameters().Select(p => p.ParameterType).ToArray()
  340. };
  341. }
  342. static Signature RewriteSignature(Signature signature)
  343. {
  344. return new Signature
  345. {
  346. Method = signature.Method,
  347. ReturnType = subst.Visit(signature.ReturnType),
  348. ParameterTypes = subst.Visit(signature.ParameterTypes)
  349. };
  350. }
  351. static Signature GetAsyncAggregateSignature(Signature signature)
  352. {
  353. var retType = signature.ReturnType == typeof(void) ? typeof(Task) : typeof(Task<>).MakeGenericType(signature.ReturnType);
  354. return new Signature
  355. {
  356. Method = signature.Method,
  357. ReturnType = retType,
  358. ParameterTypes = signature.ParameterTypes
  359. };
  360. }
  361. static string ToString(MethodInfo method)
  362. {
  363. if (method == null)
  364. {
  365. return "UNKNOWN";
  366. }
  367. if (method.IsGenericMethod && !method.IsGenericMethodDefinition)
  368. {
  369. method = method.GetGenericMethodDefinition();
  370. }
  371. return method.ToString();
  372. }
  373. class Operators
  374. {
  375. public ILookup<string, MethodInfo> Factories;
  376. public ILookup<string, MethodInfo> QueryOperators;
  377. public ILookup<string, MethodInfo> Aggregates;
  378. }
  379. class Signature : IEquatable<Signature>
  380. {
  381. public MethodInfo Method;
  382. public Type ReturnType;
  383. public Type[] ParameterTypes;
  384. public static bool operator ==(Signature s1, Signature s2)
  385. {
  386. if ((object)s1 == null && (object)s2 == null)
  387. {
  388. return true;
  389. }
  390. if ((object)s1 == null || (object)s2 == null)
  391. {
  392. return false;
  393. }
  394. return s1.Equals(s2);
  395. }
  396. public static bool operator !=(Signature s1, Signature s2)
  397. {
  398. return !(s1 == s2);
  399. }
  400. public bool Equals(Signature s)
  401. {
  402. return (object)s != null && ReturnType.Equals(s.ReturnType) && ParameterTypes.SequenceEqual(s.ParameterTypes);
  403. }
  404. public override bool Equals(object obj)
  405. {
  406. if (obj is Signature s)
  407. {
  408. return Equals(s);
  409. }
  410. return false;
  411. }
  412. public override int GetHashCode()
  413. {
  414. return ParameterTypes.Concat(new[] { ReturnType }).Aggregate(0, (a, t) => a * 17 + t.GetHashCode());
  415. }
  416. public override string ToString()
  417. {
  418. return "(" + string.Join(", ", ParameterTypes.Select(t => t.ToCSharp())) + ") -> " + ReturnType.ToCSharp();
  419. }
  420. }
  421. class TypeVisitor
  422. {
  423. public virtual Type Visit(Type type)
  424. {
  425. if (type.IsArray)
  426. {
  427. if (type.GetElementType().MakeArrayType() == type)
  428. {
  429. return VisitArray(type);
  430. }
  431. else
  432. {
  433. return VisitMultidimensionalArray(type);
  434. }
  435. }
  436. else if (type.GetTypeInfo().IsGenericTypeDefinition)
  437. {
  438. return VisitGenericTypeDefinition(type);
  439. }
  440. else if (type.IsConstructedGenericType)
  441. {
  442. return VisitGeneric(type);
  443. }
  444. else if (type.IsByRef)
  445. {
  446. return VisitByRef(type);
  447. }
  448. else if (type.IsPointer)
  449. {
  450. return VisitPointer(type);
  451. }
  452. else
  453. {
  454. return VisitSimple(type);
  455. }
  456. }
  457. protected virtual Type VisitArray(Type type)
  458. {
  459. return Visit(type.GetElementType()).MakeArrayType();
  460. }
  461. protected virtual Type VisitMultidimensionalArray(Type type)
  462. {
  463. return Visit(type.GetElementType()).MakeArrayType(type.GetArrayRank());
  464. }
  465. protected virtual Type VisitGenericTypeDefinition(Type type)
  466. {
  467. return type;
  468. }
  469. protected virtual Type VisitGeneric(Type type)
  470. {
  471. return Visit(type.GetGenericTypeDefinition()).MakeGenericType(Visit(type.GenericTypeArguments));
  472. }
  473. protected virtual Type VisitByRef(Type type)
  474. {
  475. return Visit(type.GetElementType()).MakeByRefType();
  476. }
  477. protected virtual Type VisitPointer(Type type)
  478. {
  479. return Visit(type.GetElementType()).MakePointerType();
  480. }
  481. protected virtual Type VisitSimple(Type type)
  482. {
  483. return type;
  484. }
  485. public Type[] Visit(Type[] types)
  486. {
  487. return types.Select(Visit).ToArray();
  488. }
  489. }
  490. class TypeSubstitutor : TypeVisitor
  491. {
  492. private readonly Dictionary<Type, Type> map;
  493. public TypeSubstitutor(Dictionary<Type, Type> map)
  494. {
  495. this.map = map;
  496. }
  497. public override Type Visit(Type type)
  498. {
  499. if (map.TryGetValue(type, out var subst))
  500. {
  501. return subst;
  502. }
  503. return base.Visit(type);
  504. }
  505. }
  506. private static readonly Type[] Wildcards = new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) };
  507. class T1 { }
  508. class T2 { }
  509. class T3 { }
  510. class T4 { }
  511. }
  512. static class TypeExtensions
  513. {
  514. public static string ToCSharp(this Type type)
  515. {
  516. if (type.IsArray)
  517. {
  518. if (type.GetElementType().MakeArrayType() == type)
  519. {
  520. return type.GetElementType().ToCSharp() + "[]";
  521. }
  522. else
  523. {
  524. return type.GetElementType().ToCSharp() + "[" + new string(',', type.GetArrayRank() - 1) + "]";
  525. }
  526. }
  527. else if (type.IsConstructedGenericType)
  528. {
  529. var def = type.GetGenericTypeDefinition();
  530. var defName = def.Name.Substring(0, def.Name.IndexOf('`'));
  531. return defName + "<" + string.Join(", ", type.GetGenericArguments().Select(ToCSharp)) + ">";
  532. }
  533. else
  534. {
  535. return type.Name;
  536. }
  537. }
  538. }
  539. }