diff --git a/src/EditorFeatures/CSharpTest/SignatureHelp/TupleConstructionSignatureHelpProviderTests.cs b/src/EditorFeatures/CSharpTest/SignatureHelp/TupleConstructionSignatureHelpProviderTests.cs index d508ac6b8e451..3fdaebdbffae9 100644 --- a/src/EditorFeatures/CSharpTest/SignatureHelp/TupleConstructionSignatureHelpProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/SignatureHelp/TupleConstructionSignatureHelpProviderTests.cs @@ -38,6 +38,25 @@ class C await TestAsync(markup, expectedOrderedItems, usePreviousCharAsTrigger: true); } + [WorkItem(655607, "https://devdiv.visualstudio.com/DevDiv/_workitems/edit/655607")] + [Fact, Trait(Traits.Feature, Traits.Features.SignatureHelp)] + public async Task TestMissingTupleElement() + { + var markup = @" +class C +{ + void M() + { + (a, ) = [|($$ +|] } +}"; + + var expectedOrderedItems = new List(); + expectedOrderedItems.Add(new SignatureHelpTestItem("(object a, object)", currentParameterIndex: 0)); + + await TestAsync(markup, expectedOrderedItems, usePreviousCharAsTrigger: true); + } + [Fact, Trait(Traits.Feature, Traits.Features.SignatureHelp)] public async Task InvocationAfterOpenParen2() { diff --git a/src/VisualStudio/Core/Def/Implementation/Diagnostics/VisualStudioWorkspaceDiagnosticAnalyzerProviderService.cs b/src/VisualStudio/Core/Def/Implementation/Diagnostics/VisualStudioWorkspaceDiagnosticAnalyzerProviderService.cs index 520be615d8372..624eba50277c4 100644 --- a/src/VisualStudio/Core/Def/Implementation/Diagnostics/VisualStudioWorkspaceDiagnosticAnalyzerProviderService.cs +++ b/src/VisualStudio/Core/Def/Implementation/Diagnostics/VisualStudioWorkspaceDiagnosticAnalyzerProviderService.cs @@ -4,14 +4,11 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.ComponentModel.Composition; -using System.IO; using System.Linq; using System.Reflection; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; using Microsoft.VisualStudio.Shell; -using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Implementation.Diagnostics { @@ -77,61 +74,74 @@ internal static IAnalyzerAssemblyLoader GetLoader() // internal for testing purpose internal static ImmutableArray GetHostAnalyzerPackagesWithName(object extensionManager, Type parameterType) { - // dynamic is wierd. it can't see internal type with public interface even if callee is - // implementation of the public interface in internal type. so we can't use dynamic here - - var builder = ImmutableArray.CreateBuilder(); - - // var enabledExtensions = extensionManager.GetEnabledExtensions(AnalyzerContentTypeName); - var extensionManagerType = extensionManager.GetType(); - var extensionManager_GetEnabledExtensionsMethod = extensionManagerType.GetRuntimeMethod("GetEnabledExtensions", new Type[] { typeof(string) }); - var enabledExtensions = extensionManager_GetEnabledExtensionsMethod.Invoke(extensionManager, new object[] { AnalyzerContentTypeName }) as IEnumerable; - - foreach (var extension in enabledExtensions) + try { - // var name = extension.Header.LocalizedName; - var extensionType = extension.GetType(); - var extensionType_HeaderProperty = extensionType.GetRuntimeProperty("Header"); - var extension_Header = extensionType_HeaderProperty.GetValue(extension); - var extension_HeaderType = extension_Header.GetType(); - var extension_HeaderType_LocalizedNameProperty = extension_HeaderType.GetRuntimeProperty("LocalizedName"); - var name = extension_HeaderType_LocalizedNameProperty.GetValue(extension_Header) as string; + // dynamic is wierd. it can't see internal type with public interface even if callee is + // implementation of the public interface in internal type. so we can't use dynamic here - var assemblies = ImmutableArray.CreateBuilder(); + var builder = ImmutableArray.CreateBuilder(); - // var extension_Content = extension.Content; - var extensionType_ContentProperty = extensionType.GetRuntimeProperty("Content"); - var extension_Content = extensionType_ContentProperty.GetValue(extension) as IEnumerable; + // var enabledExtensions = extensionManager.GetEnabledExtensions(AnalyzerContentTypeName); + var extensionManagerType = extensionManager.GetType(); + var extensionManager_GetEnabledExtensionsMethod = extensionManagerType.GetRuntimeMethod("GetEnabledExtensions", new Type[] { typeof(string) }); + var enabledExtensions = extensionManager_GetEnabledExtensionsMethod.Invoke(extensionManager, new object[] { AnalyzerContentTypeName }) as IEnumerable; - foreach (var content in extension_Content) + foreach (var extension in enabledExtensions) { - if (!ShouldInclude(content)) - { - continue; - } + // var name = extension.Header.LocalizedName; + var extensionType = extension.GetType(); + var extensionType_HeaderProperty = extensionType.GetRuntimeProperty("Header"); + var extension_Header = extensionType_HeaderProperty.GetValue(extension); + var extension_HeaderType = extension_Header.GetType(); + var extension_HeaderType_LocalizedNameProperty = extension_HeaderType.GetRuntimeProperty("LocalizedName"); + var name = extension_HeaderType_LocalizedNameProperty.GetValue(extension_Header) as string; + + var assemblies = ImmutableArray.CreateBuilder(); - var extensionType_GetContentMethod = extensionType.GetRuntimeMethod("GetContentLocation", new Type[] { parameterType }); - var assembly = extensionType_GetContentMethod?.Invoke(extension, new object[] { content }) as string; - if (assembly == null) + // var extension_Content = extension.Content; + var extensionType_ContentProperty = extensionType.GetRuntimeProperty("Content"); + var extension_Content = extensionType_ContentProperty.GetValue(extension) as IEnumerable; + + foreach (var content in extension_Content) { - continue; + if (!ShouldInclude(content)) + { + continue; + } + + var extensionType_GetContentMethod = extensionType.GetRuntimeMethod("GetContentLocation", new Type[] { parameterType }); + var assembly = extensionType_GetContentMethod?.Invoke(extension, new object[] { content }) as string; + if (assembly == null) + { + continue; + } + + assemblies.Add(assembly); } - assemblies.Add(assembly); + builder.Add(new HostDiagnosticAnalyzerPackage(name, assemblies.ToImmutable())); } - builder.Add(new HostDiagnosticAnalyzerPackage(name, assemblies.ToImmutable())); - } - - var packages = builder.ToImmutable(); + var packages = builder.ToImmutable(); - EnsureMandatoryAnalyzers(packages); + EnsureMandatoryAnalyzers(packages); - // make sure enabled extensions are alive in memory - // so that we can debug it through if mandatory analyzers are missing - GC.KeepAlive(enabledExtensions); + // make sure enabled extensions are alive in memory + // so that we can debug it through if mandatory analyzers are missing + GC.KeepAlive(enabledExtensions); - return packages; + return packages; + } + catch (InvalidOperationException) + { + // this can be called from any thread, and extension manager could be disposed in the middle of us using it since + // now all these are free-threaded and there is no central coordinator, or API or state is immutable that prevent states from + // changing in the middle of others using it. + // + // fortunately, this only happens on disposing at shutdown, so we just catch the exception and silently swallow it. + // we are about to shutdown anyway. + return ImmutableArray.Empty; + } } private static void EnsureMandatoryAnalyzers(ImmutableArray packages) diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioAnalyzer.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioAnalyzer.cs index 45715e03a1acd..726ff0ff59f9a 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioAnalyzer.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioAnalyzer.cs @@ -1,7 +1,6 @@ // 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.IO; using System.Reflection; @@ -24,24 +23,27 @@ internal sealed class VisualStudioAnalyzer : IDisposable private readonly IAnalyzerAssemblyLoader _loader; private readonly string _language; - private AnalyzerReference _analyzerReference; - private List _analyzerLoadErrors; - - public event EventHandler UpdatedOnDisk; + // these 2 are mutable states that must be guarded under the _gate. + private readonly object _gate = new object(); + private AnalyzerReference _analyzerReference = null; + private ImmutableArray _analyzerLoadErrors = ImmutableArray.Empty; public VisualStudioAnalyzer(string fullPath, IVsFileChangeEx fileChangeService, HostDiagnosticUpdateSource hostDiagnosticUpdateSource, ProjectId projectId, Workspace workspace, IAnalyzerAssemblyLoader loader, string language) { _fullPath = fullPath; - _tracker = new FileChangeTracker(fileChangeService, fullPath); - _tracker.UpdatedOnDisk += OnUpdatedOnDisk; - _tracker.StartFileChangeListeningAsync(); _hostDiagnosticUpdateSource = hostDiagnosticUpdateSource; _projectId = projectId; _workspace = workspace; _loader = loader; _language = language; + + _tracker = new FileChangeTracker(fileChangeService, fullPath); + _tracker.UpdatedOnDisk += OnUpdatedOnDisk; + _tracker.StartFileChangeListeningAsync(); } + public event EventHandler UpdatedOnDisk; + public string FullPath { get { return _fullPath; } @@ -49,35 +51,40 @@ public string FullPath public bool HasLoadErrors { - get { return _analyzerLoadErrors != null && _analyzerLoadErrors.Count > 0; } + get { return !_analyzerLoadErrors.IsEmpty; } } public AnalyzerReference GetReference() { - if (_analyzerReference == null) + lock (_gate) { - if (File.Exists(_fullPath)) + if (_analyzerReference == null) { - // Pass down a custom loader that will ensure we are watching for file changes once we actually load the assembly. - var assemblyLoaderForFileTracker = new AnalyzerAssemblyLoaderThatEnsuresFileBeingWatched(this); - _analyzerReference = new AnalyzerFileReference(_fullPath, assemblyLoaderForFileTracker); - ((AnalyzerFileReference)_analyzerReference).AnalyzerLoadFailed += OnAnalyzerLoadError; + if (File.Exists(_fullPath)) + { + // Pass down a custom loader that will ensure we are watching for file changes once we actually load the assembly. + var assemblyLoaderForFileTracker = new AnalyzerAssemblyLoaderThatEnsuresFileBeingWatched(this); + _analyzerReference = new AnalyzerFileReference(_fullPath, assemblyLoaderForFileTracker); + ((AnalyzerFileReference)_analyzerReference).AnalyzerLoadFailed += OnAnalyzerLoadError; + } + else + { + _analyzerReference = new VisualStudioUnresolvedAnalyzerReference(_fullPath, this); + } } - else - { - _analyzerReference = new VisualStudioUnresolvedAnalyzerReference(_fullPath, this); - } - } - return _analyzerReference; + return _analyzerReference; + } } private void OnAnalyzerLoadError(object sender, AnalyzerLoadFailureEventArgs e) { var data = AnalyzerHelper.CreateAnalyzerLoadFailureDiagnostic(_workspace, _projectId, _language, _fullPath, e); - _analyzerLoadErrors = _analyzerLoadErrors ?? new List(); - _analyzerLoadErrors.Add(data); + lock (_gate) + { + _analyzerLoadErrors = _analyzerLoadErrors.Add(data); + } _hostDiagnosticUpdateSource.UpdateDiagnosticsForProject(_projectId, this, _analyzerLoadErrors); } @@ -92,20 +99,31 @@ public void Dispose() public void Reset() { - if (_analyzerReference is AnalyzerFileReference analyzerFileReference) + ResetReferenceAndErrors(out var reference, out var loadErrors); + + if (reference is AnalyzerFileReference fileReference) { - analyzerFileReference.AnalyzerLoadFailed -= OnAnalyzerLoadError; + fileReference.AnalyzerLoadFailed -= OnAnalyzerLoadError; - if (_analyzerLoadErrors != null && _analyzerLoadErrors.Count > 0) + if (!loadErrors.IsEmpty) { _hostDiagnosticUpdateSource.ClearDiagnosticsForProject(_projectId, this); } - _hostDiagnosticUpdateSource.ClearAnalyzerReferenceDiagnostics(analyzerFileReference, _language, _projectId); + _hostDiagnosticUpdateSource.ClearAnalyzerReferenceDiagnostics(fileReference, _language, _projectId); } + } - _analyzerLoadErrors = null; - _analyzerReference = null; + private void ResetReferenceAndErrors(out AnalyzerReference reference, out ImmutableArray loadErrors) + { + lock (_gate) + { + loadErrors = _analyzerLoadErrors; + reference = _analyzerReference; + + _analyzerLoadErrors = ImmutableArray.Empty; + _analyzerReference = null; + } } private void OnUpdatedOnDisk(object sender, EventArgs e) diff --git a/src/Workspaces/CSharp/Portable/LanguageServices/CSharpTypeInferenceService.TypeInferrer.cs b/src/Workspaces/CSharp/Portable/LanguageServices/CSharpTypeInferenceService.TypeInferrer.cs index 4e3718a712959..8ffb0a388739c 100644 --- a/src/Workspaces/CSharp/Portable/LanguageServices/CSharpTypeInferenceService.TypeInferrer.cs +++ b/src/Workspaces/CSharp/Portable/LanguageServices/CSharpTypeInferenceService.TypeInferrer.cs @@ -9,10 +9,10 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.LanguageServices; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; -using Microsoft.CodeAnalysis.LanguageServices; namespace Microsoft.CodeAnalysis.CSharp { @@ -2090,9 +2090,10 @@ private bool TryGetTupleTypesAndNames( { AddTypeAndName((TupleExpressionSyntax)expr, elementTypesBuilder, elementNamesBuilder); } - else if (expr.IsKind(SyntaxKind.IdentifierName)) + else if (expr is IdentifierNameSyntax name) { - elementNamesBuilder.Add(((IdentifierNameSyntax)expr).Identifier.ValueText); + elementNamesBuilder.Add(name.Identifier.ValueText == "" ? null : + name.Identifier.ValueText); elementTypesBuilder.Add(GetTypes(expr).FirstOrDefault().InferredType ?? this.Compilation.ObjectType); } else