Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion eng/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
Visual Studio
-->
<PackageVersion Include="Microsoft.VisualStudio.SDK" Version="17.13.40008" />
<PackageVersion Include="Microsoft.Internal.VisualStudio.Shell.Framework" Version="17.9.36524" />
<PackageVersion Include="Microsoft.Internal.VisualStudio.Shell.Framework" Version="17.12.40391" />
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This bump was needed because otherwise use of UIContext in a unit test would throw. This turns out to explain a behavior @jaredpar discovered where our subscription of stuff on a UI thread was throwing exceptions in unit tests, but since it was an async method that was fire-and-forget we never noticed.

<PackageVersion Include="Microsoft.ServiceHub.Client" Version="4.2.1017" />
<PackageVersion Include="Microsoft.VisualStudio.Extensibility.Sdk" Version="17.13.40008" />
<PackageVersion Include="Microsoft.VisualStudio.Extensibility" Version="17.12.2037-preview3" />
Expand Down
3 changes: 3 additions & 0 deletions src/Setup/DevDivInsertionFiles/DevDivInsertionFiles.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
<_Dependency Remove="@(_Dependency)" Condition="$([MSBuild]::ValueOrDefault('%(Identity)', '').StartsWith('Microsoft.ServiceHub.'))"/>
<_Dependency Remove="@(_Dependency)" Condition="$([MSBuild]::ValueOrDefault('%(Identity)', '').StartsWith('System.Composition.'))"/>
<_Dependency Remove="@(_Dependency)" Condition="$([MSBuild]::ValueOrDefault('%(Identity)', '').StartsWith('Microsoft.Internal.VisualStudio.'))"/>
<_Dependency Remove="Azure.Core"/>
<_Dependency Remove="EnvDTE"/>
<_Dependency Remove="EnvDTE80"/>
<_Dependency Remove="EnvDTE90"/>
Expand Down Expand Up @@ -128,6 +129,7 @@
<_Dependency Remove="stdole"/>
<_Dependency Remove="StreamJsonRpc"/>
<_Dependency Remove="System.Buffers" />
<_Dependency Remove="System.ClientModel"/>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The version we're pulling in predates the infamous source generator.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to pull it in at all?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WCF, wtf?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's pulling in through reference to Microsoft.Internal.VisualStudio.Shell.Framework. I have no idea why.

And frankly, I'm not sure why we have this mechanism generally in DevDivInsertionFiles.csproj. The intent was so if you pull in a new dependency you don't forget to push it into VS if needed. But I'm not sure we've added a new one to this list in awhile.

<_Dependency Remove="System.Collections.Immutable"/>
<_Dependency Remove="System.Configuration.ConfigurationManager"/>
<_Dependency Remove="System.Diagnostics.DiagnosticSource"/>
Expand All @@ -136,6 +138,7 @@
<_Dependency Remove="System.IO.Packaging"/>
<_Dependency Remove="System.IO.Pipelines"/>
<_Dependency Remove="System.Memory"/>
<_Dependency Remove="System.Memory.Data"/>
<_Dependency Remove="System.Numerics.Vectors"/>
<_Dependency Remove="System.Reflection.Metadata"/>
<_Dependency Remove="System.Reflection.MetadataLoadContext"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ int IVsLanguageServiceBuildErrorReporter.ClearErrors()
int IVsLanguageServiceBuildErrorReporter2.ClearErrors()
=> _externalErrorReporter.ClearErrors();

int IVsReportExternalErrors.GetErrors(out IVsEnumExternalErrors pErrors)
int IVsReportExternalErrors.GetErrors(out IVsEnumExternalErrors? pErrors)
=> _externalErrorReporter.GetErrors(out pErrors);

int IVsLanguageServiceBuildErrorReporter.ReportError(string bstrErrorMessage, string bstrErrorId, VSTASKPRIORITY nPriority, int iLine, int iColumn, string bstrFileName)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
using Microsoft.CodeAnalysis.Workspaces.ProjectSystem;
using Microsoft.Internal.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.LanguageServices.ExternalAccess.VSTypeScript.Api;
using Microsoft.VisualStudio.LanguageServices.Implementation.Diagnostics;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Telemetry;
Expand Down Expand Up @@ -66,9 +65,6 @@ public VisualStudioProjectFactory(
// and we don't want the case where VisualStudioProjectFactory is constructed on the main thread to block on that.
await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true, cancellationToken);
_visualStudioWorkspaceImpl.Services.GetRequiredService<VisualStudioMetadataReferenceManager>();

