Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge master to features/readonly-members #33836

Merged
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: 2 additions & 0 deletions src/Compilers/CSharp/Portable/Syntax/SyntaxKindFacts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1630,6 +1630,8 @@ public static string GetText(SyntaxKind kind)
return "$\"";
case SyntaxKind.InterpolatedStringEndToken:
return "\"";
case SyntaxKind.InterpolatedVerbatimStringStartToken:
return "$@\"";
case SyntaxKind.UnderscoreToken:
return "_";
case SyntaxKind.VarKeyword:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,51 @@
// 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.Linq;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Roslyn.Test.Utilities;
using Xunit;

namespace Microsoft.CodeAnalysis.CSharp.UnitTests.CodeGen
{
public class InterpolatedStringTests : CSharpTestBase
{
[Fact, WorkItem(33713, "https://github.com/dotnet/roslyn/issues/33713")]
public void AlternateVerbatimString()
{
var source = @"
class C
{
static void Main()
{
int i = 42;
var s = @$""{i}
{i}"";
System.Console.Write(s);
var s2 = $@"""";
}
}";
var comp = CreateCompilation(source, options: TestOptions.ReleaseExe);
CompileAndVerify(comp, expectedOutput: @"42
42");

var tree = comp.SyntaxTrees.Single();
var interpolatedStrings = tree.GetRoot().DescendantNodes().OfType<InterpolatedStringExpressionSyntax>().ToArray();
var token1 = interpolatedStrings[0].StringStartToken;
Assert.Equal("@$\"", token1.Text);
Assert.Equal("@$\"", token1.ValueText);

var token2 = interpolatedStrings[1].StringStartToken;
Assert.Equal("$@\"", token2.Text);
Assert.Equal("$@\"", token2.ValueText);

foreach (var token in tree.GetRoot().DescendantTokens().Where(t => t.Kind() != SyntaxKind.EndOfFileToken))
{
Assert.False(string.IsNullOrEmpty(token.Text));
Assert.False(string.IsNullOrEmpty(token.ValueText));
}
}

[Fact]
public void ConstInterpolations()
{
Expand Down
8 changes: 8 additions & 0 deletions src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxFactoryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests
{
public class SyntaxFactoryTests : CSharpTestBase
{
[Fact, WorkItem(33713, "https://github.com/dotnet/roslyn/issues/33713")]
public void AlternateVerbatimString()
{
var token = SyntaxFactory.Token(SyntaxKind.InterpolatedVerbatimStringStartToken);
Assert.Equal("$@\"", token.Text);
Assert.Equal("$@\"", token.ValueText);
}

[Fact]
public void SyntaxTree()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public EditAndContinueDiagnosticUpdateSource(IDiagnosticUpdateSourceRegistration
public bool SupportGetDiagnostics => false;

public event EventHandler<DiagnosticsUpdatedArgs> DiagnosticsUpdated;
public event EventHandler DiagnosticsCleared { add { } remove { } }

public ImmutableArray<DiagnosticData> GetDiagnostics(Workspace workspace, ProjectId projectId, DocumentId documentId, object id, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default)
{
Expand Down
92 changes: 80 additions & 12 deletions src/EditorFeatures/Test/Diagnostics/DiagnosticServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,17 @@ public void TestGetDiagnostics1()
{
using (var workspace = new TestWorkspace(TestExportProvider.ExportProviderWithCSharpAndVisualBasic))
{
var set = new ManualResetEvent(false);
var mutex = new ManualResetEvent(false);
var document = workspace.CurrentSolution.AddProject("TestProject", "TestProject", LanguageNames.CSharp).AddDocument("TestDocument", string.Empty);

var source = new TestDiagnosticUpdateSource(false, null);
var diagnosticService = new DiagnosticService(AsynchronousOperationListenerProvider.NullProvider);
diagnosticService.Register(source);

diagnosticService.DiagnosticsUpdated += (s, o) => { set.Set(); };
diagnosticService.DiagnosticsUpdated += (s, o) => { mutex.Set(); };

var id = Tuple.Create(workspace, document);
var diagnostic = RaiseDiagnosticEvent(set, source, workspace, document.Project.Id, document.Id, id);
var diagnostic = RaiseDiagnosticEvent(mutex, source, workspace, document.Project.Id, document.Id, id);

var data1 = diagnosticService.GetDiagnostics(workspace, null, null, null, false, CancellationToken.None);
Assert.Equal(diagnostic, data1.Single());
Expand All @@ -51,27 +51,27 @@ public void TestGetDiagnostics2()
{
using (var workspace = new TestWorkspace(TestExportProvider.ExportProviderWithCSharpAndVisualBasic))
{
var set = new ManualResetEvent(false);
var mutex = new ManualResetEvent(false);
var document = workspace.CurrentSolution.AddProject("TestProject", "TestProject", LanguageNames.CSharp).AddDocument("TestDocument", string.Empty);
var document2 = document.Project.AddDocument("TestDocument2", string.Empty);

var source = new TestDiagnosticUpdateSource(false, null);
var diagnosticService = new DiagnosticService(AsynchronousOperationListenerProvider.NullProvider);
diagnosticService.Register(source);

diagnosticService.DiagnosticsUpdated += (s, o) => { set.Set(); };
diagnosticService.DiagnosticsUpdated += (s, o) => { mutex.Set(); };

var id = Tuple.Create(workspace, document);
RaiseDiagnosticEvent(set, source, workspace, document.Project.Id, document.Id, id);
RaiseDiagnosticEvent(mutex, source, workspace, document.Project.Id, document.Id, id);

var id2 = Tuple.Create(workspace, document.Project, document);
RaiseDiagnosticEvent(set, source, workspace, document.Project.Id, document.Id, id2);
RaiseDiagnosticEvent(mutex, source, workspace, document.Project.Id, document.Id, id2);

RaiseDiagnosticEvent(set, source, workspace, document2.Project.Id, document2.Id, Tuple.Create(workspace, document2));
RaiseDiagnosticEvent(mutex, source, workspace, document2.Project.Id, document2.Id, Tuple.Create(workspace, document2));

var id3 = Tuple.Create(workspace, document.Project);
RaiseDiagnosticEvent(set, source, workspace, document.Project.Id, null, id3);
RaiseDiagnosticEvent(set, source, workspace, null, null, Tuple.Create(workspace));
RaiseDiagnosticEvent(mutex, source, workspace, document.Project.Id, null, id3);
RaiseDiagnosticEvent(mutex, source, workspace, null, null, Tuple.Create(workspace));

var data1 = diagnosticService.GetDiagnostics(workspace, null, null, null, false, CancellationToken.None);
Assert.Equal(5, data1.Count());
Expand All @@ -90,13 +90,75 @@ public void TestGetDiagnostics2()
}
}

[Fact, Trait(Traits.Feature, Traits.Features.Diagnostics)]
public void TestCleared()
{
using (var workspace = new TestWorkspace(TestExportProvider.ExportProviderWithCSharpAndVisualBasic))
{
var mutex = new ManualResetEvent(false);
var document = workspace.CurrentSolution.AddProject("TestProject", "TestProject", LanguageNames.CSharp).AddDocument("TestDocument", string.Empty);
var document2 = document.Project.AddDocument("TestDocument2", string.Empty);

var diagnosticService = new DiagnosticService(AsynchronousOperationListenerProvider.NullProvider);

var source1 = new TestDiagnosticUpdateSource(support: false, diagnosticData: null);
diagnosticService.Register(source1);

var source2 = new TestDiagnosticUpdateSource(support: false, diagnosticData: null);
diagnosticService.Register(source2);

diagnosticService.DiagnosticsUpdated += MarkSet;

// add bunch of data to the service for both sources
RaiseDiagnosticEvent(mutex, source1, workspace, document.Project.Id, document.Id, Tuple.Create(workspace, document));
RaiseDiagnosticEvent(mutex, source1, workspace, document.Project.Id, document.Id, Tuple.Create(workspace, document.Project, document));
RaiseDiagnosticEvent(mutex, source1, workspace, document2.Project.Id, document2.Id, Tuple.Create(workspace, document2));

RaiseDiagnosticEvent(mutex, source2, workspace, document.Project.Id, null, Tuple.Create(workspace, document.Project));
RaiseDiagnosticEvent(mutex, source2, workspace, null, null, Tuple.Create(workspace));

// confirm data is there.
var data1 = diagnosticService.GetDiagnostics(workspace, null, null, null, false, CancellationToken.None);
Assert.Equal(5, data1.Count());

diagnosticService.DiagnosticsUpdated -= MarkSet;

// confirm clear for a source
mutex.Reset();
var count = 0;
diagnosticService.DiagnosticsUpdated += MarkCalled;

source1.RaiseDiagnosticsClearedEvent();

mutex.WaitOne();

// confirm there are 2 data left
var data2 = diagnosticService.GetDiagnostics(workspace, null, null, null, false, CancellationToken.None);
Assert.Equal(2, data2.Count());

void MarkCalled(object sender, DiagnosticsUpdatedArgs args)
{
// event is serialized. no concurrent call
if (++count == 3)
{
mutex.Set();
}
}

void MarkSet(object sender, DiagnosticsUpdatedArgs args)
{
mutex.Set();
}
}
}

private static DiagnosticData RaiseDiagnosticEvent(ManualResetEvent set, TestDiagnosticUpdateSource source, TestWorkspace workspace, ProjectId project, DocumentId document, object id)
{
set.Reset();

var diagnostic = CreateDiagnosticData(workspace, project, document);

source.RaiseUpdateEvent(
source.RaiseDiagnosticsUpdatedEvent(
DiagnosticsUpdatedArgs.DiagnosticsCreated(id, workspace, workspace.CurrentSolution, project, document, ImmutableArray.Create(diagnostic)));

set.WaitOne();
Expand Down Expand Up @@ -126,16 +188,22 @@ public TestDiagnosticUpdateSource(bool support, DiagnosticData[] diagnosticData)

public bool SupportGetDiagnostics { get { return _support; } }
public event EventHandler<DiagnosticsUpdatedArgs> DiagnosticsUpdated;
public event EventHandler DiagnosticsCleared;

public ImmutableArray<DiagnosticData> GetDiagnostics(Workspace workspace, ProjectId projectId, DocumentId documentId, object id, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default)
{
return _support ? _diagnosticData : ImmutableArray<DiagnosticData>.Empty;
}

public void RaiseUpdateEvent(DiagnosticsUpdatedArgs args)
public void RaiseDiagnosticsUpdatedEvent(DiagnosticsUpdatedArgs args)
{
DiagnosticsUpdated?.Invoke(this, args);
}

public void RaiseDiagnosticsClearedEvent()
{
DiagnosticsCleared?.Invoke(this, EventArgs.Empty);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ public void RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs args)
}

public event EventHandler<DiagnosticsUpdatedArgs> DiagnosticsUpdated;
public event EventHandler DiagnosticsCleared { add { } remove { } }

public bool SupportGetDiagnostics => false;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public ImmutableArray<DiagnosticData> GetDiagnostics(Workspace workspace, Projec
}

public event EventHandler<DiagnosticsUpdatedArgs> DiagnosticsUpdated;
public event EventHandler DiagnosticsCleared { add { } remove { } }

public void RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs args)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public IIncrementalAnalyzer CreateIncrementalAnalyzer(Workspace workspace)
}

public event EventHandler<DiagnosticsUpdatedArgs> DiagnosticsUpdated;
public event EventHandler DiagnosticsCleared { add { } remove { } }

// this only support push model, pull model will be provided by DiagnosticService by caching everything this one pushed
public bool SupportGetDiagnostics => false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,19 @@ public event EventHandler<DiagnosticsUpdatedArgs> DiagnosticsUpdated
}
}

public event EventHandler DiagnosticsCleared
{
add
{
// don't do anything. this update source doesn't use cleared event
}

remove
{
// don't do anything. this update source doesn't use cleared event
}
}

internal void RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs args)
{
// all diagnostics events are serialized.
Expand Down
73 changes: 67 additions & 6 deletions src/Features/Core/Portable/Diagnostics/DiagnosticService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,8 @@ public event EventHandler<DiagnosticsUpdatedArgs> DiagnosticsUpdated
}
}

private void RaiseDiagnosticsUpdated(object sender, DiagnosticsUpdatedArgs args)
private void RaiseDiagnosticsUpdated(IDiagnosticUpdateSource source, DiagnosticsUpdatedArgs args)
{
Contract.ThrowIfNull(sender);
var source = (IDiagnosticUpdateSource)sender;

var ev = _eventMap.GetEventHandlers<EventHandler<DiagnosticsUpdatedArgs>>(DiagnosticsUpdatedEventName);
if (!RequireRunningEventTasks(source, ev))
{
Expand All @@ -75,7 +72,35 @@ private void RaiseDiagnosticsUpdated(object sender, DiagnosticsUpdatedArgs args)
return;
}

ev.RaiseEvent(handler => handler(sender, args));
ev.RaiseEvent(handler => handler(source, args));
}).CompletesAsyncOperation(eventToken);
}

private void RaiseDiagnosticsCleared(IDiagnosticUpdateSource source)
{
var ev = _eventMap.GetEventHandlers<EventHandler<DiagnosticsUpdatedArgs>>(DiagnosticsUpdatedEventName);
if (!RequireRunningEventTasks(source, ev))
{
return;
}

var eventToken = _listener.BeginAsyncOperation(DiagnosticsUpdatedEventName);
_eventQueue.ScheduleTask(() =>
{
using (var pooledObject = SharedPools.Default<List<DiagnosticsUpdatedArgs>>().GetPooledObject())
{
var removed = pooledObject.Object;
if (!ClearDiagnosticsReportedBySource(source, removed))
{
// there is no change, nothing to raise events for.
return;
}

foreach (var args in removed)
{
ev.RaiseEvent(handler => handler(source, args));
}
}
}).CompletesAsyncOperation(eventToken);
}

Expand Down Expand Up @@ -150,10 +175,46 @@ private bool UpdateDataMap(IDiagnosticUpdateSource source, DiagnosticsUpdatedArg
}
}

private bool ClearDiagnosticsReportedBySource(IDiagnosticUpdateSource source, List<DiagnosticsUpdatedArgs> removed)
{
// we expect source who uses this ability to have small number of diagnostics.
lock (_gate)
{
Debug.Assert(_updateSources.Contains(source));

// 2 different workspaces (ex, PreviewWorkspaces) can return same Args.Id, we need to
// distinguish them. so we separate diagnostics per workspace map.
if (!_map.TryGetValue(source, out var workspaceMap))
{
return false;
}

foreach (var (workspace, map) in workspaceMap)
{
foreach (var (id, data) in map)
{
removed.Add(DiagnosticsUpdatedArgs.DiagnosticsRemoved(id, data.Workspace, solution: null, data.ProjectId, data.DocumentId));
}
}

// all diagnostics from the source is cleared
_map.Remove(source);
return true;
}
}

private void OnDiagnosticsUpdated(object sender, DiagnosticsUpdatedArgs e)
{
AssertIfNull(e.Diagnostics);
RaiseDiagnosticsUpdated(sender, e);

// all events are serialized by async event handler
RaiseDiagnosticsUpdated((IDiagnosticUpdateSource)sender, e);
}

private void OnCleared(object sender, EventArgs e)
{
// all events are serialized by async event handler
RaiseDiagnosticsCleared((IDiagnosticUpdateSource)sender);
}

public IEnumerable<DiagnosticData> GetDiagnostics(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ public void Register(IDiagnosticUpdateSource source)
}

_updateSources = _updateSources.Add(source);

source.DiagnosticsUpdated += OnDiagnosticsUpdated;
source.DiagnosticsCleared += OnCleared;
}
}
}
Expand Down
Loading