diff --git a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/CompilationContext.cs b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/CompilationContext.cs index 270e02f8d26b9737a0deaf7c9c49af89ddfbc92e..2277c7954192652f7a2416ab141659677226d296 100644 --- a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/CompilationContext.cs +++ b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/CompilationContext.cs @@ -93,7 +93,7 @@ internal sealed class CompilationContext // Assert that the cheap check for "this" is equivalent to the expensive check for "this". Debug.Assert( - _displayClassVariables.ContainsKey(GeneratedNames.ThisProxyFieldName()) == + (GetThisProxy(_displayClassVariables) != null) == _displayClassVariables.Values.Any(v => v.Kind == DisplayClassVariableKind.This)); } @@ -180,7 +180,7 @@ internal sealed class CompilationContext this, (EEMethodSymbol method, DiagnosticBag diags, out ImmutableArray declaredLocals, out ResultProperties properties) => { - var hasDisplayClassThis = _displayClassVariables.ContainsKey(GeneratedNames.ThisProxyFieldName()); + var hasDisplayClassThis = GetThisProxy(_displayClassVariables) != null; var binder = ExtendBinderChain( syntax, aliases, @@ -218,7 +218,7 @@ internal sealed class CompilationContext this, (EEMethodSymbol method, DiagnosticBag diags, out ImmutableArray declaredLocals, out ResultProperties properties) => { - var hasDisplayClassThis = _displayClassVariables.ContainsKey(GeneratedNames.ThisProxyFieldName()); + var hasDisplayClassThis = GetThisProxy(_displayClassVariables) != null; var binder = ExtendBinderChain( syntax, aliases, @@ -349,7 +349,7 @@ private static string GetNextMethodName(ArrayBuilder builder) // "this" for non-static methods that are not display class methods or // display class methods where the display class contains "<>4__this". - if (!m.IsStatic && (!IsDisplayClassType(m.ContainingType) || _displayClassVariables.ContainsKey(GeneratedNames.ThisProxyFieldName()))) + if ((!m.IsStatic && !IsDisplayClassType(m.ContainingType)) || GetThisProxy( _displayClassVariables) != null) { var methodName = GetNextMethodName(methodBuilder); var method = this.GetThisMethod(container, methodName); @@ -381,7 +381,9 @@ private static string GetNextMethodName(ArrayBuilder builder) foreach (var parameter in m.Parameters) { var parameterName = parameter.Name; - if (!_hoistedParameterNames.Contains(parameterName) && GeneratedNames.GetKind(parameterName) == GeneratedNameKind.None) + if (!_hoistedParameterNames.Contains(parameterName) && + GeneratedNames.GetKind(parameterName) == GeneratedNameKind.None && + !IsDisplayClassParameter(parameter)) { AppendParameterAndMethod(localBuilder, methodBuilder, parameter, container, parameterIndex); } @@ -1212,7 +1214,8 @@ private static DkmClrCompilationResultFlags GetLocalResultFlags(LocalSymbol loca foreach (var parameter in method.Parameters) { - if (GeneratedNames.GetKind(parameter.Name) == GeneratedNameKind.TransparentIdentifier) + if (GeneratedNames.GetKind(parameter.Name) == GeneratedNameKind.TransparentIdentifier || + IsDisplayClassParameter(parameter)) { var instance = new DisplayClassInstanceFromParameter(parameter); displayClassTypes.Add(instance.Type); @@ -1335,53 +1338,65 @@ private static DkmClrCompilationResultFlags GetLocalResultFlags(LocalSymbol loca { // Display class instance. The display class fields are variables. int n = 0; + foreach (var member in instance.Type.GetMembers()) { if (member.Kind != SymbolKind.Field) { continue; } + var field = (FieldSymbol)member; var fieldType = field.Type; - var fieldKind = GeneratedNames.GetKind(field.Name); - if (fieldKind == GeneratedNameKind.DisplayClassLocalOrField || - fieldKind == GeneratedNameKind.TransparentIdentifier || - IsTransparentIdentifierFieldInAnonymousType(field) || - (fieldKind == GeneratedNameKind.ThisProxyField && GeneratedNames.GetKind(fieldType.Name) == GeneratedNameKind.LambdaDisplayClass)) // Async lambda case. + var fieldName = field.Name; + TryParseGeneratedName(fieldName, out var fieldKind, out var part); + + switch (fieldKind) { - Debug.Assert(!field.IsStatic); - // A local that is itself a display class instance. - if (displayClassTypes.Add((NamedTypeSymbol)fieldType)) - { - var other = instance.FromField(field); - displayClassInstances.Add(other); - n++; - } + case GeneratedNameKind.DisplayClassLocalOrField: + case GeneratedNameKind.TransparentIdentifier: + break; + case GeneratedNameKind.AnonymousTypeField: + if (GeneratedNames.GetKind(part) != GeneratedNameKind.TransparentIdentifier) + { + continue; + } + break; + case GeneratedNameKind.ThisProxyField: + if (GeneratedNames.GetKind(fieldType.Name) != GeneratedNameKind.LambdaDisplayClass) + { + continue; + } + // Async lambda case. + break; + default: + continue; } - } - return n; - } - private static bool IsTransparentIdentifierFieldInAnonymousType(FieldSymbol field) - { - string fieldName = field.Name; + Debug.Assert(!field.IsStatic); - if (GeneratedNames.GetKind(fieldName) != GeneratedNameKind.AnonymousTypeField) - { - return false; - } - - GeneratedNameKind kind; - int openBracketOffset; - int closeBracketOffset; - if (!GeneratedNames.TryParseGeneratedName(fieldName, out kind, out openBracketOffset, out closeBracketOffset)) - { - return false; + // A hoisted local that is itself a display class instance. + if (displayClassTypes.Add((NamedTypeSymbol)field.Type)) + { + var other = instance.FromField(field); + displayClassInstances.Add(other); + n++; + } } - fieldName = fieldName.Substring(openBracketOffset + 1, closeBracketOffset - openBracketOffset - 1); + return n; + } - return GeneratedNames.GetKind(fieldName) == GeneratedNameKind.TransparentIdentifier; + /// + /// Returns true if the parameter is a synthesized parameter representing + /// a display class instance (used to pass hoisted symbols to local functions). + /// + private static bool IsDisplayClassParameter(ParameterSymbol parameter) + { + var type = parameter.Type; + var result = type.Kind == SymbolKind.NamedType && IsDisplayClassType((NamedTypeSymbol)type); + Debug.Assert(!result || parameter.MetadataName == ""); + return result; } private static void GetDisplayClassVariables( @@ -1407,16 +1422,13 @@ private static bool IsTransparentIdentifierFieldInAnonymousType(FieldSymbol fiel DisplayClassVariableKind variableKind; string variableName; - GeneratedNameKind fieldKind; - int openBracketOffset; - int closeBracketOffset; - GeneratedNames.TryParseGeneratedName(fieldName, out fieldKind, out openBracketOffset, out closeBracketOffset); + TryParseGeneratedName(fieldName, out var fieldKind, out var part); switch (fieldKind) { case GeneratedNameKind.AnonymousTypeField: Debug.Assert(fieldName == field.Name); // This only happens once. - fieldName = fieldName.Substring(openBracketOffset + 1, closeBracketOffset - openBracketOffset - 1); + fieldName = part; goto REPARSE; case GeneratedNameKind.TransparentIdentifier: // A transparent identifier (field) in an anonymous type synthesized for a transparent identifier. @@ -1435,13 +1447,13 @@ private static bool IsTransparentIdentifierFieldInAnonymousType(FieldSymbol fiel continue; } - variableName = fieldName.Substring(openBracketOffset + 1, closeBracketOffset - openBracketOffset - 1); + variableName = part; variableKind = DisplayClassVariableKind.Local; Debug.Assert(!field.IsStatic); break; case GeneratedNameKind.ThisProxyField: // A reference to "this". - variableName = fieldName; + variableName = ""; // Should not be referenced by name. variableKind = DisplayClassVariableKind.This; Debug.Assert(!field.IsStatic); break; @@ -1487,6 +1499,22 @@ private static bool IsTransparentIdentifierFieldInAnonymousType(FieldSymbol fiel } } + private static bool TryParseGeneratedName(string name, out GeneratedNameKind kind, out string part) + { + bool result = GeneratedNames.TryParseGeneratedName(name, out kind, out int openBracketOffset, out int closeBracketOffset); + switch (kind) + { + case GeneratedNameKind.AnonymousTypeField: + case GeneratedNameKind.HoistedLocalField: + part = name.Substring(openBracketOffset + 1, closeBracketOffset - openBracketOffset - 1); + break; + default: + part = null; + break; + } + return result; + } + private static bool IsDisplayClassType(NamedTypeSymbol type) { switch (GeneratedNames.GetKind(type.Name)) @@ -1499,6 +1527,11 @@ private static bool IsDisplayClassType(NamedTypeSymbol type) } } + internal static DisplayClassVariable GetThisProxy(ImmutableDictionary displayClassVariables) + { + return displayClassVariables.Values.FirstOrDefault(v => v.Kind == DisplayClassVariableKind.This); + } + private static NamedTypeSymbol GetNonDisplayClassContainer(NamedTypeSymbol type) { // 1) Display class and state machine types are always nested within the types diff --git a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Rewriters/CapturedVariableRewriter.cs b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Rewriters/CapturedVariableRewriter.cs index 5b7c5391e86e4e1a1ca909e5d46c41e056e05db9..c700baae15971ced08c25dd9f6778fb4bd719692 100644 --- a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Rewriters/CapturedVariableRewriter.cs +++ b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Rewriters/CapturedVariableRewriter.cs @@ -1,6 +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. -using Microsoft.CodeAnalysis.CSharp.Symbols; using Roslyn.Utilities; using System.Collections.Generic; using System.Collections.Immutable; @@ -12,28 +11,28 @@ namespace Microsoft.CodeAnalysis.CSharp.ExpressionEvaluator internal sealed class CapturedVariableRewriter : BoundTreeRewriterWithStackGuardWithoutRecursionOnTheLeftOfBinaryOperator { internal static BoundNode Rewrite( - ParameterSymbol targetMethodThisParameter, + GenerateThisReference getThisReference, Conversions conversions, ImmutableDictionary displayClassVariables, BoundNode node, DiagnosticBag diagnostics) { - var rewriter = new CapturedVariableRewriter(targetMethodThisParameter, conversions, displayClassVariables, diagnostics); + var rewriter = new CapturedVariableRewriter(getThisReference, conversions, displayClassVariables, diagnostics); return rewriter.Visit(node); } - private readonly ParameterSymbol _targetMethodThisParameter; + private readonly GenerateThisReference _getThisReference; private readonly Conversions _conversions; private readonly ImmutableDictionary _displayClassVariables; private readonly DiagnosticBag _diagnostics; private CapturedVariableRewriter( - ParameterSymbol targetMethodThisParameter, + GenerateThisReference getThisReference, Conversions conversions, ImmutableDictionary displayClassVariables, DiagnosticBag diagnostics) { - _targetMethodThisParameter = targetMethodThisParameter; + _getThisReference = getThisReference; _conversions = conversions; _displayClassVariables = displayClassVariables; _diagnostics = diagnostics; @@ -65,7 +64,13 @@ public override BoundNode VisitLocal(BoundLocal node) public override BoundNode VisitParameter(BoundParameter node) { - return RewriteParameter(node.Syntax, node.ParameterSymbol, node); + var parameter = node.ParameterSymbol; + var variable = this.GetVariable(parameter.Name); + if (variable == null) + { + return node; + } + return variable.ToBoundExpression(node.Syntax); } public override BoundNode VisitMethodGroup(BoundMethodGroup node) @@ -75,24 +80,25 @@ public override BoundNode VisitMethodGroup(BoundMethodGroup node) public override BoundNode VisitThisReference(BoundThisReference node) { - return RewriteParameter(node.Syntax, _targetMethodThisParameter, node); + var rewrittenThis = GenerateThisReference(node); + Debug.Assert(rewrittenThis.Type.Equals(node.Type, TypeCompareKind.IgnoreDynamicAndTupleNames)); + return rewrittenThis; } public override BoundNode VisitBaseReference(BoundBaseReference node) { var syntax = node.Syntax; - var rewrittenParameter = RewriteParameter(syntax, _targetMethodThisParameter, node); - + var rewrittenThis = GenerateThisReference(node); var baseType = node.Type; HashSet unusedUseSiteDiagnostics = null; - var conversion = _conversions.ClassifyImplicitConversionFromExpression(rewrittenParameter, baseType, ref unusedUseSiteDiagnostics); + var conversion = _conversions.ClassifyImplicitConversionFromExpression(rewrittenThis, baseType, ref unusedUseSiteDiagnostics); Debug.Assert(unusedUseSiteDiagnostics == null || !conversion.IsValid || unusedUseSiteDiagnostics.All(d => d.Severity < DiagnosticSeverity.Error)); // It would be nice if we could just call BoundConversion.Synthesized, but it doesn't seem worthwhile to // introduce a bunch of new overloads to accommodate isBaseConversion. return new BoundConversion( syntax, - rewrittenParameter, + rewrittenThis, conversion, isBaseConversion: true, @checked: false, @@ -103,50 +109,21 @@ public override BoundNode VisitBaseReference(BoundBaseReference node) { WasCompilerGenerated = true }; } - private BoundExpression RewriteParameter(SyntaxNode syntax, ParameterSymbol symbol, BoundExpression node) + private BoundExpression GenerateThisReference(BoundExpression node) { - // This can happen in error scenarios (e.g. user binds "this" in a lambda in a static method). - if ((object)symbol == null) - { - ReportMissingThis(node.Kind, syntax); - return node; - } - - var variable = this.GetVariable(symbol.Name); - if (variable == null) + var syntax = node.Syntax; + var rewrittenThis = _getThisReference(syntax); + if (rewrittenThis != null) { - var typeNameKind = GeneratedNames.GetKind(symbol.Type.Name); - if (typeNameKind != GeneratedNameKind.None && - typeNameKind != GeneratedNameKind.AnonymousType) - { - // The state machine case is for async lambdas. The state machine - // will have a hoisted "this" field if it needs to access the - // containing display class, but the display class may not have a - // "this" field. - Debug.Assert(typeNameKind == GeneratedNameKind.LambdaDisplayClass || - typeNameKind == GeneratedNameKind.StateMachineType, - $"Unexpected typeNameKind '{typeNameKind}'"); - ReportMissingThis(node.Kind, syntax); - return node; - } - - return (node as BoundParameter) ?? new BoundParameter(syntax, symbol); + return rewrittenThis; } - - var result = variable.ToBoundExpression(syntax); - Debug.Assert(node.Kind == BoundKind.BaseReference - ? result.Type.BaseType.Equals(node.Type, TypeCompareKind.IgnoreDynamicAndTupleNames) - : result.Type.Equals(node.Type, TypeCompareKind.IgnoreDynamicAndTupleNames)); - return result; - } - - private void ReportMissingThis(BoundKind boundKind, SyntaxNode syntax) - { + var boundKind = node.Kind; Debug.Assert(boundKind == BoundKind.ThisReference || boundKind == BoundKind.BaseReference); var errorCode = boundKind == BoundKind.BaseReference ? ErrorCode.ERR_BaseInBadContext : ErrorCode.ERR_ThisInBadContext; _diagnostics.Add(new CSDiagnostic(new CSDiagnosticInfo(errorCode), syntax.Location)); + return node; } private DisplayClassVariable GetVariable(string name) diff --git a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/DisplayClassInstance.cs b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/DisplayClassInstance.cs index b310a8c6ebdbea27e5c49d640c2da1498c702673..3eb480da949166fca65af1ea5a19a3aab0ea6737 100644 --- a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/DisplayClassInstance.cs +++ b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/DisplayClassInstance.cs @@ -1,8 +1,8 @@ // 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.Symbols; -using System.Diagnostics; using System; +using System.Diagnostics; namespace Microsoft.CodeAnalysis.CSharp.ExpressionEvaluator { @@ -60,6 +60,8 @@ internal DisplayClassInstanceFromParameter(ParameterSymbol parameter) { Debug.Assert((object)parameter != null); Debug.Assert(parameter.Name.EndsWith("this", StringComparison.Ordinal) || + parameter.Name.Equals("", StringComparison.Ordinal) || // unnamed + parameter.Name.Equals("value", StringComparison.Ordinal) || // display class instance passed to local function as parameter GeneratedNames.GetKind(parameter.Name) == GeneratedNameKind.TransparentIdentifier); this.Parameter = parameter; } diff --git a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/EEMethodSymbol.cs b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/EEMethodSymbol.cs index ec49ebc52855537a07086a7c3aeb33a6590118de..c54d367c413dc9a412a64bcd543da5b11346139d 100644 --- a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/EEMethodSymbol.cs +++ b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/EEMethodSymbol.cs @@ -13,6 +13,8 @@ namespace Microsoft.CodeAnalysis.CSharp.ExpressionEvaluator { + internal delegate BoundExpression GenerateThisReference(SyntaxNode syntax); + internal delegate BoundStatement GenerateMethodBody( EEMethodSymbol method, DiagnosticBag diagnostics, @@ -576,7 +578,7 @@ internal override void GenerateMethodBody(TypeCompilationState compilationState, // Rewrite "this" and "base" references to parameter in this method. // Rewrite variables within body to reference existing display classes. body = (BoundStatement)CapturedVariableRewriter.Rewrite( - this.SubstitutedSourceMethod.IsStatic ? null : _parameters[0], + this.GenerateThisReference, compilation.Conversions, _displayClassVariables, body, @@ -647,6 +649,28 @@ internal override void GenerateMethodBody(TypeCompilationState compilationState, } } + private BoundExpression GenerateThisReference(SyntaxNode syntax) + { + var thisProxy = CompilationContext.GetThisProxy(_displayClassVariables); + if (thisProxy != null) + { + return thisProxy.ToBoundExpression(syntax); + } + if ((object)_thisParameter != null) + { + var typeNameKind = GeneratedNames.GetKind(_thisParameter.Type.Name); + if (typeNameKind != GeneratedNameKind.None && typeNameKind != GeneratedNameKind.AnonymousType) + { + Debug.Assert(typeNameKind == GeneratedNameKind.LambdaDisplayClass || + typeNameKind == GeneratedNameKind.StateMachineType, + $"Unexpected typeNameKind '{typeNameKind}'"); + return null; + } + return new BoundParameter(syntax, _thisParameter); + } + return null; + } + private static TypeSymbol CalculateReturnType(CSharpCompilation compilation, BoundStatement bodyOpt) { if (bodyOpt == null) diff --git a/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/CSharpExpressionCompilerTest.csproj b/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/CSharpExpressionCompilerTest.csproj index 40185c2a2a4348fc11a22654fb6209c44da54874..2d86318c92ea8e885c0777755a2a2e6f92d178f5 100644 --- a/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/CSharpExpressionCompilerTest.csproj +++ b/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/CSharpExpressionCompilerTest.csproj @@ -82,6 +82,7 @@ + diff --git a/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/LocalFunctionTests.cs b/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/LocalFunctionTests.cs new file mode 100644 index 0000000000000000000000000000000000000000..b9d1cbc0d5bfa68766b984153c29917019d6cf15 --- /dev/null +++ b/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/LocalFunctionTests.cs @@ -0,0 +1,298 @@ +// 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.CodeGen; +using Microsoft.CodeAnalysis.CSharp.Test.Utilities; +using Microsoft.CodeAnalysis.ExpressionEvaluator; +using Microsoft.CodeAnalysis.ExpressionEvaluator.UnitTests; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.CSharp.ExpressionEvaluator.UnitTests +{ + public class LocalFunctionTests : ExpressionCompilerTestBase + { + [Fact] + public void NoLocals() + { + var source = +@"class C +{ + void F(int x) + { + int y = x + 1; + int G() + { + return 0; + }; + int z = G(); + } +}"; + var compilation0 = CreateCompilationWithMscorlib(source, options: TestOptions.DebugDll); + WithRuntimeInstance(compilation0, runtime => + { + var context = CreateMethodContext(runtime, "C.g__G0_0"); + var testData = new CompilationTestData(); + var locals = ArrayBuilder.GetInstance(); + string typeName; + var assembly = context.CompileGetLocals(locals, argumentsOnly: false, typeName: out typeName, testData: testData); + Assert.NotNull(assembly); + Assert.Equal(assembly.Count, 0); + Assert.Equal(locals.Count, 0); + locals.Free(); + }); + } + + [Fact] + public void Locals() + { + var source = +@"class C +{ + void F(int x) + { + int G(int y) + { + int z = y + 1; + return z; + }; + G(x + 1); + } +}"; + var compilation0 = CreateCompilationWithMscorlib(source, options: TestOptions.DebugDll); + WithRuntimeInstance(compilation0, runtime => + { + var context = CreateMethodContext(runtime, "C.g__G0_0"); + var testData = new CompilationTestData(); + var locals = ArrayBuilder.GetInstance(); + string typeName; + var assembly = context.CompileGetLocals(locals, argumentsOnly: false, typeName: out typeName, testData: testData); + Assert.Equal(locals.Count, 2); + VerifyLocal(testData, typeName, locals[0], "<>m0", "y", expectedILOpt: +@"{ + // Code size 2 (0x2) + .maxstack 1 + .locals init (int V_0, //z + int V_1) + IL_0000: ldarg.0 + IL_0001: ret +}"); + VerifyLocal(testData, typeName, locals[1], "<>m1", "z", expectedILOpt: +@"{ + // Code size 2 (0x2) + .maxstack 1 + .locals init (int V_0, //z + int V_1) + IL_0000: ldloc.0 + IL_0001: ret +}"); + locals.Free(); + string error; + context.CompileExpression("this.F(1)", out error, testData); + Assert.Equal(error, "error CS0027: Keyword 'this' is not available in the current context"); + }); + } + + [Fact] + public void CapturedVariable() + { + var source = +@"class C +{ + int x; + void F(int y) + { + int G() + { + return x + y; + }; + int z = G(); + } +}"; + var compilation0 = CreateCompilationWithMscorlib(source, options: TestOptions.DebugDll); + WithRuntimeInstance(compilation0, runtime => + { + var context = CreateMethodContext(runtime, "C.g__G1_0"); + var testData = new CompilationTestData(); + var locals = ArrayBuilder.GetInstance(); + string typeName; + var assembly = context.CompileGetLocals(locals, argumentsOnly: false, typeName: out typeName, testData: testData); + Assert.Equal(locals.Count, 2); + VerifyLocal(testData, typeName, locals[0], "<>m0", "this", expectedILOpt: +@"{ + // Code size 7 (0x7) + .maxstack 1 + .locals init (int V_0) + IL_0000: ldarg.0 + IL_0001: ldfld ""C C.<>c__DisplayClass1_0.<>4__this"" + IL_0006: ret +}"); + VerifyLocal(testData, typeName, locals[1], "<>m1", "y", expectedILOpt: +@"{ + // Code size 7 (0x7) + .maxstack 1 + .locals init (int V_0) + IL_0000: ldarg.0 + IL_0001: ldfld ""int C.<>c__DisplayClass1_0.y"" + IL_0006: ret +}"); + locals.Free(); + testData = new CompilationTestData(); + string error; + context.CompileExpression("this.F(1)", out error, testData); + Assert.Null(error); + testData.GetMethodData("<>x.<>m0").VerifyIL( + @"{ + // Code size 13 (0xd) + .maxstack 2 + .locals init (int V_0) + IL_0000: ldarg.0 + IL_0001: ldfld ""C C.<>c__DisplayClass1_0.<>4__this"" + IL_0006: ldc.i4.1 + IL_0007: callvirt ""void C.F(int)"" + IL_000c: ret +}"); + }); + } + + [Fact] + public void MultipleDisplayClasses() + { + var source = +@"class C +{ + void F1(int x) + { + int F2(int y) + { + int F3() => x + y; + return F3(); + }; + F2(1); + } +}"; + var compilation0 = CreateCompilationWithMscorlib(source, options: TestOptions.DebugDll); + WithRuntimeInstance(compilation0, runtime => + { + var context = CreateMethodContext(runtime, "C.g__F30_1"); + var testData = new CompilationTestData(); + var locals = ArrayBuilder.GetInstance(); + string typeName; + var assembly = context.CompileGetLocals(locals, argumentsOnly: false, typeName: out typeName, testData: testData); + Assert.Equal(locals.Count, 2); + VerifyLocal(testData, typeName, locals[0], "<>m0", "x", expectedILOpt: +@"{ + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: ldfld ""int C.<>c__DisplayClass0_0.x"" + IL_0006: ret +}"); + VerifyLocal(testData, typeName, locals[1], "<>m1", "y", expectedILOpt: +@"{ + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.1 + IL_0001: ldfld ""int C.<>c__DisplayClass0_1.y"" + IL_0006: ret +}"); + locals.Free(); + testData = new CompilationTestData(); + string error; + context.CompileExpression("x + y", out error, testData); + Assert.Null(error); + testData.GetMethodData("<>x.<>m0").VerifyIL( + @"{ + // Code size 14 (0xe) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldfld ""int C.<>c__DisplayClass0_0.x"" + IL_0006: ldarg.1 + IL_0007: ldfld ""int C.<>c__DisplayClass0_1.y"" + IL_000c: add + IL_000d: ret +}"); + }); + } + + // Should not bind to unnamed display class parameters + // (unnamed parameters are treated as named "value"). + [Fact] + public void CapturedVariableNamedValue() + { + var source = +@"class C +{ + void F(int value) + { + int G() + { + return value + 1; + }; + G(); + } +}"; + var compilation0 = CreateCompilationWithMscorlib(source, options: TestOptions.DebugDll); + WithRuntimeInstance(compilation0, runtime => + { + var context = CreateMethodContext(runtime, "C.g__G0_0"); + var testData = new CompilationTestData(); + var locals = ArrayBuilder.GetInstance(); + string typeName; + var assembly = context.CompileGetLocals(locals, argumentsOnly: false, typeName: out typeName, testData: testData); + Assert.Equal(locals.Count, 1); + VerifyLocal(testData, typeName, locals[0], "<>m0", "value", expectedILOpt: +@"{ + // Code size 7 (0x7) + .maxstack 1 + .locals init (int V_0) + IL_0000: ldarg.0 + IL_0001: ldfld ""int C.<>c__DisplayClass0_0.value"" + IL_0006: ret +}"); + locals.Free(); + testData = new CompilationTestData(); + string error; + context.CompileExpression("value", out error, testData); + Assert.Null(error); + testData.GetMethodData("<>x.<>m0").VerifyIL( + @"{ + // Code size 7 (0x7) + .maxstack 1 + .locals init (int V_0) + IL_0000: ldarg.0 + IL_0001: ldfld ""int C.<>c__DisplayClass0_0.value"" + IL_0006: ret +}"); + }); + } + + // Should not bind to unnamed display class parameters + // (unnamed parameters are treated as named "value"). + [WorkItem(18426, "https://github.com/dotnet/roslyn/issues/18426")] + [Fact(Skip = "18426")] + public void DisplayClassParameter() + { + var source = +@"class C +{ + void F(int x) + { + int G() + { + return x + 1; + }; + G(); + } +}"; + var compilation0 = CreateCompilationWithMscorlib(source, options: TestOptions.DebugDll); + WithRuntimeInstance(compilation0, runtime => + { + var context = CreateMethodContext(runtime, "C.g__G0_0"); + var testData = new CompilationTestData(); + string error; + context.CompileExpression("value", out error, testData); + Assert.Equal(error, "error CS0103: The name 'value' does not exist in the current context"); + }); + } + } +} \ No newline at end of file diff --git a/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/LocalsTests.cs b/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/LocalsTests.cs index 298a85f1f726b73ab17be939c5d49f090118f194..2b274edb219e948f3d7a90e00d220a7aa7c64c70 100644 --- a/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/LocalsTests.cs +++ b/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/LocalsTests.cs @@ -41,6 +41,7 @@ static void M() Assert.NotNull(assembly); Assert.Equal(assembly.Count, 0); Assert.Equal(locals.Count, 0); + locals.Free(); }); }