_visualStudioWorkspaceImpl.SubscribeExternalErrorDiagnosticUpdateSourceToSolutionBuildEvents();
_visualStudioWorkspaceImpl.SubscribeToSourceGeneratorImpactingEvents();
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,17 +103,18 @@ internal abstract partial class VisualStudioWorkspaceImpl : VisualStudioWorkspac
private readonly JoinableTaskCollection _updateUIContextJoinableTasks;

private readonly OpenFileTracker _openFileTracker;
private readonly UIContext _solutionClosingContext;

internal IFileChangeWatcher FileChangeWatcher { get; }

internal ProjectSystemProjectFactory ProjectSystemProjectFactory { get; }

private readonly Lazy<IProjectCodeModelFactory> _projectCodeModelFactory;
private readonly Lazy<ExternalErrorDiagnosticUpdateSource> _lazyExternalErrorDiagnosticUpdateSource;
private readonly IAsynchronousOperationListener _workspaceListener;
private bool _isExternalErrorDiagnosticUpdateSourceSubscribedToSolutionBuildEvents;

/// <summary>
/// Only read/written on hte UI thread.
/// Only read/written on the UI thread.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noooooooooooooooo

/// </summary>
private bool _isShowingDocumentChangeErrorInfoBar = false;
private bool _ignoreDocumentTextChangeErrors;
Expand All @@ -138,18 +139,17 @@ public VisualStudioWorkspaceImpl(ExportProvider exportProvider, IAsyncServicePro
ProjectSystemProjectFactory = new ProjectSystemProjectFactory(
this, FileChangeWatcher, CheckForAddedFileBeingOpenMaybeAsync, RemoveProjectFromMaps, _threadingContext.DisposalToken);

_solutionClosingContext = UIContext.FromUIContextGuid(VSConstants.UICONTEXT.SolutionClosing_guid);
_solutionClosingContext.UIContextChanged += SolutionClosingContext_UIContextChanged;

_openFileTracker = new OpenFileTracker(
this,
ProjectSystemProjectFactory,
exportProvider.GetExport<Microsoft.VisualStudio.Text.Editor.IEditorOptionsFactoryService>(),
_workspaceListener,
exportProvider.GetExportedValue<OpenTextBufferProvider>());

InitializeUIAffinitizedServicesAsync().Forget();

_lazyExternalErrorDiagnosticUpdateSource = new Lazy<ExternalErrorDiagnosticUpdateSource>(() =>
exportProvider.GetExportedValue<ExternalErrorDiagnosticUpdateSource>(),
isThreadSafe: true);
_lazyExternalErrorDiagnosticUpdateSource = exportProvider.GetExport<ExternalErrorDiagnosticUpdateSource>();

_updateUIContextJoinableTasks = new JoinableTaskCollection(_threadingContext.JoinableTaskContext);

Expand All @@ -160,61 +160,17 @@ public VisualStudioWorkspaceImpl(ExportProvider exportProvider, IAsyncServicePro

Logger.Log(FunctionId.Run_Environment, KeyValueLogMessage.Create(
static m => m["Version"] = FileVersionInfo.GetVersionInfo(typeof(VisualStudioWorkspace).Assembly.Location).FileVersion));
}

internal ExternalErrorDiagnosticUpdateSource ExternalErrorDiagnosticUpdateSource => _lazyExternalErrorDiagnosticUpdateSource.Value;

internal void SubscribeExternalErrorDiagnosticUpdateSourceToSolutionBuildEvents()
{
// TODO: further understand if this needs the foreground thread for any reason. UIContexts are safe to read from the UI thread;
// it's not clear to me why this is being asserted.
_threadingContext.ThrowIfNotOnUIThread();

if (_isExternalErrorDiagnosticUpdateSourceSubscribedToSolutionBuildEvents)
{
return;
}

// TODO: https://github.com/dotnet/roslyn/issues/36065
// UIContextImpl requires IVsMonitorSelection service:
if (ServiceProvider.GlobalProvider.GetService(typeof(IVsMonitorSelection)) == null)
{
return;
}

// This pattern ensures that we are called whenever the build starts/completes even if it is already in progress.
KnownUIContexts.SolutionBuildingContext.WhenActivated(() =>
{
KnownUIContexts.SolutionBuildingContext.UIContextChanged += (_, e) =>
{
if (e.Activated)
{
ExternalErrorDiagnosticUpdateSource.OnSolutionBuildStarted();
}
else
{
// A real build just finished. Clear out any results from the last "run code analysis" command.
this.Services.GetRequiredService<ICodeAnalysisDiagnosticAnalyzerService>().Clear();
ExternalErrorDiagnosticUpdateSource.OnSolutionBuildCompleted();
}
};

ExternalErrorDiagnosticUpdateSource.OnSolutionBuildStarted();
});

_isExternalErrorDiagnosticUpdateSourceSubscribedToSolutionBuildEvents = true;
SubscribeToSourceGeneratorImpactingEvents();
}

public async Task InitializeUIAffinitizedServicesAsync()
private void SolutionClosingContext_UIContextChanged(object sender, UIContextChangedEventArgs e)
{
// Yield the thread, so the caller can proceed and return immediately.
// Create services that are bound to the UI thread
await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true, _threadingContext.DisposalToken);

var solutionClosingContext = UIContext.FromUIContextGuid(VSConstants.UICONTEXT.SolutionClosing_guid);
solutionClosingContext.UIContextChanged += (_, e) => ProjectSystemProjectFactory.SolutionClosing = e.Activated;
ProjectSystemProjectFactory.SolutionClosing = e.Activated;
}

