提交 073282b9 编写于 作者: C CyrusNajmabadi

Merge remote-tracking branch 'upstream/master' into usePatternMatchingRefactoring

......@@ -498,4 +498,13 @@
<AdditionalFiles Include="@(PublicAPI)" />
</ItemGroup>
<!-- CPS doesn't show these items by default, but we want to show them. -->
<ItemGroup>
<!-- XAML pages and resources -->
<None Include="@(Page)" />
<None Include="@(Resource)" />
<!-- Special items in VSSDK projects -->
<None Include="@(VSCTCompile)" />
</ItemGroup>
</Project>
......@@ -35,4 +35,4 @@ efforts behind them.
# FAQ
- **Is target version a guarantee?**: No. It's explicitly not a guarantee. This is just the planned and ongoing work to the best of our knowledge at this time.
- **Where are these State values defined?**: Take a look at the [Developing a Language Feature](contributing/Developing a Language Feature.md) document.
- **Where are these State values defined?**: Take a look at the [Developing a Language Feature](contributing/Developing%20a%20Language%20Feature.md) document.
......@@ -3,12 +3,10 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
using Microsoft.CodeAnalysis;
namespace Microsoft.CodeAnalysis.CSharp
{
......@@ -18,18 +16,13 @@ internal partial class LambdaRewriter
/// Perform a first analysis pass in preparation for removing all lambdas from a method body. The entry point is Analyze.
/// The results of analysis are placed in the fields seenLambda, blockParent, variableBlock, captured, and captures.
/// </summary>
internal sealed class Analysis : BoundTreeWalkerWithStackGuardWithoutRecursionOnTheLeftOfBinaryOperator
internal sealed partial class Analysis : BoundTreeWalkerWithStackGuardWithoutRecursionOnTheLeftOfBinaryOperator
{
private readonly MethodSymbol _topLevelMethod;
private MethodSymbol _currentParent;
private BoundNode _currentScope;
// Some syntactic forms have an "implicit" receiver. When we encounter them, we set this to the
// syntax. That way, in case we need to report an error about the receiver, we can use this
// syntax for the location when the receiver was implicit.
private SyntaxNode _syntaxWithReceiver;
/// <summary>
/// Set to true while we are analyzing the interior of an expression lambda.
/// </summary>
......@@ -112,6 +105,8 @@ internal sealed class Analysis : BoundTreeWalkerWithStackGuardWithoutRecursionOn
/// </summary>
public Dictionary<MethodSymbol, BoundNode> LambdaScopes;
private Scope _scopeTree;
private Analysis(MethodSymbol method)
{
Debug.Assert((object)method != null);
......@@ -128,6 +123,7 @@ public static Analysis Analyze(BoundNode node, MethodSymbol method)
private void Analyze(BoundNode node)
{
_scopeTree = ScopeTreeBuilder.Build(node, this);
_currentScope = FindNodeToAnalyze(node);
Debug.Assert(!_inExpressionLambda);
......@@ -185,9 +181,11 @@ private static BoundNode FindNodeToAnalyze(BoundNode node)
}
/// <summary>
/// Optimizes local functions such that if a local function only references other local functions without closures, it itself doesn't need a closure.
/// The old version of <see cref="RemoveUnneededReferences"/> This is still necesary
/// because it modifies <see cref="CapturedVariablesByLambda"/>, which is still used
/// in other areas of the code. Once those uses are gone, this method should be removed.
/// </summary>
private void RemoveUnneededReferences()
private void OldRemoveUnneededReferences()
{
// Note: methodGraph is the inverse of the dependency graph
var methodGraph = new MultiDictionary<MethodSymbol, MethodSymbol>();
......@@ -196,14 +194,14 @@ private void RemoveUnneededReferences()
var visitStack = new Stack<MethodSymbol>();
foreach (var methodKvp in CapturedVariablesByLambda)
{
foreach (var value in methodKvp.Value)
foreach (var capture in methodKvp.Value)
{
var method = value as MethodSymbol;
var method = capture as MethodSymbol;
if (method != null)
{
methodGraph.Add(method, methodKvp.Key);
}
else if (value == _topLevelMethod.ThisParameter)
else if (capture == _topLevelMethod.ThisParameter)
{
if (capturesThis.Add(methodKvp.Key))
{
......@@ -253,18 +251,29 @@ private void RemoveUnneededReferences()
/// </summary>
internal void ComputeLambdaScopesAndFrameCaptures()
{
// We need to keep this around
OldRemoveUnneededReferences();
RemoveUnneededReferences();
LambdaScopes = new Dictionary<MethodSymbol, BoundNode>(ReferenceEqualityComparer.Instance);
NeedsParentFrame = new HashSet<BoundNode>();
RemoveUnneededReferences();
VisitClosures(_scopeTree, (scope, closure) =>
{
if (closure.CapturedVariables.Count > 0)
{
(Scope innermost, Scope outermost) = FindLambdaScopeRange(closure, scope);
RecordClosureScope(innermost, outermost, closure);
}
});
foreach (var kvp in CapturedVariablesByLambda)
(Scope innermost, Scope outermost) FindLambdaScopeRange(Closure closure, Scope closureScope)
{
var lambda = kvp.Key;
var capturedVars = kvp.Value;
Scope innermost = null;
Scope outermost = null;
var allCapturedVars = ArrayBuilder<Symbol>.GetInstance(capturedVars.Count);
allCapturedVars.AddRange(capturedVars);
var capturedVars = PooledHashSet<Symbol>.GetInstance();
capturedVars.AddAll(closure.CapturedVariables);
// If any of the captured variables are local functions we'll need
// to add the captured variables of that local function to the current
......@@ -272,54 +281,63 @@ internal void ComputeLambdaScopesAndFrameCaptures()
// captures anything "above" the current scope then parent frame
// is itself captured (so that the current lambda can call that
// local function).
foreach (var captured in capturedVars)
foreach (var captured in closure.CapturedVariables)
{
var capturedLocalFunction = captured as LocalFunctionSymbol;
if (capturedLocalFunction != null)
if (captured is LocalFunctionSymbol localFunc)
{
allCapturedVars.AddRange(
CapturedVariablesByLambda[capturedLocalFunction]);
capturedVars.AddAll(FindClosureInScope(closureScope, localFunc).CapturedVariables);
}
}
// get innermost and outermost scopes from which a lambda captures
int innermostScopeDepth = -1;
BoundNode innermostScope = null;
int outermostScopeDepth = int.MaxValue;
BoundNode outermostScope = null;
foreach (var captured in allCapturedVars)
for (var curScope = closureScope;
curScope != null && capturedVars.Count > 0;
curScope = curScope.Parent)
{
BoundNode curBlock = null;
int curBlockDepth;
if (!VariableScope.TryGetValue(captured, out curBlock))
{
// this is something that is not defined in a block, like "this"
// Since it is defined outside of the method, the depth is -1
curBlockDepth = -1;
}
else
if (!(capturedVars.Overlaps(curScope.DeclaredVariables) ||
capturedVars.Overlaps(curScope.Closures.Select(c => c.OriginalMethodSymbol))))
{
curBlockDepth = BlockDepth(curBlock);
continue;
}
if (curBlockDepth > innermostScopeDepth)
outermost = curScope;
if (innermost == null)
{
innermostScopeDepth = curBlockDepth;
innermostScope = curBlock;
innermost = curScope;
}
if (curBlockDepth < outermostScopeDepth)
capturedVars.RemoveAll(curScope.DeclaredVariables);
capturedVars.RemoveAll(curScope.Closures.Select(c => c.OriginalMethodSymbol));
}
// If any captured variables are left, they're captured above method scope
if (capturedVars.Count > 0)
{
outermost = null;
}
capturedVars.Free();
return (innermost, outermost);
}
// Walk up the scope tree looking for a closure
Closure FindClosureInScope(Scope startingScope, MethodSymbol closureSymbol)
{
var currentScope = startingScope;
while (currentScope != null)
{
var found = currentScope.Closures.SingleOrDefault(c => c.OriginalMethodSymbol == closureSymbol);
if (found != null)
{
outermostScopeDepth = curBlockDepth;
outermostScope = curBlock;
return found;
}
currentScope = currentScope.Parent;
}
return null;
}
allCapturedVars.Free();
void RecordClosureScope(Scope innermost, Scope outermost, Closure closure)
{
// 1) if there is innermost scope, lambda goes there as we cannot go any higher.
// 2) scopes in [innermostScope, outermostScope) chain need to have access to the parent scope.
//
......@@ -330,27 +348,28 @@ internal void ComputeLambdaScopesAndFrameCaptures()
// Such lambda will be placed in a closure frame that corresponds to the method's outer block
// and this frame will also lift original `this` as a field when created by its parent.
// Note that it is completely irrelevant how deeply the lexical scope of the lambda was originally nested.
if (innermostScope != null)
if (innermost != null)
{
LambdaScopes.Add(lambda, innermostScope);
LambdaScopes.Add(closure.OriginalMethodSymbol, innermost.BoundNode);
// Disable struct closures on methods converted to delegates, as well as on async and iterator methods.
var markAsNoStruct = !CanTakeRefParameters(lambda);
var markAsNoStruct = !CanTakeRefParameters(closure.OriginalMethodSymbol);
if (markAsNoStruct)
{
ScopesThatCantBeStructs.Add(innermostScope);
ScopesThatCantBeStructs.Add(innermost.BoundNode);
}
while (innermostScope != outermostScope)
while (innermost != outermost)
{
NeedsParentFrame.Add(innermostScope);
ScopeParent.TryGetValue(innermostScope, out innermostScope);
if (markAsNoStruct)
NeedsParentFrame.Add(innermost.BoundNode);
innermost = innermost.Parent;
if (markAsNoStruct && innermost != null)
{
ScopesThatCantBeStructs.Add(innermostScope);
ScopesThatCantBeStructs.Add(innermost.BoundNode);
}
}
}
}
}
......@@ -571,38 +590,11 @@ private static bool IsClosure(MethodSymbol symbol)
}
}
private BoundNode VisitSyntaxWithReceiver(SyntaxNode syntax, BoundNode receiver)
{
var previousSyntax = _syntaxWithReceiver;
_syntaxWithReceiver = syntax;
var result = Visit(receiver);
_syntaxWithReceiver = previousSyntax;
return result;
}
public override BoundNode VisitMethodGroup(BoundMethodGroup node)
{
throw ExceptionUtilities.Unreachable;
}
public override BoundNode VisitPropertyAccess(BoundPropertyAccess node)
{
_syntaxWithReceiver = node.Syntax;
return base.VisitPropertyAccess(node);
}
public override BoundNode VisitFieldAccess(BoundFieldAccess node)
{
_syntaxWithReceiver = node.Syntax;
return base.VisitFieldAccess(node);
}
public override BoundNode VisitEventAccess(BoundEventAccess node)
{
_syntaxWithReceiver = node.Syntax;
return base.VisitEventAccess(node);
}
public override BoundNode VisitThisReference(BoundThisReference node)
{
var thisParam = _topLevelMethod.ThisParameter;
......
......@@ -9,7 +9,7 @@
namespace Microsoft.CodeAnalysis.CSharp.Symbols
{
internal sealed class LambdaSymbol : MethodSymbol
internal sealed class LambdaSymbol : SourceMethodSymbol
{
private readonly Symbol _containingSymbol;
private readonly MessageID _messageID;
......@@ -405,6 +405,8 @@ internal override bool GenerateDebugInfo
get { return true; }
}
public override ImmutableArray<TypeParameterConstraintClause> TypeParameterConstraintClauses => ImmutableArray<TypeParameterConstraintClause>.Empty;
internal override int CalculateLocalSyntaxOffset(int localPosition, SyntaxTree localTree)
{
throw ExceptionUtilities.Unreachable;
......
......@@ -31,9 +31,6 @@
<PackageReference Include="xunit" Version="$(xunitVersion)" />
<PackageReference Include="xunit.runner.console" Version="$(xunitrunnerconsoleVersion)" />
</ItemGroup>
<ItemGroup>
<Folder Include="Properties\" />
</ItemGroup>
<ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup>
......
......@@ -201,11 +201,14 @@ protected internal override void AddResponseFileCommands(CommandLineBuilderExten
// If not design time build and the globalSessionGuid property was set then add a -globalsessionguid:<guid>
bool designTime = false;
if (HostObject != null)
if (HostObject is ICscHostObject csHost)
{
var csHost = HostObject as ICscHostObject;
designTime = csHost.IsDesignTime();
}
else if (HostObject != null)
{
throw new InvalidOperationException(string.Format(ErrorString.General_IncorrectHostObject, "Csc", "ICscHostObject"));
}
if (!designTime)
{
if (!string.IsNullOrWhiteSpace(VsSessionGuid))
......@@ -641,12 +644,12 @@ protected override HostObjectInitializationStatus InitializeHostObject()
// NOTE: For compat reasons this must remain ICscHostObject
// we can dynamically test for smarter interfaces later..
using (RCWForCurrentContext<ICscHostObject> hostObject = new RCWForCurrentContext<ICscHostObject>(HostObject as ICscHostObject))
if (HostObject is ICscHostObject hostObjectCOM)
{
ICscHostObject cscHostObject = hostObject.RCW;
if (cscHostObject != null)
using (RCWForCurrentContext<ICscHostObject> hostObject = new RCWForCurrentContext<ICscHostObject>(hostObjectCOM))
{
ICscHostObject cscHostObject = hostObject.RCW;
bool hostObjectSuccessfullyInitialized = InitializeHostCompiler(cscHostObject);
// If we're currently only in design-time (as opposed to build-time),
......@@ -698,10 +701,10 @@ protected override HostObjectInitializationStatus InitializeHostObject()
return HostObjectInitializationStatus.NoActionReturnFailure;
}
}
else
{
Log.LogErrorWithCodeFromResources("General_IncorrectHostObject", "Csc", "ICscHostObject");
}
}
else
{
Log.LogErrorWithCodeFromResources("General_IncorrectHostObject", "Csc", "ICscHostObject");
}
}
......
......@@ -505,11 +505,14 @@ protected internal override void AddResponseFileCommands(CommandLineBuilderExten
// If not design time build and the globalSessionGuid property was set then add a -globalsessionguid:<guid>
bool designTime = false;
if (this.HostObject != null)
if (this.HostObject is IVbcHostObject vbHost)
{
var vbHost = this.HostObject as IVbcHostObject;
designTime = vbHost.IsDesignTime();
}
else if (this.HostObject != null)
{
throw new InvalidOperationException(string.Format(ErrorString.General_IncorrectHostObject, "Vbc", "IVbcHostObject"));
}
if (!designTime)
{
if (!string.IsNullOrWhiteSpace(this.VsSessionGuid))
......@@ -1004,12 +1007,11 @@ protected override HostObjectInitializationStatus InitializeHostObject()
// NOTE: For compat reasons this must remain IVbcHostObject
// we can dynamically test for smarter interfaces later..
using (RCWForCurrentContext<IVbcHostObject> hostObject = new RCWForCurrentContext<IVbcHostObject>(this.HostObject as IVbcHostObject))
if (HostObject is IVbcHostObject hostObjectCOM)
{
IVbcHostObject vbcHostObject = hostObject.RCW;
if (vbcHostObject != null)
using (RCWForCurrentContext<IVbcHostObject> hostObject = new RCWForCurrentContext<IVbcHostObject>(hostObjectCOM))
{
IVbcHostObject vbcHostObject = hostObject.RCW;
bool hostObjectSuccessfullyInitialized = InitializeHostCompiler(vbcHostObject);
// If we're currently only in design-time (as opposed to build-time),
......@@ -1061,10 +1063,10 @@ protected override HostObjectInitializationStatus InitializeHostObject()
return HostObjectInitializationStatus.NoActionReturnFailure;
}
}
else
{
Log.LogErrorWithCodeFromResources("General_IncorrectHostObject", "Vbc", "IVbcHostObject");
}
}
else
{
Log.LogErrorWithCodeFromResources("General_IncorrectHostObject", "Vbc", "IVbcHostObject");
}
}
......
......@@ -85,7 +85,7 @@ public void Add(K key, V value)
V value;
if (!TryGetValue(key, out value))
{
throw new InvalidOperationException("key not found");
throw new KeyNotFoundException($"Could not find key {key}");
}
return value;
......
......@@ -4499,5 +4499,43 @@ void M()
}";
await TestInRegularAndScriptAsync(code, expected, ignoreTrivia: false);
}
[WorkItem(19247, "https://github.com/dotnet/roslyn/issues/19247")]
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInlineTemporary)]
public async Task InlineTemporary_LocalFunction()
{
await TestInRegularAndScriptAsync(
@"
using System;
class C
{
void M()
{
var [|testStr|] = ""test"";
expand(testStr);
void expand(string str)
{
}
}
}",
@"
using System;
class C
{
void M()
{
expand(""test"");
void expand(string str)
{
}
}
}",
ignoreTrivia: false);
}
}
}
......@@ -7411,6 +7411,188 @@ private void GetEvaluationRuleNames()
IEnumerable < Int32
return ImmutableArray.CreateRange();
}
}");
}
[WorkItem(18988, "https://github.com/dotnet/roslyn/issues/18988")]
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateVariable)]
public async Task GroupNonReadonlyFieldsTogether()
{
await TestInRegularAndScriptAsync(@"
class C
{
public bool isDisposed;
public readonly int x;
public readonly int m;
public C()
{
this.[|y|] = 0;
}
}",
@"
class C
{
public bool isDisposed;
private int y;
public readonly int x;
public readonly int m;
public C()
{
this.y = 0;
}
}");
}
[WorkItem(18988, "https://github.com/dotnet/roslyn/issues/18988")]
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateVariable)]
public async Task GroupReadonlyFieldsTogether()
{
await TestInRegularAndScriptAsync(@"
class C
{
public readonly int x;
public readonly int m;
public bool isDisposed;
public C()
{
this.[|y|] = 0;
}
}",
@"
class C
{
public readonly int x;
public readonly int m;
private readonly int y;
public bool isDisposed;
public C()
{
this.y = 0;
}
}", index: 1);
}
[WorkItem(20791, "https://github.com/dotnet/roslyn/issues/20791")]
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateVariable)]
public async Task TestWithOutOverload1()
{
await TestInRegularAndScriptAsync(
@"class Class
{
void Method()
{
Foo(out [|foo|]);
}
void Foo(int i) { }
void Foo(out bool b) { }
}",
@"class Class
{
private bool foo;
void Method()
{
Foo(out foo);
}
void Foo(int i) { }
void Foo(out bool b) { }
}");
}
[WorkItem(20791, "https://github.com/dotnet/roslyn/issues/20791")]
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateVariable)]
public async Task TestWithOutOverload2()
{
await TestInRegularAndScriptAsync(
@"class Class
{
void Method()
{
Foo([|foo|]);
}
void Foo(out bool b) { }
void Foo(int i) { }
}",
@"class Class
{
private int foo;
void Method()
{
Foo(foo);
}
void Foo(out bool b) { }
void Foo(int i) { }
}");
}
[WorkItem(20791, "https://github.com/dotnet/roslyn/issues/20791")]
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateVariable)]
public async Task TestWithRefOverload1()
{
await TestInRegularAndScriptAsync(
@"class Class
{
void Method()
{
Foo(ref [|foo|]);
}
void Foo(int i) { }
void Foo(ref bool b) { }
}",
@"class Class
{
private bool foo;
void Method()
{
Foo(ref foo);
}
void Foo(int i) { }
void Foo(ref bool b) { }
}");
}
[WorkItem(20791, "https://github.com/dotnet/roslyn/issues/20791")]
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateVariable)]
public async Task TestWithRefOverload2()
{
await TestInRegularAndScriptAsync(
@"class Class
{
void Method()
{
Foo([|foo|]);
}
void Foo(ref bool b) { }
void Foo(int i) { }
}",
@"class Class
{
private int foo;
void Method()
{
Foo(foo);
}
void Foo(ref bool b) { }
void Foo(int i) { }
}");
}
}
......
......@@ -261,8 +261,8 @@ public C([||]string s)
@"
class C
{
private int s;
private readonly string s1;
private int s;
public C(string s)
{
......
......@@ -360,6 +360,75 @@ void Foo()
string s1 = default;
string s2 = default(int);
}
}", parseOptions: s_parseOptions);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseDefaultLiteral)]
public async Task TestDoNotOfferIfTypeWouldChange()
{
await TestMissingInRegularAndScriptAsync(
@"
struct S
{
void M()
{
var s = new S();
s.Equals([||]default(S));
}
public override bool Equals(object obj)
{
return base.Equals(obj);
}
}", new TestParameters(parseOptions: s_parseOptions));
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseDefaultLiteral)]
public async Task TestDoNotOfferIfTypeWouldChange2()
{
await TestMissingInRegularAndScriptAsync(
@"
struct S<T>
{
void M()
{
var s = new S<int>();
s.Equals([||]default(S<int>));
}
public override bool Equals(object obj)
{
return base.Equals(obj);
}
}", new TestParameters(parseOptions: s_parseOptions));
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseDefaultLiteral)]
public async Task TestOnShadowedMethod()
{
await TestAsync(
@"
struct S
{
void M()
{
var s = new S();
s.Equals([||]default(S));
}
public new bool Equals(S s) => true;
}",
@"
struct S
{
void M()
{
var s = new S();
s.Equals(default);
}
public new bool Equals(S s) => true;
}", parseOptions: s_parseOptions);
}
}
......
......@@ -427,6 +427,52 @@ void M()
}", options: UseExpressionBody, parseOptions: CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp5));
}
[WorkItem(20352, "https://github.com/dotnet/roslyn/issues/20352")]
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)]
public async Task TestDoNotOfferToConvertToBlockIfExpressionBodyPreferredIfCSharp6()
{
await TestMissingAsync(
@"
using System;
class C
{
void M() [|=>|] 0;
}", new TestParameters(options: UseExpressionBody, parseOptions: CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp6)));
}
[WorkItem(20352, "https://github.com/dotnet/roslyn/issues/20352")]
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)]
public async Task TestOfferToConvertToExpressionIfCSharp6()
{
await TestAsync(
@"
using System;
class C
{
void M() { [|return|] 0; }
}",
@"
using System;
class C
{
void M() => 0;
}", options: UseExpressionBody, parseOptions: CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp6));
}
[WorkItem(20352, "https://github.com/dotnet/roslyn/issues/20352")]
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)]
public async Task TestDoNotOfferToConvertToExpressionInCSharp6IfThrowExpression()
{
await TestMissingAsync(
@"
using System;
class C
{
// throw expressions not supported in C# 6.
void M() { [|throw|] new Exception(); }
}", new TestParameters(options: UseExpressionBody, parseOptions: CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp6)));
}
[WorkItem(20362, "https://github.com/dotnet/roslyn/issues/20362")]
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)]
public async Task TestOfferToConvertToBlockEvenIfExpressionBodyPreferredIfPriorToCSharp6_FixAll()
......
......@@ -149,8 +149,5 @@
<PublicAPI Include="PublicAPI.Shipped.txt" />
<PublicAPI Include="PublicAPI.Unshipped.txt" />
</ItemGroup>
<ItemGroup>
<Folder Include="Extensibility\Navigation\" />
</ItemGroup>
<Import Project="..\..\..\build\Targets\Imports.targets" />
</Project>
\ No newline at end of file
......@@ -75,7 +75,7 @@ private sealed partial class TagSource : ForegroundThreadAffinitizedObject
#endregion
public event Action<ICollection<KeyValuePair<ITextBuffer, DiffResult>>> TagsChangedForBuffer;
public event Action<ICollection<KeyValuePair<ITextBuffer, DiffResult>>, bool> TagsChangedForBuffer;
public event EventHandler Paused;
public event EventHandler Resumed;
......@@ -87,6 +87,16 @@ private sealed partial class TagSource : ForegroundThreadAffinitizedObject
/// </summary>
private readonly CancellationTokenSource _initialComputationCancellationTokenSource = new CancellationTokenSource();
/// <summary>
/// Whether or not we've gotten any change notifications from our <see cref="ITaggerEventSource"/>.
/// The first time we hear about changes, we fast track getting tags and reporting
/// them to the UI.
///
/// We use an int so we can use <see cref="Interlocked.CompareExchange(ref int, int, int)"/>
/// to read/set this.
/// </summary>
private int _seenEventSourceChanged;
public TaggerDelay AddedTagNotificationDelay => _dataSource.AddedTagNotificationDelay;
public TaggerDelay RemovedTagNotificationDelay => _dataSource.RemovedTagNotificationDelay;
......@@ -118,20 +128,21 @@ private sealed partial class TagSource : ForegroundThreadAffinitizedObject
Connect();
// Kick off a task to immediately compute the initial set of tags. This work should
// not be cancellable (except if we get completely released), even if more events come
// in. That way we can get the initial set of results for the buffer as quickly as
// possible, without kicking the work down the road.
var initialTagsCancellationToken = _initialComputationCancellationTokenSource.Token;
// Start computing the initial set of tags immediately. We want to get the UI
// to a complete state as soon as possible.
ComputeInitialTags();
}
private void ComputeInitialTags()
{
// Note: we always kick this off to the new UI pump instead of computing tags right
// on this thread. The reason for that is that we may be getting created at a time
// when the view itself is initializing. As such the view is not in a state where
// we want code touching it.
RegisterNotification(
() => RecomputeTagsForeground(cancellationTokenOpt: initialTagsCancellationToken),
() => RecomputeTagsForeground(initialTags: true),
delay: 0,
cancellationToken: initialTagsCancellationToken);
cancellationToken: GetCancellationToken(initialTags: true));
}
private ITaggerEventSource CreateEventSource()
......@@ -217,17 +228,6 @@ private bool UpToDate
public void RegisterNotification(Action action, int delay, CancellationToken cancellationToken)
=> _notificationService.RegisterNotification(action, delay, _asyncListener.BeginAsyncOperation("TagSource"), cancellationToken);
private void RecalculateTagsOnChanged(TaggerEventArgs e)
{
// First, cancel any previous requests (either still queued, or started). We no longer
// want to continue it if new changes have come in.
_workQueue.CancelCurrentWork();
RegisterNotification(
() => RecomputeTagsForeground(cancellationTokenOpt: null),
(int)e.Delay.ComputeTimeDelay(_subjectBuffer).TotalMilliseconds,
_workQueue.CancellationToken);
}
private void Connect()
{
_workQueue.AssertIsForeground();
......@@ -289,12 +289,14 @@ private void RaiseTagsChanged(ITextBuffer buffer, DiffResult difference)
}
RaiseTagsChanged(SpecializedCollections.SingletonCollection(
new KeyValuePair<ITextBuffer, DiffResult>(buffer, difference)));
new KeyValuePair<ITextBuffer, DiffResult>(buffer, difference)),
initialTags: false);
}
private void RaiseTagsChanged(ICollection<KeyValuePair<ITextBuffer, DiffResult>> collection)
private void RaiseTagsChanged(
ICollection<KeyValuePair<ITextBuffer, DiffResult>> collection, bool initialTags)
{
TagsChangedForBuffer?.Invoke(collection);
TagsChangedForBuffer?.Invoke(collection, initialTags);
}
private void RaisePaused()
......
......@@ -56,7 +56,26 @@ private void OnUIUpdatesResumed(object sender, EventArgs e)
}
private void OnEventSourceChanged(object sender, TaggerEventArgs e)
=> RecalculateTagsOnChanged(e);
{
var result = Interlocked.CompareExchange(ref _seenEventSourceChanged, value: 1, comparand: 0);
if (result == 0)
{
// this is the first time we're hearing about changes from our event-source.
// Don't have any delay here. We want to just compute the tags and display
// them as soon as we possibly can.
ComputeInitialTags();
}
else
{
// First, cancel any previous requests (either still queued, or started). We no longer
// want to continue it if new changes have come in.
_workQueue.CancelCurrentWork();
RegisterNotification(
() => RecomputeTagsForeground(initialTags: false),
(int)e.Delay.ComputeTimeDelay().TotalMilliseconds,
GetCancellationToken(initialTags: false));
}
}
private void OnCaretPositionChanged(object sender, CaretPositionChangedEventArgs e)
{
......@@ -261,11 +280,15 @@ where tagSpan.Span.IntersectsWith(originalSpan)
}
/// <summary>
/// Called on the foreground thread. Can be passed an optional cancellationToken
/// that controls the work being done. If no cancellation token is passed, the one
/// from the <see cref="_workQueue"/> is used.
/// Called on the foreground thread. Passed a boolean to say if we're computing the
/// initial set of tags or not. If we're computing the initial set of tags, we lower
/// all our delays so that we can get results to the screen as quickly as possible.
///
/// This gives a good experience when a document is opened as the document appears
/// complete almost immediately. Once open though, our normal delays come into play
/// so as to not cause a flashy experience.
/// </summary>
private void RecomputeTagsForeground(CancellationToken? cancellationTokenOpt)
private void RecomputeTagsForeground(bool initialTags)
{
_workQueue.AssertIsForeground();
......@@ -278,7 +301,7 @@ private void RecomputeTagsForeground(CancellationToken? cancellationTokenOpt)
// tag production stage finally completes.
this.UpToDate = false;
var cancellationToken = cancellationTokenOpt ?? _workQueue.CancellationToken;
var cancellationToken = GetCancellationToken(initialTags);
var spansToTag = GetSpansAndDocumentsToTag();
// Make a copy of all the data we need while we're on the foreground. Then
......@@ -290,11 +313,28 @@ private void RecomputeTagsForeground(CancellationToken? cancellationTokenOpt)
var oldState = this.State;
_workQueue.EnqueueBackgroundTask(
ct => this.RecomputeTagsAsync(oldState, caretPosition, textChangeRange, spansToTag, oldTagTrees, ct),
ct => this.RecomputeTagsAsync(
oldState, caretPosition, textChangeRange, spansToTag, oldTagTrees, initialTags, ct),
GetType().Name + ".RecomputeTags", cancellationToken);
}
}
/// <summary>
/// Get's the cancellation token that will control the processing of this set of
/// tags. If this is the initial set of tags, we have a single cancellation token
/// that can't be interrupted *unless* the entire tagger is shut down. If this
/// is anything after the initial set of tags, then we'll control things with a
/// cancellation token that is triggered every time we hear about new changes.
///
/// This is a 'kick the can down the road' approach whereby we keep delaying
/// producing tags (and updating the UI) until a reasonable pause has happened.
/// This approach helps prevent flashing in the UI.
/// </summary>
private CancellationToken GetCancellationToken(bool initialTags)
=> initialTags
? _initialComputationCancellationTokenSource.Token
: _workQueue.CancellationToken;
private List<DocumentSnapshotSpan> GetSpansAndDocumentsToTag()
{
_workQueue.AssertIsForeground();
......@@ -535,6 +575,7 @@ private IEnumerable<ITagSpan<TTag>> GetNonIntersectingTagSpans(IEnumerable<Snaps
TextChangeRange? textChangeRange,
List<DocumentSnapshotSpan> spansToTag,
ImmutableDictionary<ITextBuffer, TagSpanIntervalTree<TTag>> oldTagTrees,
bool initialTags,
CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
......@@ -543,7 +584,7 @@ private IEnumerable<ITagSpan<TTag>> GetNonIntersectingTagSpans(IEnumerable<Snaps
oldState, spansToTag, caretPosition, textChangeRange, oldTagTrees, cancellationToken);
await ProduceTagsAsync(context).ConfigureAwait(false);
ProcessContext(spansToTag, oldTagTrees, context);
ProcessContext(spansToTag, oldTagTrees, context, initialTags);
}
private bool ShouldSkipTagProduction()
......@@ -579,7 +620,8 @@ private void ProduceTagsSynchronously(TaggerContext<TTag> context)
private void ProcessContext(
List<DocumentSnapshotSpan> spansToTag,
ImmutableDictionary<ITextBuffer, TagSpanIntervalTree<TTag>> oldTagTrees,
TaggerContext<TTag> context)
TaggerContext<TTag> context,
bool initialTags)
{
var buffersToTag = spansToTag.Select(dss => dss.SnapshotSpan.Snapshot.TextBuffer).ToSet();
......@@ -588,7 +630,9 @@ private void ProduceTagsSynchronously(TaggerContext<TTag> context)
.ToLookup(t => t.Span.Snapshot.TextBuffer);
var newTagTrees = ConvertToTagTrees(oldTagTrees, newTagsByBuffer, context._spansTagged);
ProcessNewTagTrees(spansToTag, oldTagTrees, newTagTrees, context.State, context.CancellationToken);
ProcessNewTagTrees(
spansToTag, oldTagTrees, newTagTrees,
context.State, initialTags, context.CancellationToken);
}
private void ProcessNewTagTrees(
......@@ -596,6 +640,7 @@ private void ProduceTagsSynchronously(TaggerContext<TTag> context)
ImmutableDictionary<ITextBuffer, TagSpanIntervalTree<TTag>> oldTagTrees,
ImmutableDictionary<ITextBuffer, TagSpanIntervalTree<TTag>> newTagTrees,
object newState,
bool initialTags,
CancellationToken cancellationToken)
{
var bufferToChanges = new Dictionary<ITextBuffer, DiffResult>();
......@@ -630,14 +675,14 @@ private void ProduceTagsSynchronously(TaggerContext<TTag> context)
if (_workQueue.IsForeground())
{
// If we're on the foreground already, we can just update our internal state directly.
UpdateStateAndReportChanges(newTagTrees, bufferToChanges, newState);
UpdateStateAndReportChanges(newTagTrees, bufferToChanges, newState, initialTags);
}
else
{
// Otherwise report back on the foreground asap to update the state and let our
// clients know about the change.
RegisterNotification(
() => UpdateStateAndReportChanges(newTagTrees, bufferToChanges, newState),
RegisterNotification(() => UpdateStateAndReportChanges(
newTagTrees, bufferToChanges, newState, initialTags),
delay: 0,
cancellationToken: cancellationToken);
}
......@@ -646,7 +691,8 @@ private void ProduceTagsSynchronously(TaggerContext<TTag> context)
private void UpdateStateAndReportChanges(
ImmutableDictionary<ITextBuffer, TagSpanIntervalTree<TTag>> newTagTrees,
Dictionary<ITextBuffer, DiffResult> bufferToChanges,
object newState)
object newState,
bool initialTags)
{
_workQueue.AssertIsForeground();
......@@ -675,7 +721,7 @@ private void ProduceTagsSynchronously(TaggerContext<TTag> context)
// AsynchronousTagger's BatchChangeNotifier. If we tell it about enough changes
// to a file, it will coalesce them into one large change to keep chattiness with
// the editor down.
RaiseTagsChanged(bufferToChanges);
RaiseTagsChanged(bufferToChanges, initialTags);
}
private DiffResult ComputeDifference(
......@@ -732,7 +778,7 @@ public TagSpanIntervalTree<TTag> GetAccurateTagIntervalTreeForBuffer(ITextBuffer
ProduceTagsSynchronously(context);
ProcessContext(spansToTag, oldTagTrees, context);
ProcessContext(spansToTag, oldTagTrees, context, initialTags: false);
}
Debug.Assert(this.UpToDate);
......
......@@ -110,7 +110,8 @@ private void OnPaused(object sender, EventArgs e)
private void OnResumed(object sender, EventArgs e)
=> _batchChangeNotifier.Resume();
private void OnTagsChangedForBuffer(ICollection<KeyValuePair<ITextBuffer, DiffResult>> changes)
private void OnTagsChangedForBuffer(
ICollection<KeyValuePair<ITextBuffer, DiffResult>> changes, bool initialTags)
{
_tagSource.AssertIsForeground();
......@@ -129,8 +130,8 @@ private void OnTagsChangedForBuffer(ICollection<KeyValuePair<ITextBuffer, DiffRe
// Now report them back to the UI on the main thread.
// We ask to update UI immediately for removed tags
NotifyEditors(change.Value.Removed, _tagSource.RemovedTagNotificationDelay);
NotifyEditors(change.Value.Added, _tagSource.AddedTagNotificationDelay);
NotifyEditors(change.Value.Removed, initialTags ? TaggerDelay.NearImmediate : _tagSource.RemovedTagNotificationDelay);
NotifyEditors(change.Value.Added, initialTags ? TaggerDelay.NearImmediate : _tagSource.AddedTagNotificationDelay);
}
}
......
......@@ -201,7 +201,8 @@ private void VerifyBreakIntoCharacterParts(string original, params string[] part
[InlineData("[|AbCd|]xxx[|Ef|]Cd[|Gh|]", "AbCdEfGh", PatternMatchKind.CamelCaseNonContiguousPrefix, CaseSensitive)]
[InlineData("A[|BCD|]EFGH", "bcd", PatternMatchKind.Substring, CaseInsensitive)]
[InlineData("Abcdefghij[|EfgHij|]", "efghij", PatternMatchKind.CamelCaseSubstring, CaseInsensitive)]
[InlineData("FogBar[|ChangedEventArgs|]", "changedeventargs", PatternMatchKind.Substring, CaseInsensitive)]
[InlineData("Abcdefghij[|EfgHij|]", "efghij", PatternMatchKind.Substring, CaseInsensitive)]
[InlineData("[|F|]og[|B|]ar", "FB", PatternMatchKind.CamelCaseExact, CaseSensitive)]
[InlineData("[|Fo|]g[|B|]ar", "FoB", PatternMatchKind.CamelCaseExact, CaseSensitive)]
......@@ -220,7 +221,6 @@ private void VerifyBreakIntoCharacterParts(string original, params string[] part
[InlineData("[|F|]og[|_B|]ar", "F_b", PatternMatchKind.CamelCaseExact, CaseInsensitive)]
[InlineData("[|_F|]og[|B|]ar", "_fB", PatternMatchKind.CamelCaseExact, CaseInsensitive)]
[InlineData("[|F|]og[|_B|]ar", "f_B", PatternMatchKind.CamelCaseExact, CaseInsensitive)]
[InlineData("FogBar[|ChangedEventArgs|]", "changedeventargs", PatternMatchKind.CamelCaseSubstring, CaseInsensitive)]
[InlineData("[|Si|]mple[|UI|]Element", "SiUI", PatternMatchKind.CamelCaseExact, CaseSensitive)]
......@@ -472,23 +472,23 @@ public void TryMatchSingleWordPattern_CultureAwareSingleWordPreferCaseSensitiveE
}
}
private static IList<string> PartListToSubstrings(string identifier, StringBreaks parts)
private static ImmutableArray<string> PartListToSubstrings(string identifier, ArrayBuilder<TextSpan> parts)
{
var result = new List<string>();
for (int i = 0, n = parts.GetCount(); i < n; i++)
var result = ArrayBuilder<string>.GetInstance();
foreach (var span in parts)
{
var span = parts[i];
result.Add(identifier.Substring(span.Start, span.Length));
}
return result;
parts.Free();
return result.ToImmutableAndFree();
}
private static IList<string> BreakIntoCharacterParts(string identifier)
=> PartListToSubstrings(identifier, StringBreaker.BreakIntoCharacterParts(identifier));
private static ImmutableArray<string> BreakIntoCharacterParts(string identifier)
=> PartListToSubstrings(identifier, StringBreaker.GetCharacterParts(identifier));
private static IList<string> BreakIntoWordParts(string identifier)
=> PartListToSubstrings(identifier, StringBreaker.BreakIntoWordParts(identifier));
private static ImmutableArray<string> BreakIntoWordParts(string identifier)
=> PartListToSubstrings(identifier, StringBreaker.GetWordParts(identifier));
private static PatternMatch? TestNonFuzzyMatch(string candidate, string pattern)
{
......
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Imports System.Collections.Immutable
Imports Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles
Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests
......@@ -20,7 +21,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests
End Function
Private Sub TestNameCreation(namingStyle As MutableNamingStyle, expectedName As String, ParamArray words As String())
Assert.Equal(expectedName, namingStyle.NamingStyle.CreateName(words))
Assert.Equal(expectedName, namingStyle.NamingStyle.CreateName(words.ToImmutableArray()))
End Sub
Private Sub TestNameCompliance(namingStyle As MutableNamingStyle, candidateName As String)
......
......@@ -2657,5 +2657,62 @@ end class",
end sub
end class")
End Function
<WorkItem(18988, "https://github.com/dotnet/roslyn/issues/18988")>
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateVariable)>
Public Async Function GroupNonReadonlyFieldsTogether() As Task
Await TestInRegularAndScriptAsync(
"
class C
public isDisposed as boolean
public readonly x as integer
public readonly m as integer
public sub new()
me.[|y|] = 0
end sub
end class",
"
class C
public isDisposed as boolean
Private y As Integer
public readonly x as integer
public readonly m as integer
public sub new()
me.y = 0
end sub
end class")
End Function
<WorkItem(18988, "https://github.com/dotnet/roslyn/issues/18988")>
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateVariable)>
Public Async Function GroupReadonlyFieldsTogether() As Task
Await TestInRegularAndScriptAsync("
class C
public readonly x as integer
public readonly m as integer
public isDisposed as boolean
public sub new()
me.[|y|] = 0
end sub
end class",
"
class C
public readonly x as integer
public readonly m as integer
Private ReadOnly y As Integer
public isDisposed as boolean
public sub new()
me.y = 0
end sub
end class", index:=1)
End Function
End Class
End Namespace
......@@ -202,9 +202,8 @@ class C
end class",
"
class C
private s As Integer
Private ReadOnly s1 As String
private s As Integer
public sub new(s As String)
s1 = s
......
......@@ -108,5 +108,11 @@ internal override void RemoveDataItem(DkmClrAppDomain appDomain)
{
appDomain.RemoveMetadataContext<CSharpMetadataContext>();
}
internal override ImmutableArray<MetadataBlock> GetMetadataBlocks(DkmClrAppDomain appDomain, DkmClrRuntimeInstance runtimeInstance)
{
var previous = appDomain.GetMetadataContext<CSharpMetadataContext>();
return runtimeInstance.GetMetadataBlocks(appDomain, previous.MetadataBlocks);
}
}
}
......@@ -134,7 +134,7 @@ internal override CSharpCompilation GetCompilation(DkmClrModuleInstance moduleIn
{
var appDomain = moduleInstance.AppDomain;
var previous = appDomain.GetMetadataContext<CSharpMetadataContext>();
var metadataBlocks = moduleInstance.RuntimeInstance.GetMetadataBlocks(appDomain);
var metadataBlocks = moduleInstance.RuntimeInstance.GetMetadataBlocks(appDomain, previous.MetadataBlocks);
CSharpCompilation compilation;
if (previous.Matches(metadataBlocks))
......
......@@ -43,11 +43,15 @@ private static IEnumerable<DkmClrModuleInstance> GetModulesInAppDomain(this DkmC
});
}
internal unsafe static ImmutableArray<MetadataBlock> GetMetadataBlocks(this DkmClrRuntimeInstance runtime, DkmClrAppDomain appDomain)
internal static ImmutableArray<MetadataBlock> GetMetadataBlocks(
this DkmClrRuntimeInstance runtime,
DkmClrAppDomain appDomain,
ImmutableArray<MetadataBlock> previousMetadataBlocks)
{
var builder = ArrayBuilder<MetadataBlock>.GetInstance();
IntPtr ptr;
uint size;
int index = 0;
foreach (DkmClrModuleInstance module in runtime.GetModulesInAppDomain(appDomain))
{
MetadataBlock block;
......@@ -55,7 +59,7 @@ internal unsafe static ImmutableArray<MetadataBlock> GetMetadataBlocks(this DkmC
{
ptr = module.GetMetaDataBytesPtr(out size);
Debug.Assert(size > 0);
block = GetMetadataBlock(ptr, size);
block = GetMetadataBlock(previousMetadataBlocks, index, ptr, size);
}
catch (NotImplementedException e) when (module is DkmClrNcModuleInstance)
{
......@@ -68,10 +72,11 @@ internal unsafe static ImmutableArray<MetadataBlock> GetMetadataBlocks(this DkmC
}
Debug.Assert(block.ModuleVersionId == module.Mvid);
builder.Add(block);
index++;
}
// Include "intrinsic method" assembly.
ptr = runtime.GetIntrinsicAssemblyMetaDataBytesPtr(out size);
builder.Add(GetMetadataBlock(ptr, size));
builder.Add(GetMetadataBlock(previousMetadataBlocks, index, ptr, size));
return builder.ToImmutableAndFree();
}
......@@ -142,6 +147,20 @@ private unsafe static MetadataBlock GetMetadataBlock(IntPtr ptr, uint size)
return new MetadataBlock(moduleVersionId, generationId, ptr, (int)size);
}
private static MetadataBlock GetMetadataBlock(ImmutableArray<MetadataBlock> previousMetadataBlocks, int index, IntPtr ptr, uint size)
{
if (!previousMetadataBlocks.IsDefault && index < previousMetadataBlocks.Length)
{
var previousBlock = previousMetadataBlocks[index];
if (previousBlock.Pointer == ptr && previousBlock.Size == size)
{
return previousBlock;
}
}
return GetMetadataBlock(ptr, size);
}
internal static object GetSymReader(this DkmClrModuleInstance clrModule)
{
var module = clrModule.Module; // Null if there are no symbols.
......
......@@ -42,8 +42,8 @@ static ExpressionCompiler()
: GetAliases(runtimeInstance, inspectionContext); // NB: Not affected by retrying.
string error;
var r = this.CompileWithRetry(
moduleInstance,
runtimeInstance.GetMetadataBlocks(moduleInstance.AppDomain),
moduleInstance.AppDomain,
runtimeInstance,
(blocks, useReferencedModulesOnly) => CreateMethodContext(instructionAddress, blocks, useReferencedModulesOnly),
(context, diagnostics) =>
{
......@@ -105,8 +105,8 @@ private static ImmutableArray<Alias> GetAliases(DkmClrRuntimeInstance runtimeIns
var runtimeInstance = instructionAddress.RuntimeInstance;
var aliases = GetAliases(runtimeInstance, inspectionContext); // NB: Not affected by retrying.
var r = this.CompileWithRetry(
moduleInstance,
runtimeInstance.GetMetadataBlocks(moduleInstance.AppDomain),
moduleInstance.AppDomain,
runtimeInstance,
(blocks, useReferencedModulesOnly) => CreateMethodContext(instructionAddress, blocks, useReferencedModulesOnly),
(context, diagnostics) =>
{
......@@ -142,8 +142,8 @@ private static ImmutableArray<Alias> GetAliases(DkmClrRuntimeInstance runtimeIns
var runtimeInstance = instructionAddress.RuntimeInstance;
var aliases = GetAliases(runtimeInstance, lValue.InspectionContext); // NB: Not affected by retrying.
var r = this.CompileWithRetry(
moduleInstance,
runtimeInstance.GetMetadataBlocks(moduleInstance.AppDomain),
moduleInstance.AppDomain,
runtimeInstance,
(blocks, useReferencedModulesOnly) => CreateMethodContext(instructionAddress, blocks, useReferencedModulesOnly),
(context, diagnostics) =>
{
......@@ -180,8 +180,8 @@ private static ImmutableArray<Alias> GetAliases(DkmClrRuntimeInstance runtimeIns
var runtimeInstance = moduleInstance.RuntimeInstance;
var appDomain = moduleInstance.AppDomain;
var compileResult = this.CompileWithRetry(
moduleInstance,
runtimeInstance.GetMetadataBlocks(appDomain),
appDomain,
runtimeInstance,
(blocks, useReferencedModulesOnly) => CreateTypeContext(appDomain, blocks, moduleInstance.Mvid, token, useReferencedModulesOnly),
(context, diagnostics) =>
{
......@@ -266,6 +266,10 @@ private void RemoveDataItemIfNecessary(DkmModuleInstance moduleInstance)
internal abstract void RemoveDataItem(DkmClrAppDomain appDomain);
internal abstract ImmutableArray<MetadataBlock> GetMetadataBlocks(
DkmClrAppDomain appDomain,
DkmClrRuntimeInstance runtimeInstance);
private EvaluationContextBase CreateMethodContext(
DkmClrInstructionAddress instructionAddress,
ImmutableArray<MetadataBlock> metadataBlocks,
......@@ -305,18 +309,19 @@ private void RemoveDataItemIfNecessary(DkmModuleInstance moduleInstance)
internal delegate TResult CompileDelegate<TResult>(EvaluationContextBase context, DiagnosticBag diagnostics);
private TResult CompileWithRetry<TResult>(
DkmClrModuleInstance moduleInstance,
ImmutableArray<MetadataBlock> metadataBlocks,
DkmClrAppDomain appDomain,
DkmClrRuntimeInstance runtimeInstance,
CreateContextDelegate createContext,
CompileDelegate<TResult> compile,
out string errorMessage)
{
var metadataBlocks = GetMetadataBlocks(appDomain, runtimeInstance);
return CompileWithRetry(
metadataBlocks,
this.DiagnosticFormatter,
createContext,
compile,
(AssemblyIdentity assemblyIdentity, out uint size) => moduleInstance.AppDomain.GetMetaDataBytesPtr(assemblyIdentity.GetDisplayName(), out size),
(AssemblyIdentity assemblyIdentity, out uint size) => appDomain.GetMetaDataBytesPtr(assemblyIdentity.GetDisplayName(), out size),
out errorMessage);
}
......
......@@ -109,6 +109,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ExpressionEvaluator
appDomain.RemoveMetadataContext(Of VisualBasicMetadataContext)()
End Sub
Friend Overrides Function GetMetadataBlocks(appDomain As DkmClrAppDomain, runtimeInstance As DkmClrRuntimeInstance) As ImmutableArray(Of MetadataBlock)
Dim previous = appDomain.GetMetadataContext(Of VisualBasicMetadataContext)()
Return runtimeInstance.GetMetadataBlocks(appDomain, previous.MetadataBlocks)
End Function
End Class
End Namespace
......@@ -83,7 +83,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ExpressionEvaluator
Friend Overrides Function GetCompilation(moduleInstance As DkmClrModuleInstance) As VisualBasicCompilation
Dim appDomain = moduleInstance.AppDomain
Dim previous = appDomain.GetMetadataContext(Of VisualBasicMetadataContext)()
Dim metadataBlocks = moduleInstance.RuntimeInstance.GetMetadataBlocks(appDomain)
Dim metadataBlocks = moduleInstance.RuntimeInstance.GetMetadataBlocks(appDomain, previous.MetadataBlocks)
Dim compilation As VisualBasicCompilation
If previous.Matches(metadataBlocks) Then
......
// 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.Collections.Generic;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Words = System.Collections.Generic.IEnumerable<string>;
using Microsoft.CodeAnalysis.Text;
using Words = System.Collections.Immutable.ImmutableArray<string>;
namespace Microsoft.CodeAnalysis.CSharp.Completion.Providers
{
......@@ -13,22 +13,22 @@ internal partial class DeclarationNameCompletionProvider
{
internal class NameGenerator
{
internal static ImmutableArray<IEnumerable<string>> GetBaseNames(ITypeSymbol type)
internal static ImmutableArray<Words> GetBaseNames(ITypeSymbol type)
{
var baseName = TryRemoveInterfacePrefix(type);
using (var breaks = StringBreaker.BreakIntoWordParts(baseName))
{
return GetInterleavedPatterns(breaks, baseName);
}
var parts = StringBreaker.GetWordParts(baseName);
var result = GetInterleavedPatterns(parts, baseName);
parts.Free();
return result;
}
private static ImmutableArray<IEnumerable<string>> GetInterleavedPatterns(StringBreaks breaks, string baseName)
private static ImmutableArray<Words> GetInterleavedPatterns(ArrayBuilder<TextSpan> breaks, string baseName)
{
var result = ArrayBuilder<IEnumerable<string>>.GetInstance();
var breakCount = breaks.GetCount();
var result = ArrayBuilder<Words>.GetInstance();
var breakCount = breaks.Count;
result.Add(GetWords(0, breakCount, breaks, baseName));
for (int length = breakCount - 1; length > 0; length--)
for (var length = breakCount - 1; length > 0; length--)
{
// going forward
result.Add(GetLongestForwardSubsequence(length, breaks, baseName));
......@@ -40,29 +40,28 @@ private static ImmutableArray<IEnumerable<string>> GetInterleavedPatterns(String
return result.ToImmutable();
}
private static Words GetLongestBackwardSubsequence(int length, StringBreaks breaks, string baseName)
private static Words GetLongestBackwardSubsequence(int length, ArrayBuilder<TextSpan> breaks, string baseName)
{
var breakCount = breaks.GetCount();
var breakCount = breaks.Count;
var start = breakCount - length;
return GetWords(start, breakCount, breaks, baseName);
}
private static Words GetLongestForwardSubsequence(int length, StringBreaks breaks, string baseName)
private static Words GetLongestForwardSubsequence(int length, ArrayBuilder<TextSpan> breaks, string baseName)
{
var end = length;
return GetWords(0, end, breaks, baseName);
return GetWords(0, length, breaks, baseName);
}
private static Words GetWords(int start, int end, StringBreaks breaks, string baseName)
private static Words GetWords(int start, int end, ArrayBuilder<TextSpan> breaks, string baseName)
{
var result = ImmutableArray.Create<string>();
var result = ArrayBuilder<string>.GetInstance();
for (; start < end; start++)
{
var @break = breaks[start];
result = result.Add(baseName.Substring(@break.Start, @break.Length));
result.Add(baseName.Substring(@break.Start, @break.Length));
}
return result;
return result.ToImmutableAndFree();
}
private static string TryRemoveInterfacePrefix(ITypeSymbol type)
......
......@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
......@@ -155,8 +156,8 @@ private ITypeSymbol UnwrapType(ITypeSymbol type, Compilation compilation)
return type;
}
private async Task<IEnumerable<(string, SymbolKind)>> GetRecommendedNamesAsync(
IEnumerable<IEnumerable<string>> baseNames,
private async Task<ImmutableArray<(string, SymbolKind)>> GetRecommendedNamesAsync(
ImmutableArray<ImmutableArray<string>> baseNames,
NameDeclarationInfo declarationInfo,
CSharpSyntaxContext context,
Document document,
......@@ -186,7 +187,7 @@ private ITypeSymbol UnwrapType(ITypeSymbol type, Compilation compilation)
}
}
return result.Select(kvp => (kvp.Key, kvp.Value));
return result.Select(kvp => (kvp.Key, kvp.Value)).ToImmutableArray();
}
CompletionItem CreateCompletionItem(string name, Glyph glyph, string sortText)
......
......@@ -142,10 +142,7 @@ protected override string GenerateNameForArgument(SemanticModel semanticModel, A
=> semanticModel.GenerateNameForArgument(argument, cancellationToken);
protected override RefKind GetRefKind(ArgumentSyntax argument)
{
return argument.RefOrOutKeyword.Kind() == SyntaxKind.RefKeyword ? RefKind.Ref :
argument.RefOrOutKeyword.Kind() == SyntaxKind.OutKeyword ? RefKind.Out : RefKind.None;
}
=> argument.GetRefKind();
protected override bool IsNamedArgument(ArgumentSyntax argument)
{
......
......@@ -95,7 +95,7 @@ public void RemoveSetMethod(SyntaxEditor editor, SyntaxNode setMethodDeclaration
}
else if (getAccessor.Body != null &&
getAccessor.Body.TryConvertToExpressionBody(
parseOptions, expressionBodyPreference,
propertyDeclaration.Kind(), parseOptions, expressionBodyPreference,
out var arrowExpression, out var semicolonToken))
{
return propertyDeclaration.WithExpressionBody(arrowExpression)
......@@ -183,7 +183,7 @@ private SyntaxToken GetPropertyName(SyntaxToken identifier, string propertyName,
if (accessorDeclaration?.Body != null && expressionBodyPreference != ExpressionBodyPreference.Never)
{
if (accessorDeclaration.Body.TryConvertToExpressionBody(
parseOptions, expressionBodyPreference,
accessorDeclaration.Kind(), parseOptions, expressionBodyPreference,
out var arrowExpression, out var semicolonToken))
{
return accessorDeclaration.WithBody(null)
......
......@@ -227,7 +227,8 @@ private static SyntaxTrivia ConvertDocumentationComment(SyntaxTrivia trivia, CSh
if (methodDeclaration?.Body != null && expressionBodyPreference != ExpressionBodyPreference.Never)
{
if (methodDeclaration.Body.TryConvertToExpressionBody(
parseOptions, expressionBodyPreference, out var arrowExpression, out var semicolonToken))
methodDeclaration.Kind(), parseOptions, expressionBodyPreference,
out var arrowExpression, out var semicolonToken))
{
return methodDeclaration.WithBody(null)
.WithExpressionBody(arrowExpression)
......
......@@ -2,7 +2,9 @@
using System;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Options;
......@@ -58,5 +60,16 @@ protected override IndexerDeclarationSyntax WithBody(IndexerDeclarationSyntax de
}
protected override bool CreateReturnStatementForExpression(IndexerDeclarationSyntax declaration) => true;
protected override bool TryConvertToExpressionBody(
IndexerDeclarationSyntax declaration, ParseOptions options,
ExpressionBodyPreference conversionPreference,
out ArrowExpressionClauseSyntax arrowExpression,
out SyntaxToken semicolonToken)
{
return this.TryConvertToExpressionBodyForBaseProperty(
declaration, options, conversionPreference,
out arrowExpression, out semicolonToken);
}
}
}
......@@ -67,21 +67,9 @@ protected override PropertyDeclarationSyntax WithBody(PropertyDeclarationSyntax
out ArrowExpressionClauseSyntax arrowExpression,
out SyntaxToken semicolonToken)
{
if (base.TryConvertToExpressionBody(declaration, options, conversionPreference, out arrowExpression, out semicolonToken))
{
return true;
}
var getAccessor = GetSingleGetAccessor(declaration.AccessorList);
if (getAccessor?.ExpressionBody != null &&
BlockSyntaxExtensions.MatchesPreference(getAccessor.ExpressionBody.Expression, conversionPreference))
{
arrowExpression = SyntaxFactory.ArrowExpressionClause(getAccessor.ExpressionBody.Expression);
semicolonToken = getAccessor.SemicolonToken;
return true;
}
return false;
return this.TryConvertToExpressionBodyForBaseProperty(
declaration, options, conversionPreference,
out arrowExpression, out semicolonToken);
}
protected override Location GetDiagnosticLocation(PropertyDeclarationSyntax declaration)
......
......@@ -111,13 +111,48 @@ protected virtual Location GetDiagnosticLocation(TDeclaration declaration)
ParseOptions options, ExpressionBodyPreference conversionPreference,
out ArrowExpressionClauseSyntax expressionWhenOnSingleLine,
out SyntaxToken semicolonWhenOnSingleLine)
{
return TryConvertToExpressionBodyWorker(
declaration, options, conversionPreference,
out expressionWhenOnSingleLine, out semicolonWhenOnSingleLine);
}
private bool TryConvertToExpressionBodyWorker(
SyntaxNode declaration, ParseOptions options, ExpressionBodyPreference conversionPreference,
out ArrowExpressionClauseSyntax expressionWhenOnSingleLine, out SyntaxToken semicolonWhenOnSingleLine)
{
var body = this.GetBody(declaration);
return body.TryConvertToExpressionBody(options, conversionPreference,
return body.TryConvertToExpressionBody(
declaration.Kind(), options, conversionPreference,
out expressionWhenOnSingleLine, out semicolonWhenOnSingleLine);
}
protected bool TryConvertToExpressionBodyForBaseProperty(
BasePropertyDeclarationSyntax declaration, ParseOptions options,
ExpressionBodyPreference conversionPreference,
out ArrowExpressionClauseSyntax arrowExpression,
out SyntaxToken semicolonToken)
{
if (this.TryConvertToExpressionBodyWorker(
declaration, options, conversionPreference,
out arrowExpression, out semicolonToken))
{
return true;
}
var getAccessor = GetSingleGetAccessor(declaration.AccessorList);
if (getAccessor?.ExpressionBody != null &&
BlockSyntaxExtensions.MatchesPreference(getAccessor.ExpressionBody.Expression, conversionPreference))
{
arrowExpression = SyntaxFactory.ArrowExpressionClause(getAccessor.ExpressionBody.Expression);
semicolonToken = getAccessor.SemicolonToken;
return true;
}
return false;
}
public (bool canOffer, bool fixesError) CanOfferUseBlockBody(
OptionSet optionSet, TDeclaration declaration, bool forAnalyzer)
{
......@@ -171,8 +206,9 @@ protected virtual Location GetDiagnosticLocation(TDeclaration declaration)
{
if (useExpressionBody)
{
TryConvertToExpressionBody(declaration, declaration.SyntaxTree.Options,
ExpressionBodyPreference.WhenPossible, out var expressionBody, out var semicolonToken);
TryConvertToExpressionBody(
declaration, declaration.SyntaxTree.Options, ExpressionBodyPreference.WhenPossible,
out var expressionBody, out var semicolonToken);
var trailingTrivia = semicolonToken.TrailingTrivia
.Where(t => t.Kind() != SyntaxKind.EndOfLineTrivia)
......
......@@ -11,10 +11,12 @@
using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Semantics;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Microsoft.CodeAnalysis.Simplification;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.InitializeParameter
......@@ -128,7 +130,7 @@ internal abstract partial class AbstractInitializeMemberFromParameterCodeRefacto
}
private IFieldSymbol CreateField(
IParameterSymbol parameter, ImmutableArray<NamingRule> rules, List<string> parameterNameParts)
IParameterSymbol parameter, ImmutableArray<NamingRule> rules, ImmutableArray<string> parameterNameParts)
{
foreach (var rule in rules)
{
......@@ -149,7 +151,7 @@ internal abstract partial class AbstractInitializeMemberFromParameterCodeRefacto
throw ExceptionUtilities.Unreachable;
}
private static string GenerateUniqueName(IParameterSymbol parameter, List<string> parameterNameParts, NamingRule rule)
private static string GenerateUniqueName(IParameterSymbol parameter, ImmutableArray<string> parameterNameParts, NamingRule rule)
{
// Determine an appropriate name to call the new field.
var containingType = parameter.ContainingType;
......@@ -163,7 +165,7 @@ private static string GenerateUniqueName(IParameterSymbol parameter, List<string
}
private IPropertySymbol CreateProperty(
IParameterSymbol parameter, ImmutableArray<NamingRule> rules, List<string> parameterNameParts)
IParameterSymbol parameter, ImmutableArray<NamingRule> rules, ImmutableArray<string> parameterNameParts)
{
foreach (var rule in rules)
{
......@@ -489,24 +491,23 @@ private IOperation TryFindFieldOrPropertyAssignmentStatement(IParameterSymbol pa
/// Get the individual words in the parameter name. This way we can generate
/// appropriate field/property names based on the user's preference.
/// </summary>
private List<string> GetParameterWordParts(IParameterSymbol parameter)
private ImmutableArray<string> GetParameterWordParts(IParameterSymbol parameter)
{
using (var breaks = StringBreaker.BreakIntoWordParts(parameter.Name))
{
return CreateWords(breaks, parameter.Name);
}
var parts = StringBreaker.GetWordParts(parameter.Name);
var result = CreateWords(parts, parameter.Name);
parts.Free();
return result;
}
private List<string> CreateWords(StringBreaks wordBreaks, string name)
private ImmutableArray<string> CreateWords(ArrayBuilder<TextSpan> parts, string name)
{
var result = new List<string>(wordBreaks.GetCount());
for (int i = 0, n = wordBreaks.GetCount(); i < n; i++)
var result = ArrayBuilder<string>.GetInstance(parts.Count);
foreach (var part in parts)
{
var br = wordBreaks[i];
result.Add(name.Substring(br.Start, br.Length));
result.Add(name.Substring(part.Start, part.Length));
}
return result;
return result.ToImmutableAndFree();
}
}
}
......@@ -11,6 +11,7 @@
using Microsoft.CodeAnalysis.PatternMatching;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.NavigateTo
......@@ -60,9 +61,13 @@ internal abstract partial class AbstractNavigateToSearchService
// use the last computed results we have for that project. If so, it can
// be much faster to reuse and filter that result than to compute it from
// scratch.
#if true
var task = searchDocument != null
? ComputeSearchResultsAsync(project, searchDocument, nameMatcher, containerMatcherOpt, nameMatches, containerMatches, cancellationToken)
: TryFilterPreviousSearchResultsAsync(project, searchDocument, pattern, nameMatcher, containerMatcherOpt, nameMatches, containerMatches, cancellationToken);
#else
var task = ComputeSearchResultsAsync(project, searchDocument, nameMatcher, containerMatcherOpt, nameMatches, containerMatches, cancellationToken);
#endif
var searchResults = await task.ConfigureAwait(false);
return ImmutableArray<INavigateToSearchResult>.CastUp(searchResults);
......@@ -199,9 +204,15 @@ internal abstract partial class AbstractNavigateToSearchService
var kind = GetItemKind(declaredSymbolInfo);
var navigableItem = NavigableItemFactory.GetItemFromDeclaredSymbolInfo(declaredSymbolInfo, document);
var matchedSpans = ArrayBuilder<TextSpan>.GetInstance();
foreach (var match in nameMatches)
{
matchedSpans.AddRange(match.MatchedSpans);
}
return new SearchResult(
document, declaredSymbolInfo, kind, matchKind, isCaseSensitive, navigableItem,
nameMatches.SelectMany(m => m.MatchedSpans).ToImmutableArray());
matchedSpans.ToImmutableAndFree());
}
private static string GetItemKind(DeclaredSymbolInfo declaredSymbolInfo)
......
......@@ -2,7 +2,6 @@
using System;
using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.Navigation;
......@@ -15,14 +14,13 @@ internal abstract partial class AbstractNavigateToSearchService
{
private class SearchResult : INavigateToSearchResult
{
public string AdditionalInformation => _lazyAdditionalInfo.Value;
public string Name => DeclaredSymbolInfo.Name;
public string Summary { get; }
public string AdditionalInformation => _lazyAdditionalInfo.Value;
public string Summary { get; }
public string Kind { get; }
public NavigateToMatchKind MatchKind { get; }
public INavigableItem NavigableItem { get; }
public string SecondarySort { get; }
public bool IsCaseSensitive { get; }
public ImmutableArray<TextSpan> NameMatchSpans { get; }
......@@ -31,6 +29,8 @@ private class SearchResult : INavigateToSearchResult
private readonly Lazy<string> _lazyAdditionalInfo;
private string _secondarySort;
public SearchResult(
Document document, DeclaredSymbolInfo declaredSymbolInfo, string kind,
NavigateToMatchKind matchKind, bool isCaseSensitive, INavigableItem navigableItem,
......@@ -43,7 +43,6 @@ private class SearchResult : INavigateToSearchResult
IsCaseSensitive = isCaseSensitive;
NavigableItem = navigableItem;
NameMatchSpans = nameMatchSpans;
SecondarySort = ConstructSecondarySortString(document, declaredSymbolInfo);
_lazyAdditionalInfo = new Lazy<string>(() =>
{
......@@ -67,22 +66,20 @@ private class SearchResult : INavigateToSearchResult
private static readonly char[] s_dotArray = { '.' };
private static string ConstructSecondarySortString(
Document document,
DeclaredSymbolInfo declaredSymbolInfo)
private string ConstructSecondarySortString()
{
var parts = ArrayBuilder<string>.GetInstance();
try
{
parts.Add(declaredSymbolInfo.ParameterCount.ToString("X4"));
parts.Add(declaredSymbolInfo.TypeParameterCount.ToString("X4"));
parts.Add(declaredSymbolInfo.Name);
parts.Add(DeclaredSymbolInfo.ParameterCount.ToString("X4"));
parts.Add(DeclaredSymbolInfo.TypeParameterCount.ToString("X4"));
parts.Add(DeclaredSymbolInfo.Name);
// For partial types, we break up the file name into pieces. i.e. If we have
// Outer.cs and Outer.Inner.cs then we add "Outer" and "Outer Inner" to
// the secondary sort string. That way "Outer.cs" will be weighted above
// "Outer.Inner.cs"
var fileName = Path.GetFileNameWithoutExtension(document.FilePath ?? "");
var fileName = Path.GetFileNameWithoutExtension(Document.FilePath ?? "");
parts.AddRange(fileName.Split(s_dotArray));
return string.Join(" ", parts);
......@@ -92,6 +89,19 @@ private class SearchResult : INavigateToSearchResult
parts.Free();
}
}
public string SecondarySort
{
get
{
if (_secondarySort == null)
{
_secondarySort = ConstructSecondarySortString();
}
return _secondarySort;
}
}
}
}
}
......@@ -4,6 +4,7 @@
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Execution;
namespace Microsoft.CodeAnalysis.TodoComments
{
......@@ -12,6 +13,6 @@ namespace Microsoft.CodeAnalysis.TodoComments
/// </summary>
internal interface IRemoteTodoCommentService
{
Task<IList<TodoComment>> GetTodoCommentsAsync(DocumentId documentId, ImmutableArray<TodoCommentDescriptor> commentDescriptors, CancellationToken cancellationToken);
Task<IList<TodoComment>> GetTodoCommentsAsync(PinnedSolutionInfo solutionInfo, DocumentId documentId, ImmutableArray<TodoCommentDescriptor> commentDescriptors, CancellationToken cancellationToken);
}
}
......@@ -37,8 +37,5 @@
<ProjectReference Include="..\..\..\Compilers\CSharp\Portable\CSharpCodeAnalysis.csproj" />
<ProjectReference Include="..\Utilities\Perf.Utilities.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="CPC-Consumption\" />
</ItemGroup>
<Import Project="..\..\..\..\build\Targets\Imports.targets" />
</Project>
\ No newline at end of file
......@@ -24,8 +24,5 @@
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Folder Include="Properties\" />
</ItemGroup>
<Import Project="..\..\..\..\build\Targets\Imports.targets" />
</Project>
\ No newline at end of file
......@@ -39,9 +39,6 @@
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<Folder Include="Properties\" />
</ItemGroup>
<ItemGroup>
<BootstrapperPackage Include=".NETFramework,Version=v4.6">
<Visible>False</Visible>
......
......@@ -142,10 +142,14 @@
<ProjectReference Include="..\SyntaxVisualizerControl\SyntaxVisualizerControl.csproj">
<Project>{afe45e23-e7ee-48c5-8143-ebe2ff67070f}</Project>
<Name>SyntaxVisualizerControl</Name>
<IncludeOutputGroupsInVSIX>BuiltProjectOutputGroup%3b</IncludeOutputGroupsInVSIX>
<IncludeOutputGroupsInVSIXLocalOnly>DebugSymbolsProjectOutputGroup%3b</IncludeOutputGroupsInVSIXLocalOnly>
</ProjectReference>
<ProjectReference Include="..\SyntaxVisualizerDgmlHelper\SyntaxVisualizerDgmlHelper.vbproj">
<Project>{da4f74af-2694-4ac9-a8cc-18382de8215e}</Project>
<Name>SyntaxVisualizerDgmlHelper</Name>
<IncludeOutputGroupsInVSIX>BuiltProjectOutputGroup%3b</IncludeOutputGroupsInVSIX>
<IncludeOutputGroupsInVSIXLocalOnly>DebugSymbolsProjectOutputGroup%3b</IncludeOutputGroupsInVSIXLocalOnly>
</ProjectReference>
</ItemGroup>
<ItemGroup>
......@@ -182,4 +186,4 @@
</None>
</ItemGroup>
<Import Project="..\..\..\..\..\build\Targets\Imports.targets" />
</Project>
\ No newline at end of file
</Project>
......@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles;
using Microsoft.CodeAnalysis.Notification;
......@@ -69,7 +70,7 @@ public string CurrentConfiguration
{
get
{
return _style.NamingStyle.CreateName(new[] { ServicesVSResources.example, ServicesVSResources.identifier });
return _style.NamingStyle.CreateName(ImmutableArray.Create(ServicesVSResources.example, ServicesVSResources.identifier));
}
set
{
......
......@@ -119,11 +119,21 @@ private static async Task RegisterWorkspaceHostAsync(Workspace workspace, Remote
public override async Task<Connection> TryCreateConnectionAsync(string serviceName, object callbackTarget, CancellationToken cancellationToken)
{
var dataRpc = _remotableDataRpc.TryAddReference();
if (dataRpc == null)
{
// dataRpc is disposed. this can happen if someone killed remote host process while there is
// no other one holding the data connection.
// in those error case, don't crash but return null. this method is TryCreate since caller expects it to return null
// on such error situation.
return null;
}
// get stream from service hub to communicate service specific information
// this is what consumer actually use to communicate information
var serviceStream = await RequestServiceAsync(_hubClient, serviceName, _hostGroup, _timeout, cancellationToken).ConfigureAwait(false);
return new JsonRpcConnection(callbackTarget, serviceStream, _remotableDataRpc.TryAddReference());
return new JsonRpcConnection(callbackTarget, serviceStream, dataRpc);
}
protected override void OnStarted()
......
......@@ -56,9 +56,6 @@
<InternalsVisibleToTest Include="Roslyn.VisualStudio.Test.Utilities2" />
<InternalsVisibleToTest Include="RoslynETAHost" />
</ItemGroup>
<ItemGroup>
<Folder Include="Properties\" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="SolutionExplorerShim.resx">
<Generator>ResXFileCodeGenerator</Generator>
......
......@@ -73,11 +73,16 @@ public async Task TestTodoComments()
var solution = workspace.CurrentSolution;
var comments = await client.TryRunCodeAnalysisRemoteAsync<IList<TodoComment>>(
solution, nameof(IRemoteTodoCommentService.GetTodoCommentsAsync),
new object[] { solution.Projects.First().DocumentIds.First(), ImmutableArray.Create(new TodoCommentDescriptor("TODO", 0)) }, CancellationToken.None);
var keepAliveSession = await client.TryCreateCodeAnalysisKeepAliveSessionAsync(CancellationToken.None);
var comments = await keepAliveSession.TryInvokeAsync<IList<TodoComment>>(
nameof(IRemoteTodoCommentService.GetTodoCommentsAsync),
solution,
new object[] { solution.Projects.First().DocumentIds.First(), ImmutableArray.Create(new TodoCommentDescriptor("TODO", 0)) },
CancellationToken.None);
Assert.Equal(comments.Count, 1);
keepAliveSession.Shutdown(CancellationToken.None);
}
}
......
......@@ -60,8 +60,5 @@
<SubType>Designer</SubType>
</None>
</ItemGroup>
<ItemGroup>
<Folder Include="Properties\" />
</ItemGroup>
<Import Project="..\..\..\build\Targets\Imports.targets" />
</Project>
\ No newline at end of file
......@@ -107,9 +107,6 @@
<SubType>Designer</SubType>
</None>
</ItemGroup>
<ItemGroup>
<Folder Include="Properties\" />
</ItemGroup>
<ItemGroup>
<Page Include="VenusMargin\ProjectionBufferMargin.xaml">
<Generator>MSBuild:Compile</Generator>
......
......@@ -48,9 +48,6 @@
<DependentUpon>CSharpWorkspaceResources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<Folder Include="CodeCleanup\Providers\" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="CSharpWorkspaceResources.resx">
<Generator>ResXFileCodeGenerator</Generator>
......
......@@ -151,6 +151,7 @@ private int Compare(BaseFieldDeclarationSyntax x, BaseFieldDeclarationSyntax y)
{
if (EqualConstness(x.Modifiers, y.Modifiers, out var result) &&
EqualStaticness(x.Modifiers, y.Modifiers, out result) &&
EqualReadOnlyness(x.Modifiers, y.Modifiers, out result) &&
EqualAccessibility(x, x.Modifiers, y, y.Modifiers, out result))
{
if (_includeName)
......@@ -378,14 +379,13 @@ private static bool BothHaveModifier(SyntaxTokenList x, SyntaxTokenList y, Synta
}
private static bool EqualStaticness(SyntaxTokenList x, SyntaxTokenList y, out int comparisonResult)
{
return BothHaveModifier(x, y, SyntaxKind.StaticKeyword, out comparisonResult);
}
=> BothHaveModifier(x, y, SyntaxKind.StaticKeyword, out comparisonResult);
private static bool EqualConstness(SyntaxTokenList x, SyntaxTokenList y, out int comparisonResult)
{
return BothHaveModifier(x, y, SyntaxKind.ConstKeyword, out comparisonResult);
}
=> BothHaveModifier(x, y, SyntaxKind.ConstKeyword, out comparisonResult);
private static bool EqualReadOnlyness(SyntaxTokenList x, SyntaxTokenList y, out int comparisonResult)
=> BothHaveModifier(x, y, SyntaxKind.ReadOnlyKeyword, out comparisonResult);
private static bool EqualAccessibility(SyntaxNode x, SyntaxTokenList xModifiers, SyntaxNode y, SyntaxTokenList yModifiers, out int comparisonResult)
{
......
......@@ -74,7 +74,8 @@ private static MemberDeclarationSyntax LastConstructorOrField(SyntaxList<MemberD
{
var expressionBodyPreference = workspace.Options.GetOption(CSharpCodeStyleOptions.PreferExpressionBodiedConstructors).Value;
if (declaration.Body.TryConvertToExpressionBody(
options, expressionBodyPreference, out var expressionBody, out var semicolonToken))
declaration.Kind(), options, expressionBodyPreference,
out var expressionBody, out var semicolonToken))
{
return declaration.WithBody(null)
.WithExpressionBody(expressionBody)
......
......@@ -86,7 +86,8 @@ internal static class ConversionGenerator
var expressionBodyPreference = workspace.Options.GetOption(CSharpCodeStyleOptions.PreferExpressionBodiedOperators).Value;
if (declaration.Body.TryConvertToExpressionBody(
options, expressionBodyPreference, out var expressionBody, out var semicolonToken))
declaration.Kind(), options, expressionBodyPreference,
out var expressionBody, out var semicolonToken))
{
return declaration.WithBody(null)
.WithExpressionBody(expressionBody)
......
......@@ -17,18 +17,30 @@ internal static class FieldGenerator
SyntaxList<MemberDeclarationSyntax> members,
FieldDeclarationSyntax fieldDeclaration)
{
var lastConst = members.AsEnumerable()
.OfType<FieldDeclarationSyntax>()
.Where(f => f.Modifiers.Any(SyntaxKind.ConstKeyword)).LastOrDefault();
var lastConst = members.OfType<FieldDeclarationSyntax>()
.Where(f => f.Modifiers.Any(SyntaxKind.ConstKeyword))
.LastOrDefault();
// Place a const after the last existing const.
// Place a const after the last existing const. If we don't have a last const
// we'll just place the const before the first member in the type.
if (fieldDeclaration.Modifiers.Any(SyntaxKind.ConstKeyword))
{
return lastConst;
}
// Place a field after the last field, or after the last const.
return CSharpCodeGenerationHelpers.LastField(members) ?? lastConst;
var lastReadOnly = members.OfType<FieldDeclarationSyntax>()
.Where(f => f.Modifiers.Any(SyntaxKind.ReadOnlyKeyword))
.LastOrDefault();
var lastNormal = members.OfType<FieldDeclarationSyntax>()
.Where(f => !f.Modifiers.Any(SyntaxKind.ReadOnlyKeyword) && !f.Modifiers.Any(SyntaxKind.ConstKeyword))
.LastOrDefault();
// Place a readonly field after the last readonly field if we have one. Otherwise
// after the last field/const.
return fieldDeclaration.Modifiers.Any(SyntaxKind.ReadOnlyKeyword)
? lastReadOnly ?? lastConst ?? lastNormal
: lastNormal ?? lastReadOnly ?? lastConst;
}
internal static CompilationUnitSyntax AddFieldTo(
......
......@@ -119,7 +119,8 @@ internal static class MethodGenerator
{
var expressionBodyPreference = workspace.Options.GetOption(CSharpCodeStyleOptions.PreferExpressionBodiedMethods).Value;
if (methodDeclaration.Body.TryConvertToExpressionBody(
options, expressionBodyPreference, out var expressionBody, out var semicolonToken))
methodDeclaration.Kind(), options, expressionBodyPreference,
out var expressionBody, out var semicolonToken))
{
return methodDeclaration.WithBody(null)
.WithExpressionBody(expressionBody)
......
......@@ -58,7 +58,8 @@ internal static class OperatorGenerator
{
var expressionBodyPreference = workspace.Options.GetOption(CSharpCodeStyleOptions.PreferExpressionBodiedOperators).Value;
if (declaration.Body.TryConvertToExpressionBody(
options, expressionBodyPreference, out var expressionBody, out var semicolonToken))
declaration.Kind(), options, expressionBodyPreference,
out var expressionBody, out var semicolonToken))
{
return declaration.WithBody(null)
.WithExpressionBody(expressionBody)
......
......@@ -146,16 +146,19 @@ private static TypeSyntax GeneratePropertyType(IPropertySymbol property)
}
private static bool TryGetExpressionBody(
AccessorListSyntax accessorList, ParseOptions options, ExpressionBodyPreference preference,
BasePropertyDeclarationSyntax baseProperty, ParseOptions options, ExpressionBodyPreference preference,
out ArrowExpressionClauseSyntax arrowExpression, out SyntaxToken semicolonToken)
{
var accessorList = baseProperty.AccessorList;
if (preference != ExpressionBodyPreference.Never &&
accessorList.Accessors.Count == 1)
{
var accessor = accessorList.Accessors[0];
if (accessor.IsKind(SyntaxKind.GetAccessorDeclaration))
{
return TryGetExpressionBody(accessor, options, preference, out arrowExpression, out semicolonToken);
return TryGetExpressionBody(
baseProperty.Kind(), accessor, options, preference,
out arrowExpression, out semicolonToken);
}
}
......@@ -172,8 +175,9 @@ private static TypeSyntax GeneratePropertyType(IPropertySymbol property)
var expressionBodyPreference = workspace.Options.GetOption(CSharpCodeStyleOptions.PreferExpressionBodiedProperties).Value;
if (declaration.Initializer == null)
{
if (TryGetExpressionBody(declaration.AccessorList, options,
expressionBodyPreference, out var expressionBody, out var semicolonToken))
if (TryGetExpressionBody(
declaration, options, expressionBodyPreference,
out var expressionBody, out var semicolonToken))
{
declaration = declaration.WithAccessorList(null)
.WithExpressionBody(expressionBody)
......@@ -191,8 +195,9 @@ private static TypeSyntax GeneratePropertyType(IPropertySymbol property)
if (declaration.ExpressionBody == null)
{
var expressionBodyPreference = workspace.Options.GetOption(CSharpCodeStyleOptions.PreferExpressionBodiedIndexers).Value;
if (TryGetExpressionBody(declaration.AccessorList,
options, expressionBodyPreference, out var expressionBody, out var semicolonToken))
if (TryGetExpressionBody(
declaration, options, expressionBodyPreference,
out var expressionBody, out var semicolonToken))
{
declaration = declaration.WithAccessorList(null)
.WithExpressionBody(expressionBody)
......@@ -210,7 +215,8 @@ private static TypeSyntax GeneratePropertyType(IPropertySymbol property)
{
var expressionBodyPreference = workspace.Options.GetOption(CSharpCodeStyleOptions.PreferExpressionBodiedAccessors).Value;
if (declaration.Body.TryConvertToExpressionBody(
options, expressionBodyPreference, out var expressionBody, out var semicolonToken))
declaration.Kind(), options, expressionBodyPreference,
out var expressionBody, out var semicolonToken))
{
declaration = declaration.WithBody(null)
.WithExpressionBody(expressionBody)
......@@ -222,7 +228,7 @@ private static TypeSyntax GeneratePropertyType(IPropertySymbol property)
}
private static bool TryGetExpressionBody(
AccessorDeclarationSyntax accessor, ParseOptions options, ExpressionBodyPreference preference,
SyntaxKind declaratoinKind, AccessorDeclarationSyntax accessor, ParseOptions options, ExpressionBodyPreference preference,
out ArrowExpressionClauseSyntax arrowExpression, out SyntaxToken semicolonToken)
{
// If the accessor has an expression body already, then use that as the expression body
......@@ -235,7 +241,7 @@ private static TypeSyntax GeneratePropertyType(IPropertySymbol property)
}
return accessor.Body.TryConvertToExpressionBody(
options, preference, out arrowExpression, out semicolonToken);
declaratoinKind, options, preference, out arrowExpression, out semicolonToken);
}
private static AccessorListSyntax GenerateAccessorList(
......
......@@ -27,7 +27,7 @@ public static SyntaxTokenList GenerateParameterModifiers(this ArgumentSyntax arg
public static RefKind GetRefKind(this ArgumentSyntax argument)
{
var refSyntaxKind = argument.RefOrOutKeyword.Kind();
var refSyntaxKind = argument?.RefOrOutKeyword.Kind();
return
refSyntaxKind == SyntaxKind.RefKeyword ? RefKind.Ref :
refSyntaxKind == SyntaxKind.OutKeyword ? RefKind.Out : RefKind.None;
......
......@@ -13,19 +13,24 @@ namespace Microsoft.CodeAnalysis.CSharp.Extensions
internal static class BlockSyntaxExtensions
{
public static bool TryConvertToExpressionBody(
this BlockSyntax block, ParseOptions options,
ExpressionBodyPreference preference,
this BlockSyntax block, SyntaxKind declarationKind,
ParseOptions options, ExpressionBodyPreference preference,
out ArrowExpressionClauseSyntax arrowExpression,
out SyntaxToken semicolonToken)
{
if (preference != ExpressionBodyPreference.Never &&
(options as CSharpParseOptions)?.LanguageVersion >= LanguageVersion.CSharp7)
block != null && block.Statements.Count == 1)
{
if (block != null && block.Statements.Count == 1)
var version = ((CSharpParseOptions)options).LanguageVersion;
var acceptableVersion =
version >= LanguageVersion.CSharp7 ||
(version >= LanguageVersion.CSharp6 && IsSupportedInCSharp6(declarationKind));
if (acceptableVersion)
{
var firstStatement = block.Statements[0];
if (TryGetExpression(firstStatement, out var expression, out semicolonToken) &&
if (TryGetExpression(version, firstStatement, out var expression, out semicolonToken) &&
MatchesPreference(expression, preference))
{
arrowExpression = SyntaxFactory.ArrowExpressionClause(expression);
......@@ -45,6 +50,22 @@ internal static class BlockSyntaxExtensions
return false;
}
private static bool IsSupportedInCSharp6(SyntaxKind declarationKind)
{
switch (declarationKind)
{
case SyntaxKind.ConstructorDeclaration:
case SyntaxKind.DestructorDeclaration:
case SyntaxKind.AddAccessorDeclaration:
case SyntaxKind.RemoveAccessorDeclaration:
case SyntaxKind.GetAccessorDeclaration:
case SyntaxKind.SetAccessorDeclaration:
return false;
}
return true;
}
public static bool MatchesPreference(
ExpressionSyntax expression, ExpressionBodyPreference preference)
{
......@@ -58,7 +79,8 @@ internal static class BlockSyntaxExtensions
}
private static bool TryGetExpression(
StatementSyntax firstStatement, out ExpressionSyntax expression, out SyntaxToken semicolonToken)
LanguageVersion version, StatementSyntax firstStatement,
out ExpressionSyntax expression, out SyntaxToken semicolonToken)
{
if (firstStatement is ExpressionStatementSyntax exprStatement)
{
......@@ -81,7 +103,7 @@ internal static class BlockSyntaxExtensions
}
else if (firstStatement is ThrowStatementSyntax throwStatement)
{
if (throwStatement.Expression != null)
if (version >= LanguageVersion.CSharp7 && throwStatement.Expression != null)
{
expression = SyntaxFactory.ThrowExpression(throwStatement.ThrowKeyword, throwStatement.Expression);
semicolonToken = throwStatement.SemicolonToken;
......
......@@ -699,7 +699,7 @@ private void DetermineTypeParameterMapping(ITypeSymbol inferredType, ITypeSymbol
}
var name = argumentOpt != null && argumentOpt.NameColon != null ? argumentOpt.NameColon.Name.Identifier.ValueText : null;
return InferTypeInArgument(index, parameterizedSymbols, name);
return InferTypeInArgument(index, parameterizedSymbols, name, RefKind.None);
}
private IEnumerable<TypeInferenceInfo> InferTypeInArgument(
......@@ -708,39 +708,55 @@ private void DetermineTypeParameterMapping(ITypeSymbol inferredType, ITypeSymbol
ArgumentSyntax argumentOpt)
{
var name = argumentOpt != null && argumentOpt.NameColon != null ? argumentOpt.NameColon.Name.Identifier.ValueText : null;
return InferTypeInArgument(index, parameterizedSymbols, name);
var refKind = argumentOpt.GetRefKind();
return InferTypeInArgument(index, parameterizedSymbols, name, refKind);
}
private IEnumerable<TypeInferenceInfo> InferTypeInArgument(
int index,
IEnumerable<ImmutableArray<IParameterSymbol>> parameterizedSymbols,
string name)
string name,
RefKind refKind)
{
// If the callsite has a named argument, then try to find a method overload that has a
// parameter with that name. If we can find one, then return the type of that one.
if (name != null)
{
var parameters = parameterizedSymbols.SelectMany(m => m)
.Where(p => p.Name == name)
.Select(p => new TypeInferenceInfo(p.Type, p.IsParams));
if (parameters.Any())
var matchingNameParameters = parameterizedSymbols.SelectMany(m => m)
.Where(p => p.Name == name)
.Select(p => new TypeInferenceInfo(p.Type, p.IsParams));
return matchingNameParameters;
}
var allParameters = ArrayBuilder<TypeInferenceInfo>.GetInstance();
var matchingRefParameters = ArrayBuilder<TypeInferenceInfo>.GetInstance();
try
{
foreach (var parameterSet in parameterizedSymbols)
{
return parameters;
if (index < parameterSet.Length)
{
var parameter = parameterSet[index];
var info = new TypeInferenceInfo(parameter.Type, parameter.IsParams);
allParameters.Add(info);
if (parameter.RefKind == refKind)
{
matchingRefParameters.Add(info);
}
}
}
return matchingRefParameters.Count > 0
? matchingRefParameters.ToImmutable()
: allParameters.ToImmutable();
}
else
finally
{
// Otherwise, just take the first overload and pick what type this parameter is
// based on index.
var q = from parameterSet in parameterizedSymbols
where index < parameterSet.Length
let param = parameterSet[index]
select new TypeInferenceInfo(param.Type, param.IsParams);
return q;
allParameters.Free();
matchingRefParameters.Free();
}
return SpecializedCollections.EmptyEnumerable<TypeInferenceInfo>();
}
private IEnumerable<TypeInferenceInfo> InferTypeInArrayCreationExpression(
......
......@@ -1082,6 +1082,11 @@ private bool IsTypeOfUnboundGenericType(SemanticModel semanticModel, TypeOfExpre
public override SyntaxNode VisitInvocationExpression(InvocationExpressionSyntax originalNode)
{
if (this._semanticModel.GetSymbolInfo(originalNode).Symbol.IsLocalFunction())
{
return originalNode;
}
var rewrittenNode = (InvocationExpressionSyntax)base.VisitInvocationExpression(originalNode);
if (originalNode.Expression.IsKind(SyntaxKind.SimpleMemberAccessExpression))
{
......
......@@ -444,6 +444,10 @@ protected override bool ReplacementChangesSemanticsForNodeLanguageSpecific(Synta
var replacedAnonymousObjectMemberDeclarator = (AnonymousObjectMemberDeclaratorSyntax)currentReplacedNode;
return ReplacementBreaksAnonymousObjectMemberDeclarator(originalAnonymousObjectMemberDeclarator, replacedAnonymousObjectMemberDeclarator);
}
else if (currentOriginalNode.Kind() == SyntaxKind.DefaultExpression)
{
return !TypesAreCompatible((ExpressionSyntax)currentOriginalNode, (ExpressionSyntax)currentReplacedNode);
}
return false;
}
......
......@@ -86,10 +86,9 @@ internal partial class SymbolTreeInfo : IChecksummedObject
ImmutableArray<Node> sortedNodes,
Task<SpellChecker> spellCheckerTask,
OrderPreservingMultiDictionary<string, string> inheritanceMap)
: this(checksum, concatenatedNames, sortedNodes, spellCheckerTask)
: this(checksum, concatenatedNames, sortedNodes, spellCheckerTask,
CreateIndexBasedInheritanceMap(concatenatedNames, sortedNodes, inheritanceMap))
{
var indexBasedInheritanceMap = CreateIndexBasedInheritanceMap(inheritanceMap);
_inheritanceMap = indexBasedInheritanceMap;
}
private SymbolTreeInfo(
......@@ -98,21 +97,12 @@ internal partial class SymbolTreeInfo : IChecksummedObject
ImmutableArray<Node> sortedNodes,
Task<SpellChecker> spellCheckerTask,
OrderPreservingMultiDictionary<int, int> inheritanceMap)
: this(checksum, concatenatedNames, sortedNodes, spellCheckerTask)
{
_inheritanceMap = inheritanceMap;
}
private SymbolTreeInfo(
Checksum checksum,
string concatenatedNames,
ImmutableArray<Node> sortedNodes,
Task<SpellChecker> spellCheckerTask)
{
Checksum = checksum;
_concatenatedNames = concatenatedNames;
_nodes = sortedNodes;
_spellCheckerTask = spellCheckerTask;
_inheritanceMap = inheritanceMap;
}
public static SymbolTreeInfo CreateEmpty(Checksum checksum)
......@@ -236,39 +226,43 @@ private static StringSliceComparer GetComparer(bool ignoreCase)
: StringSliceComparer.Ordinal;
}
private IEnumerable<int> FindNodeIndices(string name, StringSliceComparer comparer)
=> FindNodeIndices(_concatenatedNames, _nodes, name, comparer);
/// <summary>
/// Gets all the node indices with matching names per the <paramref name="comparer" />.
/// </summary>
private IEnumerable<int> FindNodeIndices(
private static IEnumerable<int> FindNodeIndices(
string concatenatedNames, ImmutableArray<Node> nodes,
string name, StringSliceComparer comparer)
{
// find any node that matches case-insensitively
var startingPosition = BinarySearch(name);
var startingPosition = BinarySearch(concatenatedNames, nodes, name);
var nameSlice = new StringSlice(name);
if (startingPosition != -1)
{
// yield if this matches by the actual given comparer
if (comparer.Equals(nameSlice, GetNameSlice(startingPosition)))
if (comparer.Equals(nameSlice, GetNameSlice(concatenatedNames, nodes, startingPosition)))
{
yield return startingPosition;
}
int position = startingPosition;
while (position > 0 && s_caseInsensitiveComparer.Equals(GetNameSlice(position - 1), nameSlice))
while (position > 0 && s_caseInsensitiveComparer.Equals(GetNameSlice(concatenatedNames, nodes, position - 1), nameSlice))
{
position--;
if (comparer.Equals(GetNameSlice(position), nameSlice))
if (comparer.Equals(GetNameSlice(concatenatedNames, nodes, position), nameSlice))
{
yield return position;
}
}
position = startingPosition;
while (position + 1 < _nodes.Length && s_caseInsensitiveComparer.Equals(GetNameSlice(position + 1), nameSlice))
while (position + 1 < nodes.Length && s_caseInsensitiveComparer.Equals(GetNameSlice(concatenatedNames, nodes, position + 1), nameSlice))
{
position++;
if (comparer.Equals(GetNameSlice(position), nameSlice))
if (comparer.Equals(GetNameSlice(concatenatedNames, nodes, position), nameSlice))
{
yield return position;
}
......@@ -277,24 +271,32 @@ private static StringSliceComparer GetComparer(bool ignoreCase)
}
private StringSlice GetNameSlice(int nodeIndex)
=> GetNameSlice(_concatenatedNames, _nodes, nodeIndex);
private static StringSlice GetNameSlice(
string concatenatedNames, ImmutableArray<Node> nodes, int nodeIndex)
{
return new StringSlice(_concatenatedNames, _nodes[nodeIndex].NameSpan);
return new StringSlice(concatenatedNames, nodes[nodeIndex].NameSpan);
}
private int BinarySearch(string name)
=> BinarySearch(_concatenatedNames, _nodes, name);
/// <summary>
/// Searches for a name in the ordered list that matches per the <see cref="s_caseInsensitiveComparer" />.
/// </summary>
private int BinarySearch(string name)
private static int BinarySearch(string concatenatedNames, ImmutableArray<Node> nodes, string name)
{
var nameSlice = new StringSlice(name);
int max = _nodes.Length - 1;
int max = nodes.Length - 1;
int min = 0;
while (max >= min)
{
int mid = min + ((max - min) >> 1);
var comparison = s_caseInsensitiveComparer.Compare(GetNameSlice(mid), nameSlice);
var comparison = s_caseInsensitiveComparer.Compare(
GetNameSlice(concatenatedNames, nodes, mid), nameSlice);
if (comparison < 0)
{
min = mid + 1;
......@@ -534,10 +536,12 @@ internal void AssertEquivalentTo(SymbolTreeInfo other)
solution, checksum, filePath, concatenatedNames, sortedNodes);
return new SymbolTreeInfo(
checksum, concatenatedNames, sortedNodes, createSpellCheckerTask, inheritanceMap);
checksum, concatenatedNames,
sortedNodes, createSpellCheckerTask, inheritanceMap);
}
private OrderPreservingMultiDictionary<int, int> CreateIndexBasedInheritanceMap(
private static OrderPreservingMultiDictionary<int, int> CreateIndexBasedInheritanceMap(
string concatenatedNames, ImmutableArray<Node> nodes,
OrderPreservingMultiDictionary<string, string> inheritanceMap)
{
// All names in metadata will be case sensitive.
......@@ -547,12 +551,12 @@ internal void AssertEquivalentTo(SymbolTreeInfo other)
foreach (var kvp in inheritanceMap)
{
var baseName = kvp.Key;
var baseNameIndex = BinarySearch(baseName);
var baseNameIndex = BinarySearch(concatenatedNames, nodes, baseName);
Debug.Assert(baseNameIndex >= 0);
foreach (var derivedName in kvp.Value)
{
foreach (var derivedNameIndex in FindNodeIndices(derivedName, comparer))
foreach (var derivedNameIndex in FindNodeIndices(concatenatedNames, nodes, derivedName, comparer))
{
result.Add(baseNameIndex, derivedNameIndex);
}
......
......@@ -12,7 +12,6 @@
using Microsoft.CodeAnalysis.Collections;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Serialization;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Microsoft.CodeAnalysis.Utilities;
using Roslyn.Utilities;
......
// 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.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
......
......@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Xml.Linq;
......@@ -59,7 +60,7 @@ internal partial struct NamingStyle
newName, newPrefix, newSuffix, newWordSeparator, newCapitalizationScheme);
}
public string CreateName(IEnumerable<string> words)
public string CreateName(ImmutableArray<string> words)
{
var wordsWithCasing = ApplyCapitalization(words);
var combinedWordsWithCasing = string.Join(WordSeparator, wordsWithCasing);
......
......@@ -3,6 +3,8 @@
using System;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Globalization;
using System.Threading;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared;
using Microsoft.CodeAnalysis.Shared.Utilities;
......@@ -21,17 +23,22 @@ private struct AllLowerCamelCaseMatcher
{
private readonly bool _includeMatchedSpans;
private readonly string _candidate;
private readonly StringBreaks _candidateHumps;
private readonly ArrayBuilder<TextSpan> _candidateHumps;
private readonly TextChunk _patternChunk;
private readonly string _patternText;
private readonly TextInfo _textInfo;
public AllLowerCamelCaseMatcher(bool includeMatchedSpans, string candidate, StringBreaks candidateHumps, TextChunk patternChunk)
public AllLowerCamelCaseMatcher(
bool includeMatchedSpans, string candidate,
ArrayBuilder<TextSpan> candidateHumps, TextChunk patternChunk,
TextInfo textInfo)
{
_includeMatchedSpans = includeMatchedSpans;
_candidate = candidate;
_candidateHumps = candidateHumps;
_patternChunk = patternChunk;
_patternText = _patternChunk.Text;
_textInfo = textInfo;
}
/// <summary>
......@@ -78,7 +85,7 @@ private PatternMatchKind GetKind(CamelCaseResult result)
// We are contiguous if our contiguous tracker was not set to false.
var matchedSpansInReverse = _includeMatchedSpans ? ArrayBuilder<TextSpan>.GetInstance() : null;
return new CamelCaseResult(
fromStart: false,
fromStart: false,
contiguous: contiguous != false,
matchCount: 0,
matchedSpansInReverse: matchedSpansInReverse);
......@@ -88,7 +95,7 @@ private PatternMatchKind GetKind(CamelCaseResult result)
// Look for a hump in the candidate that matches the current letter we're on.
var patternCharacter = _patternText[patternIndex];
for (int humpIndex = candidateHumpIndex, n = _candidateHumps.GetCount(); humpIndex < n; humpIndex++)
for (int humpIndex = candidateHumpIndex, n = _candidateHumps.Count; humpIndex < n; humpIndex++)
{
// If we've been contiguous, but we jumped past a hump, then we're no longer contiguous.
if (contiguous.HasValue && contiguous.Value)
......@@ -97,7 +104,7 @@ private PatternMatchKind GetKind(CamelCaseResult result)
}
var candidateHump = _candidateHumps[humpIndex];
if (char.ToLower(_candidate[candidateHump.Start]) == patternCharacter)
if (ToLower(_candidate[candidateHump.Start], _textInfo) == patternCharacter)
{
// Found a hump in the candidate string that matches the current pattern
// character we're on. i.e. we matched the c in cofipro against the C in
......@@ -134,6 +141,21 @@ private PatternMatchKind GetKind(CamelCaseResult result)
return bestResult;
}
private static char ToLower(char v, TextInfo textInfo)
{
return IsAscii(v)
? ToLowerAsciiInvariant(v)
: textInfo.ToLower(v);
}
private static bool IsAscii(char v)
=> v < 0x80;
private static char ToLowerAsciiInvariant(char c)
=> 'A' <= c && c <= 'Z'
? (char)(c | 0x20)
: c;
private CamelCaseResult? TryConsumePatternOrMatchNextHump(
int patternIndex, int humpIndex, bool contiguous)
{
......@@ -240,9 +262,10 @@ private bool IsBetter(CamelCaseResult result, CamelCaseResult? currentBestResult
private bool LowercaseSubstringsMatch(
string s1, int start1, string s2, int start2, int length)
{
var textInfo = _textInfo;
for (var i = 0; i < length; i++)
{
if (char.ToLower(s1[start1 + i]) != char.ToLower(s2[start2 + i]))
if (ToLower(s1[start1 + i], textInfo) != ToLower(s2[start2 + i], textInfo))
{
return false;
}
......
......@@ -41,9 +41,9 @@ public CamelCaseResult WithAddedMatchedSpan(TextSpan value)
}
}
private static PatternMatchKind GetCamelCaseKind(CamelCaseResult result, StringBreaks candidateHumps)
private static PatternMatchKind GetCamelCaseKind(CamelCaseResult result, ArrayBuilder<TextSpan> candidateHumps)
{
var toEnd = result.MatchCount == candidateHumps.GetCount();
var toEnd = result.MatchCount == candidateHumps.Count;
if (result.FromStart)
{
if (result.Contiguous)
......
// 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 Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.PatternMatching
......@@ -23,7 +25,7 @@ private struct TextChunk : IDisposable
/// capitalized runs and lowercase runs. i.e. if you have AAbb, then there will be two
/// character spans, one for AA and one for BB.
/// </summary>
public readonly StringBreaks CharacterSpans;
public readonly ArrayBuilder<TextSpan> PatternHumps;
public readonly WordSimilarityChecker SimilarityChecker;
......@@ -32,7 +34,7 @@ private struct TextChunk : IDisposable
public TextChunk(string text, bool allowFuzzingMatching)
{
this.Text = text;
this.CharacterSpans = StringBreaker.BreakIntoCharacterParts(text);
this.PatternHumps = StringBreaker.GetCharacterParts(text);
this.SimilarityChecker = allowFuzzingMatching
? WordSimilarityChecker.Allocate(text, substringsAreSimilar: false)
: null;
......@@ -42,7 +44,7 @@ public TextChunk(string text, bool allowFuzzingMatching)
public void Dispose()
{
this.CharacterSpans.Dispose();
this.PatternHumps.Free();
this.SimilarityChecker?.Free();
}
}
......
......@@ -12,7 +12,7 @@ internal static class PatternMatcherExtensions
var matches = ArrayBuilder<PatternMatch>.GetInstance();
matcher.AddMatches(candidate, matches);
var result = matches.FirstOrNullable();
var result = matches.Any() ? (PatternMatch?)matches.First() : null;
matches.Free();
return result;
......
......@@ -69,153 +69,157 @@ public Task<T> InvokeAsync<T>(string targetName, IReadOnlyList<object> arguments
/// This will let one to hold onto <see cref="RemoteHostClient.Connection"/> for a while.
/// this helper will let you not care about remote host being gone while you hold onto the connection if that ever happen
///
/// and also make sure state is correct even if multiple threads call TryInvokeAsync at the same time. but this
/// is not optimized to handle highly concurrent usage. if highly concurrent usage is required, either using
/// <see cref="RemoteHostClient.Connection"/> direclty or using <see cref="SessionWithSolution"/> would be better choice
/// when this is used, solution must be explicitly passed around between client (VS) and remote host (OOP)
/// </summary>
internal sealed class KeepAliveSession
{
private readonly SemaphoreSlim _gate;
private readonly object _gate;
private readonly IRemoteHostClientService _remoteHostClientService;
private readonly string _serviceName;
private readonly object _callbackTarget;
private RemoteHostClient _client;
private RemoteHostClient.Connection _connection;
private ReferenceCountedDisposable<RemoteHostClient.Connection> _connectionDoNotAccessDirectly;
public KeepAliveSession(RemoteHostClient client, RemoteHostClient.Connection connection, string serviceName, object callbackTarget)
{
Initialize_NoLock(client, connection);
_gate = new object();
_gate = new SemaphoreSlim(initialCount: 1);
_remoteHostClientService = client.Workspace.Services.GetService<IRemoteHostClientService>();
Initialize(client, connection);
_remoteHostClientService = client.Workspace.Services.GetService<IRemoteHostClientService>();
_serviceName = serviceName;
_callbackTarget = callbackTarget;
}
public void Shutdown(CancellationToken cancellationToken)
{
using (_gate.DisposableWait(cancellationToken))
ReferenceCountedDisposable<RemoteHostClient.Connection> connection;
lock (_gate)
{
if (_client != null)
{
_client.StatusChanged -= OnStatusChanged;
}
_connection?.Dispose();
connection = _connectionDoNotAccessDirectly;
_client = null;
_connection = null;
_connectionDoNotAccessDirectly = null;
}
connection?.Dispose();
}
public async Task<bool> TryInvokeAsync(string targetName, IReadOnlyList<object> arguments, CancellationToken cancellationToken)
{
using (await _gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false))
using (var connection = await TryGetConnectionAsync(cancellationToken).ConfigureAwait(false))
{
var connection = await TryGetConnection_NoLockAsync(cancellationToken).ConfigureAwait(false);
if (connection == null)
{
return false;
}
await connection.InvokeAsync(targetName, arguments, cancellationToken).ConfigureAwait(false);
await connection.Target.InvokeAsync(targetName, arguments, cancellationToken).ConfigureAwait(false);
return true;
}
}
public async Task<T> TryInvokeAsync<T>(string targetName, IReadOnlyList<object> arguments, CancellationToken cancellationToken)
{
using (await _gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false))
using (var connection = await TryGetConnectionAsync(cancellationToken).ConfigureAwait(false))
{
var connection = await TryGetConnection_NoLockAsync(cancellationToken).ConfigureAwait(false);
if (connection == null)
{
return default;
}
return await connection.InvokeAsync<T>(targetName, arguments, cancellationToken).ConfigureAwait(false);
return await connection.Target.InvokeAsync<T>(targetName, arguments, cancellationToken).ConfigureAwait(false);
}
}
public async Task<bool> TryInvokeAsync(string targetName, IReadOnlyList<object> arguments, Func<Stream, CancellationToken, Task> funcWithDirectStreamAsync, CancellationToken cancellationToken)
{
using (await _gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false))
using (var connection = await TryGetConnectionAsync(cancellationToken).ConfigureAwait(false))
{
var connection = await TryGetConnection_NoLockAsync(cancellationToken).ConfigureAwait(false);
if (connection == null)
{
return false;
}
await connection.InvokeAsync(targetName, arguments, funcWithDirectStreamAsync, cancellationToken).ConfigureAwait(false);
await connection.Target.InvokeAsync(targetName, arguments, funcWithDirectStreamAsync, cancellationToken).ConfigureAwait(false);
return true;
}
}
public async Task<T> TryInvokeAsync<T>(string targetName, IReadOnlyList<object> arguments, Func<Stream, CancellationToken, Task<T>> funcWithDirectStreamAsync, CancellationToken cancellationToken)
{
using (await _gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false))
using (var connection = await TryGetConnectionAsync(cancellationToken).ConfigureAwait(false))
{
var connection = await TryGetConnection_NoLockAsync(cancellationToken).ConfigureAwait(false);
if (connection == null)
{
return default;
}
return await connection.InvokeAsync(targetName, arguments, funcWithDirectStreamAsync, cancellationToken).ConfigureAwait(false);
return await connection.Target.InvokeAsync(targetName, arguments, funcWithDirectStreamAsync, cancellationToken).ConfigureAwait(false);
}
}
public async Task<bool> TryInvokeAsync(string targetName, Solution solution, IReadOnlyList<object> arguments, CancellationToken cancellationToken)
{
using (await _gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false))
using (var pooledObject = SharedPools.Default<List<object>>().GetPooledObject())
using (var scope = await solution.GetPinnedScopeAsync(cancellationToken).ConfigureAwait(false))
using (var connection = await TryGetConnectionAsync(cancellationToken).ConfigureAwait(false))
{
var connection = await TryGetConnection_NoLockAsync(cancellationToken).ConfigureAwait(false);
if (connection == null)
{
return false;
}
await connection.RegisterPinnedRemotableDataScopeAsync(scope).ConfigureAwait(false);
await connection.InvokeAsync(targetName, arguments, cancellationToken).ConfigureAwait(false);
pooledObject.Object.Add(scope.SolutionInfo);
pooledObject.Object.AddRange(arguments);
await connection.Target.InvokeAsync(targetName, pooledObject.Object, cancellationToken).ConfigureAwait(false);
return true;
}
}
public async Task<T> TryInvokeAsync<T>(string targetName, Solution solution, IReadOnlyList<object> arguments, CancellationToken cancellationToken)
{
using (await _gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false))
using (var pooledObject = SharedPools.Default<List<object>>().GetPooledObject())
using (var scope = await solution.GetPinnedScopeAsync(cancellationToken).ConfigureAwait(false))
using (var connection = await TryGetConnectionAsync(cancellationToken).ConfigureAwait(false))
{
var connection = await TryGetConnection_NoLockAsync(cancellationToken).ConfigureAwait(false);
if (connection == null)
{
return default;
}
await connection.RegisterPinnedRemotableDataScopeAsync(scope).ConfigureAwait(false);
return await connection.InvokeAsync<T>(targetName, arguments, cancellationToken).ConfigureAwait(false);
pooledObject.Object.Add(scope.SolutionInfo);
pooledObject.Object.AddRange(arguments);
return await connection.Target.InvokeAsync<T>(targetName, pooledObject.Object, cancellationToken).ConfigureAwait(false);
}
}
public async Task<bool> TryInvokeAsync(
string targetName, Solution solution, IReadOnlyList<object> arguments, Func<Stream, CancellationToken, Task> funcWithDirectStreamAsync, CancellationToken cancellationToken)
{
using (await _gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false))
using (var pooledObject = SharedPools.Default<List<object>>().GetPooledObject())
using (var scope = await solution.GetPinnedScopeAsync(cancellationToken).ConfigureAwait(false))
using (var connection = await TryGetConnectionAsync(cancellationToken).ConfigureAwait(false))
{
var connection = await TryGetConnection_NoLockAsync(cancellationToken).ConfigureAwait(false);
if (connection == null)
{
return false;
}
await connection.RegisterPinnedRemotableDataScopeAsync(scope).ConfigureAwait(false);
await connection.InvokeAsync(targetName, arguments, funcWithDirectStreamAsync, cancellationToken).ConfigureAwait(false);
pooledObject.Object.Add(scope.SolutionInfo);
pooledObject.Object.AddRange(arguments);
await connection.Target.InvokeAsync(targetName, pooledObject.Object, funcWithDirectStreamAsync, cancellationToken).ConfigureAwait(false);
return true;
}
}
......@@ -223,25 +227,30 @@ public async Task<T> TryInvokeAsync<T>(string targetName, Solution solution, IRe
public async Task<T> TryInvokeAsync<T>(
string targetName, Solution solution, IReadOnlyList<object> arguments, Func<Stream, CancellationToken, Task<T>> funcWithDirectStreamAsync, CancellationToken cancellationToken)
{
using (await _gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false))
using (var pooledObject = SharedPools.Default<List<object>>().GetPooledObject())
using (var scope = await solution.GetPinnedScopeAsync(cancellationToken).ConfigureAwait(false))
using (var connection = await TryGetConnectionAsync(cancellationToken).ConfigureAwait(false))
{
var connection = await TryGetConnection_NoLockAsync(cancellationToken).ConfigureAwait(false);
if (connection == null)
{
return default;
}
await connection.RegisterPinnedRemotableDataScopeAsync(scope).ConfigureAwait(false);
return await connection.InvokeAsync(targetName, arguments, funcWithDirectStreamAsync, cancellationToken).ConfigureAwait(false);
pooledObject.Object.Add(scope.SolutionInfo);
pooledObject.Object.AddRange(arguments);
return await connection.Target.InvokeAsync(targetName, pooledObject.Object, funcWithDirectStreamAsync, cancellationToken).ConfigureAwait(false);
}
}
private async Task<RemoteHostClient.Connection> TryGetConnection_NoLockAsync(CancellationToken cancellationToken)
private async Task<ReferenceCountedDisposable<RemoteHostClient.Connection>> TryGetConnectionAsync(CancellationToken cancellationToken)
{
if (_connection != null)
lock (_gate)
{
return _connection;
if (_connectionDoNotAccessDirectly != null)
{
return _connectionDoNotAccessDirectly.TryAddReference();
}
}
var client = await _remoteHostClientService.TryGetRemoteHostClientAsync(cancellationToken).ConfigureAwait(false);
......@@ -250,15 +259,15 @@ private async Task<RemoteHostClient.Connection> TryGetConnection_NoLockAsync(Can
return null;
}
var session = await client.TryCreateConnectionAsync(_serviceName, _callbackTarget, cancellationToken).ConfigureAwait(false);
if (session == null)
var connection = await client.TryCreateConnectionAsync(_serviceName, _callbackTarget, cancellationToken).ConfigureAwait(false);
if (connection == null)
{
return null;
}
Initialize_NoLock(client, session);
Initialize(client, connection);
return _connection;
return await TryGetConnectionAsync(cancellationToken).ConfigureAwait(false);
}
private void OnStatusChanged(object sender, bool connection)
......@@ -271,15 +280,28 @@ private void OnStatusChanged(object sender, bool connection)
Shutdown(CancellationToken.None);
}
private void Initialize_NoLock(RemoteHostClient client, RemoteHostClient.Connection connection)
private void Initialize(RemoteHostClient client, RemoteHostClient.Connection connection)
{
Contract.ThrowIfNull(client);
Contract.ThrowIfNull(connection);
_client = client;
_client.StatusChanged += OnStatusChanged;
lock (_gate)
{
if (_client != null)
{
Contract.ThrowIfNull(_connectionDoNotAccessDirectly);
_connection = connection;
// someone else beat us and set the connection.
// let this connection closed.
connection.Dispose();
return;
}
_client = client;
_client.StatusChanged += OnStatusChanged;
_connectionDoNotAccessDirectly = new ReferenceCountedDisposable<RemoteHostClient.Connection>(connection);
}
}
}
}
......@@ -289,8 +289,8 @@ public static string GetLocalName(this ITypeSymbol containingType)
var name = containingType.Name;
if (name.Length > 0)
{
var parts = StringBreaker.BreakIntoWordParts(name);
for (var i = parts.GetCount() - 1; i >= 0; i--)
var parts = StringBreaker.GetWordParts(name);
for (var i = parts.Count - 1; i >= 0; i--)
{
var p = parts[i];
if (p.Length > 0 && char.IsLetter(name[p.Start]))
......
......@@ -218,6 +218,11 @@ public static bool IsExtensionMethod(this ISymbol symbol)
return symbol.Kind == SymbolKind.Method && ((IMethodSymbol)symbol).IsExtensionMethod;
}
public static bool IsLocalFunction(this ISymbol symbol)
{
return symbol != null && symbol.Kind == SymbolKind.Method && ((IMethodSymbol)symbol).MethodKind == MethodKind.LocalFunction;
}
public static bool IsModuleMember(this ISymbol symbol)
{
return symbol != null && symbol.ContainingSymbol is INamedTypeSymbol && symbol.ContainingType.TypeKind == TypeKind.Module;
......
......@@ -3,310 +3,204 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.Shared.Utilities
{
/// <summary>
/// Values returned from <see cref="StringBreaker"/> routines.
/// Optimized for short strings with a handful of spans.
/// Each span is encoded in two bitfields 'gap' and 'length' and these
/// bitfields are stored in a 32-bit bitmap.
/// Falls back to a <see cref="List{T}"/> if the encoding won't work.
/// </summary>
internal partial struct StringBreaks : IDisposable
internal static class StringBreaker
{
private readonly ArrayBuilder<TextSpan> _spans;
private readonly EncodedSpans _encodedSpans;
// These two values may be adjusted. The remaining constants are
// derived from them. The values are chosen to minimize the number
// of fallbacks during normal typing. With 5 total bits per span, we
// can encode up to 6 spans, each as long as 15 chars with 0 or 1 char
// gap. This is sufficient for the vast majority of framework symbols.
private const int BitsForGap = 1;
private const int BitsForLength = 4;
private const int BitsPerEncodedSpan = BitsForGap + BitsForLength;
private const int MaxShortSpans = 32 / BitsPerEncodedSpan;
private const int MaxGap = (1 << BitsForGap) - 1;
private const int MaxLength = (1 << BitsForLength) - 1;
/// <summary>
/// Breaks an identifier string into constituent parts.
/// </summary>
public static ArrayBuilder<TextSpan> GetWordParts(string identifier)
=> GetParts(identifier, word: true);
public static StringBreaks Create(string text, Func<string, int, TextSpan> spanGenerator)
{
Debug.Assert(text != null);
Debug.Assert(spanGenerator != null);
return TryEncodeSpans(text, spanGenerator, out var encodedSpans)
? new StringBreaks(encodedSpans)
: new StringBreaks(CreateFallbackList(text, spanGenerator));
}
public static ArrayBuilder<TextSpan> GetCharacterParts(string identifier)
=> GetParts(identifier, word: false);
private static bool TryEncodeSpans(string text, Func<string, int, TextSpan> spanGenerator, out EncodedSpans encodedSpans)
public static ArrayBuilder<TextSpan> GetParts(string text, bool word)
{
encodedSpans = default;
for (int start = 0, b = 0; start < text.Length;)
var parts = ArrayBuilder<TextSpan>.GetInstance();
for (int start = 0; start < text.Length;)
{
var span = spanGenerator(text, start);
var span = StringBreaker.GenerateSpan(text, start, word);
if (span.IsEmpty)
{
// All done
break;
}
int gap = span.Start - start;
Debug.Assert(gap >= 0, "Bad generator.");
if (b >= MaxShortSpans ||
span.Length > MaxLength ||
gap > MaxGap)
{
// Too many spans, or span cannot be encoded.
return false;
}
Debug.Assert(span.Start >= start, "Bad generator.");
encodedSpans[b++] = Encode(gap, span.Length);
parts.Add(span);
start = span.End;
}
return true;
return parts;
}
private static ArrayBuilder<TextSpan> CreateFallbackList(string text, Func<string, int, TextSpan> spanGenerator)
public static TextSpan GenerateSpan(string identifier, int wordStart, bool word)
{
var list = ArrayBuilder<TextSpan>.GetInstance();
for (int start = 0; start < text.Length;)
int length = identifier.Length;
wordStart = SkipPunctuation(identifier, length, wordStart);
if (wordStart < length)
{
var span = spanGenerator(text, start);
if (span.IsEmpty)
var firstChar = identifier[wordStart];
if (char.IsUpper(firstChar))
{
// All done
break;
}
Debug.Assert(span.Start >= start, "Bad generator.");
if (wordStart + 1 == length)
{
return new TextSpan(wordStart, 1);
}
list.Add(span);
start = span.End;
if (word)
{
return ScanWordRun(identifier, length, wordStart);
}
else
{
return ScanCharacterRun(identifier, length, wordStart);
}
}
else if (IsLower(firstChar))
{
return ScanLowerCaseRun(identifier, length, wordStart);
}
else if (firstChar == '_')
{
return new TextSpan(wordStart, 1);
}
else if (char.IsDigit(firstChar))
{
return ScanNumber(identifier, length, wordStart);
}
}
return list;
}
private StringBreaks(EncodedSpans encodedSpans)
{
_encodedSpans = encodedSpans;
_spans = null;
}
private StringBreaks(ArrayBuilder<TextSpan> spans)
{
_encodedSpans = default;
_spans = spans;
return default;
}
public void Dispose()
private static TextSpan ScanCharacterRun(string identifier, int length, int wordStart)
{
_spans?.Free();
}
// In a character run, if we have XMLDocument, then we will break that up into
// X, M, L, and Document.
var current = wordStart + 1;
Debug.Assert(current < length);
var c = identifier[current];
public int GetCount()
{
if (_spans != null)
if (IsLower(c))
{
return _spans.Count;
// "Do"
//
// scan the lowercase letters from here on to scna out 'Document'.
return ScanLowerCaseRun(identifier, length, wordStart);
}
int i;
for (i = 0; i < MaxShortSpans; i++)
else
{
if (_encodedSpans[i] == 0)
{
break;
}
return new TextSpan(wordStart, 1);
}
return i;
}
public TextSpan this[int index]
private static TextSpan ScanWordRun(string identifier, int length, int wordStart)
{
get
// In a word run, if we have XMLDocument, then we will break that up into
// XML and Document.
var current = wordStart + 1;
Debug.Assert(current < length);
var c = identifier[current];
if (char.IsUpper(c))
{
if (index < 0)
// "XM"
current++;
// scan all the upper case letters until we hit one followed by a lower
// case letter.
while (current < length && char.IsUpper(identifier[current]))
{
throw new IndexOutOfRangeException(nameof(index));
current++;
}
if (_spans != null)
if (current < length && IsLower(identifier[current]))
{
return _spans[index];
// hit the 'o' in XMLDo. Return "XML"
Debug.Assert(char.IsUpper(identifier[current - 1]));
var end = current - 1;
return new TextSpan(wordStart, end - wordStart);
}
for (int i = 0, start = 0; i < MaxShortSpans; i++)
else
{
byte b = _encodedSpans[i];
if (b == 0)
{
break;
}
start += DecodeGap(b);
int length = DecodeLength(b);
if (i == index)
{
return new TextSpan(start, length);
}
start += length;
// Hit something else (punctuation, end of string, etc.)
// return the entire upper-case section.
return new TextSpan(wordStart, current - wordStart);
}
throw new IndexOutOfRangeException(nameof(index));
}
}
private static byte Encode(int gap, int length)
{
Debug.Assert(gap >= 0 && gap <= MaxGap);
Debug.Assert(length >= 0 && length <= MaxLength);
return unchecked((byte)((gap << BitsForLength) | length));
}
private static int DecodeLength(byte b) => b & MaxLength;
private static int DecodeGap(byte b) => b >> BitsForLength;
}
internal static class StringBreaker
{
/// <summary>
/// Breaks an identifier string into constituent parts.
/// </summary>
public static StringBreaks BreakIntoCharacterParts(string identifier)
=> StringBreaks.Create(identifier, s_characterPartsGenerator);
/// <summary>
/// Breaks an identifier string into constituent parts.
/// </summary>
public static StringBreaks BreakIntoWordParts(string identifier)
=> StringBreaks.Create(identifier, s_wordPartsGenerator);
private static readonly Func<string, int, TextSpan> s_characterPartsGenerator = (identifier, start) => GenerateSpan(identifier, start, word: false);
private static readonly Func<string, int, TextSpan> s_wordPartsGenerator = (identifier, start) => GenerateSpan(identifier, start, word: true);
public static TextSpan GenerateSpan(string identifier, int wordStart, bool word)
{
for (int i = wordStart + 1; i < identifier.Length; i++)
else if (IsLower(c))
{
var lastIsDigit = char.IsDigit(identifier[i - 1]);
var currentIsDigit = char.IsDigit(identifier[i]);
var transitionFromLowerToUpper = TransitionFromLowerToUpper(identifier, word, i);
var transitionFromUpperToLower = TransitionFromUpperToLower(identifier, word, i, wordStart);
if (char.IsPunctuation(identifier[i - 1]) ||
char.IsPunctuation(identifier[i]) ||
lastIsDigit != currentIsDigit ||
transitionFromLowerToUpper ||
transitionFromUpperToLower)
{
if (!IsAllPunctuation(identifier, wordStart, i))
{
return new TextSpan(wordStart, i - wordStart);
}
wordStart = i;
}
// "Do"
//
// scan the lowercase letters from here on to scan out 'Document'.
return ScanLowerCaseRun(identifier, length, wordStart);
}
else
{
return new TextSpan(wordStart, 1);
}
}
if (!IsAllPunctuation(identifier, wordStart, identifier.Length))
private static TextSpan ScanLowerCaseRun(string identifier, int length, int wordStart)
{
var current = wordStart + 1;
while (current < length && IsLower(identifier[current]))
{
return new TextSpan(wordStart, identifier.Length - wordStart);
current++;
}
return default;
return new TextSpan(wordStart, current - wordStart);
}
private static bool IsAllPunctuation(string identifier, int start, int end)
private static TextSpan ScanNumber(string identifier, int length, int wordStart)
{
for (int i = start; i < end; i++)
var current = wordStart + 1;
while (current < length && char.IsDigit(identifier[current]))
{
var ch = identifier[i];
// We don't consider _ as punctuation as there may be things with that name.
if (!char.IsPunctuation(ch) || ch == '_')
{
return false;
}
current++;
}
return true;
return TextSpan.FromBounds(wordStart, current);
}
private static bool TransitionFromUpperToLower(string identifier, bool word, int index, int wordStart)
private static int SkipPunctuation(string identifier, int length, int wordStart)
{
if (word)
while (wordStart < length)
{
// Cases this supports:
// 1) IDisposable -> I, Disposable
// 2) UIElement -> UI, Element
// 3) HTMLDocument -> HTML, Document
//
// etc.
if (index != wordStart &&
index + 1 < identifier.Length)
var ch = identifier[wordStart];
if (ch != '_' && char.IsPunctuation(ch))
{
var currentIsUpper = char.IsUpper(identifier[index]);
var nextIsLower = char.IsLower(identifier[index + 1]);
if (currentIsUpper && nextIsLower)
{
// We have a transition from an upper to a lower letter here. But we only
// want to break if all the letters that preceded are uppercase. i.e. if we
// have "Foo" we don't want to break that into "F, oo". But if we have
// "IFoo" or "UIFoo", then we want to break that into "I, Foo" and "UI,
// Foo". i.e. the last uppercase letter belongs to the lowercase letters
// that follows. Note: this will make the following not split properly:
// "HELLOthere". However, these sorts of names do not show up in .Net
// programs.
for (int i = wordStart; i < index; i++)
{
if (!char.IsUpper(identifier[i]))
{
return false;
}
}
return true;
}
wordStart++;
continue;
}
break;
}
return false;
return wordStart;
}
private static bool TransitionFromLowerToUpper(string identifier, bool word, int index)
private static bool IsLower(char c)
{
var lastIsUpper = char.IsUpper(identifier[index - 1]);
var currentIsUpper = char.IsUpper(identifier[index]);
if (IsAscii(c))
{
return c >= 'a' && c <= 'z';
}
// See if the casing indicates we're starting a new word. Note: if we're breaking on
// words, then just seeing an upper case character isn't enough. Instead, it has to
// be uppercase and the previous character can't be uppercase.
//
// For example, breaking "AddMetadata" on words would make: Add Metadata
//
// on characters would be: A dd M etadata
//
// Break "AM" on words would be: AM
//
// on characters would be: A M
//
// We break the search string on characters. But we break the symbol name on words.
var transition = word
? (currentIsUpper && !lastIsUpper)
: currentIsUpper;
return transition;
return char.IsLower(c);
}
private static bool IsAscii(char v)
=> v < 0x80;
}
}
// 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.Diagnostics;
namespace Microsoft.CodeAnalysis.Shared.Utilities
{
internal partial struct StringBreaks
{
private struct EncodedSpans
{
private const uint Mask = (1u << BitsPerEncodedSpan) - 1u;
private uint _value;
public byte this[int index]
{
get
{
Debug.Assert(index >= 0 && index < MaxShortSpans);
return (byte)((_value >> (index * BitsPerEncodedSpan)) & Mask);
}
set
{
Debug.Assert(index >= 0 && index < MaxShortSpans);
int shift = index * BitsPerEncodedSpan;
_value = (_value & ~(Mask << shift)) | ((uint)value << shift);
}
}
}
}
}
......@@ -426,8 +426,10 @@ protected bool ReplacementChangesSemantics(SyntaxNode currentOriginalNode, Synta
while (true)
{
if (!skipVerificationForCurrentNode && ReplacementChangesSemanticsForNode(currentOriginalNode,
currentReplacedNode, previousOriginalNode, previousReplacedNode))
if (!skipVerificationForCurrentNode &&
ReplacementChangesSemanticsForNode(
currentOriginalNode, currentReplacedNode,
previousOriginalNode, previousReplacedNode))
{
return true;
}
......
......@@ -108,8 +108,13 @@ public int GetEditDistance(string target, int threshold = int.MaxValue)
private static readonly ThreadLocal<int[,]> t_matrixPool =
new ThreadLocal<int[,]>(() => InitializeMatrix(new int[MaxMatrixPoolDimension, MaxMatrixPoolDimension]));
private static ThreadLocal<Dictionary<char, int>> t_dictionaryPool =
new ThreadLocal<Dictionary<char, int>>(() => new Dictionary<char, int>());
// To find swapped characters we make use of a table that keeps track of the last location
// we found that character. For performnace reasons we only do this work for ascii characters
// (i.e. with value <= 127). This allows us to just use a simple array we can index into instead
// of needing something more expensive like a dictionary.
private const int LastSeenIndexLength = 128;
private static ThreadLocal<int[]> t_lastSeenIndexPool =
new ThreadLocal<int[]>(() => new int[LastSeenIndexLength]);
private static int[,] GetMatrix(int width, int height)
{
......@@ -139,7 +144,6 @@ public int GetEditDistance(string target, int threshold = int.MaxValue)
// So we initialize this once when the matrix is created. For pooled arrays we only
// have to do this once, and it will retain this layout for all future computations.
var width = matrix.GetLength(0);
var height = matrix.GetLength(1);
......@@ -179,7 +183,7 @@ private static int GetEditDistanceWorker(ArraySlice<char> source, ArraySlice<cha
//
// Also Note: sourceLength and targetLength values will mutate and represent the lengths
// of the portions of the arrays we want to compare. However, even after mutation, hte
// invariant htat sourceLength is <= targetLength will remain.
// invariant that sourceLength is <= targetLength will remain.
Debug.Assert(source.Length <= target.Length);
// First:
......@@ -487,8 +491,8 @@ private static int GetEditDistanceWorker(ArraySlice<char> source, ArraySlice<cha
var matrix = GetMatrix(sourceLength + 2, targetLength + 2);
var characterToLastSeenIndex_inSource = t_dictionaryPool.Value;
characterToLastSeenIndex_inSource.Clear();
var characterToLastSeenIndex_inSource = t_lastSeenIndexPool.Value;
Array.Clear(characterToLastSeenIndex_inSource, 0, LastSeenIndexLength);
for (int i = 1; i <= sourceLength; i++)
{
......@@ -517,7 +521,7 @@ private static int GetEditDistanceWorker(ArraySlice<char> source, ArraySlice<cha
{
var targetChar = target[j - 1];
var i1 = GetValue(characterToLastSeenIndex_inSource, targetChar);
var i1 = targetChar < LastSeenIndexLength ? characterToLastSeenIndex_inSource[targetChar] : 0;
var j1 = lastMatchIndex_inTarget;
var matched = sourceChar == targetChar;
......@@ -533,7 +537,10 @@ private static int GetEditDistanceWorker(ArraySlice<char> source, ArraySlice<cha
matrix[i1, j1] + (i - i1 - 1) + 1 + (j - j1 - 1));
}
characterToLastSeenIndex_inSource[sourceChar] = i;
if (sourceChar < LastSeenIndexLength)
{
characterToLastSeenIndex_inSource[sourceChar] = i;
}
// Recall that minimumEditCount is simply the difference in length of our two
// strings. So matrix[i+1,i+1] is the cost for the upper-left diagonal of the
......
......@@ -307,11 +307,16 @@ public Task<Compilation> GetCompilationAsync(SolutionState solution, Cancellatio
}
else
{
return GetOrBuildCompilationInfoAsync(solution, lockGate: true, cancellationToken: cancellationToken)
.ContinueWith(t => t.Result.Compilation, cancellationToken, TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Default);
return GetCompilationSlowAsync(solution, cancellationToken);
}
}
private async Task<Compilation> GetCompilationSlowAsync(SolutionState solution, CancellationToken cancellationToken)
{
var compilationInfo = await GetOrBuildCompilationInfoAsync(solution, lockGate: true, cancellationToken: cancellationToken).ConfigureAwait(false);
return compilationInfo.Compilation;
}
private static string LogBuildCompilationAsync(ProjectState state)
{
return string.Join(",", state.AssemblyName, state.DocumentIds.Count);
......@@ -854,11 +859,16 @@ public Task<bool> HasSuccessfullyLoadedAsync(SolutionState solution, Cancellatio
}
else
{
return GetOrBuildCompilationInfoAsync(solution, lockGate: true, cancellationToken: cancellationToken)
.ContinueWith(t => t.Result.HasSuccessfullyLoadedTransitively, cancellationToken, TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Default);
return HasSuccessfullyLoadedSlowAsync(solution, cancellationToken);
}
}
private async Task<bool> HasSuccessfullyLoadedSlowAsync(SolutionState solution, CancellationToken cancellationToken)
{
var compilationInfo = await GetOrBuildCompilationInfoAsync(solution, lockGate: true, cancellationToken: cancellationToken).ConfigureAwait(false);
return compilationInfo.HasSuccessfullyLoadedTransitively;
}
#region Versions
// Dependent Versions are stored on compilation tracker so they are more likely to survive when unrelated solution branching occurs.
......
......@@ -113,7 +113,7 @@ public async Task FindDeclarationsAsync_Test_NullString()
[Fact]
public async Task FindDeclarationsAsync_Test_Cancellation()
{
await Assert.ThrowsAnyAsync<TaskCanceledException>(async () =>
await Assert.ThrowsAnyAsync<OperationCanceledException>(async () =>
{
var cts = new CancellationTokenSource();
cts.Cancel();
......@@ -278,7 +278,7 @@ public async Task FindSourceDeclarationsAsync_Project_Test_NullString()
[Fact]
public async Task FindSourceDeclarationsAsync_Project_Test_Cancellation()
{
await Assert.ThrowsAnyAsync<TaskCanceledException>(async () =>
await Assert.ThrowsAnyAsync<OperationCanceledException>(async () =>
{
var cts = new CancellationTokenSource();
var project = GetProject(WorkspaceKind.SingleClass);
......
......@@ -47,9 +47,6 @@
<ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup>
<ItemGroup>
<Folder Include="Host\Utilities\" />
</ItemGroup>
<ItemGroup>
<None Include="..\..\Compilers\Core\MSBuildTask\Microsoft.CSharp.Core.targets">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
......
......@@ -683,7 +683,7 @@ public void TestGetLoadedTextAsync()
}
[MethodImpl(MethodImplOptions.NoInlining)]
[Fact, Trait(Traits.Feature, Traits.Features.Workspace)]
[Fact(Skip = "https://github.com/dotnet/roslyn/issues/19427"), Trait(Traits.Feature, Traits.Features.Workspace)]
public void TestGetRecoveredTextAsync()
{
var pid = ProjectId.CreateNewId();
......
......@@ -6,6 +6,7 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Execution;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.TodoComments;
......@@ -22,11 +23,11 @@ internal partial class CodeAnalysisService : IRemoteTodoCommentService
///
/// This will be called by ServiceHub/JsonRpc framework
/// </summary>
public async Task<IList<TodoComment>> GetTodoCommentsAsync(DocumentId documentId, ImmutableArray<TodoCommentDescriptor> tokens, CancellationToken cancellationToken)
public async Task<IList<TodoComment>> GetTodoCommentsAsync(PinnedSolutionInfo solutionInfo, DocumentId documentId, ImmutableArray<TodoCommentDescriptor> tokens, CancellationToken cancellationToken)
{
using (RoslynLogger.LogBlock(FunctionId.CodeAnalysisService_GetTodoCommentsAsync, documentId.ProjectId.DebugName, cancellationToken))
{
var solution = await GetSolutionAsync(cancellationToken).ConfigureAwait(false);
var solution = await GetSolutionAsync(solutionInfo, cancellationToken).ConfigureAwait(false);
var document = solution.GetDocument(documentId);
var service = document.GetLanguageService<ITodoCommentService>();
......
......@@ -102,8 +102,13 @@ protected Task<Solution> GetSolutionAsync(CancellationToken cancellationToken)
{
Contract.ThrowIfNull(_solutionInfo);
var solutionController = (ISolutionController)RoslynServices.SolutionService;
return solutionController.GetSolutionAsync(_solutionInfo.SolutionChecksum, _solutionInfo.FromPrimaryBranch, cancellationToken);
return GetSolutionAsync(RoslynServices, _solutionInfo, cancellationToken);
}
protected Task<Solution> GetSolutionAsync(PinnedSolutionInfo solutionInfo, CancellationToken cancellationToken)
{
var localRoslynService = new RoslynServices(solutionInfo.ScopeId, AssetStorage);
return GetSolutionAsync(localRoslynService, solutionInfo, cancellationToken);
}
protected virtual void Dispose(bool disposing)
......@@ -158,5 +163,11 @@ private void OnRpcDisconnected(object sender, JsonRpcDisconnectedEventArgs e)
Log(TraceEventType.Warning, $"Client stream disconnected unexpectedly: {e.Exception?.GetType().Name} {e.Exception?.Message}");
}
}
private static Task<Solution> GetSolutionAsync(RoslynServices roslynService, PinnedSolutionInfo solutionInfo, CancellationToken cancellationToken)
{
var solutionController = (ISolutionController)roslynService.SolutionService;
return solutionController.GetSolutionAsync(solutionInfo.SolutionChecksum, solutionInfo.FromPrimaryBranch, cancellationToken);
}
}
}
......@@ -9,18 +9,32 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration
Friend Module FieldGenerator
Private Function LastField(Of TDeclaration As SyntaxNode)(
members As SyntaxList(Of TDeclaration),
fieldDeclaration As FieldDeclarationSyntax) As TDeclaration
Dim lastConst = members.Where(Function(m) TypeOf m Is FieldDeclarationSyntax AndAlso
DirectCast(DirectCast(m, Object), FieldDeclarationSyntax).Modifiers.Any(SyntaxKind.ConstKeyword)).LastOrDefault()
members As SyntaxList(Of TDeclaration),
fieldDeclaration As FieldDeclarationSyntax) As TDeclaration
Dim lastConst = members.OfType(Of FieldDeclarationSyntax).
Where(Function(f) f.Modifiers.Any(SyntaxKind.ConstKeyword)).
LastOrDefault()
' Place a const after the last existing const.
If fieldDeclaration.Modifiers.Any(SyntaxKind.ConstKeyword) Then
Return lastConst
Return DirectCast(DirectCast(lastConst, Object), TDeclaration)
End If
' Place a field after the last field, or after the last const.
Return If(VisualBasicCodeGenerationHelpers.LastField(members), lastConst)
Dim lastReadOnly = members.OfType(Of FieldDeclarationSyntax)().
Where(Function(f) f.Modifiers.Any(SyntaxKind.ReadOnlyKeyword)).
LastOrDefault()
Dim lastNormal = members.OfType(Of FieldDeclarationSyntax)().
Where(Function(f) Not f.Modifiers.Any(SyntaxKind.ReadOnlyKeyword) AndAlso Not f.Modifiers.Any(SyntaxKind.ConstKeyword)).
LastOrDefault()
Dim result =
If(fieldDeclaration.Modifiers.Any(SyntaxKind.ReadOnlyKeyword),
If(lastReadOnly, If(lastNormal, lastConst)),
If(lastNormal, If(lastReadOnly, lastConst)))
Return DirectCast(DirectCast(result, Object), TDeclaration)
End Function
Friend Function AddFieldTo(destination As CompilationUnitSyntax,
......
......@@ -172,6 +172,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration
Dim result = 0
If EqualConstness(x.Modifiers, y.Modifiers, result) AndAlso
EqualSharedness(x.Modifiers, y.Modifiers, result) AndAlso
EqualReadOnlyNess(x.Modifiers, y.Modifiers, result) AndAlso
EqualAccessibility(x, x.Modifiers, y, y.Modifiers, result) Then
If _includeName Then
......@@ -365,6 +366,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration
Return BothHaveModifier(x, y, SyntaxKind.SharedKeyword, comparisonResult)
End Function
Private Shared Function EqualReadOnlyness(x As SyntaxTokenList, y As SyntaxTokenList, ByRef comparisonResult As Integer) As Boolean
Return BothHaveModifier(x, y, SyntaxKind.ReadOnlyKeyword, comparisonResult)
End Function
Private Shared Function EqualConstness(x As SyntaxTokenList, y As SyntaxTokenList, ByRef comparisonResult As Integer) As Boolean
Return BothHaveModifier(x, y, SyntaxKind.ConstKeyword, comparisonResult)
End Function
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册