Skip to content

Commit 6e71293

Browse files
authored
Merge pull request #56041 from dotnet/merges/main-to-main-vs-deps
Merge main to main-vs-deps
2 parents 2aaa031 + 1dd98de commit 6e71293

30 files changed

+232
-76
lines changed

src/Compilers/Core/CodeAnalysisTest/DefaultAnalyzerAssemblyLoaderTests.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,40 @@ public void AssemblyLoading_NativeDependency()
347347
Assert.Equal(FileAttributes.Archive, (FileAttributes)result!);
348348
}
349349

350+
[Fact]
351+
public void AssemblyLoading_Delete()
352+
{
353+
StringBuilder sb = new StringBuilder();
354+
355+
var loader = new DefaultAnalyzerAssemblyLoader();
356+
357+
var tempDir = Temp.CreateDirectory();
358+
var deltaCopy = tempDir.CreateFile("Delta.dll").CopyContentFrom(_testFixture.Delta1.Path);
359+
loader.AddDependencyLocation(deltaCopy.Path);
360+
Assembly delta = loader.LoadFromPath(deltaCopy.Path);
361+
362+
try
363+
{
364+
File.Delete(deltaCopy.Path);
365+
}
366+
catch (UnauthorizedAccessException)
367+
{
368+
return;
369+
}
370+
371+
// The above call may or may not throw depending on the platform configuration.
372+
// If it doesn't throw, we might as well check that things are still functioning reasonably.
373+
374+
var d = delta.CreateInstance("Delta.D");
375+
d!.GetType().GetMethod("Write")!.Invoke(d, new object[] { sb, "Test D" });
376+
377+
var actual = sb.ToString();
378+
Assert.Equal(
379+
@"Delta: Test D
380+
",
381+
actual);
382+
}
383+
350384
#if NETCOREAPP
351385
[Fact]
352386
public void VerifyCompilerAssemblySimpleNames()

src/Compilers/Core/CodeAnalysisTest/ShadowCopyAnalyzerAssemblyLoaderTests.cs

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public void LoadWithDependency()
2929
{
3030
var analyzerDependencyFile = _testFixture.AnalyzerDependency;
3131
var analyzerMainFile = _testFixture.AnalyzerWithDependency;
32-
var loader = new DefaultAnalyzerAssemblyLoader();
32+
var loader = new ShadowCopyAnalyzerAssemblyLoader();
3333
loader.AddDependencyLocation(analyzerDependencyFile.Path);
3434

3535
var analyzerMainReference = new AnalyzerFileReference(analyzerMainFile.Path, loader);
@@ -51,7 +51,7 @@ public void AssemblyLoading_MultipleVersions()
5151
{
5252
StringBuilder sb = new StringBuilder();
5353

54-
var loader = new DefaultAnalyzerAssemblyLoader();
54+
var loader = new ShadowCopyAnalyzerAssemblyLoader();
5555
loader.AddDependencyLocation(_testFixture.Gamma.Path);
5656
loader.AddDependencyLocation(_testFixture.Delta1.Path);
5757
loader.AddDependencyLocation(_testFixture.Epsilon.Path);
@@ -83,5 +83,32 @@ public void AssemblyLoading_MultipleVersions()
8383
actual);
8484
}
8585
}
86+
87+
[Fact]
88+
public void AssemblyLoading_Delete()
89+
{
90+
StringBuilder sb = new StringBuilder();
91+
92+
var loader = new ShadowCopyAnalyzerAssemblyLoader();
93+
94+
var tempDir = Temp.CreateDirectory();
95+
var gammaCopy = tempDir.CreateFile("Gamma.dll").CopyContentFrom(_testFixture.Gamma.Path);
96+
var deltaCopy = tempDir.CreateFile("Delta.dll").CopyContentFrom(_testFixture.Delta1.Path);
97+
loader.AddDependencyLocation(deltaCopy.Path);
98+
loader.AddDependencyLocation(gammaCopy.Path);
99+
100+
Assembly gamma = loader.LoadFromPath(gammaCopy.Path);
101+
var g = gamma.CreateInstance("Gamma.G");
102+
g!.GetType().GetMethod("Write")!.Invoke(g, new object[] { sb, "Test G" });
103+
104+
File.Delete(gammaCopy.Path);
105+
File.Delete(deltaCopy.Path);
106+
107+
var actual = sb.ToString();
108+
Assert.Equal(
109+
@"Delta: Gamma: Test G
110+
",
111+
actual);
112+
}
86113
}
87114
}