internal ExternalErrorDiagnosticUpdateSource ExternalErrorDiagnosticUpdateSource => _lazyExternalErrorDiagnosticUpdateSource.Value;

public Task CheckForAddedFileBeingOpenMaybeAsync(bool useAsync, ImmutableArray<string> newFileNames)
=> _openFileTracker.CheckForAddedFileBeingOpenMaybeAsync(useAsync, newFileNames);

Expand Down Expand Up @@ -1464,10 +1420,10 @@ protected override void Dispose(bool finalize)
_textBufferFactoryService.TextBufferCreated -= AddTextBufferCloneServiceToBuffer;
_projectionBufferFactoryService.ProjectionBufferCreated -= AddTextBufferCloneServiceToBuffer;

if (_lazyExternalErrorDiagnosticUpdateSource.IsValueCreated)
{
_lazyExternalErrorDiagnosticUpdateSource.Value.Dispose();
}
// UIContext.FromUIContextGuid internally has a map from the GUID to the context object itself that is stored in a static;
// if we don't unsubscribe, it will leak our workspace object which can cause memory leaks in tests that create a whole MEF container
// per test.
_solutionClosingContext?.UIContextChanged -= SolutionClosingContext_UIContextChanged;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure i entirely unnderstand why this is then ok. but i trust you.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@CyrusNajmabadi What part is confusing?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The whole 'there is something static somewhere that is leaking, but this is actually an instance member that we're hooking up to'.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since I have to push to the PR, I'll clarify: we call UIContext.FromGuid() and internally that creates a static dictionary of UIContext GUID to -> UIContext object, so each test that runs will get the same object.

}

base.Dispose(finalize);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
// See the LICENSE file in the project root for more information.

using System.Linq;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Host;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
Expand All @@ -12,20 +11,8 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem;

