PageRenderTime 53ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Features/CSharp/Portable/CodeRefactorings/LambdaSimplifier/LambdaSimplifierCodeRefactoringProvider.cs

https://gitlab.com/sharadag/Roslyn
C# | 295 lines | 241 code | 39 blank | 15 comment | 52 complexity | 3dc6b57ad6b81e3b2b23859311ff0ef8 MD5 | raw file
  1. // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Threading;
  6. using System.Threading.Tasks;
  7. using Microsoft.CodeAnalysis.CodeActions;
  8. using Microsoft.CodeAnalysis.CodeRefactorings;
  9. using Microsoft.CodeAnalysis.CSharp;
  10. using Microsoft.CodeAnalysis.CSharp.Extensions;
  11. using Microsoft.CodeAnalysis.CSharp.Syntax;
  12. using Microsoft.CodeAnalysis.Shared.Extensions;
  13. using Roslyn.Utilities;
  14. namespace Microsoft.CodeAnalysis.CSharp.CodeRefactorings.LambdaSimplifier
  15. {
  16. // [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = PredefinedCodeRefactoringProviderNames.SimplifyLambda)]
  17. internal partial class LambdaSimplifierCodeRefactoringProvider : CodeRefactoringProvider
  18. {
  19. public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
  20. {
  21. var document = context.Document;
  22. var textSpan = context.Span;
  23. var cancellationToken = context.CancellationToken;
  24. if (cancellationToken.IsCancellationRequested)
  25. {
  26. return;
  27. }
  28. if (document.Project.Solution.Workspace.Kind == WorkspaceKind.MiscellaneousFiles)
  29. {
  30. return;
  31. }
  32. var semanticDocument = await SemanticDocument.CreateAsync(document, cancellationToken).ConfigureAwait(false);
  33. var lambda = semanticDocument.Root.FindToken(textSpan.Start).GetAncestor(n =>
  34. n is SimpleLambdaExpressionSyntax || n is ParenthesizedLambdaExpressionSyntax);
  35. if (lambda == null || !lambda.Span.IntersectsWith(textSpan.Start))
  36. {
  37. return;
  38. }
  39. if (!CanSimplify(semanticDocument, lambda as SimpleLambdaExpressionSyntax, cancellationToken) &&
  40. !CanSimplify(semanticDocument, lambda as ParenthesizedLambdaExpressionSyntax, cancellationToken))
  41. {
  42. return;
  43. }
  44. context.RegisterRefactoring(
  45. new MyCodeAction(
  46. CSharpFeaturesResources.SimplifyLambdaExpression,
  47. (c) => SimplifyLambdaAsync(document, lambda, c)));
  48. context.RegisterRefactoring(
  49. new MyCodeAction(
  50. CSharpFeaturesResources.SimplifyAllOccurrences,
  51. (c) => SimplifyAllLambdasAsync(document, c)));
  52. }
  53. private async Task<Document> SimplifyLambdaAsync(
  54. Document document,
  55. SyntaxNode lambda,
  56. CancellationToken cancellationToken)
  57. {
  58. var semanticDocument = await SemanticDocument.CreateAsync(document, cancellationToken).ConfigureAwait(false);
  59. var rewriter = new Rewriter(this, semanticDocument, (n) => n == lambda, cancellationToken);
  60. var result = rewriter.Visit(semanticDocument.Root);
  61. return document.WithSyntaxRoot(result);
  62. }
  63. private async Task<Document> SimplifyAllLambdasAsync(
  64. Document document,
  65. CancellationToken cancellationToken)
  66. {
  67. var semanticDocument = await SemanticDocument.CreateAsync(document, cancellationToken).ConfigureAwait(false);
  68. var rewriter = new Rewriter(this, semanticDocument, (n) => true, cancellationToken);
  69. var result = rewriter.Visit(semanticDocument.Root);
  70. return document.WithSyntaxRoot(result);
  71. }
  72. private static bool CanSimplify(
  73. SemanticDocument document,
  74. SimpleLambdaExpressionSyntax node,
  75. CancellationToken cancellationToken)
  76. {
  77. if (node == null)
  78. {
  79. return false;
  80. }
  81. var paramName = node.Parameter.Identifier;
  82. var invocation = TryGetInvocationExpression(node.Body);
  83. return CanSimplify(document, node, new List<SyntaxToken>() { paramName }, invocation, cancellationToken);
  84. }
  85. private static bool CanSimplify(
  86. SemanticDocument document,
  87. ParenthesizedLambdaExpressionSyntax node,
  88. CancellationToken cancellationToken)
  89. {
  90. if (node == null)
  91. {
  92. return false;
  93. }
  94. var paramNames = node.ParameterList.Parameters.Select(p => p.Identifier).ToList();
  95. var invocation = TryGetInvocationExpression(node.Body);
  96. return CanSimplify(document, node, paramNames, invocation, cancellationToken);
  97. }
  98. private static bool CanSimplify(
  99. SemanticDocument document,
  100. ExpressionSyntax lambda,
  101. List<SyntaxToken> paramNames,
  102. InvocationExpressionSyntax invocation,
  103. CancellationToken cancellationToken)
  104. {
  105. if (invocation == null)
  106. {
  107. return false;
  108. }
  109. if (invocation.ArgumentList.Arguments.Count != paramNames.Count)
  110. {
  111. return false;
  112. }
  113. for (var i = 0; i < paramNames.Count; i++)
  114. {
  115. var argument = invocation.ArgumentList.Arguments[i];
  116. if (argument.NameColon != null ||
  117. argument.RefOrOutKeyword.Kind() != SyntaxKind.None ||
  118. !argument.Expression.IsKind(SyntaxKind.IdentifierName))
  119. {
  120. return false;
  121. }
  122. var identifierName = (IdentifierNameSyntax)argument.Expression;
  123. if (identifierName.Identifier.ValueText != paramNames[i].ValueText)
  124. {
  125. return false;
  126. }
  127. }
  128. var semanticModel = document.SemanticModel;
  129. var lambdaSemanticInfo = semanticModel.GetSymbolInfo(lambda, cancellationToken);
  130. var invocationSemanticInfo = semanticModel.GetSymbolInfo(invocation, cancellationToken);
  131. if (lambdaSemanticInfo.Symbol == null ||
  132. invocationSemanticInfo.Symbol == null)
  133. {
  134. // Don't offer this if there are any errors or ambiguities.
  135. return false;
  136. }
  137. var lambdaMethod = lambdaSemanticInfo.Symbol as IMethodSymbol;
  138. var invocationMethod = invocationSemanticInfo.Symbol as IMethodSymbol;
  139. if (lambdaMethod == null || invocationMethod == null)
  140. {
  141. return false;
  142. }
  143. // TODO(cyrusn): Handle extension methods as well.
  144. if (invocationMethod.IsExtensionMethod)
  145. {
  146. return false;
  147. }
  148. // Check if any of the parameter is of Type Dynamic
  149. foreach (var parameter in lambdaMethod.Parameters)
  150. {
  151. if (parameter.Type != null && parameter.Type.Kind == SymbolKind.DynamicType)
  152. {
  153. return false;
  154. }
  155. }
  156. // Check if the parameter and return types match between the lambda and the
  157. // invocation. Note: return types can be covariant and argument types can be
  158. // contravariant.
  159. if (lambdaMethod.ReturnsVoid != invocationMethod.ReturnsVoid ||
  160. lambdaMethod.Parameters.Length != invocationMethod.Parameters.Length)
  161. {
  162. return false;
  163. }
  164. if (!lambdaMethod.ReturnsVoid)
  165. {
  166. // Return type has to be covariant.
  167. var conversion = document.SemanticModel.Compilation.ClassifyConversion(
  168. invocationMethod.ReturnType, lambdaMethod.ReturnType);
  169. if (!conversion.IsIdentityOrImplicitReference())
  170. {
  171. return false;
  172. }
  173. }
  174. // Parameter types have to be contravariant.
  175. for (int i = 0; i < lambdaMethod.Parameters.Length; i++)
  176. {
  177. var conversion = document.SemanticModel.Compilation.ClassifyConversion(
  178. lambdaMethod.Parameters[i].Type, invocationMethod.Parameters[i].Type);
  179. if (!conversion.IsIdentityOrImplicitReference())
  180. {
  181. return false;
  182. }
  183. }
  184. if (WouldCauseAmbiguity(lambda, invocation, semanticModel, cancellationToken))
  185. {
  186. return false;
  187. }
  188. // Looks like something we can simplify.
  189. return true;
  190. }
  191. // Ensure that if we replace the invocation with its expression that its expression will
  192. // bind unambiguously. This can happen with awesome cases like:
  193. #if false
  194. static void Foo<T>(T x) where T : class { }
  195. static void Bar(Action<int> x) { }
  196. static void Bar(Action<string> x) { }
  197. static void Main()
  198. {
  199. Bar(x => Foo(x)); // error CS0121: The call is ambiguous between the following methods or properties: 'A.Bar(System.Action<int>)' and 'A.Bar(System.Action<string>)'
  200. }
  201. #endif
  202. private static bool WouldCauseAmbiguity(
  203. ExpressionSyntax lambda,
  204. InvocationExpressionSyntax invocation,
  205. SemanticModel oldSemanticModel,
  206. CancellationToken cancellationToken)
  207. {
  208. var annotation = new SyntaxAnnotation();
  209. // In order to check if there will be a problem, we actually make the change, fork the
  210. // compilation, and then verify that the new expression bound unambiguously.
  211. var oldExpression = invocation.Expression.WithAdditionalAnnotations(annotation);
  212. var oldCompilation = oldSemanticModel.Compilation;
  213. var oldTree = oldSemanticModel.SyntaxTree;
  214. var oldRoot = oldTree.GetRoot(cancellationToken);
  215. var newRoot = oldRoot.ReplaceNode(lambda, oldExpression);
  216. var newTree = oldTree.WithRootAndOptions(newRoot, oldTree.Options);
  217. var newCompilation = oldCompilation.ReplaceSyntaxTree(oldTree, newTree);
  218. var newExpression = newTree.GetRoot(cancellationToken).GetAnnotatedNodesAndTokens(annotation).First().AsNode();
  219. var newSemanticModel = newCompilation.GetSemanticModel(newTree);
  220. var info = newSemanticModel.GetSymbolInfo(newExpression, cancellationToken);
  221. return info.CandidateReason != CandidateReason.None;
  222. }
  223. private static InvocationExpressionSyntax TryGetInvocationExpression(
  224. SyntaxNode lambdaBody)
  225. {
  226. if (lambdaBody is ExpressionSyntax)
  227. {
  228. return ((ExpressionSyntax)lambdaBody).WalkDownParentheses() as InvocationExpressionSyntax;
  229. }
  230. else if (lambdaBody is BlockSyntax)
  231. {
  232. var block = (BlockSyntax)lambdaBody;
  233. if (block.Statements.Count == 1)
  234. {
  235. var statement = block.Statements.First();
  236. if (statement is ReturnStatementSyntax)
  237. {
  238. return ((ReturnStatementSyntax)statement).Expression.WalkDownParentheses() as InvocationExpressionSyntax;
  239. }
  240. else if (statement is ExpressionStatementSyntax)
  241. {
  242. return ((ExpressionStatementSyntax)statement).Expression.WalkDownParentheses() as InvocationExpressionSyntax;
  243. }
  244. }
  245. }
  246. return null;
  247. }
  248. private class MyCodeAction : CodeAction.DocumentChangeAction
  249. {
  250. public MyCodeAction(string title, Func<CancellationToken, Task<Document>> createChangedDocument) :
  251. base(title, createChangedDocument)
  252. {
  253. }
  254. }
  255. }
  256. }