private void WriteState(State state, SolutionState 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); }
/// <summary> /// Gets a metadata reference to the metadata-only-image corresponding to the compilation. /// </summary> private async Task <MetadataReference> GetMetadataOnlyImageReferenceAsync( SolutionState solution, ProjectReference projectReference, CancellationToken cancellationToken) { try { using (Logger.LogBlock(FunctionId.Workspace_SkeletonAssembly_GetMetadataOnlyImage, cancellationToken)) { 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); solution.Workspace.LogTestMessage($"Looking for a cached skeleton assembly for {projectReference.ProjectId} before taking the lock..."); if (!MetadataOnlyReference.TryGetReference(solution, projectReference, declarationCompilation, version, out var 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)) { solution.Workspace.LogTestMessage($"Build lock taken for {ProjectState.Id}..."); // 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); } } else { solution.Workspace.LogTestMessage($"Reusing the already cached skeleton assembly for {projectReference.ProjectId}"); } return(reference); } } catch (Exception e) when(FatalError.ReportUnlessCanceled(e)) { throw ExceptionUtilities.Unreachable; } }
public async Task <Compilation> GetCompilationAsync(SolutionState solution, CancellationToken cancellationToken) { // Fast path if we've definitely already done this before if (_compilationWithReplacement != null) { return(_compilationWithReplacement); } var underlyingCompilation = await _underlyingTracker.GetCompilationAsync(solution, cancellationToken).ConfigureAwait(false); var underlyingSourceGeneratedDocuments = await _underlyingTracker.GetSourceGeneratedDocumentStatesAsync(solution, cancellationToken).ConfigureAwait(false); underlyingSourceGeneratedDocuments.TryGetState(_replacedGeneratedDocumentState.Id, out var existingState); Compilation newCompilation; var newSyntaxTree = await _replacedGeneratedDocumentState.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); if (existingState != null) { // The generated file still exists in the underlying compilation, but the contents may not match the open file if the open file // is stale. Replace the syntax tree so we have a tree that matches the text. var existingSyntaxTree = await existingState.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); newCompilation = underlyingCompilation.ReplaceSyntaxTree(existingSyntaxTree, newSyntaxTree); } else { // The existing output no longer exists in the underlying compilation. This could happen if the user made // an edit which would cause this file to no longer exist, but they're still operating on an open representation // of that file. To ensure that this snapshot is still usable, we'll just add this document back in. This is not a // semantically correct operation, but working on stale snapshots never has that guarantee. newCompilation = underlyingCompilation.AddSyntaxTrees(newSyntaxTree); } Interlocked.CompareExchange(ref _compilationWithReplacement, newCompilation, null); return(_compilationWithReplacement); }
private async Task <VersionStamp> ComputeDependentVersionAsync(SolutionState 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); }
internal static bool TryGetReference( SolutionState solution, ProjectReference projectReference, Compilation finalOrDeclarationCompilation, VersionStamp version, out MetadataReference reference) { // if we have one from snapshot cache, use it. it will make sure same compilation will get same metadata reference always. MetadataOnlyReferenceSet referenceSet; if (s_snapshotCache.TryGetValue(finalOrDeclarationCompilation, out referenceSet)) { solution.Workspace.LogTestMessage($"Found already cached metadata in {nameof(s_snapshotCache)} for the exact compilation"); reference = referenceSet.GetMetadataReference(finalOrDeclarationCompilation, projectReference.Aliases, projectReference.EmbedInteropTypes); return(true); } // okay, now use version based cache that can live multiple compilation as long as there is no semantic changes. // get one for the branch if (TryGetReferenceFromBranch(solution.BranchId, projectReference, finalOrDeclarationCompilation, version, out reference)) { solution.Workspace.LogTestMessage($"Found already cached metadata for the branch and version {version}"); return(true); } // see whether we can use primary branch one var primaryBranchId = solution.Workspace.PrimaryBranchId; if (solution.BranchId != primaryBranchId && TryGetReferenceFromBranch(primaryBranchId, projectReference, finalOrDeclarationCompilation, version, out reference)) { solution.Workspace.LogTestMessage($"Found already cached metadata for the primary branch and version {version}"); return(true); } // noop, we don't have any reference = null; return(false); }
private async Task <bool> ComputeHasSuccessfullyLoadedTransitivelyAsync( SolutionState solution, bool hasSuccessfullyLoaded, CancellationToken cancellationToken) { if (!hasSuccessfullyLoaded) { return(false); } foreach (var projectReference in this.ProjectState.ProjectReferences) { var project = solution.GetProjectState(projectReference.ProjectId); if (project == null) { return(false); } if (!await solution.HasSuccessfullyLoadedAsync(project, cancellationToken).ConfigureAwait(false)) { return(false); } } return(true); }
private async Task <Compilation> BuildDeclarationCompilationFromInProgressAsync( SolutionState solution, InProgressState state, Compilation inProgressCompilation, CancellationToken cancellationToken) { try { var intermediateProjects = state.IntermediateProjects; while (intermediateProjects.Length > 0) { cancellationToken.ThrowIfCancellationRequested(); inProgressCompilation = await intermediateProjects[0].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; } }
/// <summary> /// 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. /// </summary> public async Task <MetadataReference> GetMetadataReferenceAsync( SolutionState 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; } }
private async Task <Compilation> GetCompilationSlowAsync(SolutionState solution, CancellationToken cancellationToken) { var compilationInfo = await GetOrBuildCompilationInfoAsync(solution, lockGate : true, cancellationToken : cancellationToken).ConfigureAwait(false); return(compilationInfo.Compilation); }
private Solution(SolutionState state) { _projectIdToProjectMap = ImmutableHashMap <ProjectId, Project> .Empty; _state = state; }
private static CompilationTracker CreateCompilationTracker(ProjectId projectId, SolutionState solution) { return new CompilationTracker(solution.GetProjectState(projectId)); }
public Task <bool> HasSuccessfullyLoadedAsync(SolutionState solution, CancellationToken cancellationToken) { return(_underlyingTracker.HasSuccessfullyLoadedAsync(solution, cancellationToken)); }
internal static MetadataReference GetOrBuildReference( SolutionState solution, ProjectReference projectReference, Compilation finalCompilation, VersionStamp version, CancellationToken cancellationToken) { solution.Workspace.LogTestMessage($"Looking to see if we already have a skeleton assembly for {projectReference.ProjectId} before we build one..."); if (TryGetReference(solution, projectReference, finalCompilation, version, out var reference)) { solution.Workspace.LogTestMessage($"A reference was found {projectReference.ProjectId} so we're skipping the build."); return(reference); } // okay, we don't have one. so create one now. // first, prepare image // * NOTE * image is cancellable, do not create it inside of conditional weak table. var service = solution.Workspace.Services.GetService <ITemporaryStorageService>(); var image = MetadataOnlyImage.Create(solution.Workspace, service, finalCompilation, cancellationToken); if (image.IsEmpty) { // unfortunately, we couldn't create one. do best effort if (TryGetReference(solution, projectReference, finalCompilation, VersionStamp.Default, out reference)) { solution.Workspace.LogTestMessage($"We failed to create metadata so we're using the one we just found from an earlier version."); // we have one from previous compilation!!, it might be out-of-date big time, but better than nothing. // re-use it return(reference); } } // okay, proceed with whatever image we have // now, remove existing set var mapFromBranch = s_cache.GetValue(solution.BranchId, s_createReferenceSetMap); mapFromBranch.Remove(projectReference.ProjectId); // create new one var newReferenceSet = new MetadataOnlyReferenceSet(version, image); var referenceSet = s_snapshotCache.GetValue(finalCompilation, _ => newReferenceSet); if (newReferenceSet != referenceSet) { // someone else has beaten us. // let image go eagerly. otherwise, finalizer in temporary storage will take care of it image.Cleanup(); // return new reference return(referenceSet.GetMetadataReference(finalCompilation, projectReference.Aliases, projectReference.EmbedInteropTypes)); } else { solution.Workspace.LogTestMessage($"Successfully stored the metadata generated for {projectReference.ProjectId}"); } // record it to version based cache as well. snapshot cache always has a higher priority. we don't need to check returned set here // since snapshot based cache will take care of same compilation for us. mapFromBranch.GetValue(projectReference.ProjectId, _ => referenceSet); // return new reference return(referenceSet.GetMetadataReference(finalCompilation, projectReference.Aliases, projectReference.EmbedInteropTypes)); }
public async Task<VersionStamp> GetDependentSemanticVersionAsync(SolutionState 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<VersionStamp>(c => ComputeDependentSemanticVersionAsync(solution, c), cacheResult: true), null); } return await _lazyDependentSemanticVersion.GetValueAsync(cancellationToken).ConfigureAwait(false); }
/// <summary> /// Gets a metadata reference to the metadata-only-image corresponding to the compilation. /// </summary> private async Task<MetadataReference> GetMetadataOnlyImageReferenceAsync( SolutionState solution, ProjectReference projectReference, CancellationToken cancellationToken) { try { using (Logger.LogBlock(FunctionId.Workspace_SkeletonAssembly_GetMetadataOnlyImage, cancellationToken)) { 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); solution.Workspace.LogTestMessage($"Looking for a cached skeleton assembly for {projectReference.ProjectId} before taking the lock..."); if (!MetadataOnlyReference.TryGetReference(solution, projectReference, declarationCompilation, version, out var 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)) { solution.Workspace.LogTestMessage($"Build lock taken for {ProjectState.Id}..."); // 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); } } else { solution.Workspace.LogTestMessage($"Reusing the already cached skeleton assembly for {projectReference.ProjectId}"); } return reference; } } catch (Exception e) when (FatalError.ReportUnlessCanceled(e)) { throw ExceptionUtilities.Unreachable; } }
private async Task<CompilationInfo> GetOrBuildCompilationInfoAsync( SolutionState 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, state.HasSuccessfullyLoadedTransitively.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; } }
private async Task<Compilation> GetOrBuildDeclarationCompilationAsync(SolutionState 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; } }
public Task<Compilation> GetCompilationAsync(SolutionState solution, CancellationToken cancellationToken) { if (this.TryGetCompilation(out var compilation)) { // PERF: This is a hot code path and Task<TResult> 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); } }
/// <summary> /// 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 /// <see cref="BuildCompilationInfoAsync(SolutionState, CancellationToken)"/>. 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. /// </summary> private void GetPartialCompilationState( SolutionState 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<ProjectReference>()); // 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<MetadataReference>(); var newProjectReferences = new List<ProjectReference>(); metadataReferences.AddRange(this.ProjectState.MetadataReferences); var metadataReferenceToProjectId = new Dictionary<MetadataReference, ProjectId>(); foreach (var projectReference in this.ProjectState.ProjectReferences) { var referencedProject = solution.GetProjectState(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. var inProgressCompilationNotRef = inProgressCompilation; metadata = inProgressCompilationNotRef.ExternalReferences.FirstOrDefault( r => solution.GetProjectState(inProgressCompilationNotRef.GetAssemblyOrModuleSymbol(r) as IAssemblySymbol, cancellationToken)?.Id == projectReference.ProjectId); } if (metadata != null) { newProjectReferences.Add(projectReference); metadataReferences.Add(metadata); metadataReferenceToProjectId.Add(metadata, projectReference.ProjectId); } } } } inProgressProject = inProgressProject.AddProjectReferences(newProjectReferences); inProgressCompilation = UpdateCompilationWithNewReferencesAndRecordAssemblySymbols(inProgressCompilation, metadataReferences, metadataReferenceToProjectId); }
public CompilationTracker FreezePartialStateWithTree(SolutionState solution, DocumentState docState, SyntaxTree tree, CancellationToken cancellationToken) { GetPartialCompilationState(solution, docState.Id, out var inProgressProject, out var 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 // 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<Compilation>(inProgressCompilation), hasSuccessfullyLoadedTransitively: false)); }
private async Task <Checksum> ComputeDependentChecksumAsync(SolutionState solution, CancellationToken cancellationToken) => Checksum.Create( await _underlyingTracker.GetDependentChecksumAsync(solution, cancellationToken).ConfigureAwait(false), await _replacedGeneratedDocumentState.GetChecksumAsync(cancellationToken).ConfigureAwait(false));
public ICompilationTracker FreezePartialStateWithTree(SolutionState solution, DocumentState docState, SyntaxTree tree, CancellationToken cancellationToken) { // Because we override SourceGeneratedDocument.WithFrozenPartialSemantics directly, we shouldn't be able to get here. throw ExceptionUtilities.Unreachable; }
public Task <VersionStamp> GetDependentVersionAsync(SolutionState solution, CancellationToken cancellationToken) { return(_underlyingTracker.GetDependentVersionAsync(solution, cancellationToken)); }
/// <summary> /// Attempts to get (without waiting) a metadata reference to a possibly in progress /// compilation. Only actual compilation references are returned. Could potentially /// return null if nothing can be provided. /// </summary> public MetadataReference GetPartialMetadataReference(SolutionState solution, ProjectState fromProject, ProjectReference projectReference, CancellationToken cancellationToken) { var state = this.ReadState(); // get compilation in any state it happens to be in right now. if (state.Compilation.TryGetValue(out var 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; }
/// <summary> /// Builds the compilation matching the project state. In the process of building, also /// produce in progress snapshots that can be accessed from other threads. /// </summary> private Task<CompilationInfo> BuildCompilationInfoAsync( SolutionState 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.HasSuccessfullyLoadedTransitively.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; } }
public Task<bool> HasSuccessfullyLoadedAsync(SolutionState solution, CancellationToken cancellationToken) { var state = this.ReadState(); if (state.HasSuccessfullyLoadedTransitively.HasValue) { return state.HasSuccessfullyLoadedTransitively.Value ? SpecializedTasks.True : SpecializedTasks.False; } else { return GetOrBuildCompilationInfoAsync(solution, lockGate: true, cancellationToken: cancellationToken) .ContinueWith(t => t.Result.HasSuccessfullyLoadedTransitively, cancellationToken, TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Default); } }
private async Task<Compilation> BuildDeclarationCompilationFromScratchAsync( SolutionState 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 async Task<VersionStamp> ComputeDependentSemanticVersionAsync(SolutionState 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; }
internal static bool TryGetReference( SolutionState solution, ProjectReference projectReference, Compilation finalOrDeclarationCompilation, VersionStamp version, out MetadataReference reference) { // if we have one from snapshot cache, use it. it will make sure same compilation will get same metadata reference always. if (s_snapshotCache.TryGetValue(finalOrDeclarationCompilation, out var referenceSet)) { solution.Workspace.LogTestMessage($"Found already cached metadata in {nameof(s_snapshotCache)} for the exact compilation"); reference = referenceSet.GetMetadataReference(finalOrDeclarationCompilation, projectReference.Aliases, projectReference.EmbedInteropTypes); return true; } // okay, now use version based cache that can live multiple compilation as long as there is no semantic changes. // get one for the branch if (TryGetReferenceFromBranch(solution.BranchId, projectReference, finalOrDeclarationCompilation, version, out reference)) { solution.Workspace.LogTestMessage($"Found already cached metadata for the branch and version {version}"); return true; } // see whether we can use primary branch one var primaryBranchId = solution.Workspace.PrimaryBranchId; if (solution.BranchId != primaryBranchId && TryGetReferenceFromBranch(primaryBranchId, projectReference, finalOrDeclarationCompilation, version, out reference)) { solution.Workspace.LogTestMessage($"Found already cached metadata for the primary branch and version {version}"); return true; } // noop, we don't have any reference = null; return false; }
private async Task<CompilationInfo> BuildFinalStateFromInProgressStateAsync( SolutionState 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; } }
public async ValueTask <TextDocumentStates <SourceGeneratedDocumentState> > GetSourceGeneratedDocumentStatesAsync(SolutionState solution, CancellationToken cancellationToken) { var underlyingGeneratedDocumentStates = await _underlyingTracker.GetSourceGeneratedDocumentStatesAsync(solution, cancellationToken).ConfigureAwait(false); if (underlyingGeneratedDocumentStates.Contains(_replacedGeneratedDocumentState.Id)) { // The generated file still exists in the underlying compilation, but the contents may not match the open file if the open file // is stale. Replace the syntax tree so we have a tree that matches the text. return(underlyingGeneratedDocumentStates.SetState(_replacedGeneratedDocumentState.Id, _replacedGeneratedDocumentState)); } else { // The generated output no longer exists in the underlying compilation. This could happen if the user made // an edit which would cause this file to no longer exist, but they're still operating on an open representation // of that file. To ensure that this snapshot is still usable, we'll just add this document back in. This is not a // semantically correct operation, but working on stale snapshots never has that guarantee. return(underlyingGeneratedDocumentStates.AddRange(ImmutableArray.Create(_replacedGeneratedDocumentState))); } }
internal static MetadataReference GetOrBuildReference( SolutionState solution, ProjectReference projectReference, Compilation finalCompilation, VersionStamp version, CancellationToken cancellationToken) { solution.Workspace.LogTestMessage($"Looking to see if we already have a skeleton assembly for {projectReference.ProjectId} before we build one..."); if (TryGetReference(solution, projectReference, finalCompilation, version, out var reference)) { solution.Workspace.LogTestMessage($"A reference was found {projectReference.ProjectId} so we're skipping the build."); return reference; } // okay, we don't have one. so create one now. // first, prepare image // * NOTE * image is cancellable, do not create it inside of conditional weak table. var service = solution.Workspace.Services.GetService<ITemporaryStorageService>(); var image = MetadataOnlyImage.Create(solution.Workspace, service, finalCompilation, cancellationToken); if (image.IsEmpty) { // unfortunately, we couldn't create one. do best effort if (TryGetReference(solution, projectReference, finalCompilation, VersionStamp.Default, out reference)) { solution.Workspace.LogTestMessage($"We failed to create metadata so we're using the one we just found from an earlier version."); // we have one from previous compilation!!, it might be out-of-date big time, but better than nothing. // re-use it return reference; } } // okay, proceed with whatever image we have // now, remove existing set var mapFromBranch = s_cache.GetValue(solution.BranchId, s_createReferenceSetMap); mapFromBranch.Remove(projectReference.ProjectId); // create new one var newReferenceSet = new MetadataOnlyReferenceSet(version, image); var referenceSet = s_snapshotCache.GetValue(finalCompilation, _ => newReferenceSet); if (newReferenceSet != referenceSet) { // someone else has beaten us. // let image go eagerly. otherwise, finalizer in temporary storage will take care of it image.Cleanup(); // return new reference return referenceSet.GetMetadataReference(finalCompilation, projectReference.Aliases, projectReference.EmbedInteropTypes); } else { solution.Workspace.LogTestMessage($"Successfully stored the metadata generated for {projectReference.ProjectId}"); } // record it to version based cache as well. snapshot cache always has a higher priority. we don't need to check returned set here // since snapshot based cache will take care of same compilation for us. mapFromBranch.GetValue(projectReference.ProjectId, _ => referenceSet); // return new reference return referenceSet.GetMetadataReference(finalCompilation, projectReference.Aliases, projectReference.EmbedInteropTypes); }
private async Task<Compilation> BuildDeclarationCompilationFromInProgressAsync( SolutionState 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; } }
public SolutionBranch(DocumentId id, SourceText text, SolutionState solution) { this.Id = id; this.Text = text; this.Solution = solution; }
/// <summary> /// Add all appropriate references to the compilation and set it as our final compilation /// state. /// </summary> private async Task<CompilationInfo> FinalizeCompilationAsync( SolutionState solution, Compilation compilation, CancellationToken cancellationToken) { try { // if HasAllInformation is false, then this project is always not completed. bool hasSuccessfullyLoaded = this.ProjectState.HasAllInformation; var newReferences = new List<MetadataReference>(); var metadataReferenceToProjectId = new Dictionary<MetadataReference, ProjectId>(); newReferences.AddRange(this.ProjectState.MetadataReferences); foreach (var projectReference in this.ProjectState.ProjectReferences) { var referencedProject = solution.GetProjectState(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); metadataReferenceToProjectId.Add(metadataReference, projectReference.ProjectId); } else { hasSuccessfullyLoaded = false; } } } } compilation = UpdateCompilationWithNewReferencesAndRecordAssemblySymbols(compilation, newReferences, metadataReferenceToProjectId); bool hasSuccessfullyLoadedTransitively = !HasMissingReferences(compilation, this.ProjectState.MetadataReferences) && await ComputeHasSuccessfullyLoadedTransitivelyAsync(solution, hasSuccessfullyLoaded, cancellationToken).ConfigureAwait(false); this.WriteState(new FinalState(State.CreateValueSource(compilation, solution.Services), hasSuccessfullyLoadedTransitively), solution); return new CompilationInfo(compilation, hasSuccessfullyLoadedTransitively); } catch (Exception e) when (FatalError.ReportUnlessCanceled(e)) { throw ExceptionUtilities.Unreachable; } }
private async Task <bool> HasSuccessfullyLoadedSlowAsync(SolutionState solution, CancellationToken cancellationToken) { var compilationInfo = await GetOrBuildCompilationInfoAsync(solution, lockGate : true, cancellationToken : cancellationToken).ConfigureAwait(false); return(compilationInfo.HasSuccessfullyLoaded); }
public Task <VersionStamp> GetDependentSemanticVersionAsync(SolutionState solution, CancellationToken cancellationToken) => _underlyingTracker.GetDependentSemanticVersionAsync(solution, cancellationToken);
/// <summary> /// 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 /// <see cref="BuildCompilationInfoAsync(SolutionState, CancellationToken)"/>. 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. /// </summary> private void GetPartialCompilationState( SolutionState 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 <ProjectReference>()); // 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 <MetadataReference>(); var newProjectReferences = new List <ProjectReference>(); metadataReferences.AddRange(this.ProjectState.MetadataReferences); var metadataReferenceToProjectId = new Dictionary <MetadataReference, ProjectId>(); foreach (var projectReference in this.ProjectState.ProjectReferences) { var referencedProject = solution.GetProjectState(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. var inProgressCompilationNotRef = inProgressCompilation; metadata = inProgressCompilationNotRef.ExternalReferences.FirstOrDefault( r => solution.GetProjectState(inProgressCompilationNotRef.GetAssemblyOrModuleSymbol(r) as IAssemblySymbol, cancellationToken)?.Id == projectReference.ProjectId); } if (metadata != null) { newProjectReferences.Add(projectReference); metadataReferences.Add(metadata); metadataReferenceToProjectId.Add(metadata, projectReference.ProjectId); } } } } inProgressProject = inProgressProject.AddProjectReferences(newProjectReferences); inProgressCompilation = UpdateCompilationWithNewReferencesAndRecordAssemblySymbols(inProgressCompilation, metadataReferences, metadataReferenceToProjectId); }
/// <summary> /// 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. /// </summary> public async Task<MetadataReference> GetMetadataReferenceAsync( SolutionState solution, ProjectState fromProject, ProjectReference projectReference, CancellationToken cancellationToken) { try { // if we already have the compilation and its right kind then use it. if (this.ProjectState.LanguageServices == fromProject.LanguageServices && this.TryGetCompilation(out var 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; } }
/// <summary> /// Add all appropriate references to the compilation and set it as our final compilation /// state. /// </summary> private async Task <CompilationInfo> FinalizeCompilationAsync( SolutionState solution, Compilation compilation, CancellationToken cancellationToken) { try { // if HasAllInformation is false, then this project is always not completed. bool hasSuccessfullyLoaded = this.ProjectState.HasAllInformation; var newReferences = new List <MetadataReference>(); var metadataReferenceToProjectId = new Dictionary <MetadataReference, ProjectId>(); newReferences.AddRange(this.ProjectState.MetadataReferences); foreach (var projectReference in this.ProjectState.ProjectReferences) { var referencedProject = solution.GetProjectState(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); metadataReferenceToProjectId.Add(metadataReference, projectReference.ProjectId); } else { hasSuccessfullyLoaded = false; } } } } compilation = UpdateCompilationWithNewReferencesAndRecordAssemblySymbols(compilation, newReferences, metadataReferenceToProjectId); this.WriteState(new FinalState(State.CreateValueSource(compilation, solution.Services), hasSuccessfullyLoaded), solution); return(new CompilationInfo(compilation, hasSuccessfullyLoaded)); } catch (Exception e) when(FatalError.ReportUnlessCanceled(e)) { throw ExceptionUtilities.Unreachable; } }
private Solution(SolutionState state) { _projectIdToProjectMap = ImmutableHashMap<ProjectId, Project>.Empty; _state = state; }
private async Task<bool> ComputeHasSuccessfullyLoadedTransitivelyAsync( SolutionState solution, bool hasSuccessfullyLoaded, CancellationToken cancellationToken) { if (!hasSuccessfullyLoaded) { return false; } foreach (var projectReference in this.ProjectState.ProjectReferences) { var project = solution.GetProjectState(projectReference.ProjectId); if (project == null) { return false; } if (!await solution.HasSuccessfullyLoadedAsync(project, cancellationToken).ConfigureAwait(false)) { return false; } } return true; }
public ValueTask <ImmutableArray <Diagnostic> > GetSourceGeneratorDiagnosticsAsync(SolutionState solution, CancellationToken cancellationToken) { // We can directly return the diagnostics from the underlying tracker; this is because // a generated document cannot have any diagnostics that are produced by a generator: // a generator cannot add diagnostics to it's own file outputs, and generators don't see the // outputs of each other. return(UnderlyingTracker.GetSourceGeneratorDiagnosticsAsync(solution, cancellationToken)); }