From ebc795d4848105846847ac2fbfee024a57aec8b8 Mon Sep 17 00:00:00 2001 From: TomasMatousek Date: Thu, 29 Jan 2015 15:34:42 -0800 Subject: [PATCH] EnC support for lambdas & closures in C# compiler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1) Change MethdCompiler.BindMethodBody to associate correct syntax with BoundBlocks it creates when binding constructor with constructor initializer call (two bound blocks are created – outer one defines a closure scope for constructor parameters, the inner one defines a closure scope for variables in the body). 2) Introduce MethodDebugId – a method ordinal and generation ordinal pair 3) Introduce LamdbaDebugInfo and ClosureDebugInfo to represent information (syntax offset) we use to figure out how to map lambdas and closures to the previous generation. 4) Adds a new PDB CDI record (#7) to store lambda and closure debug info. 5) Generalizes CalculateLocalSyntaxOffset to handle positions in field/property initializers and constructor initializers. Use it to calculate syntax offsets of lambdas and closure scopes. (TODO: rename CalculateLocalSyntaxOffset to CalculateSyntaxOffset). 6) Replace lambda and scope ordinal dispenser integers with array builders that collect LambdaDebugInfo and ClosureDebugInfo. 7) Use TryGet- pattern for all VariableSlotAllocator APIs. 8) Implements mapping of lambda method and display class names to previous generation via VariableSlotAllocator. (changeset 1407240) --- .../Binder/Binder.QueryUnboundLambdaState.cs | 37 +- .../CSharp/Portable/Binder/Binder_Query.cs | 223 +++-- .../CSharp/Portable/BoundTree/Constructors.cs | 8 - .../Portable/BoundTree/UnboundLambda.cs | 45 +- .../CSharp/Portable/CSharpCodeAnalysis.csproj | 3 +- .../Compiler/MethodBodySynthesizer.cs | 4 +- .../Portable/Compiler/MethodCompiler.cs | 308 ++++--- .../AsyncMethodToStateMachineRewriter.cs | 9 +- .../Lowering/LambdaRewriter/LambdaFrame.cs | 79 +- .../Lowering/LambdaRewriter/LambdaRewriter.cs | 239 +++-- .../LambdaRewriter/SynthesizedLambdaMethod.cs | 42 +- .../MethodToStateMachineRewriter.cs | 11 +- .../StateMachineRewriter.cs | 7 +- .../Symbols/FieldOrPropertyInitializer.cs | 9 +- .../Symbols/Source/SourceConstructorSymbol.cs | 52 +- .../Source/SourceCustomEventAccessorSymbol.cs | 2 +- .../Symbols/Source/SourceDestructorSymbol.cs | 2 +- .../Source/SourceMemberContainerSymbol.cs | 196 +++- .../Source/SourceMemberMethodSymbol.cs | 3 +- .../Source/SourceNamedTypeSymbol_Bases.cs | 2 +- .../Source/SourcePropertyAccessorSymbol.cs | 4 +- .../SourceUserDefinedConversionSymbol.cs | 3 +- .../Source/SourceUserDefinedOperatorSymbol.cs | 3 +- .../Symbols/Synthesized/GeneratedNames.cs | 2 +- .../SynthesizedInstanceConstructor.cs | 2 +- .../SynthesizedStaticConstructor.cs | 2 +- .../Portable/Syntax/CSharpSyntaxNode.cs | 11 +- .../CSharp/Portable/Syntax/LookupPosition.cs | 19 +- .../{SyntaxNodeFacts.cs => SyntaxFacts.cs} | 14 +- .../CSharp/Portable/Syntax/SyntaxNavigator.cs | 51 +- .../Portable/Syntax/SyntaxNodeExtensions.cs | 6 +- .../CSharp/Portable/Syntax/SyntaxUtilities.cs | 57 ++ .../Test/Emit/CSharpCompilerEmitTest.csproj | 1 + .../Emit/CodeGen/CodeGenFieldInitTests.cs | 3 - .../EditAndContinueClosureTests.cs | 377 +++++++- .../EditAndContinueTestBase.cs | 19 + .../CSharp/Test/Emit/PDB/PDBAsyncTests.cs | 24 + .../CSharp/Test/Emit/PDB/PDBConstantTests.cs | 4 + .../Test/Emit/PDB/PDBDynamicLocalsTests.cs | 11 + .../CSharp/Test/Emit/PDB/PDBIteratorTests.cs | 10 + .../CSharp/Test/Emit/PDB/PDBLambdaTests.cs | 840 ++++++++++++++++++ .../CSharp/Test/Emit/PDB/PDBTests.cs | 510 ++++------- .../CSharp/Test/Emit/PDB/PDBUsingTests.cs | 8 + .../Emit/CustomDebugInfoReaderTests.cs | 86 +- .../Core/Portable/CodeAnalysis.csproj | 3 + .../Core/Portable/CodeGen/ClosureDebugInfo.cs | 37 + .../Core/Portable/CodeGen/LambdaDebugInfo.cs | 57 ++ .../Portable/CodeGen/LocalSlotDebugInfo.cs | 5 + .../Core/Portable/CodeGen/MethodBody.cs | 55 +- .../Core/Portable/CodeGen/MethodDebugId.cs | 53 ++ .../Portable/CodeGen/VariableSlotAllocator.cs | 33 +- .../AddedOrChangedMethodInfo.cs | 22 +- .../Emit/EditAndContinue/DefinitionMap.cs | 58 +- .../EditAndContinue/DeltaMetadataWriter.cs | 14 +- .../EncVariableSlotAllocator.cs | 114 ++- .../Emit/EditAndContinue/SymbolChanges.cs | 17 +- .../EditAndContinueMethodDebugInformation.cs | 257 ++++-- .../Emit/NoPia/CommonEmbeddedMethod.cs | 15 + .../ImmutableArrayExtensions.cs | 30 + .../PEWriter/CustomDebugInfoConstants.cs | 1 + .../PEWriter/CustomDebugInfoWriter.cs | 30 +- .../Core/Portable/PEWriter/Members.cs | 5 + .../Core/Portable/PEWriter/PdbWriter.cs | 4 +- src/Compilers/Core/Portable/PublicAPI.txt | 2 +- .../Core/Portable/Syntax/SyntaxNode.cs | 10 + .../Utilities/CSharp/CompilingTestBase.cs | 19 +- .../Portable/BasicCodeAnalysis.vbproj | 1 + .../Portable/Compilation/MethodCompiler.vb | 5 +- ...syncRewriter.AsyncMethodToClassRewriter.vb | 12 +- .../StateMachineRewriter.vb | 9 +- .../Portable/Syntax/SyntaxNavigator.vb | 38 - .../Portable/Syntax/SyntaxUtilities.vb | 52 ++ .../Portable/Syntax/VisualBasicSyntaxNode.vb | 4 + .../EditAndContinue/SyntaxUtilitiesTests.cs | 1 + .../EditAndContinue/SyntaxUtilitiesTests.vb | 1 + src/Features/CSharp/CSharpFeatures.csproj | 3 + .../CSharpEditAndContinueAnalyzer.cs | 3 +- .../CSharp/EditAndContinue/SyntaxUtilities.cs | 75 -- src/Features/VisualBasic/BasicFeatures.vbproj | 3 + .../EditAndContinue/SyntaxUtilities.vb | 41 - .../VisualBasicEditAndContinueAnalyzer.vb | 3 +- src/Test/PdbUtilities/Pdb/PdbToXml.cs | 198 ++++- .../Shared/CustomDebugInfoReader.cs | 1 + src/Test/Utilities/AssertEx.cs | 60 +- src/Test/Utilities/PdbTestUtilities.cs | 5 +- src/Test/Utilities/RegexExtensions.cs | 18 + src/Test/Utilities/SharedCompilationUtils.cs | 8 +- src/Test/Utilities/SourceWithMarkedNodes.cs | 99 +++ src/Test/Utilities/TestUtilities.csproj | 2 + 89 files changed, 3533 insertions(+), 1298 deletions(-) rename src/Compilers/CSharp/Portable/Syntax/{SyntaxNodeFacts.cs => SyntaxFacts.cs} (96%) create mode 100644 src/Compilers/CSharp/Portable/Syntax/SyntaxUtilities.cs create mode 100644 src/Compilers/CSharp/Test/Emit/PDB/PDBLambdaTests.cs create mode 100644 src/Compilers/Core/Portable/CodeGen/ClosureDebugInfo.cs create mode 100644 src/Compilers/Core/Portable/CodeGen/LambdaDebugInfo.cs create mode 100644 src/Compilers/Core/Portable/CodeGen/MethodDebugId.cs create mode 100644 src/Compilers/VisualBasic/Portable/Syntax/SyntaxUtilities.vb create mode 100644 src/Test/Utilities/RegexExtensions.cs create mode 100644 src/Test/Utilities/SourceWithMarkedNodes.cs diff --git a/src/Compilers/CSharp/Portable/Binder/Binder.QueryUnboundLambdaState.cs b/src/Compilers/CSharp/Portable/Binder/Binder.QueryUnboundLambdaState.cs index 09d387575dcda..9f5341bc5ce55 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder.QueryUnboundLambdaState.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder.QueryUnboundLambdaState.cs @@ -10,45 +10,20 @@ namespace Microsoft.CodeAnalysis.CSharp { internal partial class Binder { - delegate BoundBlock LambdaBodyResolver(LambdaSymbol lambdaSymbol, ref Binder lambdaBodyBinder, DiagnosticBag diagnostics); + private delegate BoundBlock LambdaBodyFactory(LambdaSymbol lambdaSymbol, ref Binder lambdaBodyBinder, DiagnosticBag diagnostics); private class QueryUnboundLambdaState : UnboundLambdaState { private readonly ImmutableArray parameters; - private readonly LambdaBodyResolver bodyResolver; + private readonly LambdaBodyFactory bodyFactory; private readonly RangeVariableMap rangeVariableMap; - public QueryUnboundLambdaState(UnboundLambda unbound, Binder binder, RangeVariableMap rangeVariableMap, ImmutableArray parameters, LambdaBodyResolver bodyResolver) - : base(unbound, binder) + public QueryUnboundLambdaState(Binder binder, RangeVariableMap rangeVariableMap, ImmutableArray parameters, LambdaBodyFactory bodyFactory) + : base(binder, unboundLambdaOpt: null) { this.parameters = parameters; - this.bodyResolver = bodyResolver; this.rangeVariableMap = rangeVariableMap; - } - - public QueryUnboundLambdaState(UnboundLambda unbound, Binder binder, RangeVariableMap rangeVariableMap, ImmutableArray parameters, ExpressionSyntax body, TypeSyntax castTypeSyntax, TypeSymbol castType) - : this(unbound, binder, rangeVariableMap, parameters, (LambdaSymbol lambdaSymbol, ref Binder lambdaBodyBinder, DiagnosticBag diagnostics) => - { - BoundExpression expression = lambdaBodyBinder.BindValue(body, diagnostics, BindValueKind.RValue); - Debug.Assert((object)castType != null); - Debug.Assert(castTypeSyntax != null); - // We transform the expression from "expr" to "expr.Cast()". - expression = lambdaBodyBinder.MakeQueryInvocation(body, expression, "Cast", castTypeSyntax, castType, diagnostics); - return lambdaBodyBinder.CreateBlockFromExpression(body, lambdaBodyBinder.Locals, body, expression, diagnostics); - }) - { } - - public QueryUnboundLambdaState(UnboundLambda unbound, Binder binder, RangeVariableMap rangeVariableMap, ImmutableArray parameters, ExpressionSyntax body) - : this(unbound, binder, rangeVariableMap, parameters, (LambdaSymbol lambdaSymbol, ref Binder lambdaBodyBinder, DiagnosticBag diagnostics) => - { - return lambdaBodyBinder.BindLambdaExpressionAsBlock(body, diagnostics); - }) - { } - - internal void SetUnboundLambda(UnboundLambda unbound) - { - Debug.Assert(base.unboundLambda == null); - base.unboundLambda = unbound; + this.bodyFactory = bodyFactory; } public override string ParameterName(int index) { return parameters[index].Name; } @@ -74,7 +49,7 @@ public override Binder ParameterBinder(LambdaSymbol lambdaSymbol, Binder binder) protected override BoundBlock BindLambdaBody(LambdaSymbol lambdaSymbol, ref Binder lambdaBodyBinder, DiagnosticBag diagnostics) { - return bodyResolver(lambdaSymbol, ref lambdaBodyBinder, diagnostics); + return bodyFactory(lambdaSymbol, ref lambdaBodyBinder, diagnostics); } } } diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Query.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Query.cs index 4f8de5f04458f..31a237971397c 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Query.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Query.cs @@ -175,7 +175,7 @@ private BoundExpression FinalTranslation(QueryTranslationState state, Diagnostic // this is the unoptimized form (when v is not the identifier x) var d = DiagnosticBag.GetInstance(); BoundExpression lambdaRight = MakeQueryUnboundLambda(state.RangeVariableMap(), x, v); - result = MakeQueryInvocation(state.selectOrGroup, e, "GroupBy", Args(lambdaLeft, lambdaRight), d); + result = MakeQueryInvocation(state.selectOrGroup, e, "GroupBy", ImmutableArray.Create(lambdaLeft, lambdaRight), d); // k and v appear reversed in the invocation, so we reorder their evaluation result = ReverseLastTwoParameterOrder(result); @@ -200,7 +200,7 @@ private BoundExpression FinalTranslation(QueryTranslationState state, Diagnostic // there should have been a syntax error if we get here. return new BoundBadExpression( state.selectOrGroup, LookupResultKind.OverloadResolutionFailure, ImmutableArray.Empty, - Args(state.fromExpression), state.fromExpression.Type); + ImmutableArray.Create(state.fromExpression), state.fromExpression.Type); } } } @@ -267,14 +267,14 @@ void ReduceWhere(WhereClauseSyntax where, QueryTranslationState state, Diagnosti void ReduceJoin(JoinClauseSyntax join, QueryTranslationState state, DiagnosticBag diagnostics) { - var e2 = BindValue(join.InExpression, diagnostics, BindValueKind.RValue); + var inExpression = BindValue(join.InExpression, diagnostics, BindValueKind.RValue); // If the from expression is of the type dynamic we can't infer the types for any lambdas that occur in the query. // Only if there are none we could bind the query but we report an error regardless since such queries are not useful. - if (e2.HasDynamicType()) + if (inExpression.HasDynamicType()) { diagnostics.Add(ErrorCode.ERR_BadDynamicQuery, join.InExpression.Location); - e2 = BadExpression(join.InExpression, e2); + inExpression = BadExpression(join.InExpression, inExpression); } BoundExpression castInvocation = null; @@ -285,18 +285,16 @@ void ReduceJoin(JoinClauseSyntax join, QueryTranslationState state, DiagnosticBa // is translated into // join x in ( e ) . Cast < T > ( ) on k1 equals k2 var castType = BindTypeArgument(join.Type, diagnostics); - castInvocation = MakeQueryInvocation(join, e2, "Cast", join.Type, castType, diagnostics); - e2 = castInvocation; + castInvocation = MakeQueryInvocation(join, inExpression, "Cast", join.Type, castType, diagnostics); + inExpression = castInvocation; } - var args = ArrayBuilder.GetInstance(); - args.Add(e2); - var lambda1 = MakeQueryUnboundLambda(state.RangeVariableMap(), state.rangeVariable, join.LeftExpression); - args.Add(lambda1); + + var outerKeySelectorLambda = MakeQueryUnboundLambda(state.RangeVariableMap(), state.rangeVariable, join.LeftExpression); + var x1 = state.rangeVariable; var x2 = state.AddRangeVariable(this, join.Identifier, diagnostics); - var lambda2 = MakeQueryUnboundLambda(QueryTranslationState.RangeVariableMap(x2), x2, join.RightExpression); - args.Add(lambda2); - + var innerKeySelectorLambda = MakeQueryUnboundLambda(QueryTranslationState.RangeVariableMap(x2), x2, join.RightExpression); + if (state.clauses.IsEmpty() && state.selectOrGroup.Kind() == SyntaxKind.SelectClause) { var select = state.selectOrGroup as SelectClauseSyntax; @@ -309,9 +307,14 @@ void ReduceJoin(JoinClauseSyntax join, QueryTranslationState state, DiagnosticBa // select v // is translated into // ( e1 ) . Join( e2 , x1 => k1 , x2 => k2 , ( x1 , x2 ) => v ) - var lambda3 = MakeQueryUnboundLambda(state.RangeVariableMap(), Args(x1, x2), select.Expression); - args.Add(lambda3); - invocation = MakeQueryInvocation(join, state.fromExpression, "Join", args.ToImmutableAndFree(), diagnostics); + var resultSelectorLambda = MakeQueryUnboundLambda(state.RangeVariableMap(), ImmutableArray.Create(x1, x2), select.Expression); + + invocation = MakeQueryInvocation( + join, + state.fromExpression, + "Join", + ImmutableArray.Create(inExpression, outerKeySelectorLambda, innerKeySelectorLambda, resultSelectorLambda), + diagnostics); } else { @@ -324,16 +327,21 @@ void ReduceJoin(JoinClauseSyntax join, QueryTranslationState state, DiagnosticBa state.allRangeVariables[x2].Free(); state.allRangeVariables.Remove(x2); var g = state.AddRangeVariable(this, join.Into.Identifier, diagnostics); - var lambda3 = MakeQueryUnboundLambda(state.RangeVariableMap(), Args(x1, g), select.Expression); - args.Add(lambda3); - invocation = MakeQueryInvocation(join, state.fromExpression, "GroupJoin", args.ToImmutableAndFree(), diagnostics); - var arguments = invocation.Arguments.ToArray(); + + var resultSelectorLambda = MakeQueryUnboundLambda(state.RangeVariableMap(), ImmutableArray.Create(x1, g), select.Expression); + + invocation = MakeQueryInvocation( + join, + state.fromExpression, + "GroupJoin", + ImmutableArray.Create(inExpression, outerKeySelectorLambda, innerKeySelectorLambda, resultSelectorLambda), + diagnostics); + // record the into clause in the bound tree - arguments[arguments.Length - 1] = MakeQueryClause(join.Into, arguments[arguments.Length - 1], g); - invocation = invocation.Update( - receiverOpt: invocation.ReceiverOpt, - method: invocation.Method, - arguments: Args(arguments)); + var arguments = invocation.Arguments; + arguments = arguments.SetItem(arguments.Length - 1, MakeQueryClause(join.Into, arguments[arguments.Length - 1], g)); + + invocation = invocation.Update(invocation.ReceiverOpt, invocation.Method, arguments); } state.Clear(); // this completes the whole query @@ -353,9 +361,14 @@ void ReduceJoin(JoinClauseSyntax join, QueryTranslationState state, DiagnosticBa // from * in ( e1 ) . Join( // e2 , x1 => k1 , x2 => k2 , ( x1 , x2 ) => new { x1 , x2 }) // ... - var lambda3 = MakePairLambda(join, state, x1, x2); - args.Add(lambda3); - invocation = MakeQueryInvocation(join, state.fromExpression, "Join", args.ToImmutableAndFree(), diagnostics); + var resultSelectorLambda = MakePairLambda(join, state, x1, x2); + + invocation = MakeQueryInvocation( + join, + state.fromExpression, + "Join", + ImmutableArray.Create(inExpression, outerKeySelectorLambda, innerKeySelectorLambda, resultSelectorLambda), + diagnostics); } else { @@ -369,23 +382,28 @@ void ReduceJoin(JoinClauseSyntax join, QueryTranslationState state, DiagnosticBa // ... state.allRangeVariables[x2].Free(); state.allRangeVariables.Remove(x2); + var g = state.AddRangeVariable(this, join.Into.Identifier, diagnostics); - var lambda3 = MakePairLambda(join, state, x1, g); - args.Add(lambda3); - invocation = MakeQueryInvocation(join, state.fromExpression, "GroupJoin", args.ToImmutableAndFree(), diagnostics); - var arguments = invocation.Arguments.ToArray(); - arguments[arguments.Length - 1] = MakeQueryClause(join.Into, arguments[arguments.Length - 1], g); - invocation = invocation.Update( - receiverOpt: invocation.ReceiverOpt, - method: invocation.Method, - arguments: Args(arguments)); + var resultSelectorLambda = MakePairLambda(join, state, x1, g); + + invocation = MakeQueryInvocation( + join, + state.fromExpression, + "GroupJoin", + ImmutableArray.Create(inExpression, outerKeySelectorLambda, innerKeySelectorLambda, resultSelectorLambda), + diagnostics); + + var arguments = invocation.Arguments; + arguments = arguments.SetItem(arguments.Length - 1, MakeQueryClause(join.Into, arguments[arguments.Length - 1], g)); + + invocation = invocation.Update(invocation.ReceiverOpt, invocation.Method, arguments); } state.fromExpression = MakeQueryClause(join, invocation, x2, invocation, castInvocation); } } - void ReduceOrderBy(OrderByClauseSyntax orderby, QueryTranslationState state, DiagnosticBag diagnostics) + private void ReduceOrderBy(OrderByClauseSyntax orderby, QueryTranslationState state, DiagnosticBag diagnostics) { // A query expression with an orderby clause // from x in e @@ -403,7 +421,7 @@ void ReduceOrderBy(OrderByClauseSyntax orderby, QueryTranslationState state, Dia bool first = true; foreach (var ordering in orderby.Orderings) { - string methodName = (first ? "OrderBy" : "ThenBy") + (ordering.Kind() == SyntaxKind.DescendingOrdering ? "Descending" : ""); + string methodName = (first ? "OrderBy" : "ThenBy") + (ordering.IsKind(SyntaxKind.DescendingOrdering) ? "Descending" : ""); var lambda = MakeQueryUnboundLambda(state.RangeVariableMap(), state.rangeVariable, ordering.Expression); var invocation = MakeQueryInvocation(ordering, state.fromExpression, methodName, lambda, diagnostics); state.fromExpression = MakeQueryClause(ordering, invocation, queryInvocation: invocation); @@ -413,29 +431,50 @@ void ReduceOrderBy(OrderByClauseSyntax orderby, QueryTranslationState state, Dia state.fromExpression = MakeQueryClause(orderby, state.fromExpression); } - void ReduceFrom(FromClauseSyntax from, QueryTranslationState state, DiagnosticBag diagnostics) + private void ReduceFrom(FromClauseSyntax from, QueryTranslationState state, DiagnosticBag diagnostics) { var x1 = state.rangeVariable; - TypeSymbol castType = from.Type == null ? null : BindTypeArgument(from.Type, diagnostics); - BoundExpression lambda1 = MakeQueryUnboundLambda(state.RangeVariableMap(), x1, from.Expression, from.Type, castType); + + BoundExpression collectionSelectorLambda; + if (from.Type == null) + { + collectionSelectorLambda = MakeQueryUnboundLambda(state.RangeVariableMap(), x1, from.Expression); + } + else + { + collectionSelectorLambda = MakeQueryUnboundLambdaWithCast(state.RangeVariableMap(), x1, from.Expression, from.Type, BindTypeArgument(from.Type, diagnostics)); + } + var x2 = state.AddRangeVariable(this, from.Identifier, diagnostics); - if (state.clauses.IsEmpty() && state.selectOrGroup.Kind() == SyntaxKind.SelectClause) + if (state.clauses.IsEmpty() && state.selectOrGroup.IsKind(SyntaxKind.SelectClause)) { + var select = (SelectClauseSyntax)state.selectOrGroup; + // A query expression with a second from clause followed by a select clause // from x1 in e1 // from x2 in e2 // select v // is translated into // ( e1 ) . SelectMany( x1 => e2 , ( x1 , x2 ) => v ) - var select = state.selectOrGroup as SelectClauseSyntax; - BoundExpression lambda2 = MakeQueryUnboundLambda(state.RangeVariableMap(), Args(x1, x2), select.Expression); - var invocation = MakeQueryInvocation(from, state.fromExpression, "SelectMany", Args(lambda1, lambda2), diagnostics); - BoundExpression castInvocation = (object)castType != null ? ExtractCastInvocation(invocation) : null; - var arguments = invocation.Arguments.ToArray(); - // Adjust the second-to-last parameter to be a query clause. (if it was an extension method, an extra parameter was added) - arguments[arguments.Length - 2] = MakeQueryClause(from, arguments[arguments.Length - 2], x2, invocation, castInvocation); - invocation = invocation.Update(invocation.ReceiverOpt, invocation.Method, Args(arguments)); + var resultSelectorLambda = MakeQueryUnboundLambda(state.RangeVariableMap(), ImmutableArray.Create(x1, x2), select.Expression); + + var invocation = MakeQueryInvocation( + from, + state.fromExpression, + "SelectMany", + ImmutableArray.Create(collectionSelectorLambda, resultSelectorLambda), + diagnostics); + + // Adjust the second-to-last parameter to be a query clause (if it was an extension method, an extra parameter was added) + BoundExpression castInvocation = (from.Type != null) ? ExtractCastInvocation(invocation) : null; + + var arguments = invocation.Arguments; + invocation = invocation.Update( + invocation.ReceiverOpt, + invocation.Method, + arguments.SetItem(arguments.Length - 2, MakeQueryClause(from, arguments[arguments.Length - 2], x2, invocation, castInvocation))); + state.Clear(); state.fromExpression = MakeQueryClause(from, invocation, definedSymbol: x2, queryInvocation: invocation); state.fromExpression = MakeQueryClause(select, state.fromExpression); @@ -457,9 +496,16 @@ void ReduceFrom(FromClauseSyntax from, QueryTranslationState state, DiagnosticBa // are accessed as TRID.Item1 (or members of that), and x2 is accessed // as TRID.Item2, where TRID is the compiler-generated identifier used // to represent the transparent identifier in the result. - var lambda2 = MakePairLambda(from, state, x1, x2); - var invocation = MakeQueryInvocation(from, state.fromExpression, "SelectMany", Args(lambda1, lambda2), diagnostics); - BoundExpression castInvocation = (object)castType != null ? ExtractCastInvocation(invocation) : null; + var resultSelectorLambda = MakePairLambda(from, state, x1, x2); + + var invocation = MakeQueryInvocation( + from, + state.fromExpression, + "SelectMany", + ImmutableArray.Create(collectionSelectorLambda, resultSelectorLambda), + diagnostics); + + BoundExpression castInvocation = (from.Type != null) ? ExtractCastInvocation(invocation) : null; state.fromExpression = MakeQueryClause(from, invocation, x2, invocation, castInvocation); } } @@ -474,16 +520,19 @@ private static BoundExpression ExtractCastInvocation(BoundCall invocation) return i1; } - UnboundLambda MakePairLambda(CSharpSyntaxNode node, QueryTranslationState state, RangeVariableSymbol x1, RangeVariableSymbol x2) + private UnboundLambda MakePairLambda(CSharpSyntaxNode node, QueryTranslationState state, RangeVariableSymbol x1, RangeVariableSymbol x2) { - LambdaBodyResolver resolver = (LambdaSymbol lambdaSymbol, ref Binder lambdaBodyBinder, DiagnosticBag d) => + Debug.Assert(SyntaxFacts.IsQueryPairLambda(node)); + + LambdaBodyFactory bodyFactory = (LambdaSymbol lambdaSymbol, ref Binder lambdaBodyBinder, DiagnosticBag d) => { var x1Expression = new BoundParameter(node, lambdaSymbol.Parameters[0]) { WasCompilerGenerated = true }; var x2Expression = new BoundParameter(node, lambdaSymbol.Parameters[1]) { WasCompilerGenerated = true }; var construction = MakePair(node, x1.Name, x1Expression, x2.Name, x2Expression, state, d); return lambdaBodyBinder.CreateBlockFromExpression(node, ImmutableArray.Empty, null, construction, d); }; - var result = MakeQueryUnboundLambda(state.RangeVariableMap(), Args(x1, x2), node, resolver); + + var result = MakeQueryUnboundLambda(state.RangeVariableMap(), ImmutableArray.Create(x1, x2), node, bodyFactory); state.rangeVariable = state.TransparentRangeVariable(this); state.AddTransparentIdentifier(x1.Name); var x2m = state.allRangeVariables[x2]; @@ -491,7 +540,7 @@ UnboundLambda MakePairLambda(CSharpSyntaxNode node, QueryTranslationState state, return result; } - void ReduceLet(LetClauseSyntax let, QueryTranslationState state, DiagnosticBag diagnostics) + private void ReduceLet(LetClauseSyntax let, QueryTranslationState state, DiagnosticBag diagnostics) { // A query expression with a let clause // from x in e @@ -509,7 +558,7 @@ void ReduceLet(LetClauseSyntax let, QueryTranslationState state, DiagnosticBag d // are accessed as TRID.Item1 (or members of that), and y is accessed // as TRID.Item2, where TRID is the compiler-generated identifier used // to represent the transparent identifier in the result. - LambdaBodyResolver resolver = (LambdaSymbol lambdaSymbol, ref Binder lambdaBodyBinder, DiagnosticBag d) => + LambdaBodyFactory bodyFactory = (LambdaSymbol lambdaSymbol, ref Binder lambdaBodyBinder, DiagnosticBag d) => { var xExpression = new BoundParameter(let, lambdaSymbol.Parameters[0]) { WasCompilerGenerated = true }; @@ -543,7 +592,8 @@ void ReduceLet(LetClauseSyntax let, QueryTranslationState state, DiagnosticBag d var construction = MakePair(let, x.Name, xExpression, let.Identifier.ValueText, yExpression, state, d); return lambdaBodyBinder.CreateBlockFromExpression(let, lambdaBodyBinder.Locals, null, construction, d); }; - var lambda = MakeQueryUnboundLambda(state.RangeVariableMap(), x, let.Expression, resolver); + + var lambda = MakeQueryUnboundLambda(state.RangeVariableMap(), ImmutableArray.Create(x), let.Expression, bodyFactory); state.rangeVariable = state.TransparentRangeVariable(this); state.AddTransparentIdentifier(x.Name); var y = state.AddRangeVariable(this, let.Identifier, diagnostics); @@ -552,11 +602,6 @@ void ReduceLet(LetClauseSyntax let, QueryTranslationState state, DiagnosticBag d state.fromExpression = MakeQueryClause(let, invocation, y, invocation); } - static ImmutableArray Args(params T[] args) - { - return ImmutableArray.Create(args); - } - BoundQueryClause MakeQueryClause( CSharpSyntaxNode syntax, BoundExpression expression, @@ -594,49 +639,49 @@ BoundExpression MakePair(CSharpSyntaxNode node, string field1Name, BoundExpressi AnonymousTypeManager manager = this.Compilation.AnonymousTypeManager; NamedTypeSymbol anonymousType = manager.ConstructAnonymousTypeSymbol(typeDescriptor); - return MakeConstruction(node, anonymousType, Args(field1Value, field2Value), diagnostics); + return MakeConstruction(node, anonymousType, ImmutableArray.Create(field1Value, field2Value), diagnostics); } - TypeSymbol TypeOrError(BoundExpression e) + private TypeSymbol TypeOrError(BoundExpression e) { return e.Type ?? CreateErrorType(); } - UnboundLambda MakeQueryUnboundLambda(RangeVariableMap qvm, ImmutableArray parameters, ExpressionSyntax expression) + private UnboundLambda MakeQueryUnboundLambda(RangeVariableMap qvm, RangeVariableSymbol parameter, ExpressionSyntax expression) { - // generate the unbound lambda expression (parameters) => expression - return MakeQueryUnboundLambda(qvm, parameters, expression, null, null); + return MakeQueryUnboundLambda(qvm, ImmutableArray.Create(parameter), expression); } - UnboundLambda MakeQueryUnboundLambda(RangeVariableMap qvm, RangeVariableSymbol parameter, ExpressionSyntax expression) + private UnboundLambda MakeQueryUnboundLambda(RangeVariableMap qvm, ImmutableArray parameters, ExpressionSyntax expression) { - return MakeQueryUnboundLambda(qvm, Args(parameter), expression); + return MakeQueryUnboundLambda(expression, new QueryUnboundLambdaState(this, qvm, parameters, (LambdaSymbol lambdaSymbol, ref Binder lambdaBodyBinder, DiagnosticBag diagnostics) => + { + return lambdaBodyBinder.BindLambdaExpressionAsBlock(expression, diagnostics); + })); } - UnboundLambda MakeQueryUnboundLambda(RangeVariableMap qvm, RangeVariableSymbol parameter, ExpressionSyntax expression, TypeSyntax castTypeSyntaxOpt, TypeSymbol castTypeOpt) + private UnboundLambda MakeQueryUnboundLambdaWithCast(RangeVariableMap qvm, RangeVariableSymbol parameter, ExpressionSyntax expression, TypeSyntax castTypeSyntax, TypeSymbol castType) { - return MakeQueryUnboundLambda(qvm, Args(parameter), expression, castTypeSyntaxOpt, castTypeOpt); - } + return MakeQueryUnboundLambda(expression, new QueryUnboundLambdaState(this, qvm, ImmutableArray.Create(parameter), (LambdaSymbol lambdaSymbol, ref Binder lambdaBodyBinder, DiagnosticBag diagnostics) => + { + BoundExpression boundExpression = lambdaBodyBinder.BindValue(expression, diagnostics, BindValueKind.RValue); - UnboundLambda MakeQueryUnboundLambda(RangeVariableMap qvm, ImmutableArray parameters, ExpressionSyntax expression, TypeSyntax castTypeSyntaxOpt, TypeSymbol castTypeOpt) - { - var state = ((object)castTypeOpt == null) - ? new QueryUnboundLambdaState(null, this, qvm, parameters, expression) - : new QueryUnboundLambdaState(null, this, qvm, parameters, expression, castTypeSyntaxOpt, castTypeOpt); - var lambda = new UnboundLambda(expression, state, false) { WasCompilerGenerated = true }; - state.SetUnboundLambda(lambda); - return lambda; + // We transform the expression from "expr" to "expr.Cast()". + boundExpression = lambdaBodyBinder.MakeQueryInvocation(expression, boundExpression, "Cast", castTypeSyntax, castType, diagnostics); + + return lambdaBodyBinder.CreateBlockFromExpression(expression, lambdaBodyBinder.Locals, expression, boundExpression, diagnostics); + })); } - UnboundLambda MakeQueryUnboundLambda(RangeVariableMap qvm, RangeVariableSymbol parameter, CSharpSyntaxNode node, LambdaBodyResolver resolver) + private UnboundLambda MakeQueryUnboundLambda(RangeVariableMap qvm, ImmutableArray parameters, CSharpSyntaxNode node, LambdaBodyFactory bodyFactory) { - return MakeQueryUnboundLambda(qvm, Args(parameter), node, resolver); + return MakeQueryUnboundLambda(node, new QueryUnboundLambdaState(this, qvm, parameters, bodyFactory)); } - UnboundLambda MakeQueryUnboundLambda(RangeVariableMap qvm, ImmutableArray parameters, CSharpSyntaxNode node, LambdaBodyResolver resolver) + private UnboundLambda MakeQueryUnboundLambda(CSharpSyntaxNode node, QueryUnboundLambdaState state) { - var state = new QueryUnboundLambdaState(null, this, qvm, parameters, resolver); - var lambda = new UnboundLambda(node, state, false) { WasCompilerGenerated = true }; + Debug.Assert(node is ExpressionSyntax || SyntaxFacts.IsQueryPairLambda(node)); + var lambda = new UnboundLambda(node, state, hasErrors: false) { WasCompilerGenerated = true }; state.SetUnboundLambda(lambda); return lambda; } diff --git a/src/Compilers/CSharp/Portable/BoundTree/Constructors.cs b/src/Compilers/CSharp/Portable/BoundTree/Constructors.cs index 862789592dff6..8954fc1145cc6 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/Constructors.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/Constructors.cs @@ -465,14 +465,6 @@ public BoundBadExpression(CSharpSyntaxNode syntax, LookupResultKind resultKind, } } - internal sealed partial class BoundLambda - { - public BoundLambda(CSharpSyntaxNode syntax, BoundBlock body, ImmutableArray diagnostics, Binder binder, TypeSymbol type) - : this(syntax, (LambdaSymbol)binder.ContainingMemberOrLambda, body, diagnostics, binder, type) - { - } - } - internal partial class BoundStatementList { public static BoundStatementList Synthesized(CSharpSyntaxNode syntax, params BoundStatement[] statements) diff --git a/src/Compilers/CSharp/Portable/BoundTree/UnboundLambda.cs b/src/Compilers/CSharp/Portable/BoundTree/UnboundLambda.cs index e4f6ac68e04d0..c0dfc2eed0fc5 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/UnboundLambda.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/UnboundLambda.cs @@ -34,14 +34,23 @@ public bool InferredFromSingleType } } - public BoundLambda(CSharpSyntaxNode syntax, BoundBlock body, ImmutableArray diagnostics, Binder binder, TypeSymbol type, bool inferReturnTypeMarker) + public BoundLambda(CSharpSyntaxNode syntax, BoundBlock body, ImmutableArray diagnostics, Binder binder, TypeSymbol type, bool inferReturnType) : this(syntax, (LambdaSymbol)binder.ContainingMemberOrLambda, body, diagnostics, binder, type) { - this.inferredReturnType = InferReturnType(this.Body, this.Binder, this.Symbol.IsAsync, ref this.inferredReturnTypeUseSiteDiagnostics, out this.inferredFromSingleType); + if (inferReturnType) + { + this.inferredReturnType = InferReturnType(this.Body, this.Binder, this.Symbol.IsAsync, ref this.inferredReturnTypeUseSiteDiagnostics, out this.inferredFromSingleType); #if DEBUG - this.hasInferredReturnType = true; + this.hasInferredReturnType = true; #endif + } + + Debug.Assert( + syntax.IsAnonymousFunction() || // lambda expressions + syntax is ExpressionSyntax && SyntaxFacts.IsLambdaBody(syntax) || // query lambdas + SyntaxFacts.IsQueryPairLambda(syntax) // "pair" lambdas in queries + ); } public TypeSymbol InferredReturnType(ref HashSet useSiteDiagnostics) @@ -200,7 +209,7 @@ public UnboundLambda( internal abstract class UnboundLambdaState { - protected UnboundLambda unboundLambda; // we would prefer this readonly, but we have an initialization cycle. + private UnboundLambda unboundLambda; // we would prefer this readonly, but we have an initialization cycle. protected readonly Binder binder; private readonly ConcurrentDictionary bindingCache = new ConcurrentDictionary(); @@ -209,12 +218,24 @@ internal abstract class UnboundLambdaState private BoundLambda errorBinding; - public UnboundLambdaState(UnboundLambda unboundLambda, Binder binder) + public UnboundLambdaState(Binder binder, UnboundLambda unboundLambdaOpt) { - this.unboundLambda = unboundLambda; + Debug.Assert(binder != null); + + // might be initialized later (for query lambdas) + this.unboundLambda = unboundLambdaOpt; this.binder = binder; } + public void SetUnboundLambda(UnboundLambda unbound) + { + Debug.Assert(unbound != null); + Debug.Assert(unboundLambda == null); + unboundLambda = unbound; + } + + public UnboundLambda UnboundLambda => unboundLambda; + public abstract MessageID MessageID { get; } public abstract string ParameterName(int index); public abstract bool HasSignature { get; } @@ -368,7 +389,7 @@ private BoundLambda ReallyBind(NamedTypeSymbol delegateType) SourceMemberMethodSymbol.ReportAsyncParameterErrors(lambdaSymbol, diagnostics, lambdaSymbol.Locations[0]); } - var result = new BoundLambda(this.unboundLambda.Syntax, block, diagnostics.ToReadOnlyAndFree(), lambdaBodyBinder, delegateType) + var result = new BoundLambda(this.unboundLambda.Syntax, block, diagnostics.ToReadOnlyAndFree(), lambdaBodyBinder, delegateType, inferReturnType: false) { WasCompilerGenerated = this.unboundLambda.WasCompilerGenerated }; return result; @@ -405,7 +426,7 @@ private BoundLambda ReallyInferReturnType(NamedTypeSymbol delegateType) Binder lambdaBodyBinder = new ExecutableCodeBinder(this.unboundLambda.Syntax, lambdaSymbol, ParameterBinder(lambdaSymbol, binder)); var block = BindLambdaBody(lambdaSymbol, ref lambdaBodyBinder, diagnostics); - var result = new BoundLambda(this.unboundLambda.Syntax, block, diagnostics.ToReadOnlyAndFree(), lambdaBodyBinder, delegateType, inferReturnTypeMarker: true) + var result = new BoundLambda(this.unboundLambda.Syntax, block, diagnostics.ToReadOnlyAndFree(), lambdaBodyBinder, delegateType, inferReturnType: true) { WasCompilerGenerated = this.unboundLambda.WasCompilerGenerated }; HashSet useSiteDiagnostics = null; // TODO: figure out if this should be somehow merged into BoundLambda.Diagnostics. @@ -672,7 +693,7 @@ internal PlainUnboundLambdaState( ImmutableArray parameterTypes, ImmutableArray parameterRefKinds, bool isAsync) - : base(unboundLambda, binder) + : base(binder, unboundLambda) { this.parameterNames = parameterNames; this.parameterTypes = parameterTypes; @@ -688,13 +709,13 @@ internal PlainUnboundLambdaState( public override bool IsAsync { get { return this.isAsync; } } - public override MessageID MessageID { get { return this.unboundLambda.Syntax.Kind() == SyntaxKind.AnonymousMethodExpression ? MessageID.IDS_AnonMethod : MessageID.IDS_Lambda; } } + public override MessageID MessageID { get { return this.UnboundLambda.Syntax.Kind() == SyntaxKind.AnonymousMethodExpression ? MessageID.IDS_AnonMethod : MessageID.IDS_Lambda; } } private CSharpSyntaxNode Body { get { - var Syntax = unboundLambda.Syntax; + var Syntax = UnboundLambda.Syntax; switch (Syntax.Kind()) { default: @@ -711,7 +732,7 @@ private CSharpSyntaxNode Body public override Location ParameterLocation(int index) { Debug.Assert(HasSignature && 0 <= index && index < ParameterCount); - var Syntax = unboundLambda.Syntax; + var Syntax = UnboundLambda.Syntax; switch (Syntax.Kind()) { default: diff --git a/src/Compilers/CSharp/Portable/CSharpCodeAnalysis.csproj b/src/Compilers/CSharp/Portable/CSharpCodeAnalysis.csproj index fc0c67f2edbd7..34c4230386e1e 100644 --- a/src/Compilers/CSharp/Portable/CSharpCodeAnalysis.csproj +++ b/src/Compilers/CSharp/Portable/CSharpCodeAnalysis.csproj @@ -793,6 +793,7 @@ + @@ -808,7 +809,7 @@ - + diff --git a/src/Compilers/CSharp/Portable/Compiler/MethodBodySynthesizer.cs b/src/Compilers/CSharp/Portable/Compiler/MethodBodySynthesizer.cs index 4971480bfe346..578110b6a966e 100644 --- a/src/Compilers/CSharp/Portable/Compiler/MethodBodySynthesizer.cs +++ b/src/Compilers/CSharp/Portable/Compiler/MethodBodySynthesizer.cs @@ -519,8 +519,10 @@ internal static BoundBlock ConstructFieldLikeEventAccessorBody_Regular(SourceEve } - internal static BoundBlock ConstructDestructorBody(CSharpSyntaxNode syntax, MethodSymbol method, BoundBlock block) + internal static BoundBlock ConstructDestructorBody(MethodSymbol method, BoundBlock block) { + var syntax = block.Syntax; + Debug.Assert(method.MethodKind == MethodKind.Destructor); Debug.Assert(syntax.Kind() == SyntaxKind.Block); diff --git a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs index dd513e61a4523..1cab85418c4b5 100644 --- a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs +++ b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs @@ -225,10 +225,15 @@ internal static MethodSymbol DefineScriptEntryPoint(CSharpCompilation compilatio { var body = scriptEntryPoint.CreateBody(); + const int methodOrdinal = -1; + var emittedBody = GenerateMethodBody( moduleBeingBuilt, scriptEntryPoint, + methodOrdinal, body, + ImmutableArray.Empty, + ImmutableArray.Empty, stateMachineTypeOpt: null, variableSlotAllocatorOpt: null, diagnostics: diagnostics, @@ -612,7 +617,10 @@ private void CompileSynthesizedMethods(TypeCompilationState compilationState) emittedBody = GenerateMethodBody( moduleBeingBuiltOpt, method, + methodOrdinal, bodyWithoutAsync, + ImmutableArray.Empty, + ImmutableArray.Empty, stateMachineType, variableSlotAllocatorOpt, diagnosticsThisMethod, @@ -705,10 +713,15 @@ private void CompileFieldLikeEventAccessor(SourceEventSymbol eventSymbol, bool i // or if had declaration errors - we will fail anyways, but if some types are bad enough, generating may produce duplicate errors about that. if (!hasErrors && !hasDeclarationErrors) { + const int accessorOrdinal = -1; + MethodBody emittedBody = GenerateMethodBody( moduleBeingBuiltOpt, accessor, + accessorOrdinal, boundBody, + ImmutableArray.Empty, + ImmutableArray.Empty, stateMachineTypeOpt: null, variableSlotAllocatorOpt: null, diagnostics: diagnosticsThisMethod, @@ -941,134 +954,152 @@ private void CompileMethod( // and will not be reported to callers Compilation.GetDiagnostics() bool hasBody = flowAnalyzedBody != null; - VariableSlotAllocator variableSlotAllocatorOpt = null; + VariableSlotAllocator lazyVariableSlotAllocator = null; StateMachineTypeSymbol stateMachineTypeOpt = null; - int lambdaOrdinalDispenser = 0; - int scopeOrdinalDispenser = 0; + var lambdaDebugInfoBuilder = ArrayBuilder.GetInstance(); + var closureDebugInfoBuilder = ArrayBuilder.GetInstance(); BoundStatement loweredBodyOpt = null; - if (hasBody) - { - loweredBodyOpt = LowerBodyOrInitializer( - methodSymbol, - methodOrdinal, - flowAnalyzedBody, - previousSubmissionFields, - compilationState, - diagsForCurrentMethod, - ref lambdaOrdinalDispenser, - ref scopeOrdinalDispenser, - out stateMachineTypeOpt, - out variableSlotAllocatorOpt); - - Debug.Assert(loweredBodyOpt != null); - } - else - { - loweredBodyOpt = null; - } - - hasErrors = hasErrors || (hasBody && loweredBodyOpt.HasErrors) || diagsForCurrentMethod.HasAnyErrors(); - SetGlobalErrorIfTrue(hasErrors); - - // don't emit if the resulting method would contain initializers with errors - if (!hasErrors && (hasBody || includeInitializersInBody)) + try { - // Fields must be initialized before constructor initializer (which is the first statement of the analyzed body, if specified), - // so that the initialization occurs before any method overridden by the declaring class can be invoked from the base constructor - // and access the fields. - - ImmutableArray boundStatements; - - if (methodSymbol.IsScriptConstructor) + if (hasBody) { - boundStatements = MethodBodySynthesizer.ConstructScriptConstructorBody(loweredBodyOpt, methodSymbol, previousSubmissionFields, compilation); + loweredBodyOpt = LowerBodyOrInitializer( + methodSymbol, + methodOrdinal, + flowAnalyzedBody, + previousSubmissionFields, + compilationState, + diagsForCurrentMethod, + ref lazyVariableSlotAllocator, + lambdaDebugInfoBuilder, + closureDebugInfoBuilder, + out stateMachineTypeOpt); + + Debug.Assert(loweredBodyOpt != null); } else { - boundStatements = ImmutableArray.Empty; + loweredBodyOpt = null; + } + + hasErrors = hasErrors || (hasBody && loweredBodyOpt.HasErrors) || diagsForCurrentMethod.HasAnyErrors(); + SetGlobalErrorIfTrue(hasErrors); + + // don't emit if the resulting method would contain initializers with errors + if (!hasErrors && (hasBody || includeInitializersInBody)) + { + // Fields must be initialized before constructor initializer (which is the first statement of the analyzed body, if specified), + // so that the initialization occurs before any method overridden by the declaring class can be invoked from the base constructor + // and access the fields. - if (analyzedInitializers != null) + ImmutableArray boundStatements; + + if (methodSymbol.IsScriptConstructor) { - processedInitializers.LoweredInitializers = (BoundStatementList)LowerBodyOrInitializer( - methodSymbol, - methodOrdinal, - analyzedInitializers, - previousSubmissionFields, - compilationState, - diagsForCurrentMethod, - ref lambdaOrdinalDispenser, - ref scopeOrdinalDispenser, - out stateMachineTypeOpt, - out variableSlotAllocatorOpt); + boundStatements = MethodBodySynthesizer.ConstructScriptConstructorBody(loweredBodyOpt, methodSymbol, previousSubmissionFields, compilation); + } + else + { + boundStatements = ImmutableArray.Empty; - // initializers can't produce state machines - Debug.Assert(stateMachineTypeOpt == null); + if (analyzedInitializers != null) + { + StateMachineTypeSymbol initializerStateMachineTypeOpt = null; + + processedInitializers.LoweredInitializers = (BoundStatementList)LowerBodyOrInitializer( + methodSymbol, + methodOrdinal, + analyzedInitializers, + previousSubmissionFields, + compilationState, + diagsForCurrentMethod, + ref lazyVariableSlotAllocator, + lambdaDebugInfoBuilder, + closureDebugInfoBuilder, + out initializerStateMachineTypeOpt); + + // initializers can't produce state machines + Debug.Assert((object)initializerStateMachineTypeOpt == null); + + Debug.Assert(processedInitializers.LoweredInitializers.Kind == BoundKind.StatementList); + Debug.Assert(!hasErrors); + hasErrors = processedInitializers.LoweredInitializers.HasAnyErrors || diagsForCurrentMethod.HasAnyErrors(); + SetGlobalErrorIfTrue(hasErrors); + + if (hasErrors) + { + this.diagnostics.AddRange(diagsForCurrentMethod); + return; + } + } - Debug.Assert(processedInitializers.LoweredInitializers.Kind == BoundKind.StatementList); - Debug.Assert(!hasErrors); - hasErrors = processedInitializers.LoweredInitializers.HasAnyErrors || diagsForCurrentMethod.HasAnyErrors(); - SetGlobalErrorIfTrue(hasErrors); + // initializers for global code have already been included in the body + if (includeInitializersInBody) + { + boundStatements = boundStatements.Concat(processedInitializers.LoweredInitializers.Statements); + } - if (hasErrors) + if (hasBody) { - this.diagnostics.AddRange(diagsForCurrentMethod); - return; + boundStatements = boundStatements.Concat(ImmutableArray.Create(loweredBodyOpt)); } } - // initializers for global code have already been included in the body - if (includeInitializersInBody) + // generated struct constructors should ensure that all fields are assigned (even those that do not have initializers) + var container = methodSymbol.ContainingType as SourceMemberContainerTypeSymbol; + if (container != null && + container.IsStructType() && + methodSymbol.IsImplicitInstanceConstructor) { - boundStatements = boundStatements.Concat(processedInitializers.LoweredInitializers.Statements); - } + StateMachineTypeSymbol ctorStateMachineTypeOpt; - if (hasBody) - { - boundStatements = boundStatements.Concat(ImmutableArray.Create(loweredBodyOpt)); + var chain = ChainImplicitStructConstructor(methodSymbol, container); + chain = LowerBodyOrInitializer( + methodSymbol, + methodOrdinal, + chain, + previousSubmissionFields, + compilationState, + diagsForCurrentMethod, + ref lazyVariableSlotAllocator, + lambdaDebugInfoBuilder, + closureDebugInfoBuilder, + out ctorStateMachineTypeOpt); + + // construcot can't produce state machine + Debug.Assert((object)ctorStateMachineTypeOpt == null); + + boundStatements = boundStatements.Insert(0, chain); } - } - // generated struct constructors should ensure that all fields are assigned (even those that do not have initializers) - var container = methodSymbol.ContainingType as SourceMemberContainerTypeSymbol; - if (container != null && - container.IsStructType() && - methodSymbol.IsImplicitInstanceConstructor) - { - var chain = ChainImplicitStructConstructor(methodSymbol, container); - chain = LowerBodyOrInitializer( + CSharpSyntaxNode syntax = methodSymbol.GetNonNullSyntaxNode(); + + var boundBody = BoundStatementList.Synthesized(syntax, boundStatements); + + var emittedBody = GenerateMethodBody( + moduleBeingBuiltOpt, methodSymbol, methodOrdinal, - chain, - previousSubmissionFields, - compilationState, + boundBody, + lambdaDebugInfoBuilder.ToImmutable(), + closureDebugInfoBuilder.ToImmutable(), + stateMachineTypeOpt, + lazyVariableSlotAllocator, diagsForCurrentMethod, - ref lambdaOrdinalDispenser, - ref scopeOrdinalDispenser, - out stateMachineTypeOpt, - out variableSlotAllocatorOpt); + debugDocumentProvider, + GetNamespaceScopes(methodSymbol, debugImports)); - boundStatements = boundStatements.Insert(0, chain); + moduleBeingBuiltOpt.SetMethodBody(methodSymbol.PartialDefinitionPart ?? methodSymbol, emittedBody); } - CSharpSyntaxNode syntax = methodSymbol.GetNonNullSyntaxNode(); - - var boundBody = BoundStatementList.Synthesized(syntax, boundStatements); - - var emittedBody = GenerateMethodBody( - moduleBeingBuiltOpt, - methodSymbol, - boundBody, - stateMachineTypeOpt, - variableSlotAllocatorOpt, - diagsForCurrentMethod, - debugDocumentProvider, - GetNamespaceScopes(methodSymbol, debugImports)); - - moduleBeingBuiltOpt.SetMethodBody(methodSymbol.PartialDefinitionPart ?? methodSymbol, emittedBody); + this.diagnostics.AddRange(diagsForCurrentMethod); + } + finally + { + lambdaDebugInfoBuilder.Free(); + closureDebugInfoBuilder.Free(); } - - this.diagnostics.AddRange(diagsForCurrentMethod); } finally { @@ -1107,14 +1138,13 @@ internal static BoundStatement LowerBodyOrInitializer( SynthesizedSubmissionFields previousSubmissionFields, TypeCompilationState compilationState, DiagnosticBag diagnostics, - ref int lambdaOrdinalDispenser, - ref int scopeOrdinalDispenser, - out StateMachineTypeSymbol stateMachineTypeOpt, - out VariableSlotAllocator variableSlotAllocatorOpt) + ref VariableSlotAllocator lazyVariableSlotAllocator, + ArrayBuilder lambdaDebugInfoBuilder, + ArrayBuilder closureDebugInfoBuilder, + out StateMachineTypeSymbol stateMachineTypeOpt) { Debug.Assert(compilationState.ModuleBuilderOpt != null); stateMachineTypeOpt = null; - variableSlotAllocatorOpt = null; if (body.HasErrors) { @@ -1162,16 +1192,26 @@ internal static BoundStatement LowerBodyOrInitializer( return loweredBody; } - variableSlotAllocatorOpt = compilationState.ModuleBuilderOpt.TryCreateVariableSlotAllocator(method); + if (lazyVariableSlotAllocator == null) + { + lazyVariableSlotAllocator = compilationState.ModuleBuilderOpt.TryCreateVariableSlotAllocator(method); + } BoundStatement bodyWithoutLambdas = loweredBody; if (sawLambdas) { - var lambdaAnalysis = LambdaRewriter.Analysis.Analyze(loweredBody, method); - if (lambdaAnalysis.SeenLambda) - { - bodyWithoutLambdas = LambdaRewriter.Rewrite(loweredBody, method.ContainingType, method.ThisParameter, method, methodOrdinal, ref lambdaOrdinalDispenser, ref scopeOrdinalDispenser, variableSlotAllocatorOpt, compilationState, diagnostics, lambdaAnalysis); - } + bodyWithoutLambdas = LambdaRewriter.Rewrite( + loweredBody, + method.ContainingType, + method.ThisParameter, + method, + methodOrdinal, + lambdaDebugInfoBuilder, + closureDebugInfoBuilder, + lazyVariableSlotAllocator, + compilationState, + diagnostics, + assignLocals: false); } if (bodyWithoutLambdas.HasErrors) @@ -1180,7 +1220,7 @@ internal static BoundStatement LowerBodyOrInitializer( } IteratorStateMachine iteratorStateMachine; - BoundStatement bodyWithoutIterators = IteratorRewriter.Rewrite(bodyWithoutLambdas, method, methodOrdinal, variableSlotAllocatorOpt, compilationState, diagnostics, out iteratorStateMachine); + BoundStatement bodyWithoutIterators = IteratorRewriter.Rewrite(bodyWithoutLambdas, method, methodOrdinal, lazyVariableSlotAllocator, compilationState, diagnostics, out iteratorStateMachine); if (bodyWithoutIterators.HasErrors) { @@ -1188,7 +1228,7 @@ internal static BoundStatement LowerBodyOrInitializer( } AsyncStateMachine asyncStateMachine; - BoundStatement bodyWithoutAsync = AsyncRewriter.Rewrite(bodyWithoutIterators, method, methodOrdinal, variableSlotAllocatorOpt, compilationState, diagnostics, out asyncStateMachine); + BoundStatement bodyWithoutAsync = AsyncRewriter.Rewrite(bodyWithoutIterators, method, methodOrdinal, lazyVariableSlotAllocator, compilationState, diagnostics, out asyncStateMachine); Debug.Assert(iteratorStateMachine == null || asyncStateMachine == null); stateMachineTypeOpt = (StateMachineTypeSymbol)iteratorStateMachine ?? asyncStateMachine; @@ -1199,7 +1239,10 @@ internal static BoundStatement LowerBodyOrInitializer( private static MethodBody GenerateMethodBody( PEModuleBuilder moduleBuilder, MethodSymbol method, + int methodOrdinal, BoundStatement block, + ImmutableArray lambdaDebugInfo, + ImmutableArray closureDebugInfo, StateMachineTypeSymbol stateMachineTypeOpt, VariableSlotAllocator variableSlotAllocatorOpt, DiagnosticBag diagnostics, @@ -1286,6 +1329,7 @@ private static MethodBody GenerateMethodBody( builder.RealizedIL, builder.MaxStack, method.PartialDefinitionPart ?? method, + methodOrdinal, localVariables, builder.RealizedSequencePoints, debugDocumentProvider, @@ -1294,6 +1338,8 @@ private static MethodBody GenerateMethodBody( builder.HasDynamicLocal, namespaceScopes, Cci.NamespaceScopeEncoding.InPlace, + lambdaDebugInfo, + closureDebugInfo, stateMachineTypeOpt?.Name, stateMachineHoistedLocalScopes, stateMachineHoistedLocalSlots, @@ -1470,33 +1516,35 @@ private static BoundBlock BindMethodBody(MethodSymbol method, TypeCompilationSta body = null; } - var statements = ArrayBuilder.GetInstance(); + ImmutableArray statements; - if (constructorInitializer != null) + if (constructorInitializer == null) { - statements.Add(constructorInitializer); - } + if (body != null) + { + // most common case - we just have a single block for the body. + if (method.MethodKind == MethodKind.Destructor) + { + return MethodBodySynthesizer.ConstructDestructorBody(method, body); + } + else + { + return body; + } + } - if (body != null) - { - statements.Add(body); + statements = ImmutableArray.Empty; } - - CSharpSyntaxNode syntax = body != null ? body.Syntax : method.GetNonNullSyntaxNode(); - BoundBlock block; - - if (statements.Count == 1 && statements[0].Kind == ((body == null) ? BoundKind.Block : body.Kind)) + else if (body == null) { - // most common case - we just have a single block for the body. - block = (BoundBlock)statements[0]; - statements.Free(); + statements = ImmutableArray.Create(constructorInitializer); } else { - block = new BoundBlock(syntax, ImmutableArray.Empty, statements.ToImmutableAndFree()) { WasCompilerGenerated = true }; + statements = ImmutableArray.Create(constructorInitializer, body); } - return method.MethodKind == MethodKind.Destructor ? MethodBodySynthesizer.ConstructDestructorBody(syntax, method, block) : block; + return new BoundBlock(method.GetNonNullSyntaxNode(), ImmutableArray.Empty, statements) { WasCompilerGenerated = true }; } /// diff --git a/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncMethodToStateMachineRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncMethodToStateMachineRewriter.cs index 30a470aebba80..6c111f1de2ecb 100644 --- a/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncMethodToStateMachineRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncMethodToStateMachineRewriter.cs @@ -95,13 +95,8 @@ private FieldSymbol GetAwaiterField(TypeSymbol awaiterType) // to find the previous awaiter field. if (!awaiterFields.TryGetValue(awaiterType, out result)) { - int slotIndex = -1; - if (slotAllocatorOpt != null) - { - slotIndex = slotAllocatorOpt.GetPreviousAwaiterSlotIndex((Cci.ITypeReference)awaiterType); - } - - if (slotIndex == -1) + int slotIndex; + if (slotAllocatorOpt == null || !slotAllocatorOpt.TryGetPreviousAwaiterSlotIndex((Cci.ITypeReference)awaiterType, out slotIndex)) { slotIndex = nextAwaiterId++; } diff --git a/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/LambdaFrame.cs b/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/LambdaFrame.cs index 9d561c058c74d..4d07136867a40 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/LambdaFrame.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/LambdaFrame.cs @@ -20,112 +20,129 @@ internal sealed class LambdaFrame : SynthesizedContainer, ISynthesizedMethodBody private readonly MethodSymbol staticConstructor; private readonly FieldSymbol singletonCache; internal readonly CSharpSyntaxNode ScopeSyntaxOpt; + internal readonly int ClosureOrdinal; - internal LambdaFrame(VariableSlotAllocator slotAllocatorOpt, TypeCompilationState compilationState, MethodSymbol topLevelMethod, int methodOrdinal, CSharpSyntaxNode scopeSyntax, int scopeOrdinal, bool isStatic) - : base(MakeName(slotAllocatorOpt, compilationState, methodOrdinal, scopeOrdinal, isStatic), topLevelMethod) + internal LambdaFrame(VariableSlotAllocator slotAllocatorOpt, MethodSymbol topLevelMethod, MethodDebugId methodId, CSharpSyntaxNode scopeSyntaxOpt, int closureOrdinal) + : base(MakeName(slotAllocatorOpt, scopeSyntaxOpt, methodId, closureOrdinal), topLevelMethod) { this.topLevelMethod = topLevelMethod; this.constructor = new LambdaFrameConstructor(this); + this.ClosureOrdinal = closureOrdinal; // static lambdas technically have the class scope so the scope syntax is null - if (isStatic) + if (scopeSyntaxOpt == null) { this.staticConstructor = new SynthesizedStaticConstructor(this); var cacheVariableName = GeneratedNames.MakeCachedFrameInstanceFieldName(); singletonCache = new SynthesizedLambdaCacheFieldSymbol(this, this, cacheVariableName, topLevelMethod, isReadOnly: true, isStatic: true); - this.ScopeSyntaxOpt = null; - } - else - { - this.ScopeSyntaxOpt = scopeSyntax; } - AssertIsLambdaScopeSyntax(this.ScopeSyntaxOpt); + AssertIsLambdaScopeSyntax(scopeSyntaxOpt); + this.ScopeSyntaxOpt = scopeSyntaxOpt; } - private static string MakeName(VariableSlotAllocator slotAllocatorOpt, TypeCompilationState compilationState, int methodOrdinal, int scopeOrdinal, bool isStatic) + private static string MakeName(VariableSlotAllocator slotAllocatorOpt, SyntaxNode scopeSyntaxOpt, MethodDebugId methodId, int closureOrdinal) { - // TODO: slotAllocatorOpt?.GetPrevious() - - int generation = compilationState.ModuleBuilderOpt.CurrentGenerationOrdinal; + if (scopeSyntaxOpt == null) + { + // Display class is shared among static non-generic lambdas accross generations, method ordinal is -1 in that case. + // A new display class of a static generic lambda is created for each method and each generation. + return GeneratedNames.MakeStaticLambdaDisplayClassName(methodId.Ordinal, methodId.Generation); + } - if (isStatic) + int previousClosureOrdinal; + if (slotAllocatorOpt != null && slotAllocatorOpt.TryGetPreviousClosure(scopeSyntaxOpt, out previousClosureOrdinal)) { - // Display class is shared among static non-generic lambdas and also accross generations, method ordinal is -1. - Debug.Assert(methodOrdinal >= -1); - return GeneratedNames.MakeStaticLambdaDisplayClassName(methodOrdinal, generation); + methodId = slotAllocatorOpt.PreviousMethodId; + closureOrdinal = previousClosureOrdinal; } - Debug.Assert(methodOrdinal >= 0); - return GeneratedNames.MakeLambdaDisplayClassName(methodOrdinal, generation, scopeOrdinal); + // If we haven't found existing closure in the previous generation, use the current generation method ordinal. + // That is, don't try to reuse previous generation method ordinal as that might create name conflict. + // E.g. + // Gen0 Gen1 + // F() { new closure } // ordinal 0 + // G() { } // ordinal 0 G() { new closure } // ordinal 1 + // + // In the example above G is updated and F is added. + // G's ordinal in Gen0 is 0. If we used that ordinal for updated G's new closure it would conflict with F's ordinal. + + Debug.Assert(methodId.Ordinal >= 0); + return GeneratedNames.MakeLambdaDisplayClassName(methodId.Ordinal, methodId.Generation, closureOrdinal); } [Conditional("DEBUG")] - private static void AssertIsLambdaScopeSyntax(CSharpSyntaxNode syntax) + private static void AssertIsLambdaScopeSyntax(CSharpSyntaxNode syntaxOpt) { // See C# specification, chapter 3.7 Scopes. // static lambdas technically have the class scope so the scope syntax is null - if (syntax == null) + if (syntaxOpt == null) { return; } // block: - if (syntax.IsKind(SyntaxKind.Block)) + if (syntaxOpt.IsKind(SyntaxKind.Block)) { return; } // switch block: - if (syntax.IsKind(SyntaxKind.SwitchStatement)) + if (syntaxOpt.IsKind(SyntaxKind.SwitchStatement)) { return; } // expression-bodied member: - if (syntax.IsKind(SyntaxKind.ArrowExpressionClause)) + if (syntaxOpt.IsKind(SyntaxKind.ArrowExpressionClause)) { return; } // catch clause (including filter): - if (syntax.IsKind(SyntaxKind.CatchClause)) + if (syntaxOpt.IsKind(SyntaxKind.CatchClause)) { return; } // class/struct containing a field/property with a declaration expression - if (syntax.IsKind(SyntaxKind.ClassDeclaration) || syntax.IsKind(SyntaxKind.StructDeclaration)) + if (syntaxOpt.IsKind(SyntaxKind.ClassDeclaration) || syntaxOpt.IsKind(SyntaxKind.StructDeclaration)) { return; } // lambda in a let clause, // e.g. from item in array let a = new Func(() => item) - if (syntax.IsKind(SyntaxKind.LetClause)) + if (syntaxOpt.IsKind(SyntaxKind.LetClause)) { return; } - if (IsStatementWithEmbeddedStatementBody(syntax.Kind())) + if (IsStatementWithEmbeddedStatementBody(syntaxOpt.Kind())) { return; } // lambda bodies: - if (SyntaxFacts.IsLambdaBody(syntax)) + if (SyntaxFacts.IsLambdaBody(syntaxOpt)) + { + return; + } + + // lambda in a ctor initializer that refers to a ctor parameter + if (syntaxOpt.IsKind(SyntaxKind.ConstructorDeclaration)) { return; } // TODO: EE expression - if (syntax is ExpressionSyntax && syntax.Parent.Parent == null) + if (syntaxOpt is ExpressionSyntax && syntaxOpt.Parent.Parent == null) { return; } - throw ExceptionUtilities.UnexpectedValue(syntax.Kind()); + throw ExceptionUtilities.UnexpectedValue(syntaxOpt.Kind()); } private static bool IsStatementWithEmbeddedStatementBody(SyntaxKind syntax) diff --git a/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/LambdaRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/LambdaRewriter.cs index 722ed46fce952..0e1734aebb873 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/LambdaRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/LambdaRewriter.cs @@ -1,4 +1,4 @@ -// 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. #if DEBUG //#define CHECK_LOCALS // define CHECK_LOCALS to help debug some rewriting problems that would otherwise cause code-gen failures @@ -10,6 +10,7 @@ using System.Diagnostics; using Microsoft.CodeAnalysis.CodeGen; using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp @@ -84,7 +85,7 @@ sealed partial class LambdaRewriter : MethodToClassRewriter // The "this" symbol for the current method. private ParameterSymbol currentFrameThis; - private int lambdaOrdinalDispenser; + private ArrayBuilder lambdaDebugInfoBuilder; // ID dispenser for field names of frame references private int synthesizedFieldNameIdDispenser; @@ -123,7 +124,7 @@ private LambdaRewriter( ParameterSymbol thisParameterOpt, MethodSymbol method, int methodOrdinal, - int lambdaOrdinalDispenser, + ArrayBuilder lambdaDebugInfoBuilder, VariableSlotAllocator slotAllocatorOpt, TypeCompilationState compilationState, DiagnosticBag diagnostics, @@ -138,7 +139,7 @@ private LambdaRewriter( this.topLevelMethod = method; this.topLevelMethodOrdinal = methodOrdinal; - this.lambdaOrdinalDispenser = lambdaOrdinalDispenser; + this.lambdaDebugInfoBuilder = lambdaDebugInfoBuilder; this.currentMethod = method; this.analysis = analysis; this.assignLocals = assignLocals; @@ -162,55 +163,63 @@ protected override bool NeedsProxy(Symbol localOrParameter) /// MethodBodyCompiler. See this class' documentation /// for a more thorough explanation of the algorithm and its use by clients. /// - /// The bound node to be rewritten + /// The bound node to be rewritten /// The type of the top-most frame /// The "this" parameter in the top-most frame, or null if static method /// The containing method of the node to be rewritten /// Index of the method symbol in its containing type member list. - /// The current lambda ordinal. - /// The current closure scope ordinal. + /// Information on lambdas defined in needed for debugging. + /// Information on closures defined in needed for debugging. /// Slot allocator. /// The caller's buffer into which we produce additional methods to be emitted by the caller /// Diagnostic bag for diagnostics - /// A caller-provided analysis of the node's lambdas /// The rewritten tree should include assignments of the original locals to the lifted proxies public static BoundStatement Rewrite( - BoundStatement node, + BoundStatement loweredBody, NamedTypeSymbol thisType, ParameterSymbol thisParameter, MethodSymbol method, int methodOrdinal, - ref int lambdaOrdinalDispenser, - ref int scopeOrdinalDispenser, + ArrayBuilder lambdaDebugInfoBuilder, + ArrayBuilder closureDebugInfoBuilder, VariableSlotAllocator slotAllocatorOpt, TypeCompilationState compilationState, DiagnosticBag diagnostics, - Analysis analysis, - bool assignLocals = false) + bool assignLocals) { Debug.Assert((object)thisType != null); Debug.Assert(((object)thisParameter == null) || (thisParameter.Type == thisType)); + Debug.Assert(compilationState.ModuleBuilderOpt != null); + + var analysis = Analysis.Analyze(loweredBody, method); + if (!analysis.SeenLambda) + { + // Unreachable anonymous functions are ignored by the analyzer. + // No closures or lambda methods are generated. + // E.g. + // int y = 0; + // var b = false && from z in new X(y) select f(z + y) + return loweredBody; + } - CheckLocalsDefined(node); + CheckLocalsDefined(loweredBody); var rewriter = new LambdaRewriter( analysis, thisType, thisParameter, method, methodOrdinal, - lambdaOrdinalDispenser, + lambdaDebugInfoBuilder, slotAllocatorOpt, compilationState, diagnostics, assignLocals); analysis.ComputeLambdaScopesAndFrameCaptures(); - rewriter.MakeFrames(ref scopeOrdinalDispenser); - var body = rewriter.AddStatementsIfNeeded((BoundStatement)rewriter.Visit(node)); + rewriter.MakeFrames(closureDebugInfoBuilder); + var body = rewriter.AddStatementsIfNeeded((BoundStatement)rewriter.Visit(loweredBody)); CheckLocalsDefined(body); - // Lambdas created during the rewriter are assigned indices and the dispenser field is updated. - lambdaOrdinalDispenser = rewriter.lambdaOrdinalDispenser; return body; } @@ -255,7 +264,7 @@ protected override NamedTypeSymbol ContainingType /// /// Create the frame types. /// - private void MakeFrames(ref int scopeOrdinalDispenser) + private void MakeFrames(ArrayBuilder closureDebugInfo) { NamedTypeSymbol containingType = this.ContainingType; @@ -269,14 +278,11 @@ private void MakeFrames(ref int scopeOrdinalDispenser) continue; } - LambdaFrame frame = GetFrameForScope(scope, ref scopeOrdinalDispenser); + LambdaFrame frame = GetFrameForScope(scope, closureDebugInfo); var hoistedField = LambdaCapturedVariable.Create(frame, captured, ref synthesizedFieldNameIdDispenser); proxies.Add(captured, new CapturedToFrameSymbolReplacement(hoistedField, isReusable: false)); - if (CompilationState.Emitting) - { - CompilationState.ModuleBuilderOpt.AddSynthesizedDefinition(frame, hoistedField); - } + CompilationState.ModuleBuilderOpt.AddSynthesizedDefinition(frame, hoistedField); if (hoistedField.Type.IsRestrictedType()) { @@ -289,22 +295,28 @@ private void MakeFrames(ref int scopeOrdinalDispenser) } } - private LambdaFrame GetFrameForScope(BoundNode scope, ref int scopeOrdinalDispenser) + private LambdaFrame GetFrameForScope(BoundNode scope, ArrayBuilder closureDebugInfo) { LambdaFrame frame; if (!frames.TryGetValue(scope, out frame)) { - frame = new LambdaFrame(slotAllocatorOpt, CompilationState, topLevelMethod, topLevelMethodOrdinal, scope.Syntax, scopeOrdinalDispenser++, isStatic: false); + var syntax = scope.Syntax; + Debug.Assert(syntax != null); + + int closureOrdinal = closureDebugInfo.Count; + int syntaxOffset = topLevelMethod.CalculateLocalSyntaxOffset(syntax.SpanStart, syntax.SyntaxTree); + closureDebugInfo.Add(new ClosureDebugInfo(syntaxOffset)); + + var methodId = new MethodDebugId(topLevelMethodOrdinal, CompilationState.ModuleBuilderOpt.CurrentGenerationOrdinal); + + frame = new LambdaFrame(slotAllocatorOpt, topLevelMethod, methodId, syntax, closureOrdinal); frames.Add(scope, frame); - if (CompilationState.Emitting) - { - CompilationState.ModuleBuilderOpt.AddSynthesizedDefinition(this.ContainingType, frame); - CompilationState.AddSynthesizedMethod( - frame.Constructor, - FlowAnalysisPass.AppendImplicitReturn(MethodCompiler.BindMethodBody(frame.Constructor, CompilationState, null), - frame.Constructor)); - } + CompilationState.ModuleBuilderOpt.AddSynthesizedDefinition(this.ContainingType, frame); + CompilationState.AddSynthesizedMethod( + frame.Constructor, + FlowAnalysisPass.AppendImplicitReturn(MethodCompiler.BindMethodBody(frame.Constructor, CompilationState, null), + frame.Constructor)); } return frame; @@ -322,13 +334,17 @@ private LambdaFrame GetStaticFrame(DiagnosticBag diagnostics, BoundNode lambda) if (this.lazyStaticLambdaFrame == null) { - // associate the frame with the the first lambda that caused it to exist. - // we need to associate this with somme syntax. - // unfortunately either containing method or containing class could be synthetic - // therefore could have no syntax. - CSharpSyntaxNode syntax = lambda.Syntax; + MethodDebugId methodId; + if (isNonGeneric) + { + methodId = new MethodDebugId(MethodDebugId.UndefinedOrdinal, CompilationState.ModuleBuilderOpt.CurrentGenerationOrdinal); + } + else + { + methodId = slotAllocatorOpt?.PreviousMethodId ?? new MethodDebugId(topLevelMethodOrdinal, CompilationState.ModuleBuilderOpt.CurrentGenerationOrdinal); + } - this.lazyStaticLambdaFrame = new LambdaFrame(slotAllocatorOpt, CompilationState, topLevelMethod, isNonGeneric ? -1 : topLevelMethodOrdinal, syntax, scopeOrdinal: -1, isStatic: true); + this.lazyStaticLambdaFrame = new LambdaFrame(slotAllocatorOpt, topLevelMethod, methodId, scopeSyntaxOpt: null, closureOrdinal: -1); // nongeneric static lambdas can share the frame if (isNonGeneric) @@ -336,30 +352,33 @@ private LambdaFrame GetStaticFrame(DiagnosticBag diagnostics, BoundNode lambda) CompilationState.staticLambdaFrame = this.lazyStaticLambdaFrame; } - if (CompilationState.Emitting) - { - var frame = this.lazyStaticLambdaFrame; - - // add frame type - CompilationState.ModuleBuilderOpt.AddSynthesizedDefinition(this.ContainingType, frame); - - // add its ctor - CompilationState.AddSynthesizedMethod( - frame.Constructor, - FlowAnalysisPass.AppendImplicitReturn(MethodCompiler.BindMethodBody(frame.Constructor, CompilationState, null), - frame.Constructor)); - - // add cctor - // Frame.inst = new Frame() - var F = new SyntheticBoundNodeFactory(frame.StaticConstructor, syntax, CompilationState, diagnostics); - var body = F.Block( - F.Assignment( - F.Field(null, frame.SingletonCache), - F.New(frame.Constructor)), - F.Return()); - - CompilationState.AddSynthesizedMethod(frame.StaticConstructor, body); - } + var frame = this.lazyStaticLambdaFrame; + + // add frame type + CompilationState.ModuleBuilderOpt.AddSynthesizedDefinition(this.ContainingType, frame); + + // add its ctor + CompilationState.AddSynthesizedMethod( + frame.Constructor, + FlowAnalysisPass.AppendImplicitReturn(MethodCompiler.BindMethodBody(frame.Constructor, CompilationState, null), + frame.Constructor)); + + // associate the frame with the the first lambda that caused it to exist. + // we need to associate this with somme syntax. + // unfortunately either containing method or containing class could be synthetic + // therefore could have no syntax. + CSharpSyntaxNode syntax = lambda.Syntax; + + // add cctor + // Frame.inst = new Frame() + var F = new SyntheticBoundNodeFactory(frame.StaticConstructor, syntax, CompilationState, diagnostics); + var body = F.Block( + F.Assignment( + F.Field(null, frame.SingletonCache), + F.New(frame.Constructor)), + F.Return()); + + CompilationState.AddSynthesizedMethod(frame.StaticConstructor, body); } } @@ -433,6 +452,8 @@ private static void InsertAndFreePrologue(ArrayBuilder result, A private T IntroduceFrame(BoundNode node, LambdaFrame frame, Func, ArrayBuilder, T> F) { NamedTypeSymbol frameType = frame.ConstructIfGeneric(StaticCast.From(currentTypeParameters)); + + Debug.Assert(frame.ScopeSyntaxOpt != null); LocalSymbol framePointer = new SynthesizedLocal(this.topLevelMethod, frameType, SynthesizedLocalKind.LambdaDisplayClass, frame.ScopeSyntaxOpt); CSharpSyntaxNode syntax = node.Syntax; @@ -552,7 +573,7 @@ private void InitVariableProxy(CSharpSyntaxNode syntax, Symbol symbol, LocalSymb { return; } - + var local = (LocalSymbol)symbol; LocalSymbol localToUse; if (!localMap.TryGetValue(local, out localToUse)) @@ -778,7 +799,7 @@ private BoundNode RewriteCatch(BoundCatchBlock node, ArrayBuilder.From(currentTypeParameters)) : + NamedTypeSymbol constructedFrame = (object)containerAsFrame != null ? + translatedLambdaContainer.ConstructIfGeneric(StaticCast.From(currentTypeParameters)) : translatedLambdaContainer; // for instance lambdas, receiver is the frame @@ -1051,16 +1130,14 @@ private BoundNode RewriteLambdaConversion(BoundLambda node) BoundExpression cache; if (shouldCacheForStaticMethod || shouldCacheInLoop && (object)containerAsFrame != null) { - var cacheVariableType = containerAsFrame? - .TypeMap.SubstituteType(type) ?? - translatedLambdaContainer; + var cacheVariableType = containerAsFrame?.TypeMap.SubstituteType(type) ?? translatedLambdaContainer; // If we are generating the field into a display class created exclusively for the lambda the lambdaOrdinal itself is unique already, // no need to include the top-level method ordinal in the field name. - + var cacheVariableName = GeneratedNames.MakeLambdaCacheFieldName( - (closureKind == ClosureKind.General) ? -1 : topLevelMethodOrdinal, - CompilationState.ModuleBuilderOpt.CurrentGenerationOrdinal, + (closureKind == ClosureKind.General) ? -1 : topLevelMethodId.Ordinal, + topLevelMethodId.Generation, lambdaOrdinal); var cacheField = new SynthesizedLambdaCacheFieldSymbol(translatedLambdaContainer, cacheVariableType, cacheVariableName, topLevelMethod, isReadOnly: false, isStatic: closureKind == ClosureKind.Static); diff --git a/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/SynthesizedLambdaMethod.cs b/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/SynthesizedLambdaMethod.cs index c327ec0d29b0f..c8d748368141a 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/SynthesizedLambdaMethod.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/SynthesizedLambdaMethod.cs @@ -15,12 +15,10 @@ internal sealed class SynthesizedLambdaMethod : SynthesizedMethodBaseSymbol, ISy private readonly MethodSymbol topLevelMethod; internal SynthesizedLambdaMethod( - VariableSlotAllocator slotAllocatorOpt, - TypeCompilationState compilationState, NamedTypeSymbol containingType, - ClosureKind closureKind, - MethodSymbol topLevelMethod, - int topLevelMethodOrdinal, + ClosureKind closureKind, + MethodSymbol topLevelMethod, + MethodDebugId topLevelMethodId, BoundLambda lambdaNode, int lambdaOrdinal) : base(containingType, @@ -28,7 +26,7 @@ internal SynthesizedLambdaMethod( null, lambdaNode.SyntaxTree.GetReference(lambdaNode.Body.Syntax), lambdaNode.Syntax.GetLocation(), - MakeName(slotAllocatorOpt, compilationState, closureKind, topLevelMethod, topLevelMethodOrdinal, lambdaOrdinal), + MakeName(topLevelMethod.Name, topLevelMethodId, closureKind, lambdaOrdinal), (closureKind == ClosureKind.ThisOnly ? DeclarationModifiers.Private : DeclarationModifiers.Internal) | (lambdaNode.Symbol.IsAsync ? DeclarationModifiers.Async : 0)) { @@ -56,33 +54,31 @@ internal SynthesizedLambdaMethod( AssignTypeMapAndTypeParameters(typeMap, typeParameters); } - private static string MakeName(VariableSlotAllocator slotAllocatorOpt, TypeCompilationState compilationState, ClosureKind closureKind, MethodSymbol topLevelMethod, int topLevelMethodOrdinal, int lambdaOrdinal) + private static string MakeName(string topLevelMethodName, MethodDebugId topLevelMethodId, ClosureKind closureKind, int lambdaOrdinal) { - // TODO: slotAllocatorOpt?.GetPrevious() - // Lambda method name must contain the declaring method ordinal to be unique unless the method is emitted into a closure class exclusive to the declaring method. // Lambdas that only close over "this" are emitted directly into the top-level method containing type. // Lambdas that don't close over anything (static) are emitted into a shared closure singleton. return GeneratedNames.MakeLambdaMethodName( - topLevelMethod.Name, - (closureKind == ClosureKind.General) ? -1 : topLevelMethodOrdinal, - compilationState.ModuleBuilderOpt.CurrentGenerationOrdinal, + topLevelMethodName, + (closureKind == ClosureKind.General) ? -1 : topLevelMethodId.Ordinal, + topLevelMethodId.Generation, lambdaOrdinal); } internal override int ParameterCount => this.BaseMethod.ParameterCount; - // The lambda symbol might have declared no parameters in the case - // - // D d = delegate {}; - // - // but there still might be parameters that need to be generated for the - // synthetic method. If there are no lambda parameters, try the delegate - // parameters instead. - // - // UNDONE: In the native compiler in this scenario we make up new names for - // UNDONE: synthetic parameters; in this implementation we use the parameter - // UNDONE: names from the delegate. Does it really matter? + // The lambda symbol might have declared no parameters in the case + // + // D d = delegate {}; + // + // but there still might be parameters that need to be generated for the + // synthetic method. If there are no lambda parameters, try the delegate + // parameters instead. + // + // UNDONE: In the native compiler in this scenario we make up new names for + // UNDONE: synthetic parameters; in this implementation we use the parameter + // UNDONE: names from the delegate. Does it really matter? protected override ImmutableArray BaseMethodParameters => this.BaseMethod.Parameters; internal override bool GenerateDebugInfo => !this.IsAsync; diff --git a/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/MethodToStateMachineRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/MethodToStateMachineRewriter.cs index 1712b59e39bff..90fccd5e1faa6 100644 --- a/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/MethodToStateMachineRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/MethodToStateMachineRewriter.cs @@ -1,4 +1,4 @@ -// 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; using System.Collections.Generic; @@ -519,13 +519,8 @@ private BoundExpression HoistExpression( // Editing await expression is not allowed. Thus all spilled fields will be present in the previous state machine. // However, it may happen that the type changes, in which case we need to allocate a new slot. - int slotIndex = -1; - if (slotAllocatorOpt != null) - { - slotIndex = slotAllocatorOpt.GetPreviousHoistedLocalSlotIndex(awaitSyntaxOpt, (Cci.ITypeReference)fieldType, kind, id); - } - - if (slotIndex == -1) + int slotIndex; + if (slotAllocatorOpt == null || !slotAllocatorOpt.TryGetPreviousHoistedLocalSlotIndex(awaitSyntaxOpt, (Cci.ITypeReference)fieldType, kind, id, out slotIndex)) { slotIndex = nextFreeHoistedLocalSlot++; } diff --git a/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/StateMachineRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/StateMachineRewriter.cs index c8fa74071afca..872d158d80e51 100644 --- a/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/StateMachineRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/StateMachineRewriter.cs @@ -170,10 +170,11 @@ private void CreateNonReusableLocalProxies( int ordinal = synthesizedLocalOrdinals.AssignLocalOrdinal(synthesizedKind, syntaxOffset); id = new LocalDebugId(syntaxOffset, ordinal); - if (mapToPreviousFields) + // map local id to the previous id, if available: + int previousSlotIndex; + if (mapToPreviousFields && slotAllocatorOpt.TryGetPreviousHoistedLocalSlotIndex(declaratorSyntax, (Cci.ITypeReference)fieldType, synthesizedKind, id, out previousSlotIndex)) { - // map local id to the previous id, if available: - slotIndex = slotAllocatorOpt.GetPreviousHoistedLocalSlotIndex(declaratorSyntax, (Cci.ITypeReference)fieldType, synthesizedKind, id); + slotIndex = previousSlotIndex; } } else diff --git a/src/Compilers/CSharp/Portable/Symbols/FieldOrPropertyInitializer.cs b/src/Compilers/CSharp/Portable/Symbols/FieldOrPropertyInitializer.cs index c792aeca22163..ecc603350fbd5 100644 --- a/src/Compilers/CSharp/Portable/Symbols/FieldOrPropertyInitializer.cs +++ b/src/Compilers/CSharp/Portable/Symbols/FieldOrPropertyInitializer.cs @@ -20,12 +20,19 @@ internal struct FieldOrPropertyInitializer /// internal readonly SyntaxReference Syntax; - public FieldOrPropertyInitializer(FieldSymbol fieldOpt, SyntaxNode syntax) + /// + /// A sum of widths of full spans of all preceding initializers + /// (instance and static initializers are summed separately). + /// + internal readonly int PrecedingInitializersLength; + + public FieldOrPropertyInitializer(FieldSymbol fieldOpt, SyntaxNode syntax, int precedingInitializersLength) { Debug.Assert(syntax.IsKind(SyntaxKind.EqualsValueClause) && fieldOpt != null || syntax is StatementSyntax); FieldOpt = fieldOpt; Syntax = syntax.GetReference(); + PrecedingInitializersLength = precedingInitializersLength; } } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceConstructorSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceConstructorSymbol.cs index b691705bff744..30011730aaa2f 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceConstructorSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceConstructorSymbol.cs @@ -1,4 +1,4 @@ -// 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.Immutable; using System.Diagnostics; @@ -29,7 +29,7 @@ private SourceConstructorSymbol( ConstructorDeclarationSyntax syntax, MethodKind methodKind, DiagnosticBag diagnostics) : - base(containingType, syntax.GetReference(), syntax.Body.GetReferenceOrNull(), ImmutableArray.Create(location)) + base(containingType, syntax.GetReference(), syntax.Body?.GetReference(), ImmutableArray.Create(location)) { bool modifierErrors; var declarationModifiers = this.MakeModifiers(syntax.Modifiers, methodKind, location, diagnostics, out modifierErrors); @@ -229,19 +229,57 @@ internal override bool GenerateDebugInfo get { return true; } } - internal override int CalculateLocalSyntaxOffset(int localPosition, SyntaxTree localTree) + internal override int CalculateLocalSyntaxOffset(int position, SyntaxTree tree) { + Debug.Assert(position >= 0 && tree != null); + TextSpan span; - // local defined within the body of the constructor: - if (bodySyntaxReferenceOpt != null && localTree == bodySyntaxReferenceOpt.SyntaxTree) + // local/lambda/closure defined within the body of the constructor: + if (tree == bodySyntaxReferenceOpt?.SyntaxTree) { span = bodySyntaxReferenceOpt.Span; - if (span.Contains(localPosition)) + if (span.Contains(position)) + { + return position - span.Start; + } + } + + var ctorSyntax = GetSyntax(); + + // closure in ctor initializer lifting its parameter(s) spans the constructor declaration: + if (position == ctorSyntax.SpanStart) + { + // Use a constant that is distinct from any other syntax offset. + // -1 works since a field initializer and a consturctor declaration header can't squeeze into a single character. + return -1; + } + + // lambdas in ctor initializer: + int ctorInitializerLength; + var ctorInitializer = ctorSyntax.Initializer; + if (tree == ctorInitializer?.SyntaxTree) + { + span = ctorInitializer.Span; + ctorInitializerLength = span.Length; + + if (span.Contains(position)) { - return localPosition - span.Start; + return -ctorInitializerLength + (position - span.Start); } } + else + { + ctorInitializerLength = 0; + } + + // lambdas in field/property initializers: + int syntaxOffset; + var containingType = (SourceNamedTypeSymbol)this.ContainingType; + if (containingType.TryCalculateSyntaxOffsetOfPositionInInitializer(position, tree, this.IsStatic, ctorInitializerLength, out syntaxOffset)) + { + return syntaxOffset; + } // we haven't found the contructor part that declares the variable: throw ExceptionUtilities.Unreachable; diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceCustomEventAccessorSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceCustomEventAccessorSymbol.cs index d506a6f2ab471..32e4a9e230bea 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceCustomEventAccessorSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceCustomEventAccessorSymbol.cs @@ -29,7 +29,7 @@ internal SourceCustomEventAccessorSymbol( DiagnosticBag diagnostics) : base(@event, syntax.GetReference(), - syntax.Body.GetReferenceOrNull(), + syntax.Body?.GetReference(), ImmutableArray.Create(syntax.Keyword.GetLocation())) { Debug.Assert(syntax != null); diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceDestructorSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceDestructorSymbol.cs index 4693d68c86269..60ec0e75ee34d 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceDestructorSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceDestructorSymbol.cs @@ -15,7 +15,7 @@ internal SourceDestructorSymbol( SourceMemberContainerTypeSymbol containingType, DestructorDeclarationSyntax syntax, DiagnosticBag diagnostics) : - base(containingType, syntax.GetReference(), syntax.Body.GetReferenceOrNull(), syntax.Identifier.GetLocation()) + base(containingType, syntax.GetReference(), syntax.Body?.GetReference(), syntax.Identifier.GetLocation()) { const MethodKind methodKind = MethodKind.Destructor; Location location = this.Locations[0]; diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs index a14c42bd90892..c13c77a1cb0ca 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs @@ -20,7 +20,7 @@ namespace Microsoft.CodeAnalysis.CSharp.Symbols /// Represents a named type symbol whose members are declared in source. /// internal abstract partial class SourceMemberContainerTypeSymbol : NamedTypeSymbol - { + { // The flags type is used to compact many different bits of information efficiently. private struct Flags { @@ -64,7 +64,7 @@ public SpecialType SpecialType { get { return (SpecialType)((this.flags >> SpecialTypeOffset) & SpecialTypeMask); } } - + public DeclarationModifiers DeclarationModifiers { get { return (DeclarationModifiers)((this.flags >> DeclarationModifiersOffset) & DeclarationModifiersMask); } @@ -842,12 +842,16 @@ private sealed class MembersAndInitializers internal readonly ImmutableArray> StaticInitializers; internal readonly ImmutableArray> InstanceInitializers; internal readonly ImmutableArray IndexerDeclarations; + internal readonly int StaticInitializersSyntaxLength; + internal readonly int InstanceInitializersSyntaxLength; public MembersAndInitializers( ImmutableArray nonTypeNonIndexerMembers, ImmutableArray> staticInitializers, ImmutableArray> instanceInitializers, - ImmutableArray indexerDeclarations) + ImmutableArray indexerDeclarations, + int staticInitializersSyntaxLength, + int instanceInitializersSyntaxLength) { Debug.Assert(!nonTypeNonIndexerMembers.IsDefault); Debug.Assert(!staticInitializers.IsDefault); @@ -858,10 +862,15 @@ public MembersAndInitializers( Debug.Assert(!nonTypeNonIndexerMembers.Any(s => s.IsIndexer())); Debug.Assert(!nonTypeNonIndexerMembers.Any(s => s.IsAccessor() && ((MethodSymbol)s).AssociatedSymbol.IsIndexer())); + Debug.Assert(staticInitializersSyntaxLength == staticInitializers.Sum(s => s.Sum(i => (i.FieldOpt == null || !i.FieldOpt.IsMetadataConstant) ? i.Syntax.Span.Length : 0))); + Debug.Assert(instanceInitializersSyntaxLength == instanceInitializers.Sum(s => s.Sum(i => i.Syntax.Span.Length))); + this.NonTypeNonIndexerMembers = nonTypeNonIndexerMembers; this.StaticInitializers = staticInitializers; this.InstanceInitializers = instanceInitializers; this.IndexerDeclarations = indexerDeclarations; + this.StaticInitializersSyntaxLength = staticInitializersSyntaxLength; + this.InstanceInitializersSyntaxLength = instanceInitializersSyntaxLength; } } @@ -875,20 +884,18 @@ internal ImmutableArray> InstanceInit get { return GetMembersAndInitializers().InstanceInitializers; } } - internal int CalculateLocalSyntaxOffsetInSynthesizedConstructor(int localPosition, SyntaxTree localTree, bool isStatic) + internal int CalculateSyntaxOffsetInSynthesizedConstructor(int position, SyntaxTree tree, bool isStatic) { - int aggregateLength; - if (IsScriptClass && !isStatic) { - aggregateLength = 0; + int aggregateLength = 0; foreach (var declaration in this.declaration.Declarations) { var syntaxRef = declaration.SyntaxReference; - if (localTree == syntaxRef.SyntaxTree) + if (tree == syntaxRef.SyntaxTree) { - return aggregateLength + localPosition; + return aggregateLength + position; } aggregateLength += syntaxRef.Span.Length; @@ -897,10 +904,90 @@ internal int CalculateLocalSyntaxOffsetInSynthesizedConstructor(int localPositio throw ExceptionUtilities.Unreachable; } + int syntaxOffset; + if (TryCalculateSyntaxOffsetOfPositionInInitializer(position, tree, isStatic, ctorInitializerLength: 0, syntaxOffset: out syntaxOffset)) + { + return syntaxOffset; + } + // an implicit constructor has no body and no initializer, so the variable has to be declared in a member initializer throw ExceptionUtilities.Unreachable; } + /// + /// Calculates a syntax offset of a syntax position that is contained in a property or field initializer (if it is in fact contained in one). + /// + internal bool TryCalculateSyntaxOffsetOfPositionInInitializer(int position, SyntaxTree tree, bool isStatic, int ctorInitializerLength, out int syntaxOffset) + { + Debug.Assert(ctorInitializerLength >= 0); + + var membersAndInitializers = GetMembersAndInitializers(); + var allInitializers = isStatic ? membersAndInitializers.StaticInitializers : membersAndInitializers.InstanceInitializers; + + var siblingInitializers = GetInitializersInSourceTree(tree, allInitializers); + int index = IndexOfInitializerContainingPosition(siblingInitializers, position); + if (index < 0) + { + syntaxOffset = 0; + return false; + } + + // |<-----------distanceFromCtorBody----------->| + // [ initializer 0 ][ initializer 1 ][ initializer 2 ][ctor initializer][ctor body] + // |<--preceding init len-->| ^ + // position + + int initializersLength = isStatic ? membersAndInitializers.StaticInitializersSyntaxLength : membersAndInitializers.InstanceInitializersSyntaxLength; + int distanceFromInitializerStart = position - siblingInitializers[index].Syntax.Span.Start; + + int distanceFromCtorBody = + initializersLength + ctorInitializerLength - + (siblingInitializers[index].PrecedingInitializersLength + distanceFromInitializerStart); + + Debug.Assert(distanceFromCtorBody > 0); + + // syntax offset 0 is at the start of the ctor body: + syntaxOffset = -distanceFromCtorBody; + return true; + } + + internal static ImmutableArray GetInitializersInSourceTree(SyntaxTree tree, ImmutableArray> initializers) + { + foreach (var siblingInitializers in initializers) + { + Debug.Assert(!siblingInitializers.IsEmpty); + + if (siblingInitializers[0].Syntax.SyntaxTree == tree) + { + return siblingInitializers; + } + } + + return ImmutableArray.Empty; + } + + internal static int IndexOfInitializerContainingPosition(ImmutableArray initializers, int position) + { + // Search for the start of the span (the spans are non-overlapping and sorted) + int index = initializers.BinarySearch(position, (initializer, pos) => initializer.Syntax.Span.Start.CompareTo(pos)); + + // Binary search returns non-negative result if the position is exactly the start of some span. + if (index >= 0) + { + return index; + } + + // Otherwise, ~index is the closest span whose start is greater than the position. + // => Check if the preceding initializer span contains the position. + int precedingInitializerIndex = ~index - 1; + if (precedingInitializerIndex >= 0 && initializers[precedingInitializerIndex].Syntax.Span.Contains(position)) + { + return precedingInitializerIndex; + } + + return -1; + } + public override IEnumerable MemberNames { get { return this.declaration.MemberNames; } @@ -1458,7 +1545,7 @@ private void ReportMethodSignatureCollision(DiagnosticBag diagnostics, SourceMet // Type '{1}' already defines a member called '{0}' with the same parameter types diagnostics.Add(ErrorCode.ERR_MemberAlreadyExists, method1.Locations[0], "~" + this.Name, this); } - else + else { // Type '{1}' already defines a member called '{0}' with the same parameter types diagnostics.Add(ErrorCode.ERR_MemberAlreadyExists, method1.Locations[0], method1.Name, this); @@ -2115,13 +2202,18 @@ private class MembersAndInitializersBuilder public readonly ArrayBuilder> InstanceInitializers = ArrayBuilder>.GetInstance(); public readonly ArrayBuilder IndexerDeclarations = ArrayBuilder.GetInstance(); + public int StaticSyntaxLength = 0; + public int InstanceSyntaxLength = 0; + public MembersAndInitializers ToReadOnlyAndFree() { return new MembersAndInitializers( NonTypeNonIndexerMembers.ToImmutableAndFree(), StaticInitializers.ToImmutableAndFree(), InstanceInitializers.ToImmutableAndFree(), - IndexerDeclarations.ToImmutableAndFree()); + IndexerDeclarations.ToImmutableAndFree(), + StaticSyntaxLength, + InstanceSyntaxLength); } } @@ -2153,6 +2245,15 @@ private MembersAndInitializers BuildMembersAndInitializers(DiagnosticBag diagnos break; } + // We already built the members and initializers on another thread, we might have detected that condition + // during member building on this thread and bailed, which results in incomplete data in the builder. + // In such case we have to avoid creating the instance of MemberAndInitializers since it checks the consistency + // of the data in the builder and would fail in an assertion if we tried to construct it from incomplete builder. + if (this.lazyMembersAndInitializers != null) + { + return null; + } + return builder.ToReadOnlyAndFree(); } @@ -2513,7 +2614,7 @@ private void AddEnumMembers(MembersAndInitializersBuilder result, EnumDeclaratio } } - private static void AddInitializer(ref ArrayBuilder initializers, FieldSymbol fieldOpt, CSharpSyntaxNode node) + private static void AddInitializer(ref ArrayBuilder initializers, ref int aggregateSyntaxLength, FieldSymbol fieldOpt, CSharpSyntaxNode node) { if (initializers == null) { @@ -2526,7 +2627,18 @@ private static void AddInitializer(ref ArrayBuilder Debug.Assert(node.SpanStart > initializers.Last().Syntax.GetSyntax().SpanStart); } - initializers.Add(new FieldOrPropertyInitializer(fieldOpt, node)); + int currentLength = aggregateSyntaxLength; + + // A constant field of type decimal needs a field initializer, so + // check if it is a metadata constant, not just a constant to exclude + // decimals. Other constants do not need field initializers. + if (fieldOpt == null || !fieldOpt.IsMetadataConstant) + { + // ignore leading and trailing trivia of the node: + aggregateSyntaxLength += node.Span.Length; + } + + initializers.Add(new FieldOrPropertyInitializer(fieldOpt, node, currentLength)); } private static void AddInitializers(ArrayBuilder> allInitializers, ArrayBuilder siblingsOpt) @@ -2596,8 +2708,8 @@ private static void CheckInterfaceMember(Symbol member, DiagnosticBag diagnostic } private static void CheckForStructDefaultConstructors( - ArrayBuilder members, - bool isEnum, + ArrayBuilder members, + bool isEnum, DiagnosticBag diagnostics) { foreach (var s in members) @@ -2722,7 +2834,7 @@ private static bool HasNonConstantInitializer(ArrayBuilder members, DiagnosticBag diagnostics) { @@ -2766,17 +2878,17 @@ private void AddNonTypeMembers( var fieldSymbol = (modifiers & DeclarationModifiers.Fixed) == 0 ? new SourceMemberFieldSymbol(this, variable, modifiers, modifierErrors, diagnostics) : new SourceFixedFieldSymbol(this, variable, modifiers, modifierErrors, diagnostics); - result.NonTypeNonIndexerMembers.Add(fieldSymbol); + builder.NonTypeNonIndexerMembers.Add(fieldSymbol); if (variable.Initializer != null) { if (fieldSymbol.IsStatic) { - AddInitializer(ref staticInitializers, fieldSymbol, variable.Initializer); + AddInitializer(ref staticInitializers, ref builder.StaticSyntaxLength, fieldSymbol, variable.Initializer); } else { - AddInitializer(ref instanceInitializers, fieldSymbol, variable.Initializer); + AddInitializer(ref instanceInitializers, ref builder.InstanceSyntaxLength, fieldSymbol, variable.Initializer); } } } @@ -2793,7 +2905,7 @@ private void AddNonTypeMembers( } var method = SourceMemberMethodSymbol.CreateMethodSymbol(this, bodyBinder, methodSyntax, diagnostics); - result.NonTypeNonIndexerMembers.Add(method); + builder.NonTypeNonIndexerMembers.Add(method); } break; @@ -2807,7 +2919,7 @@ private void AddNonTypeMembers( } var constructor = SourceConstructorSymbol.CreateConstructorSymbol(this, constructorSyntax, diagnostics); - result.NonTypeNonIndexerMembers.Add(constructor); + builder.NonTypeNonIndexerMembers.Add(constructor); } break; @@ -2825,7 +2937,7 @@ private void AddNonTypeMembers( // when it is loaded from metadata. Perhaps we should just treat it as an Ordinary // method in such cases? var destructor = new SourceDestructorSymbol(this, destructorSyntax, diagnostics); - result.NonTypeNonIndexerMembers.Add(destructor); + builder.NonTypeNonIndexerMembers.Add(destructor); } break; @@ -2839,10 +2951,10 @@ private void AddNonTypeMembers( } var property = SourcePropertySymbol.Create(this, bodyBinder, propertySyntax, diagnostics); - result.NonTypeNonIndexerMembers.Add(property); + builder.NonTypeNonIndexerMembers.Add(property); - AddAccessorIfAvailable(result.NonTypeNonIndexerMembers, property.GetMethod, diagnostics); - AddAccessorIfAvailable(result.NonTypeNonIndexerMembers, property.SetMethod, diagnostics); + AddAccessorIfAvailable(builder.NonTypeNonIndexerMembers, property.GetMethod, diagnostics); + AddAccessorIfAvailable(builder.NonTypeNonIndexerMembers, property.SetMethod, diagnostics); // TODO: can we leave this out of the member list? // From the 10/12/11 design notes: @@ -2850,18 +2962,18 @@ private void AddNonTypeMembers( // a similar manner and make the autoproperty fields private. if ((object)property.BackingField != null) { - result.NonTypeNonIndexerMembers.Add(property.BackingField); + builder.NonTypeNonIndexerMembers.Add(property.BackingField); var initializer = propertySyntax.Initializer; if (initializer != null) { if (property.IsStatic) { - AddInitializer(ref staticInitializers, property.BackingField, initializer); + AddInitializer(ref staticInitializers, ref builder.StaticSyntaxLength, property.BackingField, initializer); } else { - AddInitializer(ref instanceInitializers, property.BackingField, initializer); + AddInitializer(ref instanceInitializers, ref builder.InstanceSyntaxLength, property.BackingField, initializer); } } } @@ -2881,7 +2993,7 @@ private void AddNonTypeMembers( foreach (VariableDeclaratorSyntax declarator in eventFieldSyntax.Declaration.Variables) { SourceFieldLikeEventSymbol @event = new SourceFieldLikeEventSymbol(this, bodyBinder, eventFieldSyntax.Modifiers, declarator, diagnostics); - result.NonTypeNonIndexerMembers.Add(@event); + builder.NonTypeNonIndexerMembers.Add(@event); FieldSymbol associatedField = @event.AssociatedField; if ((object)associatedField != null) @@ -2893,11 +3005,11 @@ private void AddNonTypeMembers( { if (associatedField.IsStatic) { - AddInitializer(ref staticInitializers, associatedField, declarator.Initializer); + AddInitializer(ref staticInitializers, ref builder.StaticSyntaxLength, associatedField, declarator.Initializer); } else { - AddInitializer(ref instanceInitializers, associatedField, declarator.Initializer); + AddInitializer(ref instanceInitializers, ref builder.InstanceSyntaxLength, associatedField, declarator.Initializer); } } } @@ -2905,8 +3017,8 @@ private void AddNonTypeMembers( Debug.Assert((object)@event.AddMethod != null); Debug.Assert((object)@event.RemoveMethod != null); - AddAccessorIfAvailable(result.NonTypeNonIndexerMembers, @event.AddMethod, diagnostics); - AddAccessorIfAvailable(result.NonTypeNonIndexerMembers, @event.RemoveMethod, diagnostics); + AddAccessorIfAvailable(builder.NonTypeNonIndexerMembers, @event.AddMethod, diagnostics); + AddAccessorIfAvailable(builder.NonTypeNonIndexerMembers, @event.RemoveMethod, diagnostics); } } break; @@ -2922,10 +3034,10 @@ private void AddNonTypeMembers( var @event = new SourceCustomEventSymbol(this, bodyBinder, eventSyntax, diagnostics); - result.NonTypeNonIndexerMembers.Add(@event); + builder.NonTypeNonIndexerMembers.Add(@event); - AddAccessorIfAvailable(result.NonTypeNonIndexerMembers, @event.AddMethod, diagnostics); - AddAccessorIfAvailable(result.NonTypeNonIndexerMembers, @event.RemoveMethod, diagnostics); + AddAccessorIfAvailable(builder.NonTypeNonIndexerMembers, @event.AddMethod, diagnostics); + AddAccessorIfAvailable(builder.NonTypeNonIndexerMembers, @event.RemoveMethod, diagnostics); Debug.Assert((object)@event.AssociatedField == null); } @@ -2944,7 +3056,7 @@ private void AddNonTypeMembers( // what name it will have after attribute binding (because of // IndexerNameAttribute). Instead, we'll keep a (weak) reference // to the syntax and bind it again after early attribute decoding. - result.IndexerDeclarations.Add(indexerSyntax.GetReference()); + builder.IndexerDeclarations.Add(indexerSyntax.GetReference()); } break; @@ -2959,7 +3071,7 @@ private void AddNonTypeMembers( var method = SourceUserDefinedConversionSymbol.CreateUserDefinedConversionSymbol (this, conversionOperatorSyntax, diagnostics); - result.NonTypeNonIndexerMembers.Add(method); + builder.NonTypeNonIndexerMembers.Add(method); } break; @@ -2974,7 +3086,7 @@ private void AddNonTypeMembers( var method = SourceUserDefinedOperatorSymbol.CreateUserDefinedOperatorSymbol (this, operatorSyntax, diagnostics); - result.NonTypeNonIndexerMembers.Add(method); + builder.NonTypeNonIndexerMembers.Add(method); } break; @@ -2987,7 +3099,7 @@ private void AddNonTypeMembers( diagnostics.Add(ErrorCode.ERR_GlobalStatement, new SourceLocation(globalStatement)); } - AddInitializer(ref instanceInitializers, null, globalStatement); + AddInitializer(ref instanceInitializers, ref builder.InstanceSyntaxLength, null, globalStatement); } break; @@ -3000,8 +3112,8 @@ private void AddNonTypeMembers( } } - AddInitializers(result.InstanceInitializers, instanceInitializers); - AddInitializers(result.StaticInitializers, staticInitializers); + AddInitializers(builder.InstanceInitializers, instanceInitializers); + AddInitializers(builder.StaticInitializers, staticInitializers); } private static bool IsGlobalCodeAllowed(CSharpSyntaxNode parent) diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberMethodSymbol.cs index 313f05bbb5fcf..884d6af7155fd 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberMethodSymbol.cs @@ -78,8 +78,7 @@ private SourceMemberMethodSymbol( base(containingType, syntax.GetReference(), // Prefer a block body if both exist - syntax.Body.GetReferenceOrNull() - ?? syntax.ExpressionBody.GetReferenceOrNull(), + syntax.Body?.GetReference() ?? syntax.ExpressionBody?.GetReference(), location) { this.name = name; diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol_Bases.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol_Bases.cs index 787eeb9b539d0..b8df7bd3eb700 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol_Bases.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol_Bases.cs @@ -1,4 +1,4 @@ -// 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; using System.Collections.Immutable; diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertyAccessorSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertyAccessorSymbol.cs index e9acfff03593b..c97f84547b55d 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertyAccessorSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertyAccessorSymbol.cs @@ -1,4 +1,4 @@ -// 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; using System.Collections.Immutable; @@ -192,7 +192,7 @@ private SourcePropertyAccessorSymbol( MethodKind methodKind, bool isAutoPropertyAccessor, DiagnosticBag diagnostics) : - base(containingType, syntax.GetReference(), syntax.Body.GetReferenceOrNull(), location) + base(containingType, syntax.GetReference(), syntax.Body?.GetReference(), location) { this.property = property; this.explicitInterfaceImplementations = explicitInterfaceImplementations; diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceUserDefinedConversionSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceUserDefinedConversionSymbol.cs index 6de45d616a4ea..6dc8bd2b07b17 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceUserDefinedConversionSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceUserDefinedConversionSymbol.cs @@ -43,8 +43,7 @@ private SourceUserDefinedConversionSymbol( containingType, location, syntax.GetReference(), - syntax.Body.GetReferenceOrNull() - ?? syntax.ExpressionBody.GetReferenceOrNull(), + syntax.Body?.GetReference() ?? syntax.ExpressionBody?.GetReference(), syntax.Modifiers, diagnostics, isExpressionBodied) diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceUserDefinedOperatorSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceUserDefinedOperatorSymbol.cs index 506d56459bd49..f1f78fa45b14f 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceUserDefinedOperatorSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceUserDefinedOperatorSymbol.cs @@ -38,8 +38,7 @@ private SourceUserDefinedOperatorSymbol( containingType, location, syntax.GetReference(), - syntax.Body.GetReferenceOrNull() - ?? syntax.ExpressionBody.GetReferenceOrNull(), + syntax.Body?.GetReference() ?? syntax.ExpressionBody?.GetReference(), syntax.Modifiers, diagnostics, isExpressionBodied) diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNames.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNames.cs index f5b6e9396fc1d..d4a457e852d73 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNames.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNames.cs @@ -142,7 +142,7 @@ internal static string MakeLambdaCacheFieldName(int methodOrdinal, int generatio private static string MakeMethodScopedSynthesizedName(GeneratedNameKind kind, int methodOrdinal, int generation, string methodNameOpt = null, string suffix = null, int uniqueId = -1, bool isTypeName = false) { Debug.Assert(methodOrdinal >= -1); - Debug.Assert(generation >= 0); + Debug.Assert(generation >= 0 || generation == -1 && methodOrdinal == -1); Debug.Assert(uniqueId >= -1); var result = PooledStringBuilder.GetInstance(); diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedInstanceConstructor.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedInstanceConstructor.cs index a0d48d134696a..b6ff22ef77848 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedInstanceConstructor.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedInstanceConstructor.cs @@ -253,7 +253,7 @@ public sealed override ImmutableArray ExplicitInterfaceImplementat internal sealed override int CalculateLocalSyntaxOffset(int localPosition, SyntaxTree localTree) { var containingType = (SourceMemberContainerTypeSymbol)this.ContainingType; - return containingType.CalculateLocalSyntaxOffsetInSynthesizedConstructor(localPosition, localTree, isStatic: false); + return containingType.CalculateSyntaxOffsetInSynthesizedConstructor(localPosition, localTree, isStatic: false); } #endregion diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedStaticConstructor.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedStaticConstructor.cs index ddb45b7d5ef56..b5d13ebb537e6 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedStaticConstructor.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedStaticConstructor.cs @@ -346,7 +346,7 @@ internal override ImmutableArray GetAppliedConditionalSymbols() internal override int CalculateLocalSyntaxOffset(int localPosition, SyntaxTree localTree) { var containingType = (SourceMemberContainerTypeSymbol)this.ContainingType; - return containingType.CalculateLocalSyntaxOffsetInSynthesizedConstructor(localPosition, localTree, isStatic: true); + return containingType.CalculateSyntaxOffsetInSynthesizedConstructor(localPosition, localTree, isStatic: true); } } } diff --git a/src/Compilers/CSharp/Portable/Syntax/CSharpSyntaxNode.cs b/src/Compilers/CSharp/Portable/Syntax/CSharpSyntaxNode.cs index cf38742c5f666..337484505fbae 100644 --- a/src/Compilers/CSharp/Portable/Syntax/CSharpSyntaxNode.cs +++ b/src/Compilers/CSharp/Portable/Syntax/CSharpSyntaxNode.cs @@ -451,6 +451,11 @@ internal bool IsEquivalentTo(CSharpSyntaxNode other) return this.SyntaxTree.GetDiagnostics(this); } + internal sealed override SyntaxNode GetCorrespondingLambdaBody(SyntaxNode body) + { + return SyntaxUtilities.GetCorrespondingLambdaBody(body, this); + } + #region Directives internal IList GetDirectives(Func filter = null) @@ -574,7 +579,7 @@ public override SyntaxNodeOrToken ChildThatContainsPosition(int position) /// public new SyntaxToken GetFirstToken(bool includeZeroWidth = false, bool includeSkipped = false, bool includeDirectives = false, bool includeDocumentationComments = false) { - return (SyntaxToken)base.GetFirstToken(includeZeroWidth, includeSkipped, includeDirectives, includeDocumentationComments); + return base.GetFirstToken(includeZeroWidth, includeSkipped, includeDirectives, includeDocumentationComments); } /// @@ -587,7 +592,7 @@ public override SyntaxNodeOrToken ChildThatContainsPosition(int position) /// internal SyntaxToken GetFirstToken(Func predicate, Func stepInto = null) { - return (SyntaxToken)SyntaxNavigator.Instance.GetFirstToken(this, SyntaxNavigator.ToCommon(predicate), SyntaxNavigator.ToCommon(stepInto)); + return SyntaxNavigator.Instance.GetFirstToken(this, predicate, stepInto); } /// @@ -602,7 +607,7 @@ internal SyntaxToken GetFirstToken(Func predicate, Func public new SyntaxToken GetLastToken(bool includeZeroWidth = false, bool includeSkipped = false, bool includeDirectives = false, bool includeDocumentationComments = false) { - return (SyntaxToken)base.GetLastToken(includeZeroWidth, includeSkipped, includeDirectives, includeDocumentationComments); + return base.GetLastToken(includeZeroWidth, includeSkipped, includeDirectives, includeDocumentationComments); } internal SyntaxToken FindTokenInternal(int position) diff --git a/src/Compilers/CSharp/Portable/Syntax/LookupPosition.cs b/src/Compilers/CSharp/Portable/Syntax/LookupPosition.cs index 415aab20cdf20..60bd671b1762d 100644 --- a/src/Compilers/CSharp/Portable/Syntax/LookupPosition.cs +++ b/src/Compilers/CSharp/Portable/Syntax/LookupPosition.cs @@ -399,32 +399,37 @@ private static SyntaxToken GetFirstExcludedToken(StatementSyntax statement) } } - internal static bool IsInAnonymousFunctionOrQuery(int position, CSharpSyntaxNode anonymousFunction) + internal static bool IsInAnonymousFunctionOrQuery(int position, CSharpSyntaxNode lambdaExpressionOrQueryNode) { + Debug.Assert(lambdaExpressionOrQueryNode.IsAnonymousFunction() || lambdaExpressionOrQueryNode.IsQuery()); + SyntaxToken firstIncluded; CSharpSyntaxNode body; - switch (anonymousFunction.Kind()) + switch (lambdaExpressionOrQueryNode.Kind()) { case SyntaxKind.SimpleLambdaExpression: - SimpleLambdaExpressionSyntax simple = (SimpleLambdaExpressionSyntax)anonymousFunction; + SimpleLambdaExpressionSyntax simple = (SimpleLambdaExpressionSyntax)lambdaExpressionOrQueryNode; firstIncluded = simple.Parameter.Identifier; body = simple.Body; break; + case SyntaxKind.ParenthesizedLambdaExpression: - ParenthesizedLambdaExpressionSyntax parenthesized = (ParenthesizedLambdaExpressionSyntax)anonymousFunction; + ParenthesizedLambdaExpressionSyntax parenthesized = (ParenthesizedLambdaExpressionSyntax)lambdaExpressionOrQueryNode; firstIncluded = parenthesized.ParameterList.OpenParenToken; body = parenthesized.Body; break; + case SyntaxKind.AnonymousMethodExpression: - AnonymousMethodExpressionSyntax anon = (AnonymousMethodExpressionSyntax)anonymousFunction; + AnonymousMethodExpressionSyntax anon = (AnonymousMethodExpressionSyntax)lambdaExpressionOrQueryNode; firstIncluded = anon.DelegateKeyword; body = anon.Block; break; + default: // OK, so we have some kind of query clause. They all start with a keyword token, so we'll skip that. - firstIncluded = anonymousFunction.GetFirstToken().GetNextToken(); - return IsBetweenTokens(position, firstIncluded, anonymousFunction.GetLastToken().GetNextToken()); + firstIncluded = lambdaExpressionOrQueryNode.GetFirstToken().GetNextToken(); + return IsBetweenTokens(position, firstIncluded, lambdaExpressionOrQueryNode.GetLastToken().GetNextToken()); } var bodyStatement = body as StatementSyntax; diff --git a/src/Compilers/CSharp/Portable/Syntax/SyntaxNodeFacts.cs b/src/Compilers/CSharp/Portable/Syntax/SyntaxFacts.cs similarity index 96% rename from src/Compilers/CSharp/Portable/Syntax/SyntaxNodeFacts.cs rename to src/Compilers/CSharp/Portable/Syntax/SyntaxFacts.cs index 269ccda42d174..1f5197ac37d24 100644 --- a/src/Compilers/CSharp/Portable/Syntax/SyntaxNodeFacts.cs +++ b/src/Compilers/CSharp/Portable/Syntax/SyntaxFacts.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.CodeAnalysis.CSharp.Syntax; - using static Microsoft.CodeAnalysis.CSharp.SyntaxKind; namespace Microsoft.CodeAnalysis.CSharp @@ -73,7 +72,7 @@ public static bool IsNamespaceAliasQualifier(ExpressionSyntax node) /// public static bool IsInTypeOnlyContext(ExpressionSyntax node) { - node = (ExpressionSyntax)SyntaxFactory.GetStandaloneExpression(node); + node = SyntaxFactory.GetStandaloneExpression(node); var parent = node.Parent; if (parent != null) { @@ -422,5 +421,14 @@ public static bool IsLambdaBody(SyntaxNode node) return false; } + + /// + /// "Pair lambda" is a synthesized lambda that creates an instance of an anonymous type representing a pair of values. + /// TODO: Avoid generating lambdas. Instead generate a method on the anonymous type, or use KeyValuePair instead. + /// + internal static bool IsQueryPairLambda(SyntaxNode syntax) + { + return syntax.IsKind(GroupClause) || syntax.IsKind(JoinClause) || syntax.IsKind(FromClause); + } } -} +} \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/Syntax/SyntaxNavigator.cs b/src/Compilers/CSharp/Portable/Syntax/SyntaxNavigator.cs index 6e13213ca2702..3fa0f7555040c 100644 --- a/src/Compilers/CSharp/Portable/Syntax/SyntaxNavigator.cs +++ b/src/Compilers/CSharp/Portable/Syntax/SyntaxNavigator.cs @@ -1,22 +1,13 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using Microsoft.CodeAnalysis.CSharp.Symbols; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.CSharp.Syntax { - internal class SyntaxNavigator : AbstractSyntaxNavigator + internal sealed class SyntaxNavigator : AbstractSyntaxNavigator { public static readonly AbstractSyntaxNavigator Instance = new SyntaxNavigator(); - private static readonly Func CommonSyntaxTriviaSkipped = - t => t.RawKind == (int)SyntaxKind.SkippedTokensTrivia; - [Flags] private enum SyntaxKinds { @@ -44,45 +35,5 @@ protected override Func GetStepIntoFunction(bool skipped, bo (docComments ? SyntaxKinds.DocComments : 0); return StepIntoFunctions[(int)index]; } - - public static Func ToCommon(Func func) - { - if (ReferenceEquals(func, SyntaxTriviaFunctions.Any)) - { - return SyntaxTrivia.Any; - } - - if (ReferenceEquals(func, SyntaxTriviaFunctions.Skipped)) - { - return CommonSyntaxTriviaSkipped; - } - - if (ReferenceEquals(func, null)) - { - return null; - } - - return t => func((SyntaxTrivia)t); - } - - public static Func ToCommon(Func func) - { - if (ReferenceEquals(func, SyntaxToken.Any)) - { - return SyntaxToken.Any; - } - - if (ReferenceEquals(func, SyntaxToken.NonZeroWidth)) - { - return SyntaxToken.NonZeroWidth; - } - - if (ReferenceEquals(func, null)) - { - return null; - } - - return t => func((SyntaxToken)t); - } } } diff --git a/src/Compilers/CSharp/Portable/Syntax/SyntaxNodeExtensions.cs b/src/Compilers/CSharp/Portable/Syntax/SyntaxNodeExtensions.cs index e726f7cd7a9d0..b0e95d068095b 100644 --- a/src/Compilers/CSharp/Portable/Syntax/SyntaxNodeExtensions.cs +++ b/src/Compilers/CSharp/Portable/Syntax/SyntaxNodeExtensions.cs @@ -4,6 +4,7 @@ using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp { @@ -99,10 +100,5 @@ internal static SyntaxToken ExtractAnonymousTypeMemberName(this ExpressionSyntax } } } - - public static SyntaxReference GetReferenceOrNull(this CSharpSyntaxNode nodeOpt) - { - return (nodeOpt != null) ? nodeOpt.GetReference() : null; - } } } diff --git a/src/Compilers/CSharp/Portable/Syntax/SyntaxUtilities.cs b/src/Compilers/CSharp/Portable/Syntax/SyntaxUtilities.cs new file mode 100644 index 0000000000000..cd6b296353282 --- /dev/null +++ b/src/Compilers/CSharp/Portable/Syntax/SyntaxUtilities.cs @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Diagnostics; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp +{ + internal partial class SyntaxUtilities + { + /// + /// + /// + internal static SyntaxNode GetCorrespondingLambdaBody(SyntaxNode oldBody, SyntaxNode newLambda) + { + var oldLambda = oldBody.Parent; + switch (oldLambda.Kind()) + { + case SyntaxKind.ParenthesizedLambdaExpression: + case SyntaxKind.SimpleLambdaExpression: + case SyntaxKind.AnonymousMethodExpression: + return ((AnonymousFunctionExpressionSyntax)newLambda).Body; + + case SyntaxKind.FromClause: + return ((FromClauseSyntax)newLambda).Expression; + + case SyntaxKind.LetClause: + return ((LetClauseSyntax)newLambda).Expression; + + case SyntaxKind.WhereClause: + return ((WhereClauseSyntax)newLambda).Condition; + + case SyntaxKind.AscendingOrdering: + case SyntaxKind.DescendingOrdering: + return ((OrderingSyntax)newLambda).Expression; + + case SyntaxKind.SelectClause: + return ((SelectClauseSyntax)newLambda).Expression; + + case SyntaxKind.JoinClause: + var oldJoin = (JoinClauseSyntax)oldLambda; + var newJoin = (JoinClauseSyntax)newLambda; + Debug.Assert(oldJoin.LeftExpression == oldBody || oldJoin.RightExpression == oldBody); + return (oldJoin.LeftExpression == oldBody) ? newJoin.LeftExpression : newJoin.RightExpression; + + case SyntaxKind.GroupClause: + var oldGroup = (GroupClauseSyntax)oldLambda; + var newGroup = (GroupClauseSyntax)newLambda; + Debug.Assert(oldGroup.GroupExpression == oldBody || oldGroup.ByExpression == oldBody); + return (oldGroup.GroupExpression == oldBody) ? newGroup.GroupExpression : newGroup.ByExpression; + + default: + throw ExceptionUtilities.UnexpectedValue(oldLambda.Kind()); + } + } + } +} diff --git a/src/Compilers/CSharp/Test/Emit/CSharpCompilerEmitTest.csproj b/src/Compilers/CSharp/Test/Emit/CSharpCompilerEmitTest.csproj index 34e086c7e12bc..445ae3f08ba7b 100644 --- a/src/Compilers/CSharp/Test/Emit/CSharpCompilerEmitTest.csproj +++ b/src/Compilers/CSharp/Test/Emit/CSharpCompilerEmitTest.csproj @@ -170,6 +170,7 @@ + diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenFieldInitTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenFieldInitTests.cs index 2b4e61b000194..df4b5d8db2994 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenFieldInitTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenFieldInitTests.cs @@ -2,10 +2,7 @@ using System.Collections.Generic; using System.Text; -using Microsoft.CodeAnalysis.CSharp.Symbols; -using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; -using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Test.Utilities; using Xunit; diff --git a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueClosureTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueClosureTests.cs index 055fea729844b..37274cefb8812 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueClosureTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueClosureTests.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Immutable; -using System.IO; using System.Linq; using System.Reflection.Metadata.Ecma335; using Microsoft.CodeAnalysis.CSharp.Symbols; @@ -9,7 +8,6 @@ using Microsoft.CodeAnalysis.CSharp.UnitTests; using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.Test.Utilities; -using Roslyn.Test.MetadataUtilities; using Roslyn.Test.Utilities; using Xunit; @@ -51,11 +49,6 @@ static object F(object o) { var reader1 = md1.Reader; - var w = new StringWriter(); - var v = new MetadataVisualizer(new[] { generation0.MetadataReader, reader1 }, w); - v.VisualizeAllGenerations(); - var s = w.ToString(); - // Field 'o' // Methods: 'F', '.ctor', 'b__1' // Type: display class @@ -75,6 +68,376 @@ static object F(object o) } } + [Fact] + public void MethodWithStaticLambda1() + { + var source0 = MarkedSource(@" +using System; + +class C +{ + void F() + { + Func x = () => 1; + } +}"); + var source1 = MarkedSource(@" +using System; + +class C +{ + void F() + { + Func x = () => 2; + } +}"); + var compilation0 = CreateCompilationWithMscorlib(source0.Tree, options: ComSafeDebugDll.WithMetadataImportOptions(MetadataImportOptions.All)); + var compilation1 = compilation0.WithSource(source1.Tree); + + var v0 = CompileAndVerify(compilation0); + var md0 = ModuleMetadata.CreateFromImage(v0.EmittedAssemblyData); + + var f0 = compilation0.GetMember("C.F"); + var f1 = compilation1.GetMember("C.F"); + + var generation0 = EmitBaseline.CreateInitialBaseline(md0, v0.CreatePdbInfoProvider().GetEncMethodDebugInfo); + + var diff1 = compilation1.EmitDifference( + generation0, + ImmutableArray.Create(new SemanticEdit(SemanticEditKind.Update, f0, f1, GetSyntaxMapFromMarkers(source0, source1), preserveLocalVariables: true))); + + // no new synthesized members generated (with #1 in names): + diff1.VerifySynthesizedMembers( + "C: {<>c}", + "C.<>c: {<>9__0_0, b__0_0}"); + + var md1 = diff1.GetMetadata(); + var reader1 = md1.Reader; + + // Method updates + CheckEncLogDefinitions(reader1, + Row(3, TableIndex.StandAloneSig, EditAndContinueOperation.Default), + Row(4, TableIndex.StandAloneSig, EditAndContinueOperation.Default), + Row(1, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(5, TableIndex.MethodDef, EditAndContinueOperation.Default)); + } + + [Fact] + public void MethodWithStaticLambdaGeneric1() + { + var source0 = MarkedSource(@" +using System; + +class C +{ + void F() + { + Func x = () => default(T); + } +}"); + var source1 = MarkedSource(@" +using System; + +class C +{ + void F() + { + Func x = () => default(T); + } +}"); + var compilation0 = CreateCompilationWithMscorlib(source0.Tree, options: ComSafeDebugDll.WithMetadataImportOptions(MetadataImportOptions.All)); + var compilation1 = compilation0.WithSource(source1.Tree); + + var v0 = CompileAndVerify(compilation0); + var md0 = ModuleMetadata.CreateFromImage(v0.EmittedAssemblyData); + + var f0 = compilation0.GetMember("C.F"); + var f1 = compilation1.GetMember("C.F"); + + var generation0 = EmitBaseline.CreateInitialBaseline(md0, v0.CreatePdbInfoProvider().GetEncMethodDebugInfo); + + var diff1 = compilation1.EmitDifference( + generation0, + ImmutableArray.Create(new SemanticEdit(SemanticEditKind.Update, f0, f1, GetSyntaxMapFromMarkers(source0, source1), preserveLocalVariables: true))); + + // no new synthesized members generated (with #1 in names): + diff1.VerifySynthesizedMembers( + "C: {<>c__0}", + "C.<>c__0: {<>9__0_0, b__0_0}"); + + var md1 = diff1.GetMetadata(); + var reader1 = md1.Reader; + + // Method updates + CheckEncLogDefinitions(reader1, + Row(3, TableIndex.StandAloneSig, EditAndContinueOperation.Default), + Row(4, TableIndex.StandAloneSig, EditAndContinueOperation.Default), + Row(1, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(5, TableIndex.MethodDef, EditAndContinueOperation.Default)); + } + + [Fact] + public void MethodWithThisOnlyClosure1() + { + var source0 = MarkedSource(@" +using System; + +class C +{ + int F(int a) + { + Func x = () => F(1); + return 1; + } +}"); + var source1 = MarkedSource(@" +using System; + +class C +{ + int F(int a) + { + Func x = () => F(2); + return 2; + } +}"); + var compilation0 = CreateCompilationWithMscorlib(source0.Tree, options: ComSafeDebugDll.WithMetadataImportOptions(MetadataImportOptions.All)); + var compilation1 = compilation0.WithSource(source1.Tree); + + var v0 = CompileAndVerify(compilation0); + var md0 = ModuleMetadata.CreateFromImage(v0.EmittedAssemblyData); + + var f0 = compilation0.GetMember("C.F"); + var f1 = compilation1.GetMember("C.F"); + + var generation0 = EmitBaseline.CreateInitialBaseline(md0, v0.CreatePdbInfoProvider().GetEncMethodDebugInfo); + + var diff1 = compilation1.EmitDifference( + generation0, + ImmutableArray.Create(new SemanticEdit(SemanticEditKind.Update, f0, f1, GetSyntaxMapFromMarkers(source0, source1), preserveLocalVariables: true))); + + // no new synthesized members generated (with #1 in names): + diff1.VerifySynthesizedMembers( + "C: {b__0_0}"); + + var md1 = diff1.GetMetadata(); + var reader1 = md1.Reader; + + // Method updates + CheckEncLogDefinitions(reader1, + Row(3, TableIndex.StandAloneSig, EditAndContinueOperation.Default), + Row(4, TableIndex.StandAloneSig, EditAndContinueOperation.Default), + Row(1, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(3, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(5, TableIndex.CustomAttribute, EditAndContinueOperation.Default)); + } + + [Fact] + public void MethodWithClosure1() + { + var source0 = MarkedSource(@" +using System; + +class C +{ + int F(int a) + { + Func x = () => F(a + 1); + return 1; + } +}"); + var source1 = MarkedSource(@" +using System; + +class C +{ + int F(int a) + { + Func x = () => F(a + 2); + return 2; + } +}"); + var compilation0 = CreateCompilationWithMscorlib(source0.Tree, options: ComSafeDebugDll.WithMetadataImportOptions(MetadataImportOptions.All)); + var compilation1 = compilation0.WithSource(source1.Tree); + + var v0 = CompileAndVerify(compilation0); + var md0 = ModuleMetadata.CreateFromImage(v0.EmittedAssemblyData); + + var f0 = compilation0.GetMember("C.F"); + var f1 = compilation1.GetMember("C.F"); + + var generation0 = EmitBaseline.CreateInitialBaseline(md0, v0.CreatePdbInfoProvider().GetEncMethodDebugInfo); + + var diff1 = compilation1.EmitDifference( + generation0, + ImmutableArray.Create(new SemanticEdit(SemanticEditKind.Update, f0, f1, GetSyntaxMapFromMarkers(source0, source1), preserveLocalVariables: true))); + + // no new synthesized members generated (with #1 in names): + diff1.VerifySynthesizedMembers( + "C: {<>c__DisplayClass0_0}", + "C.<>c__DisplayClass0_0: {a, <>4__this, b__0}"); + + var md1 = diff1.GetMetadata(); + var reader1 = md1.Reader; + + // Method updates + CheckEncLogDefinitions(reader1, + Row(3, TableIndex.StandAloneSig, EditAndContinueOperation.Default), + Row(4, TableIndex.StandAloneSig, EditAndContinueOperation.Default), + Row(1, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(4, TableIndex.MethodDef, EditAndContinueOperation.Default)); + } + + [Fact] + public void ConstructorWithClosure1() + { + var source0 = MarkedSource(@" +using System; + +class D { public D(Func f) { } } + +class C : D +{ + public C(int a, int b) + : base(() => a) + { + int c = 0; + + Func f1 = () => a + 1; + Func f2 = () => b + 2; + Func f3 = () => c + 3; + Func f4 = () => a + b + c; + Func f5 = () => a + c; + Func f6 = () => b + c; + } +}"); + var source1 = MarkedSource(@" +using System; + +class D { public D(Func f) { } } + +class C : D +{ + public C(int a, int b) + : base(() => a * 10) + { + int c = 0; + + Func f1 = () => a * 10 + 1; + Func f2 = () => b * 10 + 2; + Func f3 = () => c * 10 + 3; + Func f4 = () => a * 10 + b + c; + Func f5 = () => a * 10 + c; + Func f6 = () => b * 10 + c; + } +}"); + var compilation0 = CreateCompilationWithMscorlib(source0.Tree, options: ComSafeDebugDll.WithMetadataImportOptions(MetadataImportOptions.All)); + var compilation1 = compilation0.WithSource(source1.Tree); + + var v0 = CompileAndVerify(compilation0); + var md0 = ModuleMetadata.CreateFromImage(v0.EmittedAssemblyData); + + var ctor0 = compilation0.GetMember("C").InstanceConstructors.Single(); + var ctor1 = compilation1.GetMember("C").InstanceConstructors.Single(); + + var generation0 = EmitBaseline.CreateInitialBaseline(md0, v0.CreatePdbInfoProvider().GetEncMethodDebugInfo); + + var diff1 = compilation1.EmitDifference( + generation0, + ImmutableArray.Create(new SemanticEdit(SemanticEditKind.Update, ctor0, ctor1, GetSyntaxMapFromMarkers(source0, source1), preserveLocalVariables: true))); + + // no new synthesized members generated (with #1 in names): + diff1.VerifySynthesizedMembers( + "C: {<>c__DisplayClass0_0, <>c__DisplayClass0_1}", + "C.<>c__DisplayClass0_0: {a, b, <.ctor>b__0, <.ctor>b__1, <.ctor>b__2}", + "C.<>c__DisplayClass0_1: {c, CS$<>8__locals1, <.ctor>b__3, <.ctor>b__4, <.ctor>b__5, <.ctor>b__6}"); + + var md1 = diff1.GetMetadata(); + var reader1 = md1.Reader; + + // Method updates + CheckEncLogDefinitions(reader1, + Row(3, TableIndex.StandAloneSig, EditAndContinueOperation.Default), + Row(4, TableIndex.StandAloneSig, EditAndContinueOperation.Default), + Row(2, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(4, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(5, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(6, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(8, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(9, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(10, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(11, TableIndex.MethodDef, EditAndContinueOperation.Default)); + } + + [Fact] + public void JoinAndGroupByClauses() + { + var source0 = MarkedSource(@" +using System.Linq; + +class C +{ + void F() + { + var result = from a in new[] { 1, 2, 3 } + join b in new[] { 5 } on a + 1 equals b - 1 + group new { a, b = a + 5 } by new { c = a + 4 } into d + select d.Key; + } +}"); + var source1 = MarkedSource(@" +using System.Linq; + +class C +{ + void F() + { + var result = from a in new[] { 10, 20, 30 } + join b in new[] { 50 } on a + 10 equals b - 10 + group new { a, b = a + 50 } by new { c = a + 40 } into d + select d.Key; + } +}"); + var compilation0 = CreateCompilationWithMscorlib(source0.Tree, new[] { SystemCoreRef }, options: ComSafeDebugDll.WithMetadataImportOptions(MetadataImportOptions.All)); + var compilation1 = compilation0.WithSource(source1.Tree); + + var v0 = CompileAndVerify(compilation0); + var md0 = ModuleMetadata.CreateFromImage(v0.EmittedAssemblyData); + + var f0 = compilation0.GetMember("C.F"); + var f1 = compilation1.GetMember("C.F"); + + var generation0 = EmitBaseline.CreateInitialBaseline(md0, v0.CreatePdbInfoProvider().GetEncMethodDebugInfo); + + var diff1 = compilation1.EmitDifference( + generation0, + ImmutableArray.Create(new SemanticEdit(SemanticEditKind.Update, f0, f1, GetSyntaxMapFromMarkers(source0, source1), preserveLocalVariables: true))); + + // no new synthesized members generated (with #1 in names): + diff1.VerifySynthesizedMembers( + "C: {<>c}", + "C.<>c: {<>9__0_0, <>9__0_1, <>9__0_2, <>9__0_3, <>9__0_4, <>9__0_5, b__0_0, b__0_1, b__0_2, b__0_3, b__0_4, b__0_5}", + "<>f__AnonymousType1<j__TPar>: {Equals, GetHashCode, ToString}", + "<>f__AnonymousType0<j__TPar, j__TPar>: {Equals, GetHashCode, ToString}"); + + var md1 = diff1.GetMetadata(); + var reader1 = md1.Reader; + + // Method updates for lambdas: + CheckEncLogDefinitions(reader1, + Row(9, TableIndex.StandAloneSig, EditAndContinueOperation.Default), + Row(10, TableIndex.StandAloneSig, EditAndContinueOperation.Default), + Row(11, TableIndex.StandAloneSig, EditAndContinueOperation.Default), + Row(12, TableIndex.StandAloneSig, EditAndContinueOperation.Default), + Row(12, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(16, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(17, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(18, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(19, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(20, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(21, TableIndex.MethodDef, EditAndContinueOperation.Default)); + } + [Fact] public void UniqueSynthesizedNames1() { diff --git a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTestBase.cs b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTestBase.cs index 34e12464bf108..f91be117a4c09 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTestBase.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTestBase.cs @@ -1,10 +1,12 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.IO; using System.Linq; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; +using System.Text.RegularExpressions; using Microsoft.CodeAnalysis.CodeGen; using Microsoft.CodeAnalysis.CSharp.Emit; using Microsoft.CodeAnalysis.CSharp.Symbols; @@ -13,10 +15,12 @@ using Microsoft.CodeAnalysis.CSharp.UnitTests; using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.SymReaderInterop; using Roslyn.Test.MetadataUtilities; using Roslyn.Test.PdbUtilities; using Roslyn.Test.Utilities; +using Roslyn.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.CSharp.EditAndContinue.UnitTests @@ -35,6 +39,16 @@ internal static string Visualize(ModuleMetadata baseline, params PinnedMetadata[ return result.ToString(); } + internal static SourceWithMarkedNodes MarkedSource(string markedSource, string fileName = "", CSharpParseOptions options = null) + { + return new SourceWithMarkedNodes(markedSource, s => Parse(s, fileName, options), s => (int)(SyntaxKind)typeof(SyntaxKind).GetField(s).GetValue(null)); + } + + internal static Func GetSyntaxMapFromMarkers(SourceWithMarkedNodes source0, SourceWithMarkedNodes source1) + { + return SourceWithMarkedNodes.GetSyntaxMap(source0, source1); + } + internal static ImmutableArray GetAllLocals(MethodSymbol method) { var sourceMethod = method as SourceMethodSymbol; @@ -241,5 +255,10 @@ internal static CSharpCompilation WithSource(this CSharpCompilation compilation, { return compilation.RemoveAllSyntaxTrees().AddSyntaxTrees(CSharpTestBase.Parse(newSource)); } + + internal static CSharpCompilation WithSource(this CSharpCompilation compilation, SyntaxTree newTree) + { + return compilation.RemoveAllSyntaxTrees().AddSyntaxTrees(newTree); + } } } diff --git a/src/Compilers/CSharp/Test/Emit/PDB/PDBAsyncTests.cs b/src/Compilers/CSharp/Test/Emit/PDB/PDBAsyncTests.cs index 630578eb7732c..c99d1c840ed10 100644 --- a/src/Compilers/CSharp/Test/Emit/PDB/PDBAsyncTests.cs +++ b/src/Compilers/CSharp/Test/Emit/PDB/PDBAsyncTests.cs @@ -227,6 +227,10 @@ .locals init (int V_0, + + 1 + + @@ -650,6 +654,11 @@ public void F() { } // needs to be present to work around SymWriter bug #1068894 + + 0 + + + @@ -746,6 +755,11 @@ public void F() { } // needs to be present to work around SymWriter bug #1068894 + + 0 + + + @@ -831,6 +845,11 @@ public void F() { } // needs to be present to work around SymWriter bug #1068894 + + 0 + + + @@ -927,6 +946,11 @@ public void F() { } // needs to be present to work around SymWriter bug #1068894 + + 0 + + + diff --git a/src/Compilers/CSharp/Test/Emit/PDB/PDBConstantTests.cs b/src/Compilers/CSharp/Test/Emit/PDB/PDBConstantTests.cs index 5c70f7baa7a3c..2d72f7ff76257 100644 --- a/src/Compilers/CSharp/Test/Emit/PDB/PDBConstantTests.cs +++ b/src/Compilers/CSharp/Test/Emit/PDB/PDBConstantTests.cs @@ -87,6 +87,10 @@ void M(Action a) + + 0 + + diff --git a/src/Compilers/CSharp/Test/Emit/PDB/PDBDynamicLocalsTests.cs b/src/Compilers/CSharp/Test/Emit/PDB/PDBDynamicLocalsTests.cs index 47539258bb53b..9ffa36bef66e9 100644 --- a/src/Compilers/CSharp/Test/Emit/PDB/PDBDynamicLocalsTests.cs +++ b/src/Compilers/CSharp/Test/Emit/PDB/PDBDynamicLocalsTests.cs @@ -1032,6 +1032,12 @@ public static void Main(string[] args) + + 2 + + + + @@ -1216,6 +1222,11 @@ from score in scores + + 0 + + + diff --git a/src/Compilers/CSharp/Test/Emit/PDB/PDBIteratorTests.cs b/src/Compilers/CSharp/Test/Emit/PDB/PDBIteratorTests.cs index 2d89aeee85fdf..57af9c3aac5b7 100644 --- a/src/Compilers/CSharp/Test/Emit/PDB/PDBIteratorTests.cs +++ b/src/Compilers/CSharp/Test/Emit/PDB/PDBIteratorTests.cs @@ -1220,6 +1220,11 @@ .locals init (int V_0) + + 0 + + + @@ -1373,6 +1378,11 @@ .locals init (int V_0, + + 0 + + + diff --git a/src/Compilers/CSharp/Test/Emit/PDB/PDBLambdaTests.cs b/src/Compilers/CSharp/Test/Emit/PDB/PDBLambdaTests.cs new file mode 100644 index 0000000000000..e3f00359a87a7 --- /dev/null +++ b/src/Compilers/CSharp/Test/Emit/PDB/PDBLambdaTests.cs @@ -0,0 +1,840 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.CodeAnalysis.CSharp.Test.Utilities; +using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.CSharp.UnitTests.PDB +{ + public class PDBLambdaTests : CSharpPDBTestBase + { + [WorkItem(539898, "DevDiv")] + [Fact] + public void SequencePoints_Body() + { + var source = @" +using System; +delegate void D(); +class C +{ + public static void Main() + { + D d = () => Console.Write(1); + d(); + } +} +"; + + var c = CreateCompilationWithMscorlibAndSystemCore(source, options: TestOptions.DebugDll); + c.VerifyPdb(@" + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + +"); + } + + [Fact, WorkItem(543479, "DevDiv")] + public void Nested() + { + var source = @" +using System; +class Test +{ + public static int Main() + { + if (M(1) != 10) + return 1; + return 0; + } + + static public int M(int p) + { + Func f1 = delegate(int x) + { + int q = 2; + Func f2 = (y) => + { + return p + q + x + y; + }; + return f2(3); + }; + return f1(4); + } +} +"; + var c = CreateCompilationWithMscorlibAndSystemCore(source, options: TestOptions.DebugExe); + c.VerifyPdb(@" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +"); + } + + [Fact, WorkItem(543479, "DevDiv")] + public void InitialSequencePoints() + { + var source = @" +class Test +{ + void Foo(int p) + { + System.Func f1 = () => p; + f1(); + } +} +"; + // Specifically note the sequence points at 0x0 in Test.Main, Test.M, and the lambda bodies. + var c = CreateCompilationWithMscorlibAndSystemCore(source, options: TestOptions.DebugDll); + c.VerifyPdb(@" + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + +"); + } + + [Fact, WorkItem(543479, "DevDiv")] + public void Nested_InitialSequencePoints() + { + var source = @" +using System; +class Test +{ + public static int Main() + { + if (M(1) != 10) // can't step into M() at all + return 1; + return 0; + } + + static public int M(int p) + { + Func f1 = delegate(int x) + { + int q = 2; + Func f2 = (y) => { return p + q + x + y; }; + return f2(3); + }; + return f1(4); + } +} +"; + // Specifically note the sequence points at 0x0 in Test.Main, Test.M, and the lambda bodies. + var c = CreateCompilationWithMscorlibAndSystemCore(source, options: TestOptions.DebugDll); + c.VerifyPdb(@" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +"); + } + + [Fact] + public void FieldAndPropertyInitializers() + { + var source = @" +using System; + +class B +{ + public B(Func f) { } +} + +class C : B +{ + Func FI = () => 1; + static Func FS = () => 2; + Func P { get; } = () => FS(); + public C() : base(() => 3) {} +} +"; + + var c = CreateCompilationWithMscorlibAndSystemCore(source, options: TestOptions.DebugDll); + c.VerifyDiagnostics(); + + c.VerifyPdb(@" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + + + + + + + + + + + + + + + + + + + 6 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +"); + } + + [Fact] + public void ClosuresInCtor() + { + var source = @" +using System; + +class B +{ + public B(Func f) { } +} + +class C : B +{ + Func f, g, h; + + public C(int a, int b) : base(() => a) + { + int c = 1; + f = () => b; + g = () => f(); + h = () => c; + } +} +"; + + var c = CreateCompilationWithMscorlibAndSystemCore(source, options: TestOptions.DebugDll); + c.VerifyDiagnostics(); + + c.VerifyPdb(@" + + + + + + + + + + + + + + + + + + + + + + + + + + + 3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +"); + } + + [Fact] + public void Queries1() + { + var source = @" +using System.Linq; + +class C +{ + public void M() + { + int c = 1; + var x = from a in new[] { 1, 2, 3 } + let b = a + c + where b > 10 + select b * 10; + } +} +"; + + var c = CreateCompilationWithMscorlibAndSystemCore(source, options: TestOptions.DebugDll); + c.VerifyDiagnostics(); + + c.VerifyPdb(@" + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +"); + } + + [Fact] + public void Queries_GroupBy1() + { + var source = @" +using System.Linq; + +class C +{ + void F() + { + var result = from/*0*/ a in new[] { 1, 2, 3 } + join/*1*/ b in new[] { 5 } on a + 1 equals b - 1 + group/*2*/ new { a, b = a + 5 } by new { c = a + 4 } into d + select/*3*/ d.Key; + } +} +"; + + var c = CreateCompilationWithMscorlibAndSystemCore(source, options: TestOptions.DebugDll); + c.VerifyDiagnostics(); + + c.VerifyPdb(@" + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +"); + } + } +} diff --git a/src/Compilers/CSharp/Test/Emit/PDB/PDBTests.cs b/src/Compilers/CSharp/Test/Emit/PDB/PDBTests.cs index dc2978629221b..069aa0d2a4c62 100644 --- a/src/Compilers/CSharp/Test/Emit/PDB/PDBTests.cs +++ b/src/Compilers/CSharp/Test/Emit/PDB/PDBTests.cs @@ -1,4 +1,4 @@ -// 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; using System.Globalization; @@ -278,6 +278,14 @@ class C + + 2 + + + + + + @@ -353,6 +361,144 @@ class C "); } + /// + /// Leading trivia is not included in the syntax offset. + /// + [Fact] + public void SyntaxOffsetInPresenceOfTrivia_Methods() + { + string source = @" +class C +{ + public static void Main1() /*Comment1*/{/*Comment2*/int a = 1;/*Comment3*/}/*Comment4*/ + public static void Main2() {/*Comment2*/int a = 2;/*Comment3*/}/*Comment4*/ +}"; + var c = CreateCompilationWithMscorlibAndSystemCore(source, options: TestOptions.DebugDll); + + // verify that both syntax offsets are the same + c.VerifyPdb(@" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +"); + } + + /// + /// Leading and trailing trivia are not included in the syntax offset. + /// + [Fact] + public void SyntaxOffsetInPresenceOfTrivia_Initializers() + { + string source = @" +using System; +class C1 +{ + public static Func e=() => 0; + public static Func f/*Comment0*/=/*Comment1*/() => 1;/*Comment2*/ + public static Func g=() => 2; +} +class C2 +{ + public static Func e=() => 0; + public static Func f=/*Comment1*/() => 1; + public static Func g=() => 2; +} +"; + var c = CreateCompilationWithMscorlibAndSystemCore(source, options: TestOptions.DebugDll); + + // verify that syntax offsets of both .cctor's are the same + c.VerifyPdb("C1..cctor", @" + + + + + + + + + 4 + + + + + + + + + + + + + + + + +"); + + c.VerifyPdb("C2..cctor", @" + + + + + + + 4 + + + + + + + + + + + + + +"); + } + #endregion #region IfStatement @@ -1596,364 +1742,6 @@ public class Derived : Base #endregion - #region Lambdas - - [WorkItem(539898, "DevDiv")] - [Fact] - public void LambdaSequencePoints_Body() - { - var source = @"using System; -delegate void D(); -class C -{ - public static void Main() - { - D d = () => Console.Write(1); - d(); - } -} -"; - - var c = CreateCompilationWithMscorlibAndSystemCore(source, options: TestOptions.DebugDll); - c.VerifyPdb(@" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); - } - - [Fact, WorkItem(543479, "DevDiv")] - public void Lambdas_Nested() - { - var source = @"using System; -class Test -{ - public static int Main() - { - if (M(1) != 10) - return 1; - return 0; - } - - static public int M(int p) - { - Func f1 = delegate(int x) - { - int q = 2; - Func f2 = (y) => - { - return p + q + x + y; - }; - return f2(3); - }; - return f1(4); - } -} -"; - var c = CreateCompilationWithMscorlibAndSystemCore(source, options: TestOptions.DebugExe); - c.VerifyPdb(@" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); - } - - [Fact, WorkItem(543479, "DevDiv")] - public void Lambdas_InitialSequencePoints() - { - var source = @" -class Test -{ - void Foo(int p) - { - System.Func f1 = () => p; - f1(); - } -} -"; - // Specifically note the sequence points at 0x0 in Test.Main, Test.M, and the lambda bodies. - var c = CreateCompilationWithMscorlibAndSystemCore(source, options: TestOptions.DebugDll); - c.VerifyPdb(@" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); - } - - [Fact, WorkItem(543479, "DevDiv")] - public void Lambdas_Nested_InitialSequencePoints() - { - var source = @" -using System; -class Test -{ - public static int Main() - { - if (M(1) != 10) // can't step into M() at all - return 1; - return 0; - } - - static public int M(int p) - { - Func f1 = delegate(int x) - { - int q = 2; - Func f2 = (y) => { return p + q + x + y; }; - return f2(3); - }; - return f1(4); - } -} -"; - // Specifically note the sequence points at 0x0 in Test.Main, Test.M, and the lambda bodies. - var c = CreateCompilationWithMscorlibAndSystemCore(source, options: TestOptions.DebugDll); - c.VerifyPdb(@" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); - } - - #endregion - #region Field and Property Initializers [Fact] @@ -2125,6 +1913,10 @@ class C + + 1 + + diff --git a/src/Compilers/CSharp/Test/Emit/PDB/PDBUsingTests.cs b/src/Compilers/CSharp/Test/Emit/PDB/PDBUsingTests.cs index da12dda716735..3f9370f8ba2f2 100644 --- a/src/Compilers/CSharp/Test/Emit/PDB/PDBUsingTests.cs +++ b/src/Compilers/CSharp/Test/Emit/PDB/PDBUsingTests.cs @@ -1337,6 +1337,10 @@ class C + + 2 + + @@ -1349,6 +1353,10 @@ class C + + 3 + + diff --git a/src/Compilers/Core/CodeAnalysisTest/Emit/CustomDebugInfoReaderTests.cs b/src/Compilers/Core/CodeAnalysisTest/Emit/CustomDebugInfoReaderTests.cs index ff7a36cafa259..7481049543be0 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Emit/CustomDebugInfoReaderTests.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Emit/CustomDebugInfoReaderTests.cs @@ -145,17 +145,97 @@ public void EditAndContinueLocalSlotMap_NegativeSyntaxOffsets() new LocalSlotDebugInfo(SynthesizedLocalKind.UserDefined, new LocalDebugId(-1, 10)), new LocalSlotDebugInfo(SynthesizedLocalKind.TryAwaitPendingCaughtException, new LocalDebugId(-20000, 10))); + var closures = ImmutableArray.Empty; + var lambdas = ImmutableArray.Empty; + var customMetadata = new Cci.MemoryStream(); var cmw = new Cci.BinaryWriter(customMetadata); - new EditAndContinueMethodDebugInformation(slots).SerializeLocalSlots(cmw); + new EditAndContinueMethodDebugInformation(123, slots, closures, lambdas).SerializeLocalSlots(cmw); var bytes = customMetadata.ToImmutableArray(); - AssertEx.Equal(new byte[] { 0xFE, 0xC0, 0x00, 0x4E, 0x20, 0x81, 0xC0, 0x00, 0x4E, 0x1F, 0x0A, 0x9A, 0x00, 0x0A }, bytes); + AssertEx.Equal(new byte[] { 0xFF, 0xC0, 0x00, 0x4E, 0x20, 0x81, 0xC0, 0x00, 0x4E, 0x1F, 0x0A, 0x9A, 0x00, 0x0A }, bytes); - var deserialized = EditAndContinueMethodDebugInformation.Create(bytes).LocalSlots; + var deserialized = EditAndContinueMethodDebugInformation.Create(bytes, default(ImmutableArray)).LocalSlots; AssertEx.Equal(slots, deserialized); } + + [Fact] + public void EditAndContinueLambdaAndClosureMap_NegativeSyntaxOffsets() + { + var slots = ImmutableArray.Empty; + + var closures = ImmutableArray.Create( + new ClosureDebugInfo(-100), + new ClosureDebugInfo(10), + new ClosureDebugInfo(-200)); + + var lambdas = ImmutableArray.Create( + new LambdaDebugInfo(20, 1), + new LambdaDebugInfo(-50, 0), + new LambdaDebugInfo(-180, -1)); + + var customMetadata = new Cci.MemoryStream(); + var cmw = new Cci.BinaryWriter(customMetadata); + + new EditAndContinueMethodDebugInformation(0x7b, slots, closures, lambdas).SerializeLambdaMap(cmw); + + var bytes = customMetadata.ToImmutableArray(); + + AssertEx.Equal(new byte[] { 0x7C, 0x80, 0xC8, 0x03, 0x64, 0x80, 0xD2, 0x00, 0x80, 0xDC, 0x02, 0x80, 0x96, 0x01, 0x14, 0x00 }, bytes); + + var deserialized = EditAndContinueMethodDebugInformation.Create(default(ImmutableArray), bytes); + + AssertEx.Equal(closures, deserialized.Closures); + AssertEx.Equal(lambdas, deserialized.Lambdas); + } + + [Fact] + public void EditAndContinueLambdaAndClosureMap_NoClosures() + { + var slots = ImmutableArray.Empty; + + var closures = ImmutableArray.Empty; + var lambdas = ImmutableArray.Create(new LambdaDebugInfo(20, -1)); + + var customMetadata = new Cci.MemoryStream(); + var cmw = new Cci.BinaryWriter(customMetadata); + + new EditAndContinueMethodDebugInformation(-1, slots, closures, lambdas).SerializeLambdaMap(cmw); + + var bytes = customMetadata.ToImmutableArray(); + + AssertEx.Equal(new byte[] { 0x00, 0x01, 0x00, 0x15, 0x00 }, bytes); + + var deserialized = EditAndContinueMethodDebugInformation.Create(default(ImmutableArray), bytes); + + AssertEx.Equal(closures, deserialized.Closures); + AssertEx.Equal(lambdas, deserialized.Lambdas); + } + + [Fact] + public void EditAndContinueLambdaAndClosureMap_NoLambdas() + { + // should not happen in practice, but EditAndContinueMethodDebugInformation should handle it just fine + + var slots = ImmutableArray.Empty; + var closures = ImmutableArray.Empty; + var lambdas = ImmutableArray.Empty; + + var customMetadata = new Cci.MemoryStream(); + var cmw = new Cci.BinaryWriter(customMetadata); + + new EditAndContinueMethodDebugInformation(10, slots, closures, lambdas).SerializeLambdaMap(cmw); + + var bytes = customMetadata.ToImmutableArray(); + + AssertEx.Equal(new byte[] { 0x0B, 0x01, 0x00 }, bytes); + + var deserialized = EditAndContinueMethodDebugInformation.Create(default(ImmutableArray), bytes); + + AssertEx.Equal(closures, deserialized.Closures); + AssertEx.Equal(lambdas, deserialized.Lambdas); + } } } diff --git a/src/Compilers/Core/Portable/CodeAnalysis.csproj b/src/Compilers/Core/Portable/CodeAnalysis.csproj index f3d23e2014b02..1d82337594116 100644 --- a/src/Compilers/Core/Portable/CodeAnalysis.csproj +++ b/src/Compilers/Core/Portable/CodeAnalysis.csproj @@ -62,6 +62,7 @@ + @@ -72,6 +73,7 @@ + @@ -83,6 +85,7 @@ + diff --git a/src/Compilers/Core/Portable/CodeGen/ClosureDebugInfo.cs b/src/Compilers/Core/Portable/CodeGen/ClosureDebugInfo.cs new file mode 100644 index 0000000000000..d7e93f618dd6b --- /dev/null +++ b/src/Compilers/Core/Portable/CodeGen/ClosureDebugInfo.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CodeGen +{ + internal struct ClosureDebugInfo : IEquatable + { + public readonly int SyntaxOffset; + + public ClosureDebugInfo(int syntaxOffset) + { + this.SyntaxOffset = syntaxOffset; + } + + public bool Equals(ClosureDebugInfo other) + { + return this.SyntaxOffset == other.SyntaxOffset; + } + + public override bool Equals(object obj) + { + return obj is ClosureDebugInfo && Equals((ClosureDebugInfo)obj); + } + + public override int GetHashCode() + { + return SyntaxOffset.GetHashCode(); + } + + public override string ToString() + { + return $"({SyntaxOffset})"; + } + } +} diff --git a/src/Compilers/Core/Portable/CodeGen/LambdaDebugInfo.cs b/src/Compilers/Core/Portable/CodeGen/LambdaDebugInfo.cs new file mode 100644 index 0000000000000..8baa72aa199fa --- /dev/null +++ b/src/Compilers/Core/Portable/CodeGen/LambdaDebugInfo.cs @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Diagnostics; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CodeGen +{ + /// + /// Debug information maintained for each lambda. + /// + /// + /// The information is emitted to PDB in Custom Debug Information record for a method containing the lambda. + /// + internal struct LambdaDebugInfo : IEquatable + { + /// + /// The syntax offset of the syntax node declaring the lambda (lambda expression) or its body (lambda in a query). + /// + public readonly int SyntaxOffset; + + /// + /// The ordinal of the closure frame the lambda belongs to, or -1 if not applicable + /// (static lambdas, lambdas closing over this pointer only). + /// + public readonly int ClosureOrdinal; + + public LambdaDebugInfo(int syntaxOffset, int closureOrdinal) + { + Debug.Assert(closureOrdinal >= -1); + + this.SyntaxOffset = syntaxOffset; + this.ClosureOrdinal = closureOrdinal; + } + + public bool Equals(LambdaDebugInfo other) + { + return this.SyntaxOffset == other.SyntaxOffset + && this.ClosureOrdinal == other.ClosureOrdinal; + } + + public override bool Equals(object obj) + { + return obj is LambdaDebugInfo && Equals((LambdaDebugInfo)obj); + } + + public override int GetHashCode() + { + return Hash.Combine(ClosureOrdinal, SyntaxOffset); + } + + public override string ToString() + { + return $"({SyntaxOffset}, {ClosureOrdinal})"; + } + } +} diff --git a/src/Compilers/Core/Portable/CodeGen/LocalSlotDebugInfo.cs b/src/Compilers/Core/Portable/CodeGen/LocalSlotDebugInfo.cs index 0ec91936073b8..65dcd12995ee9 100644 --- a/src/Compilers/Core/Portable/CodeGen/LocalSlotDebugInfo.cs +++ b/src/Compilers/Core/Portable/CodeGen/LocalSlotDebugInfo.cs @@ -31,5 +31,10 @@ public override int GetHashCode() { return Hash.Combine((int)SynthesizedKind, Id.GetHashCode()); } + + public override string ToString() + { + return SynthesizedKind.ToString() + " " + Id.ToString(); + } } } diff --git a/src/Compilers/Core/Portable/CodeGen/MethodBody.cs b/src/Compilers/Core/Portable/CodeGen/MethodBody.cs index a11853028d014..9f2237a36608f 100644 --- a/src/Compilers/Core/Portable/CodeGen/MethodBody.cs +++ b/src/Compilers/Core/Portable/CodeGen/MethodBody.cs @@ -1,6 +1,7 @@ -// 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; using System.Collections.Immutable; using System.Diagnostics; using Microsoft.CodeAnalysis.Emit; @@ -13,27 +14,38 @@ namespace Microsoft.CodeAnalysis.CodeGen /// internal sealed class MethodBody : Cci.IMethodBody { + private readonly Cci.IMethodDefinition _parent; + private readonly byte[] _ilBits; - private readonly Cci.AsyncMethodBodyDebugInfo _asyncMethodDebugInfo; private readonly ushort _maxStack; - private readonly Cci.IMethodDefinition _parent; - private readonly ImmutableArray _locals; // built by someone else + private readonly ImmutableArray _locals; + private readonly ImmutableArray _exceptionHandlers; + + // Debug information emitted to Release & Debug PDBs supporting the debugger, EEs and other tools: private readonly SequencePointList _sequencePoints; private readonly DebugDocumentProvider _debugDocumentProvider; - private readonly ImmutableArray _exceptionHandlers; private readonly ImmutableArray _localScopes; private readonly ImmutableArray _namespaceScopes; private readonly string _stateMachineTypeNameOpt; private readonly ImmutableArray _stateMachineHoistedLocalScopes; - private readonly ImmutableArray _stateMachineHoistedLocalSlots; - private readonly ImmutableArray _stateMachineAwaiterSlots; private readonly Cci.NamespaceScopeEncoding _namespaceScopeEncoding; private readonly bool _hasDynamicLocalVariables; + private readonly Cci.AsyncMethodBodyDebugInfo _asyncMethodDebugInfo; + + // Debug information emitted to Debug PDBs supporting EnC: + private readonly int _methodOrdinal; + private readonly ImmutableArray _stateMachineHoistedLocalSlots; + private readonly ImmutableArray _lambdaDebugInfo; + private readonly ImmutableArray _closureDebugInfo; + + // Data used when emitting EnC delta: + private readonly ImmutableArray _stateMachineAwaiterSlots; public MethodBody( byte[] ilBits, ushort maxStack, Cci.IMethodDefinition parent, + int methodOrdinal, ImmutableArray locals, SequencePointList sequencePoints, DebugDocumentProvider debugDocumentProvider, @@ -42,6 +54,8 @@ public MethodBody( bool hasDynamicLocalVariables, ImmutableArray namespaceScopes, Cci.NamespaceScopeEncoding namespaceScopeEncoding, + ImmutableArray lambdaDebugInfo, + ImmutableArray closureDebugInfo, string stateMachineTypeNameOpt, ImmutableArray stateMachineHoistedLocalScopes, ImmutableArray stateMachineHoistedLocalSlots, @@ -56,6 +70,7 @@ public MethodBody( _asyncMethodDebugInfo = asyncMethodDebugInfo; _maxStack = maxStack; _parent = parent; + _methodOrdinal = methodOrdinal; _locals = locals; _sequencePoints = sequencePoints; _debugDocumentProvider = debugDocumentProvider; @@ -64,6 +79,8 @@ public MethodBody( _namespaceScopeEncoding = namespaceScopeEncoding; _hasDynamicLocalVariables = hasDynamicLocalVariables; _namespaceScopes = namespaceScopes.IsDefault ? ImmutableArray.Empty : namespaceScopes; + _lambdaDebugInfo = lambdaDebugInfo; + _closureDebugInfo = closureDebugInfo; _stateMachineTypeNameOpt = stateMachineTypeNameOpt; _stateMachineHoistedLocalScopes = stateMachineHoistedLocalScopes; _stateMachineHoistedLocalSlots = stateMachineHoistedLocalSlots; @@ -196,5 +213,29 @@ public bool HasDynamicLocalVariables return _hasDynamicLocalVariables; } } + + public int MethodOrdinal + { + get + { + return _methodOrdinal; + } + } + + public ImmutableArray LambdaDebugInfo + { + get + { + return _lambdaDebugInfo; + } + } + + public ImmutableArray ClosureDebugInfo + { + get + { + return _closureDebugInfo; + } + } } } diff --git a/src/Compilers/Core/Portable/CodeGen/MethodDebugId.cs b/src/Compilers/Core/Portable/CodeGen/MethodDebugId.cs new file mode 100644 index 0000000000000..9d8b1a6f33544 --- /dev/null +++ b/src/Compilers/Core/Portable/CodeGen/MethodDebugId.cs @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Diagnostics; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CodeGen +{ + /// + /// Unique identification of an emitted method used for debugging purposes (EnC). + /// If the method is synthesized the id is included its name. + /// For user defined methods the id is included in Custom Debug Information record attached to the method. + /// + internal struct MethodDebugId : IEquatable + { + public const int UndefinedOrdinal = -1; + + /// + /// The index of the method in member list of the containing type, or if undefined. + /// + public readonly int Ordinal; + + /// + /// The EnC generation the method was defined in (0 is the baseline build). + /// + public readonly int Generation; + + public MethodDebugId(int ordinal, int generation) + { + Debug.Assert(ordinal >= 0 || ordinal == UndefinedOrdinal); + Debug.Assert(generation >= 0); + + this.Ordinal = ordinal; + this.Generation = generation; + } + + public bool Equals(MethodDebugId other) + { + return this.Ordinal == other.Ordinal + && this.Generation == other.Generation; + } + + public override bool Equals(object obj) + { + return obj is MethodDebugId && Equals((MethodDebugId)obj); + } + + public override int GetHashCode() + { + return Hash.Combine(this.Ordinal, this.Generation); + } + } +} diff --git a/src/Compilers/Core/Portable/CodeGen/VariableSlotAllocator.cs b/src/Compilers/Core/Portable/CodeGen/VariableSlotAllocator.cs index 8b9587155ffb1..2d40370fa6ab9 100644 --- a/src/Compilers/Core/Portable/CodeGen/VariableSlotAllocator.cs +++ b/src/Compilers/Core/Portable/CodeGen/VariableSlotAllocator.cs @@ -23,14 +23,14 @@ public abstract LocalDefinition GetPreviousLocal( public abstract string PreviousStateMachineTypeName { get; } /// - /// Returns an index of a slot that stores specified hoisted local variable in the previous generation, - /// or -1 if there is no such slot. + /// Returns an index of a slot that stores specified hoisted local variable in the previous generation. /// - public abstract int GetPreviousHoistedLocalSlotIndex( + public abstract bool TryGetPreviousHoistedLocalSlotIndex( SyntaxNode currentDeclarator, Cci.ITypeReference currentType, SynthesizedLocalKind synthesizedKind, - LocalDebugId currentId); + LocalDebugId currentId, + out int slotIndex); /// /// Number of slots reserved for hoisted local variables. @@ -42,10 +42,9 @@ public abstract int GetPreviousHoistedLocalSlotIndex( public abstract int PreviousHoistedLocalSlotCount { get; } /// - /// Returns an index of a slot that stores an awaiter of a specified type in the previous generation, - /// or -1 if there is no such slot. + /// Returns true and an index of a slot that stores an awaiter of a specified type in the previous generation, if any. /// - public abstract int GetPreviousAwaiterSlotIndex(Cci.ITypeReference currentType); + public abstract bool TryGetPreviousAwaiterSlotIndex(Cci.ITypeReference currentType, out int slotIndex); /// /// Number of slots reserved for awaiters. @@ -55,5 +54,25 @@ public abstract int GetPreviousHoistedLocalSlotIndex( /// Still, new awaiters are assigned slots starting with . /// public abstract int PreviousAwaiterSlotCount { get; } + + /// + /// The id of the method in the previous generation. + /// + public abstract MethodDebugId PreviousMethodId { get; } + + /// + /// Finds a closure in the previous generation that corresponds to the specified syntax. + /// + /// + /// See LambdaFrame.AssertIsLambdaScopeSyntax for kinds of syntax nodes that represent closures. + /// + public abstract bool TryGetPreviousClosure(SyntaxNode closureSyntax, out int closureOrdinal); + + /// + /// Finds a lambda in the previous generation that corresponds to the specified syntax. + /// The is either a lambda syntax ( is false), + /// or lambda body syntax ( is true). + /// + public abstract bool TryGetPreviousLambda(SyntaxNode lambdaOrLambdaBodySyntax, bool isLambdaBody, out int lambdaOrdinal); } } diff --git a/src/Compilers/Core/Portable/Emit/EditAndContinue/AddedOrChangedMethodInfo.cs b/src/Compilers/Core/Portable/Emit/EditAndContinue/AddedOrChangedMethodInfo.cs index c94e9a3049196..d39a4491f8d7f 100644 --- a/src/Compilers/Core/Portable/Emit/EditAndContinue/AddedOrChangedMethodInfo.cs +++ b/src/Compilers/Core/Portable/Emit/EditAndContinue/AddedOrChangedMethodInfo.cs @@ -2,30 +2,48 @@ using System.Collections.Immutable; using System.Diagnostics; +using Microsoft.CodeAnalysis.CodeGen; namespace Microsoft.CodeAnalysis.Emit { internal struct AddedOrChangedMethodInfo { + public readonly MethodDebugId MethodId; + + // locals: public readonly ImmutableArray Locals; + // lambdas, closures: + public readonly ImmutableArray LambdaDebugInfo; + public readonly ImmutableArray ClosureDebugInfo; + + // state machines: public readonly string StateMachineTypeNameOpt; public readonly ImmutableArray StateMachineHoistedLocalSlotsOpt; public readonly ImmutableArray StateMachineAwaiterSlotsOpt; public AddedOrChangedMethodInfo( - ImmutableArray locals, + MethodDebugId methodId, + ImmutableArray locals, + ImmutableArray lambdaDebugInfo, + ImmutableArray closureDebugInfo, string stateMachineTypeNameOpt, ImmutableArray stateMachineHoistedLocalSlotsOpt, ImmutableArray stateMachineAwaiterSlotsOpt) { + // method can only be added/changed during EnC, thus generation can't be 0. + Debug.Assert(methodId.Generation >= 1); + // each state machine has to have awaiters: Debug.Assert(stateMachineAwaiterSlotsOpt.IsDefault == (stateMachineTypeNameOpt == null)); // a state machine might not have hoisted variables: Debug.Assert(stateMachineHoistedLocalSlotsOpt.IsDefault || (stateMachineTypeNameOpt != null)); + this.MethodId = methodId; this.Locals = locals; + this.LambdaDebugInfo = lambdaDebugInfo; + this.ClosureDebugInfo = closureDebugInfo; this.StateMachineTypeNameOpt = stateMachineTypeNameOpt; this.StateMachineHoistedLocalSlotsOpt = stateMachineHoistedLocalSlotsOpt; this.StateMachineAwaiterSlotsOpt = stateMachineAwaiterSlotsOpt; @@ -37,7 +55,7 @@ public AddedOrChangedMethodInfo MapTypes(SymbolMatcher map) var mappedHoistedLocalSlots = StateMachineHoistedLocalSlotsOpt.IsDefault ? StateMachineHoistedLocalSlotsOpt : ImmutableArray.CreateRange(StateMachineHoistedLocalSlotsOpt, MapHoistedLocalSlot, map); var mappedAwaiterSlots = StateMachineAwaiterSlotsOpt.IsDefault ? StateMachineAwaiterSlotsOpt : ImmutableArray.CreateRange(StateMachineAwaiterSlotsOpt, map.MapReference); - return new AddedOrChangedMethodInfo(mappedLocals, StateMachineTypeNameOpt, mappedHoistedLocalSlots, mappedAwaiterSlots); + return new AddedOrChangedMethodInfo(this.MethodId, mappedLocals, LambdaDebugInfo, ClosureDebugInfo, StateMachineTypeNameOpt, mappedHoistedLocalSlots, mappedAwaiterSlots); } private static EncLocalInfo MapLocalInfo(EncLocalInfo info, SymbolMatcher map) diff --git a/src/Compilers/Core/Portable/Emit/EditAndContinue/DefinitionMap.cs b/src/Compilers/Core/Portable/Emit/EditAndContinue/DefinitionMap.cs index 6170557fec3f0..f8473f2c3d890 100644 --- a/src/Compilers/Core/Portable/Emit/EditAndContinue/DefinitionMap.cs +++ b/src/Compilers/Core/Portable/Emit/EditAndContinue/DefinitionMap.cs @@ -80,7 +80,7 @@ internal abstract class DefinitionMap : DefinitionMap { protected readonly TSymbolMatcher mapToMetadata; protected readonly TSymbolMatcher mapToPrevious; - + protected DefinitionMap(PEModule module, IEnumerable edits, TSymbolMatcher mapToMetadata, TSymbolMatcher mapToPrevious) : base(module, edits) { @@ -136,6 +136,9 @@ protected abstract void GetStateMachineFieldMapFromMetadata( out IReadOnlyDictionary awaiterMap, out int awaiterSlotCount); + protected abstract ImmutableArray TryGetLocalSlotMapFromMetadata(MethodDefinitionHandle handle, EditAndContinueMethodDebugInformation debugInfo); + protected abstract ITypeSymbol TryGetStateMachineType(Handle methodHandle); + internal VariableSlotAllocator TryCreateVariableSlotAllocator(EmitBaseline baseline, IMethodSymbol method) { MethodDefinitionHandle handle; @@ -159,23 +162,37 @@ internal VariableSlotAllocator TryCreateVariableSlotAllocator(EmitBaseline basel // since a method may be incorrectly marked by Iterator/AsyncStateMachine attribute by the user, // in which case we can't reliably figure out that it's an error in semantic edit set. + // We should also "preserve locals" of any updated method containing lambdas. The goal is to + // treat lambdas the same as method declarations. Lambdas declared in a method body that escape + // the method (are assigned to a field, added to an event, e.g.) might be invoked after the method + // is updated and when it no longer contains active statements. If we didn't map the lambdas of + // the updated body to the original lambdas we would run the out-of-date lambda bodies, + // which would not happen if the lambdas were named methods. return null; } ImmutableArray previousLocals; IReadOnlyDictionary hoistedLocalMap = null; IReadOnlyDictionary awaiterMap = null; + IReadOnlyDictionary> lambdaMap = null; + IReadOnlyDictionary closureMap = null; + int hoistedLocalSlotCount = 0; int awaiterSlotCount = 0; string stateMachineTypeNameOpt = null; TSymbolMatcher symbolMap; uint methodIndex = (uint)MetadataTokens.GetRowNumber(handle); - + MethodDebugId methodId; + // Check if method has changed previously. If so, we already have a map. AddedOrChangedMethodInfo addedOrChangedMethod; if (baseline.AddedOrChangedMethods.TryGetValue(methodIndex, out addedOrChangedMethod)) { + methodId = addedOrChangedMethod.MethodId; + + MakeLambdaAndClosureMaps(addedOrChangedMethod.LambdaDebugInfo, addedOrChangedMethod.ClosureDebugInfo, out lambdaMap, out closureMap); + if (addedOrChangedMethod.StateMachineTypeNameOpt != null) { // method is async/iterator kickoff method @@ -208,6 +225,14 @@ internal VariableSlotAllocator TryCreateVariableSlotAllocator(EmitBaseline basel // Method has not changed since initial generation. Generate a map // using the local names provided with the initial metadata. var debugInfo = baseline.DebugInformationProvider(handle); + + methodId = new MethodDebugId(debugInfo.MethodOrdinal, 0); + + if (!debugInfo.Lambdas.IsDefaultOrEmpty) + { + MakeLambdaAndClosureMaps(debugInfo.Lambdas, debugInfo.Closures, out lambdaMap, out closureMap); + } + ITypeSymbol stateMachineType = TryGetStateMachineType(handle); if (stateMachineType != null) { @@ -239,7 +264,10 @@ internal VariableSlotAllocator TryCreateVariableSlotAllocator(EmitBaseline basel symbolMap, methodUpdate.SyntaxMap, methodUpdate.PreviousMethod, + methodId, previousLocals, + lambdaMap, + closureMap, stateMachineTypeNameOpt, hoistedLocalSlotCount, hoistedLocalMap, @@ -247,8 +275,30 @@ internal VariableSlotAllocator TryCreateVariableSlotAllocator(EmitBaseline basel awaiterMap); } - protected abstract ImmutableArray TryGetLocalSlotMapFromMetadata(MethodDefinitionHandle handle, EditAndContinueMethodDebugInformation debugInfo); - protected abstract ITypeSymbol TryGetStateMachineType(Handle methodHandle); + private static void MakeLambdaAndClosureMaps( + ImmutableArray lambdaDebugInfo, + ImmutableArray closureDebugInfo, + out IReadOnlyDictionary> lambdaMap, + out IReadOnlyDictionary closureMap) + { + var lambdas = new Dictionary>(lambdaDebugInfo.Length); + var closures = new Dictionary(closureDebugInfo.Length); + + for (int i = 0; i < lambdaDebugInfo.Length; i++) + { + var lambdaInfo = lambdaDebugInfo[i]; + lambdas[lambdaInfo.SyntaxOffset] = KeyValuePair.Create(i, lambdaInfo.ClosureOrdinal); + } + + for (int i = 0; i < closureDebugInfo.Length; i++) + { + var closureInfo = closureDebugInfo[i]; + closures[closureInfo.SyntaxOffset] = i; + } + + lambdaMap = lambdas; + closureMap = closures; + } private static void GetStateMachineFieldMapFromPreviousCompilation( ImmutableArray hoistedLocalSlots, diff --git a/src/Compilers/Core/Portable/Emit/EditAndContinue/DeltaMetadataWriter.cs b/src/Compilers/Core/Portable/Emit/EditAndContinue/DeltaMetadataWriter.cs index 8be066df4626e..b5df1711287ba 100644 --- a/src/Compilers/Core/Portable/Emit/EditAndContinue/DeltaMetadataWriter.cs +++ b/src/Compilers/Core/Portable/Emit/EditAndContinue/DeltaMetadataWriter.cs @@ -10,6 +10,7 @@ using System.Threading; using Microsoft.Cci; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeGen; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Emit @@ -220,7 +221,7 @@ protected override ushort Generation get { return (ushort)(_previousGeneration.Ordinal + 1); } } - protected override System.Guid EncId + protected override Guid EncId { get { return _encId; } } @@ -597,8 +598,8 @@ private void ReportReferencesToAddedSymbol(ISymbol symbolOpt) if (symbolOpt != null && _changes.IsAdded(symbolOpt)) { this.Context.Diagnostics.Add(this.messageProvider.CreateDiagnostic( - this.messageProvider.ERR_EncReferenceToAddedMember, - GetSymbolLocation(symbolOpt), + this.messageProvider.ERR_EncReferenceToAddedMember, + GetSymbolLocation(symbolOpt), symbolOpt.Name, symbolOpt.ContainingAssembly.Name)); } @@ -650,7 +651,10 @@ protected override uint SerializeLocalVariablesSignature(IMethodBody body) if (!method.IsImplicitlyDeclared) { var info = new AddedOrChangedMethodInfo( - encInfos.ToImmutable(), + new MethodDebugId(body.MethodOrdinal, this.Generation), + encInfos.ToImmutable(), + body.LambdaDebugInfo, + body.ClosureDebugInfo, body.StateMachineTypeName, body.StateMachineHoistedLocalSlots, body.StateMachineAwaiterSlots); @@ -671,7 +675,7 @@ private static EncLocalInfo CreateEncLocalInfo(ILocalDefinition localDef, byte[] return new EncLocalInfo(localDef.SlotInfo, localDef.Type, localDef.Constraints, signature); } - + protected override void PopulateEncLogTableRows(List table, ImmutableArray rowCounts) { // The EncLog table is a log of all the operations needed diff --git a/src/Compilers/Core/Portable/Emit/EditAndContinue/EncVariableSlotAllocator.cs b/src/Compilers/Core/Portable/Emit/EditAndContinue/EncVariableSlotAllocator.cs index d0cecd200f77d..2a04d711ab6c5 100644 --- a/src/Compilers/Core/Portable/Emit/EditAndContinue/EncVariableSlotAllocator.cs +++ b/src/Compilers/Core/Portable/Emit/EditAndContinue/EncVariableSlotAllocator.cs @@ -19,6 +19,7 @@ internal sealed class EncVariableSlotAllocator : VariableSlotAllocator // syntax: private readonly Func _syntaxMapOpt; private readonly IMethodSymbolInternal _previousMethod; + private readonly MethodDebugId _previousMethodId; // locals: private readonly IReadOnlyDictionary _previousLocalSlots; @@ -31,11 +32,18 @@ internal sealed class EncVariableSlotAllocator : VariableSlotAllocator private readonly int _awaiterCount; private readonly IReadOnlyDictionary _awaiterMapOpt; + // closures: + private readonly IReadOnlyDictionary> _lambdaMapOpt; // SyntaxOffset -> (Lambda Ordinal, Closure Ordinal) + private readonly IReadOnlyDictionary _closureMapOpt; // SyntaxOffset -> Ordinal + public EncVariableSlotAllocator( SymbolMatcher symbolMap, Func syntaxMapOpt, IMethodSymbolInternal previousMethod, + MethodDebugId previousMethodId, ImmutableArray previousLocals, + IReadOnlyDictionary> lambdaMapOpt, + IReadOnlyDictionary closureMapOpt, string stateMachineTypeNameOpt, int hoistedLocalSlotCount, IReadOnlyDictionary hoistedLocalSlotsOpt, @@ -50,11 +58,14 @@ public EncVariableSlotAllocator( _syntaxMapOpt = syntaxMapOpt; _previousLocals = previousLocals; _previousMethod = previousMethod; + _previousMethodId = previousMethodId; _hoistedLocalSlotsOpt = hoistedLocalSlotsOpt; _hoistedLocalSlotCount = hoistedLocalSlotCount; _stateMachineTypeNameOpt = stateMachineTypeNameOpt; _awaiterCount = awaiterCount; _awaiterMapOpt = awaiterMapOpt; + _lambdaMapOpt = lambdaMapOpt; + _closureMapOpt = closureMapOpt; // Create a map from local info to slot. var previousLocalInfoToSlot = new Dictionary(); @@ -74,6 +85,8 @@ public EncVariableSlotAllocator( _previousLocalSlots = previousLocalInfoToSlot; } + public override MethodDebugId PreviousMethodId => _previousMethodId; + public override void AddPreviousLocals(ArrayBuilder builder) { builder.AddRange(_previousLocals.Select((info, index) => new SignatureOnlyLocalDefinition(info.Signature, index))); @@ -154,56 +167,119 @@ public override LocalDefinition GetPreviousLocal( dynamicTransformFlags); } - public override string PreviousStateMachineTypeName - { - get { return _stateMachineTypeNameOpt; } - } + public override string PreviousStateMachineTypeName => _stateMachineTypeNameOpt; - public override int GetPreviousHoistedLocalSlotIndex(SyntaxNode currentDeclarator, Cci.ITypeReference currentType, SynthesizedLocalKind synthesizedKind, LocalDebugId currentId) + public override bool TryGetPreviousHoistedLocalSlotIndex(SyntaxNode currentDeclarator, Cci.ITypeReference currentType, SynthesizedLocalKind synthesizedKind, LocalDebugId currentId, out int slotIndex) { Debug.Assert(_hoistedLocalSlotsOpt != null); LocalDebugId previousId; if (!TryGetPreviousLocalId(currentDeclarator, currentId, out previousId)) { - return -1; + slotIndex = -1; + return false; } var previousType = _symbolMap.MapReference(currentType); if (previousType == null) { - return -1; + slotIndex = -1; + return false; } // TODO (bug #781309): Should report a warning if the type of the local has changed // and the previous value will be dropped. var localKey = new EncHoistedLocalInfo(new LocalSlotDebugInfo(synthesizedKind, previousId), previousType); - int slotIndex; - if (!_hoistedLocalSlotsOpt.TryGetValue(localKey, out slotIndex)) + return _hoistedLocalSlotsOpt.TryGetValue(localKey, out slotIndex); + } + + public override int PreviousHoistedLocalSlotCount => _hoistedLocalSlotCount; + public override int PreviousAwaiterSlotCount => _awaiterCount; + + public override bool TryGetPreviousAwaiterSlotIndex(Cci.ITypeReference currentType, out int slotIndex) + { + Debug.Assert(_awaiterMapOpt != null); + return _awaiterMapOpt.TryGetValue(_symbolMap.MapReference(currentType), out slotIndex); + } + + private bool TryGetPreviousSyntaxOffset(SyntaxNode currentSyntax, out int previousSyntaxOffset) + { + // no syntax map + // => the source of the current method is the same as the source of the previous method + // => relative positions are the same + // => ids are the same + SyntaxNode previousSyntax = _syntaxMapOpt?.Invoke(currentSyntax); + if (previousSyntax == null) { - return -1; + previousSyntaxOffset = 0; + return false; } - return slotIndex; + previousSyntaxOffset = _previousMethod.CalculateLocalSyntaxOffset(previousSyntax.SpanStart, previousSyntax.SyntaxTree); + return true; } - public override int PreviousHoistedLocalSlotCount + private bool TryGetPreviousLambdaSyntaxOffset(SyntaxNode lambdaOrLambdaBodySyntax, bool isLambdaBody, out int previousSyntaxOffset) { - get { return _hoistedLocalSlotCount; } + // Syntax map contains mapping for lambdas, but not their bodies. + // Map the lambda first and then determine the corresponding body. + var currentLambdaSyntax = isLambdaBody ? lambdaOrLambdaBodySyntax.Parent : lambdaOrLambdaBodySyntax; + + // no syntax map + // => the source of the current method is the same as the source of the previous method + // => relative positions are the same + // => ids are the same + SyntaxNode previousLambdaSyntax = _syntaxMapOpt?.Invoke(currentLambdaSyntax); + if (previousLambdaSyntax == null) + { + previousSyntaxOffset = 0; + return false; + } + + SyntaxNode previousSyntax; + if (isLambdaBody) + { + previousSyntax = previousLambdaSyntax.GetCorrespondingLambdaBody(lambdaOrLambdaBodySyntax); + } + else + { + previousSyntax = previousLambdaSyntax; + } + + previousSyntaxOffset = _previousMethod.CalculateLocalSyntaxOffset(previousSyntax.SpanStart, previousSyntax.SyntaxTree); + return true; } - public override int GetPreviousAwaiterSlotIndex(Cci.ITypeReference currentType) + public override bool TryGetPreviousClosure(SyntaxNode scopeSyntax, out int closureOrdinal) { - Debug.Assert(_awaiterMapOpt != null); + int syntaxOffset; + if (_closureMapOpt != null && + TryGetPreviousSyntaxOffset(scopeSyntax, out syntaxOffset) && + _closureMapOpt.TryGetValue(syntaxOffset, out closureOrdinal)) + { + return true; + } - int slotIndex; - return _awaiterMapOpt.TryGetValue(_symbolMap.MapReference(currentType), out slotIndex) ? slotIndex : -1; + closureOrdinal = -1; + return false; } - public override int PreviousAwaiterSlotCount + public override bool TryGetPreviousLambda(SyntaxNode lambdaOrLambdaBodySyntax, bool isLambdaBody, out int lambdaOrdinal) { - get { return _awaiterCount; } + KeyValuePair id; + + int syntaxOffset; + if (_lambdaMapOpt != null && + TryGetPreviousLambdaSyntaxOffset(lambdaOrLambdaBodySyntax, isLambdaBody, out syntaxOffset) && + _lambdaMapOpt.TryGetValue(syntaxOffset, out id)) + { + lambdaOrdinal = id.Key; + return true; + } + + lambdaOrdinal = -1; + return false; } } } diff --git a/src/Compilers/Core/Portable/Emit/EditAndContinue/SymbolChanges.cs b/src/Compilers/Core/Portable/Emit/EditAndContinue/SymbolChanges.cs index 7e926f27b3bbc..0b81b3da2f0f5 100644 --- a/src/Compilers/Core/Portable/Emit/EditAndContinue/SymbolChanges.cs +++ b/src/Compilers/Core/Portable/Emit/EditAndContinue/SymbolChanges.cs @@ -64,12 +64,6 @@ public SymbolChange GetChange(IDefinition def) return SymbolChange.Added; } - // The symbol should be reused when the generator is updated. - if (!synthesizedDef.HasMethodBodyDependency) - { - return SymbolChange.None; - } - if (!_definitionMap.DefinitionExists(def)) { // A method was changed to a method containing a lambda, to an interator, or to an async method. @@ -77,6 +71,15 @@ public SymbolChange GetChange(IDefinition def) return SymbolChange.Added; } + // The existing symbol should be reused when the generator is updated, + // not updated since it's form doesn't depend on the content of the generator. + // For example, when an iterator method changes all methods that implement IEnumerable + // but MoveNext can be reused as they are. + if (!synthesizedDef.HasMethodBodyDependency) + { + return SymbolChange.None; + } + // If the type produced from the method body existed before then its members are updated. if (synthesizedSymbol.Kind == SymbolKind.NamedType) { @@ -88,7 +91,7 @@ public SymbolChange GetChange(IDefinition def) // The method body might have been updated. return SymbolChange.Updated; } - + return SymbolChange.None; case SymbolChange.Added: diff --git a/src/Compilers/Core/Portable/Emit/EditAndContinueMethodDebugInformation.cs b/src/Compilers/Core/Portable/Emit/EditAndContinueMethodDebugInformation.cs index c22f2cb00540c..5464380fd3e58 100644 --- a/src/Compilers/Core/Portable/Emit/EditAndContinueMethodDebugInformation.cs +++ b/src/Compilers/Core/Portable/Emit/EditAndContinueMethodDebugInformation.cs @@ -1,13 +1,11 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Reflection.Metadata; using Microsoft.CodeAnalysis.CodeGen; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Emit { @@ -16,20 +14,67 @@ namespace Microsoft.CodeAnalysis.Emit /// public struct EditAndContinueMethodDebugInformation { + internal readonly int MethodOrdinal; internal readonly ImmutableArray LocalSlots; + internal readonly ImmutableArray Lambdas; + internal readonly ImmutableArray Closures; - internal EditAndContinueMethodDebugInformation(ImmutableArray localSlots) + internal EditAndContinueMethodDebugInformation(int methodOrdinal, ImmutableArray localSlots, ImmutableArray closures, ImmutableArray lambdas) { + Debug.Assert(methodOrdinal >= -1); + + this.MethodOrdinal = methodOrdinal; this.LocalSlots = localSlots; + this.Lambdas = lambdas; + this.Closures = closures; } - public static EditAndContinueMethodDebugInformation Create(ImmutableArray compressedSlotMap) + public static EditAndContinueMethodDebugInformation Create(ImmutableArray compressedSlotMap, ImmutableArray compressedLambdaMap) + { + int methodOrdinal; + ImmutableArray closures; + ImmutableArray lambdas; + UncompressLambdaMap(compressedLambdaMap, out methodOrdinal, out closures, out lambdas); + return new EditAndContinueMethodDebugInformation(methodOrdinal, UncompressSlotMap(compressedSlotMap), closures, lambdas); + } + + internal void SerializeCustomDebugInformation(ArrayBuilder customDebugInfo) { - return new EditAndContinueMethodDebugInformation(UncompressSlotMap(compressedSlotMap)); + if (!this.LocalSlots.IsDefaultOrEmpty) + { + customDebugInfo.Add(SerializeRecord(Cci.CustomDebugInfoConstants.CdiKindEditAndContinueLocalSlotMap, SerializeLocalSlots)); + } + + if (!this.Lambdas.IsDefaultOrEmpty) + { + customDebugInfo.Add(SerializeRecord(Cci.CustomDebugInfoConstants.CdiKindEditAndContinueLambdaMap, SerializeLambdaMap)); + } + } + + private Cci.MemoryStream SerializeRecord(byte kind, Action data) + { + Cci.MemoryStream customMetadata = new Cci.MemoryStream(); + Cci.BinaryWriter cmw = new Cci.BinaryWriter(customMetadata); + cmw.WriteByte(Cci.CustomDebugInfoConstants.CdiVersion); + cmw.WriteByte(kind); + cmw.Align(4); + + // length (will be patched) + uint lengthPosition = cmw.BaseStream.Position; + cmw.WriteUint(0); + + data(cmw); + + uint length = customMetadata.Position; + cmw.BaseStream.Position = lengthPosition; + cmw.WriteUint(length); + cmw.BaseStream.Position = length; + return customMetadata; } - private const byte AlignmentValue = 0xff; - private const byte SyntaxOffsetBaseline = 0xfe; + #region Local Slots + + private const byte SyntaxOffsetBaseline = 0xff; private unsafe static ImmutableArray UncompressSlotMap(ImmutableArray compressedSlotMap) { @@ -48,11 +93,6 @@ private unsafe static ImmutableArray UncompressSlotMap(Immut { byte b = blobReader.ReadByte(); - if (b == AlignmentValue) - { - break; - } - if (b == SyntaxOffsetBaseline) { syntaxOffsetBaseline = -blobReader.ReadCompressedInteger(); @@ -72,6 +112,7 @@ private unsafe static ImmutableArray UncompressSlotMap(Immut int syntaxOffset; if (!blobReader.TryReadCompressedInteger(out syntaxOffset)) { + // invalid data return default(ImmutableArray); } @@ -80,6 +121,7 @@ private unsafe static ImmutableArray UncompressSlotMap(Immut int ordinal = 0; if (hasOrdinal && !blobReader.TryReadCompressedInteger(out ordinal)) { + // invalid data return default(ImmutableArray); } @@ -90,42 +132,8 @@ private unsafe static ImmutableArray UncompressSlotMap(Immut return mapBuilder.ToImmutableAndFree(); } - internal void SerializeCustomDebugInformation(ArrayBuilder customDebugInfo) - { - if (this.LocalSlots.IsDefaultOrEmpty) - { - return; - } - - Cci.MemoryStream customMetadata = new Cci.MemoryStream(); - Cci.BinaryWriter cmw = new Cci.BinaryWriter(customMetadata); - cmw.WriteByte(4); // version - cmw.WriteByte(6); // kind: EditAndContinueLocalSlotMap - cmw.Align(4); - - // length (will be patched) - uint lengthPosition = cmw.BaseStream.Position; - cmw.WriteUint(0); - - SerializeLocalSlots(cmw); - - uint length = customMetadata.Position; - - // align with values that the reader skips - while (length % 4 != 0) - { - cmw.WriteByte(AlignmentValue); - length++; - } - - cmw.BaseStream.Position = lengthPosition; - cmw.WriteUint(length); - cmw.BaseStream.Position = length; - - customDebugInfo.Add(customMetadata); - } - - internal void SerializeLocalSlots(Cci.BinaryWriter cmw) + // internal for testing + internal void SerializeLocalSlots(Cci.BinaryWriter writer) { int syntaxOffsetBaseline = -1; foreach (LocalSlotDebugInfo localSlot in this.LocalSlots) @@ -138,8 +146,8 @@ internal void SerializeLocalSlots(Cci.BinaryWriter cmw) if (syntaxOffsetBaseline != -1) { - cmw.WriteByte(SyntaxOffsetBaseline); - cmw.WriteCompressedUInt((uint)(-syntaxOffsetBaseline)); + writer.WriteByte(SyntaxOffsetBaseline); + writer.WriteCompressedUInt((uint)(-syntaxOffsetBaseline)); } foreach (LocalSlotDebugInfo localSlot in this.LocalSlots) @@ -147,30 +155,167 @@ internal void SerializeLocalSlots(Cci.BinaryWriter cmw) SynthesizedLocalKind kind = localSlot.SynthesizedKind; Debug.Assert(kind <= SynthesizedLocalKind.MaxValidValueForLocalVariableSerializedToDebugInformation); - bool hasOrdinal = localSlot.Id.Ordinal > 0; - if (!kind.IsLongLived()) { - cmw.WriteByte(0); + writer.WriteByte(0); continue; } byte b = (byte)(kind + 1); Debug.Assert((b & (1 << 7)) == 0); + bool hasOrdinal = localSlot.Id.Ordinal > 0; + if (hasOrdinal) { b |= 1 << 7; } - cmw.WriteByte(b); - cmw.WriteCompressedUInt((uint)(localSlot.Id.SyntaxOffset - syntaxOffsetBaseline)); + writer.WriteByte(b); + writer.WriteCompressedUInt((uint)(localSlot.Id.SyntaxOffset - syntaxOffsetBaseline)); if (hasOrdinal) { - cmw.WriteCompressedUInt((uint)localSlot.Id.Ordinal); + writer.WriteCompressedUInt((uint)localSlot.Id.Ordinal); } } } + + #endregion + + #region Lambdas + + private unsafe static void UncompressLambdaMap( + ImmutableArray compressedLambdaMap, + out int methodOrdinal, + out ImmutableArray closures, + out ImmutableArray lambdas) + { + methodOrdinal = MethodDebugId.UndefinedOrdinal; + closures = default(ImmutableArray); + lambdas = default(ImmutableArray); + + if (compressedLambdaMap.IsDefaultOrEmpty) + { + return; + } + + var closuresBuilder = ArrayBuilder.GetInstance(); + var lambdasBuilder = ArrayBuilder.GetInstance(); + + int syntaxOffsetBaseline = -1; + int closureCount; + + fixed (byte* blobPtr = &compressedLambdaMap.ToArray()[0]) + { + var blobReader = new BlobReader(blobPtr, compressedLambdaMap.Length); + + if (!blobReader.TryReadCompressedInteger(out methodOrdinal)) + { + // invalid data + return; + } + + // [-1, inf) + methodOrdinal--; + + if (!blobReader.TryReadCompressedInteger(out syntaxOffsetBaseline)) + { + // invalid data + return; + } + + syntaxOffsetBaseline = -syntaxOffsetBaseline; + + if (!blobReader.TryReadCompressedInteger(out closureCount)) + { + // invalid data + return; + } + + for (int i = 0; i < closureCount; i++) + { + int syntaxOffset; + if (!blobReader.TryReadCompressedInteger(out syntaxOffset)) + { + // invalid data + return; + } + + closuresBuilder.Add(new ClosureDebugInfo(syntaxOffset + syntaxOffsetBaseline)); + } + + while (blobReader.RemainingBytes > 0) + { + int syntaxOffset; + if (!blobReader.TryReadCompressedInteger(out syntaxOffset)) + { + // invalid data + return; + } + + int closureOrdinal; + if (!blobReader.TryReadCompressedInteger(out closureOrdinal)) + { + // invalid data + return; + } + + closureOrdinal--; + if (closureOrdinal < -1 || closureOrdinal >= closureCount) + { + // invalid data + return; + } + + lambdasBuilder.Add(new LambdaDebugInfo(syntaxOffset + syntaxOffsetBaseline, closureOrdinal)); + } + } + + closures = closuresBuilder.ToImmutableAndFree(); + lambdas = lambdasBuilder.ToImmutableAndFree(); + } + + // internal for testing + internal void SerializeLambdaMap(Cci.BinaryWriter writer) + { + Debug.Assert(this.MethodOrdinal >= -1); + writer.WriteCompressedUInt((uint)(this.MethodOrdinal + 1)); + + int syntaxOffsetBaseline = -1; + foreach (ClosureDebugInfo info in this.Closures) + { + if (info.SyntaxOffset < syntaxOffsetBaseline) + { + syntaxOffsetBaseline = info.SyntaxOffset; + } + } + + foreach (LambdaDebugInfo info in this.Lambdas) + { + if (info.SyntaxOffset < syntaxOffsetBaseline) + { + syntaxOffsetBaseline = info.SyntaxOffset; + } + } + + writer.WriteCompressedUInt((uint)(-syntaxOffsetBaseline)); + writer.WriteCompressedUInt((uint)this.Closures.Length); + + foreach (ClosureDebugInfo info in this.Closures) + { + writer.WriteCompressedUInt((uint)(info.SyntaxOffset - syntaxOffsetBaseline)); + } + + foreach (LambdaDebugInfo info in this.Lambdas) + { + Debug.Assert(info.ClosureOrdinal >= -1); + + writer.WriteCompressedUInt((uint)(info.SyntaxOffset - syntaxOffsetBaseline)); + writer.WriteCompressedUInt((uint)(info.ClosureOrdinal + 1)); + } + } + + #endregion } } diff --git a/src/Compilers/Core/Portable/Emit/NoPia/CommonEmbeddedMethod.cs b/src/Compilers/Core/Portable/Emit/NoPia/CommonEmbeddedMethod.cs index a4ed3d0b43668..f2a94f5124f4e 100644 --- a/src/Compilers/Core/Portable/Emit/NoPia/CommonEmbeddedMethod.cs +++ b/src/Compilers/Core/Portable/Emit/NoPia/CommonEmbeddedMethod.cs @@ -210,6 +210,21 @@ ImmutableArray Cci.IMethodBody.StateMachineHoistedLocalSlot { get { return default(ImmutableArray); } } + + ImmutableArray Cci.IMethodBody.ClosureDebugInfo + { + get { return default(ImmutableArray); } + } + + ImmutableArray Cci.IMethodBody.LambdaDebugInfo + { + get { return default(ImmutableArray); } + } + + public int MethodOrdinal + { + get { return -1; } + } } IEnumerable Cci.IMethodDefinition.GenericParameters diff --git a/src/Compilers/Core/Portable/InternalUtilities/ImmutableArrayExtensions.cs b/src/Compilers/Core/Portable/InternalUtilities/ImmutableArrayExtensions.cs index 7a9a7f8a75233..0500cac26ad49 100644 --- a/src/Compilers/Core/Portable/InternalUtilities/ImmutableArrayExtensions.cs +++ b/src/Compilers/Core/Portable/InternalUtilities/ImmutableArrayExtensions.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -36,5 +37,34 @@ internal static ImmutableArray ToImmutableArrayOrEmpty(this ImmutableArray return items; } + + // same as Array.BinarySearch but the ability to pass arbitrary value to the comparer without allocation + internal static int BinarySearch(this ImmutableArray array, TValue value, Func comparer) + { + int low = 0; + int high = array.Length - 1; + + while (low <= high) + { + int middle = low + ((high - low) >> 1); + int comparison = comparer(array[middle], value); + + if (comparison == 0) + { + return middle; + } + + if (comparison > 0) + { + high = middle - 1; + } + else + { + low = middle + 1; + } + } + + return ~low; + } } } diff --git a/src/Compilers/Core/Portable/PEWriter/CustomDebugInfoConstants.cs b/src/Compilers/Core/Portable/PEWriter/CustomDebugInfoConstants.cs index 479e0b9e66b57..ee15987efa30b 100644 --- a/src/Compilers/Core/Portable/PEWriter/CustomDebugInfoConstants.cs +++ b/src/Compilers/Core/Portable/PEWriter/CustomDebugInfoConstants.cs @@ -27,5 +27,6 @@ internal static class CustomDebugInfoConstants internal const byte CdiKindForwardIterator = 4; internal const byte CdiKindDynamicLocals = 5; internal const byte CdiKindEditAndContinueLocalSlotMap = 6; + internal const byte CdiKindEditAndContinueLambdaMap = 7; } } diff --git a/src/Compilers/Core/Portable/PEWriter/CustomDebugInfoWriter.cs b/src/Compilers/Core/Portable/PEWriter/CustomDebugInfoWriter.cs index c2a8aa165372c..e18cceb02a2c9 100644 --- a/src/Compilers/Core/Portable/PEWriter/CustomDebugInfoWriter.cs +++ b/src/Compilers/Core/Portable/PEWriter/CustomDebugInfoWriter.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using System.Linq; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeGen; using Microsoft.CodeAnalysis.Emit; using Roslyn.Utilities; using CDI = Microsoft.Cci.CustomDebugInfoConstants; @@ -99,15 +100,22 @@ public byte[] SerializeMethodDebugInfo(IModule module, IMethodBody methodBody, u // delta doesn't need this information - we use information recorded by previous generation emit if (!isEncDelta) { - var encSlotInfo = methodBody.StateMachineHoistedLocalSlots; + ImmutableArray encLocalSlots; // Kickoff method of a state machine (async/iterator method) doens't have any interesting locals, // so we use its EnC method debug info to store information about locals hoisted to the state machine. - var encDebugInfo = encSlotInfo.IsDefault ? - GetEncDebugInfoForLocals(methodBody.LocalVariables) : - GetEncDebugInfoForLocals(encSlotInfo); + var encSlotInfo = methodBody.StateMachineHoistedLocalSlots; + if (encSlotInfo.IsDefault) + { + encLocalSlots = GetLocalSlotDebugInfos(methodBody.LocalVariables); + } + else + { + encLocalSlots = GetLocalSlotDebugInfos(encSlotInfo); + } - encDebugInfo.SerializeCustomDebugInformation(customDebugInfo); + var encMethodInfo = new EditAndContinueMethodDebugInformation(methodBody.MethodOrdinal, encLocalSlots, methodBody.ClosureDebugInfo, methodBody.LambdaDebugInfo); + encMethodInfo.SerializeCustomDebugInformation(customDebugInfo); } } @@ -116,24 +124,24 @@ public byte[] SerializeMethodDebugInfo(IModule module, IMethodBody methodBody, u return result; } - public static EditAndContinueMethodDebugInformation GetEncDebugInfoForLocals(ImmutableArray locals) + public static ImmutableArray GetLocalSlotDebugInfos(ImmutableArray locals) { if (!locals.Any(variable => !variable.SlotInfo.Id.IsNone)) { - return default(EditAndContinueMethodDebugInformation); + return ImmutableArray.Empty; } - return new EditAndContinueMethodDebugInformation(locals.SelectAsArray(variable => variable.SlotInfo)); + return locals.SelectAsArray(variable => variable.SlotInfo); } - public static EditAndContinueMethodDebugInformation GetEncDebugInfoForLocals(ImmutableArray locals) + public static ImmutableArray GetLocalSlotDebugInfos(ImmutableArray locals) { if (!locals.Any(variable => !variable.SlotInfo.Id.IsNone)) { - return default(EditAndContinueMethodDebugInformation); + return ImmutableArray.Empty; } - return new EditAndContinueMethodDebugInformation(locals.SelectAsArray(variable => variable.SlotInfo)); + return locals.SelectAsArray(variable => variable.SlotInfo); } private static void SerializeIteratorClassMetadata(IMethodBody methodBody, ArrayBuilder customDebugInfo) diff --git a/src/Compilers/Core/Portable/PEWriter/Members.cs b/src/Compilers/Core/Portable/PEWriter/Members.cs index d8e19128f81a6..430afc5e0baa7 100644 --- a/src/Compilers/Core/Portable/PEWriter/Members.cs +++ b/src/Compilers/Core/Portable/PEWriter/Members.cs @@ -442,6 +442,8 @@ IMethodDefinition MethodDefinition /// ImmutableArray NamespaceScopes { get; } + int MethodOrdinal { get; } + /// /// Returns debug information for local variables hoisted to state machine fields, /// or null if this method isn't MoveNext method of a state machine. @@ -475,6 +477,9 @@ IMethodDefinition MethodDefinition /// or null if the method isn't the kickoff method of a state machine. /// ImmutableArray StateMachineAwaiterSlots { get; } + + ImmutableArray ClosureDebugInfo { get; } + ImmutableArray LambdaDebugInfo { get; } } /// diff --git a/src/Compilers/Core/Portable/PEWriter/PdbWriter.cs b/src/Compilers/Core/Portable/PEWriter/PdbWriter.cs index 9a507d294e8f5..2814c22146188 100644 --- a/src/Compilers/Core/Portable/PEWriter/PdbWriter.cs +++ b/src/Compilers/Core/Portable/PEWriter/PdbWriter.cs @@ -153,8 +153,10 @@ public void SerializeDebugInfo(IMethodBody methodBody, uint localSignatureToken, bool suppressNewCustomDebugInfo = !compilationOptions.ExtendedCustomDebugInformation || (compilationOptions.OutputKind == OutputKind.WindowsRuntimeMetadata); + bool emitEncInfo = compilationOptions.EnableEditAndContinue && !_metadataWriter.IsFullMetadata; + bool emitExternNamespaces; - byte[] blob = customDebugInfoWriter.SerializeMethodDebugInfo(module, methodBody, methodToken, !_metadataWriter.IsFullMetadata, suppressNewCustomDebugInfo, out emitExternNamespaces); + byte[] blob = customDebugInfoWriter.SerializeMethodDebugInfo(module, methodBody, methodToken, emitEncInfo, suppressNewCustomDebugInfo, out emitExternNamespaces); if (blob != null) { DefineCustomMetadata("MD2", blob); diff --git a/src/Compilers/Core/Portable/PublicAPI.txt b/src/Compilers/Core/Portable/PublicAPI.txt index 979e3d6a06a58..ab8fda74f8b06 100644 --- a/src/Compilers/Core/Portable/PublicAPI.txt +++ b/src/Compilers/Core/Portable/PublicAPI.txt @@ -1894,7 +1894,7 @@ static Microsoft.CodeAnalysis.Diagnostic.Create(string id, string category, Micr static Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzers.GetEffectiveDiagnostics(System.Collections.Generic.IEnumerable diagnostics, Microsoft.CodeAnalysis.Compilation compilation) static Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzers.IsDiagnosticAnalyzerSuppressed(Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzer analyzer, Microsoft.CodeAnalysis.CompilationOptions options, System.Func continueOnAnalyzerException) static Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzerExtensions.WithAnalyzers(this Microsoft.CodeAnalysis.Compilation compilation, System.Collections.Immutable.ImmutableArray analyzers, Microsoft.CodeAnalysis.Diagnostics.AnalyzerOptions options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -static Microsoft.CodeAnalysis.Emit.EditAndContinueMethodDebugInformation.Create(System.Collections.Immutable.ImmutableArray compressedSlotMap) +static Microsoft.CodeAnalysis.Emit.EditAndContinueMethodDebugInformation.Create(System.Collections.Immutable.ImmutableArray compressedSlotMap, System.Collections.Immutable.ImmutableArray compressedLambdaMap) static Microsoft.CodeAnalysis.Emit.EmitBaseline.CreateInitialBaseline(Microsoft.CodeAnalysis.ModuleMetadata module, System.Func debugInformationProvider) static Microsoft.CodeAnalysis.Emit.EmitOptions.operator !=(Microsoft.CodeAnalysis.Emit.EmitOptions left, Microsoft.CodeAnalysis.Emit.EmitOptions right) static Microsoft.CodeAnalysis.Emit.EmitOptions.operator ==(Microsoft.CodeAnalysis.Emit.EmitOptions left, Microsoft.CodeAnalysis.Emit.EmitOptions right) diff --git a/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs b/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs index 41528f8afe680..8420226a2d9f3 100644 --- a/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs +++ b/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs @@ -611,6 +611,16 @@ public SyntaxReference GetReference() return this.SyntaxTree.GetReference(this); } + /// + /// When invoked on a node that represents an anonymous function or a query clause [1] + /// with a of another anonymous function or a query clause of the same kind [2], + /// returns the body of the [1] that positionally corresponds to the specified . + /// + /// 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. + /// + internal abstract SyntaxNode GetCorrespondingLambdaBody(SyntaxNode body); + #region Node Lookup /// diff --git a/src/Compilers/Test/Utilities/CSharp/CompilingTestBase.cs b/src/Compilers/Test/Utilities/CSharp/CompilingTestBase.cs index 53cd4a1650115..91da971293fb2 100644 --- a/src/Compilers/Test/Utilities/CSharp/CompilingTestBase.cs +++ b/src/Compilers/Test/Utilities/CSharp/CompilingTestBase.cs @@ -1,4 +1,4 @@ -// 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; using System.Collections.Generic; @@ -53,9 +53,9 @@ internal static BoundStatement ParseAndBindMethodBody(string program, bool lower } StateMachineTypeSymbol stateMachineTypeOpt; - VariableSlotAllocator variableSlotAllocatorOpt; - int lambdaIndexDispenser = 0; - int scopeIndexDispenser = 0; + VariableSlotAllocator lazyVariableSlotAllocator = null; + var lambdaDebugInfoBuilder = ArrayBuilder.GetInstance(); + var closureDebugInfoBuilder = ArrayBuilder.GetInstance(); var body = MethodCompiler.LowerBodyOrInitializer( method: method, @@ -64,10 +64,13 @@ internal static BoundStatement ParseAndBindMethodBody(string program, bool lower previousSubmissionFields: null, compilationState: compilationState, diagnostics: diagnostics, - lambdaOrdinalDispenser: ref lambdaIndexDispenser, - scopeOrdinalDispenser: ref scopeIndexDispenser, - stateMachineTypeOpt: out stateMachineTypeOpt, - variableSlotAllocatorOpt: out variableSlotAllocatorOpt); + lazyVariableSlotAllocator: ref lazyVariableSlotAllocator, + lambdaDebugInfoBuilder: lambdaDebugInfoBuilder, + closureDebugInfoBuilder: closureDebugInfoBuilder, + stateMachineTypeOpt: out stateMachineTypeOpt); + + lambdaDebugInfoBuilder.Free(); + closureDebugInfoBuilder.Free(); return body; } diff --git a/src/Compilers/VisualBasic/Portable/BasicCodeAnalysis.vbproj b/src/Compilers/VisualBasic/Portable/BasicCodeAnalysis.vbproj index 27202b06758aa..f64ec69a90bd5 100644 --- a/src/Compilers/VisualBasic/Portable/BasicCodeAnalysis.vbproj +++ b/src/Compilers/VisualBasic/Portable/BasicCodeAnalysis.vbproj @@ -919,6 +919,7 @@ + diff --git a/src/Compilers/VisualBasic/Portable/Compilation/MethodCompiler.vb b/src/Compilers/VisualBasic/Portable/Compilation/MethodCompiler.vb index b0dd30ed50712..7895b607ad94c 100644 --- a/src/Compilers/VisualBasic/Portable/Compilation/MethodCompiler.vb +++ b/src/Compilers/VisualBasic/Portable/Compilation/MethodCompiler.vb @@ -1,4 +1,4 @@ -' 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.Collections.Concurrent @@ -1550,6 +1550,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Return New MethodBody(builder.RealizedIL, builder.MaxStack, If(method.PartialDefinitionPart, method), + 0, ' TODO: implement Lambda EnC builder.LocalSlotManager.LocalsInOrder(), builder.RealizedSequencePoints, debugDocumentProvider, @@ -1558,6 +1559,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic hasDynamicLocalVariables:=False, namespaceScopes:=namespaceScopes, namespaceScopeEncoding:=Cci.NamespaceScopeEncoding.Forwarding, + lambdaDebugInfo:=ImmutableArray(Of LambdaDebugInfo).Empty, + closureDebugInfo:=ImmutableArray(Of ClosureDebugInfo).Empty, stateMachineTypeNameOpt:=stateMachineTypeOpt?.Name, ' TODO: remove or update AddedOrChangedMethodInfo stateMachineHoistedLocalScopes:=Nothing, stateMachineHoistedLocalSlots:=stateMachineHoistedLocalSlots, diff --git a/src/Compilers/VisualBasic/Portable/Lowering/AsyncRewriter/AsyncRewriter.AsyncMethodToClassRewriter.vb b/src/Compilers/VisualBasic/Portable/Lowering/AsyncRewriter/AsyncRewriter.AsyncMethodToClassRewriter.vb index 184ccd2ce6d45..7436d2dbbc04c 100644 --- a/src/Compilers/VisualBasic/Portable/Lowering/AsyncRewriter/AsyncRewriter.AsyncMethodToClassRewriter.vb +++ b/src/Compilers/VisualBasic/Portable/Lowering/AsyncRewriter/AsyncRewriter.AsyncMethodToClassRewriter.vb @@ -1,13 +1,7 @@ ' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -Imports System.Collections.Generic Imports System.Collections.Immutable -Imports System.Threading -Imports Microsoft.Cci -Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.CodeGen -Imports Microsoft.CodeAnalysis.Collections -Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.VisualBasic.Symbols Imports Microsoft.CodeAnalysis.VisualBasic.Syntax @@ -103,11 +97,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic ' to find the previous awaiter field. If Not Me._awaiterFields.TryGetValue(awaiterType, result) Then Dim slotIndex As Integer = -1 - If Me.SlotAllocatorOpt IsNot Nothing Then - slotIndex = Me.SlotAllocatorOpt.GetPreviousAwaiterSlotIndex(DirectCast(awaiterType, Cci.ITypeReference)) - End If - - If slotIndex = -1 Then + If Me.SlotAllocatorOpt Is Nothing OrElse Not Me.SlotAllocatorOpt.TryGetPreviousAwaiterSlotIndex(DirectCast(awaiterType, Cci.ITypeReference), slotIndex) Then slotIndex = _nextAwaiterId _nextAwaiterId = _nextAwaiterId + 1 End If diff --git a/src/Compilers/VisualBasic/Portable/Lowering/StateMachineRewriter/StateMachineRewriter.vb b/src/Compilers/VisualBasic/Portable/Lowering/StateMachineRewriter/StateMachineRewriter.vb index 955adc8f868f9..4fd580fa8d11f 100644 --- a/src/Compilers/VisualBasic/Portable/Lowering/StateMachineRewriter/StateMachineRewriter.vb +++ b/src/Compilers/VisualBasic/Portable/Lowering/StateMachineRewriter/StateMachineRewriter.vb @@ -2,13 +2,9 @@ Imports System.Collections.Immutable Imports System.Runtime.InteropServices -Imports Microsoft.Cci Imports Microsoft.CodeAnalysis.CodeGen Imports Microsoft.CodeAnalysis.Collections -Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.VisualBasic.Symbols -Imports Microsoft.CodeAnalysis.VisualBasic.Syntax -Imports TypeKind = Microsoft.CodeAnalysis.TypeKind Namespace Microsoft.CodeAnalysis.VisualBasic @@ -295,8 +291,9 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Dim ordinal As Integer = SynthesizedLocalOrdinals.AssignLocalOrdinal(local.SynthesizedKind, syntaxOffset) id = New LocalDebugId(syntaxOffset, ordinal) - If SlotAllocatorOpt IsNot Nothing Then - slotIndex = SlotAllocatorOpt.GetPreviousHoistedLocalSlotIndex(declaratorSyntax, DirectCast(fieldType, Cci.ITypeReference), local.SynthesizedKind, id) + Dim previousSlotIndex = -1 + If SlotAllocatorOpt IsNot Nothing AndAlso SlotAllocatorOpt.TryGetPreviousHoistedLocalSlotIndex(declaratorSyntax, DirectCast(fieldType, Cci.ITypeReference), local.SynthesizedKind, id, previousSlotIndex) Then + slotIndex = previousSlotIndex End If End If diff --git a/src/Compilers/VisualBasic/Portable/Syntax/SyntaxNavigator.vb b/src/Compilers/VisualBasic/Portable/Syntax/SyntaxNavigator.vb index 043314acb75d8..88056e13308e2 100644 --- a/src/Compilers/VisualBasic/Portable/Syntax/SyntaxNavigator.vb +++ b/src/Compilers/VisualBasic/Portable/Syntax/SyntaxNavigator.vb @@ -1,9 +1,5 @@ ' 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 Microsoft.CodeAnalysis.Text -Imports Microsoft.CodeAnalysis.VisualBasic.Symbols -Imports Microsoft.CodeAnalysis.VisualBasic.Syntax - Namespace Microsoft.CodeAnalysis.VisualBasic.Syntax Friend Class SyntaxNavigator Inherits AbstractSyntaxNavigator @@ -17,9 +13,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Syntax Public Shared ReadOnly Instance As AbstractSyntaxNavigator = New SyntaxNavigator() - Private Shared ReadOnly CommonSyntaxTriviaSkipped As Func(Of SyntaxTrivia, Boolean) = - Function(t) t.RawKind = SyntaxKind.SkippedTokensTrivia - Private ReadOnly StepIntoFunctions As Func(Of SyntaxTrivia, Boolean)() = New Func(Of SyntaxTrivia, Boolean)() { Nothing, Function(t) t.RawKind = SyntaxKind.DocumentationCommentTrivia, @@ -36,36 +29,5 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Syntax Return StepIntoFunctions(index) End Function - Public Shared Function ToCommon(func As Func(Of SyntaxTrivia, Boolean)) As Func(Of SyntaxTrivia, Boolean) - If func Is SyntaxTrivia.Any Then - Return SyntaxTrivia.Any - End If - - If func Is SyntaxTriviaFunctions.Skipped Then - Return CommonSyntaxTriviaSkipped - End If - - If func Is Nothing Then - Return Nothing - End If - - Return Function(t) func(CType(t, SyntaxTrivia)) - End Function - - Public Shared Function ToCommon(func As Func(Of SyntaxToken, Boolean)) As Func(Of SyntaxToken, Boolean) - If func Is SyntaxToken.Any Then - Return SyntaxToken.Any - End If - - If func Is SyntaxToken.NonZeroWidth Then - Return SyntaxToken.NonZeroWidth - End If - - If func Is Nothing Then - Return Nothing - End If - - Return Function(t) func(CType(t, SyntaxToken)) - End Function End Class End Namespace diff --git a/src/Compilers/VisualBasic/Portable/Syntax/SyntaxUtilities.vb b/src/Compilers/VisualBasic/Portable/Syntax/SyntaxUtilities.vb new file mode 100644 index 0000000000000..9be12b2d2f4c3 --- /dev/null +++ b/src/Compilers/VisualBasic/Portable/Syntax/SyntaxUtilities.vb @@ -0,0 +1,52 @@ +' 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 Microsoft.CodeAnalysis.VisualBasic.Syntax + +Namespace Microsoft.CodeAnalysis.VisualBasic + + ''' + ''' + ''' + Friend NotInheritable Class SyntaxUtilities + Friend Shared Function GetCorrespondingLambdaBody(oldBody As SyntaxNode, newLambda As SyntaxNode) As SyntaxNode + Dim oldLambda = oldBody.Parent + + Select Case oldLambda.Kind + Case SyntaxKind.MultiLineFunctionLambdaExpression, + SyntaxKind.MultiLineSubLambdaExpression, + SyntaxKind.SingleLineFunctionLambdaExpression, + SyntaxKind.SingleLineSubLambdaExpression + ' Any statement or header can be used to represent the lambda body. + ' Let's pick the header since the lambda may have no other statements. + Return DirectCast(newLambda, LambdaExpressionSyntax).SubOrFunctionHeader + + Case SyntaxKind.WhereClause + Return DirectCast(newLambda, WhereClauseSyntax).Condition + + Case SyntaxKind.CollectionRangeVariable + Return DirectCast(newLambda, CollectionRangeVariableSyntax).Expression + + Case SyntaxKind.FunctionAggregation + Return DirectCast(newLambda, FunctionAggregationSyntax).Argument + + Case SyntaxKind.ExpressionRangeVariable + Return DirectCast(newLambda, ExpressionRangeVariableSyntax).Expression + + Case SyntaxKind.TakeWhileClause, SyntaxKind.SkipWhileClause + Return DirectCast(newLambda, PartitionWhileClauseSyntax).Condition + + Case SyntaxKind.AscendingOrdering, SyntaxKind.DescendingOrdering + Return DirectCast(newLambda, OrderingSyntax).Expression + + Case SyntaxKind.JoinCondition + Dim oldJoin = DirectCast(oldLambda, JoinConditionSyntax) + Dim newJoin = DirectCast(newLambda, JoinConditionSyntax) + Debug.Assert(oldJoin.Left Is oldBody OrElse oldJoin.Right Is oldBody) + Return If(oldJoin.Left Is oldBody, newJoin.Left, newJoin.Right) + + Case Else + Throw ExceptionUtilities.Unreachable + End Select + End Function + End Class +End Namespace \ No newline at end of file diff --git a/src/Compilers/VisualBasic/Portable/Syntax/VisualBasicSyntaxNode.vb b/src/Compilers/VisualBasic/Portable/Syntax/VisualBasicSyntaxNode.vb index b34fbf2758aaa..1d079c48c19fa 100644 --- a/src/Compilers/VisualBasic/Portable/Syntax/VisualBasicSyntaxNode.vb +++ b/src/Compilers/VisualBasic/Portable/Syntax/VisualBasicSyntaxNode.vb @@ -682,5 +682,9 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Return SyntaxFactory.AreEquivalent(Me, DirectCast(node, VisualBasicSyntaxNode), topLevel) End Function + Friend Overrides Function GetCorrespondingLambdaBody(body As SyntaxNode) As SyntaxNode + Return SyntaxUtilities.GetCorrespondingLambdaBody(body, Me) + End Function + End Class End Namespace diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/SyntaxUtilitiesTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/SyntaxUtilitiesTests.cs index 1b1bdd6b322c1..7abaeb1c40136 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/SyntaxUtilitiesTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/SyntaxUtilitiesTests.cs @@ -4,6 +4,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.EditAndContinue; using Xunit; +using SyntaxUtilities = Microsoft.CodeAnalysis.CSharp.EditAndContinue.SyntaxUtilities; namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.EditAndContinue { diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/SyntaxUtilitiesTests.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/SyntaxUtilitiesTests.vb index 37bbe9d392f15..4799c6136bc95 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/SyntaxUtilitiesTests.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/SyntaxUtilitiesTests.vb @@ -4,6 +4,7 @@ Imports System.Linq Imports Microsoft.CodeAnalysis.VisualBasic Imports Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Imports Xunit +Imports SyntaxUtilities = Microsoft.CodeAnalysis.VisualBasic.EditAndContinue.SyntaxUtilities Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.EditAndContinue diff --git a/src/Features/CSharp/CSharpFeatures.csproj b/src/Features/CSharp/CSharpFeatures.csproj index 69f6e2f7e2817..1c299049bc3ce 100644 --- a/src/Features/CSharp/CSharpFeatures.csproj +++ b/src/Features/CSharp/CSharpFeatures.csproj @@ -88,6 +88,9 @@ + + InternalUtilities\SyntaxUtilities.cs + diff --git a/src/Features/CSharp/EditAndContinue/CSharpEditAndContinueAnalyzer.cs b/src/Features/CSharp/EditAndContinue/CSharpEditAndContinueAnalyzer.cs index 45f40a13f1603..73f54b8bbed4e 100644 --- a/src/Features/CSharp/EditAndContinue/CSharpEditAndContinueAnalyzer.cs +++ b/src/Features/CSharp/EditAndContinue/CSharpEditAndContinueAnalyzer.cs @@ -15,6 +15,7 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; +using CompilerSyntaxUtilities = Microsoft.CodeAnalysis.CSharp.SyntaxUtilities; namespace Microsoft.CodeAnalysis.CSharp.EditAndContinue { @@ -377,7 +378,7 @@ protected override SyntaxNode FindEnclosingLambdaBody(SyntaxNode containerOpt, S protected override SyntaxNode GetPartnerLambdaBody(SyntaxNode oldBody, SyntaxNode newLambda) { - return SyntaxUtilities.GetPartnerLambdaBody(oldBody, newLambda); + return CompilerSyntaxUtilities.GetCorrespondingLambdaBody(oldBody, newLambda); } protected override Match ComputeTopLevelMatch(SyntaxNode oldCompilationUnit, SyntaxNode newCompilationUnit) diff --git a/src/Features/CSharp/EditAndContinue/SyntaxUtilities.cs b/src/Features/CSharp/EditAndContinue/SyntaxUtilities.cs index c92978915dca7..f5abb91727bfb 100644 --- a/src/Features/CSharp/EditAndContinue/SyntaxUtilities.cs +++ b/src/Features/CSharp/EditAndContinue/SyntaxUtilities.cs @@ -109,62 +109,6 @@ public static void AssertIsBody(SyntaxNode syntax, bool allowLambda) Debug.Assert(false); } - public static SyntaxNode GetPartnerLambdaBody(SyntaxNode oldBody, SyntaxNode newLambda) - { - var oldLambda = oldBody.Parent; - switch (oldLambda.Kind()) - { - case SyntaxKind.ParenthesizedLambdaExpression: - case SyntaxKind.SimpleLambdaExpression: - case SyntaxKind.AnonymousMethodExpression: - switch (newLambda.Kind()) - { - case SyntaxKind.ParenthesizedLambdaExpression: - return ((ParenthesizedLambdaExpressionSyntax)newLambda).Body; - - case SyntaxKind.SimpleLambdaExpression: - return ((SimpleLambdaExpressionSyntax)newLambda).Body; - - case SyntaxKind.AnonymousMethodExpression: - return ((AnonymousMethodExpressionSyntax)newLambda).Block; - - default: - throw ExceptionUtilities.Unreachable; - } - - case SyntaxKind.FromClause: - return ((FromClauseSyntax)newLambda).Expression; - - case SyntaxKind.LetClause: - return ((LetClauseSyntax)newLambda).Expression; - - case SyntaxKind.WhereClause: - return ((WhereClauseSyntax)newLambda).Condition; - - case SyntaxKind.AscendingOrdering: - case SyntaxKind.DescendingOrdering: - return ((OrderingSyntax)newLambda).Expression; - - case SyntaxKind.SelectClause: - return ((SelectClauseSyntax)newLambda).Expression; - - case SyntaxKind.JoinClause: - var oldJoin = (JoinClauseSyntax)oldLambda; - var newJoin = (JoinClauseSyntax)newLambda; - Debug.Assert(oldJoin.LeftExpression == oldBody || oldJoin.RightExpression == oldBody); - return (oldJoin.LeftExpression == oldBody) ? newJoin.LeftExpression : newJoin.RightExpression; - - case SyntaxKind.GroupClause: - var oldGroup = (GroupClauseSyntax)oldLambda; - var newGroup = (GroupClauseSyntax)newLambda; - Debug.Assert(oldGroup.GroupExpression == oldBody || oldGroup.ByExpression == oldBody); - return (oldGroup.GroupExpression == oldBody) ? newGroup.GroupExpression : newGroup.ByExpression; - - default: - throw ExceptionUtilities.Unreachable; - } - } - public static void FindLeafNodeAndPartner(SyntaxNode leftRoot, int leftPosition, SyntaxNode rightRoot, out SyntaxNode leftNode, out SyntaxNode rightNode) { leftNode = leftRoot; @@ -243,25 +187,6 @@ public static bool IsLambda(SyntaxKind kind) return false; } - public static bool? IsPrivate(SyntaxTokenList modifiers) - { - foreach (var modifier in modifiers) - { - switch (modifier.Kind()) - { - case SyntaxKind.PublicKeyword: - case SyntaxKind.ProtectedKeyword: - case SyntaxKind.InternalKeyword: - return false; - - case SyntaxKind.PrivateKeyword: - return true; - } - } - - return null; - } - public static bool Any(TypeParameterListSyntax listOpt) { return listOpt != null && listOpt.ChildNodesAndTokens().Count != 0; diff --git a/src/Features/VisualBasic/BasicFeatures.vbproj b/src/Features/VisualBasic/BasicFeatures.vbproj index 3eb99506b6c6f..05570a3949945 100644 --- a/src/Features/VisualBasic/BasicFeatures.vbproj +++ b/src/Features/VisualBasic/BasicFeatures.vbproj @@ -117,6 +117,9 @@ + + InternalUtilities\SyntaxUtilities.vb + diff --git a/src/Features/VisualBasic/EditAndContinue/SyntaxUtilities.vb b/src/Features/VisualBasic/EditAndContinue/SyntaxUtilities.vb index e8b8ff66c37bc..3972dda0eb011 100644 --- a/src/Features/VisualBasic/EditAndContinue/SyntaxUtilities.vb +++ b/src/Features/VisualBasic/EditAndContinue/SyntaxUtilities.vb @@ -143,47 +143,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Return variables.IndexOf(collectionRangeVariable) = 0 End Function - Public Shared Function GetPartnerLambdaBody(oldBody As SyntaxNode, newLambda As SyntaxNode) As SyntaxNode - Dim oldLambda = oldBody.Parent - - Select Case oldLambda.Kind - Case SyntaxKind.MultiLineFunctionLambdaExpression, - SyntaxKind.MultiLineSubLambdaExpression, - SyntaxKind.SingleLineFunctionLambdaExpression, - SyntaxKind.SingleLineSubLambdaExpression - ' Any statement or header can be used to represent the lambda body. - ' Let's pick the header since the lambda may have no other statements. - Return DirectCast(newLambda, LambdaExpressionSyntax).SubOrFunctionHeader - - Case SyntaxKind.WhereClause - Return DirectCast(newLambda, WhereClauseSyntax).Condition - - Case SyntaxKind.CollectionRangeVariable - Return DirectCast(newLambda, CollectionRangeVariableSyntax).Expression - - Case SyntaxKind.FunctionAggregation - Return DirectCast(newLambda, FunctionAggregationSyntax).Argument - - Case SyntaxKind.ExpressionRangeVariable - Return DirectCast(newLambda, ExpressionRangeVariableSyntax).Expression - - Case SyntaxKind.TakeWhileClause, SyntaxKind.SkipWhileClause - Return DirectCast(newLambda, PartitionWhileClauseSyntax).Condition - - Case SyntaxKind.AscendingOrdering, SyntaxKind.DescendingOrdering - Return DirectCast(newLambda, OrderingSyntax).Expression - - Case SyntaxKind.JoinCondition - Dim oldJoin = DirectCast(oldLambda, JoinConditionSyntax) - Dim newJoin = DirectCast(newLambda, JoinConditionSyntax) - Debug.Assert(oldJoin.Left Is oldBody OrElse oldJoin.Right Is oldBody) - Return If(oldJoin.Left Is oldBody, newJoin.Left, newJoin.Right) - - Case Else - Throw ExceptionUtilities.Unreachable - End Select - End Function - Public Shared Sub FindLeafNodeAndPartner(leftRoot As SyntaxNode, leftPosition As Integer, rightRoot As SyntaxNode, diff --git a/src/Features/VisualBasic/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb b/src/Features/VisualBasic/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb index 3ea9f94b16411..a3c33461b9818 100644 --- a/src/Features/VisualBasic/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb +++ b/src/Features/VisualBasic/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb @@ -10,6 +10,7 @@ Imports Microsoft.CodeAnalysis.Host Imports Microsoft.CodeAnalysis.Host.Mef Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.VisualBasic.Syntax +Imports CompilerSyntaxUtilities = Microsoft.CodeAnalysis.VisualBasic.SyntaxUtilities Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue @@ -418,7 +419,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue End Function Protected Overrides Function GetPartnerLambdaBody(oldBody As SyntaxNode, newLambda As SyntaxNode) As SyntaxNode - Return SyntaxUtilities.GetPartnerLambdaBody(oldBody, newLambda) + Return CompilerSyntaxUtilities.GetCorrespondingLambdaBody(oldBody, newLambda) End Function Protected Overrides Function ComputeTopLevelMatch(oldCompilationUnit As SyntaxNode, newCompilationUnit As SyntaxNode) As Match(Of SyntaxNode) diff --git a/src/Test/PdbUtilities/Pdb/PdbToXml.cs b/src/Test/PdbUtilities/Pdb/PdbToXml.cs index 778ade4408124..cad4b0a8649c3 100644 --- a/src/Test/PdbUtilities/Pdb/PdbToXml.cs +++ b/src/Test/PdbUtilities/Pdb/PdbToXml.cs @@ -48,10 +48,10 @@ public unsafe static string DeltaPdbToXml(Stream deltaPdb, IEnumerable meth { var writer = new StringWriter(); ToXml( - writer, - deltaPdb, + writer, + deltaPdb, metadataReaderOpt: null, - options: PdbToXmlOptions.IncludeTokens, + options: PdbToXmlOptions.IncludeTokens, methodHandles: methodTokens.Select(token => (MethodDefinitionHandle)MetadataTokens.Handle(token))); return writer.ToString(); @@ -143,7 +143,7 @@ private static void ToXml(TextWriter xmlWriter, Stream pdbStream, MetadataReader converter.WriteRoot(methodHandles ?? metadataReaderOpt.MethodDefinitions); } - + writer.Close(); // Save xml to disk @@ -261,7 +261,7 @@ private void WriteMethod(MethodDefinitionHandle methodHandle) private void WriteCustomDebugInfo(byte[] bytes) { var records = CustomDebugInfoReader.GetCustomDebugInfoRecords(bytes).ToArray(); - + writer.WriteStartElement("customDebugInfo"); foreach (var record in records) @@ -295,6 +295,9 @@ private void WriteCustomDebugInfo(byte[] bytes) case CustomDebugInfoKind.EditAndContinueLocalSlotMap: WriteEditAndContinueLocalSlotMap(record); break; + case CustomDebugInfoKind.EditAndContinueLambdaMap: + WriteEditAndContinueLambdaMap(record); + break; default: WriteUnknownCustomDebugInfo(record); break; @@ -482,62 +485,173 @@ private unsafe void WriteEditAndContinueLocalSlotMap(CustomDebugInfoRecord recor Debug.Assert(record.Kind == CustomDebugInfoKind.EditAndContinueLocalSlotMap); writer.WriteStartElement("encLocalSlotMap"); + try + { + int syntaxOffsetBaseline = -1; + + fixed (byte* compressedSlotMapPtr = &record.Data.ToArray()[0]) + { + var blobReader = new BlobReader(compressedSlotMapPtr, record.Data.Length); + + while (blobReader.RemainingBytes > 0) + { + byte b = blobReader.ReadByte(); + + if (b == 0xff) + { + if (!blobReader.TryReadCompressedInteger(out syntaxOffsetBaseline)) + { + writer.WriteElementString("baseline", "?"); + return; + } + + syntaxOffsetBaseline = -syntaxOffsetBaseline; + continue; + } - int syntaxOffsetBaseline = -1; + writer.WriteStartElement("slot"); + + if (b == 0) + { + // short-lived temp, no info + writer.WriteAttributeString("kind", "temp"); + } + else + { + int synthesizedKind = (b & 0x3f) - 1; + bool hasOrdinal = (b & (1 << 7)) != 0; - fixed (byte* compressedSlotMapPtr = &record.Data.ToArray()[0]) + int syntaxOffset; + bool badSyntaxOffset = !blobReader.TryReadCompressedInteger(out syntaxOffset); + syntaxOffset += syntaxOffsetBaseline; + + int ordinal = 0; + bool badOrdinal = hasOrdinal && !blobReader.TryReadCompressedInteger(out ordinal); + + writer.WriteAttributeString("kind", synthesizedKind.ToString()); + writer.WriteAttributeString("offset", badSyntaxOffset ? "?" : syntaxOffset.ToString()); + + if (badOrdinal || hasOrdinal) + { + writer.WriteAttributeString("ordinal", badOrdinal ? "?" : ordinal.ToString()); + } + } + + writer.WriteEndElement(); + } + } + } + finally { - var blobReader = new BlobReader(compressedSlotMapPtr, record.Data.Length); + writer.WriteEndElement(); //encLocalSlotMap + } + } + + private unsafe void WriteEditAndContinueLambdaMap(CustomDebugInfoRecord record) + { + Debug.Assert(record.Kind == CustomDebugInfoKind.EditAndContinueLambdaMap); - while (blobReader.RemainingBytes > 0) + writer.WriteStartElement("encLambdaMap"); + try + { + if (record.Data.Length == 0) { - byte b = blobReader.ReadByte(); + return; + } - if (b == 0xff) + int methodOrdinal = -1; + int syntaxOffsetBaseline = -1; + int closureCount; + + fixed (byte* blobPtr = &record.Data.ToArray()[0]) + { + var blobReader = new BlobReader(blobPtr, record.Data.Length); + + if (!blobReader.TryReadCompressedInteger(out methodOrdinal)) { - break; + writer.WriteElementString("methodOrdinal", "?"); + writer.WriteEndElement(); + return; } - if (b == 0xfe) + // [-1, inf) + methodOrdinal--; + writer.WriteElementString("methodOrdinal", methodOrdinal.ToString()); + + if (!blobReader.TryReadCompressedInteger(out syntaxOffsetBaseline)) { - syntaxOffsetBaseline = -blobReader.ReadCompressedInteger(); - writer.WriteElementString("baseline", syntaxOffsetBaseline.ToString()); - continue; + writer.WriteElementString("baseline", "?"); + writer.WriteEndElement(); + return; } - writer.WriteStartElement("slot"); - - if (b == 0) + syntaxOffsetBaseline = -syntaxOffsetBaseline; + if (!blobReader.TryReadCompressedInteger(out closureCount)) { - // short-lived temp, no info - writer.WriteAttributeString("kind", "temp"); + writer.WriteElementString("closureCount", "?"); + writer.WriteEndElement(); + return; } - else + + for (int i = 0; i < closureCount; i++) { - int synthesizedKind = (b & 0x3f) - 1; - bool hasOrdinal = (b & (1 << 7)) != 0; + writer.WriteStartElement("closure"); + try + { + int syntaxOffset; + if (!blobReader.TryReadCompressedInteger(out syntaxOffset)) + { + writer.WriteElementString("offset", "?"); + break; + } + + writer.WriteAttributeString("offset", (syntaxOffset + syntaxOffsetBaseline).ToString()); + } + finally + { + writer.WriteEndElement(); + } + } - int syntaxOffset; - bool badSyntaxOffset = !blobReader.TryReadCompressedInteger(out syntaxOffset); - syntaxOffset += syntaxOffsetBaseline; + while (blobReader.RemainingBytes > 0) + { + writer.WriteStartElement("lambda"); + try + { + int syntaxOffset; + if (!blobReader.TryReadCompressedInteger(out syntaxOffset)) + { + writer.WriteElementString("offset", "?"); + return; + } - int ordinal = 0; - bool badOrdinal = hasOrdinal && !blobReader.TryReadCompressedInteger(out ordinal); + writer.WriteAttributeString("offset", (syntaxOffset + syntaxOffsetBaseline).ToString()); - writer.WriteAttributeString("kind", synthesizedKind.ToString()); - writer.WriteAttributeString("offset", badSyntaxOffset ? "?" : syntaxOffset.ToString()); + int closureOrdinal; + if (!blobReader.TryReadCompressedInteger(out closureOrdinal)) + { + writer.WriteElementString("closure", "?"); + return; + } - if (badOrdinal || hasOrdinal) + closureOrdinal--; + if (closureOrdinal != -1) + { + writer.WriteAttributeString("closure", + closureOrdinal.ToString() + (closureOrdinal < -1 || closureOrdinal >= closureCount ? " (invalid)" : "")); + } + } + finally { - writer.WriteAttributeString("ordinal", badOrdinal ? "?" : ordinal.ToString()); + writer.WriteEndElement(); } } - - writer.WriteEndElement(); } } - - writer.WriteEndElement(); //encLocalSlotMap + finally + { + writer.WriteEndElement(); //encLocalSlotMap + } } private void WriteScopes(ISymUnmanagedScope scope) @@ -856,7 +970,7 @@ private void WriteLocalsHelper(ISymUnmanagedScope scope, Dictionary string.Format("{0,2:X}", b) + ", ")) ; + var checkSum = string.Concat(doc.GetCheckSum().Select(b => string.Format("{0,2:X}", b) + ", ")); if (!string.IsNullOrEmpty(checkSum)) { @@ -1011,7 +1125,7 @@ private void WriteAllMethodSpans() foreach (var methodDocument in method.GetDocumentsForMethod()) { writer.WriteStartElement("document"); - + int startLine, endLine; method.GetSourceExtentInDocument(methodDocument, out startLine, out endLine); @@ -1103,7 +1217,7 @@ private void WriteResolvedToken(MethodDefinitionHandle methodHandle, bool isRefe var fullName = GetFullTypeName(metadataReader, containingTypeHandle); if (fullName != null) { - writer.WriteAttributeString(isReference ? "declaringType" : "containingType", fullName); + writer.WriteAttributeString(isReference ? "declaringType" : "containingType", fullName); } // method name diff --git a/src/Test/PdbUtilities/Shared/CustomDebugInfoReader.cs b/src/Test/PdbUtilities/Shared/CustomDebugInfoReader.cs index 4f4009d3c2400..dfeaee0ac0d18 100644 --- a/src/Test/PdbUtilities/Shared/CustomDebugInfoReader.cs +++ b/src/Test/PdbUtilities/Shared/CustomDebugInfoReader.cs @@ -986,5 +986,6 @@ internal enum CustomDebugInfoKind : byte ForwardIterator = CDI.CdiKindForwardIterator, DynamicLocals = CDI.CdiKindDynamicLocals, EditAndContinueLocalSlotMap = CDI.CdiKindEditAndContinueLocalSlotMap, + EditAndContinueLambdaMap = CDI.CdiKindEditAndContinueLambdaMap, } } diff --git a/src/Test/Utilities/AssertEx.cs b/src/Test/Utilities/AssertEx.cs index 7fefaf64c74a7..ab6aeaee9e2b2 100644 --- a/src/Test/Utilities/AssertEx.cs +++ b/src/Test/Utilities/AssertEx.cs @@ -163,7 +163,7 @@ public static void Equal(ImmutableArray expected, IEnumerable actual, I } } - public static void Equal(IEnumerable expected, ImmutableArray actual, IEqualityComparer comparer = null, string message = null, string itemSeparator = ", ") + public static void Equal(IEnumerable expected, ImmutableArray actual, IEqualityComparer comparer = null, string message = null, string itemSeparator = null) { if (expected == null || actual.IsDefault) { @@ -175,13 +175,13 @@ public static void Equal(IEnumerable expected, ImmutableArray actual, I } } - public static void Equal(ImmutableArray expected, ImmutableArray actual, IEqualityComparer comparer = null, string message = null, string itemSeparator = ", ") + public static void Equal(ImmutableArray expected, ImmutableArray actual, IEqualityComparer comparer = null, string message = null, string itemSeparator = null) { - Equal((IEnumerable)expected, (IEnumerable)actual, comparer, message, itemSeparator); + Equal(expected, (IEnumerable)actual, comparer, message, itemSeparator); } public static void Equal(IEnumerable expected, IEnumerable actual, IEqualityComparer comparer = null, string message = null, - string itemSeparator = ",\r\n", Func itemInspector = null) + string itemSeparator = null, Func itemInspector = null) { if (ReferenceEquals(expected, actual)) { @@ -196,19 +196,16 @@ public static void Equal(IEnumerable expected, IEnumerable actual, IEqu { Fail("actual was null, but expected wasn't\r\n" + message); } - else + else if (!SequenceEqual(expected, actual, comparer)) { - if (!SequenceEqual(expected, actual, comparer)) - { - string assertMessage = GetAssertMessage(expected, actual, comparer, itemInspector, itemSeparator); - - if (message != null) - { - assertMessage = message + "\r\n" + assertMessage; - } + string assertMessage = GetAssertMessage(expected, actual, comparer, itemInspector, itemSeparator); - Assert.True(false, assertMessage); + if (message != null) + { + assertMessage = message + "\r\n" + assertMessage; } + + Assert.True(false, assertMessage); } } @@ -435,8 +432,8 @@ public static string GetAssertMessage(string expected, string actual, bool escap public static string GetAssertMessage(IEnumerable expected, IEnumerable actual, bool escapeQuotes, string expectedValueSourcePath = null, int expectedValueSourceLine = 0) { - Func toString = escapeQuotes ? new Func(t => t.ToString().Replace("\"", "\"\"")) : null; - return GetAssertMessage(expected, actual, toString: toString, separator: "\r\n", expectedValueSourcePath: expectedValueSourcePath, expectedValueSourceLine: expectedValueSourceLine); + Func itemInspector = escapeQuotes ? new Func(t => t.ToString().Replace("\"", "\"\"")) : null; + return GetAssertMessage(expected, actual, itemInspector: itemInspector, itemSeparator: "\r\n", expectedValueSourcePath: expectedValueSourcePath, expectedValueSourceLine: expectedValueSourceLine); } private static readonly string s_diffToolPath = Environment.GetEnvironmentVariable("ROSLYN_DIFFTOOL"); @@ -445,24 +442,43 @@ public static string GetAssertMessage( IEnumerable expected, IEnumerable actual, IEqualityComparer comparer = null, - Func toString = null, - string separator = ",\r\n", + Func itemInspector = null, + string itemSeparator = null, string expectedValueSourcePath = null, int expectedValueSourceLine = 0) { - if (toString == null) + if (itemInspector == null) { - toString = new Func(obj => (obj != null) ? obj.ToString() : ""); + if (expected is IEnumerable) + { + itemInspector = b => $"0x{b:X2}"; + } + else + { + itemInspector = new Func(obj => (obj != null) ? obj.ToString() : ""); + } + } + + if (itemSeparator == null) + { + if (expected is IEnumerable) + { + itemSeparator = ", "; + } + else + { + itemSeparator = ",\r\n"; + } } - var actualString = string.Join(separator, actual.Select(toString)); + var actualString = string.Join(itemSeparator, actual.Select(itemInspector)); var message = new StringBuilder(); message.AppendLine(); message.AppendLine("Actual:"); message.AppendLine(actualString); message.AppendLine("Differences:"); - message.AppendLine(DiffUtil.DiffReport(expected, actual, comparer, toString, separator)); + message.AppendLine(DiffUtil.DiffReport(expected, actual, comparer, itemInspector, itemSeparator)); string link; if (TryGenerateExpectedSourceFielAndGetDiffLink(actualString, expected.Count(), expectedValueSourcePath, expectedValueSourceLine, out link)) diff --git a/src/Test/Utilities/PdbTestUtilities.cs b/src/Test/Utilities/PdbTestUtilities.cs index 375ff7cac9fac..2c466c4d8407e 100644 --- a/src/Test/Utilities/PdbTestUtilities.cs +++ b/src/Test/Utilities/PdbTestUtilities.cs @@ -23,8 +23,9 @@ public static EditAndContinueMethodDebugInformation GetEncMethodDebugInfo(this I public static EditAndContinueMethodDebugInformation GetEncMethodDebugInfo(byte[] customDebugInfoBlob) { - var record = CustomDebugInfoReader.TryGetCustomDebugInfoRecord(customDebugInfoBlob, CustomDebugInfoKind.EditAndContinueLocalSlotMap); - return EditAndContinueMethodDebugInformation.Create(record); + var localSlots = CustomDebugInfoReader.TryGetCustomDebugInfoRecord(customDebugInfoBlob, CustomDebugInfoKind.EditAndContinueLocalSlotMap); + var lambdaMap = CustomDebugInfoReader.TryGetCustomDebugInfoRecord(customDebugInfoBlob, CustomDebugInfoKind.EditAndContinueLambdaMap); + return EditAndContinueMethodDebugInformation.Create(localSlots, lambdaMap); } } } diff --git a/src/Test/Utilities/RegexExtensions.cs b/src/Test/Utilities/RegexExtensions.cs new file mode 100644 index 0000000000000..92dc3f7b48292 --- /dev/null +++ b/src/Test/Utilities/RegexExtensions.cs @@ -0,0 +1,18 @@ +// 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.Text.RegularExpressions; + +namespace Roslyn.Test.Utilities +{ + public static class RegexExtensions + { + public static IEnumerable ToEnumerable(this MatchCollection collection) + { + foreach (Match m in collection) + { + yield return m; + } + } + } +} diff --git a/src/Test/Utilities/SharedCompilationUtils.cs b/src/Test/Utilities/SharedCompilationUtils.cs index 8c8f7a0beb6ec..d13b168a0d388 100644 --- a/src/Test/Utilities/SharedCompilationUtils.cs +++ b/src/Test/Utilities/SharedCompilationUtils.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.IO; using System.Linq; using System.Reflection; @@ -133,7 +134,12 @@ internal static string GetMethodIL(this CompilationTestData.MethodData method) internal static EditAndContinueMethodDebugInformation GetEncDebugInfo(this CompilationTestData.MethodData methodData) { - return Cci.CustomDebugInfoWriter.GetEncDebugInfoForLocals(methodData.ILBuilder.LocalSlotManager.LocalsInOrder()); + // TODO: + return new EditAndContinueMethodDebugInformation( + 0, + Cci.CustomDebugInfoWriter.GetLocalSlotDebugInfos(methodData.ILBuilder.LocalSlotManager.LocalsInOrder()), + closures: ImmutableArray.Empty, + lambdas: ImmutableArray.Empty); } internal static Func EncDebugInfoProvider(this CompilationTestData.MethodData methodData) diff --git a/src/Test/Utilities/SourceWithMarkedNodes.cs b/src/Test/Utilities/SourceWithMarkedNodes.cs new file mode 100644 index 0000000000000..3101f727a8c23 --- /dev/null +++ b/src/Test/Utilities/SourceWithMarkedNodes.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; +using Xunit; + +namespace Roslyn.Test.Utilities +{ + internal sealed class SourceWithMarkedNodes + { + public readonly string Source; + public readonly SyntaxTree Tree; + public readonly ImmutableArray> SpansAndKinds; + + public SourceWithMarkedNodes(string markedSource, Func parser, Func getSyntaxKind) + { + Source = ClearTags(markedSource); + Tree = parser(Source); + + SpansAndKinds = ImmutableArray.CreateRange( + from match in MarkerPattern.Matches(markedSource).ToEnumerable() + let markedSyntax = match.Groups["MarkedSyntax"] + let syntaxKindOpt = match.Groups["SyntaxKind"].Value + let parsedKind = string.IsNullOrEmpty(syntaxKindOpt) ? 0 : getSyntaxKind(syntaxKindOpt) + select ValueTuple.Create(new TextSpan(markedSyntax.Index, markedSyntax.Length), parsedKind)); + } + + internal static string ClearTags(string source) + { + return Tags.Replace(source, m => new string(' ', m.Length)); + } + + private static readonly Regex Tags = new Regex( + @"[<][/]?N[:][:A-Za-z0-9]+[>]", + RegexOptions.IgnorePatternWhitespace | RegexOptions.Singleline); + + private static readonly Regex MarkerPattern = new Regex( + @"[<]N[:] (?[0-9]+) ([:](?[A-Za-z]+))? [>] + (?.*) + [<][/]N[:](\k) [>]", + RegexOptions.IgnorePatternWhitespace | RegexOptions.Singleline); + + public ImmutableDictionary MapSyntaxNodesToMarks() + { + var root = Tree.GetRoot(); + var builder = ImmutableDictionary.CreateBuilder(); + for (int i = 0; i < SpansAndKinds.Length; i++) + { + builder.Add(GetNode(root, SpansAndKinds[i]), i); + } + + return builder.ToImmutableDictionary(); + } + + private SyntaxNode GetNode(SyntaxNode root, ValueTuple spanAndKind) + { + var node = root.FindNode(spanAndKind.Item1, getInnermostNodeForTie: true); + if (spanAndKind.Item2 == 0) + { + return node; + } + + var nodeOfKind = node.FirstAncestorOrSelf(n => n.RawKind == spanAndKind.Item2); + Assert.NotNull(nodeOfKind); + return nodeOfKind; + } + + public ImmutableDictionary MapMarksToSyntaxNodes() + { + var root = Tree.GetRoot(); + var builder = ImmutableDictionary.CreateBuilder(); + for (int i = 0; i < SpansAndKinds.Length; i++) + { + builder.Add(i, GetNode(root, SpansAndKinds[i])); + } + + return builder.ToImmutableDictionary(); + } + + public static Func GetSyntaxMap(SourceWithMarkedNodes source0, SourceWithMarkedNodes source1) + { + var map0 = source0.MapMarksToSyntaxNodes(); + var map1 = source1.MapSyntaxNodesToMarks(); + + return new Func(node1 => + { + int mark; + var result = map1.TryGetValue(node1, out mark) ? map0[mark] : null; + return result; + }); + } + } +} diff --git a/src/Test/Utilities/TestUtilities.csproj b/src/Test/Utilities/TestUtilities.csproj index 28678af996916..29839fd9b4d99 100644 --- a/src/Test/Utilities/TestUtilities.csproj +++ b/src/Test/Utilities/TestUtilities.csproj @@ -142,6 +142,7 @@ + @@ -157,6 +158,7 @@ +