mirror of
https://github.com/icsharpcode/ILSpy.git
synced 2026-10-12 02:15:58 +00:00
Merge pull request #4160 from icsharpcode/refactor/extension-method-syntax-in-callbuilder
Decide extension method syntax in CallBuilder
This commit is contained in:
@@ -1016,6 +1016,12 @@ namespace ICSharpCode.Decompiler.Tests
|
|||||||
await RunForLibrary(cscOptions: cscOptions);
|
await RunForLibrary(cscOptions: cscOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task ExtensionMethods([ValueSource(nameof(roslyn2OrNewerOptions))] CompilerOptions cscOptions)
|
||||||
|
{
|
||||||
|
await RunForLibrary(cscOptions: cscOptions);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task CS9_ExtensionGetEnumerator([ValueSource(nameof(roslyn3OrNewerWithNet40Options))] CompilerOptions cscOptions)
|
public async Task CS9_ExtensionGetEnumerator([ValueSource(nameof(roslyn3OrNewerWithNet40Options))] CompilerOptions cscOptions)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -529,7 +529,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction
|
|||||||
|
|
||||||
public static void ExtensionDelegateReference(IEnumerable<int> ints)
|
public static void ExtensionDelegateReference(IEnumerable<int> ints)
|
||||||
{
|
{
|
||||||
Use2(ints.Select<int, int>);
|
Use2(ints.Select);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if CS70
|
#if CS70
|
||||||
|
|||||||
@@ -512,7 +512,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
|
|||||||
|
|
||||||
public void MethodGroupAsExtensionMethod()
|
public void MethodGroupAsExtensionMethod()
|
||||||
{
|
{
|
||||||
ToCode(X(), (Expression<Func<Func<bool>>>)(() => ((IEnumerable<int>)new int[4] { 2000, 2004, 2008, 2012 }).Any<int>));
|
ToCode(X(), (Expression<Func<Func<bool>>>)(() => ((IEnumerable<int>)new int[4] { 2000, 2004, 2008, 2012 }).Any));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void MethodGroupConstant()
|
public void MethodGroupConstant()
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
|
||||||
|
{
|
||||||
|
public class ExtensionMethods
|
||||||
|
{
|
||||||
|
public struct Value
|
||||||
|
{
|
||||||
|
public int Field;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class HasInstanceMethod
|
||||||
|
{
|
||||||
|
public void Ambiguous(int i)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Simple(string text)
|
||||||
|
{
|
||||||
|
text.Print();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void NamedArgumentAfterReceiver(List<int> list)
|
||||||
|
{
|
||||||
|
list.FirstOrLast(last: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void NullReceiver()
|
||||||
|
{
|
||||||
|
((string)null).Print();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ExplicitTypeArguments(object o)
|
||||||
|
{
|
||||||
|
o.As<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RefReceiver(Value value)
|
||||||
|
{
|
||||||
|
value.Increment();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InReceiver(Value value)
|
||||||
|
{
|
||||||
|
value.Read();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ParamsExpansion(string text)
|
||||||
|
{
|
||||||
|
text.Repeat(1, 2, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InstanceMethodWinsSoTheCallStaysStatic(HasInstanceMethod x)
|
||||||
|
{
|
||||||
|
ExtensionMethodsProvider.Ambiguous(x, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Action MethodGroup(string text)
|
||||||
|
{
|
||||||
|
return text.Print;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ExtensionMethodsProvider
|
||||||
|
{
|
||||||
|
public static void Print(this string text)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int FirstOrLast<T>(this List<T> list, bool last)
|
||||||
|
{
|
||||||
|
return list.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static T As<T>(this object o) where T : class
|
||||||
|
{
|
||||||
|
return o as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Increment(this ref ExtensionMethods.Value value)
|
||||||
|
{
|
||||||
|
value.Field++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int Read(this in ExtensionMethods.Value value)
|
||||||
|
{
|
||||||
|
return value.Field;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int Repeat(this string text, params int[] values)
|
||||||
|
{
|
||||||
|
return text.Length + values.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Ambiguous(this ExtensionMethods.HasInstanceMethod x, int i)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -279,8 +279,7 @@ namespace ICSharpCode.Decompiler.CSharp
|
|||||||
new TransformFieldAndConstructorInitializers(), // must run after DeclareVariables
|
new TransformFieldAndConstructorInitializers(), // must run after DeclareVariables
|
||||||
new PrettifyAssignments(), // must run after DeclareVariables
|
new PrettifyAssignments(), // must run after DeclareVariables
|
||||||
new IntroduceUsingDeclarations(),
|
new IntroduceUsingDeclarations(),
|
||||||
new IntroduceExtensionMethods(), // must run after IntroduceUsingDeclarations
|
new IntroduceQueryExpressions(), // needs the extension method syntax CallBuilder writes
|
||||||
new IntroduceQueryExpressions(), // must run after IntroduceExtensionMethods
|
|
||||||
new CombineQueryExpressions(),
|
new CombineQueryExpressions(),
|
||||||
new NormalizeBlockStatements(),
|
new NormalizeBlockStatements(),
|
||||||
new FlattenSwitchBlocks(),
|
new FlattenSwitchBlocks(),
|
||||||
|
|||||||
@@ -66,13 +66,16 @@ namespace ICSharpCode.Decompiler.CSharp
|
|||||||
return FirstOptionalArgumentIndex;
|
return FirstOptionalArgumentIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string[]? GetArgumentNames(int skipCount = 0)
|
/// <summary>
|
||||||
|
/// The name to write each argument with, indexed like <see cref="Arguments"/>; null where
|
||||||
|
/// every argument is written positionally.
|
||||||
|
/// </summary>
|
||||||
|
public string[]? GetArgumentNames()
|
||||||
{
|
{
|
||||||
string[]? argumentNames = ArgumentNames;
|
string[]? argumentNames = ArgumentNames;
|
||||||
if (AddNamesToPrimitiveValues && IsPrimitiveValue.Any() && !IsExpandedForm
|
if (AddNamesToPrimitiveValues && IsPrimitiveValue.Any() && !IsExpandedForm
|
||||||
&& !ParameterNames.Any(string.IsNullOrEmpty))
|
&& !ParameterNames.Any(string.IsNullOrEmpty))
|
||||||
{
|
{
|
||||||
Debug.Assert(skipCount == 0);
|
|
||||||
if (argumentNames == null)
|
if (argumentNames == null)
|
||||||
{
|
{
|
||||||
argumentNames = new string[Arguments.Length];
|
argumentNames = new string[Arguments.Length];
|
||||||
@@ -106,8 +109,8 @@ namespace ICSharpCode.Decompiler.CSharp
|
|||||||
|
|
||||||
return Arguments
|
return Arguments
|
||||||
.SelectWithIndex(GetResolveResult)
|
.SelectWithIndex(GetResolveResult)
|
||||||
.Skip(skipCount)
|
|
||||||
.Take(GetActualArgumentCount())
|
.Take(GetActualArgumentCount())
|
||||||
|
.Skip(skipCount)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
ResolveResult GetResolveResult(int index, TranslatedExpression expression)
|
ResolveResult GetResolveResult(int index, TranslatedExpression expression)
|
||||||
@@ -122,28 +125,27 @@ namespace ICSharpCode.Decompiler.CSharp
|
|||||||
public IList<ResolveResult> GetArgumentResolveResultsDirect(int skipCount = 0)
|
public IList<ResolveResult> GetArgumentResolveResultsDirect(int skipCount = 0)
|
||||||
{
|
{
|
||||||
return Arguments
|
return Arguments
|
||||||
.Skip(skipCount)
|
|
||||||
.Take(GetActualArgumentCount())
|
.Take(GetActualArgumentCount())
|
||||||
|
.Skip(skipCount)
|
||||||
.Select(a => a.ResolveResult)
|
.Select(a => a.ResolveResult)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<Expression> GetArgumentExpressions(int skipCount = 0)
|
public IEnumerable<Expression> GetArgumentExpressions(int skipCount = 0)
|
||||||
{
|
{
|
||||||
var argumentNames = GetArgumentNames(skipCount);
|
var argumentNames = GetArgumentNames();
|
||||||
int argumentCount = GetActualArgumentCount();
|
int argumentCount = GetActualArgumentCount();
|
||||||
var useImplicitlyTypedOut = UseImplicitlyTypedOut;
|
var useImplicitlyTypedOut = UseImplicitlyTypedOut;
|
||||||
if (argumentNames == null)
|
if (argumentNames == null)
|
||||||
{
|
{
|
||||||
return Arguments.Skip(skipCount).Take(argumentCount).Select(arg => AddAnnotations(arg.Expression));
|
return Arguments.Take(argumentCount).Skip(skipCount).Select(arg => AddAnnotations(arg.Expression));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Debug.Assert(skipCount == 0);
|
|
||||||
// Zip stops at the shorter sequence, so names that ran short would silently drop
|
// Zip stops at the shorter sequence, so names that ran short would silently drop
|
||||||
// the arguments past their end instead of leaving them unnamed.
|
// the arguments past their end instead of leaving them unnamed.
|
||||||
Debug.Assert(argumentNames.Length == argumentCount);
|
Debug.Assert(argumentNames.Length == argumentCount);
|
||||||
return Arguments.Take(argumentCount).Zip(argumentNames,
|
return Arguments.Take(argumentCount).Skip(skipCount).Zip(argumentNames.Skip(skipCount),
|
||||||
(arg, name) => {
|
(arg, name) => {
|
||||||
if (name == null)
|
if (name == null)
|
||||||
return AddAnnotations(arg.Expression);
|
return AddAnnotations(arg.Expression);
|
||||||
@@ -631,6 +633,16 @@ namespace ICSharpCode.Decompiler.CSharp
|
|||||||
{
|
{
|
||||||
argumentList.FirstOptionalArgumentIndex = -1;
|
argumentList.FirstOptionalArgumentIndex = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (TryUseExtensionMethodSyntax(foundMethod, transform, argumentList, out var extensionTarget,
|
||||||
|
out var extensionTargetResolveResult))
|
||||||
|
{
|
||||||
|
return new InvocationExpression(extensionTarget, argumentList.GetArgumentExpressions(skipCount: 1))
|
||||||
|
.WithRR(new CSharpInvocationResolveResult(extensionTargetResolveResult, foundMethod,
|
||||||
|
argumentList.GetArgumentResolveResultsDirect(skipCount: 1),
|
||||||
|
isExtensionMethodInvocation: true, isExpandedForm: argumentList.IsExpandedForm));
|
||||||
|
}
|
||||||
|
|
||||||
if ((transform & ReferenceTransformation.RequireTarget) != 0)
|
if ((transform & ReferenceTransformation.RequireTarget) != 0)
|
||||||
{
|
{
|
||||||
targetExpr = new MemberReferenceExpression(target.Expression, methodName);
|
targetExpr = new MemberReferenceExpression(target.Expression, methodName);
|
||||||
@@ -1267,6 +1279,99 @@ namespace ICSharpCode.Decompiler.CSharp
|
|||||||
return object.Equals(parameter.GetConstantValue(), arg.ResolveResult.ConstantValue);
|
return object.Equals(parameter.GetConstantValue(), arg.ResolveResult.ConstantValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extension method syntax is the shortest spelling of a call to an extension method: the
|
||||||
|
/// first argument becomes the target and the rest stay arguments. It is only available
|
||||||
|
/// when the name resolves back to <paramref name="foundMethod"/> from that target, which a
|
||||||
|
/// competing instance method, another extension method in scope or an inaccessible
|
||||||
|
/// declaring type can all prevent - then the call is written as the static call it is in
|
||||||
|
/// IL and this returns false.
|
||||||
|
/// <paramref name="transform"/> must be the one the call is being written with, because
|
||||||
|
/// whether the type arguments are spelled out decides which overloads the name reaches.
|
||||||
|
/// </summary>
|
||||||
|
private bool TryUseExtensionMethodSyntax(IParameterizedMember foundMethod, ReferenceTransformation transform,
|
||||||
|
ArgumentList argumentList, [NotNullWhen(true)] out MemberReferenceExpression? memberRef,
|
||||||
|
[NotNullWhen(true)] out ResolveResult? targetResolveResult)
|
||||||
|
{
|
||||||
|
memberRef = null;
|
||||||
|
targetResolveResult = null;
|
||||||
|
// The overload the call resolves to, not the one the IL named: the two can differ in
|
||||||
|
// the type arguments inference substitutes, and the check below compares the candidate
|
||||||
|
// it finds against this one for equality.
|
||||||
|
if (foundMethod is not IMethod method)
|
||||||
|
return false;
|
||||||
|
// IsExtensionMethod is false unless settings.ExtensionMethods asked the type system
|
||||||
|
// for it, so it is the gate for the setting as well.
|
||||||
|
if (!method.IsExtensionMethod || argumentList.Length == 0)
|
||||||
|
return false;
|
||||||
|
// Without using declarations every type is named in full instead. An extension method
|
||||||
|
// has no such spelling: the namespace has to be imported for the name to be found.
|
||||||
|
if (!settings.UsingDeclarations)
|
||||||
|
return false;
|
||||||
|
// The target is the first argument, so it has to be written first and positionally.
|
||||||
|
var argumentNames = argumentList.GetArgumentNames();
|
||||||
|
if (argumentNames?[0] != null)
|
||||||
|
return false;
|
||||||
|
if (argumentList.ArgumentToParameterMap is { } map && map[0] != 0)
|
||||||
|
return false;
|
||||||
|
if (argumentList.FirstOptionalArgumentIndex == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var firstArgument = argumentList.Arguments[0];
|
||||||
|
bool writeTypeArguments = (transform & ReferenceTransformation.RequireTypeArguments) != 0
|
||||||
|
&& (!settings.AnonymousTypes || !method.TypeArguments.Any(a => a.ContainsAnonymousType()));
|
||||||
|
IType[] typeArguments = writeTypeArguments ? method.TypeArguments.ToArray() : Empty<IType>.Array;
|
||||||
|
|
||||||
|
var directionExpression = firstArgument.Expression as DirectionExpression;
|
||||||
|
ResolveResult target = firstArgument.ResolveResult;
|
||||||
|
if (target is ConstantResolveResult { ConstantValue: null } nullLiteral)
|
||||||
|
{
|
||||||
|
// A null literal has no type of its own; the target type is the one the parameter
|
||||||
|
// gives it, which the cast below then writes out.
|
||||||
|
target = new ConversionResolveResult(method.Parameters[0].Type, nullLiteral,
|
||||||
|
Conversion.NullLiteralConversion);
|
||||||
|
}
|
||||||
|
else if (directionExpression != null)
|
||||||
|
{
|
||||||
|
if (!settings.RefExtensionMethods || directionExpression.FieldDirection == FieldDirection.Out)
|
||||||
|
return false;
|
||||||
|
target = directionExpression.Expression.GetResolveResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
int actualArgumentCount = argumentList.GetActualArgumentCount();
|
||||||
|
string[]? remainingNames = argumentNames?.Take(actualArgumentCount).Skip(1).ToArray();
|
||||||
|
if (remainingNames != null && remainingNames.All(name => name == null))
|
||||||
|
remainingNames = null;
|
||||||
|
if (!resolver.CanTransformToExtensionMethodCall(method, typeArguments, target,
|
||||||
|
argumentList.GetArgumentResolveResults(skipCount: 1).ToArray(), remainingNames))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Expression targetExpression;
|
||||||
|
if (directionExpression != null)
|
||||||
|
{
|
||||||
|
// 'ref x.Ext()' is not a thing: the target carries the reference implicitly.
|
||||||
|
targetExpression = directionExpression.Expression.Detach();
|
||||||
|
}
|
||||||
|
else if (firstArgument.Expression is NullReferenceExpression)
|
||||||
|
{
|
||||||
|
targetExpression = new CastExpression(
|
||||||
|
expressionBuilder.ConvertType(method.Parameters[0].Type), firstArgument.Expression);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
targetExpression = firstArgument.Expression;
|
||||||
|
}
|
||||||
|
memberRef = new MemberReferenceExpression(targetExpression, method.Name);
|
||||||
|
if (writeTypeArguments)
|
||||||
|
{
|
||||||
|
memberRef.TypeArguments.AddRange(method.TypeArguments.Select(expressionBuilder.ConvertType));
|
||||||
|
}
|
||||||
|
targetResolveResult = target;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private ReferenceTransformation GetRequiredTransformationsForCall(ExpectedTargetDetails expectedTargetDetails, IMethod method,
|
private ReferenceTransformation GetRequiredTransformationsForCall(ExpectedTargetDetails expectedTargetDetails, IMethod method,
|
||||||
ref TranslatedExpression target, ref ArgumentList argumentList, ReferenceTransformation allowedTransforms, out IParameterizedMember? foundMethod)
|
ref TranslatedExpression target, ref ArgumentList argumentList, ReferenceTransformation allowedTransforms, out IParameterizedMember? foundMethod)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -814,8 +814,12 @@ namespace ICSharpCode.Decompiler.CSharp
|
|||||||
result = resolver.ResolveMemberAccess(target, method.Name, typeArguments, NameLookupMode.InvocationTarget) as MethodGroupResolveResult;
|
result = resolver.ResolveMemberAccess(target, method.Name, typeArguments, NameLookupMode.InvocationTarget) as MethodGroupResolveResult;
|
||||||
if (result == null)
|
if (result == null)
|
||||||
return false;
|
return false;
|
||||||
|
// The receiver is the target, not an argument: the delegate being built has one
|
||||||
|
// parameter fewer than the method, so passing the receiver's parameter too leaves
|
||||||
|
// overload resolution with one argument too many and it reports every candidate
|
||||||
|
// ambiguous.
|
||||||
or = ((MethodGroupResolveResult)result).PerformOverloadResolution(resolver.CurrentTypeResolveContext.Compilation,
|
or = ((MethodGroupResolveResult)result).PerformOverloadResolution(resolver.CurrentTypeResolveContext.Compilation,
|
||||||
method.Parameters.SelectReadOnlyArray(p => new TypeResolveResult(p.Type)),
|
method.Parameters.Skip(1).Select(p => (ResolveResult)new TypeResolveResult(p.Type)).ToArray(),
|
||||||
argumentNames: null, allowExtensionMethods: true);
|
argumentNames: null, allowExtensionMethods: true);
|
||||||
if (or == null || or.IsAmbiguous)
|
if (or == null || or.IsAmbiguous)
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ namespace ICSharpCode.Decompiler.CSharp
|
|||||||
this.compilation = decompilationContext.Compilation;
|
this.compilation = decompilationContext.Compilation;
|
||||||
this.resolver = new CSharpResolver(new CSharpTypeResolveContext(
|
this.resolver = new CSharpResolver(new CSharpTypeResolveContext(
|
||||||
compilation.MainModule,
|
compilation.MainModule,
|
||||||
decompileRun.UsingScope,
|
decompileRun.GetUsingScopeFor(decompilationContext.CurrentTypeDefinition?.Namespace),
|
||||||
decompilationContext.CurrentTypeDefinition,
|
decompilationContext.CurrentTypeDefinition,
|
||||||
decompilationContext.CurrentMember
|
decompilationContext.CurrentMember
|
||||||
));
|
));
|
||||||
|
|||||||
@@ -633,7 +633,9 @@ namespace ICSharpCode.Decompiler.CSharp
|
|||||||
m = getEnumeratorPattern.Match(resource);
|
m = getEnumeratorPattern.Match(resource);
|
||||||
if (!m.Success)
|
if (!m.Success)
|
||||||
{
|
{
|
||||||
// ... or the extension GetEnumeratorPattern.
|
// ... or the extension GetEnumeratorPattern. CallBuilder writes the call this
|
||||||
|
// way when extension method syntax would not resolve back to the method, e.g.
|
||||||
|
// where two imported namespaces both offer a GetEnumerator extension.
|
||||||
m = extensionGetEnumeratorPattern.Match(resource);
|
m = extensionGetEnumeratorPattern.Match(resource);
|
||||||
if (!m.Success)
|
if (!m.Success)
|
||||||
return false;
|
return false;
|
||||||
@@ -651,6 +653,10 @@ namespace ICSharpCode.Decompiler.CSharp
|
|||||||
m = getEnumeratorPattern.Match(resource);
|
m = getEnumeratorPattern.Match(resource);
|
||||||
if (!m.Success)
|
if (!m.Success)
|
||||||
return false;
|
return false;
|
||||||
|
// An extension GetEnumerator written in extension method syntax is spelled like an
|
||||||
|
// instance call, so the pattern alone no longer tells them apart.
|
||||||
|
if (resource.GetSymbol() is IMethod { IsExtensionMethod: true })
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
isAsync = ((MemberReferenceExpression)((InvocationExpression)resource).Target).MemberName == "GetAsyncEnumerator";
|
isAsync = ((MemberReferenceExpression)((InvocationExpression)resource).Target).MemberName == "GetAsyncEnumerator";
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -1,221 +0,0 @@
|
|||||||
// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
|
|
||||||
//
|
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
|
||||||
// software and associated documentation files (the "Software"), to deal in the Software
|
|
||||||
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
|
||||||
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
|
||||||
// to whom the Software is furnished to do so, subject to the following conditions:
|
|
||||||
//
|
|
||||||
// The above copyright notice and this permission notice shall be included in all copies or
|
|
||||||
// substantial portions of the Software.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
||||||
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
|
||||||
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
|
||||||
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
||||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
||||||
// DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
||||||
#nullable enable
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
using ICSharpCode.Decompiler.CSharp.Resolver;
|
|
||||||
using ICSharpCode.Decompiler.CSharp.Syntax;
|
|
||||||
using ICSharpCode.Decompiler.Semantics;
|
|
||||||
using ICSharpCode.Decompiler.TypeSystem;
|
|
||||||
using ICSharpCode.Decompiler.TypeSystem.Implementation;
|
|
||||||
using ICSharpCode.Decompiler.Util;
|
|
||||||
|
|
||||||
namespace ICSharpCode.Decompiler.CSharp.Transforms
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Converts extension method calls into infix syntax.
|
|
||||||
/// </summary>
|
|
||||||
public class IntroduceExtensionMethods : DepthFirstAstVisitor, IAstTransform
|
|
||||||
{
|
|
||||||
[AllowNull]
|
|
||||||
TransformContext context;
|
|
||||||
[AllowNull]
|
|
||||||
CSharpResolver resolver;
|
|
||||||
[AllowNull]
|
|
||||||
CSharpConversions conversions;
|
|
||||||
|
|
||||||
public void Run(AstNode rootNode, TransformContext context)
|
|
||||||
{
|
|
||||||
this.context = context;
|
|
||||||
this.conversions = CSharpConversions.Get(context.TypeSystem);
|
|
||||||
// The decompiler attaches a UsingScope annotation to the syntax-tree root.
|
|
||||||
InitializeContext(rootNode.Annotation<UsingScope>()!);
|
|
||||||
rootNode.AcceptVisitor(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
void InitializeContext(UsingScope usingScope)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(context.CurrentTypeDefinition?.Namespace))
|
|
||||||
{
|
|
||||||
foreach (string ns in context.CurrentTypeDefinition.Namespace.Split('.'))
|
|
||||||
{
|
|
||||||
usingScope = usingScope.WithNestedNamespace(ns);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var currentContext = new CSharpTypeResolveContext(context.TypeSystem.MainModule, usingScope, context.CurrentTypeDefinition);
|
|
||||||
this.resolver = new CSharpResolver(currentContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void VisitNamespaceDeclaration(NamespaceDeclaration namespaceDeclaration)
|
|
||||||
{
|
|
||||||
var usingScope = resolver.CurrentUsingScope;
|
|
||||||
foreach (string ident in namespaceDeclaration.Identifiers)
|
|
||||||
{
|
|
||||||
usingScope = usingScope.WithNestedNamespace(ident);
|
|
||||||
}
|
|
||||||
var previousResolver = this.resolver;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
this.resolver = this.resolver.WithCurrentUsingScope(usingScope);
|
|
||||||
base.VisitNamespaceDeclaration(namespaceDeclaration);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
this.resolver = previousResolver;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void VisitTypeDeclaration(TypeDeclaration typeDeclaration)
|
|
||||||
{
|
|
||||||
var previousResolver = this.resolver;
|
|
||||||
this.resolver = resolver.WithCurrentTypeDefinition(typeDeclaration.GetSymbol() as ITypeDefinition);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
base.VisitTypeDeclaration(typeDeclaration);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
this.resolver = previousResolver;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void VisitInvocationExpression(InvocationExpression invocationExpression)
|
|
||||||
{
|
|
||||||
base.VisitInvocationExpression(invocationExpression);
|
|
||||||
if (!CanTransformToExtensionMethodCall(resolver, invocationExpression, out var memberRefExpr,
|
|
||||||
out var target, out var firstArgument))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var method = (IMethod)invocationExpression.GetSymbol()!;
|
|
||||||
bool stepped = false;
|
|
||||||
if (firstArgument is DirectionExpression dirExpr)
|
|
||||||
{
|
|
||||||
if (!context.Settings.RefExtensionMethods || dirExpr.FieldDirection == FieldDirection.Out)
|
|
||||||
return;
|
|
||||||
context.Step("Introduce extension method call", invocationExpression);
|
|
||||||
stepped = true;
|
|
||||||
// A ref/out direction expression always wraps an operand.
|
|
||||||
firstArgument = dirExpr.Expression!;
|
|
||||||
target = firstArgument.GetResolveResult();
|
|
||||||
dirExpr.Detach();
|
|
||||||
}
|
|
||||||
else if (firstArgument is NullReferenceExpression)
|
|
||||||
{
|
|
||||||
Debug.Assert(context.RequiredNamespacesSuperset.Contains(method.Parameters[0].Type.Namespace));
|
|
||||||
context.Step("Introduce extension method call", invocationExpression);
|
|
||||||
stepped = true;
|
|
||||||
// The replacement is a freshly created CastExpression, so the result is non-null.
|
|
||||||
firstArgument = firstArgument.ReplaceWith(expr => new CastExpression(context.TypeSystemAstBuilder.ConvertType(method.Parameters[0].Type), expr.Detach()))!;
|
|
||||||
}
|
|
||||||
if (invocationExpression.Target is IdentifierExpression identifierExpression)
|
|
||||||
{
|
|
||||||
if (!stepped)
|
|
||||||
context.Step("Introduce extension method call", invocationExpression);
|
|
||||||
identifierExpression.Detach();
|
|
||||||
memberRefExpr = new MemberReferenceExpression(firstArgument.Detach(), method.Name, identifierExpression.TypeArguments.Detach());
|
|
||||||
invocationExpression.Target = memberRefExpr;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (!stepped)
|
|
||||||
context.Step("Introduce extension method call", invocationExpression);
|
|
||||||
// The target is not an IdentifierExpression, so CanTransformToExtensionMethodCall
|
|
||||||
// matched the MemberReferenceExpression case and memberRefExpr is non-null.
|
|
||||||
memberRefExpr!.Target = firstArgument.Detach();
|
|
||||||
}
|
|
||||||
if (invocationExpression.GetResolveResult() is CSharpInvocationResolveResult irr)
|
|
||||||
{
|
|
||||||
// do not forget to update the CSharpInvocationResolveResult => set IsExtensionMethodInvocation == true
|
|
||||||
invocationExpression.RemoveAnnotations<CSharpInvocationResolveResult>();
|
|
||||||
var newResolveResult = new CSharpInvocationResolveResult(
|
|
||||||
irr.TargetResult, irr.Member, irr.Arguments, irr.OverloadResolutionErrors,
|
|
||||||
isExtensionMethodInvocation: true, irr.IsExpandedForm, irr.IsDelegateInvocation,
|
|
||||||
irr.GetArgumentToParameterMap(), irr.InitializerStatements);
|
|
||||||
invocationExpression.AddAnnotation(newResolveResult);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool CanTransformToExtensionMethodCall(CSharpResolver resolver,
|
|
||||||
InvocationExpression invocationExpression, out MemberReferenceExpression? memberRefExpr,
|
|
||||||
[NotNullWhen(true)] out ResolveResult? target,
|
|
||||||
[NotNullWhen(true)] out Expression? firstArgument)
|
|
||||||
{
|
|
||||||
var method = invocationExpression.GetSymbol() as IMethod;
|
|
||||||
memberRefExpr = null;
|
|
||||||
target = null;
|
|
||||||
firstArgument = null;
|
|
||||||
if (method == null || !method.IsExtensionMethod || !invocationExpression.Arguments.Any())
|
|
||||||
return false;
|
|
||||||
IReadOnlyList<IType> typeArguments;
|
|
||||||
switch (invocationExpression.Target)
|
|
||||||
{
|
|
||||||
case MemberReferenceExpression mre:
|
|
||||||
typeArguments = mre.TypeArguments.Any() ? method.TypeArguments : EmptyList<IType>.Instance;
|
|
||||||
memberRefExpr = mre;
|
|
||||||
break;
|
|
||||||
case IdentifierExpression ide:
|
|
||||||
typeArguments = ide.TypeArguments.Any() ? method.TypeArguments : EmptyList<IType>.Instance;
|
|
||||||
memberRefExpr = null;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
firstArgument = invocationExpression.Arguments.First();
|
|
||||||
if (firstArgument is NamedArgumentExpression)
|
|
||||||
return false;
|
|
||||||
target = firstArgument.GetResolveResult();
|
|
||||||
if (target is ConstantResolveResult crr && crr.ConstantValue == null)
|
|
||||||
{
|
|
||||||
target = new ConversionResolveResult(method.Parameters[0].Type, crr, Conversion.NullLiteralConversion);
|
|
||||||
}
|
|
||||||
else if (firstArgument is DirectionExpression de)
|
|
||||||
{
|
|
||||||
target = de.Expression.GetResolveResult();
|
|
||||||
}
|
|
||||||
Debug.Assert(target != null);
|
|
||||||
ResolveResult[] args = new ResolveResult[invocationExpression.Arguments.Count - 1];
|
|
||||||
string[]? argNames = null;
|
|
||||||
int pos = 0;
|
|
||||||
foreach (var arg in invocationExpression.Arguments.Skip(1))
|
|
||||||
{
|
|
||||||
if (arg is NamedArgumentExpression nae)
|
|
||||||
{
|
|
||||||
if (argNames == null)
|
|
||||||
{
|
|
||||||
argNames = new string[args.Length];
|
|
||||||
}
|
|
||||||
argNames[pos] = nae.Name;
|
|
||||||
args[pos] = nae.Expression.GetResolveResult();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
args[pos] = arg.GetResolveResult();
|
|
||||||
}
|
|
||||||
pos++;
|
|
||||||
}
|
|
||||||
return resolver.CanTransformToExtensionMethodCall(method, typeArguments, target, args, argNames);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -146,6 +146,20 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
|
|||||||
currentNamespace = oldNamespace;
|
currentNamespace = oldNamespace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void VisitInvocationExpression(InvocationExpression invocationExpression)
|
||||||
|
{
|
||||||
|
// A call written in extension method syntax no longer names its declaring type,
|
||||||
|
// so nothing else in the tree asks for that namespace to be imported - and
|
||||||
|
// without the import the name does not resolve back to this method.
|
||||||
|
if (invocationExpression.GetResolveResult() is CSharpInvocationResolveResult {
|
||||||
|
IsExtensionMethodInvocation: true, Member: var extensionMethod
|
||||||
|
})
|
||||||
|
{
|
||||||
|
AddImportedNamespace(extensionMethod.DeclaringType);
|
||||||
|
}
|
||||||
|
base.VisitInvocationExpression(invocationExpression);
|
||||||
|
}
|
||||||
|
|
||||||
public override void VisitForeachStatement(ForeachStatement foreachStatement)
|
public override void VisitForeachStatement(ForeachStatement foreachStatement)
|
||||||
{
|
{
|
||||||
var annotation = foreachStatement.Annotation<ForeachAnnotation>();
|
var annotation = foreachStatement.Annotation<ForeachAnnotation>();
|
||||||
|
|||||||
@@ -48,6 +48,31 @@ namespace ICSharpCode.Decompiler
|
|||||||
|
|
||||||
public UsingScope UsingScope { get; }
|
public UsingScope UsingScope { get; }
|
||||||
|
|
||||||
|
readonly Dictionary<string, UsingScope> nestedUsingScopes = new Dictionary<string, UsingScope>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="UsingScope"/> as seen from inside <paramref name="namespaceName"/>.
|
||||||
|
/// C# looks for extension methods one namespace at a time, innermost first, so a scope that
|
||||||
|
/// is not nested into the namespace the code is written in lets an extension method declared
|
||||||
|
/// there compete with merely imported ones instead of beating them. Memoized, because the
|
||||||
|
/// list of extension methods a scope can reach is built once per scope.
|
||||||
|
/// </summary>
|
||||||
|
public UsingScope GetUsingScopeFor(string namespaceName)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(namespaceName))
|
||||||
|
return UsingScope;
|
||||||
|
if (!nestedUsingScopes.TryGetValue(namespaceName, out var scope))
|
||||||
|
{
|
||||||
|
scope = UsingScope;
|
||||||
|
foreach (string part in namespaceName.Split('.'))
|
||||||
|
{
|
||||||
|
scope = scope.WithNestedNamespace(part);
|
||||||
|
}
|
||||||
|
nestedUsingScopes.Add(namespaceName, scope);
|
||||||
|
}
|
||||||
|
return scope;
|
||||||
|
}
|
||||||
|
|
||||||
public DecompileRun(DecompilerSettings settings, UsingScope usingScope)
|
public DecompileRun(DecompilerSettings settings, UsingScope usingScope)
|
||||||
{
|
{
|
||||||
this.Settings = settings ?? throw new ArgumentNullException(nameof(settings));
|
this.Settings = settings ?? throw new ArgumentNullException(nameof(settings));
|
||||||
|
|||||||
@@ -1476,8 +1476,7 @@ new DeclareVariables(), <span class="cm">// should run after most transforms tha
|
|||||||
new TransformFieldAndConstructorInitializers(), <span class="cm">// must run after DeclareVariables</span>
|
new TransformFieldAndConstructorInitializers(), <span class="cm">// must run after DeclareVariables</span>
|
||||||
new PrettifyAssignments(), <span class="cm">// must run after DeclareVariables</span>
|
new PrettifyAssignments(), <span class="cm">// must run after DeclareVariables</span>
|
||||||
new IntroduceUsingDeclarations(),
|
new IntroduceUsingDeclarations(),
|
||||||
new IntroduceExtensionMethods(), <span class="cm">// must run after IntroduceUsingDeclarations</span>
|
new IntroduceQueryExpressions(), <span class="cm">// needs the extension method syntax CallBuilder writes</span>
|
||||||
new IntroduceQueryExpressions(), <span class="cm">// must run after IntroduceExtensionMethods</span>
|
|
||||||
new CombineQueryExpressions(),
|
new CombineQueryExpressions(),
|
||||||
new NormalizeBlockStatements(),
|
new NormalizeBlockStatements(),
|
||||||
new FlattenSwitchBlocks(),
|
new FlattenSwitchBlocks(),
|
||||||
@@ -1522,10 +1521,11 @@ eventual imports to answer ambiguity questions — null propagation, for exa
|
|||||||
<code>a != null ? Extensions.Method(a) : null</code> to <code>a?.Method()</code> only if it can
|
<code>a != null ? Extensions.Method(a) : null</code> to <code>a?.Method()</code> only if it can
|
||||||
guarantee the extension-method form will resolve unambiguously. Since many compiler-generated calls
|
guarantee the extension-method form will resolve unambiguously. Since many compiler-generated calls
|
||||||
have disappeared by the time the AST exists, the AST-derived set is a subset of the
|
have disappeared by the time the AST exists, the AST-derived set is a subset of the
|
||||||
<code>DecompileRun</code>'s, and only this subset gets directives. Only after imports exist can
|
<code>DecompileRun</code>'s, and only this subset gets directives. Extension-method syntax — <code>Enumerable.Where(xs, p)</code> written as
|
||||||
<strong><code>IntroduceExtensionMethods</code></strong> rewrite
|
<code>xs.Where(p)</code> — is not decided here: <code>CallBuilder</code> picks it while it
|
||||||
<code>Enumerable.Where(xs, p)</code> to <code>xs.Where(p)</code> (an ordering that predates the
|
builds the call, resolving against the <code>DecompileRun</code> superset. The collector
|
||||||
<code>DecompileRun</code> namespace superset), and only after <em>that</em> can
|
therefore has to import the namespace of any call it finds in that form, because nothing in the
|
||||||
|
tree names the declaring type any more. Only then can
|
||||||
<strong><code>IntroduceQueryExpressions</code></strong> rebuild <code>from …
|
<strong><code>IntroduceQueryExpressions</code></strong> rebuild <code>from …
|
||||||
select</code> query syntax (running the C# spec's query translation backwards), with
|
select</code> query syntax (running the C# spec's query translation backwards), with
|
||||||
<strong><code>CombineQueryExpressions</code></strong> merging nested queries and dissolving the
|
<strong><code>CombineQueryExpressions</code></strong> merging nested queries and dissolving the
|
||||||
|
|||||||
Reference in New Issue
Block a user