Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cache the delegate for static method group conversions. #58288

Merged
merged 73 commits into from
Jan 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
9978f93
Cache the delegate for static method group conversions.
pawchen Dec 12, 2021
0130613
Use static local functions for the delegate caching tests.
pawchen Dec 14, 2021
1daafd8
Fix a flaw in choosing the container type that targets a local function.
pawchen Dec 14, 2021
08d1544
Add some tests for tuples and local functions.
pawchen Dec 15, 2021
3afc667
Remove unnecessary stuffs.
pawchen Dec 15, 2021
e03960f
Feedback: Avoid targeted new for places where types are not immediate…
pawchen Dec 21, 2021
846f163
Feedback: Add doc comments.
pawchen Dec 21, 2021
6f9c4ab
Feedback: Use shared instances.
pawchen Dec 21, 2021
dc5c5bd
Feedback: Rename and optimize.
pawchen Dec 21, 2021
346f1c0
Rename.
pawchen Dec 21, 2021
9ec9776
Feedback: Share containers for functions with zero arity within the s…
pawchen Dec 21, 2021
af2f823
Use MessageID for feature enabling.
pawchen Dec 21, 2021
0ba2f89
Merge with main.
pawchen Dec 21, 2021
f0b45f9
Fix test options.
pawchen Dec 21, 2021
bb135c1
Revert file rename.
pawchen Dec 21, 2021
357883b
Keep existing tests that explicitly target older language versions.
pawchen Dec 21, 2021
fef8b7a
Feedback: Improve readability.
pawchen Dec 21, 2021
797e281
Feedback: Pin affected pre-existing tests to C# 10.
pawchen Dec 22, 2021
17e0056
Restore original comments and whitespaces.
pawchen Dec 22, 2021
97acc82
Move 10 vs preview to the new test file.
pawchen Dec 22, 2021
87c448b
Feedback: Remove duplicate information.
pawchen Dec 23, 2021
55b0171
Feedback: Adjust TypeSymbol.VisitType and remove the visitor.
pawchen Dec 26, 2021
1a75a7f
Fix IndexOutOfRangeException.
pawchen Dec 26, 2021
656466a
Remove tuple branch.
pawchen Dec 26, 2021
f5bdc79
Feedback: Add asserts.
pawchen Dec 27, 2021
c244c51
Feedback: Adjust VisitType.
pawchen Dec 27, 2021
8c197fe
Feedback: Use ancestor cache containers (less arity) when possible.
pawchen Dec 28, 2021
8f85ed6
Add tests for annoymous delegate in anonymous class.
pawchen Dec 28, 2021
0758ddc
Feedback: Remove confusing comments. Add an assert.
pawchen Dec 29, 2021
49c7420
Feedback: Update assert. Use static field.
pawchen Dec 29, 2021
67429d3
Feedback: Use CSharpCustomModifier.ModifierSymbol.
pawchen Dec 29, 2021
d0912a9
Feedback: Avoid ImmutableArray allocation.
pawchen Dec 29, 2021
48c5cd7
Fix var scope.
pawchen Dec 29, 2021
3784e4a
Feedback: Add direct tests for VisitType.
pawchen Dec 29, 2021
9ecd5bd
Merge branch 'main' of https://github.com/dotnet/roslyn into mgcClean
pawchen Dec 30, 2021
5113cdc
Feedback: Remove commented code.
pawchen Dec 30, 2021
1d98b32
Add test with custom modifiers with type parameter.
pawchen Dec 30, 2021
fcb0159
Simplify container validations when possible. Add type parameter veri…
pawchen Dec 30, 2021
60b72d6
Feedback: Remove unused method.
pawchen Dec 30, 2021
ee75f3c
Use AssertEx.NotNull.
pawchen Dec 31, 2021
764c863
Add IL verification for cases we shouldn't cache.
pawchen Dec 31, 2021
cb66daf
Test the actual interested method.
pawchen Dec 31, 2021
194e320
Add an overload of VerifySynthesizedFields that also perform validati…
pawchen Dec 31, 2021
7c75892
Add IL verifications show that the container and fields are used.
pawchen Jan 1, 2022
fd2d727
Prepare for EnC tests.
pawchen Jan 1, 2022
b6c09d6
Pass the previous ParseOptions to the next compilation.
pawchen Jan 1, 2022
b26d098
Add EnC tests.
pawchen Jan 1, 2022
e26476c
Merge branch 'main' of https://github.com/dotnet/roslyn into mgcClean
pawchen Jan 2, 2022
ef66214
Add VerifyNoCacheContainers that also verifies the produced assembly.…
pawchen Jan 5, 2022
0f25a6e
Merge branch 'main' into mgcClean
pawchen Jan 5, 2022
9ca84f4
Merge branch 'main' of https://github.com/dotnet/roslyn into mgcClean
pawchen Jan 6, 2022
1eed89b
Feedback: Use regular type cast and move to a shared library.
pawchen Jan 9, 2022
9f4a296
Merge branch 'mgcClean' of https://github.com/diryboy/roslyn into mgc…
pawchen Jan 9, 2022
c985538
Feedback: Use regular type cast. Revert "Move to a shared library."
pawchen Jan 9, 2022
eecb17b
Feedback: Use the symbolValidator parameter.
pawchen Jan 9, 2022
67dfa96
Add tests for conversions from instances.
pawchen Jan 9, 2022
35de0aa
Feedback: Remove a duplicated C# 10 test case.
pawchen Jan 9, 2022
75a11cc
Feedback: Print the expression tree.
pawchen Jan 9, 2022
35a9f64
Feedback: Add name ambiguity tests.
pawchen Jan 9, 2022
0ed19da
Fix expectedOutput for expression lamba.
pawchen Jan 9, 2022
e9cb8a9
Add an assert.
pawchen Jan 9, 2022
e659853
Rename helper methods.
pawchen Jan 9, 2022
7b2bbea
Merge branch 'main' of https://github.com/dotnet/roslyn into mgcClean
pawchen Jan 11, 2022
ffe1c99
Feedback: Move EnC tests from Emit2 to Emit.
pawchen Jan 11, 2022
c0f729c
Feedback: Only compare CLR signature for the cache fields.
pawchen Jan 11, 2022
336c95b
Address some feedback.
pawchen Jan 11, 2022
e673d05
Feedback: Explicitly target some tests to C# Next.
pawchen Jan 11, 2022
3679330
Add a test where the target is a local function outside of the curren…
pawchen Jan 11, 2022
af2d179
Feedback: Add an assert.
pawchen Jan 11, 2022
ebb945e
Feedback: Rename the custom comparer.
pawchen Jan 11, 2022
aaa2621
Feedback: Use ReferenceEqualityComparer.
pawchen Jan 11, 2022
28ffe27
Feedback: Add a test for C# 10. Add tests for cache field sharing the…
pawchen Jan 11, 2022
84551f1
Address feedback.
pawchen Jan 12, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ internal MethodWithBody(MethodSymbol method, BoundStatement body, ImportChain? i

public SynthesizedClosureEnvironment? StaticLambdaFrame;

public DelegateCacheContainer? ConcreteDelegateCacheContainer;

/// <summary>
/// A graph of method->method references for this(...) constructor initializers.
/// Used to detect and report initializer cycles.
Expand Down
3 changes: 3 additions & 0 deletions src/Compilers/CSharp/Portable/Errors/MessageID.cs
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,8 @@ internal enum MessageID
IDS_FeatureNewLinesInInterpolations = MessageBase + 12813,
IDS_FeatureListPattern = MessageBase + 12814,
IDS_ParameterNullChecking = MessageBase + 12815,

IDS_FeatureCacheStaticMethodGroupConversion = MessageBase + 12816,
}

