| File: Debugging\CSharpProximityExpressionsService.cs | Web Access |
| Project: src\roslyn\src\Features\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.Features.csproj (Microsoft.CodeAnalysis.CSharp.Features) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. #nullable disable using System; using System.Collections.Generic; using System.Composition; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Debugging; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Shared.Extensions; namespace Microsoft.CodeAnalysis.CSharp.Debugging; /// <summary> /// Given a position in a source file, returns the expressions in close proximity that should /// show up in the debugger 'autos' window. In general, the expressions we place into the autos /// window are things that appear to be 'side effect free'. Note: because we only use the syntax /// tree for this, it's possible for us to get this wrong. However, this should only happen in /// code that behaves unexpectedly. For example, we will assume that "a + b" is side effect free /// (when in practice it may not be). /// /// The general tactic we take is to add the expressions for the statements on the /// line the debugger is currently at. We will also try to find the 'previous' statement as well /// to add the expressions from that. The 'previous' statement is a bit of an interesting beast. /// Consider, for example, if the user has just jumped out of a switch and is the statement /// directly following it. What is the previous statement? Without keeping state, there's no way /// to know. So, in this case, we treat all 'exit points' (i.e. the last statement of a switch /// section) of the switch statement as the 'previous statement'. There are many cases like this /// we need to handle. Basically anything that might have nested statements/blocks might /// contribute to the 'previous statement' /// </summary> [ExportLanguageService(typeof(IProximityExpressionsService), LanguageNames.CSharp), Shared] internal sealed partial class CSharpProximityExpressionsService : IProximityExpressionsService { [ImportingConstructor] [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] public CSharpProximityExpressionsService() { } public async Task<bool> IsValidAsync( Document document, int position, string expressionValue, CancellationToken cancellationToken) { var expression = SyntaxFactory.ParseExpression(expressionValue); var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var token = root.FindToken(position); if (token.Kind() == SyntaxKind.CloseBraceToken && token.GetPreviousToken().Kind() != SyntaxKind.None) { token = token.GetPreviousToken(); } var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); var info = semanticModel.GetSpeculativeSymbolInfo(token.SpanStart, expression, SpeculativeBindingOption.BindAsExpression); if (info.Symbol == null) { return false; } // We seem to have bound successfully. However, if it bound to a local, then make // sure that that local isn't after the statement that we're currently looking at. if (info.Symbol.Kind == SymbolKind.Local) { var statement = info.Symbol.Locations.First().FindToken(cancellationToken).GetAncestor<StatementSyntax>(); if (statement != null && position < statement.SpanStart) { return false; } } return true; } /// <summary> /// Returns null indicating a failure. /// </summary> public async Task<IList<string>> GetProximityExpressionsAsync( Document document, int position, CancellationToken cancellationToken) { try { var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); return GetProximityExpressions(tree, position, cancellationToken); } catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) { return null; } } public static IList<string> GetProximityExpressions(SyntaxTree syntaxTree, int position, CancellationToken cancellationToken) => new Worker(syntaxTree, position).Do(cancellationToken); private static void AddRelevantExpressions( StatementSyntax statement, IList<string> expressions, bool includeDeclarations) { new RelevantExpressionsCollector(includeDeclarations, expressions).Visit(statement); } }