提交 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 ...@@ -47,9 +47,9 @@ public BoundLambda(CSharpSyntaxNode syntax, BoundBlock body, ImmutableArray<Diag
} }
Debug.Assert( Debug.Assert(
syntax.IsAnonymousFunction() || // lambda expressions syntax.IsAnonymousFunction() || // lambda expressions
syntax is ExpressionSyntax && LambdaUtilities.IsLambdaBody(syntax) || // query lambdas syntax is ExpressionSyntax && LambdaUtilities.IsLambdaBody(syntax, allowReducedLambas: true) || // query lambdas
LambdaUtilities.IsQueryPairLambda(syntax) // "pair" lambdas in queries LambdaUtilities.IsQueryPairLambda(syntax) // "pair" lambdas in queries
); );
} }
......
...@@ -441,9 +441,9 @@ public new IEnumerable<Diagnostic> GetDiagnostics() ...@@ -441,9 +441,9 @@ public new IEnumerable<Diagnostic> GetDiagnostics()
return this.SyntaxTree.GetDiagnostics(this); 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() internal override SyntaxNode GetLambda()
......
...@@ -22,11 +22,14 @@ public static bool IsLambda(SyntaxNode node) ...@@ -22,11 +22,14 @@ public static bool IsLambda(SyntaxNode node)
case SyntaxKind.WhereClause: case SyntaxKind.WhereClause:
case SyntaxKind.AscendingOrdering: case SyntaxKind.AscendingOrdering:
case SyntaxKind.DescendingOrdering: case SyntaxKind.DescendingOrdering:
case SyntaxKind.SelectClause:
case SyntaxKind.JoinClause: case SyntaxKind.JoinClause:
case SyntaxKind.GroupClause: case SyntaxKind.GroupClause:
return true; return true;
case SyntaxKind.SelectClause:
var selectClause = (SelectClauseSyntax)node;
return !IsReducedSelectOrGroupByClause(selectClause, selectClause.Expression);
case SyntaxKind.FromClause: case SyntaxKind.FromClause:
// The first from clause of a query expression is not a lambda. // The first from clause of a query expression is not a lambda.
return !node.Parent.IsKind(SyntaxKind.QueryExpression); return !node.Parent.IsKind(SyntaxKind.QueryExpression);
...@@ -50,7 +53,7 @@ public static SyntaxNode GetLambda(SyntaxNode lambdaBody) ...@@ -50,7 +53,7 @@ public static SyntaxNode GetLambda(SyntaxNode lambdaBody)
/// <summary> /// <summary>
/// See SyntaxNode.GetCorrespondingLambdaBody. /// See SyntaxNode.GetCorrespondingLambdaBody.
/// </summary> /// </summary>
internal static SyntaxNode GetCorrespondingLambdaBody(SyntaxNode oldBody, SyntaxNode newLambda) internal static SyntaxNode TryGetCorrespondingLambdaBody(SyntaxNode oldBody, SyntaxNode newLambda)
{ {
var oldLambda = oldBody.Parent; var oldLambda = oldBody.Parent;
switch (oldLambda.Kind()) switch (oldLambda.Kind())
...@@ -74,7 +77,11 @@ internal static SyntaxNode GetCorrespondingLambdaBody(SyntaxNode oldBody, Syntax ...@@ -74,7 +77,11 @@ internal static SyntaxNode GetCorrespondingLambdaBody(SyntaxNode oldBody, Syntax
return ((OrderingSyntax)newLambda).Expression; return ((OrderingSyntax)newLambda).Expression;
case SyntaxKind.SelectClause: 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: case SyntaxKind.JoinClause:
var oldJoin = (JoinClauseSyntax)oldLambda; var oldJoin = (JoinClauseSyntax)oldLambda;
...@@ -86,7 +93,8 @@ internal static SyntaxNode GetCorrespondingLambdaBody(SyntaxNode oldBody, Syntax ...@@ -86,7 +93,8 @@ internal static SyntaxNode GetCorrespondingLambdaBody(SyntaxNode oldBody, Syntax
var oldGroup = (GroupClauseSyntax)oldLambda; var oldGroup = (GroupClauseSyntax)oldLambda;
var newGroup = (GroupClauseSyntax)newLambda; var newGroup = (GroupClauseSyntax)newLambda;
Debug.Assert(oldGroup.GroupExpression == oldBody || oldGroup.ByExpression == oldBody); 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: default:
throw ExceptionUtilities.UnexpectedValue(oldLambda.Kind()); throw ExceptionUtilities.UnexpectedValue(oldLambda.Kind());
...@@ -101,7 +109,7 @@ public static bool IsNotLambdaBody(SyntaxNode node) ...@@ -101,7 +109,7 @@ public static bool IsNotLambdaBody(SyntaxNode node)
/// <summary> /// <summary>
/// Returns true if the specified <paramref name="node"/> represents a body of a lambda. /// Returns true if the specified <paramref name="node"/> represents a body of a lambda.
/// </summary> /// </summary>
public static bool IsLambdaBody(SyntaxNode node) public static bool IsLambdaBody(SyntaxNode node, bool allowReducedLambas = false)
{ {
var parent = node?.Parent; var parent = node?.Parent;
if (parent == null) if (parent == null)
...@@ -140,16 +148,83 @@ public static bool IsLambdaBody(SyntaxNode node) ...@@ -140,16 +148,83 @@ public static bool IsLambdaBody(SyntaxNode node)
case SyntaxKind.SelectClause: case SyntaxKind.SelectClause:
var selectClause = (SelectClauseSyntax)parent; var selectClause = (SelectClauseSyntax)parent;
return selectClause.Expression == node; return selectClause.Expression == node && (allowReducedLambas || !IsReducedSelectOrGroupByClause(selectClause, selectClause.Expression));
case SyntaxKind.GroupClause: case SyntaxKind.GroupClause:
var groupClause = (GroupClauseSyntax)parent; 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; 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> /// <remarks>
/// In C# lambda bodies are expressions or block statements. In both cases it's a single node. /// 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). /// 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 ...@@ -212,13 +287,27 @@ public static bool TryGetLambdaBodies(SyntaxNode node, out SyntaxNode lambdaBody
return true; return true;
case SyntaxKind.SelectClause: case SyntaxKind.SelectClause:
lambdaBody1 = ((SelectClauseSyntax)node).Expression; var selectClause = (SelectClauseSyntax)node;
if (IsReducedSelectOrGroupByClause(selectClause, selectClause.Expression))
{
return false;
}
lambdaBody1 = selectClause.Expression;
return true; return true;
case SyntaxKind.GroupClause: case SyntaxKind.GroupClause:
var groupClause = (GroupClauseSyntax)node; var groupClause = (GroupClauseSyntax)node;
lambdaBody1 = groupClause.GroupExpression; if (IsReducedSelectOrGroupByClause(groupClause, groupClause.GroupExpression))
lambdaBody2 = groupClause.ByExpression; {
lambdaBody1 = groupClause.ByExpression;
}
else
{
lambdaBody1 = groupClause.GroupExpression;
lambdaBody2 = groupClause.ByExpression;
}
return true; return true;
} }
......
...@@ -2042,5 +2042,491 @@ class C ...@@ -2042,5 +2042,491 @@ class C
"C: {<> 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}"); "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. // 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; using Xunit;
namespace Microsoft.CodeAnalysis.CSharp.UnitTests 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] [Fact]
public void AreEquivalentIgnoringLambdaBodies1() public void AreEquivalentIgnoringLambdaBodies1()
{ {
...@@ -49,6 +227,18 @@ 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)"),
SyntaxFactory.ParseExpression("F(from a in new[] { 1, 2 } select a + 1)"))); 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( Assert.False(LambdaUtilities.AreEquivalentIgnoringLambdaBodies(
SyntaxFactory.ParseExpression("F(from a in new[] { 1, 2 } select a)"), 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)"))); SyntaxFactory.ParseExpression("F(from a in new[] { 1, 2 } where b > 0 select a)")));
...@@ -69,9 +259,13 @@ public void AreEquivalentIgnoringLambdaBodies1() ...@@ -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 } select a)"),
SyntaxFactory.ParseExpression("F(from a in new[] { 1, 2 } join b in new[] { 3, 4 } on a equals b 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( 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 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( 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 by a into g select g)"),
......
...@@ -248,7 +248,12 @@ private bool TryGetPreviousLambdaSyntaxOffset(SyntaxNode lambdaOrLambdaBodySynta ...@@ -248,7 +248,12 @@ private bool TryGetPreviousLambdaSyntaxOffset(SyntaxNode lambdaOrLambdaBodySynta
SyntaxNode previousSyntax; SyntaxNode previousSyntax;
if (isLambdaBody) if (isLambdaBody)
{ {
previousSyntax = previousLambdaSyntax.GetCorrespondingLambdaBody(lambdaOrLambdaBodySyntax); previousSyntax = previousLambdaSyntax.TryGetCorrespondingLambdaBody(lambdaOrLambdaBodySyntax);
if (previousSyntax == null)
{
previousSyntaxOffset = 0;
return false;
}
} }
else else
{ {
......
...@@ -563,7 +563,7 @@ public SyntaxReference GetReference() ...@@ -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. /// 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. /// JoinClause1.GetCorrespondingLambdaBody(JoinClause2.RightExpression) returns JoinClause1.RightExpression.
/// </summary> /// </summary>
internal abstract SyntaxNode GetCorrespondingLambdaBody(SyntaxNode body); internal abstract SyntaxNode TryGetCorrespondingLambdaBody(SyntaxNode body);
internal abstract SyntaxNode GetLambda(); internal abstract SyntaxNode GetLambda();
......
...@@ -677,7 +677,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic ...@@ -677,7 +677,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
Return SyntaxFactory.AreEquivalent(Me, DirectCast(node, VisualBasicSyntaxNode), topLevel) Return SyntaxFactory.AreEquivalent(Me, DirectCast(node, VisualBasicSyntaxNode), topLevel)
End Function 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) Return LambdaUtilities.GetCorrespondingLambdaBody(body, Me)
End Function End Function
......
...@@ -266,6 +266,7 @@ ...@@ -266,6 +266,7 @@
<Compile Include="EditAndContinue\ActiveStatementTests.cs" /> <Compile Include="EditAndContinue\ActiveStatementTests.cs" />
<Compile Include="EditAndContinue\ActiveStatementTests.Methods.cs" /> <Compile Include="EditAndContinue\ActiveStatementTests.Methods.cs" />
<Compile Include="EditAndContinue\ActiveStatementTrackingServiceTests.cs" /> <Compile Include="EditAndContinue\ActiveStatementTrackingServiceTests.cs" />
<Compile Include="EditAndContinue\BreakpointSpansTests.cs" />
<Compile Include="EditAndContinue\CSharpEditAndContinueAnalyzerTests.cs" /> <Compile Include="EditAndContinue\CSharpEditAndContinueAnalyzerTests.cs" />
<Compile Include="EditAndContinue\Helpers\CSharpEditAndContinueTestHelpers.cs" /> <Compile Include="EditAndContinue\Helpers\CSharpEditAndContinueTestHelpers.cs" />
<Compile Include="EditAndContinue\Helpers\Extensions.cs" /> <Compile Include="EditAndContinue\Helpers\Extensions.cs" />
...@@ -673,4 +674,4 @@ ...@@ -673,4 +674,4 @@
<Import Project="..\..\..\build\Roslyn.Toolsets.Xunit.targets" /> <Import Project="..\..\..\build\Roslyn.Toolsets.Xunit.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" /> <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
</ImportGroup> </ImportGroup>
</Project> </Project>
\ No newline at end of file
...@@ -7194,6 +7194,128 @@ static void Main() ...@@ -7194,6 +7194,128 @@ static void Main()
Diagnostic(RudeEditKind.ActiveStatementLambdaRemoved, "join", CSharpFeaturesResources.SelectClause)); 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 #endregion
#region State Machines #region State Machines
......
...@@ -5435,6 +5435,72 @@ public void Queries_FromSelect_Update3() ...@@ -5435,6 +5435,72 @@ public void Queries_FromSelect_Update3()
"Update [await b]@14 -> [await c]@14"); "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] [Fact]
public void Queries_FromSelect_Delete() public void Queries_FromSelect_Delete()
{ {
...@@ -5499,6 +5565,132 @@ public void Queries_OrderBy_Reorder() ...@@ -5499,6 +5565,132 @@ public void Queries_OrderBy_Reorder()
"Reorder [a.c ascending]@46 -> @30"); "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] [Fact]
public void Queries_OrderBy_Continuation_Update() public void Queries_OrderBy_Continuation_Update()
{ {
...@@ -5803,8 +5995,7 @@ void F() ...@@ -5803,8 +5995,7 @@ void F()
} }
"; ";
var edits = GetTopEdits(src1, src2); var edits = GetTopEdits(src1, src2);
edits.VerifySemanticDiagnostics( edits.VerifySemanticDiagnostics();
Diagnostic(RudeEditKind.ChangingQueryLambdaType, "select", CSharpFeaturesResources.SelectClause));
} }
[Fact] [Fact]
......
...@@ -239,6 +239,7 @@ ...@@ -239,6 +239,7 @@
<Compile Include="DocumentationComments\XmlTagCompletionTests.vb" /> <Compile Include="DocumentationComments\XmlTagCompletionTests.vb" />
<Compile Include="EditAndContinue\ActiveStatementTests.vb" /> <Compile Include="EditAndContinue\ActiveStatementTests.vb" />
<Compile Include="EditAndContinue\ActiveStatementTrackingServiceTests.vb" /> <Compile Include="EditAndContinue\ActiveStatementTrackingServiceTests.vb" />
<Compile Include="EditAndContinue\BreakpointSpansTests.vb" />
<Compile Include="EditAndContinue\SyntaxComparerTests.vb" /> <Compile Include="EditAndContinue\SyntaxComparerTests.vb" />
<Compile Include="EditAndContinue\Helpers\Extensions.vb" /> <Compile Include="EditAndContinue\Helpers\Extensions.vb" />
<Compile Include="EditAndContinue\Helpers\RudeEditTestBase.vb" /> <Compile Include="EditAndContinue\Helpers\RudeEditTestBase.vb" />
...@@ -641,4 +642,4 @@ ...@@ -641,4 +642,4 @@
<Import Project="..\..\..\build\Roslyn.Toolsets.Xunit.targets" /> <Import Project="..\..\..\build\Roslyn.Toolsets.Xunit.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" /> <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
</ImportGroup> </ImportGroup>
</Project> </Project>
\ No newline at end of file
...@@ -4,18 +4,11 @@ Imports System.Threading ...@@ -4,18 +4,11 @@ Imports System.Threading
Imports System.Xml.Linq Imports System.Xml.Linq
Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis
Imports Microsoft.CodeAnalysis.Editor.UnitTests.Extensions Imports Microsoft.CodeAnalysis.Editor.UnitTests.Extensions
Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces
Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.Text
Imports Microsoft.CodeAnalysis.VisualBasic
Imports Microsoft.CodeAnalysis.VisualBasic.Extensions Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue.UnitTests
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
<Trait(Traits.Feature, Traits.Features.DebuggingBreakpoints)> <Trait(Traits.Feature, Traits.Features.DebuggingBreakpoints)>
Public Class BreakpointLocationValidatorTests Public Class BreakpointSpansTests
#Region "Helpers" #Region "Helpers"
...@@ -48,7 +41,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.UnitTests.Debuggin ...@@ -48,7 +41,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.UnitTests.Debuggin
Dim tree = SyntaxFactory.ParseSyntaxTree(source) Dim tree = SyntaxFactory.ParseSyntaxTree(source)
Dim breakpointSpan As TextSpan Dim breakpointSpan As TextSpan
Dim hasBreakpoint = BreakpointGetter.TryGetBreakpointSpan(tree, Dim hasBreakpoint = BreakpointSpans.TryGetBreakpointSpan(tree,
position.Value, position.Value,
CancellationToken.None, CancellationToken.None,
breakpointSpan) breakpointSpan)
...@@ -69,6 +62,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.UnitTests.Debuggin ...@@ -69,6 +62,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.UnitTests.Debuggin
End If End If
End Sub End Sub
Private Sub TestAll(markup As String) Private Sub TestAll(markup As String)
Dim position As Integer = Nothing Dim position As Integer = Nothing
Dim expectedSpans As IList(Of TextSpan) = Nothing Dim expectedSpans As IList(Of TextSpan) = Nothing
...@@ -91,7 +85,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.UnitTests.Debuggin ...@@ -91,7 +85,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.UnitTests.Debuggin
Dim lastSpanEnd = 0 Dim lastSpanEnd = 0
While position < endPosition While position < endPosition
Dim span As TextSpan = Nothing 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 position = span.End
lastSpanEnd = span.End lastSpanEnd = span.End
Yield span Yield span
...@@ -3115,48 +3109,5 @@ End Class ...@@ -3115,48 +3109,5 @@ End Class
End Sub End Sub
#End Region #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 Class
End Namespace End Namespace
...@@ -77,12 +77,13 @@ ...@@ -77,12 +77,13 @@
<ItemGroup> <ItemGroup>
<InternalsVisibleTo Include="Microsoft.CodeAnalysis.CSharp.EditorFeatures" /> <InternalsVisibleTo Include="Microsoft.CodeAnalysis.CSharp.EditorFeatures" />
<InternalsVisibleTo Include="Microsoft.VisualStudio.LanguageServices.CSharp" /> <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.CSharp.UnitTests" />
<InternalsVisibleToTest Include="Roslyn.Services.Editor.UnitTests" /> <InternalsVisibleToTest Include="Roslyn.Services.Editor.UnitTests" />
<InternalsVisibleToTest Include="Roslyn.Services.Editor.UnitTests2" /> <InternalsVisibleToTest Include="Roslyn.Services.Editor.UnitTests2" />
<InternalsVisibleToTest Include="Roslyn.Services.Editor.VisualBasic.UnitTests" /> <InternalsVisibleToTest Include="Roslyn.Services.Editor.VisualBasic.UnitTests" />
<InternalsVisibleToTest Include="Roslyn.Services.UnitTests" /> <InternalsVisibleToTest Include="Roslyn.Services.UnitTests" />
<InternalsVisibleToTest Include="Roslyn.VisualStudio.CSharp.UnitTests" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="..\..\Compilers\CSharp\Portable\Syntax\LambdaUtilities.cs"> <Compile Include="..\..\Compilers\CSharp\Portable\Syntax\LambdaUtilities.cs">
...@@ -293,6 +294,7 @@ ...@@ -293,6 +294,7 @@
<Compile Include="Diagnostics\CSharpSyntaxNodeAnalyzerService.cs" /> <Compile Include="Diagnostics\CSharpSyntaxNodeAnalyzerService.cs" />
<Compile Include="DocumentationCommentFormatting\CSharpDocumentationCommentFormattingService.cs" /> <Compile Include="DocumentationCommentFormatting\CSharpDocumentationCommentFormattingService.cs" />
<Compile Include="DocumentationCommentFormatting\DocumentationCommentUtilities.cs" /> <Compile Include="DocumentationCommentFormatting\DocumentationCommentUtilities.cs" />
<Compile Include="EditAndContinue\BreakpointSpans.cs" />
<Compile Include="EditAndContinue\CSharpEditAndContinueAnalyzer.cs" /> <Compile Include="EditAndContinue\CSharpEditAndContinueAnalyzer.cs" />
<Compile Include="EditAndContinue\StatementSyntaxComparer.cs" /> <Compile Include="EditAndContinue\StatementSyntaxComparer.cs" />
<Compile Include="EditAndContinue\SyntaxComparer.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. // 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.Diagnostics;
using System.Linq; using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text; 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 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> /// <summary>
/// Given a syntax token determines a text span delimited by the closest applicable sequence points /// Given a syntax token determines a text span delimited by the closest applicable sequence points
/// encompassing the token. /// encompassing the token.
...@@ -18,7 +57,7 @@ internal static class BreakpointSpans ...@@ -18,7 +57,7 @@ internal static class BreakpointSpans
/// <remarks> /// <remarks>
/// If the span exists it is possible to place a breakpoint at the given position. /// If the span exists it is possible to place a breakpoint at the given position.
/// </remarks> /// </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; SyntaxNode node = root.FindToken(position).Parent;
while (node != null) while (node != null)
...@@ -657,11 +696,6 @@ private static bool IsBreakableExpression(ExpressionSyntax expression) ...@@ -657,11 +696,6 @@ private static bool IsBreakableExpression(ExpressionSyntax expression)
Debug.Assert(((ArrowExpressionClauseSyntax)parent).Expression == expression); Debug.Assert(((ArrowExpressionClauseSyntax)parent).Expression == expression);
return true; return true;
case SyntaxKind.SimpleLambdaExpression:
case SyntaxKind.ParenthesizedLambdaExpression:
Debug.Assert(((AnonymousFunctionExpressionSyntax)parent).Body == expression);
return true;
case SyntaxKind.ForStatement: case SyntaxKind.ForStatement:
var forStatement = (ForStatementSyntax)parent; var forStatement = (ForStatementSyntax)parent;
return return
...@@ -673,51 +707,9 @@ private static bool IsBreakableExpression(ExpressionSyntax expression) ...@@ -673,51 +707,9 @@ private static bool IsBreakableExpression(ExpressionSyntax expression)
var forEachStatement = (ForEachStatementSyntax)parent; var forEachStatement = (ForEachStatementSyntax)parent;
return forEachStatement.Expression == expression; return forEachStatement.Expression == expression;
// Query clauses default:
case SyntaxKind.FromClause: return LambdaUtilities.IsLambdaBodyStatementOrExpression(expression);
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;
} }
return false;
} }
private static TextSpan? CreateSpanForAccessors(SyntaxList<AccessorDeclarationSyntax> accessors, int position) private static TextSpan? CreateSpanForAccessors(SyntaxList<AccessorDeclarationSyntax> accessors, int position)
......
...@@ -472,9 +472,9 @@ protected override IEnumerable<SyntaxNode> GetLambdaBodyExpressionsAndStatements ...@@ -472,9 +472,9 @@ protected override IEnumerable<SyntaxNode> GetLambdaBodyExpressionsAndStatements
return SpecializedCollections.SingletonEnumerable(lambdaBody); 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) protected override Match<SyntaxNode> ComputeTopLevelMatch(SyntaxNode oldCompilationUnit, SyntaxNode newCompilationUnit)
...@@ -587,7 +587,7 @@ protected override bool StatementLabelEquals(SyntaxNode node1, SyntaxNode node2) ...@@ -587,7 +587,7 @@ protected override bool StatementLabelEquals(SyntaxNode node1, SyntaxNode node2)
protected override bool TryGetEnclosingBreakpointSpan(SyntaxNode root, int position, out TextSpan span) 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) protected override bool TryGetActiveSpan(SyntaxNode node, int statementPart, out TextSpan span)
...@@ -607,7 +607,7 @@ protected override bool TryGetActiveSpan(SyntaxNode node, int statementPart, out ...@@ -607,7 +607,7 @@ protected override bool TryGetActiveSpan(SyntaxNode node, int statementPart, out
// which is lexically not the closest breakpoint span (the body is). // which is lexically not the closest breakpoint span (the body is).
// do { ... } [|while (condition);|] // do { ... } [|while (condition);|]
var doStatement = (DoStatementSyntax)node; var doStatement = (DoStatementSyntax)node;
return node.TryGetClosestBreakpointSpan(doStatement.WhileKeyword.SpanStart, out span); return BreakpointSpans.TryGetClosestBreakpointSpan(node, doStatement.WhileKeyword.SpanStart, out span);
case SyntaxKind.PropertyDeclaration: case SyntaxKind.PropertyDeclaration:
// The active span corresponding to a property declaration is the span corresponding to its initializer (if any), // 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 ...@@ -616,7 +616,7 @@ protected override bool TryGetActiveSpan(SyntaxNode node, int statementPart, out
var propertyDeclaration = (PropertyDeclarationSyntax)node; var propertyDeclaration = (PropertyDeclarationSyntax)node;
if (propertyDeclaration.Initializer != null && if (propertyDeclaration.Initializer != null &&
node.TryGetClosestBreakpointSpan(propertyDeclaration.Initializer.SpanStart, out span)) BreakpointSpans.TryGetClosestBreakpointSpan(node, propertyDeclaration.Initializer.SpanStart, out span))
{ {
return true; return true;
} }
...@@ -627,7 +627,7 @@ protected override bool TryGetActiveSpan(SyntaxNode node, int statementPart, out ...@@ -627,7 +627,7 @@ protected override bool TryGetActiveSpan(SyntaxNode node, int statementPart, out
} }
default: 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, ...@@ -986,18 +986,71 @@ internal override bool QueryClauseLambdasTypeEquivalent(SemanticModel oldModel,
case SyntaxKind.AscendingOrdering: case SyntaxKind.AscendingOrdering:
case SyntaxKind.DescendingOrdering: 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.SelectClause:
case SyntaxKind.GroupClause: var oldSelectInfo = oldModel.GetSymbolInfo(oldNode, cancellationToken);
var oldSymbolInfo = oldModel.GetSymbolInfo(oldNode, cancellationToken); var newSelectInfo = newModel.GetSymbolInfo(newNode, cancellationToken);
var newSymbolInfo = 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: default:
return true; 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 #endregion
#region Diagnostic Info #region Diagnostic Info
......
...@@ -156,7 +156,7 @@ private SyntaxNode FindStatement(SyntaxNode declarationBody, int position, out i ...@@ -156,7 +156,7 @@ private SyntaxNode FindStatement(SyntaxNode declarationBody, int position, out i
/// </remarks> /// </remarks>
protected abstract IEnumerable<SyntaxNode> GetLambdaBodyExpressionsAndStatements(SyntaxNode lambdaBody); 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> ComputeTopLevelMatch(SyntaxNode oldCompilationUnit, SyntaxNode newCompilationUnit);
protected abstract Match<SyntaxNode> ComputeBodyMatch(SyntaxNode oldBody, SyntaxNode newBody, IEnumerable<KeyValuePair<SyntaxNode, SyntaxNode>> knownMatches); protected abstract Match<SyntaxNode> ComputeBodyMatch(SyntaxNode oldBody, SyntaxNode newBody, IEnumerable<KeyValuePair<SyntaxNode, SyntaxNode>> knownMatches);
...@@ -1130,8 +1130,6 @@ internal struct UpdatedMemberInfo ...@@ -1130,8 +1130,6 @@ internal struct UpdatedMemberInfo
if (TryGetLambdaBodies(oldLambda, out oldLambdaBody1, out oldLambdaBody2)) if (TryGetLambdaBodies(oldLambda, out oldLambdaBody1, out oldLambdaBody2))
{ {
Debug.Assert(IsLambda(newLambda));
if (lambdaBodyMatches == null) if (lambdaBodyMatches == null)
{ {
lambdaBodyMatches = ArrayBuilder<Match<SyntaxNode>>.GetInstance(); lambdaBodyMatches = ArrayBuilder<Match<SyntaxNode>>.GetInstance();
...@@ -1142,11 +1140,19 @@ internal struct UpdatedMemberInfo ...@@ -1142,11 +1140,19 @@ internal struct UpdatedMemberInfo
lazyActiveOrMatchedLambdas = new Dictionary<SyntaxNode, LambdaInfo>(); 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) 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 ...@@ -1194,16 +1200,14 @@ internal struct UpdatedMemberInfo
private Match<SyntaxNode> ComputeLambdaBodyMatch( private Match<SyntaxNode> ComputeLambdaBodyMatch(
SyntaxNode oldLambdaBody, SyntaxNode oldLambdaBody,
SyntaxNode newLambda, SyntaxNode newLambdaBody,
ActiveNode[] activeNodes, ActiveNode[] activeNodes,
[Out]Dictionary<SyntaxNode, LambdaInfo> lazyActiveOrMatchedLambdas, [Out]Dictionary<SyntaxNode, LambdaInfo> activeOrMatchedLambdas,
[Out]List<RudeEditDiagnostic> diagnostics) [Out]List<RudeEditDiagnostic> diagnostics)
{ {
SyntaxNode newLambdaBody = GetPartnerLambdaBody(oldLambdaBody, newLambda);
ActiveNode[] activeNodesInLambda; ActiveNode[] activeNodesInLambda;
LambdaInfo info; LambdaInfo info;
if (lazyActiveOrMatchedLambdas.TryGetValue(oldLambdaBody, out info)) if (activeOrMatchedLambdas.TryGetValue(oldLambdaBody, out info))
{ {
// Lambda may be matched but not be active. // Lambda may be matched but not be active.
activeNodesInLambda = info.ActiveNodeIndices?.Select(i => activeNodes[i]).ToArray(); activeNodesInLambda = info.ActiveNodeIndices?.Select(i => activeNodes[i]).ToArray();
...@@ -1218,7 +1222,7 @@ internal struct UpdatedMemberInfo ...@@ -1218,7 +1222,7 @@ internal struct UpdatedMemberInfo
bool needsSyntaxMap; bool needsSyntaxMap;
var lambdaBodyMatch = ComputeBodyMatch(oldLambdaBody, newLambdaBody, activeNodesInLambda ?? SpecializedCollections.EmptyArray<ActiveNode>(), diagnostics, out 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; return lambdaBodyMatch;
} }
...@@ -1948,19 +1952,16 @@ public int GetHashCode(IAssemblySymbol obj) ...@@ -1948,19 +1952,16 @@ public int GetHashCode(IAssemblySymbol obj)
protected static readonly SymbolEquivalenceComparer s_assemblyEqualityComparer = new SymbolEquivalenceComparer(AssemblyEqualityComparer.Instance); 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) && return oldParameters.SequenceEqual(newParameters, s_assemblyEqualityComparer.ParameterEquivalenceComparer) &&
s_assemblyEqualityComparer.ReturnTypeEquals(oldMethod, newMethod); s_assemblyEqualityComparer.Equals(oldReturnType, newReturnType);
} }
private static bool PropertySignaturesEquivalent(IPropertySymbol oldProperty, IPropertySymbol newProperty) protected static bool MemberSignaturesEquivalent(
{ ISymbol oldMemberOpt,
return oldProperty.Parameters.SequenceEqual(newProperty.Parameters, s_assemblyEqualityComparer.ParameterEquivalenceComparer) && ISymbol newMemberOpt,
s_assemblyEqualityComparer.Equals(oldProperty.Type, newProperty.Type); Func<ImmutableArray<IParameterSymbol>, ITypeSymbol, ImmutableArray<IParameterSymbol>, ITypeSymbol, bool> signatureComparer = null)
}
protected static bool MemberSignaturesEquivalent(ISymbol oldMemberOpt, ISymbol newMemberOpt)
{ {
if (oldMemberOpt == newMemberOpt) if (oldMemberOpt == newMemberOpt)
{ {
...@@ -1972,16 +1973,27 @@ protected static bool MemberSignaturesEquivalent(ISymbol oldMemberOpt, ISymbol n ...@@ -1972,16 +1973,27 @@ protected static bool MemberSignaturesEquivalent(ISymbol oldMemberOpt, ISymbol n
return false; return false;
} }
if (signatureComparer == null)
{
signatureComparer = SignaturesEquivalent;
}
switch (oldMemberOpt.Kind) switch (oldMemberOpt.Kind)
{ {
case SymbolKind.Field: 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: 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: 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: default:
throw ExceptionUtilities.UnexpectedValue(oldMemberOpt.Kind); throw ExceptionUtilities.UnexpectedValue(oldMemberOpt.Kind);
......
...@@ -343,6 +343,7 @@ ...@@ -343,6 +343,7 @@
<Compile Include="Diagnostics\VisualBasicSyntaxNodeAnalyzerService.vb" /> <Compile Include="Diagnostics\VisualBasicSyntaxNodeAnalyzerService.vb" />
<Compile Include="DocumentationCommentFormatting\DocumentationCommentUtilities.vb" /> <Compile Include="DocumentationCommentFormatting\DocumentationCommentUtilities.vb" />
<Compile Include="DocumentationCommentFormatting\VisualBasicDocumentationCommentFormattingService.vb" /> <Compile Include="DocumentationCommentFormatting\VisualBasicDocumentationCommentFormattingService.vb" />
<Compile Include="EditAndContinue\BreakpointSpans.vb" />
<Compile Include="EditAndContinue\StatementSyntaxComparer.vb" /> <Compile Include="EditAndContinue\StatementSyntaxComparer.vb" />
<Compile Include="EditAndContinue\SyntaxComparer.vb" /> <Compile Include="EditAndContinue\SyntaxComparer.vb" />
<Compile Include="EditAndContinue\SyntaxUtilities.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. ' 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.Runtime.InteropServices
Imports System.Threading
Imports Microsoft.CodeAnalysis.VisualBasic Imports Microsoft.CodeAnalysis.VisualBasic
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.Text
Imports System.Runtime.CompilerServices Imports System.Threading
Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue
' TODO (tomat): move to the compiler. Bug #764678.
Friend Module BreakpointSpans 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> ''' <summary>
''' Given a syntax token determines a text span delimited by the closest applicable sequence points ''' Given a syntax token determines a text span delimited by the closest applicable sequence points
''' encompassing the token. ''' encompassing the token.
...@@ -19,7 +48,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions ...@@ -19,7 +48,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions
''' <remarks> ''' <remarks>
''' If the span exists it Is possible To place a breakpoint at the given position. ''' If the span exists it Is possible To place a breakpoint at the given position.
''' </remarks> ''' </remarks>
<Extension>
Public Function TryGetEnclosingBreakpointSpan(root As SyntaxNode, position As Integer, <Out> ByRef span As TextSpan) As Boolean Public Function TryGetEnclosingBreakpointSpan(root As SyntaxNode, position As Integer, <Out> ByRef span As TextSpan) As Boolean
Dim node = root.FindToken(position).Parent Dim node = root.FindToken(position).Parent
......
...@@ -555,7 +555,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue ...@@ -555,7 +555,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue
Return Nothing Return Nothing
End Function 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) Return LambdaUtilities.GetCorrespondingLambdaBody(oldBody, newLambda)
End Function End Function
...@@ -726,11 +726,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue ...@@ -726,11 +726,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue
End Function End Function
Protected Overrides Function TryGetEnclosingBreakpointSpan(root As SyntaxNode, position As Integer, <Out> ByRef span As TextSpan) As Boolean 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 End Function
Protected Overrides Function TryGetActiveSpan(node As SyntaxNode, statementPart As Integer, <Out> ByRef span As TextSpan) As Boolean 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 End Function
Protected Overrides Iterator Function EnumerateNearStatements(statement As SyntaxNode) As IEnumerable(Of KeyValuePair(Of SyntaxNode, Integer)) Protected Overrides Iterator Function EnumerateNearStatements(statement As SyntaxNode) As IEnumerable(Of KeyValuePair(Of SyntaxNode, Integer))
......
...@@ -180,7 +180,6 @@ ...@@ -180,7 +180,6 @@
<Compile Include="CodeModel\ParameterFlags.cs" /> <Compile Include="CodeModel\ParameterFlags.cs" />
<Compile Include="CodeModel\ParameterFlagsExtensions.cs" /> <Compile Include="CodeModel\ParameterFlagsExtensions.cs" />
<Compile Include="CodeModel\SyntaxListExtensions.cs" /> <Compile Include="CodeModel\SyntaxListExtensions.cs" />
<Compile Include="Debugging\BreakpointGetter.cs" />
<Compile Include="Debugging\BreakpointResolver.cs" /> <Compile Include="Debugging\BreakpointResolver.cs" />
<Compile Include="Debugging\CSharpBreakpointResolutionService.cs" /> <Compile Include="Debugging\CSharpBreakpointResolutionService.cs" />
<Compile Include="Debugging\CSharpLanguageDebugInfoService.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 @@ ...@@ -5,6 +5,7 @@
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.EditAndContinue;
using Microsoft.CodeAnalysis.Editor.Implementation.Debugging; using Microsoft.CodeAnalysis.Editor.Implementation.Debugging;
using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Text;
...@@ -14,10 +15,28 @@ namespace Microsoft.VisualStudio.LanguageServices.CSharp.Debugging ...@@ -14,10 +15,28 @@ namespace Microsoft.VisualStudio.LanguageServices.CSharp.Debugging
[ExportLanguageService(typeof(IBreakpointResolutionService), LanguageNames.CSharp), Shared] [ExportLanguageService(typeof(IBreakpointResolutionService), LanguageNames.CSharp), Shared]
internal partial class CSharpBreakpointResolutionService : IBreakpointResolutionService 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( public Task<BreakpointResolutionResult> ResolveBreakpointAsync(
Document document, TextSpan textSpan, CancellationToken cancellationToken) 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) public Task<IEnumerable<BreakpointResolutionResult>> ResolveBreakpointsAsync(Solution solution, string name, CancellationToken cancellationToken)
......
...@@ -160,7 +160,6 @@ ...@@ -160,7 +160,6 @@
<Compile Include="CodeModel\MockTextManagerAdapter.cs" /> <Compile Include="CodeModel\MockTextManagerAdapter.cs" />
<Compile Include="CodeModel\MockTextManagerAdapter.TextPoint.cs" /> <Compile Include="CodeModel\MockTextManagerAdapter.TextPoint.cs" />
<Compile Include="CodeModel\VisualStudioTestExportProvider.cs" /> <Compile Include="CodeModel\VisualStudioTestExportProvider.cs" />
<Compile Include="Debugging\BreakpointLocationValidatorTests.cs" />
<Compile Include="Debugging\DataTipInfoGetterTests.cs" /> <Compile Include="Debugging\DataTipInfoGetterTests.cs" />
<Compile Include="Debugging\LocationInfoGetterTests.cs" /> <Compile Include="Debugging\LocationInfoGetterTests.cs" />
<Compile Include="Debugging\NameResolverTests.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 @@ ...@@ -313,7 +313,6 @@
<Compile Include="DebuggerIntelliSense\SignatureHelpWaiter.vb" /> <Compile Include="DebuggerIntelliSense\SignatureHelpWaiter.vb" />
<Compile Include="DebuggerIntelliSense\TestState.vb" /> <Compile Include="DebuggerIntelliSense\TestState.vb" />
<Compile Include="DebuggerIntelliSense\VisualBasicDebuggerIntelliSenseTests.vb" /> <Compile Include="DebuggerIntelliSense\VisualBasicDebuggerIntelliSenseTests.vb" />
<Compile Include="Debugging\BreakpointLocationValidatorTests.vb" />
<Compile Include="Debugging\DataTipInfoGetterTests.vb" /> <Compile Include="Debugging\DataTipInfoGetterTests.vb" />
<Compile Include="Debugging\LocationInfoGetterTests.vb" /> <Compile Include="Debugging\LocationInfoGetterTests.vb" />
<Compile Include="Debugging\NameResolverTests.vb" /> <Compile Include="Debugging\NameResolverTests.vb" />
...@@ -325,6 +324,7 @@ ...@@ -325,6 +324,7 @@
<DesignTime>True</DesignTime> <DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon> <DependentUpon>Resources.resx</DependentUpon>
</Compile> </Compile>
<Compile Include="Debugging\VisualBasicBreakpointResolutionServiceTests.vb" />
<Compile Include="Diagnostics\DiagnosticTableDataSourceTests.vb" /> <Compile Include="Diagnostics\DiagnosticTableDataSourceTests.vb" />
<Compile Include="Diagnostics\ExternalDiagnosticUpdateSourceTests.vb" /> <Compile Include="Diagnostics\ExternalDiagnosticUpdateSourceTests.vb" />
<Compile Include="Diagnostics\MiscDiagnosticUpdateSourceTests.vb" /> <Compile Include="Diagnostics\MiscDiagnosticUpdateSourceTests.vb" />
......
...@@ -107,7 +107,6 @@ ...@@ -107,7 +107,6 @@
<Compile Include="CodeModel\VisualBasicCodeModelService_Prototype.vb" /> <Compile Include="CodeModel\VisualBasicCodeModelService_Prototype.vb" />
<Compile Include="CodeModel\VisualBasicCodeModelServiceFactory.vb" /> <Compile Include="CodeModel\VisualBasicCodeModelServiceFactory.vb" />
<Compile Include="CodeModel\VisualBasicProjectCodeModel.vb" /> <Compile Include="CodeModel\VisualBasicProjectCodeModel.vb" />
<Compile Include="Debugging\BreakpointGetter.vb" />
<Compile Include="Debugging\BreakpointResolver.vb" /> <Compile Include="Debugging\BreakpointResolver.vb" />
<Compile Include="Debugging\DataTipInfoGetter.vb" /> <Compile Include="Debugging\DataTipInfoGetter.vb" />
<Compile Include="Debugging\LocationInfoGetter.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 ...@@ -7,6 +7,9 @@ Imports Microsoft.CodeAnalysis
Imports Microsoft.CodeAnalysis.Editor.Implementation.Debugging Imports Microsoft.CodeAnalysis.Editor.Implementation.Debugging
Imports Microsoft.CodeAnalysis.Host.Mef Imports Microsoft.CodeAnalysis.Host.Mef
Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.Text
Imports Microsoft.CodeAnalysis.VisualBasic
Imports Microsoft.CodeAnalysis.VisualBasic.EditAndContinue
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Debugging Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Debugging
...@@ -14,8 +17,52 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Debugging ...@@ -14,8 +17,52 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Debugging
Partial Friend Class VisualBasicBreakpointResolutionService Partial Friend Class VisualBasicBreakpointResolutionService
Implements IBreakpointResolutionService 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 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 End Function
Public Function ResolveBreakpointsAsync(solution As Solution, Public Function ResolveBreakpointsAsync(solution As Solution,
......
...@@ -126,7 +126,6 @@ ...@@ -126,7 +126,6 @@
<Compile Include="Extensions\ArgumentSyntaxExtensions.cs" /> <Compile Include="Extensions\ArgumentSyntaxExtensions.cs" />
<Compile Include="Extensions\BaseArgumentListSyntaxExtensions.cs" /> <Compile Include="Extensions\BaseArgumentListSyntaxExtensions.cs" />
<Compile Include="Extensions\BaseParameterListSyntaxExtensions.cs" /> <Compile Include="Extensions\BaseParameterListSyntaxExtensions.cs" />
<Compile Include="Extensions\BreakpointSpans.cs" />
<Compile Include="Extensions\CastExpressionSyntaxExtensions.cs" /> <Compile Include="Extensions\CastExpressionSyntaxExtensions.cs" />
<Compile Include="Extensions\CompilationUnitSyntaxExtensions.cs" /> <Compile Include="Extensions\CompilationUnitSyntaxExtensions.cs" />
<Compile Include="Extensions\ContextQuery\CSharpSyntaxContext.cs" /> <Compile Include="Extensions\ContextQuery\CSharpSyntaxContext.cs" />
...@@ -268,4 +267,4 @@ ...@@ -268,4 +267,4 @@
<Import Project="..\..\..\..\build\VSL.Imports.Closed.targets" /> <Import Project="..\..\..\..\build\VSL.Imports.Closed.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" /> <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
</ImportGroup> </ImportGroup>
</Project> </Project>
\ No newline at end of file
...@@ -137,7 +137,6 @@ ...@@ -137,7 +137,6 @@
<Compile Include="Editing\VisualBasicImportAdder.vb" /> <Compile Include="Editing\VisualBasicImportAdder.vb" />
<Compile Include="Extensions\ArgumentListSyntaxExtensions.vb" /> <Compile Include="Extensions\ArgumentListSyntaxExtensions.vb" />
<Compile Include="Extensions\ArgumentSyntaxExtensions.vb" /> <Compile Include="Extensions\ArgumentSyntaxExtensions.vb" />
<Compile Include="Extensions\BreakpointSpans.vb" />
<Compile Include="Extensions\CallStatementSyntaxExtensions.vb" /> <Compile Include="Extensions\CallStatementSyntaxExtensions.vb" />
<Compile Include="Extensions\CastAnalyzer.vb" /> <Compile Include="Extensions\CastAnalyzer.vb" />
<Compile Include="Extensions\CompilationUnitSyntaxExtensions.vb" /> <Compile Include="Extensions\CompilationUnitSyntaxExtensions.vb" />
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册