// Message IDs may refer to strings that need to be localized.
Expand Down Expand Up @@ -354,6 +356,7 @@ internal static LanguageVersion RequiredVersion(this MessageID feature)
case MessageID.IDS_FeatureGenericAttributes: // semantic check
case MessageID.IDS_FeatureNewLinesInInterpolations: // semantic check
case MessageID.IDS_FeatureListPattern: // semantic check
case MessageID.IDS_FeatureCacheStaticMethodGroupConversion: // lowering check
case MessageID.IDS_ParameterNullChecking: // syntax check
return LanguageVersion.Preview;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections.Generic;
using System.Diagnostics;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.CSharp.Symbols;

/// <summary>
/// This type is synthesized to hold the cached delegates that target static method groups.
/// </summary>
internal sealed class DelegateCacheContainer : SynthesizedContainer
pawchen marked this conversation as resolved.
Show resolved Hide resolved
{
private readonly Symbol _containingSymbol;
private readonly NamedTypeSymbol? _constructedContainer;
pawchen marked this conversation as resolved.
Show resolved Hide resolved
private readonly Dictionary<(TypeSymbol, MethodSymbol), FieldSymbol> _delegateFields = new(CLRSignatureComparer.Instance);

/// <summary>Creates a type-scope concrete delegate cache container.</summary>
internal DelegateCacheContainer(TypeSymbol containingType, int generationOrdinal)
: base(GeneratedNames.DelegateCacheContainerType(generationOrdinal), containingMethod: null)
{
Debug.Assert(containingType.IsDefinition);

_containingSymbol = containingType;
}

/// <summary>Creates a method-scope generic delegate cache container.</summary>
internal DelegateCacheContainer(MethodSymbol ownerMethod, int topLevelMethodOrdinal, int ownerUniqueId, int generationOrdinal)
: base(GeneratedNames.DelegateCacheContainerType(generationOrdinal, ownerMethod.Name, topLevelMethodOrdinal, ownerUniqueId), ownerMethod)
pawchen marked this conversation as resolved.
Show resolved Hide resolved
{
Debug.Assert(ownerMethod.IsDefinition);
Debug.Assert(ownerMethod.Arity > 0);

_containingSymbol = ownerMethod.ContainingType;
_constructedContainer = Construct(ConstructedFromTypeParameters);
pawchen marked this conversation as resolved.
Show resolved Hide resolved
}

public override Symbol ContainingSymbol => _containingSymbol;

public override bool AreLocalsZeroed => throw ExceptionUtilities.Unreachable;

public override TypeKind TypeKind => TypeKind.Class;

public override bool IsStatic => true;

internal override bool IsRecord => false;

internal override bool IsRecordStruct => false;

internal override bool HasPossibleWellKnownCloneMethod() => false;

internal FieldSymbol GetOrAddCacheField(SyntheticBoundNodeFactory factory, TypeSymbol delegateType, MethodSymbol targetMethod)
{
Debug.Assert(delegateType.IsDelegateType());

if (_delegateFields.TryGetValue((delegateType, targetMethod), out var field))
{
return field;
}

var fieldType = TypeParameters.IsEmpty ? delegateType : TypeMap.SubstituteType(delegateType).Type;
pawchen marked this conversation as resolved.
Show resolved Hide resolved
var fieldName = GeneratedNames.DelegateCacheContainerFieldName(_delegateFields.Count, targetMethod.Name);
pawchen marked this conversation as resolved.
Show resolved Hide resolved

field = new SynthesizedFieldSymbol(this, fieldType, fieldName, isPublic: true, isStatic: true);
factory.AddField(this, field);

if (!TypeParameters.IsEmpty)
{
Debug.Assert(_constructedContainer is { });
pawchen marked this conversation as resolved.
Show resolved Hide resolved

field = field.AsMember(_constructedContainer);
}

_delegateFields.Add((delegateType, targetMethod), field);

return field;
}

private sealed class CLRSignatureComparer : IEqualityComparer<(TypeSymbol delegateType, MethodSymbol targetMethod)>
{
public static readonly CLRSignatureComparer Instance = new();

public bool Equals((TypeSymbol delegateType, MethodSymbol targetMethod) x, (TypeSymbol delegateType, MethodSymbol targetMethod) y)
{
var symbolComparer = SymbolEqualityComparer.CLRSignature;

return symbolComparer.Equals(x.delegateType, y.delegateType) && symbolComparer.Equals(x.targetMethod, y.targetMethod);
}

public int GetHashCode((TypeSymbol delegateType, MethodSymbol targetMethod) conversion)
{
var symbolComparer = SymbolEqualityComparer.CLRSignature;

return Hash.Combine(symbolComparer.GetHashCode(conversion.delegateType), symbolComparer.GetHashCode(conversion.targetMethod));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.CSharp;

/// <summary>
/// This type helps rewrite the delegate creations that target static method groups to use a cached instance of delegate.
/// </summary>
internal sealed class DelegateCacheRewriter
{
private readonly SyntheticBoundNodeFactory _factory;
private readonly int _topLevelMethodOrdinal;

private Dictionary<MethodSymbol, DelegateCacheContainer>? _genericCacheContainers;

internal DelegateCacheRewriter(SyntheticBoundNodeFactory factory, int topLevelMethodOrdinal)
{
Debug.Assert(factory.TopLevelMethod is { });

_factory = factory;
_topLevelMethodOrdinal = topLevelMethodOrdinal;
}

internal static bool CanRewrite(BoundDelegateCreationExpression boundDelegateCreation)
{
var targetMethod = boundDelegateCreation.MethodOpt;

Debug.Assert(targetMethod is { });

return targetMethod.IsStatic && !boundDelegateCreation.IsExtensionMethod;
pawchen marked this conversation as resolved.
Show resolved Hide resolved
}

internal BoundExpression Rewrite(BoundDelegateCreationExpression boundDelegateCreation)
{
var targetMethod = boundDelegateCreation.MethodOpt;
var delegateType = boundDelegateCreation.Type;

Debug.Assert(targetMethod is { });

var oldSyntax = _factory.Syntax;
_factory.Syntax = boundDelegateCreation.Syntax;

var cacheContainer = GetOrAddCacheContainer(delegateType, targetMethod);
var cacheField = cacheContainer.GetOrAddCacheField(_factory, delegateType, targetMethod);

var boundCacheField = _factory.Field(receiver: null, cacheField);
var rewrittenNode = _factory.Coalesce(boundCacheField, _factory.AssignmentExpression(boundCacheField, boundDelegateCreation));

_factory.Syntax = oldSyntax;

return rewrittenNode;
}

private DelegateCacheContainer GetOrAddCacheContainer(TypeSymbol delegateType, MethodSymbol targetMethod)
{
Debug.Assert(_factory.ModuleBuilderOpt is { });
Debug.Assert(_factory.CurrentFunction is { });

var generation = _factory.ModuleBuilderOpt.CurrentGenerationOrdinal;

DelegateCacheContainer? container;

// We don't need to synthesize a container for each and every function.
//
// For example:
// void LF1<T>()
// {
// void LF2<G>()
// {
// void LF3()
// {
// Func<T> d = SomeMethod<T>;
// static void LF4 () { Func<T> d = SomeMethod<T>; }
// }
//
// void LF5()
// {
// Func<T> d = SomeMethod<T>;
// }
// }
// }
//
// In the above case, only one cached delegate is necessary, and it could be assigned to the container 'owned' by LF1.

if (!TryGetOwnerFunction(_factory.CurrentFunction, delegateType, targetMethod, out var ownerFunction))
{
var typeCompilationState = _factory.CompilationState;
container = typeCompilationState.ConcreteDelegateCacheContainer;

if (container is { })
{
return container;
}

container = new DelegateCacheContainer(typeCompilationState.Type, generation);
typeCompilationState.ConcreteDelegateCacheContainer = container;
}
else
{
var containers = _genericCacheContainers ??= new Dictionary<MethodSymbol, DelegateCacheContainer>(ReferenceEqualityComparer.Instance);

if (containers.TryGetValue(ownerFunction, out container))
{
return container;
}

container = new DelegateCacheContainer(ownerFunction, _topLevelMethodOrdinal, containers.Count, generation);
containers.Add(ownerFunction, container);
}

_factory.AddNestedType(container);

return container;
}

private static bool TryGetOwnerFunction(MethodSymbol currentFunction, TypeSymbol delegateType, MethodSymbol targetMethod, [NotNullWhen(true)] out MethodSymbol? ownerFunction)
{
if (targetMethod.MethodKind == MethodKind.LocalFunction)
{
// Local functions can use type parameters from their enclosing methods!
//
// For example:
// void Test<T>()
// {
// var t = Target<int>;
// static object Target<V>() => default(T);
// }
//
// Therefore, without too much analysis, we select the closest generic enclosing function as the cache container owner.

for (Symbol? enclosingSymbol = currentFunction; enclosingSymbol is MethodSymbol enclosingMethod; enclosingSymbol = enclosingSymbol.ContainingSymbol)
{
if (enclosingMethod.Arity > 0)
{
ownerFunction = enclosingMethod;
return true;
}
}

ownerFunction = null;
return false;
}

// @AlekseyTs: It is Ok to create delegates for other method kinds as well.
// @jcouv: We'd likely want to pay attention to this code if this happens.
// What we really cared above was,
// - "Are there any type parameters from the target method that we cannot discover simply from it's signature?"
// As of C# 10, we only observe local functions could potentially answer yes, so we used that.
// If this is hit, feel free to change but please also add tests.
Debug.Assert(targetMethod.MethodKind == MethodKind.Ordinary);

var usedTypeParameters = PooledHashSet<TypeParameterSymbol>.GetInstance();
Copy link
Member

@jcouv jcouv Jan 11, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: consider asserting on MethodKind (we can only get Ordinary here, right?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. I assumed Ordinary here. Is it possible ending up EventAdd or PropertyGet? Let me play a little bit and get back here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, added in af2d179. Please check.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jcouv

consider asserting on MethodKind (we can only get Ordinary here, right?)

I am not sure if this assert adds any value. I think that for the purpose of this component, the kind of the target method is not significant. We are not relying on the fact anywhere.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It adds to readability in my opinion (we know what we're dealing with).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It adds to readability in my opinion (we know what we're dealing with).

I don't think I can agree with this. There is a whole bunch of unrelated things that we can assert here. But we are not doing this. In my opinion this assert hurts readability. It confuses the reader, making him think that some how doing all this here for other methods is going to be somehow wrong. But it won't be. It is Ok to create delegates for other method kinds as well, perhaps language doesn't provide means for that today, but that is not something that this component should worry about in my opinion. It is given a delegate creation to cache, That is all what it is supposed to do, validating the delegate creation is not its job. And knowing what method kind we are dealing with here today doesn't really help because it doesn't affect correctness of what we are doing.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps language doesn't provide means for that today

To me that's an argument for an assertion, not against, as we'd likely want to pay attention to this code if this happens.
That said, I don't think this is worth holding the PR over (I'd marked the comment as nit) so I'm okay either way.

Copy link
Contributor

@AlekseyTs AlekseyTs Jan 11, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am fine with letting @pawchen to decide whether to keep the assert.

Copy link
Contributor Author

@pawchen pawchen Jan 12, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AlekseyTs: It is Ok to create delegates for other method kinds as well, perhaps language doesn't provide means for that today, but that is not something that this component should worry about.

@jcouv: we'd likely want to pay attention to this code if this happens.

I agree with both of your opinions. Originally I assumed Ordinary here, and all tests we have right now at this point will be Ordinary here. In my opinion, BoundDelegateCreation can be from anywhere not in the source, like WinRT Event, and the MethodKind is not limited to ordinary methods.

I think fundamentally the condition supplied for the if above is kind of "flawed", what we really cared above was, "Are there any type parameters from the target method that we cannot discover simply from it's signature?", as of today, we only observe local functions could potentially answer yes, and we used that to drive down the complexity.

That said, I'm keeping the assert here and I'm also adding comments to capture this discussion.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comments added, please check 84551f1.

try
{
FindTypeParameters(delegateType, usedTypeParameters);
FindTypeParameters(targetMethod, usedTypeParameters);

for (Symbol? enclosingSymbol = currentFunction; enclosingSymbol is MethodSymbol enclosingMethod; enclosingSymbol = enclosingSymbol.ContainingSymbol)
{
if (usedTypeParametersContains(usedTypeParameters, enclosingMethod.TypeParameters))
{
ownerFunction = enclosingMethod;
return true;
}
}

ownerFunction = null;
return false;
}
finally
{
usedTypeParameters.Free();
}

static bool usedTypeParametersContains(HashSet<TypeParameterSymbol> used, ImmutableArray<TypeParameterSymbol> typeParameters)
{
foreach (var typeParameter in typeParameters)
{
if (used.Contains(typeParameter))
{
return true;
}
}

return false;
}
}

private static void FindTypeParameters(TypeSymbol type, HashSet<TypeParameterSymbol> result)
=> type.VisitType(s_typeParameterSymbolCollector, result, visitCustomModifiers: true);

private static void FindTypeParameters(MethodSymbol method, HashSet<TypeParameterSymbol> result)
pawchen marked this conversation as resolved.
Show resolved Hide resolved
{
FindTypeParameters(method.ContainingType, result);

foreach (var typeArgument in method.TypeArgumentsWithAnnotations)
{
typeArgument.VisitType(type: null, typeWithAnnotationsPredicate: null, s_typeParameterSymbolCollector, result, visitCustomModifiers: true);
}
}

private static readonly Func<TypeSymbol, HashSet<TypeParameterSymbol>, bool, bool> s_typeParameterSymbolCollector = (typeSymbol, result, _) =>
{
if (typeSymbol is TypeParameterSymbol typeParameter)
{
result.Add(typeParameter);
}

return false;
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ internal sealed partial class LocalRewriter : BoundTreeRewriterWithStackGuard
private LoweredDynamicOperationFactory _dynamicFactory;
private bool _sawLambdas;
private int _availableLocalFunctionOrdinal;
private readonly int _topLevelMethodOrdinal;
private DelegateCacheRewriter? _lazyDelegateCacheRewriter;
private bool _inExpressionLambda;

private bool _sawAwait;
Expand Down Expand Up @@ -57,6 +59,7 @@ private LocalRewriter(
_dynamicFactory = new LoweredDynamicOperationFactory(factory, containingMethodOrdinal);
_previousSubmissionFields = previousSubmissionFields;
_allowOmissionOfConditionalCalls = allowOmissionOfConditionalCalls;
_topLevelMethodOrdinal = containingMethodOrdinal;
_diagnostics = diagnostics;

Debug.Assert(instrumenter != null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -425,9 +425,26 @@ private BoundExpression MakeConversionNodeCore(
var receiver = (!method.RequiresInstanceReceiver && !oldNodeOpt.IsExtensionMethod && !method.IsAbstract) ? _factory.Type(method.ContainingType) : mg.ReceiverOpt;
Debug.Assert(receiver is { });
_factory.Syntax = oldSyntax;
return new BoundDelegateCreationExpression(syntax, argument: receiver, methodOpt: method,
isExtensionMethod: oldNodeOpt.IsExtensionMethod, wasTargetTyped: false, type: rewrittenType);

var boundDelegateCreation = new BoundDelegateCreationExpression(syntax, argument: receiver, methodOpt: method,
isExtensionMethod: oldNodeOpt.IsExtensionMethod, wasTargetTyped: false, type: rewrittenType);

Debug.Assert(_factory.TopLevelMethod is { });

if (_factory.Compilation.LanguageVersion >= MessageID.IDS_FeatureCacheStaticMethodGroupConversion.RequiredVersion()
&& !_inExpressionLambda // The tree structure / meaning for expression trees should remain untouched.
&& _factory.TopLevelMethod.MethodKind != MethodKind.StaticConstructor // Avoid caching twice if people do it manually.
&& DelegateCacheRewriter.CanRewrite(boundDelegateCreation))
{
var rewriter = _lazyDelegateCacheRewriter ??= new DelegateCacheRewriter(_factory, _topLevelMethodOrdinal);
return rewriter.Rewrite(boundDelegateCreation);
}
else
{
return boundDelegateCreation;
}
}

default:
break;
}
Expand Down
Loading