File: Rename\LightweightRenameLocations.cs
Web Access
Project: src\src\Workspaces\Core\Portable\Microsoft.CodeAnalysis.Workspaces.csproj (Microsoft.CodeAnalysis.Workspaces)
// 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.
 
using System;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeCleanup;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.Remote;
using Microsoft.CodeAnalysis.Rename.ConflictEngine;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Rename;
 
/// <summary>
/// Equivalent to <see cref="SymbolicRenameLocations"/> except that references to symbols are kept in a lightweight fashion
/// to avoid expensive rehydration steps as a host and OOP communicate.
/// </summary>
internal sealed partial class LightweightRenameLocations
{
    public readonly Solution Solution;
    public readonly SymbolRenameOptions Options;
 
    public readonly ImmutableArray<RenameLocation> Locations;
    private readonly ImmutableArray<SerializableReferenceLocation> _implicitLocations;
    private readonly ImmutableArray<SerializableSymbolAndProjectId> _referencedSymbols;
 
    private LightweightRenameLocations(
        Solution solution,
        SymbolRenameOptions options,
        ImmutableArray<RenameLocation> locations,
        ImmutableArray<SerializableReferenceLocation> implicitLocations,
        ImmutableArray<SerializableSymbolAndProjectId> referencedSymbols)
    {
        Contract.ThrowIfTrue(locations.IsDefault);
        Contract.ThrowIfTrue(implicitLocations.IsDefault);
        Contract.ThrowIfTrue(referencedSymbols.IsDefault);
        Solution = solution;
        Options = options;
        Locations = locations;
        _implicitLocations = implicitLocations;
        _referencedSymbols = referencedSymbols;
    }
 
    public async Task<SymbolicRenameLocations?> ToSymbolicLocationsAsync(ISymbol symbol, CancellationToken cancellationToken)
    {
        var referencedSymbols = await _referencedSymbols.SelectAsArrayAsync(
            static (sym, solution, cancellationToken) => sym.TryRehydrateAsync(solution, cancellationToken), Solution, cancellationToken).ConfigureAwait(false);
 
        if (referencedSymbols.Any(s => s is null))
            return null;
 
        var implicitLocations = await _implicitLocations.SelectAsArrayAsync(
            static (loc, solution, cancellationToken) => loc.RehydrateAsync(solution, cancellationToken), Solution, cancellationToken).ConfigureAwait(false);
 
        return new SymbolicRenameLocations(
            symbol,
            Solution,
            Options,
            Locations,
            implicitLocations,
            referencedSymbols);
    }
 
    /// <summary>
    /// Find the locations that need to be renamed.  Can cross process boundaries efficiently to do this.
    /// </summary>
    public static async Task<LightweightRenameLocations> FindRenameLocationsAsync(
        ISymbol symbol, Solution solution, SymbolRenameOptions options, CancellationToken cancellationToken)
    {
        Contract.ThrowIfNull(solution);
        Contract.ThrowIfNull(symbol);
 
        cancellationToken.ThrowIfCancellationRequested();
 
        using (Logger.LogBlock(FunctionId.Renamer_FindRenameLocationsAsync, cancellationToken))
        {
            if (SerializableSymbolAndProjectId.TryCreate(symbol, solution, cancellationToken, out var serializedSymbol))
            {
                var client = await RemoteHostClient.TryGetClientAsync(solution.Services, cancellationToken).ConfigureAwait(false);
                if (client != null)
                {
                    var result = await client.TryInvokeAsync<IRemoteRenamerService, SerializableRenameLocations?>(
                        solution,
                        (service, solutionInfo, cancellationToken) => service.FindRenameLocationsAsync(solutionInfo, serializedSymbol, options, cancellationToken),
                        cancellationToken).ConfigureAwait(false);
 
                    if (result.HasValue && result.Value != null)
                    {
                        var rehydratedLocations = await result.Value.RehydrateLocationsAsync(solution, cancellationToken).ConfigureAwait(false);
                        return new LightweightRenameLocations(
                            solution, options,
                            rehydratedLocations,
                            result.Value.ImplicitLocations,
                            result.Value.ReferencedSymbols);
                    }
 
                    // TODO: do not fall back to in-proc if client is available (https://github.com/dotnet/roslyn/issues/47557)
                }
            }
        }
 
        // Couldn't effectively search in OOP. Perform the search in-proc.
        var renameLocations = await SymbolicRenameLocations.FindLocationsInCurrentProcessAsync(
            symbol, solution, options, cancellationToken).ConfigureAwait(false);
 
        return new LightweightRenameLocations(
            solution, options, renameLocations.Locations,
            renameLocations.ImplicitLocations.SelectAsArray(loc => SerializableReferenceLocation.Dehydrate(loc, cancellationToken)),
            renameLocations.ReferencedSymbols.SelectAsArray(sym => SerializableSymbolAndProjectId.Dehydrate(solution, sym, cancellationToken)));
    }
 
    public Task<ConflictResolution> ResolveConflictsAsync(ISymbol symbol, string replacementText, ImmutableArray<SymbolKey> nonConflictSymbolKeys, CancellationToken cancellationToken)
        => ConflictResolver.ResolveLightweightConflictsAsync(symbol, this, replacementText, nonConflictSymbolKeys, cancellationToken);
 
    public LightweightRenameLocations Filter(Func<DocumentId, TextSpan, bool> filter)
        => new(
            this.Solution,
            this.Options,
            this.Locations.WhereAsArray(loc => filter(loc.DocumentId, loc.Location.SourceSpan)),
            _implicitLocations.WhereAsArray(loc => filter(loc.Document, loc.Location)),
            _referencedSymbols);
}