internal abstract partial class VisualStudioWorkspaceImpl
{
private bool _isSubscribedToSourceGeneratorImpactingEvents;

public void SubscribeToSourceGeneratorImpactingEvents()
protected virtual void SubscribeToSourceGeneratorImpactingEvents()
{
_threadingContext.ThrowIfNotOnUIThread();
if (_isSubscribedToSourceGeneratorImpactingEvents)
return;

// UIContextImpl requires IVsMonitorSelection service:
if (ServiceProvider.GlobalProvider.GetService(typeof(IVsMonitorSelection)) == null)
return;

_isSubscribedToSourceGeneratorImpactingEvents = true;

// This pattern ensures that we are called whenever the build starts/completes even if it is already in progress.
KnownUIContexts.SolutionBuildingContext.WhenActivated(() =>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,17 @@
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.LanguageServer;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Collections;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.CodeAnalysis.Threading;
using Microsoft.ServiceHub.Framework;
using Microsoft.VisualStudio.RpcContracts.DiagnosticManagement;
using Microsoft.VisualStudio.RpcContracts.Utilities;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.ServiceBroker;
using Roslyn.Utilities;

namespace Microsoft.VisualStudio.LanguageServices.Implementation.TaskList;

/// <summary>
/// Diagnostic source for warnings and errors reported from explicit build command invocations in Visual Studio.
/// VS workspaces calls into us when a build is invoked or completed in Visual Studio.
Expand Down Expand Up @@ -79,6 +80,26 @@ public ExternalErrorDiagnosticUpdateSource(
_listener,
_disposalToken
);

// This pattern ensures that we are called whenever the build starts/completes even if it is already in progress.
KnownUIContexts.SolutionBuildingContext.WhenActivated(() =>
{
KnownUIContexts.SolutionBuildingContext.UIContextChanged += (_, e) =>
{
if (e.Activated)
{
OnSolutionBuildStarted();
}
else
{
// A real build just finished. Clear out any results from the last "run code analysis" command.
_workspace.Services.GetRequiredService<ICodeAnalysisDiagnosticAnalyzerService>().Clear();
OnSolutionBuildCompleted();
}
};

OnSolutionBuildStarted();
});
}

private async ValueTask ProcessTaskQueueItemsAsync(ImmutableSegmentedList<Func<CancellationToken, Task>> list, CancellationToken cancellationToken)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
// 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.Immutable;
using System.Diagnostics;
Expand Down Expand Up @@ -45,10 +43,6 @@ internal sealed class ProjectExternalErrorReporter : IVsReportExternalErrors, IV

public ProjectExternalErrorReporter(ProjectId projectId, Guid projectHierarchyGuid, string errorCodePrefix, string language, VisualStudioWorkspaceImpl workspace)
{
Debug.Assert(projectId != null);
Debug.Assert(errorCodePrefix != null);
Debug.Assert(workspace != null);

_projectId = projectId;
_projectHierarchyGuid = projectHierarchyGuid;
_errorCodePrefix = errorCodePrefix;
Expand Down Expand Up @@ -83,7 +77,7 @@ public int AddNewErrors(IVsEnumExternalErrors pErrors)
using var _ = ArrayBuilder<DiagnosticData>.GetInstance(out var allDiagnostics);

var errors = new ExternalError[1];
var project = _workspace.CurrentSolution.GetProject(_projectId);
var project = _workspace.CurrentSolution.GetRequiredProject(_projectId);
while (pErrors.Next(1, errors, out var fetched) == VSConstants.S_OK && fetched == 1)
{
var error = errors[0];
Expand Down Expand Up @@ -119,21 +113,21 @@ public int ClearAllErrors()
return VSConstants.S_OK;
}

public int GetErrors(out IVsEnumExternalErrors pErrors)
public int GetErrors(out IVsEnumExternalErrors? pErrors)
{
pErrors = null;
Debug.Fail("This is not implemented, because no one called it.");
return VSConstants.E_NOTIMPL;
}

private DocumentId TryGetDocumentId(string filePath)
private DocumentId? TryGetDocumentId(string filePath)
{
return _workspace.CurrentSolution.GetDocumentIdsWithFilePath(filePath)
.Where(f => f.ProjectId == _projectId)
.FirstOrDefault();
}

private DiagnosticData TryCreateDocumentDiagnosticItem(ExternalError error)
private DiagnosticData? TryCreateDocumentDiagnosticItem(ExternalError error)
{
var documentId = TryGetDocumentId(error.bstrFileName);
if (documentId == null)
Expand Down Expand Up @@ -218,8 +212,8 @@ public void ReportError2(string bstrErrorMessage, string bstrErrorId, [ComAliasN
_ => throw new ArgumentException(ServicesVSResources.Not_a_valid_value, nameof(nPriority))
};

DocumentId documentId;
if (bstrFileName == null || iStartLine < 0 || iStartColumn < 0)
DocumentId? documentId;
if (iStartLine < 0 || iStartColumn < 0)
{
documentId = null;
iStartLine = iStartColumn = iEndLine = iEndColumn = 0;
Expand Down Expand Up @@ -258,7 +252,7 @@ public int ClearErrors()
}

private static DiagnosticData GetDiagnosticData(
DocumentId documentId,
DocumentId? documentId,
ProjectId projectId,
Workspace workspace,
string errorId,
Expand All @@ -268,7 +262,8 @@ private static DiagnosticData GetDiagnosticData(
FileLinePositionSpan unmappedSpan,
DiagnosticAnalyzerInfoCache analyzerInfoCache)
{
string title, description, category, helpLink;
string title, description, category;
string? helpLink;
DiagnosticSeverity defaultSeverity;
bool isEnabledByDefault;
ImmutableArray<string> customTags;
Expand Down Expand Up @@ -316,7 +311,7 @@ private static DiagnosticData GetDiagnosticData(
if (workspace.CurrentSolution.GetDocument(documentId) is Document document &&
document.SupportsSyntaxTree)
{
var tree = document.GetSyntaxTreeSynchronously(CancellationToken.None);
var tree = document.GetRequiredSyntaxTreeSynchronously(CancellationToken.None);
var text = tree.GetText();
var span = diagnostic.DataLocation.UnmappedFileSpan.GetClampedTextSpan(text);
var location = diagnostic.DataLocation.WithSpan(span, tree);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public int ClearAllErrors()
public int AddNewErrors(IVsEnumExternalErrors pErrors)
=> GetExternalErrorReporter().AddNewErrors(pErrors);

public int GetErrors(out IVsEnumExternalErrors pErrors)
public int GetErrors(out IVsEnumExternalErrors? pErrors)
=> GetExternalErrorReporter().GetErrors(out pErrors);

public int ReportError(string bstrErrorMessage, string bstrErrorId, VSTASKPRIORITY nPriority, int iLine, int iColumn, string bstrFileName)
Expand Down
Loading