mirror of
https://github.com/icsharpcode/ILSpy.git
synced 2026-10-11 21:59:56 +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);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ExtensionMethods([ValueSource(nameof(roslyn2OrNewerOptions))] CompilerOptions cscOptions)
|
||||
{
|
||||
await RunForLibrary(cscOptions: cscOptions);
|
||||
}
|
||||
|
||||
[Test]
|
||||
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)
|
||||
{
|
||||
Use2(ints.Select<int, int>);
|
||||
Use2(ints.Select);
|
||||
}
|
||||
|
||||
#if CS70
|
||||
|
||||
@@ -512,7 +512,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
|
||||
|
||||
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()
|
||||
|
||||
@@ -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 PrettifyAssignments(), // must run after DeclareVariables
|
||||
new IntroduceUsingDeclarations(),
|
||||
new IntroduceExtensionMethods(), // must run after IntroduceUsingDeclarations
|
||||
new IntroduceQueryExpressions(), // must run after IntroduceExtensionMethods
|
||||
new IntroduceQueryExpressions(), // needs the extension method syntax CallBuilder writes
|
||||
new CombineQueryExpressions(),
|
||||
new NormalizeBlockStatements(),
|
||||
new FlattenSwitchBlocks(),
|
||||
|
||||
@@ -66,13 +66,16 @@ namespace ICSharpCode.Decompiler.CSharp
|
||||
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;
|
||||
if (AddNamesToPrimitiveValues && IsPrimitiveValue.Any() && !IsExpandedForm
|
||||
&& !ParameterNames.Any(string.IsNullOrEmpty))
|
||||
{
|
||||
Debug.Assert(skipCount == 0);
|
||||
if (argumentNames == null)
|
||||
{
|
||||
argumentNames = new string[Arguments.Length];
|
||||
@@ -106,8 +109,8 @@ namespace ICSharpCode.Decompiler.CSharp
|
||||
|
||||
return Arguments
|
||||
.SelectWithIndex(GetResolveResult)
|
||||
.Skip(skipCount)
|
||||
.Take(GetActualArgumentCount())
|
||||
.Skip(skipCount)
|
||||
.ToArray();
|
||||
|
||||
ResolveResult GetResolveResult(int index, TranslatedExpression expression)
|
||||
@@ -122,28 +125,27 @@ namespace ICSharpCode.Decompiler.CSharp
|
||||
public IList<ResolveResult> GetArgumentResolveResultsDirect(int skipCount = 0)
|
||||
{
|
||||
return Arguments
|
||||
.Skip(skipCount)
|
||||
.Take(GetActualArgumentCount())
|
||||
.Skip(skipCount)
|
||||
.Select(a => a.ResolveResult)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
public IEnumerable<Expression> GetArgumentExpressions(int skipCount = 0)
|
||||
{
|
||||
var argumentNames = GetArgumentNames(skipCount);
|
||||
var argumentNames = GetArgumentNames();
|
||||
int argumentCount = GetActualArgumentCount();
|
||||
var useImplicitlyTypedOut = UseImplicitlyTypedOut;
|
||||
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
|
||||
{
|
||||
Debug.Assert(skipCount == 0);
|
||||
// Zip stops at the shorter sequence, so names that ran short would silently drop
|
||||
// the arguments past their end instead of leaving them unnamed.
|
||||
Debug.Assert(argumentNames.Length == argumentCount);
|
||||
return Arguments.Take(argumentCount).Zip(argumentNames,
|
||||
return Arguments.Take(argumentCount).Skip(skipCount).Zip(argumentNames.Skip(skipCount),
|
||||
(arg, name) => {
|
||||
if (name == null)
|
||||
return AddAnnotations(arg.Expression);
|
||||
@@ -631,6 +633,16 @@ namespace ICSharpCode.Decompiler.CSharp
|
||||
{
|
||||
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)
|
||||
{
|
||||
targetExpr = new MemberReferenceExpression(target.Expression, methodName);
|
||||
@@ -1267,6 +1279,99 @@ namespace ICSharpCode.Decompiler.CSharp
|
||||
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,
|
||||
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;
|
||||
if (result == null)
|
||||
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,
|
||||
method.Parameters.SelectReadOnlyArray(p => new TypeResolveResult(p.Type)),
|
||||
method.Parameters.Skip(1).Select(p => (ResolveResult)new TypeResolveResult(p.Type)).ToArray(),
|
||||
argumentNames: null, allowExtensionMethods: true);
|
||||
if (or == null || or.IsAmbiguous)
|
||||
return false;
|
||||
|
||||
@@ -100,7 +100,7 @@ namespace ICSharpCode.Decompiler.CSharp
|
||||
this.compilation = decompilationContext.Compilation;
|
||||
this.resolver = new CSharpResolver(new CSharpTypeResolveContext(
|
||||
compilation.MainModule,
|
||||
decompileRun.UsingScope,
|
||||
decompileRun.GetUsingScopeFor(decompilationContext.CurrentTypeDefinition?.Namespace),
|
||||
decompilationContext.CurrentTypeDefinition,
|
||||
decompilationContext.CurrentMember
|
||||
));
|
||||
|
||||
@@ -633,7 +633,9 @@ namespace ICSharpCode.Decompiler.CSharp
|
||||
m = getEnumeratorPattern.Match(resource);
|
||||
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);
|
||||
if (!m.Success)
|
||||
return false;
|
||||
@@ -651,6 +653,10 @@ namespace ICSharpCode.Decompiler.CSharp
|
||||
m = getEnumeratorPattern.Match(resource);
|
||||
if (!m.Success)
|
||||
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";
|
||||
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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
var annotation = foreachStatement.Annotation<ForeachAnnotation>();
|
||||
|
||||
@@ -48,6 +48,31 @@ namespace ICSharpCode.Decompiler
|
||||
|
||||
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)
|
||||
{
|
||||
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 PrettifyAssignments(), <span class="cm">// must run after DeclareVariables</span>
|
||||
new IntroduceUsingDeclarations(),
|
||||
new IntroduceExtensionMethods(), <span class="cm">// must run after IntroduceUsingDeclarations</span>
|
||||
new IntroduceQueryExpressions(), <span class="cm">// must run after IntroduceExtensionMethods</span>
|
||||
new IntroduceQueryExpressions(), <span class="cm">// needs the extension method syntax CallBuilder writes</span>
|
||||
new CombineQueryExpressions(),
|
||||
new NormalizeBlockStatements(),
|
||||
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
|
||||
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
|
||||
<code>DecompileRun</code>'s, and only this subset gets directives. Only after imports exist can
|
||||
<strong><code>IntroduceExtensionMethods</code></strong> rewrite
|
||||
<code>Enumerable.Where(xs, p)</code> to <code>xs.Where(p)</code> (an ordering that predates the
|
||||
<code>DecompileRun</code> namespace superset), and only after <em>that</em> can
|
||||
<code>DecompileRun</code>'s, and only this subset gets directives. Extension-method syntax — <code>Enumerable.Where(xs, p)</code> written as
|
||||
<code>xs.Where(p)</code> — is not decided here: <code>CallBuilder</code> picks it while it
|
||||
builds the call, resolving against the <code>DecompileRun</code> superset. The collector
|
||||
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 …
|
||||
select</code> query syntax (running the C# spec's query translation backwards), with
|
||||
<strong><code>CombineQueryExpressions</code></strong> merging nested queries and dissolving the
|
||||
|
||||
Reference in New Issue
Block a user