mirror of
https://github.com/icsharpcode/ILSpy.git
synced 2026-10-12 02:15:58 +00:00
Keep 'delegate {}' when a parameter type is inaccessible
The parameter-list-less anonymous method form is compatible with any delegate signature, and C# code must rely on exactly that when a delegate's parameter types cannot be named at the use site: IL, unlike C#, permits a delegate signature to reference less accessible types. Expanding such an anonymous method into a lambda would force the unnameable type into a parameter list. Keep the delegate form, with its parameter list dropped, when the parameters are unused and one of their types is not accessible from the current context. Assisted-by: Claude:claude-fable-5:Claude Code
This commit is contained in:
committed by
Siegfried Pammer
parent
3cc49044ec
commit
60c08fcb74
@@ -105,6 +105,7 @@
|
||||
<None Include="TestCases\ILPretty\ExtensionEncodingV2.il" />
|
||||
<None Include="testcases\ilpretty\ExtensionEncodingV1.il" />
|
||||
<None Include="TestCases\ILPretty\GuessAccessors.il" />
|
||||
<None Include="TestCases\ILPretty\InaccessibleParameterTypes.il" />
|
||||
<None Include="TestCases\ILPretty\Issue2260SwitchString.il" />
|
||||
<None Include="TestCases\ILPretty\Issue3442.il" />
|
||||
<None Include="TestCases\ILPretty\Issue3344CkFinite.il" />
|
||||
@@ -209,6 +210,8 @@
|
||||
<None Include="TestCases\ILPretty\FSharpUsing_Release.cs" />
|
||||
<Compile Remove="TestCases\ILPretty\GuessAccessors.cs" />
|
||||
<None Include="TestCases\ILPretty\GuessAccessors.cs" />
|
||||
<Compile Remove="TestCases\ILPretty\InaccessibleParameterTypes.cs" />
|
||||
<None Include="TestCases\ILPretty\InaccessibleParameterTypes.cs" />
|
||||
<Compile Remove="TestCases\ILPretty\NoAccessorProperties.cs" />
|
||||
<None Include="TestCases\ILPretty\NoAccessorProperties.cs" />
|
||||
<Compile Remove="TestCases\ILPretty\Issue1145.cs" />
|
||||
|
||||
@@ -359,6 +359,12 @@ namespace ICSharpCode.Decompiler.Tests
|
||||
await Run();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task InaccessibleParameterTypes()
|
||||
{
|
||||
await Run();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task EmptyBodies()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
|
||||
namespace ICSharpCode.Decompiler.Tests.TestCases.ILPretty
|
||||
{
|
||||
public class InaccessibleParameterTypes
|
||||
{
|
||||
private class Hidden
|
||||
{
|
||||
}
|
||||
|
||||
public delegate void Handler(Hidden h);
|
||||
|
||||
public static void Register(Action<Hidden> callback)
|
||||
{
|
||||
}
|
||||
}
|
||||
public class InaccessibleParameterTypesConsumer
|
||||
{
|
||||
public InaccessibleParameterTypes.Handler Create()
|
||||
{
|
||||
return delegate {
|
||||
};
|
||||
}
|
||||
|
||||
public void Run()
|
||||
{
|
||||
InaccessibleParameterTypes.Register(delegate {
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
// Anonymous methods whose delegate signature contains a type the use site cannot name.
|
||||
// IL (unlike C#) permits a public delegate with a less-accessible parameter type, so the
|
||||
// parameter-list-less "delegate {}" form is the only C# syntax the consumer class below
|
||||
// could legally have used - the decompiler must not expand it to a lambda parameter list.
|
||||
.assembly extern mscorlib
|
||||
{
|
||||
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4..
|
||||
.ver 4:0:0:0
|
||||
}
|
||||
.assembly extern System.Core
|
||||
{
|
||||
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4..
|
||||
.ver 4:0:0:0
|
||||
}
|
||||
.assembly InaccessibleParameterTypes
|
||||
{
|
||||
.ver 1:0:0:0
|
||||
}
|
||||
.module InaccessibleParameterTypes.dll
|
||||
.imagebase 0x00400000
|
||||
.file alignment 0x00000200
|
||||
.stackreserve 0x00100000
|
||||
.subsystem 0x0003 // WINDOWS_CUI
|
||||
.corflags 0x00020003 // ILONLY 32BITPREFERRED
|
||||
|
||||
.class public auto ansi beforefieldinit ICSharpCode.Decompiler.Tests.TestCases.ILPretty.InaccessibleParameterTypes
|
||||
extends [mscorlib]System.Object
|
||||
{
|
||||
.class nested private auto ansi beforefieldinit Hidden
|
||||
extends [mscorlib]System.Object
|
||||
{
|
||||
.method public hidebysig specialname rtspecialname instance void .ctor () cil managed
|
||||
{
|
||||
.maxstack 8
|
||||
|
||||
ldarg.0
|
||||
call instance void [mscorlib]System.Object::.ctor()
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
.class nested public auto ansi sealed Handler
|
||||
extends [mscorlib]System.MulticastDelegate
|
||||
{
|
||||
.method public hidebysig specialname rtspecialname instance void .ctor (object 'object', native int 'method') runtime managed
|
||||
{
|
||||
}
|
||||
|
||||
.method public hidebysig newslot virtual instance void Invoke (class ICSharpCode.Decompiler.Tests.TestCases.ILPretty.InaccessibleParameterTypes/Hidden h) runtime managed
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
.method public hidebysig static void Register (class [System.Core]System.Action`1<class ICSharpCode.Decompiler.Tests.TestCases.ILPretty.InaccessibleParameterTypes/Hidden> callback) cil managed
|
||||
{
|
||||
.maxstack 8
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
.method public hidebysig specialname rtspecialname instance void .ctor () cil managed
|
||||
{
|
||||
.maxstack 8
|
||||
|
||||
ldarg.0
|
||||
call instance void [mscorlib]System.Object::.ctor()
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
.class public auto ansi beforefieldinit ICSharpCode.Decompiler.Tests.TestCases.ILPretty.InaccessibleParameterTypesConsumer
|
||||
extends [mscorlib]System.Object
|
||||
{
|
||||
// Fields
|
||||
.field private static class ICSharpCode.Decompiler.Tests.TestCases.ILPretty.InaccessibleParameterTypes/Handler '<>f__am$cache0'
|
||||
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 )
|
||||
.field private static class [System.Core]System.Action`1<class ICSharpCode.Decompiler.Tests.TestCases.ILPretty.InaccessibleParameterTypes/Hidden> '<>f__am$cache1'
|
||||
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 )
|
||||
|
||||
// Methods
|
||||
.method public hidebysig instance class ICSharpCode.Decompiler.Tests.TestCases.ILPretty.InaccessibleParameterTypes/Handler Create () cil managed
|
||||
{
|
||||
.maxstack 8
|
||||
|
||||
ldsfld class ICSharpCode.Decompiler.Tests.TestCases.ILPretty.InaccessibleParameterTypes/Handler ICSharpCode.Decompiler.Tests.TestCases.ILPretty.InaccessibleParameterTypesConsumer::'<>f__am$cache0'
|
||||
brtrue.s IL_0016
|
||||
|
||||
ldnull
|
||||
ldftn void ICSharpCode.Decompiler.Tests.TestCases.ILPretty.InaccessibleParameterTypesConsumer::'<Create>m__0'(class ICSharpCode.Decompiler.Tests.TestCases.ILPretty.InaccessibleParameterTypes/Hidden)
|
||||
newobj instance void ICSharpCode.Decompiler.Tests.TestCases.ILPretty.InaccessibleParameterTypes/Handler::.ctor(object, native int)
|
||||
stsfld class ICSharpCode.Decompiler.Tests.TestCases.ILPretty.InaccessibleParameterTypes/Handler ICSharpCode.Decompiler.Tests.TestCases.ILPretty.InaccessibleParameterTypesConsumer::'<>f__am$cache0'
|
||||
|
||||
IL_0016: ldsfld class ICSharpCode.Decompiler.Tests.TestCases.ILPretty.InaccessibleParameterTypes/Handler ICSharpCode.Decompiler.Tests.TestCases.ILPretty.InaccessibleParameterTypesConsumer::'<>f__am$cache0'
|
||||
ret
|
||||
}
|
||||
|
||||
.method public hidebysig instance void Run () cil managed
|
||||
{
|
||||
.maxstack 8
|
||||
|
||||
ldsfld class [System.Core]System.Action`1<class ICSharpCode.Decompiler.Tests.TestCases.ILPretty.InaccessibleParameterTypes/Hidden> ICSharpCode.Decompiler.Tests.TestCases.ILPretty.InaccessibleParameterTypesConsumer::'<>f__am$cache1'
|
||||
brtrue.s IL_0016
|
||||
|
||||
ldnull
|
||||
ldftn void ICSharpCode.Decompiler.Tests.TestCases.ILPretty.InaccessibleParameterTypesConsumer::'<Run>m__1'(class ICSharpCode.Decompiler.Tests.TestCases.ILPretty.InaccessibleParameterTypes/Hidden)
|
||||
newobj instance void class [System.Core]System.Action`1<class ICSharpCode.Decompiler.Tests.TestCases.ILPretty.InaccessibleParameterTypes/Hidden>::.ctor(object, native int)
|
||||
stsfld class [System.Core]System.Action`1<class ICSharpCode.Decompiler.Tests.TestCases.ILPretty.InaccessibleParameterTypes/Hidden> ICSharpCode.Decompiler.Tests.TestCases.ILPretty.InaccessibleParameterTypesConsumer::'<>f__am$cache1'
|
||||
|
||||
IL_0016: ldsfld class [System.Core]System.Action`1<class ICSharpCode.Decompiler.Tests.TestCases.ILPretty.InaccessibleParameterTypes/Hidden> ICSharpCode.Decompiler.Tests.TestCases.ILPretty.InaccessibleParameterTypesConsumer::'<>f__am$cache1'
|
||||
call void ICSharpCode.Decompiler.Tests.TestCases.ILPretty.InaccessibleParameterTypes::Register(class [System.Core]System.Action`1<class ICSharpCode.Decompiler.Tests.TestCases.ILPretty.InaccessibleParameterTypes/Hidden>)
|
||||
ret
|
||||
}
|
||||
|
||||
.method private hidebysig static void '<Create>m__0' (class ICSharpCode.Decompiler.Tests.TestCases.ILPretty.InaccessibleParameterTypes/Hidden '') cil managed
|
||||
{
|
||||
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 )
|
||||
.maxstack 8
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
.method private hidebysig static void '<Run>m__1' (class ICSharpCode.Decompiler.Tests.TestCases.ILPretty.InaccessibleParameterTypes/Hidden '') cil managed
|
||||
{
|
||||
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 )
|
||||
.maxstack 8
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
.method public hidebysig specialname rtspecialname instance void .ctor () cil managed
|
||||
{
|
||||
.maxstack 8
|
||||
|
||||
ldarg.0
|
||||
call instance void [mscorlib]System.Object::.ctor()
|
||||
ret
|
||||
}
|
||||
}
|
||||
@@ -163,11 +163,17 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction
|
||||
return (int x) => this.x;
|
||||
}
|
||||
|
||||
#if ROSLYN
|
||||
// Roslyn names the parameters of an anonymous method declared without a parameter
|
||||
// list "<p0>", which no lambda parameter list can spell, so the delegate form is
|
||||
// kept. Legacy csc names them "param0" - a perfectly good identifier that does
|
||||
// become a lambda - hence the Roslyn-only guard.
|
||||
public Action<object> Bug971_DelegateWithoutParameterList()
|
||||
{
|
||||
return (object obj) => {
|
||||
return delegate {
|
||||
};
|
||||
}
|
||||
#endif
|
||||
|
||||
private void DoAction(Action action)
|
||||
{
|
||||
|
||||
@@ -2537,6 +2537,12 @@ namespace ICSharpCode.Decompiler.CSharp
|
||||
attributeSections.Add(new AttributeSection(astBuilder.ConvertAttribute(attr)) { AttributeTarget = "return" });
|
||||
}
|
||||
|
||||
bool parametersAreUsed = (
|
||||
from ident in body.Descendants.OfType<IdentifierExpression>()
|
||||
let v = ident.GetILVariable()
|
||||
where v != null && v.Function == function && v.Kind == VariableKind.Parameter
|
||||
select ident).Any();
|
||||
|
||||
bool isLambda = false;
|
||||
if (ame.Parameters.Any(p => p.Type is null))
|
||||
{
|
||||
@@ -2548,19 +2554,19 @@ namespace ICSharpCode.Decompiler.CSharp
|
||||
// C# 10 lambdas can have attributes, but anonymous methods cannot
|
||||
isLambda = true;
|
||||
}
|
||||
else if (settings.UseLambdaSyntax && ame.Parameters.All(p => p.ParameterModifier == ReferenceKind.None && !p.IsParams))
|
||||
else if (settings.UseLambdaSyntax && ame.Parameters.All(p => p.ParameterModifier == ReferenceKind.None && !p.IsParams)
|
||||
&& (parametersAreUsed || (ParameterTypesAreAccessible(function) && ParametersAreNameable(function))))
|
||||
{
|
||||
// Lambdas cover statement bodies too; anonymous method syntax remains only for
|
||||
// parameter shapes a lambda cannot express (ref/out/in and params modifiers).
|
||||
// Lambdas cover statement bodies too. Anonymous method syntax remains where
|
||||
// dropping the parameter list is the better rendering: for ref/out/in and
|
||||
// params parameters (expressible in an explicitly typed lambda list, but
|
||||
// conservatively left alone), and for unused parameters that a list would have
|
||||
// to name or type from nothing to keep. The parameter-list-less "delegate {}"
|
||||
// form is compatible with any delegate signature, so it is always legal there.
|
||||
isLambda = true;
|
||||
}
|
||||
// Remove the parameter list from an AnonymousMethodExpression if the parameters are not used in the method body
|
||||
var parameterReferencingIdentifiers =
|
||||
from ident in body.Descendants.OfType<IdentifierExpression>()
|
||||
let v = ident.GetILVariable()
|
||||
where v != null && v.Function == function && v.Kind == VariableKind.Parameter
|
||||
select ident;
|
||||
if (!isLambda && !parameterReferencingIdentifiers.Any())
|
||||
if (!isLambda && !parametersAreUsed)
|
||||
{
|
||||
ame.Parameters.Clear();
|
||||
}
|
||||
@@ -2608,6 +2614,47 @@ namespace ICSharpCode.Decompiler.CSharp
|
||||
.WithRR(new ConversionResolveResult(delegateType, rr, LambdaConversion.Instance));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True when every parameter carries a name that can be written out as-is. Unused
|
||||
/// parameters whose metadata names are missing or not identifiers (ilasm's synthetic
|
||||
/// A_0/A_1 for unnamed Param rows, "<p0>" from an anonymous method declared without a
|
||||
/// parameter list, obfuscated names) would have to be invented for a lambda's mandatory
|
||||
/// parameter list; "delegate {}" drops the list instead of presenting a made-up name as
|
||||
/// if it came from the source.
|
||||
/// </summary>
|
||||
static bool ParametersAreNameable(ILFunction function)
|
||||
{
|
||||
return function.Parameters.All(
|
||||
p => !string.IsNullOrWhiteSpace(p.Name) && AssignVariableNames.IsValidName(p.Name));
|
||||
}
|
||||
|
||||
bool ParameterTypesAreAccessible(ILFunction function)
|
||||
{
|
||||
var currentTypeDefinition = resolver.CurrentTypeDefinition;
|
||||
if (currentTypeDefinition == null)
|
||||
return true;
|
||||
var lookup = new MemberLookup(currentTypeDefinition, currentTypeDefinition.ParentModule);
|
||||
return function.Parameters.All(p => IsAccessible(p.Type));
|
||||
|
||||
bool IsAccessible(IType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case ParameterizedType pt:
|
||||
return IsAccessible(pt.GenericType) && pt.TypeArguments.All(IsAccessible);
|
||||
case TypeWithElementType t:
|
||||
return IsAccessible(t.ElementType);
|
||||
default:
|
||||
for (var td = type.GetDefinition(); td != null; td = td.DeclaringTypeDefinition)
|
||||
{
|
||||
if (!lookup.IsAccessible(td, allowProtectedAccess: true))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected internal override TranslatedExpression VisitILFunction(ILFunction function, TranslationContext context)
|
||||
{
|
||||
return TranslateFunction(function.DelegateType!, function)
|
||||
|
||||
Reference in New Issue
Block a user