src/Compilers/Core/Portable/DiagnosticAnalyzer/DefaultAnalyzerAssemblyLoader.Desktop.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,10 @@ protected override Assembly LoadFromPathImpl(string fullPath)
3434
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
3535
}
3636

37-
return LoadImpl(fullPath);
37+
var pathToLoad = GetPathToLoad(fullPath);
38+
return Assembly.LoadFrom(pathToLoad);
3839
}
3940

40-
protected virtual Assembly LoadImpl(string fullPath) => Assembly.LoadFrom(fullPath);
41-
4241
private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
4342
{
4443
try

src/EditorFeatures/Core/Implementation/InlineRename/CommandHandlers/AbstractRenameCommandHandler.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,18 @@ private void HandlePossibleTypingCommand<TArgs>(TArgs args, Action nextHandler,
7676
{
7777
actionIfInsideActiveSpan(_renameService.ActiveSession, containingSpan);
7878
}
79-
else
79+
else if (_renameService.ActiveSession.IsInOpenTextBuffer(singleSpan.Start))
8080
{
81-
// It's in a read-only area, so let's commit the rename and then let the character go
82-
// through
81+
// It's in a read-only area that is open, so let's commit the rename
82+
// and then let the character go through
8383

8484
CommitIfActiveAndCallNextHandler(args, nextHandler);
8585
}
86+
else
87+
{
88+
nextHandler();
89+
return;
90+
}
8691
}
8792

8893
private void CommitIfActive(EditorCommandArgs args)

src/EditorFeatures/Core/Implementation/InlineRename/InlineRenameSession.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -888,6 +888,9 @@ internal bool TryGetContainingEditableSpan(SnapshotPoint point, out SnapshotSpan
888888
return false;
889889
}
890890

891+
internal bool IsInOpenTextBuffer(SnapshotPoint point)
892+
=> _openTextBuffers.ContainsKey(point.Snapshot.TextBuffer);
893+
891894
internal TestAccessor GetTestAccessor()
892895
=> new TestAccessor(this);
893896

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
#nullable enable
6+
7+
using System.Runtime.Serialization;
8+
using LSP = Microsoft.VisualStudio.LanguageServer.Protocol;
9+
10+
namespace Microsoft.CodeAnalysis.LanguageServer.Handler.SemanticTokens
11+
{
12+
internal sealed class RoslynSemanticTokens : LSP.SemanticTokens
13+
{
14+
/// <summary>
15+
/// True if the token set is complete, meaning it's generated using a full semantic
16+
/// model rather than a frozen one.
17+
/// </summary>
18+
/// <remarks>
19+
/// Certain clients such as Razor need to know whether we're returning partial
20+
/// (i.e. possibly inaccurate) results. This may occur if the full compilation
21+
/// is not yet available.
22+
/// </remarks>
23+
[DataMember(Name = "isFinalized")]
24+
public bool IsFinalized { get; set; }
25+
}
26+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using LSP = Microsoft.VisualStudio.LanguageServer.Protocol;
6+
using System.Runtime.Serialization;
7+
8+
namespace Microsoft.CodeAnalysis.LanguageServer.Handler.SemanticTokens
9+
{
10+
internal sealed class RoslynSemanticTokensDelta : LSP.SemanticTokensDelta
11+
{
12+
/// <summary>
13+
/// True if the token set is complete, meaning it's generated using a full semantic
14+
/// model rather than a frozen one.
15+
/// </summary>
16+
/// <remarks>
17+
/// Certain clients such as Razor need to know whether we're returning partial
18+
/// (i.e. possibly inaccurate) results. This may occur if the full compilation
19+
/// is not yet available.
20+
/// </remarks>
21+
[DataMember(Name = "isFinalized")]
22+
public bool IsFinalized { get; set; }
23+
}
24+
}

src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensEditsHandler.cs

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
namespace Microsoft.CodeAnalysis.LanguageServer.Handler.SemanticTokens
1818
{
1919
/// <summary>
20-
/// Computes the semantic tokens edits for a file. An edit request is received every 500ms,
20+
/// Computes the semantic tokens edits for a file. Clients may make edit requests on a timer,
2121
/// or every time an edit is made by the user.
2222
/// </summary>
2323
internal class SemanticTokensEditsHandler : IRequestHandler<LSP.SemanticTokensDeltaParams, SumType<LSP.SemanticTokens, LSP.SemanticTokensDelta>>
@@ -51,7 +51,7 @@ public SemanticTokensEditsHandler(SemanticTokensCache tokensCache)
5151

5252
// Even though we want to ultimately pass edits back to LSP, we still need to compute all semantic tokens,
5353
// both for caching purposes and in order to have a baseline comparison when computing the edits.
54-
var newSemanticTokensData = await SemanticTokensHelpers.ComputeSemanticTokensDataAsync(
54+
var (newSemanticTokensData, isFinalized) = await SemanticTokensHelpers.ComputeSemanticTokensDataAsync(
5555
context.Document, SemanticTokensCache.TokenTypeToIndex,
5656
range: null, cancellationToken).ConfigureAwait(false);
5757

@@ -64,28 +64,48 @@ public SemanticTokensEditsHandler(SemanticTokensCache tokensCache)
6464
if (oldSemanticTokensData == null)
6565
{
6666
var newResultId = _tokensCache.GetNextResultId();
67-
var updatedTokens = new LSP.SemanticTokens { ResultId = newResultId, Data = newSemanticTokensData };
68-
await _tokensCache.UpdateCacheAsync(
69-
request.TextDocument.Uri, updatedTokens, cancellationToken).ConfigureAwait(false);
70-
return new LSP.SemanticTokens { ResultId = newResultId, Data = newSemanticTokensData };
67+
var updatedTokens = new RoslynSemanticTokens
68+
{
69+
ResultId = newResultId,
70+
Data = newSemanticTokensData,
71+
IsFinalized = isFinalized,
72+
};
73+
74+
if (newSemanticTokensData.Length > 0)
75+
{
76+
await _tokensCache.UpdateCacheAsync(
77+
request.TextDocument.Uri, updatedTokens, cancellationToken).ConfigureAwait(false);
78+
}
79+
80+
return updatedTokens;
7181
}
7282

73-
var resultId = request.PreviousResultId;
7483
var editArray = ComputeSemanticTokensEdits(oldSemanticTokensData, newSemanticTokensData);
84+
var resultId = request.PreviousResultId;
7585

7686
// If we have edits, generate a new ResultId. Otherwise, re-use the previous one.
7787
if (editArray.Length != 0)
7888
{
7989
resultId = _tokensCache.GetNextResultId();
80-
var updatedTokens = new LSP.SemanticTokens { ResultId = resultId, Data = newSemanticTokensData };
81-
await _tokensCache.UpdateCacheAsync(
82-
request.TextDocument.Uri, updatedTokens, cancellationToken).ConfigureAwait(false);
90+
if (newSemanticTokensData.Length > 0)
91+
{
92+
var updatedTokens = new RoslynSemanticTokens
93+
{
94+
ResultId = resultId,
95+
Data = newSemanticTokensData,
96+
IsFinalized = isFinalized
97+
};
98+
99+
await _tokensCache.UpdateCacheAsync(
100+
request.TextDocument.Uri, updatedTokens, cancellationToken).ConfigureAwait(false);
101+
}
83102
}
84103

85-
var edits = new SemanticTokensDelta
104+
var edits = new RoslynSemanticTokensDelta
86105
{
106+
ResultId = resultId,
87107
Edits = editArray,
88-
ResultId = resultId
108+
IsFinalized = isFinalized
89109
};
90110

91111
return edits;

src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensHandler.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,16 @@ public SemanticTokensHandler(SemanticTokensCache tokensCache)
4949
Contract.ThrowIfNull(context.Document, "Document is null.");
5050

5151
var resultId = _tokensCache.GetNextResultId();
52-
var tokensData = await SemanticTokensHelpers.ComputeSemanticTokensDataAsync(
52+
var (tokensData, isFinalized) = await SemanticTokensHelpers.ComputeSemanticTokensDataAsync(
5353
context.Document, SemanticTokensCache.TokenTypeToIndex,
5454
range: null, cancellationToken).ConfigureAwait(false);
5555

56-
var tokens = new LSP.SemanticTokens { ResultId = resultId, Data = tokensData };
57-
await _tokensCache.UpdateCacheAsync(request.TextDocument.Uri, tokens, cancellationToken).ConfigureAwait(false);
56+
var tokens = new RoslynSemanticTokens { ResultId = resultId, Data = tokensData, IsFinalized = isFinalized };
57+
if (tokensData.Length > 0)
58+
{
59+
await _tokensCache.UpdateCacheAsync(request.TextDocument.Uri, tokens, cancellationToken).ConfigureAwait(false);
60+
}
61+
5862
return tokens;
5963
}
6064
}

src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensHelpers.cs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5+
using System;
56
using System.Collections.Generic;
67
using System.Linq;
78
using System.Threading;
@@ -105,7 +106,7 @@ internal class SemanticTokensHelpers
105106
/// <summary>
106107
/// Returns the semantic tokens data for a given document with an optional range.
107108
/// </summary>
108-
internal static async Task<int[]> ComputeSemanticTokensDataAsync(
109+
internal static async Task<(int[], bool isFinalized)> ComputeSemanticTokensDataAsync(
109110
Document document,
110111
Dictionary<string, int> tokenTypesToIndex,
111112
LSP.Range? range,
@@ -116,9 +117,17 @@ internal static async Task<int[]> ComputeSemanticTokensDataAsync(
116117

117118
// By default we calculate the tokens for the full document span, although the user
118119
// can pass in a range if they wish.
119-
var textSpan = range == null ? root.FullSpan : ProtocolConversions.RangeToTextSpan(range, text);
120+
var textSpan = range is null ? root.FullSpan : ProtocolConversions.RangeToTextSpan(range, text);
120121

121-
var classifiedSpans = await Classifier.GetClassifiedSpansAsync(document, textSpan, cancellationToken).ConfigureAwait(false);
122+
// If the full compilation is not yet available, we'll try getting a partial one. It may contain inaccurate
123+
// results but will speed up how quickly we can respond to the client's request.
124+
var frozenDocument = document.WithFrozenPartialSemantics(cancellationToken);
125+
var semanticModel = await frozenDocument.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
126+
Contract.ThrowIfNull(semanticModel);
127+
var isFinalized = document.Project.TryGetCompilation(out var compilation) && compilation == semanticModel.Compilation;
128+
document = frozenDocument;
129+
130+
var classifiedSpans = Classifier.GetClassifiedSpans(semanticModel, textSpan, document.Project.Solution.Workspace, cancellationToken);
122131
Contract.ThrowIfNull(classifiedSpans, "classifiedSpans is null");
123132

124133
// Multi-line tokens are not supported by VS (tracked by https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1265495).
@@ -127,7 +136,7 @@ internal static async Task<int[]> ComputeSemanticTokensDataAsync(
127136

128137
// TO-DO: We should implement support for streaming if LSP adds support for it:
129138
// https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1276300
130-
return ComputeTokens(text.Lines, updatedClassifiedSpans, tokenTypesToIndex);
139+
return (ComputeTokens(text.Lines, updatedClassifiedSpans, tokenTypesToIndex), isFinalized);
131140
}
132141

133142
private static ClassifiedSpan[] ConvertMultiLineToSingleLineSpans(SourceText text, ClassifiedSpan[] classifiedSpans)

0 commit comments

Comments
 (0)