提交 18546a6f 编写于 作者: H Heejae Chang

Merge pull request #9605 from vslsnap/merge-stabilization-into-future-stabilization20160309-160110

Merge stabilization into future-stabilization
......@@ -131,14 +131,14 @@ protected override ImmutableArray<LabelSymbol> BuildLabels()
if (!IsPatternSwitch(_switchSyntax))
{
foreach (var section in _switchSyntax.Sections)
{
// add switch case/default labels
BuildSwitchLabels(section.Labels, ref labels);
foreach (var section in _switchSyntax.Sections)
{
// add switch case/default labels
BuildSwitchLabels(section.Labels, ref labels);
// add regular labels from the statements in the switch section
base.BuildLabels(section.Statements, ref labels);
}
// add regular labels from the statements in the switch section
base.BuildLabels(section.Statements, ref labels);
}
}
return (labels != null) ? labels.ToImmutableAndFree() : ImmutableArray<LabelSymbol>.Empty;
......@@ -155,22 +155,22 @@ private void BuildSwitchLabels(SyntaxList<SwitchLabelSyntax> labelsSyntax, ref A
switch (labelSyntax.Kind())
{
case SyntaxKind.CaseSwitchLabel:
// Bind the switch expression and the switch case label expression, but do not report any diagnostics here.
// Diagnostics will be reported during binding.
var caseLabel = (CaseSwitchLabelSyntax)labelSyntax;
Debug.Assert(caseLabel.Value != null);
DiagnosticBag tempDiagnosticBag = DiagnosticBag.GetInstance();
// Bind the switch expression and the switch case label expression, but do not report any diagnostics here.
// Diagnostics will be reported during binding.
var caseLabel = (CaseSwitchLabelSyntax)labelSyntax;
Debug.Assert(caseLabel.Value != null);
DiagnosticBag tempDiagnosticBag = DiagnosticBag.GetInstance();
var boundLabelExpression = BindValue(caseLabel.Value, tempDiagnosticBag, BindValueKind.RValue);
var boundLabelExpression = BindValue(caseLabel.Value, tempDiagnosticBag, BindValueKind.RValue);
if ((object)switchGoverningType == null)
{
switchGoverningType = this.BindSwitchExpression(_switchSyntax.Expression, tempDiagnosticBag).Type;
}
if ((object)switchGoverningType == null)
{
switchGoverningType = this.BindSwitchExpression(_switchSyntax.Expression, tempDiagnosticBag).Type;
}
boundLabelExpression = ConvertCaseExpression(switchGoverningType, labelSyntax, boundLabelExpression, ref boundLabelConstantOpt, tempDiagnosticBag);
boundLabelExpression = ConvertCaseExpression(switchGoverningType, labelSyntax, boundLabelExpression, ref boundLabelConstantOpt, tempDiagnosticBag);
tempDiagnosticBag.Free();
tempDiagnosticBag.Free();
break;
case SyntaxKind.DefaultSwitchLabel:
......@@ -346,7 +346,7 @@ internal override BoundStatement BindSwitchExpressionAndSections(SwitchStatement
// Bind switch section
ImmutableArray<BoundSwitchSection> boundSwitchSections = BindSwitchSections(node.Sections, originalBinder, diagnostics);
return new BoundSwitchStatement(node, boundSwitchExpression, constantTargetOpt, Locals, LocalFunctions, boundSwitchSections, this.BreakLabel, null);
return new BoundSwitchStatement(node, null, boundSwitchExpression, constantTargetOpt, Locals, LocalFunctions, boundSwitchSections, this.BreakLabel, null);
}
// Bind the switch expression and set the switch governing type
......@@ -519,29 +519,29 @@ private BoundSwitchLabel BindSwitchSectionLabel(SwitchLabelSyntax node, Diagnost
switch (node.Kind())
{
case SyntaxKind.CaseSwitchLabel:
var caseLabelSyntax = (CaseSwitchLabelSyntax)node;
// Bind the label case expression
boundLabelExpressionOpt = BindValue(caseLabelSyntax.Value, diagnostics, BindValueKind.RValue);
var caseLabelSyntax = (CaseSwitchLabelSyntax)node;
// Bind the label case expression
boundLabelExpressionOpt = BindValue(caseLabelSyntax.Value, diagnostics, BindValueKind.RValue);
boundLabelExpressionOpt = ConvertCaseExpression(switchGoverningType, caseLabelSyntax, boundLabelExpressionOpt, ref labelExpressionConstant, diagnostics);
boundLabelExpressionOpt = ConvertCaseExpression(switchGoverningType, caseLabelSyntax, boundLabelExpressionOpt, ref labelExpressionConstant, diagnostics);
// Check for bind errors
hasErrors = hasErrors || boundLabelExpressionOpt.HasAnyErrors;
// Check for bind errors
hasErrors = hasErrors || boundLabelExpressionOpt.HasAnyErrors;
// SPEC: The constant expression of each case label must denote a value that
// SPEC: is implicitly convertible (§6.1) to the governing type of the switch statement.
// SPEC: The constant expression of each case label must denote a value that
// SPEC: is implicitly convertible (§6.1) to the governing type of the switch statement.
// Prevent cascading diagnostics
if (!hasErrors && labelExpressionConstant == null)
{
diagnostics.Add(ErrorCode.ERR_ConstantExpected, caseLabelSyntax.Location);
hasErrors = true;
}
// Prevent cascading diagnostics
if (!hasErrors && labelExpressionConstant == null)
{
diagnostics.Add(ErrorCode.ERR_ConstantExpected, caseLabelSyntax.Location);
hasErrors = true;
}
// LabelSymbols for all the switch case labels are created by BuildLabels().
// Fetch the matching switch case label symbols
matchedLabelSymbols = FindMatchingSwitchCaseLabels(labelExpressionConstant, caseLabelSyntax);
// LabelSymbols for all the switch case labels are created by BuildLabels().
// Fetch the matching switch case label symbols
matchedLabelSymbols = FindMatchingSwitchCaseLabels(labelExpressionConstant, caseLabelSyntax);
break;
case SyntaxKind.CasePatternSwitchLabel:
// pattern matching in case is not yet implemented.
......@@ -549,11 +549,11 @@ private BoundSwitchLabel BindSwitchSectionLabel(SwitchLabelSyntax node, Diagnost
{
Error(diagnostics, ErrorCode.ERR_FeatureIsUnimplemented, node, MessageID.IDS_FeaturePatternMatching.Localize());
hasErrors = true;
}
}
matchedLabelSymbols = new List<SourceLabelSymbol>();
break;
case SyntaxKind.DefaultSwitchLabel:
matchedLabelSymbols = GetDefaultLabels();
matchedLabelSymbols = GetDefaultLabels();
break;
default:
throw ExceptionUtilities.Unreachable;
......
......@@ -623,8 +623,8 @@
<Node Name="BoundMultipleLocalDeclarations" Base="BoundStatement">
<Field Name="LocalDeclarations" Type="ImmutableArray&lt;BoundLocalDeclaration&gt;"/>
</Node>
<!--
<!--
Bound node that represents a local function declaration:
void Foo() { }
-->
......@@ -682,6 +682,7 @@
</Node>
<Node Name="BoundSwitchStatement" Base="BoundStatement">
<Field Name="LoweredPreambleOpt" Type="BoundStatement" Null="allow"/>
<Field Name="Expression" Type="BoundExpression"/>
<Field Name="ConstantTargetOpt" Type="LabelSymbol" Null= "allow"/>
......@@ -1400,7 +1401,7 @@
<Field Name="Alignment" Type="BoundExpression" Null="allow"/>
<Field Name="Format" Type="BoundExpression" Null="allow"/>
</Node>
<Node Name="BoundIsPatternExpression" Base="BoundExpression">
<Field Name="Expression" Type="BoundExpression" Null="disallow"/>
<Field Name="Pattern" Type="BoundPattern" Null="disallow"/>
......
......@@ -5,12 +5,9 @@
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Reflection.Metadata;
using Microsoft.CodeAnalysis.CodeGen;
using Microsoft.CodeAnalysis.Collections;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Symbols;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
......@@ -1029,6 +1026,12 @@ private void EmitCatchBlock(BoundCatchBlock catchBlock)
private void EmitSwitchStatement(BoundSwitchStatement switchStatement)
{
var preambleOpt = switchStatement.LoweredPreambleOpt;
if (preambleOpt != null)
{
EmitStatement(preambleOpt);
}
// Switch expression must have a valid switch governing type
Debug.Assert((object)switchStatement.Expression.Type != null);
Debug.Assert(switchStatement.Expression.Type.IsValidSwitchGoverningType());
......@@ -1570,11 +1573,12 @@ public override BoundNode VisitLabelStatement(BoundLabelStatement node)
public override BoundNode VisitSwitchStatement(BoundSwitchStatement node)
{
var breakLabelClone = GetLabelClone(node.BreakLabel);
var preambleOpt = (BoundStatement)this.Visit(node.LoweredPreambleOpt);
// expressions do not contain labels or branches
BoundExpression boundExpression = node.Expression;
ImmutableArray<BoundSwitchSection> switchSections = (ImmutableArray<BoundSwitchSection>)this.VisitList(node.SwitchSections);
return node.Update(boundExpression, node.ConstantTargetOpt, node.InnerLocals, node.InnerLocalFunctions, switchSections, breakLabelClone, node.StringEquality);
return node.Update(preambleOpt, boundExpression, node.ConstantTargetOpt, node.InnerLocals, node.InnerLocalFunctions, switchSections, breakLabelClone, node.StringEquality);
}
public override BoundNode VisitSwitchLabel(BoundSwitchLabel node)
......
......@@ -273,7 +273,7 @@ public void Free()
{
_localDefs.Free();
_localDefs = null;
}
}
_pool?.Free(this);
}
......@@ -378,7 +378,7 @@ internal enum ExprContext
Box
}
// Analyses the tree trying to figure which locals may live on stack.
// Analyzes the tree trying to figure which locals may live on stack.
// It is a fairly delicate process and must be very familiar with how CodeGen works.
// It is essentially a part of CodeGen.
//
......@@ -1413,6 +1413,9 @@ public override BoundNode VisitUnaryOperator(BoundUnaryOperator node)
public override BoundNode VisitSwitchStatement(BoundSwitchStatement node)
{
Debug.Assert(EvalStackIsEmpty());
var preambleOpt = (BoundStatement)this.Visit(node.LoweredPreambleOpt);
DeclareLocals(node.InnerLocals, 0);
// switch needs a byval local or a parameter as a key.
......@@ -1445,7 +1448,7 @@ public override BoundNode VisitSwitchStatement(BoundSwitchStatement node)
this.RecordLabel(breakLabel);
}
var result = node.Update(boundExpression, node.ConstantTargetOpt, node.InnerLocals, node.InnerLocalFunctions, switchSections, breakLabel, node.StringEquality);
var result = node.Update(preambleOpt, boundExpression, node.ConstantTargetOpt, node.InnerLocals, node.InnerLocalFunctions, switchSections, breakLabel, node.StringEquality);
// implicit control flow
EnsureOnlyEvalStack();
......
......@@ -325,7 +325,7 @@ public override INamedTypeSymbol CreateErrorTypeSymbol(INamespaceOrTypeSymbol co
_syntaxAndDeclarations = syntaxAndDeclarations;
Debug.Assert((object)_lazyAssemblySymbol == null);
if (EventQueue != null) EventQueue.Enqueue(new CompilationStartedEvent(this));
if (EventQueue != null) EventQueue.TryEnqueue(new CompilationStartedEvent(this));
}
internal override void ValidateDebugEntryPoint(IMethodSymbol debugEntryPoint, DiagnosticBag diagnostics)
......@@ -1716,13 +1716,15 @@ private void CompleteTree(SyntaxTree tree)
if (completedCompilationUnit)
{
EventQueue.Enqueue(new CompilationUnitCompletedEvent(this, tree));
EventQueue.TryEnqueue(new CompilationUnitCompletedEvent(this, tree));
}
if (completedCompilation)
{
EventQueue.Enqueue(new CompilationCompletedEvent(this));
EventQueue.Complete(); // signal the end of compilation events
// signal the end of compilation events
EventQueue.TryEnqueue(new CompilationCompletedEvent(this));
EventQueue.PromiseNotToEnqueue();
EventQueue.TryComplete();
}
}
......@@ -2886,7 +2888,7 @@ internal override AnalyzerDriver AnalyzerForLanguage(ImmutableArray<DiagnosticAn
internal void SymbolDeclaredEvent(Symbol symbol)
{
EventQueue?.Enqueue(new SymbolDeclaredCompilationEvent(this, symbol));
EventQueue?.TryEnqueue(new SymbolDeclaredCompilationEvent(this, symbol));
}
/// <summary>
......
......@@ -958,7 +958,7 @@ public override object VisitField(FieldSymbol symbol, TypeCompilationState argum
});
MethodSymbol symbolToProduce = methodSymbol.PartialDefinitionPart ?? methodSymbol;
_compilation.EventQueue.Enqueue(new SymbolDeclaredCompilationEvent(_compilation, symbolToProduce, lazySemanticModel));
_compilation.EventQueue.TryEnqueue(new SymbolDeclaredCompilationEvent(_compilation, symbolToProduce, lazySemanticModel));
}
// Don't lower if we're not emitting or if there were errors.
......
......@@ -708,7 +708,7 @@ private void PopFrame()
}
/// <summary>
/// Analyses method body for try blocks with awaits in finally blocks
/// Analyzes method body for try blocks with awaits in finally blocks
/// Also collects labels that such blocks contain.
/// </summary>
private sealed class AwaitInFinallyAnalysis : LabelCollector
......
......@@ -518,9 +518,10 @@ public override BoundNode VisitSwitchStatement(BoundSwitchStatement node)
EnterStatement(node);
BoundSpillSequenceBuilder builder = null;
var preambleOpt = (BoundStatement)this.Visit(node.LoweredPreambleOpt);
var boundExpression = VisitExpression(ref builder, node.Expression);
var switchSections = this.VisitList(node.SwitchSections);
return UpdateStatement(builder, node.Update(boundExpression, node.ConstantTargetOpt, node.InnerLocals, node.InnerLocalFunctions, switchSections, node.BreakLabel, node.StringEquality), substituteTemps: true);
return UpdateStatement(builder, node.Update(preambleOpt, boundExpression, node.ConstantTargetOpt, node.InnerLocals, node.InnerLocalFunctions, switchSections, node.BreakLabel, node.StringEquality), substituteTemps: true);
}
public override BoundNode VisitThrowStatement(BoundThrowStatement node)
......
......@@ -9,7 +9,7 @@ namespace Microsoft.CodeAnalysis.CSharp
internal partial class IteratorMethodToStateMachineRewriter
{
/// <summary>
/// Analyses method body for yields in try blocks and labels that they contain.
/// Analyzes method body for yields in try blocks and labels that they contain.
/// </summary>
private sealed class YieldsInTryAnalysis : LabelCollector
{
......@@ -110,7 +110,7 @@ public override BoundNode VisitExpressionStatement(BoundExpressionStatement node
}
/// <summary>
/// Analyses method body for labels.
/// Analyzes method body for labels.
/// </summary>
internal abstract class LabelCollector : BoundTreeWalkerWithStackGuardWithoutRecursionOnTheLeftOfBinaryOperator
{
......
......@@ -84,11 +84,12 @@ public override BoundNode VisitSwitchStatement(BoundSwitchStatement node)
return rewrittenExpression.Type.IsNullableType() ?
MakeSwitchStatementWithNullableExpression(syntax, rewrittenExpression, rewrittenSections, constantTargetOpt, locals, localFunctions, breakLabel, oldNode) :
MakeSwitchStatementWithNonNullableExpression(syntax, rewrittenExpression, rewrittenSections, constantTargetOpt, locals, localFunctions, breakLabel, oldNode);
MakeSwitchStatementWithNonNullableExpression(syntax, null, rewrittenExpression, rewrittenSections, constantTargetOpt, locals, localFunctions, breakLabel, oldNode);
}
private BoundStatement MakeSwitchStatementWithNonNullableExpression(
CSharpSyntaxNode syntax,
BoundStatement preambleOpt,
BoundExpression rewrittenExpression,
ImmutableArray<BoundSwitchSection> rewrittenSections,
LabelSymbol constantTargetOpt,
......@@ -112,6 +113,7 @@ public override BoundNode VisitSwitchStatement(BoundSwitchStatement node)
}
return oldNode.Update(
loweredPreambleOpt: preambleOpt,
expression: rewrittenExpression,
constantTargetOpt: constantTargetOpt,
innerLocals: locals,
......@@ -161,7 +163,6 @@ public override BoundNode VisitSwitchStatement(BoundSwitchStatement node)
condition: MakeNullCheck(exprSyntax, rewrittenExpression, BinaryOperatorKind.NullableNullEqual),
jumpIfTrue: true,
label: GetNullValueTargetSwitchLabel(rewrittenSections, breakLabel));
statementBuilder.Add(condGotoNullValueTargetLabel);
// Rewrite the switch statement using nullable expression's underlying value as the switch expression.
......@@ -171,8 +172,16 @@ public override BoundNode VisitSwitchStatement(BoundSwitchStatement node)
rewrittenExpression = callGetValueOrDefault;
// rewrite switch statement
BoundStatement rewrittenSwitchStatement = MakeSwitchStatementWithNonNullableExpression(syntax,
rewrittenExpression, rewrittenSections, constantTargetOpt, locals, localFunctions, breakLabel, oldNode);
BoundStatement rewrittenSwitchStatement = MakeSwitchStatementWithNonNullableExpression(
syntax,
condGotoNullValueTargetLabel,
rewrittenExpression,
rewrittenSections,
constantTargetOpt,
locals,
localFunctions,
breakLabel,
oldNode);
statementBuilder.Add(rewrittenSwitchStatement);
......
......@@ -147,10 +147,11 @@ public override BoundNode VisitSequence(BoundSequence node)
public override BoundNode VisitSwitchStatement(BoundSwitchStatement node)
{
var preambleOpt = (BoundStatement)this.Visit(node.LoweredPreambleOpt);
var newInnerLocals = RewriteLocals(node.InnerLocals);
BoundExpression boundExpression = (BoundExpression)this.Visit(node.Expression);
ImmutableArray<BoundSwitchSection> switchSections = (ImmutableArray<BoundSwitchSection>)this.VisitList(node.SwitchSections);
return node.Update(boundExpression, node.ConstantTargetOpt, newInnerLocals, node.InnerLocalFunctions, switchSections, node.BreakLabel, node.StringEquality);
return node.Update(preambleOpt, boundExpression, node.ConstantTargetOpt, newInnerLocals, node.InnerLocalFunctions, switchSections, node.BreakLabel, node.StringEquality);
}
public override BoundNode VisitForStatement(BoundForStatement node)
......
......@@ -763,6 +763,7 @@ public BoundStatement Switch(BoundExpression ex, params BoundSwitchSection[] sec
CheckSwitchSections(s);
return new BoundSwitchStatement(
Syntax,
null,
ex,
null,
ImmutableArray<LocalSymbol>.Empty,
......
......@@ -223,9 +223,13 @@ public override bool Equals(object obj)
public override int GetHashCode()
{
return Hash.Combine(
_distinguisher._compilation.GetHashCode(),
GetSymbol().GetHashCode());
int result = GetSymbol().GetHashCode();
var compilation = _distinguisher._compilation;
if (compilation != null)
{
result = Hash.Combine(result, compilation.GetHashCode());
}
return result;
}
public override string ToString()
......
......@@ -5224,5 +5224,73 @@ public object Foo<T>()
}";
CompileAndVerify(source, new[] { SystemCoreRef });
}
[WorkItem(9131, "https://github.com/dotnet/roslyn/issues/9131")]
[Fact]
public void ClosureInSwitchStatementWithNullableExpression()
{
string source =
@"using System;
class C
{
static void Main()
{
int? i = null;
switch (i)
{
default:
object o = null;
Func<object> f = () => o;
Console.Write(""{0}"", f() == null);
break;
case 0:
o = 1;
break;
}
}
}";
var compilation = CompileAndVerify(source, expectedOutput: @"True");
compilation.VerifyIL("C.Main",
@"{
// Code size 92 (0x5c)
.maxstack 3
.locals init (int? V_0, //i
C.<>c__DisplayClass0_0 V_1, //CS$<>8__locals0
int V_2,
System.Func<object> V_3) //f
IL_0000: ldloca.s V_0
IL_0002: initobj ""int?""
IL_0008: newobj ""C.<>c__DisplayClass0_0..ctor()""
IL_000d: stloc.1
IL_000e: ldloca.s V_0
IL_0010: call ""bool int?.HasValue.get""
IL_0015: brfalse.s IL_0022
IL_0017: ldloca.s V_0
IL_0019: call ""int int?.GetValueOrDefault()""
IL_001e: stloc.2
IL_001f: ldloc.2
IL_0020: brfalse.s IL_004f
IL_0022: ldloc.1
IL_0023: ldnull
IL_0024: stfld ""object C.<>c__DisplayClass0_0.o""
IL_0029: ldloc.1
IL_002a: ldftn ""object C.<>c__DisplayClass0_0.<Main>b__0()""
IL_0030: newobj ""System.Func<object>..ctor(object, System.IntPtr)""
IL_0035: stloc.3
IL_0036: ldstr ""{0}""
IL_003b: ldloc.3
IL_003c: callvirt ""object System.Func<object>.Invoke()""
IL_0041: ldnull
IL_0042: ceq
IL_0044: box ""bool""
IL_0049: call ""void System.Console.Write(string, object)""
IL_004e: ret
IL_004f: ldloc.1
IL_0050: ldc.i4.1
IL_0051: box ""int""
IL_0056: stfld ""object C.<>c__DisplayClass0_0.o""
IL_005b: ret
}");
}
}
}
......@@ -224,6 +224,27 @@ partial class Class
Assert.True(completedCompilationUnits.Contains(tree1.FilePath));
}
[Fact, WorkItem(8178, "https://github.com/dotnet/roslyn/issues/8178")]
public void TestEarlyCancellation()
{
var source = @"
namespace N1
{
partial class Class
{
private void NonPartialMethod1() { }
partial void PartialMethod();
}
}
";
var tree = CSharpSyntaxTree.ParseText(source, path: "file1");
var eventQueue = new AsyncQueue<CompilationEvent>();
var compilation = CreateCompilationWithMscorlib45(new[] { tree }).WithEventQueue(eventQueue);
eventQueue.TryComplete(); // complete the queue before the compiler is finished with it
var model = compilation.GetSemanticModel(tree);
model.GetDiagnostics(tree.GetRoot().FullSpan);
}
private static bool DequeueCompilationEvents(AsyncQueue<CompilationEvent> eventQueue, out bool compilationStartedFired, out HashSet<string> declaredSymbolNames, out HashSet<string> completedCompilationUnits)
{
compilationStartedFired = false;
......
......@@ -704,6 +704,52 @@ private static bool AreEqual(SymbolDistinguisher a, SymbolDistinguisher b)
return a.First.Equals(b.First) && a.Second.Equals(b.Second);
}
[WorkItem(8470, "https://github.com/dotnet/roslyn/issues/8470")]
[Fact]
public void DescriptionNoCompilation()
{
var source =
@"class A { }
class B { }";
var compilation = CreateCompilationWithMscorlib(source);
var typeA = compilation.GetMember<NamedTypeSymbol>("A");
var typeB = compilation.GetMember<NamedTypeSymbol>("B");
var distinguisher1 = new SymbolDistinguisher(compilation, typeA, typeB);
var distinguisher2 = new SymbolDistinguisher(null, typeA, typeB);
var arg1A = distinguisher1.First;
var arg2A = distinguisher2.First;
Assert.False(arg1A.Equals(arg2A));
Assert.False(arg2A.Equals(arg1A));
int hashCode1A = arg1A.GetHashCode();
int hashCode2A = arg2A.GetHashCode();
}
[WorkItem(8470, "https://github.com/dotnet/roslyn/issues/8470")]
[Fact]
public void CompareDiagnosticsNoCompilation()
{
var source1 =
@"public class A { }
public class B<T> where T : A { }";
var compilation1 = CreateCompilationWithMscorlib(source1);
compilation1.VerifyDiagnostics();
var ref1 = compilation1.EmitToImageReference();
var source2 =
@"class C : B<object> { }";
var compilation2 = CreateCompilationWithMscorlib(source2, references: new[] { ref1 });
var diagnostics = compilation2.GetDiagnostics();
diagnostics.Verify(
// (1,7): error CS0311: The type 'object' cannot be used as type parameter 'T' in the generic type or method 'B<T>'. There is no implicit reference conversion from 'object' to 'A'.
// class C : B<object> { }
Diagnostic(ErrorCode.ERR_GenericConstraintNotSatisfiedRefType, "C").WithArguments("B<T>", "A", "T", "object").WithLocation(1, 7));
// Command-line compiler calls SymbolDistinguisher.Description.GetHashCode()
// when adding diagnostics to a set.
foreach (var diagnostic in diagnostics)
{
diagnostic.GetHashCode();
}
}
[WorkItem(8588, "https://github.com/dotnet/roslyn/issues/8588")]
[Fact]
public void SameErrorTypeArgumentsDifferentSourceAssemblies()
......
......@@ -43,6 +43,17 @@ public void TryEnqueueAfterComplete()
Assert.False(queue.TryEnqueue(42));
}
[Fact]
public void TryEnqueueAfterPromisingNotTo()
{
var queue = new AsyncQueue<int>();
Assert.True(queue.TryEnqueue(42));
queue.PromiseNotToEnqueue();
Assert.Throws(typeof(InvalidOperationException), () => {
queue.TryEnqueue(42);
});
}
[Fact]
public async Task DequeueThenEnqueue()
{
......
// 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 Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Semantics;
......
......@@ -1073,7 +1073,7 @@ internal class CodeAnalysisResources {
}
/// <summary>
/// Looks up a localized string similar to Then span does not include the end of a line..
/// Looks up a localized string similar to The span does not include the end of a line..
/// </summary>
internal static string SpanDoesNotIncludeEndOfLine {
get {
......
// 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;
......@@ -71,7 +72,7 @@ public SyntaxTree SyntaxTree
/// <returns></returns>
public IOperation GetOperation(SyntaxNode node, CancellationToken cancellationToken = default(CancellationToken))
{
return this.GetOperationCore(node, cancellationToken);
return GetOperationCore(node, cancellationToken);
}
protected abstract IOperation GetOperationCore(SyntaxNode node, CancellationToken cancellationToken);
......
......@@ -196,7 +196,7 @@ private PerAnalyzerState GetAnalyzerState(DiagnosticAnalyzer analyzer)
var fullSpan = tree.GetRoot(cancellationToken).FullSpan;
var declarationInfos = new List<DeclarationInfo>();
model.ComputeDeclarationsInSpan(fullSpan, getSymbol: true, builder: declarationInfos, cancellationToken: cancellationToken);
return declarationInfos.Select(declInfo => declInfo.DeclaredSymbol).WhereNotNull();
return declarationInfos.Select(declInfo => declInfo.DeclaredSymbol).Distinct().WhereNotNull();
}
private static ImmutableArray<CompilationEvent> CreateCompilationEventsForTree(IEnumerable<ISymbol> declaredSymbols, SyntaxTree tree, Compilation compilation)
......
......@@ -24,6 +24,7 @@ internal sealed class AsyncQueue<TElement>
private readonly Queue<TElement> _data = new Queue<TElement>();
private Queue<TaskCompletionSource<TElement>> _waiters;
private bool _completed;
private bool _disallowEnqueue;
private object SyncObject
{
......@@ -70,6 +71,11 @@ public bool TryEnqueue(TElement value)
private bool EnqueueCore(TElement value)
{
if (_disallowEnqueue)
{
throw new InvalidOperationException($"Cannot enqueue data after PromiseNotToEnqueue.");
}
TaskCompletionSource<TElement> waiter;
lock (SyncObject)
{
......@@ -140,6 +146,11 @@ public void Complete()
}
}
public void PromiseNotToEnqueue()
{
_disallowEnqueue = true;
}
/// <summary>
/// Same operation as <see cref="AsyncQueue{TElement}.Complete"/> except it will not
/// throw if the queue is already completed.
......
......@@ -904,7 +904,7 @@ private AsyncQueue<CompilationEvent> GetPendingEvents(ImmutableArray<DiagnosticA
foreach (var compilationEvent in _analysisState.GetPendingEvents(analyzers, tree))
{
eventQueue.Enqueue(compilationEvent);
eventQueue.TryEnqueue(compilationEvent);
}
return eventQueue;
......@@ -920,7 +920,7 @@ private AsyncQueue<CompilationEvent> GetPendingEvents(ImmutableArray<DiagnosticA
foreach (var compilationEvent in _analysisState.GetPendingEvents(analyzers, includeSourceEvents, includeNonSourceEvents))
{
eventQueue.Enqueue(compilationEvent);
eventQueue.TryEnqueue(compilationEvent);
}
return eventQueue;
......
......@@ -11,7 +11,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
Friend Class Analyzer
''' <summary>
''' Analyses method body for error conditions such as definite assignments, unreachable code etc...
''' Analyzes method body for error conditions such as definite assignments, unreachable code etc...
'''
''' This analysis is done when doing the full compile or when responding to GetCompileDiagnostics.
''' This method assume that the trees are already bound and will not do any rewriting/lowering
......
......@@ -27,7 +27,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGen
End Enum
''' <summary>
''' Analyses the tree trying to figure which locals may live on stack. It is
''' Analyzes the tree trying to figure which locals may live on stack. It is
''' a fairly delicate process and must be very familiar with how CodeGen works.
''' It is essentially a part of CodeGen.
'''
......
......@@ -1196,7 +1196,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
Return semanticModel
End Function)
Dim symbolToProduce = If(method.PartialDefinitionPart, method)
compilation.EventQueue.Enqueue(New SymbolDeclaredCompilationEvent(compilation, symbolToProduce, lazySemanticModel))
compilation.EventQueue.TryEnqueue(New SymbolDeclaredCompilationEvent(compilation, symbolToProduce, lazySemanticModel))
End If
End If
End If
......
......@@ -448,7 +448,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
Debug.Assert(_lazyAssemblySymbol Is Nothing)
If Me.EventQueue IsNot Nothing Then
Me.EventQueue.Enqueue(New CompilationStartedEvent(Me))
Me.EventQueue.TryEnqueue(New CompilationStartedEvent(Me))
End If
End Sub
......@@ -1649,12 +1649,13 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
End SyncLock
If completedCompilationUnit Then
EventQueue.Enqueue(New CompilationUnitCompletedEvent(Me, tree))
EventQueue.TryEnqueue(New CompilationUnitCompletedEvent(Me, tree))
End If
If completedCompilation Then
EventQueue.Enqueue(New CompilationCompletedEvent(Me))
EventQueue.Complete() ' signal the End Of compilation events
EventQueue.TryEnqueue(New CompilationCompletedEvent(Me))
EventQueue.PromiseNotToEnqueue()
EventQueue.TryComplete() ' signal the End Of compilation events
End If
End Sub
......@@ -1675,7 +1676,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
Friend Sub SymbolDeclaredEvent(symbol As Symbol)
If ShouldAddEvent(symbol) Then
EventQueue.Enqueue(New SymbolDeclaredCompilationEvent(Me, symbol))
EventQueue.TryEnqueue(New SymbolDeclaredCompilationEvent(Me, symbol))
End If
End Sub
......
......@@ -130,7 +130,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
End Sub
''' <summary>
''' Analyses method body that belongs to the given method symbol.
''' Analyzes method body that belongs to the given method symbol.
''' </summary>
Public Shared Function AnalyzeMethodBody(node As BoundBlock, method As MethodSymbol, symbolsCapturedWithoutCtor As ISet(Of Symbol), diagnostics As DiagnosticBag) As Analysis
Debug.Assert(Not node.HasErrors)
......
......@@ -40,7 +40,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Syntax.InternalSyntax
Case Feature.DigitSeparators
Return "digitSeparators"
Case Feature.BinaryLiterals
Case feature.BinaryLiterals
Return "binaryLiterals"
Case Else
......
......@@ -3887,6 +3887,73 @@ End Class
</compilation>
CompileAndVerify(source)
End Sub
<Fact>
Public Sub ClosureInSwitchStatementWithNullableExpression()
Dim verifier = CompileAndVerify(
<compilation>
<file name="a.vb">
Imports System
Class C
Shared Sub Main()
Dim i As Integer? = Nothing
Select Case i
Case 0
Case Else
Dim o As Object = Nothing
Dim f = Function() o
Console.Write("{0}", f() Is Nothing)
End Select
End Sub
End Class
</file>
</compilation>, expectedOutput:="True")
verifier.VerifyIL("C.Main",
<![CDATA[
{
// Code size 104 (0x68)
.maxstack 3
.locals init (Integer? V_0,
Boolean? V_1,
VB$AnonymousDelegate_0(Of Object) V_2) //f
IL_0000: ldloca.s V_0
IL_0002: initobj "Integer?"
IL_0008: ldloc.0
IL_0009: stloc.0
IL_000a: ldloca.s V_0
IL_000c: call "Function Integer?.get_HasValue() As Boolean"
IL_0011: brtrue.s IL_001e
IL_0013: ldloca.s V_1
IL_0015: initobj "Boolean?"
IL_001b: ldloc.1
IL_001c: br.s IL_002d
IL_001e: ldloca.s V_0
IL_0020: call "Function Integer?.GetValueOrDefault() As Integer"
IL_0025: ldc.i4.0
IL_0026: ceq
IL_0028: newobj "Sub Boolean?..ctor(Boolean)"
IL_002d: stloc.1
IL_002e: ldloca.s V_1
IL_0030: call "Function Boolean?.GetValueOrDefault() As Boolean"
IL_0035: brtrue.s IL_0067
IL_0037: newobj "Sub C._Closure$__1-0..ctor()"
IL_003c: dup
IL_003d: ldnull
IL_003e: stfld "C._Closure$__1-0.$VB$Local_o As Object"
IL_0043: ldftn "Function C._Closure$__1-0._Lambda$__0() As Object"
IL_0049: newobj "Sub VB$AnonymousDelegate_0(Of Object)..ctor(Object, System.IntPtr)"
IL_004e: stloc.2
IL_004f: ldstr "{0}"
IL_0054: ldloc.2
IL_0055: callvirt "Function VB$AnonymousDelegate_0(Of Object).Invoke() As Object"
IL_005a: ldnull
IL_005b: ceq
IL_005d: box "Boolean"
IL_0062: call "Sub System.Console.Write(String, Object)"
IL_0067: ret
}
]]>)
End Sub
End Class
End Namespace
......@@ -124,8 +124,7 @@ private void ProduceTags(TaggerContext<TTag> context, DocumentSnapshotSpan spanT
{
if (_owner.IncludeDiagnostic(diagnosticData))
{
var actualSpan = diagnosticData
.GetExistingOrCalculatedTextSpan(sourceText)
var actualSpan = AdjustSpan(diagnosticData.GetExistingOrCalculatedTextSpan(sourceText), sourceText)
.ToSnapshotSpan(editorSnapshot)
.TranslateTo(requestedSnapshot, SpanTrackingMode.EdgeExclusive);
......@@ -142,6 +141,21 @@ private void ProduceTags(TaggerContext<TTag> context, DocumentSnapshotSpan spanT
}
}
private TextSpan AdjustSpan(TextSpan span, SourceText text)
{
var start = Math.Max(0, Math.Min(span.Start, text.Length));
var end = Math.Max(0, Math.Min(span.End, text.Length));
if (start > end)
{
var temp = end;
end = start;
start = temp;
}
return TextSpan.FromBounds(start, end);
}
private bool IsSuppressed(NormalizedSnapshotSpanCollection suppressedSpans, SnapshotSpan span)
{
return suppressedSpans != null && suppressedSpans.IntersectsWith(span);
......
......@@ -49,6 +49,7 @@ private void Format(ITextView textView, Document document, TextSpan? selectionOp
{
var formattingService = document.GetLanguageService<IEditorFormattingService>();
using (Logger.LogBlock(FunctionId.CommandHandler_FormatCommand, cancellationToken))
using (var transaction = new CaretPreservingEditTransaction(EditorFeaturesResources.Formatting, textView, _undoHistoryRegistry, _editorOperationsFactoryService))
{
var changes = formattingService.GetFormattingChangesAsync(document, selectionOpt, cancellationToken).WaitAndGetResult(cancellationToken);
......
......@@ -198,7 +198,11 @@ internal void SetReferenceSpans(IEnumerable<TextSpan> spans)
_activeSpan = _activeSpan.HasValue && spans.Contains(_activeSpan.Value)
? _activeSpan
: spans.FirstOrNullable();
: spans.Where(s =>
// in tests `ActiveTextview` can be null so don't depend on it
ActiveTextview == null ||
ActiveTextview.GetSpanInView(_subjectBuffer.CurrentSnapshot.GetSpan(s.ToSpan())).Count != 0) // spans were successfully projected
.FirstOrNullable(); // filter to spans that have a projection
UpdateReadOnlyRegions();
this.ApplyReplacementText(updateSelection: false);
......@@ -245,10 +249,31 @@ private void OnTextBufferChanged(object sender, TextContentChangedEventArgs args
}
}
/// <summary>
/// This is a work around for a bug in Razor where the projection spans can get out-of-sync with the
/// identifiers. When that bug is fixed this helper can be deleted.
/// </summary>
private bool AreAllReferenceSpansMappable()
{
// in tests `ActiveTextview` could be null so don't depend on it
return ActiveTextview == null ||
_referenceSpanToLinkedRenameSpanMap.Keys
.Select(s => s.ToSpan())
.All(s =>
s.End <= _subjectBuffer.CurrentSnapshot.Length && // span is valid for the snapshot
ActiveTextview.GetSpanInView(_subjectBuffer.CurrentSnapshot.GetSpan(s)).Count != 0); // spans were successfully projected
}
internal void ApplyReplacementText(bool updateSelection = true)
{
AssertIsForeground();
if (!AreAllReferenceSpansMappable())
{
// don't dynamically update the reference spans for documents with unmappable projections
return;
}
_session.UndoManager.ApplyCurrentState(
_subjectBuffer,
s_propagateSpansEditTag,
......@@ -287,6 +312,12 @@ internal void ApplyConflictResolutionEdits(IInlineRenameReplacementInfo conflict
{
AssertIsForeground();
if (!AreAllReferenceSpansMappable())
{
// don't dynamically update the reference spans for documents with unmappable projections
return;
}
using (new SelectionTracking(this))
{
// 1. Undo any previous edits and update the buffer to resulting document after conflict resolution
......@@ -553,8 +584,10 @@ public SelectionTracking(OpenTextBufferManager openTextBufferManager)
var containingSpans = openTextBufferManager._referenceSpanToLinkedRenameSpanMap.Select(kvp =>
{
var ss = textView.GetSpanInView(kvp.Value.TrackingSpan.GetSpan(snapshot)).Single();
if (ss.IntersectsWith(selection.ActivePoint.Position) || ss.IntersectsWith(selection.AnchorPoint.Position))
// GetSpanInView() can return an empty collection if the tracking span isn't mapped to anything
// in the current view, specifically a `@model SomeModelClass` directive in a Razor file.
var ss = textView.GetSpanInView(kvp.Value.TrackingSpan.GetSpan(snapshot)).FirstOrDefault();
if (ss != null && (ss.IntersectsWith(selection.ActivePoint.Position) || ss.IntersectsWith(selection.AnchorPoint.Position)))
{
return Tuple.Create(kvp.Key, ss);
}
......
// 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.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics
{
public class DiagnosticAnalyzerServiceTests
{
[Fact]
public async Task TestHasSuccessfullyLoadedBeingFalse()
{
var workspace = new AdhocWorkspace();
var document = GetDocumentFromIncompleteProject(workspace);
// create listener/service/analyzer
var listener = new AsynchronousOperationListener();
var service = new MyDiagnosticAnalyzerService(new Analyzer(), listener);
var analyzer = service.CreateIncrementalAnalyzer(workspace);
// listen to events
// check empty since this could be called to clear up existing diagnostics
service.DiagnosticsUpdated += (s, a) => Assert.Empty(a.Diagnostics);
// now call each analyze method. none of them should run.
await analyzer.AnalyzeSyntaxAsync(document, CancellationToken.None).ConfigureAwait(false);
await analyzer.AnalyzeDocumentAsync(document, bodyOpt: null, cancellationToken: CancellationToken.None).ConfigureAwait(false);
await analyzer.AnalyzeProjectAsync(document.Project, semanticsChanged: true, cancellationToken: CancellationToken.None).ConfigureAwait(false);
// wait for all events to raised
await listener.CreateWaitTask().ConfigureAwait(false);
}
[Fact]
public async Task TestHasSuccessfullyLoadedBeingFalseWhenFileOpened()
{
var workspace = new AdhocWorkspace();
var document = GetDocumentFromIncompleteProject(workspace);
// open document
workspace.OpenDocument(document.Id);
// create listener/service/analyzer
var listener = new AsynchronousOperationListener();
var service = new MyDiagnosticAnalyzerService(new Analyzer(), listener);
var analyzer = service.CreateIncrementalAnalyzer(workspace);
bool syntax = false;
bool semantic = false;
// listen to events
service.DiagnosticsUpdated += (s, a) =>
{
switch (a.Diagnostics.Length)
{
case 0:
return;
case 1:
syntax |= a.Diagnostics[0].Id == Analyzer.s_syntaxRule.Id;
semantic |= a.Diagnostics[0].Id == Analyzer.s_semanticRule.Id;
return;
default:
AssertEx.Fail("shouldn't reach here");
return;
}
};
// now call each analyze method. none of them should run.
await analyzer.AnalyzeSyntaxAsync(document, CancellationToken.None).ConfigureAwait(false);
await analyzer.AnalyzeDocumentAsync(document, bodyOpt: null, cancellationToken: CancellationToken.None).ConfigureAwait(false);
await analyzer.AnalyzeProjectAsync(document.Project, semanticsChanged: true, cancellationToken: CancellationToken.None).ConfigureAwait(false);
// wait for all events to raised
await listener.CreateWaitTask().ConfigureAwait(false);
// two should have been called.
Assert.True(syntax);
Assert.True(semantic);
}
private static Document GetDocumentFromIncompleteProject(AdhocWorkspace workspace)
{
var project = workspace.AddProject(
ProjectInfo.Create(
ProjectId.CreateNewId(),
VersionStamp.Create(),
"CSharpProject",
"CSharpProject",
LanguageNames.CSharp).WithHasAllInformation(hasAllInformation: false));
return workspace.AddDocument(project.Id, "Empty.cs", SourceText.From(""));
}
private class MyDiagnosticAnalyzerService : DiagnosticAnalyzerService
{
internal MyDiagnosticAnalyzerService(DiagnosticAnalyzer analyzer, IAsynchronousOperationListener listener)
: base(new HostAnalyzerManager(
ImmutableArray.Create<AnalyzerReference>(
new TestAnalyzerReferenceByLanguage(
ImmutableDictionary<string, ImmutableArray<DiagnosticAnalyzer>>.Empty.Add(LanguageNames.CSharp, ImmutableArray.Create(analyzer)))),
hostDiagnosticUpdateSource: null),
hostDiagnosticUpdateSource: null,
registrationService: new MockDiagnosticUpdateSourceRegistrationService(),
listener: listener)
{
}
}
private class Analyzer : DiagnosticAnalyzer
{
internal static readonly DiagnosticDescriptor s_syntaxRule = new DiagnosticDescriptor("syntax", "test", "test", "test", DiagnosticSeverity.Error, isEnabledByDefault: true);
internal static readonly DiagnosticDescriptor s_semanticRule = new DiagnosticDescriptor("semantic", "test", "test", "test", DiagnosticSeverity.Error, isEnabledByDefault: true);
internal static readonly DiagnosticDescriptor s_compilationRule = new DiagnosticDescriptor("compilation", "test", "test", "test", DiagnosticSeverity.Error, isEnabledByDefault: true);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(s_syntaxRule, s_semanticRule, s_compilationRule);
public override void Initialize(AnalysisContext context)
{
context.RegisterSyntaxTreeAction(c => c.ReportDiagnostic(Diagnostic.Create(s_syntaxRule, c.Tree.GetRoot().GetLocation())));
context.RegisterSemanticModelAction(c => c.ReportDiagnostic(Diagnostic.Create(s_semanticRule, c.SemanticModel.SyntaxTree.GetRoot().GetLocation())));
context.RegisterCompilationAction(c => c.ReportDiagnostic(Diagnostic.Create(s_compilationRule, c.Compilation.SyntaxTrees.First().GetRoot().GetLocation())));
}
}
}
}
......@@ -75,7 +75,7 @@ internal class DiagnosticTaggerWrapper : IDisposable
if (analyzerMap != null || updateSource == null)
{
AnalyzerService = CreateDiagnosticAnalyzerService(analyzerMap, new AggregateAsynchronousOperationListener(_listeners, FeatureAttribute.DiagnosticService));
AnalyzerService = CreateDiagnosticAnalyzerService(analyzerMap, _asyncListener);
}
if (updateSource == null)
......
......@@ -229,6 +229,7 @@
<Compile Include="Diagnostics\AbstractSuppressionDiagnosticTest.cs" />
<Compile Include="Diagnostics\AbstractDiagnosticProviderBasedUserDiagnosticTest.cs" />
<Compile Include="Diagnostics\AbstractUserDiagnosticTest.cs" />
<Compile Include="Diagnostics\DiagnosticAnalyzerServiceTests.cs" />
<Compile Include="Diagnostics\MockDiagnosticUpdateSourceRegistrationService.cs" />
<Compile Include="Diagnostics\TestAnalyzerReferenceByLanguage.cs" />
<Compile Include="Diagnostics\TestDiagnosticAnalyzerDriver.cs" />
......
......@@ -382,7 +382,11 @@ private static ParseOptions GetParseOptionsWithFeatures(ParseOptions parseOption
var features = entries.Select(x =>
{
var split = x.Split('=');
return new KeyValuePair<string, string>(split[0], split[1]);
var key = split[0];
var value = split.Length == 2 ? split[1] : "true";
return new KeyValuePair<string, string>(key, value);
});
return parseOptions.WithFeatures(features);
......
......@@ -809,6 +809,38 @@ class AnonymousFunctions
End Using
End Function
<WpfFact, WorkItem(9462, "https://github.com/dotnet/roslyn/issues/9462"), Trait(Traits.Feature, Traits.Features.Diagnostics)>
Public Sub TestMultiplePartialDefinitionsInAFile()
Dim test = <Workspace>
<Project Language="C#" CommonReferences="true">
<Document FilePath="Test.cs">
partial class Foo { }
partial class Foo { }
</Document>
</Project>
</Workspace>
Using workspace = TestWorkspace.CreateWorkspace(test)
Dim project = workspace.CurrentSolution.Projects.Single()
Dim analyzer = New NamedTypeAnalyzer
Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer))
project = project.AddAnalyzerReference(analyzerReference)
Dim diagnosticService = New TestDiagnosticAnalyzerService()
Dim incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace)
Dim descriptorsMap = diagnosticService.GetDiagnosticDescriptors(project)
Assert.Equal(1, descriptorsMap.Count)
' Verify no duplicate analysis/diagnostics.
Dim document = project.Documents.Single()
Dim diagnostics = diagnosticService.GetDiagnosticsAsync(project.Solution, project.Id) _
.WaitAndGetResult(CancellationToken.None) _
.Select(Function(d) d.Id = NamedTypeAnalyzer.DiagDescriptor.Id)
Assert.Equal(1, diagnostics.Count)
End Using
End Sub
<WpfFact, WorkItem(1042914, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/1042914"), Trait(Traits.Feature, Traits.Features.Diagnostics)>
Public Sub TestPartialTypeInGeneratedCode()
Dim test = <Workspace>
......@@ -1354,6 +1386,29 @@ public class B
End Sub
End Class
Private Class NamedTypeAnalyzer
Inherits DiagnosticAnalyzer
Public Shared ReadOnly DiagDescriptor As DiagnosticDescriptor = DescriptorFactory.CreateSimpleDescriptor("DummyDiagnostic")
Public Overrides ReadOnly Property SupportedDiagnostics As ImmutableArray(Of DiagnosticDescriptor)
Get
Return ImmutableArray.Create(DiagDescriptor)
End Get
End Property
Public Overrides Sub Initialize(context As AnalysisContext)
context.RegisterCompilationStartAction(Sub(compStartContext As CompilationStartAnalysisContext)
Dim symbols = New HashSet(Of ISymbol)
compStartContext.RegisterSymbolAction(Sub(sc As SymbolAnalysisContext)
If (symbols.Contains(sc.Symbol)) Then
Throw New Exception("Duplicate symbol callback")
End If
sc.ReportDiagnostic(Diagnostic.Create(DiagDescriptor, sc.Symbol.Locations.First()))
End Sub, SymbolKind.NamedType)
End Sub)
End Sub
End Class
Private Class DummySymbolAnalyzer
Inherits DiagnosticAnalyzer
......
......@@ -111,7 +111,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Rename
' Ensure we don't have a partial solution. This is to detect for possible root causes of
' https://github.com/dotnet/roslyn/issues/9298
If currentSolution.Projects.Any(Function(p) Not p.HasCompleteReferencesAsync().Result) Then
If currentSolution.Projects.Any(Function(p) Not p.HasSuccessfullyLoadedAsync().Result) Then
AssertEx.Fail("We have an incomplete project floating around which we should not.")
End If
End Sub
......
......@@ -8,6 +8,7 @@
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.Text.Shared.Extensions;
......@@ -88,7 +89,27 @@ private static SnapshotSourceText CreateText(ITextSnapshot editorSnapshot)
var text = strongBox.Value;
if (text != null && text._reiteratedVersion == editorSnapshot.Version.ReiteratedVersionNumber)
{
return text;
if (text.Length == editorSnapshot.Length)
{
return text;
}
else
{
// In editors with non-compliant Undo/Redo implementations, you can end up
// with two Versions with the same ReiteratedVersionNumber but with very
// different text. We've never provably seen this problem occur in Visual
// Studio, but we have seen crashes that look like they could have been
// caused by incorrect results being returned from this cache.
try
{
throw new InvalidOperationException(
$"The matching cached SnapshotSourceText with <Reiterated Version, Length> = <{text._reiteratedVersion}, {text.Length}> " +
$"does not match the given editorSnapshot with <{editorSnapshot.Version.ReiteratedVersionNumber}, {editorSnapshot.Length}>");
}
catch (Exception e) when (FatalError.ReportWithoutCrash(e))
{
}
}
}
text = new SnapshotSourceText(editorSnapshot, editorSnapshot.TextBuffer.GetEncodingOrUTF8());
......
......@@ -12,6 +12,7 @@ internal static class IDEDiagnosticIds
public const string AddQualificationDiagnosticId = "IDE0006";
public const string UseImplicitTypingDiagnosticId = "IDE0007";
public const string UseExplicitTypingDiagnosticId = "IDE0008";
public const string IntellisenseBuildFailedDiagnosticId = "IDE0006";
// Analyzer error Ids
public const string AnalyzerChangedId = "IDE1001";
......
......@@ -9,6 +9,7 @@
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.SolutionCrawler;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Diagnostics
{
......@@ -16,6 +17,8 @@ internal abstract class BaseDiagnosticIncrementalAnalyzer : IIncrementalAnalyzer
{
protected BaseDiagnosticIncrementalAnalyzer(DiagnosticAnalyzerService owner, Workspace workspace, HostAnalyzerManager hostAnalyzerManager, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource)
{
Contract.ThrowIfNull(owner);
this.Owner = owner;
this.Workspace = workspace;
this.HostAnalyzerManager = hostAnalyzerManager;
......@@ -27,7 +30,7 @@ protected BaseDiagnosticIncrementalAnalyzer(DiagnosticAnalyzerService owner, Wor
/// <summary>
/// Analyze a single document such that local diagnostics for that document become available,
/// prioritizing analyzing this document over analyzing the rest of the project.
/// Calls <see cref="DiagnosticAnalyzerService.RaiseDiagnosticsUpdated(object, DiagnosticsUpdatedArgs)"/> for each
/// Calls <see cref="DiagnosticAnalyzerService.RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs)"/> for each
/// unique group of diagnostics, where a group is identified by analysis classification (syntax/semantics), document, and analyzer.
/// </summary>
/// <param name="document">The document to analyze.</param>
......@@ -37,7 +40,7 @@ protected BaseDiagnosticIncrementalAnalyzer(DiagnosticAnalyzerService owner, Wor
public abstract Task AnalyzeDocumentAsync(Document document, SyntaxNode bodyOpt, CancellationToken cancellationToken);
/// <summary>
/// Analyze a single project such that diagnostics for the entire project become available.
/// Calls <see cref="DiagnosticAnalyzerService.RaiseDiagnosticsUpdated(object, DiagnosticsUpdatedArgs)"/> for each
/// Calls <see cref="DiagnosticAnalyzerService.RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs)"/> for each
/// unique group of diagnostics, where a group is identified by analysis classification (project), project, and analyzer.
/// </summary>
/// <param name="project">The project to analyze.</param>
......@@ -47,7 +50,7 @@ protected BaseDiagnosticIncrementalAnalyzer(DiagnosticAnalyzerService owner, Wor
public abstract Task AnalyzeProjectAsync(Project project, bool semanticsChanged, CancellationToken cancellationToken);
/// <summary>
/// Apply syntax tree actions (that have not already been applied) to a document.
/// Calls <see cref="DiagnosticAnalyzerService.RaiseDiagnosticsUpdated(object, DiagnosticsUpdatedArgs)"/> for each
/// Calls <see cref="DiagnosticAnalyzerService.RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs)"/> for each
/// unique group of diagnostics, where a group is identified by analysis classification (syntax), document, and analyzer.
/// </summary>
/// <param name="document">The document to analyze.</param>
......@@ -87,14 +90,14 @@ protected BaseDiagnosticIncrementalAnalyzer(DiagnosticAnalyzerService owner, Wor
/// <summary>
/// Flush diagnostics produced by a prior analysis of a document,
/// and suppress future analysis of the document.
/// Calls <see cref="DiagnosticAnalyzerService.RaiseDiagnosticsUpdated(object, DiagnosticsUpdatedArgs)"/> with an empty collection.
/// Calls <see cref="DiagnosticAnalyzerService.RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs)"/> with an empty collection.
/// </summary>
/// <param name="documentId"></param>
public abstract void RemoveDocument(DocumentId documentId);
/// <summary>
/// Flush diagnostics produced by a prior analysis of a project,
/// and suppress future analysis of the project.
/// Calls <see cref="DiagnosticAnalyzerService.RaiseDiagnosticsUpdated(object, DiagnosticsUpdatedArgs)"/> with an empty collection.
/// Calls <see cref="DiagnosticAnalyzerService.RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs)"/> with an empty collection.
/// </summary>
/// <param name="projectId"></param>
public abstract void RemoveProject(ProjectId projectId);
......@@ -106,7 +109,7 @@ protected BaseDiagnosticIncrementalAnalyzer(DiagnosticAnalyzerService owner, Wor
/// analysis classification (syntax/semantics/project), document/project, and analyzer.
/// </summary>
/// <param name="solution">The solution.</param>
/// <param name="id">Matched against values supplied in a <see cref="DiagnosticsUpdatedArgs"/> to <see cref="DiagnosticAnalyzerService.RaiseDiagnosticsUpdated(object, DiagnosticsUpdatedArgs)"/>.</param>
/// <param name="id">Matched against values supplied in a <see cref="DiagnosticsUpdatedArgs"/> to <see cref="DiagnosticAnalyzerService.RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs)"/>.</param>
/// <param name="includeSuppressedDiagnostics">Flag indicating whether diagnostics with source suppressions (pragma/SuppressMessageAttribute) should be included.</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
......@@ -126,7 +129,7 @@ protected BaseDiagnosticIncrementalAnalyzer(DiagnosticAnalyzerService owner, Wor
/// analysis classification (syntax/semantics/project), document/project, and analyzer.
/// </summary>
/// <param name="solution">The solution.</param>
/// <param name="id">Matched against values supplied in a <see cref="DiagnosticsUpdatedArgs"/> to <see cref="DiagnosticAnalyzerService.RaiseDiagnosticsUpdated(object, DiagnosticsUpdatedArgs)"/>.</param>
/// <param name="id">Matched against values supplied in a <see cref="DiagnosticsUpdatedArgs"/> to <see cref="DiagnosticAnalyzerService.RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs)"/>.</param>
/// <param name="includeSuppressedDiagnostics">Flag indicating whether diagnostics with source suppressions (pragma/SuppressMessageAttribute) should be included.</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
......@@ -237,7 +240,7 @@ public virtual void LogAnalyzerCountSummary()
// internal for testing purposes.
internal Action<Exception, DiagnosticAnalyzer, Diagnostic> GetOnAnalyzerException(ProjectId projectId)
{
return Owner?.GetOnAnalyzerException(projectId, DiagnosticLogAggregator);
return Owner.GetOnAnalyzerException(projectId, DiagnosticLogAggregator);
}
}
}
......@@ -42,17 +42,30 @@ private DiagnosticAnalyzerService(IDiagnosticUpdateSourceRegistrationService reg
}
}
internal void RaiseDiagnosticsUpdated(object sender, DiagnosticsUpdatedArgs args)
internal void RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs args)
{
// raise serialized events
// all diagnostics events are serialized.
var ev = _eventMap.GetEventHandlers<EventHandler<DiagnosticsUpdatedArgs>>(DiagnosticsUpdatedEventName);
if (ev.HasHandlers)
{
var asyncToken = Listener.BeginAsyncOperation(nameof(RaiseDiagnosticsUpdated));
_eventQueue.ScheduleTask(() =>
{
ev.RaiseEvent(handler => handler(sender, args));
}).CompletesAsyncOperation(asyncToken);
_eventQueue.ScheduleTask(() => ev.RaiseEvent(handler => handler(this, args))).CompletesAsyncOperation(asyncToken);
}
}
internal void RaiseBulkDiagnosticsUpdated(Action<Action<DiagnosticsUpdatedArgs>> eventAction)
{
// all diagnostics events are serialized.
var ev = _eventMap.GetEventHandlers<EventHandler<DiagnosticsUpdatedArgs>>(DiagnosticsUpdatedEventName);
if (ev.HasHandlers)
{
// we do this bulk update to reduce number of tasks (with captured data) enqueued.
// we saw some "out of memory" due to us having long list of pending tasks in memory.
// this is to reduce for such case to happen.
Action<DiagnosticsUpdatedArgs> raiseEvents = args => ev.RaiseEvent(handler => handler(this, args));
var asyncToken = Listener.BeginAsyncOperation(nameof(RaiseDiagnosticsUpdated));
_eventQueue.ScheduleTask(() => eventAction(raiseEvents)).CompletesAsyncOperation(asyncToken);
}
}
......
......@@ -57,49 +57,76 @@ public DiagnosticService([ImportMany] IEnumerable<Lazy<IAsynchronousOperationLis
private void RaiseDiagnosticsUpdated(object sender, DiagnosticsUpdatedArgs args)
{
Contract.ThrowIfNull(sender);
var source = (IDiagnosticUpdateSource)sender;
var ev = _eventMap.GetEventHandlers<EventHandler<DiagnosticsUpdatedArgs>>(DiagnosticsUpdatedEventName);
if (ev.HasHandlers)
if (!RequireRunningEventTasks(source, ev))
{
var eventToken = _listener.BeginAsyncOperation(DiagnosticsUpdatedEventName);
_eventQueue.ScheduleTask(() =>
{
UpdateDataMap(sender, args);
ev.RaiseEvent(handler => handler(sender, args));
}).CompletesAsyncOperation(eventToken);
return;
}
var eventToken = _listener.BeginAsyncOperation(DiagnosticsUpdatedEventName);
_eventQueue.ScheduleTask(() =>
{
if (!UpdateDataMap(source, args))
{
// there is no change, nothing to raise events for.
return;
}
ev.RaiseEvent(handler => handler(sender, args));
}).CompletesAsyncOperation(eventToken);
}
private void UpdateDataMap(object sender, DiagnosticsUpdatedArgs args)
private bool RequireRunningEventTasks(
IDiagnosticUpdateSource source, EventMap.EventHandlerSet<EventHandler<DiagnosticsUpdatedArgs>> ev)
{
var updateSource = sender as IDiagnosticUpdateSource;
if (updateSource == null)
// basically there are 2 cases when there is no event handler registered.
// first case is when diagnostic update source itself provide GetDiagnostics functionality.
// in that case, DiagnosticService doesn't need to track diagnostics reported. so, it bail out right away.
// second case is when diagnostic source doesn't provide GetDiagnostics functionality.
// in that case, DiagnosticService needs to track diagnostics reported. so it need to enqueue background
// work to process given data regardless whether there is event handler registered or not.
// this could be separated in 2 tasks, but we already saw cases where there are too many tasks enqueued,
// so I merged it to one.
// if it doesn't SupportGetDiagnostics, we need to process reported data, so enqueue task.
if (!source.SupportGetDiagnostics)
{
return;
return true;
}
Contract.Requires(_updateSources.Contains(updateSource));
return ev.HasHandlers;
}
// we expect someone who uses this ability to small.
private bool UpdateDataMap(IDiagnosticUpdateSource source, DiagnosticsUpdatedArgs args)
{
// we expect source who uses this ability to have small number of diagnostics.
lock (_gate)
{
Contract.Requires(_updateSources.Contains(source));
// check cheap early bail out
if (args.Diagnostics.Length == 0 && !_map.ContainsKey(updateSource))
if (args.Diagnostics.Length == 0 && !_map.ContainsKey(source))
{
// no new diagnostic, and we don't have update source for it.
return;
return false;
}
var list = _map.GetOrAdd(updateSource, _ => new Dictionary<object, Data>());
var data = updateSource.SupportGetDiagnostics ? new Data(args) : new Data(args, args.Diagnostics);
var diagnosticDataMap = _map.GetOrAdd(source, _ => new Dictionary<object, Data>());
list.Remove(data.Id);
if (list.Count == 0 && args.Diagnostics.Length == 0)
diagnosticDataMap.Remove(args.Id);
if (diagnosticDataMap.Count == 0 && args.Diagnostics.Length == 0)
{
_map.Remove(updateSource);
return;
_map.Remove(source);
return true;
}
list.Add(args.Id, data);
var data = source.SupportGetDiagnostics ? new Data(args) : new Data(args, args.Diagnostics);
diagnosticDataMap.Add(args.Id, data);
return true;
}
}
......
......@@ -39,10 +39,18 @@ public DiagnosticState GetState(StateType stateType)
return _state[(int)stateType];
}
public void Remove(object documentOrProjectId)
public void Remove(object documentOrProjectId, bool onlyDocumentStates = false)
{
// this is to clear up states. some caller such as re-analyzing a file wants to
// reset only document related states, some like removing a file wants to clear up
// states all together.
for (var stateType = 0; stateType < s_stateTypeCount; stateType++)
{
if (onlyDocumentStates && stateType == (int)StateType.Project)
{
continue;
}
_state[stateType].Remove(documentOrProjectId);
}
}
......
......@@ -61,13 +61,13 @@ public override Task NewSolutionSnapshotAsync(Solution solution, CancellationTok
public override void RemoveDocument(DocumentId documentId)
{
Owner.RaiseDiagnosticsUpdated(this, DiagnosticsUpdatedArgs.DiagnosticsRemoved(
Owner.RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs.DiagnosticsRemoved(
ValueTuple.Create(this, documentId), Workspace, null, null, null));
}
public override void RemoveProject(ProjectId projectId)
{
Owner.RaiseDiagnosticsUpdated(this, DiagnosticsUpdatedArgs.DiagnosticsRemoved(
Owner.RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs.DiagnosticsRemoved(
ValueTuple.Create(this, projectId), Workspace, null, null, null));
}
#endregion
......@@ -213,14 +213,12 @@ private void RaiseEvents(Project project, ImmutableArray<DiagnosticData> diagnos
{
if (kv.Key == null)
{
Owner.RaiseDiagnosticsUpdated(
this, DiagnosticsUpdatedArgs.DiagnosticsCreated(
Owner.RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs.DiagnosticsCreated(
ValueTuple.Create(this, project.Id), workspace, solution, project.Id, null, kv.ToImmutableArrayOrEmpty()));
continue;
}
Owner.RaiseDiagnosticsUpdated(
this, DiagnosticsUpdatedArgs.DiagnosticsCreated(
Owner.RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs.DiagnosticsCreated(
ValueTuple.Create(this, kv.Key), workspace, solution, project.Id, kv.Key, kv.ToImmutableArrayOrEmpty()));
}
}
......
......@@ -122,11 +122,7 @@ public int OnImportAddedEx(string filename, string project, CompilerOptions opti
bool embedInteropTypes = optionID == CompilerOptions.OPTID_IMPORTSUSINGNOPIA;
var properties = new MetadataReferenceProperties(embedInteropTypes: embedInteropTypes);
// The file may not exist, so let's return an error code. In Dev10, we were
// never called us for these at all, and the project system would immediately
// return an S_FALSE. Sadly, returning S_FALSE will convert back to S_OK, which
// would prevent this file from ever being added again. Roslyn bug 7315 for more.
return AddMetadataReferenceAndTryConvertingToProjectReferenceIfPossible(filename, properties, hResultForMissingFile: VSConstants.E_FAIL);
return AddMetadataReferenceAndTryConvertingToProjectReferenceIfPossible(filename, properties);
}
public void OnImportRemoved(string filename, string project)
......
......@@ -303,7 +303,7 @@ public ProjectInfo CreateProjectInfoForCurrentState()
{
ValidateReferences();
return ProjectInfo.Create(
var info = ProjectInfo.Create(
this.Id,
this.Version,
this.DisplayName,
......@@ -318,6 +318,8 @@ public ProjectInfo CreateProjectInfoForCurrentState()
projectReferences: _projectReferences,
analyzerReferences: _analyzers.Values.Select(a => a.GetReference()),
additionalDocuments: _additionalDocuments.Values.Select(d => d.GetInitialState()));
return info.WithHasAllInformation(_intellisenseBuildSucceeded);
}
protected ImmutableArray<string> GetStrongNameKeyPaths()
......@@ -347,7 +349,7 @@ public ImmutableArray<ProjectReference> GetCurrentProjectReferences()
{
return ImmutableArray.CreateRange(_projectReferences);
}
public ImmutableArray<VisualStudioMetadataReference> GetCurrentMetadataReferences()
{
return ImmutableArray.CreateRange(_metadataReferences);
......@@ -435,7 +437,7 @@ protected void SetOptions(CompilationOptions compilationOptions, ParseOptions pa
}
}
protected int AddMetadataReferenceAndTryConvertingToProjectReferenceIfPossible(string filePath, MetadataReferenceProperties properties, int hResultForMissingFile)
protected int AddMetadataReferenceAndTryConvertingToProjectReferenceIfPossible(string filePath, MetadataReferenceProperties properties)
{
// If this file is coming from a project, then we should convert it to a project reference instead
AbstractProject project;
......@@ -450,13 +452,50 @@ protected int AddMetadataReferenceAndTryConvertingToProjectReferenceIfPossible(s
}
}
if (!File.Exists(filePath))
{
return hResultForMissingFile;
}
// regardless whether the file exists or not, we still record it. one of reason
// we do that is some cross language p2p references might be resolved
// after they are already reported as metadata references. since we use bin path
// as a way to discover them, if we don't previously record the reference ourselves,
// cross p2p references won't be resolved as p2p references when we finally have
// all required information.
//
// it looks like
// 1. project system sometimes won't guarantee build dependency for intellisense build
// if it is cross language dependency
// 2. output path of referenced cross language project might be changed to right one
// once it is already added as a metadata reference.
//
// but this has one consequence. even if a user adds a project in the solution as
// a metadata reference explicitly, that dll will be automatically converted back to p2p
// reference.
//
// unfortunately there is no way to prevent this using information we have since,
// at this point, we don't know whether it is a metadata reference added because
// we don't have enough information yet for p2p reference or user explicitly added it
// as a metadata reference.
AddMetadataReferenceCore(this.MetadataReferenceProvider.CreateMetadataReference(this, filePath, properties));
// here, we change behavior compared to old C# language service. regardless of file being exist or not,
// we will always return S_OK. this is to support cross language p2p reference better.
//
// this should make project system to cache all cross language p2p references regardless
// whether it actually exist in disk or not.
// (see Roslyn bug 7315 for history - http://vstfdevdiv:8080/DevDiv_Projects/Roslyn/_workitems?_a=edit&id=7315)
//
// after this point, Roslyn will take care of non-exist metadata reference.
//
// But, this doesn't sovle the issue where actual metadata reference
// (not cross language p2p reference) is missing at the time project is opened.
//
// in that case, msbuild filter those actual metadata references out, so project system doesn't know
// path to the reference. since it doesn't know where dll is, it can't (or currently doesn't)
// setup file change notification either to find out when dll becomes available.
//
// at this point, user has 2 ways to recover missing metadata reference once it becomes available.
//
// one way is explicitly clicking that missing reference from solution explorer reference node.
// the other is building the project. at that point, project system will refresh references
// which will discover new dll and connect to us. once it is connected, we will take care of it.
return VSConstants.S_OK;
}
......
// 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.IO;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.Interop;
using Roslyn.Utilities;
namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem
{
internal partial class AbstractProject : IIntellisenseBuildTarget
{
private static readonly object s_diagnosticKey = new object();
// set default to true so that we maintain old behavior when project system doesn't
// implement IIntellisenseBuildTarget
private bool _intellisenseBuildSucceeded = true;
private string _intellisenseBuildFailureReason = null;
public void SetIntellisenseBuildResult(bool succeeded, string reason)
{
// set intellisense related info
_intellisenseBuildSucceeded = succeeded;
_intellisenseBuildFailureReason = string.IsNullOrWhiteSpace(reason) ? null : reason.Trim();
UpdateHostDiagnostics(succeeded, reason);
if (_pushingChangesToWorkspaceHosts)
{
// set workspace reference info
ProjectTracker.NotifyWorkspaceHosts(host => (host as IVisualStudioWorkspaceHost2)?.OnHasAllInformation(Id, succeeded));
}
}
private void UpdateHostDiagnostics(bool succeeded, string reason)
{
if (!succeeded)
{
// report intellisense build failure to error list
this.HostDiagnosticUpdateSource?.UpdateDiagnosticsForProject(
_id, s_diagnosticKey, SpecializedCollections.SingletonEnumerable(CreateIntellisenseBuildFailureDiagnostic(reason)));
}
else
{
// clear intellisense build failure diagnostic from error list.
this.HostDiagnosticUpdateSource?.ClearDiagnosticsForProject(_id, s_diagnosticKey);
}
}
private DiagnosticData CreateIntellisenseBuildFailureDiagnostic(string reason)
{
// log intellisense build failure
Logger.Log(FunctionId.IntellisenseBuild_Failed, KeyValueLogMessage.Create(m => m["Reason"] = reason ?? string.Empty));
return new DiagnosticData(
IDEDiagnosticIds.IntellisenseBuildFailedDiagnosticId,
FeaturesResources.ErrorCategory,
ServicesVSResources.IntellisenseBuildFailedMessage,
ServicesVSResources.ResourceManager.GetString(nameof(ServicesVSResources.IntellisenseBuildFailedMessage), CodeAnalysis.Diagnostics.Extensions.s_USCultureInfo),
DiagnosticSeverity.Warning,
isEnabledByDefault: true,
warningLevel: 0,
workspace: Workspace,
projectId: Id,
title: ServicesVSResources.IntellisenseBuildFailedTitle,
description: GetDescription(reason),
helpLink: "http://go.microsoft.com/fwlink/p/?LinkID=734719");
}
private string GetDescription(string reason)
{
var logFilePath = $"{Path.GetTempPath()}\\{Path.GetFileNameWithoutExtension(this._filePathOpt)}_*.designtime.log";
var logFileDescription = string.Format(ServicesVSResources.IntellisenseBuildFailedDescription, logFilePath);
if (string.IsNullOrWhiteSpace(reason))
{
return logFileDescription;
}
return string.Join(Environment.NewLine, logFileDescription, string.Empty, ServicesVSResources.IntellisenseBuildFailedDescriptionExtra, reason);
}
}
}
// 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.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell.Interop;
using Roslyn.Utilities;
......@@ -15,6 +11,8 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem
{
internal sealed class FileChangeTracker : IVsFileChangeEvents, IDisposable
{
private const uint FileChangeFlags = (uint)(_VSFILECHANGEFLAGS.VSFILECHG_Time | _VSFILECHANGEFLAGS.VSFILECHG_Add | _VSFILECHANGEFLAGS.VSFILECHG_Del | _VSFILECHANGEFLAGS.VSFILECHG_Size);
private static readonly Lazy<uint> s_none = new Lazy<uint>(() => /* value doesn't matter*/ 42424242, LazyThreadSafetyMode.ExecutionAndPublication);
private readonly IVsFileChangeEx _fileChangeService;
......@@ -74,7 +72,7 @@ public void StartFileChangeListeningAsync()
{
uint newCookie;
Marshal.ThrowExceptionForHR(
_fileChangeService.AdviseFileChange(_filePath, (uint)_VSFILECHANGEFLAGS.VSFILECHG_Time, this, out newCookie));
_fileChangeService.AdviseFileChange(_filePath, FileChangeFlags, this, out newCookie));
return newCookie;
}, LazyThreadSafetyMode.ExecutionAndPublication);
......
// 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 Microsoft.CodeAnalysis;
namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem
{
// TODO: Find a better name for this
internal interface IVisualStudioWorkspaceHost2
{
void OnHasAllInformation(ProjectId projectId, bool hasAllInformation);
}
}
// 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.Runtime.InteropServices;
namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.Interop
{
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("F304ABA7-2448-4EE9-A706-D1838F200398")]
internal interface IIntellisenseBuildTarget
{
// currently, reason is not being used.
void SetIntellisenseBuildResult(bool succeeded, [MarshalAs(UnmanagedType.LPWStr)] string reason);
}
}
......@@ -46,7 +46,7 @@ public HostProject(Workspace workspace, SolutionId solutionId, string languageNa
public ProjectInfo CreateProjectInfoForCurrentState()
{
return ProjectInfo.Create(
var info = ProjectInfo.Create(
this.Id,
_version,
name: ServicesVSResources.MiscellaneousFiles,
......@@ -60,6 +60,10 @@ public ProjectInfo CreateProjectInfoForCurrentState()
metadataReferences: _metadataReferences,
isSubmission: false,
hostObjectType: null);
// misc project will never be fully loaded since, by defintion, it won't know
// what the full set of information is.
return info.WithHasAllInformation(hasAllInformation: false);
}
public Microsoft.VisualStudio.Shell.Interop.IVsHierarchy Hierarchy => null;
......
......@@ -14,16 +14,7 @@ void IVsSolutionWorkingFoldersEvents.OnAfterLocationChange(uint location, bool c
}
// notify the working folder change
NotifyWorkspaceHosts(host =>
{
var workingFolder = host as IVisualStudioWorkingFolder;
if (workingFolder == null)
{
return;
}
workingFolder.OnAfterWorkingFolderChange();
});
NotifyWorkspaceHosts(host => (host as IVisualStudioWorkingFolder)?.OnAfterWorkingFolderChange());
}
void IVsSolutionWorkingFoldersEvents.OnQueryLocationChange(uint location, out bool pfCanMoveContent)
......@@ -36,16 +27,7 @@ void IVsSolutionWorkingFoldersEvents.OnQueryLocationChange(uint location, out bo
// notify the working folder change
pfCanMoveContent = true;
NotifyWorkspaceHosts(host =>
{
var workingFolder = host as IVisualStudioWorkingFolder;
if (workingFolder == null)
{
return;
}
workingFolder.OnBeforeWorkingFolderChange();
});
NotifyWorkspaceHosts(host => (host as IVisualStudioWorkingFolder)?.OnBeforeWorkingFolderChange());
}
}
}
......@@ -1164,7 +1164,7 @@ internal override bool CanAddProjectReference(ProjectId referencingProject, Proj
/// A trivial implementation of <see cref="IVisualStudioWorkspaceHost" /> that just
/// forwards the calls down to the underlying Workspace.
/// </summary>
protected class VisualStudioWorkspaceHost : IVisualStudioWorkspaceHost, IVisualStudioWorkingFolder
protected sealed class VisualStudioWorkspaceHost : IVisualStudioWorkspaceHost, IVisualStudioWorkspaceHost2, IVisualStudioWorkingFolder
{
private readonly VisualStudioWorkspaceImpl _workspace;
......@@ -1400,6 +1400,11 @@ void IVisualStudioWorkspaceHost.OnAdditionalDocumentTextUpdatedOnDisk(DocumentId
_workspace.OnAdditionalDocumentTextUpdatedOnDisk(id);
}
void IVisualStudioWorkspaceHost2.OnHasAllInformation(ProjectId projectId, bool hasAllInformation)
{
_workspace.OnHasAllInformationChanged(projectId, hasAllInformation);
}
void IVisualStudioWorkingFolder.OnBeforeWorkingFolderChange()
{
UnregisterPrimarySolutionForPersistentStorage(_workspace.CurrentSolution.Id, synchronousShutdown: true);
......
......@@ -14,17 +14,27 @@ namespace Microsoft.VisualStudio.LanguageServices.Packaging
internal interface IPackageSearchDelayService
{
/// <summary>
/// They time to wait after a successful update (default = 1 day).
/// The time to wait after a successful update (default = 1 day).
/// </summary>
TimeSpan UpdateSucceededDelay { get; }
/// <summary>
/// They time to wait after a failed update (default = 1 minute).
/// The time to wait after a simple expected sort of failure (i.e. IO exceptions,
/// network exceptions, etc). Things we can recover from and would expect would
/// be transient.
/// </summary>
TimeSpan UpdateFailedDelay { get; }
TimeSpan ExpectedFailureDelay { get; }
/// <summary>
/// They time to wait after writing to disk fails (default = 10 seconds).
/// The time to wait after a catastrophic failed update (default = 1 day). For
/// example, if we download the full DB xml from the server and we cannot parse
/// it. Retrying soon after will not help. We'll just have to wait until proper
/// data is on the server for us to query.
/// </summary>
TimeSpan CatastrophicFailureDelay { get; }
/// <summary>
/// The time to wait after writing to disk fails (default = 10 seconds).
/// </summary>
TimeSpan FileWriteDelay { get; }
......@@ -33,4 +43,4 @@ internal interface IPackageSearchDelayService
/// </summary>
TimeSpan CachePollDelay { get; }
}
}
\ No newline at end of file
}
......@@ -10,7 +10,8 @@ private class DelayService : IPackageSearchDelayService
{
public TimeSpan CachePollDelay { get; } = TimeSpan.FromMinutes(1);
public TimeSpan FileWriteDelay { get; } = TimeSpan.FromSeconds(10);
public TimeSpan UpdateFailedDelay { get; } = TimeSpan.FromMinutes(1);
public TimeSpan ExpectedFailureDelay { get; } = TimeSpan.FromMinutes(1);
public TimeSpan CatastrophicFailureDelay { get; } = TimeSpan.FromDays(1);
public TimeSpan UpdateSucceededDelay { get; } = TimeSpan.FromDays(1);
}
}
......
......@@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
......@@ -30,6 +31,7 @@ internal partial class PackageSearchService
{
// Internal for testing purposes.
internal const string ContentAttributeName = "content";
internal const string ChecksumAttributeName = "checksum";
internal const string UpToDateAttributeName = "upToDate";
internal const string TooOldAttributeName = "tooOld";
internal const string NugetOrgSource = "nuget.org";
......@@ -220,7 +222,7 @@ private async Task<TimeSpan> UpdateDatabaseInBackgroundWorkerAsync()
// Note: we skip OperationCanceledException because it's not 'bad'.
// It's the standard way to indicate that we've been asked to shut
// down.
var delay = _service._delayService.UpdateFailedDelay;
var delay = _service._delayService.ExpectedFailureDelay;
_service.LogException(e, $"Error occurred updating. Retrying update in {delay}");
return delay;
}
......@@ -261,11 +263,35 @@ private async Task<TimeSpan> ProcessFullDatabaseXElementAsync(XElement element)
_service.LogInfo("Processing full database element");
// Convert the database contents in the xml to a byte[].
var bytes = ParseDatabaseElement(element);
byte[] bytes;
if (!TryParseDatabaseElement(element, out bytes))
{
// Something was wrong with the full database. Trying again soon after won't
// really help. We'll just get the same busted XML from the remote service
// cache. So we want to actually wait some long enough amount of time so that
// we can retrieve good data the next time around.
var failureDelay = _service._delayService.CatastrophicFailureDelay;
_service.LogInfo($"Unable to parse full database element. Update again in {failureDelay}");
return failureDelay;
}
// Make a database out of that and set it to our in memory database that we'll be
// searching.
CreateAndSetInMemoryDatabase(bytes);
try
{
CreateAndSetInMemoryDatabase(bytes);
}
catch (Exception e) when (_service._reportAndSwallowException(e))
{
// We retrieved bytes from the server, but we couldn't make a DB
// out of it. That's very bad. Just trying again one minute later
// isn't going to help. We need to wait until there is good data
// on the server for us to download.
var failureDelay = _service._delayService.CatastrophicFailureDelay;
_service.LogInfo($"Unable to create database from full database element. Update again in {failureDelay}");
return failureDelay;
}
// Write the file out to disk so we'll have it the next time we launch VS. Do this
// after we set the in-memory instance so we at least have something to search while
......@@ -578,21 +604,53 @@ private async Task RepeatIOAsync(Action action)
}
}
private byte[] ParseDatabaseElement(XElement element)
private bool TryParseDatabaseElement(XElement element, out byte[] bytes)
{
_service.LogInfo("Parsing database element");
var contentsAttribute = element.Attribute(ContentAttributeName);
if (contentsAttribute == null)
{
throw new FormatException($"Database element invalid. Missing '{ContentAttributeName}' attribute");
_service._reportAndSwallowException(
new FormatException($"Database element invalid. Missing '{ContentAttributeName}' attribute"));
bytes = null;
return false;
}
var contentBytes = ConvertContentAttribute(contentsAttribute);
var checksumAttribute = element.Attribute(ChecksumAttributeName);
if (checksumAttribute != null)
{
var expectedChecksum = checksumAttribute.Value;
string actualChecksum;
using (var sha256 = SHA256.Create())
{
actualChecksum = Convert.ToBase64String(sha256.ComputeHash(contentBytes));
}
if (!StringComparer.Ordinal.Equals(expectedChecksum, actualChecksum))
{
_service._reportAndSwallowException(
new FormatException($"Checksum mismatch: expected != actual. {expectedChecksum} != {actualChecksum}"));
bytes = null;
return false;
}
}
bytes = contentBytes;
return true;
}
private byte[] ConvertContentAttribute(XAttribute contentsAttribute)
{
var text = contentsAttribute.Value;
var compressedBytes = Convert.FromBase64String(text);
using (var inStream = new MemoryStream(compressedBytes))
using (var outStream = new MemoryStream())
{
using (var inStream = new MemoryStream(compressedBytes))
using (var deflateStream = new DeflateStream(inStream, CompressionMode.Decompress))
{
deflateStream.CopyTo(outStream);
......
......@@ -223,6 +223,7 @@ protected override void Dispose(bool disposing)
private void ReportSessionWideTelemetry()
{
PersistedVersionStampLogger.LogSummary();
LinkedFileDiffMergingLogger.ReportTelemetry();
}
private void RegisterFindResultsLibraryManager()
......
......@@ -690,6 +690,50 @@ internal class ServicesVSResources {
}
}
/// <summary>
/// Looks up a localized string similar to To see what caused the issue, please try below.
///
///1. Close Visual Studio
///2. Open a Visual Studio Developer Command Prompt
///3. Set environment variable “TraceDesignTime” to true (set TraceDesignTime=true)
///4. Delete .vs directory/.suo file
///5. Restart VS from the command prompt you set the environment varaible (devenv)
///6. Open the solution
///7. Check &apos;{0}&apos; and look for the failed tasks (FAILED).
/// </summary>
internal static string IntellisenseBuildFailedDescription {
get {
return ResourceManager.GetString("IntellisenseBuildFailedDescription", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Additional information:.
/// </summary>
internal static string IntellisenseBuildFailedDescriptionExtra {
get {
return ResourceManager.GetString("IntellisenseBuildFailedDescriptionExtra", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Error encountered while loading the project. Some project features, such as full solution analysis for the failed project and projects that depend on it, have been disabled..
/// </summary>
internal static string IntellisenseBuildFailedMessage {
get {
return ResourceManager.GetString("IntellisenseBuildFailedMessage", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Project loading failed..
/// </summary>
internal static string IntellisenseBuildFailedTitle {
get {
return ResourceManager.GetString("IntellisenseBuildFailedTitle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Installing &apos;{0}&apos; failed.
///
......
......@@ -641,6 +641,26 @@ Use the dropdown to view and switch to other projects this file may belong to.</
<data name="Package_uninstall_failed_0" xml:space="preserve">
<value>Package uninstall failed: {0}</value>
</data>
<data name="IntellisenseBuildFailedMessage" xml:space="preserve">
<value>Error encountered while loading the project. Some project features, such as full solution analysis for the failed project and projects that depend on it, have been disabled.</value>
</data>
<data name="IntellisenseBuildFailedTitle" xml:space="preserve">
<value>Project loading failed.</value>
</data>
<data name="IntellisenseBuildFailedDescription" xml:space="preserve">
<value>To see what caused the issue, please try below.
1. Close Visual Studio
2. Open a Visual Studio Developer Command Prompt
3. Set environment variable “TraceDesignTime” to true (set TraceDesignTime=true)
4. Delete .vs directory/.suo file
5. Restart VS from the command prompt you set the environment varaible (devenv)
6. Open the solution
7. Check '{0}' and look for the failed tasks (FAILED)</value>
</data>
<data name="IntellisenseBuildFailedDescriptionExtra" xml:space="preserve">
<value>Additional information:</value>
</data>
<data name="Installing_0_failed_Additional_information_1" xml:space="preserve">
<value>Installing '{0}' failed.
......
......@@ -85,9 +85,12 @@
<Compile Include="Implementation\Preview\ReferenceChange.AnalyzerReferenceChange.cs" />
<Compile Include="Implementation\Preview\ReferenceChange.ProjectReferenceChange.cs" />
<Compile Include="Implementation\Preview\ReferenceChange.cs" />
<Compile Include="Implementation\ProjectSystem\AbstractProject_IIntellisenseBuildTarget.cs" />
<Compile Include="Implementation\ProjectSystem\AbstractRoslynProject.cs" />
<Compile Include="Implementation\ProjectSystem\Interop\IIntellisenseBuildTarget.cs" />
<Compile Include="Implementation\ProjectSystem\Interop\ICompilerOptionsHostObject.cs" />
<Compile Include="Implementation\ProjectSystem\IVisualStudioWorkingFolder.cs" />
<Compile Include="Implementation\ProjectSystem\IVisualStudioWorkspaceHost2.cs" />
<Compile Include="Implementation\ProjectSystem\MetadataReferences\VisualStudioAnalyzerAssemblyLoaderService.cs" />
<Compile Include="Implementation\ProjectSystem\MetadataReferences\VisualStudioFrameworkAssemblyPathResolverFactory.cs" />
<Compile Include="Implementation\ProjectSystem\RuleSets\RuleSetEventHandler.cs" />
......
......@@ -119,6 +119,50 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Packaging
clientMock.Verify()
End Function
<Fact, Trait(Traits.Feature, Traits.Features.Packaging)>
Public Async Function FailureToParseFullDBAtXmlLevelTakesCatastrophicPath() As Task
Dim cancellationTokenSource = New CancellationTokenSource()
Dim ioMock = New Mock(Of IPackageSearchIOService)()
' Simlute the local database being missing.
ioMock.Setup(Function(s) s.Exists(It.IsAny(Of FileSystemInfo))).Returns(False)
Dim clientMock = CreateClientMock(CreateStream(New XElement("Database",
New XAttribute(PackageSearchService.ContentAttributeName, ""),
New XAttribute(PackageSearchService.ChecksumAttributeName, Convert.ToBase64String(New Byte() {0, 1, 2})))))
Dim serviceMock = New Mock(Of IPackageSearchRemoteControlService)(MockBehavior.Strict)
' The client should request the 'Latest' database from the server.
' Cancel processing at that point so the test can complete.
serviceMock.Setup(
Function(s) s.CreateClient(It.IsAny(Of String), It.IsRegex(".*Latest.*"), It.IsAny(Of Integer))).
Returns(clientMock.Object)
Dim delayMock = New Mock(Of IPackageSearchDelayService)(MockBehavior.Strict)
delayMock.SetupGet(Function(s) s.CatastrophicFailureDelay).Returns(TimeSpan.Zero).Callback(
AddressOf cancellationTokenSource.Cancel)
Dim searchService = New PackageSearchService(
installerService:=TestInstallerService.Instance,
remoteControlService:=serviceMock.Object,
logService:=TestLogService.Instance,
delayService:=delayMock.Object,
ioService:=ioMock.Object,
patchService:=Nothing,
databaseFactoryService:=Nothing,
localSettingsDirectory:="TestDirectory",
reportAndSwallowException:=s_allButMoqExceptions,
cancellationTokenSource:=cancellationTokenSource)
Await searchService.UpdateSourceInBackgroundAsync(PackageSearchService.NugetOrgSource)
ioMock.Verify()
serviceMock.Verify()
clientMock.Verify()
delayMock.Verify()
End Function
<Fact, Trait(Traits.Feature, Traits.Features.Packaging)>
Public Async Function TestClientDisposedAfterUse() As Task
Dim cancellationTokenSource = New CancellationTokenSource()
......@@ -180,7 +224,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Packaging
' control when we do our next loop.
' Cancel processing at that point so the test can complete.
Dim delayMock = New Mock(Of IPackageSearchDelayService)(MockBehavior.Strict)
delayMock.SetupGet(Function(s) s.UpdateFailedDelay).Returns(TimeSpan.Zero).Callback(
delayMock.SetupGet(Function(s) s.ExpectedFailureDelay).Returns(TimeSpan.Zero).Callback(
AddressOf cancellationTokenSource.Cancel)
Dim searchService = New PackageSearchService(
......@@ -203,7 +247,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Packaging
End Function
<Fact, Trait(Traits.Feature, Traits.Features.Packaging)>
Public Async Function FailureToParseDBRunsFailureLoopPath() As Task
Public Async Function FailureToParseFullDBAtElfieLevelTakesCatastrophicPath() As Task
Dim cancellationTokenSource = New CancellationTokenSource()
Dim ioMock = New Mock(Of IPackageSearchIOService)()
......@@ -223,7 +267,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Packaging
' control when we do our next loop.
' Cancel processing at that point so the test can complete.
Dim delayMock = New Mock(Of IPackageSearchDelayService)(MockBehavior.Strict)
delayMock.SetupGet(Function(s) s.UpdateFailedDelay).Returns(TimeSpan.Zero).Callback(
delayMock.SetupGet(Function(s) s.CatastrophicFailureDelay).Returns(TimeSpan.Zero).Callback(
AddressOf cancellationTokenSource.Cancel)
Dim searchService = New PackageSearchService(
......@@ -667,7 +711,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Packaging
End Get
End Property
Public ReadOnly Property UpdateFailedDelay As TimeSpan Implements IPackageSearchDelayService.UpdateFailedDelay
Public ReadOnly Property ExpectedFailureDelay As TimeSpan Implements IPackageSearchDelayService.ExpectedFailureDelay
Get
Return TimeSpan.Zero
End Get
......@@ -678,6 +722,12 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Packaging
Return TimeSpan.Zero
End Get
End Property
Public ReadOnly Property CatastrophicFailureDelay As TimeSpan Implements IPackageSearchDelayService.CatastrophicFailureDelay
Get
Return TimeSpan.Zero
End Get
End Property
End Class
Private Class TestInstallerService
......
......@@ -15,7 +15,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Fr
End Function
Public Function AdviseFileChange(pszMkDocument As String, grfFilter As UInteger, pFCE As IVsFileChangeEvents, ByRef pvsCookie As UInteger) As Integer Implements IVsFileChangeEx.AdviseFileChange
If grfFilter <> _VSFILECHANGEFLAGS.VSFILECHG_Time Then
If (grfFilter And _VSFILECHANGEFLAGS.VSFILECHG_Time) <> _VSFILECHANGEFLAGS.VSFILECHG_Time Then
Throw New NotImplementedException()
End If
......
......@@ -58,7 +58,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.ProjectSystemShim
Public Function AddEmbeddedMetaDataReference(wszFileName As String) As Integer Implements IVbCompilerProject.AddEmbeddedMetaDataReference
Try
Return AddMetadataReferenceAndTryConvertingToProjectReferenceIfPossible(wszFileName, New MetadataReferenceProperties(embedInteropTypes:=True), VSConstants.S_FALSE)
Return AddMetadataReferenceAndTryConvertingToProjectReferenceIfPossible(wszFileName, New MetadataReferenceProperties(embedInteropTypes:=True))
Catch e As Exception When FilterException(e)
Throw ExceptionUtilities.Unreachable
End Try
......@@ -72,7 +72,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.ProjectSystemShim
Return VSConstants.S_OK
End If
Return AddMetadataReferenceAndTryConvertingToProjectReferenceIfPossible(wszFileName, New MetadataReferenceProperties(), VSConstants.S_FALSE)
Return AddMetadataReferenceAndTryConvertingToProjectReferenceIfPossible(wszFileName, New MetadataReferenceProperties())
Catch e As Exception When FilterException(e)
Throw ExceptionUtilities.Unreachable
End Try
......@@ -414,7 +414,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.ProjectSystemShim
If HasMetadataReference(newRuntimeLibrary) Then
_explicitlyAddedDefaultReferences.Add(newRuntimeLibrary)
Else
MyBase.AddMetadataReferenceAndTryConvertingToProjectReferenceIfPossible(newRuntimeLibrary, MetadataReferenceProperties.Assembly, hResultForMissingFile:=0)
MyBase.AddMetadataReferenceAndTryConvertingToProjectReferenceIfPossible(newRuntimeLibrary, MetadataReferenceProperties.Assembly)
End If
Next
End If
......
......@@ -14,7 +14,7 @@ namespace Microsoft.CodeAnalysis.Diagnostics
{
internal static class Extensions
{
private static readonly CultureInfo s_USCultureInfo = new CultureInfo("en-US");
public static readonly CultureInfo s_USCultureInfo = new CultureInfo("en-US");
public static string GetBingHelpMessage(this Diagnostic diagnostic, Workspace workspace = null)
{
......
......@@ -203,6 +203,17 @@ private static TextSpan GetSpanFromTokens(TextSpan span, SyntaxToken token1, Syn
}
}
if (token1.Equals(token2) && end < start)
{
// This can happen if `token1.Span` is larger than `span` on each end (due to trivia) and occurs when
// only a single token is projected into a buffer and the projection is sandwiched between two other
// projections into the same backing buffer. An example of this is during broken code scenarios when
// typing certain Razor `@` directives.
var temp = end;
end = start;
start = temp;
}
return TextSpan.FromBounds(start, end);
}
}
......
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Internal.Log;
using static Microsoft.CodeAnalysis.LinkedFileDiffMergingSession;
namespace Microsoft.CodeAnalysis
{
internal class LinkedFileDiffMergingLogger
{
private static LogAggregator LogAggregator = new LogAggregator();
internal enum MergeInfo
{
SessionsWithLinkedFiles,
LinkedFileGroupsProcessed,
IdenticalDiffs,
IsolatedDiffs,
OverlappingDistinctDiffs,
OverlappingDistinctDiffsWithSameSpan,
OverlappingDistinctDiffsWithSameSpanAndSubstringRelation,
InsertedMergeConflictComments,
InsertedMergeConflictCommentsAtAdjustedLocation
}
internal static void LogSession(Workspace workspace, LinkedFileDiffMergingSessionInfo sessionInfo)
{
if (sessionInfo.LinkedFileGroups.Count > 1)
{
LogNewSessionWithLinkedFiles();
LogNumberOfLinkedFileGroupsProcessed(sessionInfo.LinkedFileGroups.Count);
foreach (var groupInfo in sessionInfo.LinkedFileGroups)
{
LogNumberOfIdenticalDiffs(groupInfo.IdenticalDiffs);
LogNumberOfIsolatedDiffs(groupInfo.IsolatedDiffs);
LogNumberOfOverlappingDistinctDiffs(groupInfo.OverlappingDistinctDiffs);
LogNumberOfOverlappingDistinctDiffsWithSameSpan(groupInfo.OverlappingDistinctDiffsWithSameSpan);
LogNumberOfOverlappingDistinctDiffsWithSameSpanAndSubstringRelation(groupInfo.OverlappingDistinctDiffsWithSameSpanAndSubstringRelation);
LogNumberOfInsertedMergeConflictComments(groupInfo.InsertedMergeConflictComments);
LogNumberOfInsertedMergeConflictCommentsAtAdjustedLocation(groupInfo.InsertedMergeConflictCommentsAtAdjustedLocation);
if (groupInfo.InsertedMergeConflictComments > 0 ||
groupInfo.InsertedMergeConflictCommentsAtAdjustedLocation > 0)
{
Logger.Log(FunctionId.Workspace_Solution_LinkedFileDiffMergingSession_LinkedFileGroup, SessionLogMessage.Create(groupInfo));
}
}
}
}
private static void LogNewSessionWithLinkedFiles() =>
Log((int)MergeInfo.SessionsWithLinkedFiles, 1);
private static void LogNumberOfLinkedFileGroupsProcessed(int linkedFileGroupsProcessed) =>
Log((int)MergeInfo.LinkedFileGroupsProcessed, linkedFileGroupsProcessed);
private static void LogNumberOfIdenticalDiffs(int identicalDiffs) =>
Log((int)MergeInfo.IdenticalDiffs, identicalDiffs);
private static void LogNumberOfIsolatedDiffs(int isolatedDiffs) =>
Log((int)MergeInfo.IsolatedDiffs, isolatedDiffs);
private static void LogNumberOfOverlappingDistinctDiffs(int overlappingDistinctDiffs) =>
Log((int)MergeInfo.OverlappingDistinctDiffs, overlappingDistinctDiffs);
private static void LogNumberOfOverlappingDistinctDiffsWithSameSpan(int overlappingDistinctDiffsWithSameSpan) =>
Log((int)MergeInfo.OverlappingDistinctDiffsWithSameSpan, overlappingDistinctDiffsWithSameSpan);
private static void LogNumberOfOverlappingDistinctDiffsWithSameSpanAndSubstringRelation(int overlappingDistinctDiffsWithSameSpanAndSubstringRelation) =>
Log((int)MergeInfo.OverlappingDistinctDiffsWithSameSpanAndSubstringRelation, overlappingDistinctDiffsWithSameSpanAndSubstringRelation);
private static void LogNumberOfInsertedMergeConflictComments(int insertedMergeConflictComments) =>
Log((int)MergeInfo.InsertedMergeConflictComments, insertedMergeConflictComments);
private static void LogNumberOfInsertedMergeConflictCommentsAtAdjustedLocation(int insertedMergeConflictCommentsAtAdjustedLocation) =>
Log((int)MergeInfo.InsertedMergeConflictCommentsAtAdjustedLocation, insertedMergeConflictCommentsAtAdjustedLocation);
private static void Log(int mergeInfo, int count)
{
LogAggregator.IncreaseCountBy(mergeInfo, count);
}
internal static void ReportTelemetry()
{
Logger.Log(FunctionId.Workspace_Solution_LinkedFileDiffMergingSession, KeyValueLogMessage.Create(m =>
{
foreach (var kv in LogAggregator)
{
var mergeInfo = ((MergeInfo)kv.Key).ToString("f");
m[mergeInfo] = kv.Value.GetCount();
}
}));
}
private static class SessionLogMessage
{
private const string LinkedDocuments = nameof(LinkedDocuments);
private const string DocumentsWithChanges = nameof(DocumentsWithChanges);
public static KeyValueLogMessage Create(LinkedFileGroupSessionInfo groupInfo)
{
return KeyValueLogMessage.Create(m =>
{
m[LinkedDocuments] = groupInfo.LinkedDocuments;
m[DocumentsWithChanges] = groupInfo.DocumentsWithChanges;
m[MergeInfo.IdenticalDiffs.ToString("f")] = groupInfo.IdenticalDiffs;
m[MergeInfo.IsolatedDiffs.ToString("f")] = groupInfo.IsolatedDiffs;
m[MergeInfo.OverlappingDistinctDiffs.ToString("f")] = groupInfo.OverlappingDistinctDiffs;
m[MergeInfo.OverlappingDistinctDiffsWithSameSpan.ToString("f")] = groupInfo.OverlappingDistinctDiffsWithSameSpan;
m[MergeInfo.OverlappingDistinctDiffsWithSameSpanAndSubstringRelation.ToString("f")] = groupInfo.OverlappingDistinctDiffsWithSameSpanAndSubstringRelation;
m[MergeInfo.InsertedMergeConflictComments.ToString("f")] = groupInfo.InsertedMergeConflictComments;
m[MergeInfo.InsertedMergeConflictCommentsAtAdjustedLocation.ToString("f")] = groupInfo.InsertedMergeConflictCommentsAtAdjustedLocation;
});
}
}
}
}
......@@ -316,68 +316,12 @@ private IEnumerable<TextChange> NormalizeChanges(IEnumerable<TextChange> changes
private void LogLinkedFileDiffMergingSessionInfo(LinkedFileDiffMergingSessionInfo sessionInfo)
{
// don't report telemetry
if (!_logSessionInfo)
{
return;
}
var sessionId = SessionLogMessage.GetNextId();
Logger.Log(FunctionId.Workspace_Solution_LinkedFileDiffMergingSession, SessionLogMessage.Create(sessionId, sessionInfo));
foreach (var groupInfo in sessionInfo.LinkedFileGroups)
{
Logger.Log(FunctionId.Workspace_Solution_LinkedFileDiffMergingSession_LinkedFileGroup, SessionLogMessage.Create(sessionId, groupInfo));
}
}
internal static class SessionLogMessage
{
private const string SessionId = nameof(SessionId);
private const string HasLinkedFile = nameof(HasLinkedFile);
private const string LinkedDocuments = nameof(LinkedDocuments);
private const string DocumentsWithChanges = nameof(DocumentsWithChanges);
private const string IdenticalDiffs = nameof(IdenticalDiffs);
private const string IsolatedDiffs = nameof(IsolatedDiffs);
private const string OverlappingDistinctDiffs = nameof(OverlappingDistinctDiffs);
private const string OverlappingDistinctDiffsWithSameSpan = nameof(OverlappingDistinctDiffsWithSameSpan);
private const string OverlappingDistinctDiffsWithSameSpanAndSubstringRelation = nameof(OverlappingDistinctDiffsWithSameSpanAndSubstringRelation);
private const string InsertedMergeConflictComments = nameof(InsertedMergeConflictComments);
private const string InsertedMergeConflictCommentsAtAdjustedLocation = nameof(InsertedMergeConflictCommentsAtAdjustedLocation);
public static KeyValueLogMessage Create(int sessionId, LinkedFileDiffMergingSessionInfo sessionInfo)
{
return KeyValueLogMessage.Create(m =>
{
m[SessionId] = sessionId;
m[HasLinkedFile] = sessionInfo.LinkedFileGroups.Count > 0;
});
}
public static KeyValueLogMessage Create(int sessionId, LinkedFileGroupSessionInfo groupInfo)
{
return KeyValueLogMessage.Create(m =>
{
m[SessionId] = sessionId;
m[LinkedDocuments] = groupInfo.LinkedDocuments;
m[DocumentsWithChanges] = groupInfo.DocumentsWithChanges;
m[IdenticalDiffs] = groupInfo.IdenticalDiffs;
m[IsolatedDiffs] = groupInfo.IsolatedDiffs;
m[OverlappingDistinctDiffs] = groupInfo.OverlappingDistinctDiffs;
m[OverlappingDistinctDiffsWithSameSpan] = groupInfo.OverlappingDistinctDiffsWithSameSpan;
m[OverlappingDistinctDiffsWithSameSpanAndSubstringRelation] = groupInfo.OverlappingDistinctDiffsWithSameSpanAndSubstringRelation;
m[InsertedMergeConflictComments] = groupInfo.InsertedMergeConflictComments;
m[InsertedMergeConflictCommentsAtAdjustedLocation] = groupInfo.InsertedMergeConflictCommentsAtAdjustedLocation;
});
}
public static int GetNextId()
{
return LogAggregator.GetNextId();
}
LinkedFileDiffMergingLogger.LogSession(this._newSolution.Workspace, sessionInfo);
}
internal class LinkedFileDiffMergingSessionInfo
......
......@@ -60,6 +60,7 @@ internal enum FunctionId
CommandHandler_GetCommandState,
CommandHandler_ExecuteHandlers,
CommandHandler_FormatCommand,
Workspace_SourceText_GetChangeRanges,
Workspace_Recoverable_RecoverRootAsync,
......@@ -324,5 +325,6 @@ internal enum FunctionId
SymbolTreeInfo_ExceptionInCacheRead,
SpellChecker_ExceptionInCacheRead,
BKTree_ExceptionInCacheRead,
IntellisenseBuild_Failed,
}
}
......@@ -64,6 +64,12 @@ public void IncreaseCount(object key)
counter.IncreaseCount();
}
public void IncreaseCountBy(object key, int value)
{
var counter = GetCounter(key);
counter.IncreaseCountBy(value);
}
public int GetCount(object key)
{
Counter counter;
......@@ -111,6 +117,13 @@ public void IncreaseCount()
Interlocked.Increment(ref _count);
}
public void IncreaseCountBy(int value)
{
// Counter class probably not needed. but it is here for 2 reasons.
// make handling concurrency easier and be a place holder for different type of counter
Interlocked.Add(ref _count, value);
}
public int GetCount()
{
return _count;
......
......@@ -35,55 +35,26 @@ internal Project(Solution solution, ProjectState projectState)
_projectState = projectState;
}
internal ProjectState State
{
get { return _projectState; }
}
/// <summary>
/// The solution this project is part of.
/// </summary>
public Solution Solution
{
get
{
return _solution;
}
}
public Solution Solution => _solution;
/// <summary>
/// The ID of the project. Multiple <see cref="Project"/> instances may share the same ID. However, only
/// one project may have this ID in any given solution.
/// </summary>
public ProjectId Id
{
get
{
return _projectState.Id;
}
}
public ProjectId Id => _projectState.Id;
/// <summary>
/// The path to the project file or null if there is no project file.
/// </summary>
public string FilePath
{
get
{
return _projectState.FilePath;
}
}
public string FilePath => _projectState.FilePath;
/// <summary>
/// The path to the output file, or null if it is not known.
/// </summary>
public string OutputFilePath
{
get
{
return _projectState.OutputFilePath;
}
}
public string OutputFilePath => _projectState.OutputFilePath;
/// <summary>
/// <code>true</code> if this <see cref="Project"/> supports providing data through the
......@@ -91,195 +62,93 @@ public string OutputFilePath
///
/// If <code>false</code> then this method will return <code>null</code> instead.
/// </summary>
public bool SupportsCompilation
{
get
{
return this.LanguageServices.GetService<ICompilationFactoryService>() != null;
}
}
public bool SupportsCompilation => this.LanguageServices.GetService<ICompilationFactoryService>() != null;
/// <summary>
/// The language services from the host environment associated with this project's language.
/// </summary>
public HostLanguageServices LanguageServices
{
get { return _projectState.LanguageServices; }
}
public HostLanguageServices LanguageServices => _projectState.LanguageServices;
/// <summary>
/// The language associated with the project.
/// </summary>
public string Language
{
get
{
return _projectState.LanguageServices.Language;
}
}
public string Language => _projectState.LanguageServices.Language;
/// <summary>
/// The name of the assembly this project represents.
/// </summary>
public string AssemblyName
{
get
{
return _projectState.AssemblyName;
}
}
public string AssemblyName => _projectState.AssemblyName;
/// <summary>
/// The name of the project. This may be different than the assembly name.
/// </summary>
public string Name
{
get
{
return _projectState.Name;
}
}
public string Name => _projectState.Name;
/// <summary>
/// The list of all other metadata sources (assemblies) that this project references.
/// </summary>
public IReadOnlyList<MetadataReference> MetadataReferences
{
get
{
return _projectState.MetadataReferences;
}
}
public IReadOnlyList<MetadataReference> MetadataReferences => _projectState.MetadataReferences;
/// <summary>
/// The list of all other projects within the same solution that this project references.
/// </summary>
public IEnumerable<ProjectReference> ProjectReferences
{
get
{
return _projectState.ProjectReferences.Where(pr => this.Solution.ContainsProject(pr.ProjectId));
}
}
public IEnumerable<ProjectReference> ProjectReferences => _projectState.ProjectReferences.Where(pr => this.Solution.ContainsProject(pr.ProjectId));
/// <summary>
/// The list of all other projects that this project references, including projects that
/// are not part of the solution.
/// </summary>
public IReadOnlyList<ProjectReference> AllProjectReferences
{
get
{
return _projectState.ProjectReferences;
}
}
public IReadOnlyList<ProjectReference> AllProjectReferences => _projectState.ProjectReferences;
/// <summary>
/// The list of all the diagnostic analyzer references for this project.
/// </summary>
public IReadOnlyList<AnalyzerReference> AnalyzerReferences
{
get
{
return _projectState.AnalyzerReferences;
}
}
public IReadOnlyList<AnalyzerReference> AnalyzerReferences => _projectState.AnalyzerReferences;
/// <summary>
/// The options used by analyzers for this project.
/// </summary>
public AnalyzerOptions AnalyzerOptions
{
get
{
return _projectState.AnalyzerOptions;
}
}
public AnalyzerOptions AnalyzerOptions => _projectState.AnalyzerOptions;
/// <summary>
/// The options used when building the compilation for this project.
/// </summary>
public CompilationOptions CompilationOptions
{
get
{
return _projectState.CompilationOptions;
}
}
public CompilationOptions CompilationOptions => _projectState.CompilationOptions;
/// <summary>
/// The options used when parsing documents for this project.
/// </summary>
public ParseOptions ParseOptions
{
get
{
return _projectState.ParseOptions;
}
}
public ParseOptions ParseOptions => _projectState.ParseOptions;
/// <summary>
/// Returns true if this is a submission project.
/// </summary>
public bool IsSubmission
{
get
{
return _projectState.IsSubmission;
}
}
public bool IsSubmission => _projectState.IsSubmission;
/// <summary>
/// True if the project has any documents.
/// </summary>
public bool HasDocuments
{
get
{
return _projectState.HasDocuments;
}
}
public bool HasDocuments => _projectState.HasDocuments;
/// <summary>
/// All the document IDs associated with this project.
/// </summary>
public IReadOnlyList<DocumentId> DocumentIds
{
get
{
return _projectState.DocumentIds;
}
}
public IReadOnlyList<DocumentId> DocumentIds => _projectState.DocumentIds;
/// <summary>
/// All the additional document IDs associated with this project.
/// </summary>
public IReadOnlyList<DocumentId> AdditionalDocumentIds
{
get
{
return _projectState.AdditionalDocumentIds;
}
}
public IReadOnlyList<DocumentId> AdditionalDocumentIds => _projectState.AdditionalDocumentIds;
/// <summary>
/// All the documents associated with this project.
/// </summary>
public IEnumerable<Document> Documents
{
get
{
return _projectState.DocumentIds.Select(GetDocument);
}
}
public IEnumerable<Document> Documents => _projectState.DocumentIds.Select(GetDocument);
public IEnumerable<TextDocument> AdditionalDocuments
{
get
{
return _projectState.AdditionalDocumentIds.Select(GetAdditionalDocument);
}
}
/// <summary>
/// All the additional documents associated with this project.
/// </summary>
public IEnumerable<TextDocument> AdditionalDocuments => _projectState.AdditionalDocumentIds.Select(GetAdditionalDocument);
/// <summary>
/// True if the project contains a document with the specified ID.
......@@ -390,12 +259,12 @@ public Task<Compilation> GetCompilationAsync(CancellationToken cancellationToken
}
/// <summary>
/// Determines if the compilation returned by <see cref="GetCompilationAsync"/> has all the references it's expected to have.
/// Determines if the compilation returned by <see cref="GetCompilationAsync"/> and all its referenced compilaton are from fully loaded projects.
/// </summary>
// TODO: make this public
internal Task<bool> HasCompleteReferencesAsync(CancellationToken cancellationToken = default(CancellationToken))
internal Task<bool> HasSuccessfullyLoadedAsync(CancellationToken cancellationToken = default(CancellationToken))
{
return _solution.HasCompleteReferencesAsync(this, cancellationToken);
return _solution.HasSuccessfullyLoadedAsync(this, cancellationToken);
}
/// <summary>
......
......@@ -2,8 +2,6 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis.Diagnostics;
using Roslyn.Utilities;
using System.Diagnostics;
......@@ -96,6 +94,13 @@ public sealed class ProjectInfo
/// </summary>
public Type HostObjectType { get; }
/// <summary>
/// True if project information is complete. In some workspace hosts, it is possible
/// a project only has partial information. In such cases, a project might not have all
/// information on its files or references.
/// </summary>
internal bool HasAllInformation { get; }
private ProjectInfo(
ProjectId id,
VersionStamp version,
......@@ -112,7 +117,8 @@ public sealed class ProjectInfo
IEnumerable<AnalyzerReference> analyzerReferences,
IEnumerable<DocumentInfo> additionalDocuments,
bool isSubmission,
Type hostObjectType)
Type hostObjectType,
bool hasAllInformation)
{
if (id == null)
{
......@@ -150,28 +156,30 @@ public sealed class ProjectInfo
this.AdditionalDocuments = additionalDocuments.ToImmutableReadOnlyListOrEmpty();
this.IsSubmission = isSubmission;
this.HostObjectType = hostObjectType;
this.HasAllInformation = hasAllInformation;
}
/// <summary>
/// Create a new instance of a ProjectInfo.
/// </summary>
public static ProjectInfo Create(
internal static ProjectInfo Create(
ProjectId id,
VersionStamp version,
string name,
string assemblyName,
string language,
string filePath = null,
string outputFilePath = null,
CompilationOptions compilationOptions = null,
ParseOptions parseOptions = null,
IEnumerable<DocumentInfo> documents = null,
IEnumerable<ProjectReference> projectReferences = null,
IEnumerable<MetadataReference> metadataReferences = null,
IEnumerable<AnalyzerReference> analyzerReferences = null,
IEnumerable<DocumentInfo> additionalDocuments = null,
bool isSubmission = false,
Type hostObjectType = null)
string filePath,
string outputFilePath,
CompilationOptions compilationOptions,
ParseOptions parseOptions,
IEnumerable<DocumentInfo> documents,
IEnumerable<ProjectReference> projectReferences,
IEnumerable<MetadataReference> metadataReferences,
IEnumerable<AnalyzerReference> analyzerReferences,
IEnumerable<DocumentInfo> additionalDocuments,
bool isSubmission,
Type hostObjectType,
bool hasAllInformation)
{
return new ProjectInfo(
id,
......@@ -189,7 +197,36 @@ public sealed class ProjectInfo
analyzerReferences,
additionalDocuments,
isSubmission,
hostObjectType);
hostObjectType,
hasAllInformation);
}
/// <summary>
/// Create a new instance of a ProjectInfo.
/// </summary>
public static ProjectInfo Create(
ProjectId id,
VersionStamp version,
string name,
string assemblyName,
string language,
string filePath = null,
string outputFilePath = null,
CompilationOptions compilationOptions = null,
ParseOptions parseOptions = null,
IEnumerable<DocumentInfo> documents = null,
IEnumerable<ProjectReference> projectReferences = null,
IEnumerable<MetadataReference> metadataReferences = null,
IEnumerable<AnalyzerReference> analyzerReferences = null,
IEnumerable<DocumentInfo> additionalDocuments = null,
bool isSubmission = false,
Type hostObjectType = null)
{
return Create(
id, version, name, assemblyName, language,
filePath, outputFilePath, compilationOptions, parseOptions,
documents, projectReferences, metadataReferences, analyzerReferences, additionalDocuments,
isSubmission, hostObjectType, hasAllInformation: true);
}
private ProjectInfo With(
......@@ -208,7 +245,8 @@ public sealed class ProjectInfo
IEnumerable<AnalyzerReference> analyzerReferences = null,
IEnumerable<DocumentInfo> additionalDocuments = null,
Optional<bool> isSubmission = default(Optional<bool>),
Optional<Type> hostObjectType = default(Optional<Type>))
Optional<Type> hostObjectType = default(Optional<Type>),
Optional<bool> hasAllInformation = default(Optional<bool>))
{
var newId = id ?? this.Id;
var newVersion = version.HasValue ? version.Value : this.Version;
......@@ -226,6 +264,7 @@ public sealed class ProjectInfo
var newAdditionalDocuments = additionalDocuments ?? this.AdditionalDocuments;
var newIsSubmission = isSubmission.HasValue ? isSubmission.Value : this.IsSubmission;
var newHostObjectType = hostObjectType.HasValue ? hostObjectType.Value : this.HostObjectType;
var newHasAllInformation = hasAllInformation.HasValue ? hasAllInformation.Value : this.HasAllInformation;
if (newId == this.Id &&
newVersion == this.Version &&
......@@ -242,7 +281,8 @@ public sealed class ProjectInfo
newAnalyzerReferences == this.AnalyzerReferences &&
newAdditionalDocuments == this.AdditionalDocuments &&
newIsSubmission == this.IsSubmission &&
newHostObjectType == this.HostObjectType)
newHostObjectType == this.HostObjectType &&
newHasAllInformation == this.HasAllInformation)
{
return this;
}
......@@ -263,7 +303,8 @@ public sealed class ProjectInfo
newAnalyzerReferences,
newAdditionalDocuments,
newIsSubmission,
newHostObjectType);
newHostObjectType,
newHasAllInformation);
}
public ProjectInfo WithDocuments(IEnumerable<DocumentInfo> documents)
......@@ -326,6 +367,11 @@ public ProjectInfo WithAnalyzerReferences(IEnumerable<AnalyzerReference> analyze
return this.With(analyzerReferences: analyzerReferences.ToImmutableReadOnlyListOrEmpty());
}
internal ProjectInfo WithHasAllInformation(bool hasAllInformation)
{
return this.With(hasAllInformation: hasAllInformation);
}
internal string GetDebuggerDisplay()
{
return nameof(ProjectInfo) + " " + Name + (!string.IsNullOrWhiteSpace(FilePath) ? " " + FilePath : "");
......
......@@ -25,7 +25,9 @@ internal partial class ProjectState
private readonly IReadOnlyList<DocumentId> _additionalDocumentIds;
private readonly AsyncLazy<VersionStamp> _lazyLatestDocumentVersion;
private readonly AsyncLazy<VersionStamp> _lazyLatestDocumentTopLevelChangeVersion;
private AnalyzerOptions _analyzerOptions;
// this will be initialized lazily.
private AnalyzerOptions _analyzerOptionsDoNotAccessDirectly;
private ProjectState(
ProjectInfo projectInfo,
......@@ -61,12 +63,12 @@ internal ProjectState(ProjectInfo projectInfo, HostLanguageServices languageServ
_projectInfo = FixProjectInfo(projectInfo);
_documentIds = _projectInfo.Documents.Select(d => d.Id).ToImmutableArray();
_additionalDocumentIds = this.ProjectInfo.AdditionalDocuments.Select(d => d.Id).ToImmutableArray();
_additionalDocumentIds = _projectInfo.AdditionalDocuments.Select(d => d.Id).ToImmutableArray();
var docStates = ImmutableDictionary.CreateRange<DocumentId, DocumentState>(
_projectInfo.Documents.Select(d =>
new KeyValuePair<DocumentId, DocumentState>(d.Id,
CreateDocument(this.ProjectInfo, d, languageServices, solutionServices))));
CreateDocument(_projectInfo, d, languageServices, solutionServices))));
_documentStates = docStates;
......@@ -231,12 +233,12 @@ public AnalyzerOptions AnalyzerOptions
{
get
{
if (_analyzerOptions == null)
if (_analyzerOptionsDoNotAccessDirectly == null)
{
_analyzerOptions = new AnalyzerOptions(_additionalDocumentStates.Values.Select(d => new AdditionalTextDocument(d)).ToImmutableArray<AdditionalText>());
_analyzerOptionsDoNotAccessDirectly = new AnalyzerOptions(_additionalDocumentStates.Values.Select(d => new AdditionalTextDocument(d)).ToImmutableArray<AdditionalText>());
}
return _analyzerOptions;
return _analyzerOptionsDoNotAccessDirectly;
}
}
......@@ -303,6 +305,12 @@ public IReadOnlyList<ProjectReference> ProjectReferences
get { return this.ProjectInfo.ProjectReferences; }
}
[DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
public bool HasAllInformation
{
get { return this.ProjectInfo.HasAllInformation; }
}
[DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
public bool HasDocuments
{
......@@ -457,6 +465,16 @@ public ProjectState UpdateParseOptions(ParseOptions options)
documentStates: docMap);
}
public ProjectState UpdateHasAllInformation(bool hasAllInformation)
{
if (hasAllInformation == this.HasAllInformation)
{
return this;
}
return this.With(projectInfo: this.ProjectInfo.WithHasAllInformation(hasAllInformation).WithVersion(this.Version.GetNewerVersion()));
}
public static bool IsSameLanguage(ProjectState project1, ProjectState project2)
{
return project1.LanguageServices == project2.LanguageServices;
......
......@@ -39,10 +39,10 @@ private class State
public ValueSource<Compilation> Compilation { get; }
/// <summary>
/// Specifies if there are references that got dropped in the production of <see cref="FinalCompilation"/>. This can return
/// Specifies whether <see cref="FinalCompilation"/> and all compilations it depends on contain full information or not. This can return
/// null if the state isn't at the point where it would know, and it's necessary to transition to <see cref="FinalState"/> to figure that out.
/// </summary>
public virtual bool? HasCompleteReferences => null;
public virtual bool? HasSuccessfullyLoadedTransitively => null;
/// <summary>
/// The final compilation if available, otherwise an empty <see cref="ValueSource{Compilation}"/>.
......@@ -135,19 +135,15 @@ public FullDeclarationState(Compilation declarationCompilation)
/// </summary>
private sealed class FinalState : State
{
private readonly bool _hasCompleteReferences;
private readonly bool _hasSuccessfullyLoadedTransitively;
public override ValueSource<Compilation> FinalCompilation
{
get { return this.Compilation; }
}
public override bool? HasCompleteReferences => _hasCompleteReferences;
public override bool? HasSuccessfullyLoadedTransitively => _hasSuccessfullyLoadedTransitively;
public override ValueSource<Compilation> FinalCompilation => this.Compilation;
public FinalState(ValueSource<Compilation> finalCompilationSource, bool hasCompleteReferences)
public FinalState(ValueSource<Compilation> finalCompilationSource, bool hasSuccessfullyLoadedTransitively)
: base(finalCompilationSource, finalCompilationSource.GetValue().Clone().RemoveAllReferences())
{
_hasCompleteReferences = hasCompleteReferences;
_hasSuccessfullyLoadedTransitively = hasSuccessfullyLoadedTransitively;
}
}
}
......
......@@ -181,7 +181,7 @@ public CompilationTracker FreezePartialStateWithTree(Solution solution, Document
// have the compilation immediately disappear. So we force it to stay around with a ConstantValueSource.
// As a policy, all partial-state projects are said to have incomplete references, since the state has no guarantees.
return new CompilationTracker(inProgressProject,
new FinalState(new ConstantValueSource<Compilation>(inProgressCompilation), hasCompleteReferences: false));
new FinalState(new ConstantValueSource<Compilation>(inProgressCompilation), hasSuccessfullyLoadedTransitively: false));
}
/// <summary>
......@@ -313,21 +313,6 @@ public Task<Compilation> GetCompilationAsync(Solution solution, CancellationToke
}
}
public Task<bool> HasCompleteReferencesAsync(Solution solution, CancellationToken cancellationToken)
{
var state = this.ReadState();
if (state.HasCompleteReferences.HasValue)
{
return Task.FromResult(state.HasCompleteReferences.Value);
}
else
{
return GetOrBuildCompilationInfoAsync(solution, lockGate: true, cancellationToken: cancellationToken)
.ContinueWith(t => t.Result.HasCompleteReferences, cancellationToken, TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Default);
}
}
private static string LogBuildCompilationAsync(ProjectState state)
{
return string.Join(",", state.AssemblyName, state.DocumentIds.Count);
......@@ -406,7 +391,7 @@ private async Task<Compilation> GetOrBuildDeclarationCompilationAsync(Solution s
var finalCompilation = state.FinalCompilation.GetValue(cancellationToken);
if (finalCompilation != null)
{
return new CompilationInfo(finalCompilation, hasCompleteReferences: state.HasCompleteReferences.Value);
return new CompilationInfo(finalCompilation, state.HasSuccessfullyLoadedTransitively.Value);
}
// Otherwise, we actually have to build it. Ensure that only one thread is trying to
......@@ -447,7 +432,7 @@ private async Task<Compilation> GetOrBuildDeclarationCompilationAsync(Solution s
var compilation = state.FinalCompilation.GetValue(cancellationToken);
if (compilation != null)
{
return Task.FromResult(new CompilationInfo(compilation, state.HasCompleteReferences.Value));
return Task.FromResult(new CompilationInfo(compilation, state.HasSuccessfullyLoadedTransitively.Value));
}
compilation = state.Compilation.GetValue(cancellationToken);
......@@ -582,12 +567,12 @@ private Compilation CreateEmptyCompilation()
private struct CompilationInfo
{
public Compilation Compilation { get; }
public bool HasCompleteReferences { get; }
public bool HasSuccessfullyLoadedTransitively { get; }
public CompilationInfo(Compilation compilation, bool hasCompleteReferences)
public CompilationInfo(Compilation compilation, bool hasSuccessfullyLoadedTransitively)
{
this.Compilation = compilation;
this.HasCompleteReferences = hasCompleteReferences;
this.HasSuccessfullyLoadedTransitively = hasSuccessfullyLoadedTransitively;
}
}
......@@ -602,7 +587,9 @@ public CompilationInfo(Compilation compilation, bool hasCompleteReferences)
{
try
{
bool hasCompleteReferences = true;
// if HasAllInformation is false, then this project is always not completed.
bool hasSuccessfullyLoaded = this.ProjectState.HasAllInformation;
var newReferences = new List<MetadataReference>();
newReferences.AddRange(this.ProjectState.MetadataReferences);
......@@ -639,7 +626,7 @@ public CompilationInfo(Compilation compilation, bool hasCompleteReferences)
}
else
{
hasCompleteReferences = false;
hasSuccessfullyLoaded = false;
}
}
}
......@@ -650,9 +637,10 @@ public CompilationInfo(Compilation compilation, bool hasCompleteReferences)
compilation = compilation.WithReferences(newReferences);
}
this.WriteState(new FinalState(State.CreateValueSource(compilation, solution.Services), hasCompleteReferences), solution);
bool hasSuccessfullyLoadedTransitively = !HasMissingReferences(compilation, this.ProjectState.MetadataReferences) && await ComputeHasSuccessfullyLoadedTransitivelyAsync(solution, hasSuccessfullyLoaded, cancellationToken).ConfigureAwait(false);
return new CompilationInfo(compilation, hasCompleteReferences);
this.WriteState(new FinalState(State.CreateValueSource(compilation, solution.Services), hasSuccessfullyLoadedTransitively), solution);
return new CompilationInfo(compilation, hasSuccessfullyLoadedTransitively);
}
catch (Exception e) when (FatalError.ReportUnlessCanceled(e))
{
......@@ -660,6 +648,43 @@ public CompilationInfo(Compilation compilation, bool hasCompleteReferences)
}
}
private bool HasMissingReferences(Compilation compilation, IReadOnlyList<MetadataReference> metadataReferences)
{
foreach (var reference in metadataReferences)
{
if (compilation.GetAssemblyOrModuleSymbol(reference) == null)
{
return true;
}
}
return false;
}
private async Task<bool> ComputeHasSuccessfullyLoadedTransitivelyAsync(Solution solution, bool hasSuccessfullyLoaded, CancellationToken cancellationToken)
{
if (!hasSuccessfullyLoaded)
{
return false;
}
foreach (var projectReference in this.ProjectState.ProjectReferences)
{
var project = solution.GetProject(projectReference.ProjectId);
if (project == null)
{
return false;
}
if (!await solution.HasSuccessfullyLoadedAsync(project, cancellationToken).ConfigureAwait(false))
{
return false;
}
}
return true;
}
/// <summary>
/// Get a metadata reference to this compilation info's compilation with respect to
/// another project. For cross language references produce a skeletal assembly. If the
......@@ -794,6 +819,21 @@ public IEnumerable<SyntaxTree> GetSyntaxTreesWithNameFromDeclarationOnlyCompilat
return clone.GetSymbolsWithName(predicate, filter, cancellationToken).SelectMany(s => s.DeclaringSyntaxReferences.Select(r => r.SyntaxTree));
}
public Task<bool> HasSuccessfullyLoadedAsync(Solution solution, CancellationToken cancellationToken)
{
var state = this.ReadState();
if (state.HasSuccessfullyLoadedTransitively.HasValue)
{
return state.HasSuccessfullyLoadedTransitively.Value ? SpecializedTasks.True : SpecializedTasks.False;
}
else
{
return GetOrBuildCompilationInfoAsync(solution, lockGate: true, cancellationToken: cancellationToken)
.ContinueWith(t => t.Result.HasSuccessfullyLoadedTransitively, cancellationToken, TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Default);
}
}
#region Versions
// Dependent Versions are stored on compilation tracker so they are more likely to survive when unrelated solution branching occurs.
......
......@@ -3,8 +3,6 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
......@@ -839,6 +837,32 @@ public Solution WithProjectParseOptions(ProjectId projectId, ParseOptions option
}
}
/// <summary>
/// Create a new solution instance with the project specified updated to have
/// the specified hasAllInformation.
/// </summary>
// TODO: make it public
internal Solution WithHasAllInformation(ProjectId projectId, bool hasAllInformation)
{
if (projectId == null)
{
throw new ArgumentNullException(nameof(projectId));
}
Contract.Requires(this.ContainsProject(projectId));
var oldProject = this.GetProjectState(projectId);
var newProject = oldProject.UpdateHasAllInformation(hasAllInformation);
if (oldProject == newProject)
{
return this;
}
// fork without any change on compilation.
return this.ForkProject(newProject);
}
private static async Task<Compilation> ReplaceSyntaxTreesWithTreesFromNewProjectStateAsync(Compilation compilation, ProjectState projectState, CancellationToken cancellationToken)
{
var syntaxTrees = new List<SyntaxTree>(capacity: projectState.DocumentIds.Count);
......@@ -2020,11 +2044,16 @@ internal Task<Compilation> GetCompilationAsync(Project project, CancellationToke
: SpecializedTasks.Default<Compilation>();
}
internal Task<bool> HasCompleteReferencesAsync(Project project, CancellationToken cancellationToken)
/// <summary>
/// Return reference completeness for the given project and all projects this references.
/// </summary>
internal Task<bool> HasSuccessfullyLoadedAsync(Project project, CancellationToken cancellationToken)
{
// return HasAllInformation when compilation is not supported.
// regardless whether project support compilation or not, if projectInfo is not complete, we can't gurantee its reference completeness
return project.SupportsCompilation
? this.GetCompilationTracker(project.Id).HasCompleteReferencesAsync(this, cancellationToken)
: SpecializedTasks.False;
? this.GetCompilationTracker(project.Id).HasSuccessfullyLoadedAsync(this, cancellationToken)
: project.Solution.GetProjectState(project.Id).HasAllInformation ? SpecializedTasks.True : SpecializedTasks.False;
}
private static readonly ConditionalWeakTable<MetadataReference, ProjectId> s_metadataReferenceToProjectMap =
......
......@@ -703,6 +703,25 @@ protected internal void OnAdditionalDocumentTextLoaderChanged(DocumentId documen
}
}
/// <summary>
/// Call this method when status of project has changed to incomplete.
/// See <see cref="ProjectInfo.HasAllInformation"/> for more information.
/// </summary>
// TODO: make it public
internal void OnHasAllInformationChanged(ProjectId projectId, bool hasAllInformation)
{
using (_serializationLock.DisposableWait())
{
CheckProjectIsInCurrentSolution(projectId);
// if state is different than what we have
var oldSolution = this.CurrentSolution;
var newSolution = this.SetCurrentSolution(oldSolution.WithHasAllInformation(projectId, hasAllInformation));
this.RaiseWorkspaceChangedEventAsync(WorkspaceChangeKind.ProjectChanged, oldSolution, newSolution, projectId);
}
}
/// <summary>
/// Call this method when the text of a document is updated in the host environment.
/// </summary>
......
......@@ -386,6 +386,7 @@
<Compile Include="ExtensionManager\IErrorReportingService.cs" />
<Compile Include="FindSymbols\DeclaredSymbolInfo.cs" />
<Compile Include="FindSymbols\FindReferences\Finders\ILanguageServiceReferenceFinder.cs" />
<Compile Include="LinkedFileDiffMerging\LinkedFileDiffMergingLogger.cs" />
<Compile Include="Packaging\IPackageInstallerService.cs" />
<Compile Include="Packaging\IPackageSearchService.cs" />
<Compile Include="FindSymbols\SymbolTree\ISymbolTreeInfoCacheService.cs" />
......
......@@ -1404,8 +1404,8 @@ public void TestProjectWithNoBrokenReferencesHasNoIncompleteReferences()
projectReferences: new[] { new ProjectReference(project1.Id) }));
// Nothing should have incomplete references, and everything should build
Assert.True(project1.HasCompleteReferencesAsync().Result);
Assert.True(project2.HasCompleteReferencesAsync().Result);
Assert.True(project1.HasSuccessfullyLoadedAsync().Result);
Assert.True(project2.HasSuccessfullyLoadedAsync().Result);
Assert.Single(project2.GetCompilationAsync().Result.ExternalReferences);
}
......@@ -1425,8 +1425,8 @@ public void TestProjectWithBrokenCrossLanguageReferenceHasIncompleteReferences()
LanguageNames.VisualBasic,
projectReferences: new[] { new ProjectReference(project1.Id) }));
Assert.True(project1.HasCompleteReferencesAsync().Result);
Assert.False(project2.HasCompleteReferencesAsync().Result);
Assert.True(project1.HasSuccessfullyLoadedAsync().Result);
Assert.False(project2.HasSuccessfullyLoadedAsync().Result);
Assert.Empty(project2.GetCompilationAsync().Result.ExternalReferences);
}
......@@ -1450,8 +1450,103 @@ public void TestFrozenPartialProjectAlwaysIsIncomplete()
// Nothing should have incomplete references, and everything should build
var frozenSolution = document.WithFrozenPartialSemanticsAsync(CancellationToken.None).Result.Project.Solution;
Assert.True(frozenSolution.GetProject(project1.Id).HasCompleteReferencesAsync().Result);
Assert.True(frozenSolution.GetProject(project2.Id).HasCompleteReferencesAsync().Result);
Assert.True(frozenSolution.GetProject(project1.Id).HasSuccessfullyLoadedAsync().Result);
Assert.True(frozenSolution.GetProject(project2.Id).HasSuccessfullyLoadedAsync().Result);
}
[Fact]
public void TestProjectCompletenessWithMultipleProjects()
{
Project csBrokenProject;
Project vbNormalProject;
Project dependsOnBrokenProject;
Project dependsOnVbNormalProject;
Project transitivelyDependsOnBrokenProjects;
Project transitivelyDependsOnNormalProjects;
GetMultipleProjects(out csBrokenProject, out vbNormalProject, out dependsOnBrokenProject, out dependsOnVbNormalProject, out transitivelyDependsOnBrokenProjects, out transitivelyDependsOnNormalProjects);
// check flag for a broken project itself
Assert.False(csBrokenProject.HasSuccessfullyLoadedAsync().Result);
// check flag for a normal project itself
Assert.True(vbNormalProject.HasSuccessfullyLoadedAsync().Result);
// check flag for normal project that directly reference a broken project
Assert.False(dependsOnBrokenProject.HasSuccessfullyLoadedAsync().Result);
// check flag for normal project that directly reference only normal project
Assert.True(dependsOnVbNormalProject.HasSuccessfullyLoadedAsync().Result);
// check flag for normal project that indirectly reference a borken project
// normal project -> normal project -> broken project
Assert.False(transitivelyDependsOnBrokenProjects.HasSuccessfullyLoadedAsync().Result);
// check flag for normal project that indirectly reference only normal project
// normal project -> normal project -> normal project
Assert.True(transitivelyDependsOnNormalProjects.HasSuccessfullyLoadedAsync().Result);
}
private static void GetMultipleProjects(
out Project csBrokenProject,
out Project vbNormalProject,
out Project dependsOnBrokenProject,
out Project dependsOnVbNormalProject,
out Project transitivelyDependsOnBrokenProjects,
out Project transitivelyDependsOnNormalProjects)
{
var workspace = new AdhocWorkspace();
csBrokenProject = workspace.AddProject(
ProjectInfo.Create(
ProjectId.CreateNewId(),
VersionStamp.Create(),
"CSharpProject",
"CSharpProject",
LanguageNames.CSharp).WithHasAllInformation(hasAllInformation: false));
vbNormalProject = workspace.AddProject(
ProjectInfo.Create(
ProjectId.CreateNewId(),
VersionStamp.Create(),
"VisualBasicProject",
"VisualBasicProject",
LanguageNames.VisualBasic));
dependsOnBrokenProject = workspace.AddProject(
ProjectInfo.Create(
ProjectId.CreateNewId(),
VersionStamp.Create(),
"VisualBasicProject",
"VisualBasicProject",
LanguageNames.VisualBasic,
projectReferences: new[] { new ProjectReference(csBrokenProject.Id), new ProjectReference(vbNormalProject.Id) }));
dependsOnVbNormalProject = workspace.AddProject(
ProjectInfo.Create(
ProjectId.CreateNewId(),
VersionStamp.Create(),
"CSharpProject",
"CSharpProject",
LanguageNames.CSharp,
projectReferences: new[] { new ProjectReference(vbNormalProject.Id) }));
transitivelyDependsOnBrokenProjects = workspace.AddProject(
ProjectInfo.Create(
ProjectId.CreateNewId(),
VersionStamp.Create(),
"CSharpProject",
"CSharpProject",
LanguageNames.CSharp,
projectReferences: new[] { new ProjectReference(dependsOnBrokenProject.Id) }));
transitivelyDependsOnNormalProjects = workspace.AddProject(
ProjectInfo.Create(
ProjectId.CreateNewId(),
VersionStamp.Create(),
"VisualBasicProject",
"VisualBasicProject",
LanguageNames.VisualBasic,
projectReferences: new[] { new ProjectReference(dependsOnVbNormalProject.Id) }));
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册