提交 270fb2c6 编写于 作者: T Tomáš Matoušek

Merge pull request #2817 from tmat/ReducedClauses

Fix breakpoint handling and EnC of queries containing Select and GroupBy clauses that don't translate their select/group-by expression to lambda
......@@ -47,9 +47,9 @@ public BoundLambda(CSharpSyntaxNode syntax, BoundBlock body, ImmutableArray<Diag
}
Debug.Assert(
syntax.IsAnonymousFunction() || // lambda expressions
syntax is ExpressionSyntax && LambdaUtilities.IsLambdaBody(syntax) || // query lambdas
LambdaUtilities.IsQueryPairLambda(syntax) // "pair" lambdas in queries
syntax.IsAnonymousFunction() || // lambda expressions
syntax is ExpressionSyntax && LambdaUtilities.IsLambdaBody(syntax, allowReducedLambas: true) || // query lambdas
LambdaUtilities.IsQueryPairLambda(syntax) // "pair" lambdas in queries
);
}
......
......@@ -441,9 +441,9 @@ public new IEnumerable<Diagnostic> GetDiagnostics()
return this.SyntaxTree.GetDiagnostics(this);
}
internal sealed override SyntaxNode GetCorrespondingLambdaBody(SyntaxNode body)
internal sealed override SyntaxNode TryGetCorrespondingLambdaBody(SyntaxNode body)
{
return LambdaUtilities.GetCorrespondingLambdaBody(body, this);
return LambdaUtilities.TryGetCorrespondingLambdaBody(body, this);
}
internal override SyntaxNode GetLambda()
......
......@@ -22,11 +22,14 @@ public static bool IsLambda(SyntaxNode node)
case SyntaxKind.WhereClause:
case SyntaxKind.AscendingOrdering:
case SyntaxKind.DescendingOrdering:
case SyntaxKind.SelectClause:
case SyntaxKind.JoinClause:
case SyntaxKind.GroupClause:
return true;
case SyntaxKind.SelectClause:
var selectClause = (SelectClauseSyntax)node;
return !IsReducedSelectOrGroupByClause(selectClause, selectClause.Expression);
case SyntaxKind.FromClause:
// The first from clause of a query expression is not a lambda.
return !node.Parent.IsKind(SyntaxKind.QueryExpression);
......@@ -50,7 +53,7 @@ public static SyntaxNode GetLambda(SyntaxNode lambdaBody)
/// <summary>
/// See SyntaxNode.GetCorrespondingLambdaBody.
/// </summary>
internal static SyntaxNode GetCorrespondingLambdaBody(SyntaxNode oldBody, SyntaxNode newLambda)
internal static SyntaxNode TryGetCorrespondingLambdaBody(SyntaxNode oldBody, SyntaxNode newLambda)
{
var oldLambda = oldBody.Parent;
switch (oldLambda.Kind())
......@@ -74,7 +77,11 @@ internal static SyntaxNode GetCorrespondingLambdaBody(SyntaxNode oldBody, Syntax
return ((OrderingSyntax)newLambda).Expression;
case SyntaxKind.SelectClause:
return ((SelectClauseSyntax)newLambda).Expression;
var selectClause = (SelectClauseSyntax)newLambda;
// Select clause is not considered to be lambda if it's reduced,
// however to avoid complexity we allow it to be passed in and just return null.
return IsReducedSelectOrGroupByClause(selectClause, selectClause.Expression) ? null : selectClause.Expression;
case SyntaxKind.JoinClause:
var oldJoin = (JoinClauseSyntax)oldLambda;
......@@ -86,7 +93,8 @@ internal static SyntaxNode GetCorrespondingLambdaBody(SyntaxNode oldBody, Syntax
var oldGroup = (GroupClauseSyntax)oldLambda;
var newGroup = (GroupClauseSyntax)newLambda;
Debug.Assert(oldGroup.GroupExpression == oldBody || oldGroup.ByExpression == oldBody);
return (oldGroup.GroupExpression == oldBody) ? newGroup.GroupExpression : newGroup.ByExpression;
return (oldGroup.GroupExpression == oldBody) ?
(IsReducedSelectOrGroupByClause(newGroup, newGroup.GroupExpression) ? null : newGroup.GroupExpression) : newGroup.ByExpression;
default:
throw ExceptionUtilities.UnexpectedValue(oldLambda.Kind());
......@@ -101,7 +109,7 @@ public static bool IsNotLambdaBody(SyntaxNode node)
/// <summary>
/// Returns true if the specified <paramref name="node"/> represents a body of a lambda.
/// </summary>
public static bool IsLambdaBody(SyntaxNode node)
public static bool IsLambdaBody(SyntaxNode node, bool allowReducedLambas = false)
{
var parent = node?.Parent;
if (parent == null)
......@@ -140,16 +148,83 @@ public static bool IsLambdaBody(SyntaxNode node)
case SyntaxKind.SelectClause:
var selectClause = (SelectClauseSyntax)parent;
return selectClause.Expression == node;
return selectClause.Expression == node && (allowReducedLambas || !IsReducedSelectOrGroupByClause(selectClause, selectClause.Expression));
case SyntaxKind.GroupClause:
var groupClause = (GroupClauseSyntax)parent;
return groupClause.GroupExpression == node || groupClause.ByExpression == node;
return (groupClause.GroupExpression == node && (allowReducedLambas || !IsReducedSelectOrGroupByClause(groupClause, groupClause.GroupExpression))) ||
groupClause.ByExpression == node;
}
return false;
}
/// <summary>
/// When queries are translated into expressions select and group-by expressions such that
/// 1) select/group-by expression is the same identifier as the "source" identifier and
/// 2) at least one Where or OrderBy clause but no other clause is present in the contained query body or
/// the expression in question is a group-by expression and the body has no clause
///
/// do not translate into lambdas.
/// By "source" identifier we mean the identifier specified in the from clause that initiates the query or the query continuation that includes the body.
///
/// The above condition can be dervied from the language specification (chapter 7.16.2) as follows:
/// - In order for 7.16.2.5 "Select clauses" to be applicable the following conditions must hold:
/// - There has to be at least one clause in the body, otherwise the query is reduced into a final form by 7.16.2.3 "Degenerate query expressions".
/// - Only where and order-by clauses may be present in the query body, otherwise a transformation in 7.16.2.4 "From, let, where, join and orderby clauses"
/// produces pattern that doesn't match the requirements of 7.16.2.5.
///
/// - In order for 7.16.2.6 "Groupby clauses" to be applicable the following conditions must hold:
/// - Only where and order-by clauses may be present in the query body, otherwise a transformation in 7.16.2.4 "From, let, where, join and orderby clauses"
/// produces pattern that doesn't match the requirements of 7.16.2.5.
/// </summary>
private static bool IsReducedSelectOrGroupByClause(SelectOrGroupClauseSyntax selectOrGroupClause, ExpressionSyntax selectOrGroupExpression)
{
if (!selectOrGroupExpression.IsKind(SyntaxKind.IdentifierName))
{
return false;
}
var selectorIdentifier = ((IdentifierNameSyntax)selectOrGroupExpression).Identifier;
SyntaxToken sourceIdentifier;
QueryBodySyntax containingBody;
var containingQueryOrContinuation = selectOrGroupClause.Parent.Parent;
if (containingQueryOrContinuation.IsKind(SyntaxKind.QueryExpression))
{
var containingQuery = (QueryExpressionSyntax)containingQueryOrContinuation;
containingBody = containingQuery.Body;
sourceIdentifier = containingQuery.FromClause.Identifier;
}
else
{
var containingContinuation = (QueryContinuationSyntax)containingQueryOrContinuation;
sourceIdentifier = containingContinuation.Identifier;
containingBody = containingContinuation.Body;
}
if (!SyntaxFactory.AreEquivalent(sourceIdentifier, selectorIdentifier))
{
return false;
}
if (selectOrGroupClause.IsKind(SyntaxKind.SelectClause) && containingBody.Clauses.Count == 0)
{
return false;
}
foreach (var clause in containingBody.Clauses)
{
if (!clause.IsKind(SyntaxKind.WhereClause) && !clause.IsKind(SyntaxKind.OrderByClause))
{
return false;
}
}
return true;
}
/// <remarks>
/// In C# lambda bodies are expressions or block statements. In both cases it's a single node.
/// In VB a lambda body might be a sequence of nodes (statements).
......@@ -212,13 +287,27 @@ public static bool TryGetLambdaBodies(SyntaxNode node, out SyntaxNode lambdaBody
return true;
case SyntaxKind.SelectClause:
lambdaBody1 = ((SelectClauseSyntax)node).Expression;
var selectClause = (SelectClauseSyntax)node;
if (IsReducedSelectOrGroupByClause(selectClause, selectClause.Expression))
{
return false;
}
lambdaBody1 = selectClause.Expression;
return true;
case SyntaxKind.GroupClause:
var groupClause = (GroupClauseSyntax)node;
lambdaBody1 = groupClause.GroupExpression;
lambdaBody2 = groupClause.ByExpression;
if (IsReducedSelectOrGroupByClause(groupClause, groupClause.GroupExpression))
{
lambdaBody1 = groupClause.ByExpression;
}
else
{
lambdaBody1 = groupClause.GroupExpression;
lambdaBody2 = groupClause.ByExpression;
}
return true;
}
......
......@@ -2042,5 +2042,491 @@ class C
"C: {<> c}",
"C.<>c: {<>9__2_0#1, <>9__2_0, <>9__2_2#1, <.ctor>b__2_0#1, <.ctor>b__2_0, <.ctor>b__2_2#1}");
}
[Fact]
public void Queries_Select_Reduced1()
{
var source0 = MarkedSource(@"
using System;
using System.Linq;
using System.Collections.Generic;
class C
{
void F()
{
var <N:0>result = from a in array
<N:1>where a > 0</N:1>
<N:2>select a</N:2></N:0>;
}
int[] array = null;
}
");
var source1 = MarkedSource(@"
using System;
using System.Linq;
using System.Collections.Generic;
class C
{
void F()
{
var <N:0>result = from a in array
<N:1>where a > 0</N:1>
<N:2>select a + 1</N:2></N:0>;
}
int[] array = null;
}
");
var compilation0 = CreateCompilationWithMscorlib(source0.Tree, new[] { SystemCoreRef }, options: ComSafeDebugDll);
var compilation1 = compilation0.WithSource(source1.Tree);
var v0 = CompileAndVerify(compilation0);
var md0 = ModuleMetadata.CreateFromImage(v0.EmittedAssemblyData);
var f0 = compilation0.GetMember<MethodSymbol>("C.F");
var f1 = compilation1.GetMember<MethodSymbol>("C.F");
var generation0 = EmitBaseline.CreateInitialBaseline(md0, v0.CreateSymReader().GetEncMethodDebugInfo);
var diff1 = compilation1.EmitDifference(
generation0,
ImmutableArray.Create(
new SemanticEdit(SemanticEditKind.Update, f0, f1, GetSyntaxMapFromMarkers(source0, source1), preserveLocalVariables: true)));
var md1 = diff1.GetMetadata();
var reader1 = md1.Reader;
// new lambda for Select(a => a + 1)
diff1.VerifySynthesizedMembers(
"C: {<>c}",
"C.<>c: {<>9__0_0, <>9__0_1#1, <F>b__0_0, <F>b__0_1#1}");
diff1.VerifyIL("C.<>c.<F>b__0_1#1", @"
{
// Code size 4 (0x4)
.maxstack 2
IL_0000: ldarg.1
IL_0001: ldc.i4.1
IL_0002: add
IL_0003: ret
}
");
// old query:
v0.VerifyIL("C.F", @"
{
// Code size 45 (0x2d)
.maxstack 3
.locals init (System.Collections.Generic.IEnumerable<int> V_0) //result
IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldfld ""int[] C.array""
IL_0007: ldsfld ""System.Func<int, bool> C.<>c.<>9__0_0""
IL_000c: dup
IL_000d: brtrue.s IL_0026
IL_000f: pop
IL_0010: ldsfld ""C.<>c C.<>c.<>9""
IL_0015: ldftn ""bool C.<>c.<F>b__0_0(int)""
IL_001b: newobj ""System.Func<int, bool>..ctor(object, System.IntPtr)""
IL_0020: dup
IL_0021: stsfld ""System.Func<int, bool> C.<>c.<>9__0_0""
IL_0026: call ""System.Collections.Generic.IEnumerable<int> System.Linq.Enumerable.Where<int>(System.Collections.Generic.IEnumerable<int>, System.Func<int, bool>)""
IL_002b: stloc.0
IL_002c: ret
}
");
// new query:
diff1.VerifyIL("C.F", @"
{
// Code size 81 (0x51)
.maxstack 3
.locals init (System.Collections.Generic.IEnumerable<int> V_0) //result
IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldfld ""int[] C.array""
IL_0007: ldsfld ""System.Func<int, bool> C.<>c.<>9__0_0""
IL_000c: dup
IL_000d: brtrue.s IL_0026
IL_000f: pop
IL_0010: ldsfld ""C.<>c C.<>c.<>9""
IL_0015: ldftn ""bool C.<>c.<F>b__0_0(int)""
IL_001b: newobj ""System.Func<int, bool>..ctor(object, System.IntPtr)""
IL_0020: dup
IL_0021: stsfld ""System.Func<int, bool> C.<>c.<>9__0_0""
IL_0026: call ""System.Collections.Generic.IEnumerable<int> System.Linq.Enumerable.Where<int>(System.Collections.Generic.IEnumerable<int>, System.Func<int, bool>)""
IL_002b: ldsfld ""System.Func<int, int> C.<>c.<>9__0_1#1""
IL_0030: dup
IL_0031: brtrue.s IL_004a
IL_0033: pop
IL_0034: ldsfld ""C.<>c C.<>c.<>9""
IL_0039: ldftn ""int C.<>c.<F>b__0_1#1(int)""
IL_003f: newobj ""System.Func<int, int>..ctor(object, System.IntPtr)""
IL_0044: dup
IL_0045: stsfld ""System.Func<int, int> C.<>c.<>9__0_1#1""
IL_004a: call ""System.Collections.Generic.IEnumerable<int> System.Linq.Enumerable.Select<int, int>(System.Collections.Generic.IEnumerable<int>, System.Func<int, int>)""
IL_004f: stloc.0
IL_0050: ret
}
");
}
[Fact]
public void Queries_Select_Reduced2()
{
var source0 = MarkedSource(@"
using System;
using System.Linq;
using System.Collections.Generic;
class C
{
void F()
{
var <N:0>result = from a in array
<N:1>where a > 0</N:1>
<N:2>select a + 1</N:2></N:0>;
}
int[] array = null;
}
");
var source1 = MarkedSource(@"
using System;
using System.Linq;
using System.Collections.Generic;
class C
{
void F()
{
var <N:0>result = from a in array
<N:1>where a > 0</N:1>
<N:2>select a</N:2></N:0>;
}
int[] array = null;
}
");
var compilation0 = CreateCompilationWithMscorlib(source0.Tree, new[] { SystemCoreRef }, options: ComSafeDebugDll);
var compilation1 = compilation0.WithSource(source1.Tree);
var v0 = CompileAndVerify(compilation0);
var md0 = ModuleMetadata.CreateFromImage(v0.EmittedAssemblyData);
var f0 = compilation0.GetMember<MethodSymbol>("C.F");
var f1 = compilation1.GetMember<MethodSymbol>("C.F");
var generation0 = EmitBaseline.CreateInitialBaseline(md0, v0.CreateSymReader().GetEncMethodDebugInfo);
var diff1 = compilation1.EmitDifference(
generation0,
ImmutableArray.Create(
new SemanticEdit(SemanticEditKind.Update, f0, f1, GetSyntaxMapFromMarkers(source0, source1), preserveLocalVariables: true)));
var md1 = diff1.GetMetadata();
var reader1 = md1.Reader;
// lambda for Select(a => a + 1) is gone
diff1.VerifySynthesizedMembers(
"C: {<>c}",
"C.<>c: {<>9__0_0, <F>b__0_0}");
// old query:
v0.VerifyIL("C.F", @"
{
// Code size 81 (0x51)
.maxstack 3
.locals init (System.Collections.Generic.IEnumerable<int> V_0) //result
IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldfld ""int[] C.array""
IL_0007: ldsfld ""System.Func<int, bool> C.<>c.<>9__0_0""
IL_000c: dup
IL_000d: brtrue.s IL_0026
IL_000f: pop
IL_0010: ldsfld ""C.<>c C.<>c.<>9""
IL_0015: ldftn ""bool C.<>c.<F>b__0_0(int)""
IL_001b: newobj ""System.Func<int, bool>..ctor(object, System.IntPtr)""
IL_0020: dup
IL_0021: stsfld ""System.Func<int, bool> C.<>c.<>9__0_0""
IL_0026: call ""System.Collections.Generic.IEnumerable<int> System.Linq.Enumerable.Where<int>(System.Collections.Generic.IEnumerable<int>, System.Func<int, bool>)""
IL_002b: ldsfld ""System.Func<int, int> C.<>c.<>9__0_1""
IL_0030: dup
IL_0031: brtrue.s IL_004a
IL_0033: pop
IL_0034: ldsfld ""C.<>c C.<>c.<>9""
IL_0039: ldftn ""int C.<>c.<F>b__0_1(int)""
IL_003f: newobj ""System.Func<int, int>..ctor(object, System.IntPtr)""
IL_0044: dup
IL_0045: stsfld ""System.Func<int, int> C.<>c.<>9__0_1""
IL_004a: call ""System.Collections.Generic.IEnumerable<int> System.Linq.Enumerable.Select<int, int>(System.Collections.Generic.IEnumerable<int>, System.Func<int, int>)""
IL_004f: stloc.0
IL_0050: ret
}
");
// new query:
diff1.VerifyIL("C.F", @"
{
// Code size 45 (0x2d)
.maxstack 3
.locals init (System.Collections.Generic.IEnumerable<int> V_0) //result
IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldfld ""int[] C.array""
IL_0007: ldsfld ""System.Func<int, bool> C.<>c.<>9__0_0""
IL_000c: dup
IL_000d: brtrue.s IL_0026
IL_000f: pop
IL_0010: ldsfld ""C.<>c C.<>c.<>9""
IL_0015: ldftn ""bool C.<>c.<F>b__0_0(int)""
IL_001b: newobj ""System.Func<int, bool>..ctor(object, System.IntPtr)""
IL_0020: dup
IL_0021: stsfld ""System.Func<int, bool> C.<>c.<>9__0_0""
IL_0026: call ""System.Collections.Generic.IEnumerable<int> System.Linq.Enumerable.Where<int>(System.Collections.Generic.IEnumerable<int>, System.Func<int, bool>)""
IL_002b: stloc.0
IL_002c: ret
}
");
}
[Fact]
public void Queries_GroupBy_Reduced1()
{
var source0 = MarkedSource(@"
using System;
using System.Linq;
using System.Collections.Generic;
class C
{
void F()
{
var <N:0>result = from a in array
<N:1>group a by a</N:1></N:0>;
}
int[] array = null;
}
");
var source1 = MarkedSource(@"
using System;
using System.Linq;
using System.Collections.Generic;
class C
{
void F()
{
var <N:0>result = from a in array
<N:1>group a + 1 by a</N:1></N:0>;
}
int[] array = null;
}
");
var compilation0 = CreateCompilationWithMscorlib(source0.Tree, new[] { SystemCoreRef }, options: ComSafeDebugDll);
var compilation1 = compilation0.WithSource(source1.Tree);
var v0 = CompileAndVerify(compilation0);
var md0 = ModuleMetadata.CreateFromImage(v0.EmittedAssemblyData);
var f0 = compilation0.GetMember<MethodSymbol>("C.F");
var f1 = compilation1.GetMember<MethodSymbol>("C.F");
var generation0 = EmitBaseline.CreateInitialBaseline(md0, v0.CreateSymReader().GetEncMethodDebugInfo);
var diff1 = compilation1.EmitDifference(
generation0,
ImmutableArray.Create(
new SemanticEdit(SemanticEditKind.Update, f0, f1, GetSyntaxMapFromMarkers(source0, source1), preserveLocalVariables: true)));
var md1 = diff1.GetMetadata();
var reader1 = md1.Reader;
// new lambda for GroupBy(..., a => a + 1)
diff1.VerifySynthesizedMembers(
"C: {<>c}",
"C.<>c: {<>9__0_0, <>9__0_1#1, <F>b__0_0, <F>b__0_1#1}");
diff1.VerifyIL("C.<>c.<F>b__0_1#1", @"
{
// Code size 4 (0x4)
.maxstack 2
IL_0000: ldarg.1
IL_0001: ldc.i4.1
IL_0002: add
IL_0003: ret
}
");
// old query:
v0.VerifyIL("C.F", @"
{
// Code size 45 (0x2d)
.maxstack 3
.locals init (System.Collections.Generic.IEnumerable<System.Linq.IGrouping<int, int>> V_0) //result
IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldfld ""int[] C.array""
IL_0007: ldsfld ""System.Func<int, int> C.<>c.<>9__0_0""
IL_000c: dup
IL_000d: brtrue.s IL_0026
IL_000f: pop
IL_0010: ldsfld ""C.<>c C.<>c.<>9""
IL_0015: ldftn ""int C.<>c.<F>b__0_0(int)""
IL_001b: newobj ""System.Func<int, int>..ctor(object, System.IntPtr)""
IL_0020: dup
IL_0021: stsfld ""System.Func<int, int> C.<>c.<>9__0_0""
IL_0026: call ""System.Collections.Generic.IEnumerable<System.Linq.IGrouping<int, int>> System.Linq.Enumerable.GroupBy<int, int>(System.Collections.Generic.IEnumerable<int>, System.Func<int, int>)""
IL_002b: stloc.0
IL_002c: ret
}
");
// new query:
diff1.VerifyIL("C.F", @"
{
// Code size 76 (0x4c)
.maxstack 4
.locals init (System.Collections.Generic.IEnumerable<System.Linq.IGrouping<int, int>> V_0) //result
IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldfld ""int[] C.array""
IL_0007: ldsfld ""System.Func<int, int> C.<>c.<>9__0_0""
IL_000c: dup
IL_000d: brtrue.s IL_0026
IL_000f: pop
IL_0010: ldsfld ""C.<>c C.<>c.<>9""
IL_0015: ldftn ""int C.<>c.<F>b__0_0(int)""
IL_001b: newobj ""System.Func<int, int>..ctor(object, System.IntPtr)""
IL_0020: dup
IL_0021: stsfld ""System.Func<int, int> C.<>c.<>9__0_0""
IL_0026: ldsfld ""System.Func<int, int> C.<>c.<>9__0_1#1""
IL_002b: dup
IL_002c: brtrue.s IL_0045
IL_002e: pop
IL_002f: ldsfld ""C.<>c C.<>c.<>9""
IL_0034: ldftn ""int C.<>c.<F>b__0_1#1(int)""
IL_003a: newobj ""System.Func<int, int>..ctor(object, System.IntPtr)""
IL_003f: dup
IL_0040: stsfld ""System.Func<int, int> C.<>c.<>9__0_1#1""
IL_0045: call ""System.Collections.Generic.IEnumerable<System.Linq.IGrouping<int, int>> System.Linq.Enumerable.GroupBy<int, int, int>(System.Collections.Generic.IEnumerable<int>, System.Func<int, int>, System.Func<int, int>)""
IL_004a: stloc.0
IL_004b: ret
}
");
}
[Fact]
public void Queries_GroupBy_Reduced2()
{
var source0 = MarkedSource(@"
using System;
using System.Linq;
using System.Collections.Generic;
class C
{
void F()
{
var <N:0>result = from a in array
<N:1>group a + 1 by a</N:1></N:0>;
}
int[] array = null;
}
");
var source1 = MarkedSource(@"
using System;
using System.Linq;
using System.Collections.Generic;
class C
{
void F()
{
var <N:0>result = from a in array
<N:1>group a by a</N:1></N:0>;
}
int[] array = null;
}
");
var compilation0 = CreateCompilationWithMscorlib(source0.Tree, new[] { SystemCoreRef }, options: ComSafeDebugDll);
var compilation1 = compilation0.WithSource(source1.Tree);
var v0 = CompileAndVerify(compilation0);
var md0 = ModuleMetadata.CreateFromImage(v0.EmittedAssemblyData);
var f0 = compilation0.GetMember<MethodSymbol>("C.F");
var f1 = compilation1.GetMember<MethodSymbol>("C.F");
var generation0 = EmitBaseline.CreateInitialBaseline(md0, v0.CreateSymReader().GetEncMethodDebugInfo);
var diff1 = compilation1.EmitDifference(
generation0,
ImmutableArray.Create(
new SemanticEdit(SemanticEditKind.Update, f0, f1, GetSyntaxMapFromMarkers(source0, source1), preserveLocalVariables: true)));
var md1 = diff1.GetMetadata();
var reader1 = md1.Reader;
// lambda for GroupBy(..., a => a + 1) is gone
diff1.VerifySynthesizedMembers(
"C: {<>c}",
"C.<>c: {<>9__0_0, <F>b__0_0}");
// old query:
v0.VerifyIL("C.F", @"
{
// Code size 76 (0x4c)
.maxstack 4
.locals init (System.Collections.Generic.IEnumerable<System.Linq.IGrouping<int, int>> V_0) //result
IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldfld ""int[] C.array""
IL_0007: ldsfld ""System.Func<int, int> C.<>c.<>9__0_0""
IL_000c: dup
IL_000d: brtrue.s IL_0026
IL_000f: pop
IL_0010: ldsfld ""C.<>c C.<>c.<>9""
IL_0015: ldftn ""int C.<>c.<F>b__0_0(int)""
IL_001b: newobj ""System.Func<int, int>..ctor(object, System.IntPtr)""
IL_0020: dup
IL_0021: stsfld ""System.Func<int, int> C.<>c.<>9__0_0""
IL_0026: ldsfld ""System.Func<int, int> C.<>c.<>9__0_1""
IL_002b: dup
IL_002c: brtrue.s IL_0045
IL_002e: pop
IL_002f: ldsfld ""C.<>c C.<>c.<>9""
IL_0034: ldftn ""int C.<>c.<F>b__0_1(int)""
IL_003a: newobj ""System.Func<int, int>..ctor(object, System.IntPtr)""
IL_003f: dup
IL_0040: stsfld ""System.Func<int, int> C.<>c.<>9__0_1""
IL_0045: call ""System.Collections.Generic.IEnumerable<System.Linq.IGrouping<int, int>> System.Linq.Enumerable.GroupBy<int, int, int>(System.Collections.Generic.IEnumerable<int>, System.Func<int, int>, System.Func<int, int>)""
IL_004a: stloc.0
IL_004b: ret
}
");
// new query:
diff1.VerifyIL("C.F", @"
{
// Code size 45 (0x2d)
.maxstack 3
.locals init (System.Collections.Generic.IEnumerable<System.Linq.IGrouping<int, int>> V_0) //result
IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldfld ""int[] C.array""
IL_0007: ldsfld ""System.Func<int, int> C.<>c.<>9__0_0""
IL_000c: dup
IL_000d: brtrue.s IL_0026
IL_000f: pop
IL_0010: ldsfld ""C.<>c C.<>c.<>9""
IL_0015: ldftn ""int C.<>c.<F>b__0_0(int)""
IL_001b: newobj ""System.Func<int, int>..ctor(object, System.IntPtr)""
IL_0020: dup
IL_0021: stsfld ""System.Func<int, int> C.<>c.<>9__0_0""
IL_0026: call ""System.Collections.Generic.IEnumerable<System.Linq.IGrouping<int, int>> System.Linq.Enumerable.GroupBy<int, int>(System.Collections.Generic.IEnumerable<int>, System.Func<int, int>)""
IL_002b: stloc.0
IL_002c: ret
}
");
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Test.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.CSharp.UnitTests
{
public class LambdaUtilitiesTests
public class LambdaUtilitiesTests : CSharpTestBase
{
private void TestLambdaBody(string markedExpression, bool isLambdaBody)
{
string markedSource = @"
using System;
using System.Linq;
class C
{
void M()
{
var expr = " + markedExpression + @";
}
static T F<T>(T x) => x;
}";
string source;
int? position;
TextSpan? span;
MarkupTestFile.GetPositionAndSpan(markedSource, out source, out position, out span);
Assert.Null(position);
Assert.NotNull(span);
var tree = SyntaxFactory.ParseSyntaxTree(source);
var compilation = CreateCompilationWithMscorlib45(new[] { tree }, new[] { SystemCoreRef });
compilation.GetDiagnostics().Where(d => d.Severity == DiagnosticSeverity.Error).Verify();
var model = compilation.GetSemanticModel(tree, ignoreAccessibility: false);
var enclosingMethod = (IMethodSymbol)model.GetEnclosingSymbol(span.Value.Start);
var enclosingSyntax = enclosingMethod.DeclaringSyntaxReferences.Single().GetSyntax();
bool expected = enclosingMethod.MethodKind == MethodKind.LambdaMethod && enclosingSyntax.Span.Contains(span.Value);
var node = tree.GetRoot().FindNode(span.Value);
Assert.Equal(expected, LambdaUtilities.IsLambdaBody(node));
Assert.Equal(isLambdaBody, expected);
}
[Fact]
public void IsLambdaBody_AnonymousFunction1()
{
TestLambdaBody("new Func<int>(() => [|1|])", isLambdaBody: true);
TestLambdaBody("new Func<int, int>(x => [|x|])", isLambdaBody: true);
TestLambdaBody("new Func<int, int>((x) => [|x|])", isLambdaBody: true);
TestLambdaBody("new Func<int, int>(x => [|{ return x; }|])", isLambdaBody: true);
TestLambdaBody("new Func<int>(delegate [|{ return 1; }|] )", isLambdaBody: true);
}
[Fact]
public void IsLambdaBody_From1()
{
TestLambdaBody(
"from x in [|new[] { 1 }|] select x", isLambdaBody: false);
TestLambdaBody(
"from y in new[] { 1 } from x in [|new[] { 2 }|] select x", isLambdaBody: true);
}
[Fact]
public void IsLambdaBody_Join1()
{
TestLambdaBody(
"from y in new[] { 1 } join x in [|new[] { 2 }|] on y equals x select x", isLambdaBody: false);
TestLambdaBody(
"from y in new[] { 1 } join x in new[] { 2 } on [|y|] equals x select x", isLambdaBody: true);
TestLambdaBody(
"from y in new[] { 1 } join x in new[] { 2 } on y equals [|x|] select x", isLambdaBody: true);
}
[Fact]
public void IsLambdaBody_OrderBy1()
{
TestLambdaBody(
"from x in new[] { 1 } orderby [|x|] ascending select x", isLambdaBody: true);
TestLambdaBody(
"from x in new[] { 1 } orderby x descending, [|x|] ascending select x", isLambdaBody: true);
}
[Fact]
public void IsLambdaBody_Where1()
{
TestLambdaBody(
"from x in new[] { 1 } where [|x > 0|] select x", isLambdaBody: true);
}
[Fact]
public void IsLambdaBody_Let1()
{
TestLambdaBody(
"from x in new[] { 1 } let y = [|0|] select y", isLambdaBody: true);
}
[Fact]
public void IsLambdaBody_Select1()
{
TestLambdaBody(
"from x in new[] { 1 } select [|x|]", isLambdaBody: true);
TestLambdaBody(
"from x in new[] { 1 } where x > 0 select [|x|]", isLambdaBody: false);
TestLambdaBody(
"from x in new[] { 1 } where x > 0 select [|@x|]", isLambdaBody: false);
TestLambdaBody(
"from x in new[] { 1 } orderby F(x), F(x) descending select [|x|]", isLambdaBody: false);
TestLambdaBody(
"from x in new[] { 1 } orderby x where x > 0 select [|x|]", isLambdaBody: false);
TestLambdaBody(
"from x in new[] { 1 } select x into y where y > 0 select [|y|]", isLambdaBody: false);
TestLambdaBody(
"from x in new[] { 1 } select x into y orderby y select [|y|]", isLambdaBody: false);
TestLambdaBody(
"from x in new[] { 1 } select [|x|] into y where y > 0 select y", isLambdaBody: true);
TestLambdaBody(
"from x in new[] { 1 } where x > 0 select [|x|] into y where y > 0 select y", isLambdaBody: false);
TestLambdaBody(
"from x in new[] { 1 } where x > 0 select x into y where y > 0 select [|y|]", isLambdaBody: false);
TestLambdaBody(
"from x in new[] { 1 } orderby x let z = x where x > 0 select [|x|]", isLambdaBody: true);
TestLambdaBody(
"from x in new[] { 1 } from y in new[] { 2 } select [|x|]", isLambdaBody: true);
TestLambdaBody(
"from x in new[] { 1 } from y in new[] { 2 } select [|y|]", isLambdaBody: true);
TestLambdaBody(
"from x in new[] { 1 } join y in new[] { 2 } on x equals y select [|x|]", isLambdaBody: true);
}
[Fact]
public void IsLambdaBody_GroupBy1()
{
TestLambdaBody(
"from x in new[] { 1 } group [|x|] by x", isLambdaBody: false);
TestLambdaBody(
"from x in new[] { 1 } group x by [|x|]", isLambdaBody: true);
TestLambdaBody(
"from x in new[] { 1 } where x > 0 group [|x|] by x", isLambdaBody: false);
TestLambdaBody(
"from x in new[] { 1 } where x > 0 group x by [|x|]", isLambdaBody: true);
TestLambdaBody(
"from x in new[] { 1 } let y = x group [|x|] by x", isLambdaBody: true);
TestLambdaBody(
"from x in new[] { 1 } group [|x|] by x + 1 into y group y by y.Key + 2", isLambdaBody: false);
TestLambdaBody(
"from x in new[] { 1 } group x by x + 1 into y group [|y|] by y.Key + 2", isLambdaBody: false);
TestLambdaBody(
"from x in new[] { 1 } from y in new[] { 2 } group [|x|] by x", isLambdaBody: true);
TestLambdaBody(
"from x in new[] { 1 } from y in new[] { 2 } group [|y|] by y", isLambdaBody: true);
TestLambdaBody(
"from x in new[] { 1 } join y in new[] { 2 } on x equals y group [|x|] by x", isLambdaBody: true);
}
[Fact]
public void AreEquivalentIgnoringLambdaBodies1()
{
......@@ -49,6 +227,18 @@ public void AreEquivalentIgnoringLambdaBodies1()
SyntaxFactory.ParseExpression("F(from a in new[] { 1, 2 } select a)"),
SyntaxFactory.ParseExpression("F(from a in new[] { 1, 2 } select a + 1)")));
Assert.False(LambdaUtilities.AreEquivalentIgnoringLambdaBodies(
SyntaxFactory.ParseExpression("F(from a in new[] { 1, 2 } where a > 0 select a)"),
SyntaxFactory.ParseExpression("F(from a in new[] { 1, 2 } where a > 0 select a + 1)")));
Assert.False(LambdaUtilities.AreEquivalentIgnoringLambdaBodies(
SyntaxFactory.ParseExpression("F(from a in new[] { 1, 2 } orderby a select a)"),
SyntaxFactory.ParseExpression("F(from a in new[] { 1, 2 } orderby a select a + 1)")));
Assert.True(LambdaUtilities.AreEquivalentIgnoringLambdaBodies(
SyntaxFactory.ParseExpression("F(from a in new[] { 1, 2 } let b = 1 select a)"),
SyntaxFactory.ParseExpression("F(from a in new[] { 1, 2 } let b = 1 select a + 1)")));
Assert.False(LambdaUtilities.AreEquivalentIgnoringLambdaBodies(
SyntaxFactory.ParseExpression("F(from a in new[] { 1, 2 } select a)"),
SyntaxFactory.ParseExpression("F(from a in new[] { 1, 2 } where b > 0 select a)")));
......@@ -69,9 +259,13 @@ public void AreEquivalentIgnoringLambdaBodies1()
SyntaxFactory.ParseExpression("F(from a in new[] { 1, 2 } select a)"),
SyntaxFactory.ParseExpression("F(from a in new[] { 1, 2 } join b in new[] { 3, 4 } on a equals b select a)")));
Assert.False(LambdaUtilities.AreEquivalentIgnoringLambdaBodies(
SyntaxFactory.ParseExpression("F(from a in new[] { 1, 2 } group a by a into g select g)"),
SyntaxFactory.ParseExpression("F(from a in new[] { 1, 2 } group a + 1 by a into g select g)")));
Assert.True(LambdaUtilities.AreEquivalentIgnoringLambdaBodies(
SyntaxFactory.ParseExpression("F(from a in new[] { 1, 2 } group a by a into g select g)"),
SyntaxFactory.ParseExpression("F(from a in new[] { 1, 2 } group a + 1 by a + 2 into g select g)")));
SyntaxFactory.ParseExpression("F(from a in new[] { 1, 2 } group a by a + 1 into g select g)")));
Assert.False(LambdaUtilities.AreEquivalentIgnoringLambdaBodies(
SyntaxFactory.ParseExpression("F(from a in new[] { 1, 2 } group a by a into g select g)"),
......
......@@ -248,7 +248,12 @@ private bool TryGetPreviousLambdaSyntaxOffset(SyntaxNode lambdaOrLambdaBodySynta
SyntaxNode previousSyntax;
if (isLambdaBody)
{
previousSyntax = previousLambdaSyntax.GetCorrespondingLambdaBody(lambdaOrLambdaBodySyntax);
previousSyntax = previousLambdaSyntax.TryGetCorrespondingLambdaBody(lambdaOrLambdaBodySyntax);
if (previousSyntax == null)
{
previousSyntaxOffset = 0;
return false;
}
}
else
{
......
......@@ -563,7 +563,7 @@ public SyntaxReference GetReference()
/// E.g. join clause declares left expression and right expression -- each of these expressions is a lambda body.
/// JoinClause1.GetCorrespondingLambdaBody(JoinClause2.RightExpression) returns JoinClause1.RightExpression.
/// </summary>
internal abstract SyntaxNode GetCorrespondingLambdaBody(SyntaxNode body);
internal abstract SyntaxNode TryGetCorrespondingLambdaBody(SyntaxNode body);
internal abstract SyntaxNode GetLambda();
......
......@@ -677,7 +677,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
Return SyntaxFactory.AreEquivalent(Me, DirectCast(node, VisualBasicSyntaxNode), topLevel)
End Function
Friend Overrides Function GetCorrespondingLambdaBody(body As SyntaxNode) As SyntaxNode
Friend Overrides Function TryGetCorrespondingLambdaBody(body As SyntaxNode) As SyntaxNode
Return LambdaUtilities.GetCorrespondingLambdaBody(body, Me)
End Function
......
......@@ -266,6 +266,7 @@
<Compile Include="EditAndContinue\ActiveStatementTests.cs" />
<Compile Include="EditAndContinue\ActiveStatementTests.Methods.cs" />
<Compile Include="EditAndContinue\ActiveStatementTrackingServiceTests.cs" />
<Compile Include="EditAndContinue\BreakpointSpansTests.cs" />
<Compile Include="EditAndContinue\CSharpEditAndContinueAnalyzerTests.cs" />
<Compile Include="EditAndContinue\Helpers\CSharpEditAndContinueTestHelpers.cs" />
<Compile Include="EditAndContinue\Helpers\Extensions.cs" />
......@@ -673,4 +674,4 @@
<Import Project="..\..\..\build\Roslyn.Toolsets.Xunit.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
</ImportGroup>
</Project>
</Project>
\ No newline at end of file
......@@ -7194,6 +7194,128 @@ static void Main()
Diagnostic(RudeEditKind.ActiveStatementLambdaRemoved, "join", CSharpFeaturesResources.SelectClause));
}
[Fact]
public void Queries_Select_Reduced1()
{
string src1 = @"
class C
{
static void Main()
{
var q = from a in array
where a > 0
select <AS:0>a + 1</AS:0>;
}
}";
string src2 = @"
class C
{
static void Main()
{
var q = from a in array
where a > 0
<AS:0>select</AS:0> a;
}
}";
var edits = GetTopEdits(src1, src2);
var active = GetActiveStatements(src1, src2);
edits.VerifyRudeDiagnostics(active,
Diagnostic(RudeEditKind.ActiveStatementLambdaRemoved, "select", CSharpFeaturesResources.SelectClause ));
}
[Fact]
public void Queries_Select_Reduced2()
{
string src1 = @"
class C
{
static int F(IEnumerbale<int> e) => <AS:0>1</AS:0>;
static void Main()
{
<AS:1>F(from a in array where a > 0 select a + 1);</AS:1>
}
}";
string src2 = @"
class C
{
static int F(IEnumerbale<int> e) => <AS:0>1</AS:0>;
static void Main()
{
<AS:1>F(from a in array where a > 0 select a);</AS:1>
}
}";
var edits = GetTopEdits(src1, src2);
var active = GetActiveStatements(src1, src2);
edits.VerifyRudeDiagnostics(active,
Diagnostic(RudeEditKind.ActiveStatementUpdate, "F(from a in array where a > 0 select a);"));
}
[Fact]
public void Queries_GroupBy_Reduced1()
{
string src1 = @"
class C
{
static void Main()
{
var q = from a in array
group <AS:0>a + 1</AS:0> by a;
}
}";
string src2 = @"
class C
{
static void Main()
{
var q = from a in array
<AS:0>group</AS:0> a by a;
}
}";
var edits = GetTopEdits(src1, src2);
var active = GetActiveStatements(src1, src2);
edits.VerifyRudeDiagnostics(active,
Diagnostic(RudeEditKind.ActiveStatementLambdaRemoved, "group", CSharpFeaturesResources.GroupByClause));
}
[Fact]
public void Queries_GroupBy_Reduced2()
{
string src1 = @"
class C
{
static int F(IEnumerbale<IGrouping<int, int>> e) => <AS:0>1</AS:0>;
static void Main()
{
<AS:1>F(from a in array group a by a);</AS:1>
}
}";
string src2 = @"
class C
{
static int F(IEnumerbale<IGrouping<int, int>> e) => <AS:0>1</AS:0>;
static void Main()
{
<AS:1>F(from a in array group a + 1 by a);</AS:1>
}
}";
var edits = GetTopEdits(src1, src2);
var active = GetActiveStatements(src1, src2);
edits.VerifyRudeDiagnostics(active,
Diagnostic(RudeEditKind.ActiveStatementUpdate, "F(from a in array group a + 1 by a);"));
}
#endregion
#region State Machines
......
......@@ -5435,6 +5435,72 @@ public void Queries_FromSelect_Update3()
"Update [await b]@14 -> [await c]@14");
}
[Fact]
public void Queries_Select_Reduced1()
{
var src1 = @"
using System;
using System.Linq;
using System.Collections.Generic;
class C
{
void F()
{
var result = from a in new[] {1} where a > 0 select a;
}
}
";
var src2 = @"
using System;
using System.Linq;
using System.Collections.Generic;
class C
{
void F()
{
var result = from a in new[] {1} where a > 0 select a + 1;
}
}
";
var edits = GetTopEdits(src1, src2);
edits.VerifySemanticDiagnostics();
}
[Fact]
public void Queries_Select_Reduced2()
{
var src1 = @"
using System;
using System.Linq;
using System.Collections.Generic;
class C
{
void F()
{
var result = from a in new[] {1} where a > 0 select a + 1;
}
}
";
var src2 = @"
using System;
using System.Linq;
using System.Collections.Generic;
class C
{
void F()
{
var result = from a in new[] {1} where a > 0 select a;
}
}
";
var edits = GetTopEdits(src1, src2);
edits.VerifySemanticDiagnostics();
}
[Fact]
public void Queries_FromSelect_Delete()
{
......@@ -5499,6 +5565,132 @@ public void Queries_OrderBy_Reorder()
"Reorder [a.c ascending]@46 -> @30");
}
[Fact]
public void Queries_GroupBy_Reduced1()
{
var src1 = @"
using System;
using System.Linq;
class C
{
void F()
{
var result = from a in new[] {1} group a by a;
}
}
";
var src2 = @"
using System;
using System.Linq;
class C
{
void F()
{
var result = from a in new[] {1} group a + 1.0 by a;
}
}
";
var edits = GetTopEdits(src1, src2);
edits.VerifySemanticDiagnostics(
Diagnostic(RudeEditKind.ChangingQueryLambdaType, "group", CSharpFeaturesResources.GroupByClause));
}
[Fact]
public void Queries_GroupBy_Reduced2()
{
var src1 = @"
using System;
using System.Linq;
class C
{
void F()
{
var result = from a in new[] {1} group a by a;
}
}
";
var src2 = @"
using System;
using System.Linq;
class C
{
void F()
{
var result = from a in new[] {1} group a + 1 by a;
}
}
";
var edits = GetTopEdits(src1, src2);
edits.VerifySemanticDiagnostics();
}
[Fact]
public void Queries_GroupBy_Reduced3()
{
var src1 = @"
using System;
using System.Linq;
class C
{
void F()
{
var result = from a in new[] {1} group a + 1.0 by a;
}
}
";
var src2 = @"
using System;
using System.Linq;
class C
{
void F()
{
var result = from a in new[] {1} group a by a;
}
}
";
var edits = GetTopEdits(src1, src2);
edits.VerifySemanticDiagnostics(
Diagnostic(RudeEditKind.ChangingQueryLambdaType, "group", CSharpFeaturesResources.GroupByClause));
}
[Fact]
public void Queries_GroupBy_Reduced4()
{
var src1 = @"
using System;
using System.Linq;
class C
{
void F()
{
var result = from a in new[] {1} group a + 1 by a;
}
}
";
var src2 = @"
using System;
using System.Linq;
class C
{
void F()
{
var result = from a in new[] {1} group a by a;
}
}
";
var edits = GetTopEdits(src1, src2);
edits.VerifySemanticDiagnostics();
}
[Fact]
public void Queries_OrderBy_Continuation_Update()
{
......@@ -5803,8 +5995,7 @@ void F()
}
";
var edits = GetTopEdits(src1, src2);
edits.VerifySemanticDiagnostics(
Diagnostic(RudeEditKind.ChangingQueryLambdaType, "select", CSharpFeaturesResources.SelectClause));
edits.VerifySemanticDiagnostics();
}
[Fact]
......
......@@ -239,6 +239,7 @@
<Compile Include="DocumentationComments\XmlTagCompletionTests.vb" />
<Compile Include="EditAndContinue\ActiveStatementTests.vb" />
<Compile Include="EditAndContinue\ActiveStatementTrackingServiceTests.vb" />
<Compile Include="EditAndContinue\BreakpointSpansTests.vb" />
<Compile Include="EditAndContinue\SyntaxComparerTests.vb" />
<Compile Include="EditAndContinue\Helpers\Extensions.vb" />
<Compile Include="EditAndContinue\Helpers\RudeEditTestBase.vb" />
......@@ -641,4 +642,4 @@
<Import Project="..\..\..\build\Roslyn.Toolsets.Xunit.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
</ImportGroup>
</Project>
</Project>
\ No newline at end of file
......@@ -4,18 +4,11 @@ Imports System.Threading
Imports System.Xml.Linq
Imports Microsoft.CodeAnalysis
Imports Microsoft.CodeAnalysis.Editor.UnitTests.Extensions
Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces
Imports Microsoft.CodeAnalysis.Text
Imports Microsoft.CodeAnalysis.VisualBasic
Imports Microsoft.CodeAnalysis.VisualBasic.Extensions
Imports Microsoft.CodeAnalysis.Editor.Implementation.Debugging
Imports Microsoft.VisualStudio.LanguageServices.VisualBasic.Debugging
Imports Roslyn.Test.Utilities
Imports Roslyn.Utilities
Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.UnitTests.Debugging
Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue.UnitTests
<Trait(Traits.Feature, Traits.Features.DebuggingBreakpoints)>
Public Class BreakpointLocationValidatorTests
Public Class BreakpointSpansTests
#Region "Helpers"
......@@ -48,7 +41,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.UnitTests.Debuggin
Dim tree = SyntaxFactory.ParseSyntaxTree(source)
Dim breakpointSpan As TextSpan
Dim hasBreakpoint = BreakpointGetter.TryGetBreakpointSpan(tree,
Dim hasBreakpoint = BreakpointSpans.TryGetBreakpointSpan(tree,
position.Value,
CancellationToken.None,
breakpointSpan)
......@@ -69,6 +62,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.UnitTests.Debuggin
End If
End Sub
Private Sub TestAll(markup As String)
Dim position As Integer = Nothing
Dim expectedSpans As IList(Of TextSpan) = Nothing
......@@ -91,7 +85,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.UnitTests.Debuggin
Dim lastSpanEnd = 0
While position < endPosition
Dim span As TextSpan = Nothing
If root.TryGetEnclosingBreakpointSpan(position, span) AndAlso span.End > lastSpanEnd Then
If BreakpointSpans.TryGetEnclosingBreakpointSpan(root, position, span) AndAlso span.End > lastSpanEnd Then
position = span.End
lastSpanEnd = span.End
Yield span
......@@ -3115,48 +3109,5 @@ End Class
End Sub
#End Region
Public Sub TestSpanWithLength(markup As XElement, length As Integer)
Dim position As Integer? = Nothing
Dim expectedSpan As TextSpan? = Nothing
Dim source As String = Nothing
MarkupTestFile.GetPositionAndSpan(markup.NormalizedValue, source, position, expectedSpan)
Using workspace = VisualBasicWorkspaceFactory.CreateWorkspaceFromLines(source)
Dim document = workspace.CurrentSolution.Projects.First.Documents.First
Dim result As BreakpointResolutionResult = BreakpointGetter.GetBreakpointAsync(document, position.Value, length, CancellationToken.None).WaitAndGetResult(CancellationToken.None)
Assert.True(expectedSpan.Value = result.TextSpan,
String.Format(vbCrLf & "Expected: {0} ""{1}""" & vbCrLf & "Actual: {2} ""{3}""",
expectedSpan.Value,
source.Substring(expectedSpan.Value.Start, expectedSpan.Value.Length),
result.TextSpan,
source.Substring(result.TextSpan.Start, result.TextSpan.Length)))
End Using
End Sub
<WorkItem(876520)>
<Fact>
Public Sub TestBreakpointSpansMultipleMethods()
' Normal case: debugger passing BP spans "sub Foo() end sub"
TestSpanWithLength(<text>
class C
[|$$sub Foo()|]
end sub
sub Bar()
end sub
end class</text>, 20)
' Rare case: debugger passing BP spans "sub Foo() end sub sub Bar() end sub"
TestSpanWithLength(<text>
class C
$$sub Foo()
end sub
[|sub Bar()|]
end sub
end class</text>, 35)
End Sub
End Class
End Namespace
......@@ -77,12 +77,13 @@
<ItemGroup>
<InternalsVisibleTo Include="Microsoft.CodeAnalysis.CSharp.EditorFeatures" />
<InternalsVisibleTo Include="Microsoft.VisualStudio.LanguageServices.CSharp" />
<InternalsVisibleTo Include="Roslyn.CSharp.InteractiveEditorFeatures" />
<InternalsVisibleTo Include="Roslyn.CSharp.InteractiveEditorFeatures" />
<InternalsVisibleToTest Include="Roslyn.Services.Editor.CSharp.UnitTests" />
<InternalsVisibleToTest Include="Roslyn.Services.Editor.UnitTests" />
<InternalsVisibleToTest Include="Roslyn.Services.Editor.UnitTests2" />
<InternalsVisibleToTest Include="Roslyn.Services.Editor.VisualBasic.UnitTests" />
<InternalsVisibleToTest Include="Roslyn.Services.UnitTests" />
<InternalsVisibleToTest Include="Roslyn.VisualStudio.CSharp.UnitTests" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\..\Compilers\CSharp\Portable\Syntax\LambdaUtilities.cs">
......@@ -293,6 +294,7 @@
<Compile Include="Diagnostics\CSharpSyntaxNodeAnalyzerService.cs" />
<Compile Include="DocumentationCommentFormatting\CSharpDocumentationCommentFormattingService.cs" />
<Compile Include="DocumentationCommentFormatting\DocumentationCommentUtilities.cs" />
<Compile Include="EditAndContinue\BreakpointSpans.cs" />
<Compile Include="EditAndContinue\CSharpEditAndContinueAnalyzer.cs" />
<Compile Include="EditAndContinue\StatementSyntaxComparer.cs" />
<Compile Include="EditAndContinue\SyntaxComparer.cs" />
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.CSharp.Extensions;
namespace Microsoft.CodeAnalysis.CSharp.Extensions
namespace Microsoft.CodeAnalysis.CSharp.EditAndContinue
{
// TODO (tomat): Move to the compiler. Bug #764678.
internal static class BreakpointSpans
{
public static bool TryGetBreakpointSpan(SyntaxTree tree, int position, CancellationToken cancellationToken, out TextSpan breakpointSpan)
{
var source = tree.GetText(cancellationToken);
// If the line is entirely whitespace, then don't set any breakpoint there.
var line = source.Lines.GetLineFromPosition(position);
if (IsBlank(line))
{
breakpointSpan = default(TextSpan);
return false;
}
// If the user is asking for breakpoint in an inactive region, then just create a line
// breakpoint there.
if (tree.IsInInactiveRegion(position, cancellationToken))
{
breakpointSpan = default(TextSpan);
return true;
}
var root = tree.GetRoot(cancellationToken);
return TryGetClosestBreakpointSpan(root, position, out breakpointSpan);
}
private static bool IsBlank(TextLine line)
{
var text = line.ToString();
for (int i = 0; i < text.Length; i++)
{
if (!SyntaxFacts.IsWhitespace(text[i]))
{
return false;
}
}
return true;
}
/// <summary>
/// Given a syntax token determines a text span delimited by the closest applicable sequence points
/// encompassing the token.
......@@ -18,7 +57,7 @@ internal static class BreakpointSpans
/// <remarks>
/// If the span exists it is possible to place a breakpoint at the given position.
/// </remarks>
public static bool TryGetClosestBreakpointSpan(this SyntaxNode root, int position, out TextSpan span)
public static bool TryGetClosestBreakpointSpan(SyntaxNode root, int position, out TextSpan span)
{
SyntaxNode node = root.FindToken(position).Parent;
while (node != null)
......@@ -657,11 +696,6 @@ private static bool IsBreakableExpression(ExpressionSyntax expression)
Debug.Assert(((ArrowExpressionClauseSyntax)parent).Expression == expression);
return true;
case SyntaxKind.SimpleLambdaExpression:
case SyntaxKind.ParenthesizedLambdaExpression:
Debug.Assert(((AnonymousFunctionExpressionSyntax)parent).Body == expression);
return true;
case SyntaxKind.ForStatement:
var forStatement = (ForStatementSyntax)parent;
return
......@@ -673,51 +707,9 @@ private static bool IsBreakableExpression(ExpressionSyntax expression)
var forEachStatement = (ForEachStatementSyntax)parent;
return forEachStatement.Expression == expression;
// Query clauses
case SyntaxKind.FromClause:
var fromClause = (FromClauseSyntax)parent;
// We can break on the expression in a from clause, except for the first clause in a
// query. For example:
// from c in LookupCustomers() // not here
// from o in LookupBarOrders() + LookupBazOrders() // but here
// group ... into y
// from d in SomeOtherExpression() // and after a continuation, too
return fromClause.Expression == expression && fromClause.Parent is QueryBodySyntax;
case SyntaxKind.JoinClause:
var joinClause = (JoinClauseSyntax)parent;
// We can break on the inner and outer key expressions, but not the
// initializer expression. For example:
//
// join a in alpha /* no */ on beta /* yes */ equals gamma /* yes */
return joinClause.LeftExpression == expression || joinClause.RightExpression == expression;
case SyntaxKind.LetClause:
var letClause = (LetClauseSyntax)parent;
return letClause.Expression == expression;
case SyntaxKind.WhereClause:
var whereClause = (WhereClauseSyntax)parent;
return whereClause.Condition == expression;
case SyntaxKind.AscendingOrdering:
case SyntaxKind.DescendingOrdering:
var ordering = (OrderingSyntax)parent;
return ordering.Expression == expression;
case SyntaxKind.SelectClause:
var selectClause = (SelectClauseSyntax)parent;
return selectClause.Expression == expression;
case SyntaxKind.GroupClause:
var groupClause = (GroupClauseSyntax)parent;
return groupClause.GroupExpression == expression || groupClause.ByExpression == expression;
default:
return LambdaUtilities.IsLambdaBodyStatementOrExpression(expression);
}
return false;
}
private static TextSpan? CreateSpanForAccessors(SyntaxList<AccessorDeclarationSyntax> accessors, int position)
......
......@@ -472,9 +472,9 @@ protected override IEnumerable<SyntaxNode> GetLambdaBodyExpressionsAndStatements
return SpecializedCollections.SingletonEnumerable(lambdaBody);
}
protected override SyntaxNode GetPartnerLambdaBody(SyntaxNode oldBody, SyntaxNode newLambda)
protected override SyntaxNode TryGetPartnerLambdaBody(SyntaxNode oldBody, SyntaxNode newLambda)
{
return LambdaUtilities.GetCorrespondingLambdaBody(oldBody, newLambda);
return LambdaUtilities.TryGetCorrespondingLambdaBody(oldBody, newLambda);
}
protected override Match<SyntaxNode> ComputeTopLevelMatch(SyntaxNode oldCompilationUnit, SyntaxNode newCompilationUnit)
......@@ -587,7 +587,7 @@ protected override bool StatementLabelEquals(SyntaxNode node1, SyntaxNode node2)
protected override bool TryGetEnclosingBreakpointSpan(SyntaxNode root, int position, out TextSpan span)
{
return root.TryGetClosestBreakpointSpan(position, out span);
return BreakpointSpans.TryGetClosestBreakpointSpan(root, position, out span);
}
protected override bool TryGetActiveSpan(SyntaxNode node, int statementPart, out TextSpan span)
......@@ -607,7 +607,7 @@ protected override bool TryGetActiveSpan(SyntaxNode node, int statementPart, out
// which is lexically not the closest breakpoint span (the body is).
// do { ... } [|while (condition);|]
var doStatement = (DoStatementSyntax)node;
return node.TryGetClosestBreakpointSpan(doStatement.WhileKeyword.SpanStart, out span);
return BreakpointSpans.TryGetClosestBreakpointSpan(node, doStatement.WhileKeyword.SpanStart, out span);
case SyntaxKind.PropertyDeclaration:
// The active span corresponding to a property declaration is the span corresponding to its initializer (if any),
......@@ -616,7 +616,7 @@ protected override bool TryGetActiveSpan(SyntaxNode node, int statementPart, out
var propertyDeclaration = (PropertyDeclarationSyntax)node;
if (propertyDeclaration.Initializer != null &&
node.TryGetClosestBreakpointSpan(propertyDeclaration.Initializer.SpanStart, out span))
BreakpointSpans.TryGetClosestBreakpointSpan(node, propertyDeclaration.Initializer.SpanStart, out span))
{
return true;
}
......@@ -627,7 +627,7 @@ protected override bool TryGetActiveSpan(SyntaxNode node, int statementPart, out
}
default:
return node.TryGetClosestBreakpointSpan(node.SpanStart, out span);
return BreakpointSpans.TryGetClosestBreakpointSpan(node, node.SpanStart, out span);
}
}
......@@ -986,18 +986,71 @@ internal override bool QueryClauseLambdasTypeEquivalent(SemanticModel oldModel,
case SyntaxKind.AscendingOrdering:
case SyntaxKind.DescendingOrdering:
var oldOrderingInfo = oldModel.GetSymbolInfo(oldNode, cancellationToken);
var newOrderingInfo = newModel.GetSymbolInfo(newNode, cancellationToken);
return MemberSignaturesEquivalent(oldOrderingInfo.Symbol, newOrderingInfo.Symbol);
case SyntaxKind.SelectClause:
case SyntaxKind.GroupClause:
var oldSymbolInfo = oldModel.GetSymbolInfo(oldNode, cancellationToken);
var newSymbolInfo = newModel.GetSymbolInfo(newNode, cancellationToken);
var oldSelectInfo = oldModel.GetSymbolInfo(oldNode, cancellationToken);
var newSelectInfo = newModel.GetSymbolInfo(newNode, cancellationToken);
// Changing reduced select clause to a non-reduced form or vice versa
// adds/removes a call to Select method, which is a supported change.
return MemberSignaturesEquivalent(oldSymbolInfo.Symbol, newSymbolInfo.Symbol);
return oldSelectInfo.Symbol == null ||
newSelectInfo.Symbol == null ||
MemberSignaturesEquivalent(oldSelectInfo.Symbol, newSelectInfo.Symbol);
case SyntaxKind.GroupClause:
var oldGroupByInfo = oldModel.GetSymbolInfo(oldNode, cancellationToken);
var newGroupByInfo = newModel.GetSymbolInfo(newNode, cancellationToken);
return MemberSignaturesEquivalent(oldGroupByInfo.Symbol, newGroupByInfo.Symbol, GroupBySignatureComparer);
default:
return true;
}
}
private static bool GroupBySignatureComparer(ImmutableArray<IParameterSymbol> oldParameters, ITypeSymbol oldReturnType, ImmutableArray<IParameterSymbol> newParameters, ITypeSymbol newReturnType)
{
// C# spec paragraph 7.16.2.6 "Groupby clauses":
//
// A query expression of the form
// from x in e group v by k
// is translated into
// (e).GroupBy(x => k, x => v)
// except when v is the identifier x, the translation is
// (e).GroupBy(x => k)
//
// Possible signatures:
// C<G<K, T>> GroupBy<K>(Func<T, K> keySelector);
// C<G<K, E>> GroupBy<K, E>(Func<T, K> keySelector, Func<T, E> elementSelector);
if (!s_assemblyEqualityComparer.Equals(oldReturnType, newReturnType))
{
return false;
}
Debug.Assert(oldParameters.Length == 1 || oldParameters.Length == 2);
Debug.Assert(newParameters.Length == 1 || newParameters.Length == 2);
// The types of the lambdas have to be the same if present.
// The element selector may be added/removed.
if (!s_assemblyEqualityComparer.ParameterEquivalenceComparer.Equals(oldParameters[0], newParameters[0]))
{
return false;
}
if (oldParameters.Length == newParameters.Length && newParameters.Length == 2)
{
return s_assemblyEqualityComparer.ParameterEquivalenceComparer.Equals(oldParameters[1], newParameters[1]);
}
return true;
}
#endregion
#region Diagnostic Info
......
......@@ -156,7 +156,7 @@ private SyntaxNode FindStatement(SyntaxNode declarationBody, int position, out i
/// </remarks>
protected abstract IEnumerable<SyntaxNode> GetLambdaBodyExpressionsAndStatements(SyntaxNode lambdaBody);
protected abstract SyntaxNode GetPartnerLambdaBody(SyntaxNode oldBody, SyntaxNode newLambda);
protected abstract SyntaxNode TryGetPartnerLambdaBody(SyntaxNode oldBody, SyntaxNode newLambda);
protected abstract Match<SyntaxNode> ComputeTopLevelMatch(SyntaxNode oldCompilationUnit, SyntaxNode newCompilationUnit);
protected abstract Match<SyntaxNode> ComputeBodyMatch(SyntaxNode oldBody, SyntaxNode newBody, IEnumerable<KeyValuePair<SyntaxNode, SyntaxNode>> knownMatches);
......@@ -1130,8 +1130,6 @@ internal struct UpdatedMemberInfo
if (TryGetLambdaBodies(oldLambda, out oldLambdaBody1, out oldLambdaBody2))
{
Debug.Assert(IsLambda(newLambda));
if (lambdaBodyMatches == null)
{
lambdaBodyMatches = ArrayBuilder<Match<SyntaxNode>>.GetInstance();
......@@ -1142,11 +1140,19 @@ internal struct UpdatedMemberInfo
lazyActiveOrMatchedLambdas = new Dictionary<SyntaxNode, LambdaInfo>();
}
lambdaBodyMatches.Add(ComputeLambdaBodyMatch(oldLambdaBody1, newLambda, activeNodes, lazyActiveOrMatchedLambdas, diagnostics));
SyntaxNode newLambdaBody1 = TryGetPartnerLambdaBody(oldLambdaBody1, newLambda);
if (newLambdaBody1 != null)
{
lambdaBodyMatches.Add(ComputeLambdaBodyMatch(oldLambdaBody1, newLambdaBody1, activeNodes, lazyActiveOrMatchedLambdas, diagnostics));
}
if (oldLambdaBody2 != null)
{
lambdaBodyMatches.Add(ComputeLambdaBodyMatch(oldLambdaBody2, newLambda, activeNodes, lazyActiveOrMatchedLambdas, diagnostics));
SyntaxNode newLambdaBody2 = TryGetPartnerLambdaBody(oldLambdaBody2, newLambda);
if (newLambdaBody2 != null)
{
lambdaBodyMatches.Add(ComputeLambdaBodyMatch(oldLambdaBody2, newLambdaBody2, activeNodes, lazyActiveOrMatchedLambdas, diagnostics));
}
}
}
}
......@@ -1194,16 +1200,14 @@ internal struct UpdatedMemberInfo
private Match<SyntaxNode> ComputeLambdaBodyMatch(
SyntaxNode oldLambdaBody,
SyntaxNode newLambda,
SyntaxNode newLambdaBody,
ActiveNode[] activeNodes,
[Out]Dictionary<SyntaxNode, LambdaInfo> lazyActiveOrMatchedLambdas,
[Out]Dictionary<SyntaxNode, LambdaInfo> activeOrMatchedLambdas,
[Out]List<RudeEditDiagnostic> diagnostics)
{
SyntaxNode newLambdaBody = GetPartnerLambdaBody(oldLambdaBody, newLambda);
ActiveNode[] activeNodesInLambda;
LambdaInfo info;
if (lazyActiveOrMatchedLambdas.TryGetValue(oldLambdaBody, out info))
if (activeOrMatchedLambdas.TryGetValue(oldLambdaBody, out info))
{
// Lambda may be matched but not be active.
activeNodesInLambda = info.ActiveNodeIndices?.Select(i => activeNodes[i]).ToArray();
......@@ -1218,7 +1222,7 @@ internal struct UpdatedMemberInfo
bool needsSyntaxMap;
var lambdaBodyMatch = ComputeBodyMatch(oldLambdaBody, newLambdaBody, activeNodesInLambda ?? SpecializedCollections.EmptyArray<ActiveNode>(), diagnostics, out needsSyntaxMap);
lazyActiveOrMatchedLambdas[oldLambdaBody] = info.WithMatch(lambdaBodyMatch, newLambdaBody);
activeOrMatchedLambdas[oldLambdaBody] = info.WithMatch(lambdaBodyMatch, newLambdaBody);
return lambdaBodyMatch;
}
......@@ -1948,19 +1952,16 @@ public int GetHashCode(IAssemblySymbol obj)
protected static readonly SymbolEquivalenceComparer s_assemblyEqualityComparer = new SymbolEquivalenceComparer(AssemblyEqualityComparer.Instance);
private static bool MethodSignaturesEquivalent(IMethodSymbol oldMethod, IMethodSymbol newMethod)
protected static bool SignaturesEquivalent(ImmutableArray<IParameterSymbol> oldParameters, ITypeSymbol oldReturnType, ImmutableArray<IParameterSymbol> newParameters, ITypeSymbol newReturnType)
{
return oldMethod.Parameters.SequenceEqual(newMethod.Parameters, s_assemblyEqualityComparer.ParameterEquivalenceComparer) &&
s_assemblyEqualityComparer.ReturnTypeEquals(oldMethod, newMethod);
return oldParameters.SequenceEqual(newParameters, s_assemblyEqualityComparer.ParameterEquivalenceComparer) &&
s_assemblyEqualityComparer.Equals(oldReturnType, newReturnType);
}
private static bool PropertySignaturesEquivalent(IPropertySymbol oldProperty, IPropertySymbol newProperty)
{
return oldProperty.Parameters.SequenceEqual(newProperty.Parameters, s_assemblyEqualityComparer.ParameterEquivalenceComparer) &&
s_assemblyEqualityComparer.Equals(oldProperty.Type, newProperty.Type);
}
protected static bool MemberSignaturesEquivalent(ISymbol oldMemberOpt, ISymbol newMemberOpt)
protected static bool MemberSignaturesEquivalent(
ISymbol oldMemberOpt,
ISymbol newMemberOpt,
Func<ImmutableArray<IParameterSymbol>, ITypeSymbol, ImmutableArray<IParameterSymbol>, ITypeSymbol, bool> signatureComparer = null)
{
if (oldMemberOpt == newMemberOpt)
{
......@@ -1972,16 +1973,27 @@ protected static bool MemberSignaturesEquivalent(ISymbol oldMemberOpt, ISymbol n
return false;
}
if (signatureComparer == null)
{
signatureComparer = SignaturesEquivalent;
}
switch (oldMemberOpt.Kind)
{
case SymbolKind.Field:
return s_assemblyEqualityComparer.Equals(((IFieldSymbol)oldMemberOpt).Type, ((IFieldSymbol)newMemberOpt).Type);
var oldField = (IFieldSymbol)oldMemberOpt;
var newField = (IFieldSymbol)newMemberOpt;
return signatureComparer(ImmutableArray<IParameterSymbol>.Empty, oldField.Type, ImmutableArray<IParameterSymbol>.Empty, newField.Type);
case SymbolKind.Property:
return PropertySignaturesEquivalent((IPropertySymbol)oldMemberOpt, (IPropertySymbol)newMemberOpt);
var oldProperty = (IPropertySymbol)oldMemberOpt;
var newProperty = (IPropertySymbol)newMemberOpt;
return signatureComparer(oldProperty.Parameters, oldProperty.Type, newProperty.Parameters, newProperty.Type);
case SymbolKind.Method:
return MethodSignaturesEquivalent((IMethodSymbol)oldMemberOpt, (IMethodSymbol)newMemberOpt);
var oldMethod = (IMethodSymbol)oldMemberOpt;
var newMethod = (IMethodSymbol)newMemberOpt;
return signatureComparer(oldMethod.Parameters, oldMethod.ReturnType, newMethod.Parameters, newMethod.ReturnType);
default:
throw ExceptionUtilities.UnexpectedValue(oldMemberOpt.Kind);
......
......@@ -343,6 +343,7 @@
<Compile Include="Diagnostics\VisualBasicSyntaxNodeAnalyzerService.vb" />
<Compile Include="DocumentationCommentFormatting\DocumentationCommentUtilities.vb" />
<Compile Include="DocumentationCommentFormatting\VisualBasicDocumentationCommentFormattingService.vb" />
<Compile Include="EditAndContinue\BreakpointSpans.vb" />
<Compile Include="EditAndContinue\StatementSyntaxComparer.vb" />
<Compile Include="EditAndContinue\SyntaxComparer.vb" />
<Compile Include="EditAndContinue\SyntaxUtilities.vb" />
......
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Imports System
Imports System.Linq
Imports System.Runtime.InteropServices
Imports System.Threading
Imports Microsoft.CodeAnalysis.VisualBasic
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Imports Microsoft.CodeAnalysis.Text
Imports System.Runtime.CompilerServices
Imports System.Threading
Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions
' TODO (tomat): move to the compiler. Bug #764678.
Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue
Friend Module BreakpointSpans
Friend Function TryGetBreakpointSpan(tree As SyntaxTree, position As Integer, cancellationToken As CancellationToken, <Out> ByRef breakpointSpan As TextSpan) As Boolean
Dim source = tree.GetText(cancellationToken)
' If the line is entirely whitespace, then don't set any breakpoint there.
Dim line = source.Lines.GetLineFromPosition(position)
If IsBlank(line) Then
breakpointSpan = Nothing
Return False
End If
' If the user is asking for breakpoint in an inactive region, then just create a line
' breakpoint there.
If tree.IsInInactiveRegion(position, cancellationToken) Then
breakpointSpan = Nothing
Return True
End If
Dim root = tree.GetRoot(cancellationToken)
Return TryGetEnclosingBreakpointSpan(root, position, breakpointSpan)
End Function
Private Function IsBlank(line As TextLine) As Boolean
Dim text = line.ToString()
For i = 0 To text.Length - 1
If Not SyntaxFacts.IsWhitespace(text(i)) Then
Return False
End If
Next
Return True
End Function
''' <summary>
''' Given a syntax token determines a text span delimited by the closest applicable sequence points
''' encompassing the token.
......@@ -19,7 +48,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions
''' <remarks>
''' If the span exists it Is possible To place a breakpoint at the given position.
''' </remarks>
<Extension>
Public Function TryGetEnclosingBreakpointSpan(root As SyntaxNode, position As Integer, <Out> ByRef span As TextSpan) As Boolean
Dim node = root.FindToken(position).Parent
......
......@@ -555,7 +555,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue
Return Nothing
End Function
Protected Overrides Function GetPartnerLambdaBody(oldBody As SyntaxNode, newLambda As SyntaxNode) As SyntaxNode
Protected Overrides Function TryGetPartnerLambdaBody(oldBody As SyntaxNode, newLambda As SyntaxNode) As SyntaxNode
Return LambdaUtilities.GetCorrespondingLambdaBody(oldBody, newLambda)
End Function
......@@ -726,11 +726,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue
End Function
Protected Overrides Function TryGetEnclosingBreakpointSpan(root As SyntaxNode, position As Integer, <Out> ByRef span As TextSpan) As Boolean
Return root.TryGetEnclosingBreakpointSpan(position, span)
Return BreakpointSpans.TryGetEnclosingBreakpointSpan(root, position, span)
End Function
Protected Overrides Function TryGetActiveSpan(node As SyntaxNode, statementPart As Integer, <Out> ByRef span As TextSpan) As Boolean
Return node.TryGetEnclosingBreakpointSpan(node.SpanStart, span)
Return BreakpointSpans.TryGetEnclosingBreakpointSpan(node, node.SpanStart, span)
End Function
Protected Overrides Iterator Function EnumerateNearStatements(statement As SyntaxNode) As IEnumerable(Of KeyValuePair(Of SyntaxNode, Integer))
......
......@@ -180,7 +180,6 @@
<Compile Include="CodeModel\ParameterFlags.cs" />
<Compile Include="CodeModel\ParameterFlagsExtensions.cs" />
<Compile Include="CodeModel\SyntaxListExtensions.cs" />
<Compile Include="Debugging\BreakpointGetter.cs" />
<Compile Include="Debugging\BreakpointResolver.cs" />
<Compile Include="Debugging\CSharpBreakpointResolutionService.cs" />
<Compile Include="Debugging\CSharpLanguageDebugInfoService.cs" />
......
// 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.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.Editor.Implementation.Debugging;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.VisualStudio.LanguageServices.CSharp.Debugging
{
internal static class BreakpointGetter
{
internal static async Task<BreakpointResolutionResult> GetBreakpointAsync(Document document, int position, CancellationToken cancellationToken)
{
var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
TextSpan span;
if (!TryGetBreakpointSpan(tree, position, cancellationToken, out span))
{
return null;
}
if (span.Length == 0)
{
return BreakpointResolutionResult.CreateLineResult(document);
}
return BreakpointResolutionResult.CreateSpanResult(document, span);
}
internal static bool TryGetBreakpointSpan(SyntaxTree tree, int position, CancellationToken cancellationToken, out TextSpan breakpointSpan)
{
var source = tree.GetText(cancellationToken);
// If the line is entirely whitespace, then don't set any breakpoint there.
var line = source.Lines.GetLineFromPosition(position);
if (IsBlank(line))
{
breakpointSpan = default(TextSpan);
return false;
}
// If the user is asking for breakpoint in an inactive region, then just create a line
// breakpoint there.
if (tree.IsInInactiveRegion(position, cancellationToken))
{
breakpointSpan = default(TextSpan);
return true;
}
var root = tree.GetRoot(cancellationToken);
return root.TryGetClosestBreakpointSpan(position, out breakpointSpan);
}
private static bool IsBlank(TextLine line)
{
var text = line.ToString();
for (int i = 0; i < text.Length; i++)
{
if (!SyntaxFacts.IsWhitespace(text[i]))
{
return false;
}
}
return true;
}
}
}
......@@ -5,6 +5,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.EditAndContinue;
using Microsoft.CodeAnalysis.Editor.Implementation.Debugging;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Text;
......@@ -14,10 +15,28 @@ namespace Microsoft.VisualStudio.LanguageServices.CSharp.Debugging
[ExportLanguageService(typeof(IBreakpointResolutionService), LanguageNames.CSharp), Shared]
internal partial class CSharpBreakpointResolutionService : IBreakpointResolutionService
{
internal static async Task<BreakpointResolutionResult> GetBreakpointAsync(Document document, int position, CancellationToken cancellationToken)
{
var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
TextSpan span;
if (!BreakpointSpans.TryGetBreakpointSpan(tree, position, cancellationToken, out span))
{
return null;
}
if (span.Length == 0)
{
return BreakpointResolutionResult.CreateLineResult(document);
}
return BreakpointResolutionResult.CreateSpanResult(document, span);
}
public Task<BreakpointResolutionResult> ResolveBreakpointAsync(
Document document, TextSpan textSpan, CancellationToken cancellationToken)
{
return BreakpointGetter.GetBreakpointAsync(document, textSpan.Start, cancellationToken);
return GetBreakpointAsync(document, textSpan.Start, cancellationToken);
}
public Task<IEnumerable<BreakpointResolutionResult>> ResolveBreakpointsAsync(Solution solution, string name, CancellationToken cancellationToken)
......
......@@ -160,7 +160,6 @@
<Compile Include="CodeModel\MockTextManagerAdapter.cs" />
<Compile Include="CodeModel\MockTextManagerAdapter.TextPoint.cs" />
<Compile Include="CodeModel\VisualStudioTestExportProvider.cs" />
<Compile Include="Debugging\BreakpointLocationValidatorTests.cs" />
<Compile Include="Debugging\DataTipInfoGetterTests.cs" />
<Compile Include="Debugging\LocationInfoGetterTests.cs" />
<Compile Include="Debugging\NameResolverTests.cs" />
......
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Imports System.Threading
Imports System.Xml.Linq
Imports Microsoft.CodeAnalysis
Imports Microsoft.CodeAnalysis.Editor.Implementation.Debugging
Imports Microsoft.CodeAnalysis.Editor.UnitTests.Extensions
Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces
Imports Microsoft.CodeAnalysis.Text
Imports Microsoft.VisualStudio.LanguageServices.VisualBasic.Debugging
Imports Roslyn.Test.Utilities
Imports Roslyn.Utilities
Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.UnitTests.Debugging
Public Class VisualBasicBreakpointResolutionServiceTests
Public Sub TestSpanWithLength(markup As XElement, length As Integer)
Dim position As Integer? = Nothing
Dim expectedSpan As TextSpan? = Nothing
Dim source As String = Nothing
MarkupTestFile.GetPositionAndSpan(markup.NormalizedValue, source, position, expectedSpan)
Using workspace = VisualBasicWorkspaceFactory.CreateWorkspaceFromLines(source)
Dim document = workspace.CurrentSolution.Projects.First.Documents.First
Dim result As BreakpointResolutionResult = VisualBasicBreakpointResolutionService.GetBreakpointAsync(document, position.Value, length, CancellationToken.None).WaitAndGetResult(CancellationToken.None)
Assert.True(expectedSpan.Value = result.TextSpan,
String.Format(vbCrLf & "Expected: {0} ""{1}""" & vbCrLf & "Actual: {2} ""{3}""",
expectedSpan.Value,
source.Substring(expectedSpan.Value.Start, expectedSpan.Value.Length),
result.TextSpan,
source.Substring(result.TextSpan.Start, result.TextSpan.Length)))
End Using
End Sub
<WorkItem(876520)>
<Fact>
Public Sub TestBreakpointSpansMultipleMethods()
' Normal case: debugger passing BP spans "sub Foo() end sub"
TestSpanWithLength(<text>
class C
[|$$sub Foo()|]
end sub
sub Bar()
end sub
end class</text>, 20)
' Rare case: debugger passing BP spans "sub Foo() end sub sub Bar() end sub"
TestSpanWithLength(<text>
class C
$$sub Foo()
end sub
[|sub Bar()|]
end sub
end class</text>, 35)
End Sub
End Class
End Namespace
......@@ -313,7 +313,6 @@
<Compile Include="DebuggerIntelliSense\SignatureHelpWaiter.vb" />
<Compile Include="DebuggerIntelliSense\TestState.vb" />
<Compile Include="DebuggerIntelliSense\VisualBasicDebuggerIntelliSenseTests.vb" />
<Compile Include="Debugging\BreakpointLocationValidatorTests.vb" />
<Compile Include="Debugging\DataTipInfoGetterTests.vb" />
<Compile Include="Debugging\LocationInfoGetterTests.vb" />
<Compile Include="Debugging\NameResolverTests.vb" />
......@@ -325,6 +324,7 @@
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Include="Debugging\VisualBasicBreakpointResolutionServiceTests.vb" />
<Compile Include="Diagnostics\DiagnosticTableDataSourceTests.vb" />
<Compile Include="Diagnostics\ExternalDiagnosticUpdateSourceTests.vb" />
<Compile Include="Diagnostics\MiscDiagnosticUpdateSourceTests.vb" />
......
......@@ -107,7 +107,6 @@
<Compile Include="CodeModel\VisualBasicCodeModelService_Prototype.vb" />
<Compile Include="CodeModel\VisualBasicCodeModelServiceFactory.vb" />
<Compile Include="CodeModel\VisualBasicProjectCodeModel.vb" />
<Compile Include="Debugging\BreakpointGetter.vb" />
<Compile Include="Debugging\BreakpointResolver.vb" />
<Compile Include="Debugging\DataTipInfoGetter.vb" />
<Compile Include="Debugging\LocationInfoGetter.vb" />
......
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Imports System.Threading
Imports Microsoft.CodeAnalysis
Imports Microsoft.CodeAnalysis.Text
Imports Microsoft.CodeAnalysis.VisualBasic
Imports Microsoft.CodeAnalysis.VisualBasic.Extensions
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Imports System.Runtime.InteropServices
Imports System.Threading.Tasks
Imports Microsoft.CodeAnalysis.Editor.Implementation.Debugging
Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Debugging
Friend Module BreakpointGetter
Friend Async Function GetBreakpointAsync(document As Document, position As Integer, length As Integer, cancellationToken As CancellationToken) As Task(Of BreakpointResolutionResult)
Dim tree = Await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(False)
' Non-zero length means that the span is passed by the debugger and we may need validate it.
' In a rare VB case, this span may contain multiple methods, e.g.,
'
' [Sub Foo() Handles A
'
' End Sub
'
' Sub Bar() Handles B]
'
' End Sub
'
' It happens when IDE services (e.g., NavBar or CodeFix) inserts a new method at the beginning of the existing one
' which happens to have a breakpoint on its head. In this situation, we should attempt to validate the span of the existing method,
' not that of a newly-prepended method.
Dim descendIntoChildren As Func(Of SyntaxNode, Boolean) =
Function(n)
Return (Not n.IsKind(SyntaxKind.ConstructorBlock)) _
AndAlso (Not n.IsKind(SyntaxKind.SubBlock))
End Function
If length > 0 Then
Dim root = Await tree.GetRootAsync(cancellationToken).ConfigureAwait(False)
Dim item = root.DescendantNodes(New TextSpan(position, length), descendIntoChildren:=descendIntoChildren).OfType(Of MethodBlockSyntax).Skip(1).LastOrDefault()
If item IsNot Nothing Then
position = item.SpanStart
End If
End If
Dim span As TextSpan
If Not TryGetBreakpointSpan(tree, position, cancellationToken, span) Then
Return Nothing
End If
If span.Length = 0 Then
Return BreakpointResolutionResult.CreateLineResult(document)
End If
Return BreakpointResolutionResult.CreateSpanResult(document, span)
End Function
Friend Function TryGetBreakpointSpan(tree As SyntaxTree, position As Integer, cancellationToken As CancellationToken, <Out> ByRef breakpointSpan As TextSpan) As Boolean
Dim source = tree.GetText(cancellationToken)
' If the line is entirely whitespace, then don't set any breakpoint there.
Dim line = source.Lines.GetLineFromPosition(position)
If IsBlank(line) Then
breakpointSpan = Nothing
Return False
End If
' If the user is asking for breakpoint in an inactive region, then just create a line
' breakpoint there.
If tree.IsInInactiveRegion(position, cancellationToken) Then
breakpointSpan = Nothing
Return True
End If
Dim root = tree.GetRoot(cancellationToken)
Return root.TryGetEnclosingBreakpointSpan(position, breakpointSpan)
End Function
Private Function IsBlank(line As TextLine) As Boolean
Dim text = line.ToString()
For i = 0 To text.Length - 1
If Not SyntaxFacts.IsWhitespace(text(i)) Then
Return False
End If
Next
Return True
End Function
End Module
End Namespace
......@@ -7,6 +7,9 @@ Imports Microsoft.CodeAnalysis
Imports Microsoft.CodeAnalysis.Editor.Implementation.Debugging
Imports Microsoft.CodeAnalysis.Host.Mef
Imports Microsoft.CodeAnalysis.Text
Imports Microsoft.CodeAnalysis.VisualBasic
Imports Microsoft.CodeAnalysis.VisualBasic.EditAndContinue
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Debugging
......@@ -14,8 +17,52 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Debugging
Partial Friend Class VisualBasicBreakpointResolutionService
Implements IBreakpointResolutionService
Friend Shared Async Function GetBreakpointAsync(document As Document, position As Integer, length As Integer, cancellationToken As CancellationToken) As Task(Of BreakpointResolutionResult)
Dim tree = Await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(False)
' Non-zero length means that the span is passed by the debugger and we may need validate it.
' In a rare VB case, this span may contain multiple methods, e.g.,
'
' [Sub Foo() Handles A
'
' End Sub
'
' Sub Bar() Handles B]
'
' End Sub
'
' It happens when IDE services (e.g., NavBar or CodeFix) inserts a new method at the beginning of the existing one
' which happens to have a breakpoint on its head. In this situation, we should attempt to validate the span of the existing method,
' not that of a newly-prepended method.
Dim descendIntoChildren As Func(Of SyntaxNode, Boolean) =
Function(n)
Return (Not n.IsKind(SyntaxKind.ConstructorBlock)) _
AndAlso (Not n.IsKind(SyntaxKind.SubBlock))
End Function
If length > 0 Then
Dim root = Await tree.GetRootAsync(cancellationToken).ConfigureAwait(False)
Dim item = root.DescendantNodes(New TextSpan(position, length), descendIntoChildren:=descendIntoChildren).OfType(Of MethodBlockSyntax).Skip(1).LastOrDefault()
If item IsNot Nothing Then
position = item.SpanStart
End If
End If
Dim span As TextSpan
If Not BreakpointSpans.TryGetBreakpointSpan(tree, position, cancellationToken, span) Then
Return Nothing
End If
If span.Length = 0 Then
Return BreakpointResolutionResult.CreateLineResult(document)
End If
Return BreakpointResolutionResult.CreateSpanResult(document, span)
End Function
Public Function ResolveBreakpointAsync(document As Document, textSpan As TextSpan, Optional cancellationToken As CancellationToken = Nothing) As Task(Of BreakpointResolutionResult) Implements IBreakpointResolutionService.ResolveBreakpointAsync
Return BreakpointGetter.GetBreakpointAsync(document, textSpan.Start, textSpan.Length, cancellationToken)
Return GetBreakpointAsync(document, textSpan.Start, textSpan.Length, cancellationToken)
End Function
Public Function ResolveBreakpointsAsync(solution As Solution,
......
......@@ -126,7 +126,6 @@
<Compile Include="Extensions\ArgumentSyntaxExtensions.cs" />
<Compile Include="Extensions\BaseArgumentListSyntaxExtensions.cs" />
<Compile Include="Extensions\BaseParameterListSyntaxExtensions.cs" />
<Compile Include="Extensions\BreakpointSpans.cs" />
<Compile Include="Extensions\CastExpressionSyntaxExtensions.cs" />
<Compile Include="Extensions\CompilationUnitSyntaxExtensions.cs" />
<Compile Include="Extensions\ContextQuery\CSharpSyntaxContext.cs" />
......@@ -268,4 +267,4 @@
<Import Project="..\..\..\..\build\VSL.Imports.Closed.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
</ImportGroup>
</Project>
</Project>
\ No newline at end of file
......@@ -137,7 +137,6 @@
<Compile Include="Editing\VisualBasicImportAdder.vb" />
<Compile Include="Extensions\ArgumentListSyntaxExtensions.vb" />
<Compile Include="Extensions\ArgumentSyntaxExtensions.vb" />
<Compile Include="Extensions\BreakpointSpans.vb" />
<Compile Include="Extensions\CallStatementSyntaxExtensions.vb" />
<Compile Include="Extensions\CastAnalyzer.vb" />
<Compile Include="Extensions\CompilationUnitSyntaxExtensions.vb" />
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册