// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Internal.Log;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis
{
public partial class Solution
{
///
/// Tracks the changes made to a project and provides the facility to get a lazily built
/// compilation for that project. As the compilation is being built, the partial results are
/// stored as well so that they can be used in the 'in progress' workspace snapshot.
///
private partial class CompilationTracker
{
private static readonly Func s_logBuildCompilationAsync = LogBuildCompilationAsync;
public ProjectState ProjectState { get; }
///
/// Access via the and methods.
///
private State _stateDoNotAccessDirectly;
// guarantees only one thread is building at a time
private readonly SemaphoreSlim _buildLock = new SemaphoreSlim(initialCount: 1);
private CompilationTracker(
ProjectState project,
State state)
{
Contract.ThrowIfNull(project);
this.ProjectState = project;
_stateDoNotAccessDirectly = state;
}
///
/// Creates a tracker for the provided project. The tracker will be in the 'empty' state
/// and will have no extra information beyond the project itself.
///
public CompilationTracker(ProjectState project)
: this(project, State.Empty)
{
}
private State ReadState()
{
return Volatile.Read(ref _stateDoNotAccessDirectly);
}
private void WriteState(State state, Solution solution)
{
if (solution._solutionServices.SupportsCachingRecoverableObjects)
{
// Allow the cache service to create a strong reference to the compilation
solution._solutionServices.CacheService.CacheObjectIfCachingEnabledForKey(this.ProjectState.Id, state, state.Compilation.GetValue());
}
Volatile.Write(ref _stateDoNotAccessDirectly, state);
}
///
/// Returns true if this tracker currently either points to a compilation, has an in-progress
/// compilation being computed, or has a skeleton reference. Note: this is simply a weak
/// statement about the tracker at this exact moment in time. Immediately after this returns
/// the tracker might change and may no longer have a final compilation (for example, if the
/// retainer let go of it) or might not have an in-progress compilation (for example, if the
/// background compiler finished with it).
///
/// Because of the above limitations, this should only be used by clients as a weak form of
/// information about the tracker. For example, a client may see that a tracker has no
/// compilation and may choose to throw it away knowing that it could be reconstructed at a
/// later point if necessary.
///
public bool HasCompilation
{
get
{
var state = this.ReadState();
return state.Compilation.HasValue || state.DeclarationOnlyCompilation != null;
}
}
///
/// Creates a new instance of the compilation info, retaining any already built
/// compilation state as the now 'old' state
///
public CompilationTracker Fork(
ProjectState newProject,
CompilationTranslationAction translate = null,
bool clone = false,
CancellationToken cancellationToken = default(CancellationToken))
{
var state = this.ReadState();
ValueSource baseCompilationSource = state.Compilation;
var baseCompilation = baseCompilationSource.GetValue(cancellationToken);
if (baseCompilation != null)
{
// We have some pre-calculated state to incrementally update
var newInProgressCompilation = clone
? baseCompilation.Clone()
: baseCompilation;
var intermediateProjects = state is InProgressState
? ((InProgressState)state).IntermediateProjects
: ImmutableArray.Create>();
var newIntermediateProjects = translate == null
? intermediateProjects
: intermediateProjects.Add(ValueTuple.Create(this.ProjectState, translate));
var newState = State.Create(newInProgressCompilation, newIntermediateProjects);
return new CompilationTracker(newProject, newState);
}
var declarationOnlyCompilation = state.DeclarationOnlyCompilation;
if (declarationOnlyCompilation != null)
{
if (translate != null)
{
var intermediateProjects =
ImmutableArray.Create>(ValueTuple.Create(this.ProjectState, translate));
return new CompilationTracker(newProject, new InProgressState(declarationOnlyCompilation, intermediateProjects));
}
return new CompilationTracker(newProject, new LightDeclarationState(declarationOnlyCompilation));
}
// We have nothing. Just make a tracker that only points to the new project. We'll have
// to rebuild its compilation from scratch if anyone asks for it.
return new CompilationTracker(newProject);
}
///
/// Creates a fork with the same final project.
///
public CompilationTracker Clone()
{
return this.Fork(this.ProjectState, clone: true);
}
public CompilationTracker FreezePartialStateWithTree(Solution solution, DocumentState docState, SyntaxTree tree, CancellationToken cancellationToken)
{
ProjectState inProgressProject;
Compilation inProgressCompilation;
GetPartialCompilationState(solution, docState.Id, out inProgressProject, out inProgressCompilation, cancellationToken);
if (!inProgressCompilation.SyntaxTrees.Contains(tree))
{
var existingTree = inProgressCompilation.SyntaxTrees.FirstOrDefault(t => t.FilePath == tree.FilePath);
if (existingTree != null)
{
inProgressCompilation = inProgressCompilation.ReplaceSyntaxTree(existingTree, tree);
inProgressProject = inProgressProject.UpdateDocument(docState, textChanged: false, recalculateDependentVersions: false);
}
else
{
inProgressCompilation = inProgressCompilation.AddSyntaxTrees(tree);
Debug.Assert(!inProgressProject.DocumentIds.Contains(docState.Id));
inProgressProject = inProgressProject.AddDocument(docState);
}
}
// The user is asking for an in progress snap. We don't want to create it and then a
// have the compilation immediately disappear. So we force it to stay around with a ConstantValueSource.
// As a policy, all partial-state projects are said to have incomplete references, since the state has no guarantees.
return new CompilationTracker(inProgressProject,
new FinalState(new ConstantValueSource(inProgressCompilation), hasCompleteReferences: false));
}
///
/// Tries to get the latest snapshot of the compilation without waiting for it to be
/// fully built. This method takes advantage of the progress side-effect produced during
/// . It will either return the already built compilation, any
/// in-progress compilation or any known old compilation in that order of preference.
/// The compilation state that is returned will have a compilation that is retained so
/// that it cannot disappear.
///
private void GetPartialCompilationState(
Solution solution,
DocumentId id,
out ProjectState inProgressProject,
out Compilation inProgressCompilation,
CancellationToken cancellationToken)
{
var state = this.ReadState();
inProgressCompilation = state.Compilation.GetValue(cancellationToken);
// check whether we can bail out quickly for typing case
var inProgressState = state as InProgressState;
// all changes left for this document is modifying the given document.
// we can use current state as it is since we will replace the document with latest document anyway.
if (inProgressState != null &&
inProgressCompilation != null &&
inProgressState.IntermediateProjects.All(t => IsTouchDocumentActionForDocument(t, id)))
{
inProgressProject = this.ProjectState;
return;
}
inProgressProject = inProgressState != null ? inProgressState.IntermediateProjects.First().Item1 : this.ProjectState;
// if we already have a final compilation we are done.
if (inProgressCompilation != null && state is FinalState)
{
return;
}
// 1) if we have an in-progress compilation use it.
// 2) If we don't, then create a simple empty compilation/project.
// 3) then, make sure that all it's p2p refs and whatnot are correct.
if (inProgressCompilation == null)
{
inProgressProject = inProgressProject.RemoveAllDocuments();
inProgressCompilation = this.CreateEmptyCompilation();
}
// first remove all project from the project and compilation.
inProgressProject = inProgressProject.WithProjectReferences(ImmutableArray.Create());
// Now add in back a consistent set of project references. For project references
// try to get either a CompilationReference or a SkeletonReference. This ensures
// that the in-progress project only reports a reference to another project if it
// could actually get a reference to that project's metadata.
var metadataReferences = new List();
var newProjectReferences = new List();
metadataReferences.AddRange(this.ProjectState.MetadataReferences);
foreach (var projectReference in this.ProjectState.ProjectReferences)
{
var referencedProject = solution.GetProject(projectReference.ProjectId);
if (referencedProject != null)
{
if (referencedProject.IsSubmission)
{
var compilation = solution.GetCompilationAsync(projectReference.ProjectId, cancellationToken).WaitAndGetResult(cancellationToken);
inProgressCompilation = inProgressCompilation.WithScriptCompilationInfo(inProgressCompilation.ScriptCompilationInfo.WithPreviousScriptCompilation(compilation));
}
else
{
// get the latest metadata for the partial compilation of the referenced project.
var metadata = solution.GetPartialMetadataReference(projectReference, this.ProjectState, cancellationToken);
if (metadata == null)
{
// if we failed to get the metadata, check to see if we previously had existing metadata and reuse it instead.
metadata = inProgressCompilation.ExternalReferences.FirstOrDefault(r => solution.GetProjectId(r) == projectReference.ProjectId);
}
if (metadata != null)
{
newProjectReferences.Add(projectReference);
metadataReferences.Add(metadata);
}
}
}
}
inProgressProject = inProgressProject.AddProjectReferences(newProjectReferences);
if (!Enumerable.SequenceEqual(inProgressCompilation.ExternalReferences, metadataReferences))
{
inProgressCompilation = inProgressCompilation.WithReferences(metadataReferences);
}
}
private static bool IsTouchDocumentActionForDocument(ValueTuple tuple, DocumentId id)
{
var touchDocumentAction = tuple.Item2 as CompilationTranslationAction.TouchDocumentAction;
return touchDocumentAction != null && touchDocumentAction.DocumentId == id;
}
///
/// Gets the final compilation if it is available.
///
public bool TryGetCompilation(out Compilation compilation)
{
var state = this.ReadState();
return state.FinalCompilation.TryGetValue(out compilation) && compilation != null;
}
public Task GetCompilationAsync(Solution solution, CancellationToken cancellationToken)
{
Compilation compilation;
if (this.TryGetCompilation(out compilation))
{
// PERF: This is a hot code path and Task isn't cheap,
// so cache the completed tasks to reduce allocations. We also
// need to avoid keeping a strong reference to the Compilation,
// so use a ConditionalWeakTable.
return SpecializedTasks.FromResult(compilation);
}
else
{
return GetOrBuildCompilationInfoAsync(solution, lockGate: true, cancellationToken: cancellationToken)
.ContinueWith(t => t.Result.Compilation, cancellationToken, TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Default);
}
}
public Task HasCompleteReferencesAsync(Solution solution, CancellationToken cancellationToken)
{
var state = this.ReadState();
if (state.HasCompleteReferences.HasValue)
{
return Task.FromResult(state.HasCompleteReferences.Value);
}
else
{
return GetOrBuildCompilationInfoAsync(solution, lockGate: true, cancellationToken: cancellationToken)
.ContinueWith(t => t.Result.HasCompleteReferences, cancellationToken, TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Default);
}
}
private static string LogBuildCompilationAsync(ProjectState state)
{
return string.Join(",", state.AssemblyName, state.DocumentIds.Count);
}
private async Task GetOrBuildDeclarationCompilationAsync(Solution solution, CancellationToken cancellationToken)
{
try
{
cancellationToken.ThrowIfCancellationRequested();
using (await _buildLock.DisposableWaitAsync(cancellationToken).ConfigureAwait(false))
{
var state = this.ReadState();
// we are already in the final stage. just return it.
var compilation = state.FinalCompilation.GetValue(cancellationToken);
if (compilation != null)
{
return compilation;
}
compilation = state.Compilation.GetValue(cancellationToken);
if (compilation == null)
{
// let's see whether we have declaration only compilation
if (state.DeclarationOnlyCompilation != null)
{
// okay, move to full declaration state. do this so that declaration only compilation never
// realize symbols.
var declarationOnlyCompilation = state.DeclarationOnlyCompilation.Clone();
this.WriteState(new FullDeclarationState(declarationOnlyCompilation), solution);
return declarationOnlyCompilation;
}
// We've got nothing. Build it from scratch :(
return await BuildDeclarationCompilationFromScratchAsync(solution, cancellationToken).ConfigureAwait(false);
}
else if (state is FullDeclarationState)
{
// we have full declaration, just use it.
return state.Compilation.GetValue(cancellationToken);
}
else if (state is InProgressState)
{
// We have an in progress compilation. Build off of that.
return await BuildDeclarationCompilationFromInProgressAsync(solution, state as InProgressState, compilation, cancellationToken).ConfigureAwait(false);
}
else
{
throw ExceptionUtilities.Unreachable;
}
}
}
catch (Exception e) when (FatalError.ReportUnlessCanceled(e))
{
throw ExceptionUtilities.Unreachable;
}
}
private async Task GetOrBuildCompilationInfoAsync(
Solution solution,
bool lockGate,
CancellationToken cancellationToken)
{
try
{
using (Logger.LogBlock(FunctionId.Workspace_Project_CompilationTracker_BuildCompilationAsync,
s_logBuildCompilationAsync, this.ProjectState, cancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
var state = this.ReadState();
// Try to get the built compilation. If it exists, then we can just return that.
var finalCompilation = state.FinalCompilation.GetValue(cancellationToken);
if (finalCompilation != null)
{
return new CompilationInfo(finalCompilation, hasCompleteReferences: state.HasCompleteReferences.Value);
}
// Otherwise, we actually have to build it. Ensure that only one thread is trying to
// build this compilation at a time.
if (lockGate)
{
using (await _buildLock.DisposableWaitAsync(cancellationToken).ConfigureAwait(false))
{
return await BuildCompilationInfoAsync(solution, cancellationToken).ConfigureAwait(false);
}
}
else
{
return await BuildCompilationInfoAsync(solution, cancellationToken).ConfigureAwait(false);
}
}
}
catch (Exception e) when (FatalError.ReportUnlessCanceled(e))
{
throw ExceptionUtilities.Unreachable;
}
}
///
/// Builds the compilation matching the project state. In the process of building, also
/// produce in progress snapshots that can be accessed from other threads.
///
private Task BuildCompilationInfoAsync(
Solution solution,
CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
var state = this.ReadState();
// if we already have a compilation, we must be already done! This can happen if two
// threads were waiting to build, and we came in after the other succeeded.
var compilation = state.FinalCompilation.GetValue(cancellationToken);
if (compilation != null)
{
return Task.FromResult(new CompilationInfo(compilation, state.HasCompleteReferences.Value));
}
compilation = state.Compilation.GetValue(cancellationToken);
if (compilation == null)
{
// this can happen if compilation is already kicked out from the cache.
// check whether the state we have support declaration only compilation
if (state.DeclarationOnlyCompilation != null)
{
// we have declaration only compilation. build final one from it.
return FinalizeCompilationAsync(solution, state.DeclarationOnlyCompilation, cancellationToken);
}
// We've got nothing. Build it from scratch :(
return BuildCompilationInfoFromScratchAsync(solution, state, cancellationToken);
}
else if (state is FullDeclarationState)
{
// We have a declaration compilation, use it to reconstruct the final compilation
return this.FinalizeCompilationAsync(solution, compilation, cancellationToken);
}
else if (state is InProgressState)
{
// We have an in progress compilation. Build off of that.
return BuildFinalStateFromInProgressStateAsync(solution, state as InProgressState, compilation, cancellationToken);
}
else
{
throw ExceptionUtilities.Unreachable;
}
}
private async Task BuildCompilationInfoFromScratchAsync(
Solution solution, State state, CancellationToken cancellationToken)
{
try
{
var compilation = await BuildDeclarationCompilationFromScratchAsync(solution, cancellationToken).ConfigureAwait(false);
return await FinalizeCompilationAsync(solution, compilation, cancellationToken).ConfigureAwait(false);
}
catch (Exception e) when (FatalError.ReportUnlessCanceled(e))
{
throw ExceptionUtilities.Unreachable;
}
}
private async Task BuildDeclarationCompilationFromScratchAsync(
Solution solution, CancellationToken cancellationToken)
{
try
{
var compilation = CreateEmptyCompilation();
foreach (var document in this.ProjectState.OrderedDocumentStates)
{
cancellationToken.ThrowIfCancellationRequested();
compilation = compilation.AddSyntaxTrees(await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false));
}
this.WriteState(new FullDeclarationState(compilation), solution);
return compilation;
}
catch (Exception e) when (FatalError.ReportUnlessCanceled(e))
{
throw ExceptionUtilities.Unreachable;
}
}
private Compilation CreateEmptyCompilation()
{
var compilationFactory = this.ProjectState.LanguageServices.GetService();
if (this.ProjectState.IsSubmission)
{
return compilationFactory.CreateSubmissionCompilation(
this.ProjectState.AssemblyName,
this.ProjectState.CompilationOptions,
this.ProjectState.HostObjectType);
}
else
{
return compilationFactory.CreateCompilation(
this.ProjectState.AssemblyName,
this.ProjectState.CompilationOptions);
}
}
private async Task BuildFinalStateFromInProgressStateAsync(
Solution solution, InProgressState state, Compilation inProgressCompilation, CancellationToken cancellationToken)
{
try
{
var compilation = await BuildDeclarationCompilationFromInProgressAsync(solution, state, inProgressCompilation, cancellationToken).ConfigureAwait(false);
return await FinalizeCompilationAsync(solution, compilation, cancellationToken).ConfigureAwait(false);
}
catch (Exception e) when (FatalError.ReportUnlessCanceled(e))
{
throw ExceptionUtilities.Unreachable;
}
}
private async Task BuildDeclarationCompilationFromInProgressAsync(
Solution solution, InProgressState state, Compilation inProgressCompilation, CancellationToken cancellationToken)
{
try
{
Contract.Requires(inProgressCompilation != null);
var intermediateProjects = state.IntermediateProjects;
while (intermediateProjects.Length > 0)
{
cancellationToken.ThrowIfCancellationRequested();
var intermediateProject = intermediateProjects[0];
var inProgressProject = intermediateProject.Item1;
var action = intermediateProject.Item2;
inProgressCompilation = await action.InvokeAsync(inProgressCompilation, cancellationToken).ConfigureAwait(false);
intermediateProjects = intermediateProjects.RemoveAt(0);
this.WriteState(State.Create(inProgressCompilation, intermediateProjects), solution);
}
return inProgressCompilation;
}
catch (Exception e) when (FatalError.ReportUnlessCanceled(e))
{
throw ExceptionUtilities.Unreachable;
}
}
private struct CompilationInfo
{
public Compilation Compilation { get; }
public bool HasCompleteReferences { get; }
public CompilationInfo(Compilation compilation, bool hasCompleteReferences)
{
this.Compilation = compilation;
this.HasCompleteReferences = hasCompleteReferences;
}
}
///
/// Add all appropriate references to the compilation and set it as our final compilation
/// state.
///
private async Task FinalizeCompilationAsync(
Solution solution,
Compilation compilation,
CancellationToken cancellationToken)
{
try
{
bool hasCompleteReferences = true;
var newReferences = new List();
newReferences.AddRange(this.ProjectState.MetadataReferences);
foreach (var projectReference in this.ProjectState.ProjectReferences)
{
var referencedProject = solution.GetProject(projectReference.ProjectId);
// Even though we're creating a final compilation (vs. an in progress compilation),
// it's possible that the target project has been removed.
if (referencedProject != null)
{
// If both projects are submissions, we'll count this as a previous submission link
// instead of a regular metadata reference
if (referencedProject.IsSubmission)
{
// if the referenced project is a submission project must be a submission as well:
Debug.Assert(this.ProjectState.IsSubmission);
var previousSubmissionCompilation =
await solution.GetCompilationAsync(projectReference.ProjectId, cancellationToken).ConfigureAwait(false);
compilation = compilation.WithScriptCompilationInfo(
compilation.ScriptCompilationInfo.WithPreviousScriptCompilation(previousSubmissionCompilation));
}
else
{
var metadataReference = await solution.GetMetadataReferenceAsync(
projectReference, this.ProjectState, cancellationToken).ConfigureAwait(false);
// A reference can fail to be created if a skeleton assembly could not be constructed.
if (metadataReference != null)
{
newReferences.Add(metadataReference);
}
else
{
hasCompleteReferences = false;
}
}
}
}
if (!Enumerable.SequenceEqual(compilation.ExternalReferences, newReferences))
{
compilation = compilation.WithReferences(newReferences);
}
this.WriteState(new FinalState(State.CreateValueSource(compilation, solution.Services), hasCompleteReferences), solution);
return new CompilationInfo(compilation, hasCompleteReferences);
}
catch (Exception e) when (FatalError.ReportUnlessCanceled(e))
{
throw ExceptionUtilities.Unreachable;
}
}
///
/// Get a metadata reference to this compilation info's compilation with respect to
/// another project. For cross language references produce a skeletal assembly. If the
/// compilation is not available, it is built. If a skeletal assembly reference is
/// needed and does not exist, it is also built.
///
public async Task GetMetadataReferenceAsync(
Solution solution,
ProjectState fromProject,
ProjectReference projectReference,
CancellationToken cancellationToken)
{
try
{
Compilation compilation;
// if we already have the compilation and its right kind then use it.
if (this.ProjectState.LanguageServices == fromProject.LanguageServices
&& this.TryGetCompilation(out compilation))
{
return compilation.ToMetadataReference(projectReference.Aliases, projectReference.EmbedInteropTypes);
}
// If same language then we can wrap the other project's compilation into a compilation reference
if (this.ProjectState.LanguageServices == fromProject.LanguageServices)
{
// otherwise, base it off the compilation by building it first.
compilation = await this.GetCompilationAsync(solution, cancellationToken).ConfigureAwait(false);
return compilation.ToMetadataReference(projectReference.Aliases, projectReference.EmbedInteropTypes);
}
else
{
// otherwise get a metadata only image reference that is built by emitting the metadata from the referenced project's compilation and re-importing it.
return await this.GetMetadataOnlyImageReferenceAsync(solution, projectReference, cancellationToken).ConfigureAwait(false);
}
}
catch (Exception e) when (FatalError.ReportUnlessCanceled(e))
{
throw ExceptionUtilities.Unreachable;
}
}
///
/// Attempts to get (without waiting) a metadata reference to a possibly in progress
/// compilation. Actual compilation references are preferred over skeletal assembly
/// references. Could potentially return null if nothing can be provided.
///
public MetadataReference GetPartialMetadataReference(Solution solution, ProjectState fromProject, ProjectReference projectReference, CancellationToken cancellationToken)
{
var state = this.ReadState();
// get compilation in any state it happens to be in right now.
Compilation compilation;
if (state.Compilation.TryGetValue(out compilation)
&& compilation != null
&& this.ProjectState.LanguageServices == fromProject.LanguageServices)
{
// if we have a compilation and its the correct language, use a simple compilation reference
return compilation.ToMetadataReference(projectReference.Aliases, projectReference.EmbedInteropTypes);
}
return null;
}
///
/// Gets a metadata reference to the metadata-only-image corresponding to the compilation.
///
private async Task GetMetadataOnlyImageReferenceAsync(
Solution solution, ProjectReference projectReference, CancellationToken cancellationToken)
{
try
{
using (Logger.LogBlock(FunctionId.Workspace_SkeletonAssembly_GetMetadataOnlyImage, cancellationToken))
{
var projectId = this.ProjectState.Id;
var version = await this.GetDependentSemanticVersionAsync(solution, cancellationToken).ConfigureAwait(false);
// get or build compilation up to declaration state. this compilation will be used to provide live xml doc comment
var declarationCompilation = await this.GetOrBuildDeclarationCompilationAsync(solution, cancellationToken: cancellationToken).ConfigureAwait(false);
MetadataReference reference;
if (!MetadataOnlyReference.TryGetReference(solution, projectReference, declarationCompilation, version, out reference))
{
// using async build lock so we don't get multiple consumers attempting to build metadata-only images for the same compilation.
using (await _buildLock.DisposableWaitAsync(cancellationToken).ConfigureAwait(false))
{
// okay, we still don't have one. bring the compilation to final state since we are going to use it to create skeleton assembly
var compilationInfo = await this.GetOrBuildCompilationInfoAsync(solution, lockGate: false, cancellationToken: cancellationToken).ConfigureAwait(false);
reference = MetadataOnlyReference.GetOrBuildReference(solution, projectReference, compilationInfo.Compilation, version, cancellationToken);
}
}
return reference;
}
}
catch (Exception e) when (FatalError.ReportUnlessCanceled(e))
{
throw ExceptionUtilities.Unreachable;
}
}
///
/// check whether the compilation contains any declaration symbol from syntax trees with given name
///
public bool? ContainsSymbolsWithNameFromDeclarationOnlyCompilation(Func predicate, SymbolFilter filter, CancellationToken cancellationToken)
{
var state = this.ReadState();
if (state.DeclarationOnlyCompilation == null)
{
return null;
}
// DO NOT expose declaration only compilation to outside since it can be held alive long time, we don't want to create any symbol from the declaration only compilation.
return state.DeclarationOnlyCompilation.ContainsSymbolsWithName(predicate, filter, cancellationToken);
}
///
/// get all syntax trees that contain declaration node with the given name
///
public IEnumerable GetSyntaxTreesWithNameFromDeclarationOnlyCompilation(Func predicate, SymbolFilter filter, CancellationToken cancellationToken)
{
var state = this.ReadState();
if (state.DeclarationOnlyCompilation == null)
{
return null;
}
// DO NOT expose declaration only compilation to outside since it can be held alive long time, we don't want to create any symbol from the declaration only compilation.
// use cloned compilation since this will cause symbols to be created.
var clone = state.DeclarationOnlyCompilation.Clone();
return clone.GetSymbolsWithName(predicate, filter, cancellationToken).SelectMany(s => s.DeclaringSyntaxReferences.Select(r => r.SyntaxTree));
}
#region Versions
// Dependent Versions are stored on compilation tracker so they are more likely to survive when unrelated solution branching occurs.
private AsyncLazy _lazyDependentVersion;
private AsyncLazy _lazyDependentSemanticVersion;
public async Task GetDependentVersionAsync(Solution solution, CancellationToken cancellationToken)
{
if (_lazyDependentVersion == null)
{
// note: solution is captured here, but it will go away once GetValueAsync executes.
Interlocked.CompareExchange(ref _lazyDependentVersion, new AsyncLazy(c => ComputeDependentVersionAsync(solution, c), cacheResult: true), null);
}
return await _lazyDependentVersion.GetValueAsync(cancellationToken).ConfigureAwait(false);
}
private async Task ComputeDependentVersionAsync(Solution solution, CancellationToken cancellationToken)
{
var projectState = this.ProjectState;
var projVersion = projectState.Version;
var docVersion = await projectState.GetLatestDocumentVersionAsync(cancellationToken).ConfigureAwait(false);
var version = docVersion.GetNewerVersion(projVersion);
foreach (var dependentProjectReference in projectState.ProjectReferences)
{
cancellationToken.ThrowIfCancellationRequested();
if (solution.ContainsProject(dependentProjectReference.ProjectId))
{
var dependentProjectVersion = await solution.GetDependentVersionAsync(dependentProjectReference.ProjectId, cancellationToken).ConfigureAwait(false);
version = dependentProjectVersion.GetNewerVersion(version);
}
}
return version;
}
public async Task GetDependentSemanticVersionAsync(Solution solution, CancellationToken cancellationToken)
{
if (_lazyDependentSemanticVersion == null)
{
// note: solution is captured here, but it will go away once GetValueAsync executes.
Interlocked.CompareExchange(ref _lazyDependentSemanticVersion, new AsyncLazy(c => ComputeDependentSemanticVersionAsync(solution, c), cacheResult: true), null);
}
return await _lazyDependentSemanticVersion.GetValueAsync(cancellationToken).ConfigureAwait(false);
}
private async Task ComputeDependentSemanticVersionAsync(Solution solution, CancellationToken cancellationToken)
{
var projectState = this.ProjectState;
var version = await projectState.GetSemanticVersionAsync(cancellationToken).ConfigureAwait(false);
foreach (var dependentProjectReference in projectState.ProjectReferences)
{
cancellationToken.ThrowIfCancellationRequested();
if (solution.ContainsProject(dependentProjectReference.ProjectId))
{
var dependentProjectVersion = await solution.GetDependentSemanticVersionAsync(dependentProjectReference.ProjectId, cancellationToken).ConfigureAwait(false);
version = dependentProjectVersion.GetNewerVersion(version);
}
}
return version;
}
#endregion
}
}
}