提交 e887a8ab 编写于 作者: A Andy Gocke 提交者: GitHub

Remove LocalFunctionRewriting pass (#21408)


Perform synthesis of closure methods in an early pass before visitation,
meaning we no longer need to do a second visitation of the tree to lower
local functions.

The baselines have been changed because we now do closure id and
synthessis in order of closure visitation, rather than bound node
visitation. Closure visitation visits all the closures in a given scope,
then recurs into nested scopes, while BoundNode visitation treats
closures as scopes themselves, so nested closures are visited before
closures declared in the same scope.

Fixes https://github.com/dotnet/roslyn/projects/26#card-3753331
上级 0369561c
......@@ -660,7 +660,7 @@ private void CompileSynthesizedMethods(TypeCompilationState compilationState)
var diagnosticsThisMethod = DiagnosticBag.GetInstance();
var method = methodWithBody.Method;
var lambda = method as SynthesizedLambdaMethod;
var lambda = method as SynthesizedClosureMethod;
var variableSlotAllocatorOpt = ((object)lambda != null) ?
_moduleBeingBuiltOpt.TryCreateVariableSlotAllocator(lambda, lambda.TopLevelMethod, diagnosticsThisMethod) :
_moduleBeingBuiltOpt.TryCreateVariableSlotAllocator(method, method, diagnosticsThisMethod);
......
......@@ -112,6 +112,11 @@ public sealed class Closure
/// </summary>
public readonly MethodSymbol OriginalMethodSymbol;
/// <summary>
/// Syntax for the block of the nested function.
/// </summary>
public readonly SyntaxReference BlockSyntax;
public readonly PooledHashSet<Symbol> CapturedVariables = PooledHashSet<Symbol>.GetInstance();
public readonly ArrayBuilder<ClosureEnvironment> CapturedEnvironments
......@@ -136,10 +141,13 @@ public bool CapturesThis
}
}
public Closure(MethodSymbol symbol)
public SynthesizedClosureMethod SynthesizedLoweredMethod;
public Closure(MethodSymbol symbol, SyntaxReference blockSyntax)
{
Debug.Assert(symbol != null);
OriginalMethodSymbol = symbol;
BlockSyntax = blockSyntax;
}
public void Free()
......@@ -154,9 +162,8 @@ public sealed class ClosureEnvironment
public readonly SetWithInsertionOrder<Symbol> CapturedVariables;
/// <summary>
/// Represents a <see cref="SynthesizedEnvironment"/> that had its environment
/// pointer (a local pointing to the environment) captured like a captured
/// variable. Assigned in
/// True if this environment captures a reference to a class environment
/// declared in a higher scope. Assigned by
/// <see cref="ComputeLambdaScopesAndFrameCaptures(ParameterSymbol)"/>
/// </summary>
public bool CapturesParent;
......@@ -435,7 +442,7 @@ private BoundNode VisitClosure(MethodSymbol closureSymbol, BoundBlock body)
// Closure is declared (lives) in the parent scope, but its
// variables are in a nested scope
var closure = new Closure(closureSymbol);
var closure = new Closure(closureSymbol, body.Syntax.GetReference());
_currentScope.Closures.Add(closure);
var oldClosure = _currentClosure;
......
// 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 Microsoft.CodeAnalysis.PooledObjects;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
namespace Microsoft.CodeAnalysis.CSharp
{
internal sealed partial class LambdaRewriter : MethodToClassRewriter
{
/// <summary>
/// This pass is expected to run on partially lowered methods
/// previously containing one or more local functions. At this
/// point all local functions should have been rewritten into
/// proper closure classes and have frames and proxies generated
/// for them.
///
/// The only thing left is to visit all "references" to local functions
/// and rewrite them to be references to the rewritten form.
/// </summary>
private sealed class LocalFunctionReferenceRewriter : BoundTreeRewriterWithStackGuard
{
private readonly LambdaRewriter _lambdaRewriter;
public LocalFunctionReferenceRewriter(LambdaRewriter lambdaRewriter)
{
_lambdaRewriter = lambdaRewriter;
}
public override BoundNode Visit(BoundNode node)
{
var partiallyLowered = node as PartiallyLoweredLocalFunctionReference;
if (partiallyLowered != null)
{
var underlying = partiallyLowered.UnderlyingNode;
Debug.Assert(underlying.Kind == BoundKind.Call ||
underlying.Kind == BoundKind.DelegateCreationExpression ||
underlying.Kind == BoundKind.Conversion);
var oldProxies = _lambdaRewriter.proxies;
_lambdaRewriter.proxies = partiallyLowered.Proxies;
var result = base.Visit(underlying);
_lambdaRewriter.proxies = oldProxies;
return result;
}
return base.Visit(node);
}
public override BoundNode VisitCall(BoundCall node)
{
if (node.Method.MethodKind == MethodKind.LocalFunction)
{
BoundExpression receiver;
MethodSymbol method;
var arguments = node.Arguments;
_lambdaRewriter.RemapLocalFunction(
node.Syntax,
node.Method,
out receiver,
out method,
ref arguments);
node = node.Update(receiver, method, arguments);
}
return base.VisitCall(node);
}
public override BoundNode VisitDelegateCreationExpression(BoundDelegateCreationExpression node)
{
if (node.MethodOpt?.MethodKind == MethodKind.LocalFunction)
{
BoundExpression receiver;
MethodSymbol method;
var arguments = default(ImmutableArray<BoundExpression>);
_lambdaRewriter.RemapLocalFunction(
node.Syntax,
node.MethodOpt,
out receiver,
out method,
ref arguments);
return new BoundDelegateCreationExpression(
node.Syntax, receiver, method, isExtensionMethod: false, type: node.Type);
}
return base.VisitDelegateCreationExpression(node);
}
}
/// <summary>
/// Visit all references to local functions (calls, delegate
/// conversions, delegate creations) and rewrite them to point
/// to the rewritten local function method instead of the original.
/// </summary>
public BoundStatement RewriteLocalFunctionReferences(BoundStatement loweredBody)
{
var rewriter = new LocalFunctionReferenceRewriter(this);
Debug.Assert(_currentMethod == _topLevelMethod);
// Visit the body first since the state is already set
// for the top-level method
var newBody = (BoundStatement)rewriter.Visit(loweredBody);
// Visit all the rewritten methods as well
var synthesizedMethods = _synthesizedMethods;
if (synthesizedMethods != null)
{
var newMethods = ArrayBuilder<TypeCompilationState.MethodWithBody>.GetInstance(
synthesizedMethods.Count);
foreach (var oldMethod in synthesizedMethods)
{
var synthesizedLambda = oldMethod.Method as SynthesizedLambdaMethod;
if (synthesizedLambda == null)
{
// The only methods synthesized by the rewriter should
// be lowered closures and frame constructors
Debug.Assert(oldMethod.Method.MethodKind == MethodKind.Constructor ||
oldMethod.Method.MethodKind == MethodKind.StaticConstructor);
newMethods.Add(oldMethod);
continue;
}
_currentMethod = synthesizedLambda;
var closureKind = synthesizedLambda.ClosureKind;
if (closureKind == ClosureKind.Static || closureKind == ClosureKind.Singleton)
{
// no link from a static lambda to its container
_innermostFramePointer = _currentFrameThis = null;
}
else
{
_currentFrameThis = synthesizedLambda.ThisParameter;
_innermostFramePointer = null;
_framePointers.TryGetValue(synthesizedLambda.ContainingType, out _innermostFramePointer);
}
var containerAsFrame = synthesizedLambda.ContainingType as SynthesizedClosureEnvironment;
// Includes type parameters from the containing type iff
// the containing type is a frame. If it is a frame then
// the type parameters are captured, meaning that the
// type parameters should be included.
// If it is not a frame then the local function is being
// directly lowered into the method's containing type and
// the parameters should never be substituted.
_currentTypeParameters = containerAsFrame?.TypeParameters.Concat(synthesizedLambda.TypeParameters)
?? synthesizedLambda.TypeParameters;
_currentLambdaBodyTypeMap = synthesizedLambda.TypeMap;
var rewrittenBody = (BoundStatement)rewriter.Visit(oldMethod.Body);
var newMethod = new TypeCompilationState.MethodWithBody(
synthesizedLambda, rewrittenBody, oldMethod.ImportChainOpt);
newMethods.Add(newMethod);
}
_synthesizedMethods = newMethods;
synthesizedMethods.Free();
}
return newBody;
}
/// <summary>
/// Rewrites a reference to an unlowered local function to the newly
/// lowered local function.
/// </summary>
private void RemapLocalFunction(
SyntaxNode syntax,
MethodSymbol localFunc,
out BoundExpression receiver,
out MethodSymbol method,
ref ImmutableArray<BoundExpression> parameters)
{
Debug.Assert(localFunc.MethodKind == MethodKind.LocalFunction);
var mappedLocalFunction = _localFunctionMap[(LocalFunctionSymbol)localFunc.OriginalDefinition];
var loweredSymbol = mappedLocalFunction.Symbol;
// If the local function captured variables then they will be stored
// in frames and the frames need to be passed as extra parameters.
var frameCount = loweredSymbol.ExtraSynthesizedParameterCount;
if (frameCount != 0)
{
Debug.Assert(!parameters.IsDefault);
// Build a new list of parameters to pass to the local function
// call that includes any necessary capture frames
var parametersBuilder = ArrayBuilder<BoundExpression>.GetInstance();
parametersBuilder.AddRange(parameters);
var start = loweredSymbol.ParameterCount - frameCount;
for (int i = start; i < loweredSymbol.ParameterCount; i++)
{
// will always be a LambdaFrame, it's always a capture frame
var frameType = (NamedTypeSymbol)loweredSymbol.Parameters[i].Type.OriginalDefinition;
Debug.Assert(frameType is SynthesizedClosureEnvironment);
if (frameType.Arity > 0)
{
var typeParameters = ((SynthesizedClosureEnvironment)frameType).ConstructedFromTypeParameters;
Debug.Assert(typeParameters.Length == frameType.Arity);
var subst = this.TypeMap.SubstituteTypeParameters(typeParameters);
frameType = frameType.Construct(subst);
}
var frame = FrameOfType(syntax, frameType);
parametersBuilder.Add(frame);
}
parameters = parametersBuilder.ToImmutableAndFree();
}
method = loweredSymbol;
NamedTypeSymbol constructedFrame;
RemapLambdaOrLocalFunction(syntax,
localFunc,
SubstituteTypeArguments(localFunc.TypeArguments),
mappedLocalFunction.ClosureKind,
ref method,
out receiver,
out constructedFrame);
}
/// <summary>
/// Substitutes references from old type arguments to new type arguments
/// in the lowered methods.
/// </summary>
/// <example>
/// Consider the following method:
/// void M() {
/// void L&lt;T&gt;(T t) => Console.Write(t);
/// L("A");
/// }
///
/// In this example, L&lt;T&gt; is a local function that will be
/// lowered into its own method and the type parameter T will be
/// alpha renamed to something else (let's call it T'). In this case,
/// all references to the original type parameter T in L must be
/// rewritten to the renamed parameter, T'.
/// </example>
private ImmutableArray<TypeSymbol> SubstituteTypeArguments(ImmutableArray<TypeSymbol> typeArguments)
{
Debug.Assert(!typeArguments.IsDefault);
if (typeArguments.IsEmpty)
{
return typeArguments;
}
// We must perform this process repeatedly as local
// functions may nest inside one another and capture type
// parameters from the enclosing local functions. Each
// iteration of nesting will cause alpha-renaming of the captured
// parameters, meaning that we must replace until there are no
// more alpha-rename mappings.
//
// The method symbol references are different from all other
// substituted types in this context because the method symbol in
// local function references is not rewritten until all local
// functions have already been lowered. Everything else is rewritten
// by the visitors as the definition is lowered. This means that
// only one substition happens per lowering, but we need to do
// N substitutions all at once, where N is the number of lowerings.
var builder = ArrayBuilder<TypeSymbol>.GetInstance();
foreach (var typeArg in typeArguments)
{
TypeSymbol oldTypeArg;
TypeSymbol newTypeArg = typeArg;
do
{
oldTypeArg = newTypeArg;
newTypeArg = this.TypeMap.SubstituteType(typeArg).Type;
}
while (oldTypeArg != newTypeArg);
Debug.Assert((object)oldTypeArg == newTypeArg);
builder.Add(newTypeArg);
}
return builder.ToImmutableAndFree();
}
}
}
using System;
using System.Collections.Generic;
using Microsoft.CodeAnalysis.Semantics;
namespace Microsoft.CodeAnalysis.CSharp
{
/// <summary>
/// This represents a partially lowered local function reference (e.g.,
/// a local function call or delegate conversion) with relevant proxies
/// attached. It will later be rewritten by the
/// <see cref="LambdaRewriter.LocalFunctionReferenceRewriter"/> into a
/// proper call.
/// </summary>
internal class PartiallyLoweredLocalFunctionReference : BoundExpression
{
private const BoundKind s_privateKind = (BoundKind)byte.MaxValue;
public BoundExpression UnderlyingNode { get; }
public Dictionary<Symbol, CapturedSymbolReplacement> Proxies { get; }
public PartiallyLoweredLocalFunctionReference(
BoundExpression underlying,
Dictionary<Symbol, CapturedSymbolReplacement> proxies)
: base(s_privateKind, underlying.Syntax, underlying.Type)
{
UnderlyingNode = underlying;
Proxies = proxies;
}
public override BoundNode Accept(BoundTreeVisitor visitor) =>
visitor.Visit(this);
protected override OperationKind ExpressionKind
{
get
{
throw new InvalidOperationException();
}
}
public override void Accept(OperationVisitor visitor)
{
throw new InvalidOperationException();
}
public override TResult Accept<TArgument, TResult>(OperationVisitor<TArgument, TResult> visitor, TArgument argument)
{
throw new InvalidOperationException();
}
}
}
......@@ -12,51 +12,62 @@ namespace Microsoft.CodeAnalysis.CSharp
/// <summary>
/// A method that results from the translation of a single lambda expression.
/// </summary>
internal sealed class SynthesizedLambdaMethod : SynthesizedMethodBaseSymbol, ISynthesizedMethodBodyImplementationSymbol
internal sealed class SynthesizedClosureMethod : SynthesizedMethodBaseSymbol, ISynthesizedMethodBodyImplementationSymbol
{
private readonly MethodSymbol _topLevelMethod;
private readonly ImmutableArray<NamedTypeSymbol> _structEnvironments;
internal SynthesizedLambdaMethod(
internal readonly DebugId LambdaId;
internal SynthesizedClosureMethod(
NamedTypeSymbol containingType,
ImmutableArray<SynthesizedClosureEnvironment> structEnvironments,
ClosureKind closureKind,
MethodSymbol topLevelMethod,
DebugId topLevelMethodId,
IBoundLambdaOrFunction lambdaNode,
MethodSymbol originalMethod,
SyntaxReference blockSyntax,
DebugId lambdaId)
: base(containingType,
lambdaNode.Symbol,
originalMethod,
null,
lambdaNode.Syntax.SyntaxTree.GetReference(lambdaNode.Body.Syntax),
lambdaNode.Syntax.GetLocation(),
lambdaNode is BoundLocalFunctionStatement ?
MakeName(topLevelMethod.Name, lambdaNode.Symbol.Name, topLevelMethodId, closureKind, lambdaId) :
MakeName(topLevelMethod.Name, topLevelMethodId, closureKind, lambdaId),
(closureKind == ClosureKind.ThisOnly ? DeclarationModifiers.Private : DeclarationModifiers.Internal)
| (closureKind == ClosureKind.Static ? DeclarationModifiers.Static : 0)
| (lambdaNode.Symbol.IsAsync ? DeclarationModifiers.Async : 0))
blockSyntax,
originalMethod.DeclaringSyntaxReferences[0].GetLocation(),
originalMethod is LocalFunctionSymbol
? MakeName(topLevelMethod.Name, originalMethod.Name, topLevelMethodId, closureKind, lambdaId)
: MakeName(topLevelMethod.Name, topLevelMethodId, closureKind, lambdaId),
MakeDeclarationModifiers(closureKind, originalMethod))
{
_topLevelMethod = topLevelMethod;
ClosureKind = closureKind;
LambdaId = lambdaId;
TypeMap typeMap;
ImmutableArray<TypeParameterSymbol> typeParameters;
ImmutableArray<TypeParameterSymbol> constructedFromTypeParameters;
SynthesizedClosureEnvironment lambdaFrame;
lambdaFrame = this.ContainingType as SynthesizedClosureEnvironment;
var lambdaFrame = ContainingType as SynthesizedClosureEnvironment;
switch (closureKind)
{
case ClosureKind.Singleton: // all type parameters on method (except the top level method's)
case ClosureKind.General: // only lambda's type parameters on method (rest on class)
Debug.Assert(lambdaFrame != null);
typeMap = lambdaFrame.TypeMap.WithConcatAlphaRename(lambdaNode.Symbol, this, out typeParameters, out constructedFromTypeParameters, lambdaFrame.OriginalContainingMethodOpt);
typeMap = lambdaFrame.TypeMap.WithConcatAlphaRename(
originalMethod,
this,
out typeParameters,
out constructedFromTypeParameters,
lambdaFrame.OriginalContainingMethodOpt);
break;
case ClosureKind.ThisOnly: // all type parameters on method
case ClosureKind.Static:
Debug.Assert(lambdaFrame == null);
typeMap = TypeMap.Empty.WithConcatAlphaRename(lambdaNode.Symbol, this, out typeParameters, out constructedFromTypeParameters, null);
typeMap = TypeMap.Empty.WithConcatAlphaRename(
originalMethod,
this,
out typeParameters,
out constructedFromTypeParameters,
stopAt: null);
break;
default:
throw ExceptionUtilities.UnexpectedValue(closureKind);
......@@ -90,6 +101,23 @@ internal sealed class SynthesizedLambdaMethod : SynthesizedMethodBaseSymbol, ISy
AssignTypeMapAndTypeParameters(typeMap, typeParameters);
}
private static DeclarationModifiers MakeDeclarationModifiers(ClosureKind closureKind, MethodSymbol originalMethod)
{
var mods = closureKind == ClosureKind.ThisOnly ? DeclarationModifiers.Private : DeclarationModifiers.Internal;
if (closureKind == ClosureKind.Static)
{
mods |= DeclarationModifiers.Static;
}
if (originalMethod.IsAsync)
{
mods |= DeclarationModifiers.Async;
}
return mods;
}
private static string MakeName(string topLevelMethodName, string localFunctionName, DebugId topLevelMethodId, ClosureKind closureKind, DebugId lambdaId)
{
return GeneratedNames.MakeLocalFunctionName(
......
......@@ -4889,15 +4889,15 @@ .maxstack 3
IL_0018: ldc.i4.1
IL_0019: callvirt ""System.Func<int> System.Func<int, System.Func<int>>.Invoke(int)""
IL_001e: pop
IL_001f: ldsfld ""System.Func<int, System.Func<int>> Program.<>c.<>9__1_2""
IL_001f: ldsfld ""System.Func<int, System.Func<int>> Program.<>c.<>9__1_1""
IL_0024: dup
IL_0025: brtrue.s IL_003e
IL_0027: pop
IL_0028: ldsfld ""Program.<>c Program.<>c.<>9""
IL_002d: ldftn ""System.Func<int> Program.<>c.<Test>b__1_2(int)""
IL_002d: ldftn ""System.Func<int> Program.<>c.<Test>b__1_1(int)""
IL_0033: newobj ""System.Func<int, System.Func<int>>..ctor(object, System.IntPtr)""
IL_0038: dup
IL_0039: stsfld ""System.Func<int, System.Func<int>> Program.<>c.<>9__1_2""
IL_0039: stsfld ""System.Func<int, System.Func<int>> Program.<>c.<>9__1_1""
IL_003e: ldc.i4.1
IL_003f: callvirt ""System.Func<int> System.Func<int, System.Func<int>>.Invoke(int)""
IL_0044: pop
......@@ -4905,7 +4905,7 @@ .maxstack 3
}
");
verifier.VerifyIL("Program.<>c.<Test>b__1_2(int)",
verifier.VerifyIL("Program.<>c.<Test>b__1_1(int)",
@"
{
// Code size 44 (0x2c)
......@@ -4943,17 +4943,17 @@ .locals init (System.Func<int> V_0)
IL_0004: ldc.i4.s 123
IL_0006: call ""void System.Console.WriteLine(int)""
IL_000b: ldarg.0
IL_000c: ldfld ""System.Func<int> Program.<>c__DisplayClass1_0.<>9__1""
IL_000c: ldfld ""System.Func<int> Program.<>c__DisplayClass1_0.<>9__2""
IL_0011: dup
IL_0012: brtrue.s IL_002a
IL_0014: pop
IL_0015: ldarg.0
IL_0016: ldarg.0
IL_0017: ldftn ""int Program.<>c__DisplayClass1_0.<Test>b__1()""
IL_0017: ldftn ""int Program.<>c__DisplayClass1_0.<Test>b__2()""
IL_001d: newobj ""System.Func<int>..ctor(object, System.IntPtr)""
IL_0022: dup
IL_0023: stloc.0
IL_0024: stfld ""System.Func<int> Program.<>c__DisplayClass1_0.<>9__1""
IL_0024: stfld ""System.Func<int> Program.<>c__DisplayClass1_0.<>9__2""
IL_0029: ldloc.0
IL_002a: ret
IL_002b: ldnull
......
......@@ -274,23 +274,23 @@ .maxstack 2
.maxstack 2
IL_0000: ldarg.0
IL_0001: ldarg.1
IL_0002: call ""void C.<M>g__L32_2(ref C.<>c__DisplayClass2_0)""
IL_0002: call ""void C.<M>g__L32_3(ref C.<>c__DisplayClass2_0)""
IL_0007: ret
}");
// Skip some... L5
verifier.VerifyIL("C.<M>g__L52_4(ref C.<>c__DisplayClass2_0, ref C.<>c__DisplayClass2_1)", @"
verifier.VerifyIL("C.<M>g__L52_5(ref C.<>c__DisplayClass2_0, ref C.<>c__DisplayClass2_1)", @"
{
// Code size 10 (0xa)
.maxstack 3
IL_0000: ldarg.0
IL_0001: ldarg.1
IL_0002: ldarg.2
IL_0003: call ""int C.<M>g__L62_5(ref C.<>c__DisplayClass2_0, ref C.<>c__DisplayClass2_1)""
IL_0003: call ""int C.<M>g__L62_6(ref C.<>c__DisplayClass2_0, ref C.<>c__DisplayClass2_1)""
IL_0008: pop
IL_0009: ret
}");
// L6
verifier.VerifyIL("C.<M>g__L62_5(ref C.<>c__DisplayClass2_0, ref C.<>c__DisplayClass2_1)", @"
verifier.VerifyIL("C.<M>g__L62_6(ref C.<>c__DisplayClass2_0, ref C.<>c__DisplayClass2_1)", @"
{
// Code size 25 (0x19)
.maxstack 4
......
......@@ -549,10 +549,10 @@ void F()
// no new synthesized members generated (with #1 in names):
diff1.VerifySynthesizedMembers(
"C: {<F>b__1_2, <F>b__1_4, <>c__DisplayClass1_0, <>c__DisplayClass1_1, <>c}",
"C.<>c: {<>9__1_0, <>9__1_1, <>9__1_6, <F>b__1_0, <F>b__1_1, <F>b__1_6}",
"C.<>c__DisplayClass1_0: {<>h__TransparentIdentifier0, <F>b__3}",
"C.<>c__DisplayClass1_1: {<>h__TransparentIdentifier0, <F>b__5}",
"C.<>c__DisplayClass1_1: {<>h__TransparentIdentifier0, <F>b__6}",
"C.<>c__DisplayClass1_0: {<>h__TransparentIdentifier0, <F>b__5}",
"C.<>c: {<>9__1_0, <>9__1_1, <>9__1_4, <F>b__1_0, <F>b__1_1, <F>b__1_4}",
"C: {<F>b__1_2, <F>b__1_3, <>c__DisplayClass1_0, <>c__DisplayClass1_1, <>c}",
"<>f__AnonymousType0<<a>j__TPar, <b>j__TPar>: {Equals, GetHashCode, ToString}");
var md1 = diff1.GetMetadata();
......@@ -632,10 +632,10 @@ void F()
// no new synthesized members generated (with #1 in names):
diff1.VerifySynthesizedMembers(
"C: {<F>b__1_0, <>c__DisplayClass1_0, <>c}",
"C.<>c: {<>9__1_2, <F>b__1_2}",
"C.<>c__DisplayClass1_0: {a, <F>b__1}",
"<>f__AnonymousType0<<a>j__TPar, <b>j__TPar>: {Equals, GetHashCode, ToString}");
"C.<>c: {<>9__1_1, <F>b__1_1}",
"<>f__AnonymousType0<<a>j__TPar, <b>j__TPar>: {Equals, GetHashCode, ToString}",
"C.<>c__DisplayClass1_0: {a, <F>b__2}",
"C: {<F>b__1_0, <>c__DisplayClass1_0, <>c}");
var md1 = diff1.GetMetadata();
var reader1 = md1.Reader;
......@@ -714,10 +714,10 @@ public void F()
// no new synthesized members generated (with #1 in names):
diff1.VerifySynthesizedMembers(
"C.<>c__DisplayClass1_2: {a, b, <F>b__5}",
"C.<>c__DisplayClass1_1: {b, <F>b__3}",
"C: {<F>b__1_0, <F>b__1_2, <F>b__1_4, <>c__DisplayClass1_0, <>c__DisplayClass1_1, <>c__DisplayClass1_2}",
"C.<>c__DisplayClass1_0: {a, <F>b__1}");
"C.<>c__DisplayClass1_1: {b, <F>b__4}",
"C.<>c__DisplayClass1_2: {a, b, <F>b__5}",
"C.<>c__DisplayClass1_0: {a, <F>b__3}",
"C: {<F>b__1_0, <F>b__1_1, <F>b__1_2, <>c__DisplayClass1_0, <>c__DisplayClass1_1, <>c__DisplayClass1_2}");
var md1 = diff1.GetMetadata();
var reader1 = md1.Reader;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册