Synthesize delegate types for lambdas with parameter types or return type that are not valid type arguments#57295
Synthesize delegate types for lambdas with parameter types or return type that are not valid type arguments#57295cston merged 19 commits intodotnet:mainfrom
Conversation
f2e1ae5 to
b28620d
Compare
18b3e97 to
d141ba4
Compare
|
@dotnet/roslyn-compiler, please review, thanks. |
|
Why not use hash of the signature display string instead of an index in the synthesized name? In reply to: 1001717281 |
Using an index (based on source order) matches the naming for anonymous types, and it seemed straightforward and sufficient to cover the scenarios. |
|
Yeah, but it does not work with EnC. In reply to: 1001893183 |
| return (NamedTypeSymbol?)value.Delegate?.GetInternalSymbol(); | ||
| if (delegateTemplate.HasFixedTypes) | ||
| { | ||
| TryFindAnonymousDelegateWithFixedType(delegateTemplate, out var value); |
There was a problem hiding this comment.
The branches were flipped because HasFixedTypes == !IsParameterizedDelegateType.
| template.Construct(typeParameters); | ||
| } | ||
|
|
||
| static bool allValidTypeArguments(AnonymousTypeDescriptor typeDescr) |
There was a problem hiding this comment.
The name of the local function doesn't seem to match its purpose #Closed
| { | ||
| typeArgumentsBuilder.Add(fields[i].TypeWithAnnotations); | ||
| var xOwner = x.ContainingSymbol!; | ||
| var yOwner = y.ContainingSymbol!; |
There was a problem hiding this comment.
var yOwner = y.ContainingSymbol!;
Can remove the nullable suppressions? #Closed
| TypeSymbol? voidReturnTypeOpt, | ||
| int parameterCount, | ||
| RefKindVector refKinds) | ||
| : base(manager, Location.None) // Location is not needed since NameAndIndex is set explicitly below. |
There was a problem hiding this comment.
Would it make sense to split the base constructor (one that takes a NameAndIndex and one that takes a Location)? #Closed
There was a problem hiding this comment.
It's not ideal that the two constructors here have different ways to set NameAndIndex, but it might be better to leave that difference here rather than change the base type.
| RefKindVector refKinds) | ||
| : base(manager, Location.None) // Location is not needed since NameAndIndex is set explicitly below. | ||
| { | ||
| Debug.Assert(refKinds.IsNull || parameterCount == refKinds.Capacity - (voidReturnTypeOpt is { } ? 0 : 1)); |
There was a problem hiding this comment.
I'll leave as is because this constructor was essentially just moved from SynthesizedDelegateSymbol.
| Debug.Assert((ordinal == 0) == (initialBaseline == null)); | ||
| Debug.Assert((ordinal == 0) == (anonymousTypeMap == null)); | ||
| Debug.Assert((ordinal == 0) == (synthesizedDelegates == null)); | ||
| Debug.Assert((ordinal == 0) == (anonymousDelegates == null)); |
There was a problem hiding this comment.
Nit: Should there be a corresponding assert here for anonymousDelegatesWithFixedTypes? #Resolved
| changedTypes: changedTypes.ToImmutableAndFree()); | ||
| } | ||
|
|
||
| private static bool ContainsAnonymousDelegates( |
There was a problem hiding this comment.
Nit: Naming seems off to me. This checks for changes to delegates, not just whether they exist, right? Or does the current name mean "Contains all existing anonymous delegates"? #Resolved
There was a problem hiding this comment.
The method checks the new collection contains all existing anonymous delegates. Renamed to ContainsPreviousAnonymousDelegates to clarify.
| } | ||
| } | ||
|
|
||
| internal AnonymousDelegateTemplateSymbol(AnonymousTypeManager manager, AnonymousTypeDescriptor typeDescr, ImmutableArray<TypeParameterSymbol> typeParametersToSubstitute) |
There was a problem hiding this comment.
nit: xml doc on these constructors would be useful. This constructor is for non-generic case, and above is generic case #Closed
| @".class public A`1<T> | ||
| { | ||
| .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { ret } | ||
| .method public static void F1(int32* modopt(class A`1<!T>) i) { ret } |
There was a problem hiding this comment.
Consider expanding this test to observe the modopt in the synthesized delegate type
There was a problem hiding this comment.
The modopt is used to test that the resulting delegate is generic only (the type parameter is referenced in the modopt only), but the resulting delegate does not include a modopt.
| internal override bool HasPossibleWellKnownCloneMethod() => false; | ||
| } | ||
|
|
||
| internal sealed class SynthesizedDelegateConstructor : SynthesizedInstanceConstructor |
There was a problem hiding this comment.
Given that SynthesizedDelegateConstructor is the only type left in this file, it'd be good to rename the file #Closed
There was a problem hiding this comment.
I'm reluctant to rename the file with changes in case that makes it difficult to track the history.
| Assert.Equal("<>9__0_2", field3.Name); | ||
|
|
||
| var matcher = new CSharpSymbolMatcher(null, synthesizedDelegates0, compilation1.SourceAssembly, emitContext, peAssemblySymbol0); | ||
| var matcher = new CSharpSymbolMatcher(null, synthesizedDelegates0, null, compilation1.SourceAssembly, emitContext, peAssemblySymbol0); |
There was a problem hiding this comment.
Feels like we should add some SymbolMatcher tests for delegates with fixed types #Closed
| if (ReferenceEquals(template.Manager, this) && !template.HasFixedTypes) | ||
| { | ||
| builder.Add(template.Delegate); | ||
| builder.Add(template); |
There was a problem hiding this comment.
nit: Consider adding an assertion that template.SmallestLocation is Location.None (which explains why we use the SynthesizedDelegateSymbolComparer comparer below) #Closed
There was a problem hiding this comment.
There's actually no requirement that SmallestLocation is Location.None. But we are able to compare by MetadataName because the name is based only on the signature of the delegate type, not it's source order.
| } | ||
|
|
||
| internal IReadOnlyDictionary<CodeAnalysis.Emit.SynthesizedDelegateKey, CodeAnalysis.Emit.SynthesizedDelegateValue> GetSynthesizedDelegates() | ||
| internal IReadOnlyDictionary<CodeAnalysis.Emit.SynthesizedDelegateKey, CodeAnalysis.Emit.SynthesizedDelegateValue> GetAnonymousDelegates() |
There was a problem hiding this comment.
nit: consider renaming GetAnonymousDelegatesWithoutFixedTypes (and propagate up into PEDeltaAssemblyBuilder and EmitBaseline) #Closed
There was a problem hiding this comment.
I'd considered that but slightly preferred having a simpler name for the more common delegate types.
jcouv
left a comment
There was a problem hiding this comment.
Done with review pass (iteration 16)
Synthesize delegate types for lambda expressions and method groups with parameter types or return type that are not valid type arguments.
For delegates where all parameter types and return type are valid as type arguments, the synthesized delegate type is a generic type where the types are the type arguments That allows a synthesized delegate type to be shared across all delegates with the same number of parameters and same ref kinds.
For instance the delegate type for
(ref int p) => pis emitted as<>F{00000001}<int, int>with the generic typeinternal delegate TResult <>F{00000001}<T1, TResult>(ref T1).For delegates where one or more of the parameter types or return type are not valid as type arguments (this PR), the synthesized delegate type is non-generic with fixed parameter and return types. The synthesized delegate type is still shared across all delegates with the exact signature however.
For instance the delegate type for
(int* p) => *pis emitted asinternal delegate int <>f__AnonymousDelegate0(int*).The PR includes changes to edit-and-continue where the new delegate types are matched across edits by comparing delegate type names and signatures, and where a signature change, which should be rare, is considered a rude edit.
Fixes #55217.