Browse Source

#1083: Initial implementation of named arguments.

pull/1167/head
Daniel Grunwald 7 years ago
parent
commit
810adea8b4
  1. 124
      ICSharpCode.Decompiler/CSharp/CallBuilder.cs
  2. 4
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  3. 6
      ICSharpCode.Decompiler/CSharp/Resolver/CSharpInvocationResolveResult.cs
  4. 2
      ICSharpCode.Decompiler/CSharp/Resolver/OverloadResolution.cs
  5. 2
      ICSharpCode.Decompiler/DecompilerSettings.cs
  6. 9
      ICSharpCode.Decompiler/IL/ILVariable.cs
  7. 4
      ICSharpCode.Decompiler/IL/Instructions/Block.cs
  8. 10
      ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
  9. 4
      ICSharpCode.Decompiler/TypeSystem/VarArgInstanceMethod.cs

124
ICSharpCode.Decompiler/CSharp/CallBuilder.cs

@ -74,10 +74,13 @@ namespace ICSharpCode.Decompiler.CSharp @@ -74,10 +74,13 @@ namespace ICSharpCode.Decompiler.CSharp
elementRRs.ToImmutableArray()
)).WithILInstruction(inst);
}
return Build(inst.OpCode, inst.Method, inst.Arguments, inst.ConstrainedTo).WithILInstruction(inst);
return Build(inst.OpCode, inst.Method, inst.Arguments, constrainedTo: inst.ConstrainedTo)
.WithILInstruction(inst);
}
public ExpressionWithResolveResult Build(OpCode callOpCode, IMethod method, IReadOnlyList<ILInstruction> callArguments,
public ExpressionWithResolveResult Build(OpCode callOpCode, IMethod method,
IReadOnlyList<ILInstruction> callArguments,
IReadOnlyList<int> argumentToParameterMap = null,
IType constrainedTo = null)
{
// Used for Call, CallVirt and NewObj
@ -108,16 +111,32 @@ namespace ICSharpCode.Decompiler.CSharp @@ -108,16 +111,32 @@ namespace ICSharpCode.Decompiler.CSharp
}
int firstParamIndex = (method.IsStatic || callOpCode == OpCode.NewObj) ? 0 : 1;
Debug.Assert(firstParamIndex == 0 || argumentToParameterMap == null
|| argumentToParameterMap[0] == -1);
// Translate arguments to the expected parameter types
var arguments = new List<TranslatedExpression>(method.Parameters.Count);
string[] argumentNames = null;
Debug.Assert(callArguments.Count == firstParamIndex + method.Parameters.Count);
var expectedParameters = method.Parameters.ToList();
var expectedParameters = new List<IParameter>(arguments.Count); // parameters, but in argument order
bool isExpandedForm = false;
for (int i = 0; i < method.Parameters.Count; i++) {
var parameter = expectedParameters[i];
var arg = expressionBuilder.Translate(callArguments[firstParamIndex + i], parameter.Type);
if (parameter.IsParams && i + 1 == method.Parameters.Count) {
for (int i = firstParamIndex; i < callArguments.Count; i++) {
IParameter parameter;
if (argumentToParameterMap != null) {
if (argumentNames == null && argumentToParameterMap[i] != i - firstParamIndex) {
// Starting at the first argument that is out-of-place,
// assign names to that argument and all following arguments:
argumentNames = new string[method.Parameters.Count];
}
parameter = method.Parameters[argumentToParameterMap[i]];
if (argumentNames != null) {
argumentNames[arguments.Count] = parameter.Name;
}
} else {
parameter = method.Parameters[i - firstParamIndex];
}
var arg = expressionBuilder.Translate(callArguments[i], parameter.Type);
if (parameter.IsParams && i + 1 == callArguments.Count && argumentToParameterMap == null) {
// Parameter is marked params
// If the argument is an array creation, inline all elements into the call and add missing default values.
// Otherwise handle it normally.
@ -138,7 +157,8 @@ namespace ICSharpCode.Decompiler.CSharp @@ -138,7 +157,8 @@ namespace ICSharpCode.Decompiler.CSharp
expandedArguments.Add(expressionBuilder.GetDefaultValueExpression(elementType).WithoutILInstruction());
}
}
if (IsUnambiguousCall(expectedTargetDetails, method, target.ResolveResult, Empty<IType>.Array, expandedArguments, out _) == OverloadResolutionErrors.None) {
Debug.Assert(argumentNames == null);
if (IsUnambiguousCall(expectedTargetDetails, method, target.ResolveResult, Empty<IType>.Array, expandedArguments, argumentNames, out _) == OverloadResolutionErrors.None) {
isExpandedForm = true;
expectedParameters = expandedParameters;
arguments = expandedArguments.SelectList(a => new TranslatedExpression(a.Expression.Detach()));
@ -147,9 +167,8 @@ namespace ICSharpCode.Decompiler.CSharp @@ -147,9 +167,8 @@ namespace ICSharpCode.Decompiler.CSharp
}
}
arguments.Add(arg.ConvertTo(parameter.Type, expressionBuilder, allowImplicitConversion: true));
if (parameter.IsOut && arguments[i].Expression is DirectionExpression dirExpr && arguments[i].ResolveResult is ByReferenceResolveResult brrr) {
arg = arg.ConvertTo(parameter.Type, expressionBuilder, allowImplicitConversion: true);
if (parameter.IsOut && arg.Expression is DirectionExpression dirExpr && arg.ResolveResult is ByReferenceResolveResult brrr) {
dirExpr.FieldDirection = FieldDirection.Out;
dirExpr.RemoveAnnotations<ByReferenceResolveResult>();
if (brrr.ElementResult == null)
@ -157,8 +176,11 @@ namespace ICSharpCode.Decompiler.CSharp @@ -157,8 +176,11 @@ namespace ICSharpCode.Decompiler.CSharp
else
brrr = new ByReferenceResolveResult(brrr.ElementResult, isOut: true);
dirExpr.AddAnnotation(brrr);
arguments[i] = new TranslatedExpression(dirExpr);
arg = new TranslatedExpression(dirExpr);
}
arguments.Add(arg);
expectedParameters.Add(parameter);
}
if (method is VarArgInstanceMethod) {
@ -167,19 +189,22 @@ namespace ICSharpCode.Decompiler.CSharp @@ -167,19 +189,22 @@ namespace ICSharpCode.Decompiler.CSharp
argListArg.UndocumentedExpressionType = UndocumentedExpressionType.ArgList;
int paramIndex = regularParameterCount;
var builder = expressionBuilder;
Debug.Assert(argumentToParameterMap == null && argumentNames == null);
argListArg.Arguments.AddRange(arguments.Skip(regularParameterCount).Select(arg => arg.ConvertTo(expectedParameters[paramIndex++].Type, builder).Expression));
var argListRR = new ResolveResult(SpecialType.ArgList);
arguments = arguments.Take(regularParameterCount)
.Concat(new[] { argListArg.WithoutILInstruction().WithRR(argListRR) }).ToList();
method = (IMethod)method.MemberDefinition;
method = ((VarArgInstanceMethod)method).BaseMethod;
expectedParameters = method.Parameters.ToList();
}
var argumentResolveResults = arguments.Select(arg => arg.ResolveResult).ToList();
if (callOpCode == OpCode.NewObj) {
ResolveResult rr = new CSharpInvocationResolveResult(target.ResolveResult, method, argumentResolveResults, isExpandedForm: isExpandedForm);
ResolveResult rr = new CSharpInvocationResolveResult(target.ResolveResult, method, argumentResolveResults,
isExpandedForm: isExpandedForm, argumentToParameterMap: argumentToParameterMap);
if (settings.AnonymousTypes && method.DeclaringType.IsAnonymousType()) {
Debug.Assert(argumentToParameterMap == null && argumentNames == null);
var argumentExpressions = arguments.SelectArray(arg => arg.Expression);
AnonymousTypeCreateExpression atce = new AnonymousTypeCreateExpression();
if (CanInferAnonymousTypePropertyNamesFromArguments(argumentExpressions, expectedParameters)) {
@ -196,7 +221,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -196,7 +221,7 @@ namespace ICSharpCode.Decompiler.CSharp
return atce
.WithRR(rr);
} else {
if (IsUnambiguousCall(expectedTargetDetails, method, null, Empty<IType>.Array, arguments, out _) != OverloadResolutionErrors.None) {
if (IsUnambiguousCall(expectedTargetDetails, method, null, Empty<IType>.Array, arguments, argumentNames, out _) != OverloadResolutionErrors.None) {
for (int i = 0; i < arguments.Count; i++) {
if (settings.AnonymousTypes && expectedParameters[i].Type.ContainsAnonymousType()) {
if (arguments[i].Expression is LambdaExpression lambda) {
@ -207,17 +232,21 @@ namespace ICSharpCode.Decompiler.CSharp @@ -207,17 +232,21 @@ namespace ICSharpCode.Decompiler.CSharp
}
}
}
return new ObjectCreateExpression(expressionBuilder.ConvertType(method.DeclaringType), arguments.SelectArray(arg => arg.Expression))
.WithRR(rr);
return new ObjectCreateExpression(
expressionBuilder.ConvertType(method.DeclaringType),
GetArgumentExpressions(arguments, argumentNames)
).WithRR(rr);
}
} else {
int allowedParamCount = (method.ReturnType.IsKnownType(KnownTypeCode.Void) ? 1 : 0);
if (method.IsAccessor && (method.AccessorOwner.SymbolKind == SymbolKind.Indexer || expectedParameters.Count == allowedParamCount)) {
Debug.Assert(argumentToParameterMap == null && argumentNames == null);
return HandleAccessorCall(expectedTargetDetails, method, target, arguments.ToList());
} else if (method.Name == "Invoke" && method.DeclaringType.Kind == TypeKind.Delegate && !IsNullConditional(target)) {
return new InvocationExpression(target, arguments.Select(arg => arg.Expression))
return new InvocationExpression(target, GetArgumentExpressions(arguments, argumentNames))
.WithRR(new CSharpInvocationResolveResult(target.ResolveResult, method, argumentResolveResults, isExpandedForm: isExpandedForm));
} else if (IsDelegateEqualityComparison(method, arguments)) {
Debug.Assert(argumentToParameterMap == null && argumentNames == null);
return HandleDelegateEqualityComparison(method, arguments)
.WithRR(new CSharpInvocationResolveResult(target.ResolveResult, method, argumentResolveResults, isExpandedForm: isExpandedForm));
} else if (method.IsOperator && method.Name == "op_Implicit" && arguments.Count == 1) {
@ -243,7 +272,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -243,7 +272,7 @@ namespace ICSharpCode.Decompiler.CSharp
var targetResolveResult = requireTarget ? target.ResolveResult : null;
IParameterizedMember foundMethod;
OverloadResolutionErrors errors;
while ((errors = IsUnambiguousCall(expectedTargetDetails, method, targetResolveResult, typeArguments, arguments, out foundMethod)) != OverloadResolutionErrors.None) {
while ((errors = IsUnambiguousCall(expectedTargetDetails, method, targetResolveResult, typeArguments, arguments, argumentNames, out foundMethod)) != OverloadResolutionErrors.None) {
switch (errors) {
case OverloadResolutionErrors.TypeInferenceFailed:
case OverloadResolutionErrors.WrongNumberOfTypeArguments:
@ -308,13 +337,28 @@ namespace ICSharpCode.Decompiler.CSharp @@ -308,13 +337,28 @@ namespace ICSharpCode.Decompiler.CSharp
if (requireTypeArguments && (!settings.AnonymousTypes || !method.TypeArguments.Any(a => a.ContainsAnonymousType())))
typeArgumentList.AddRange(method.TypeArguments.Select(expressionBuilder.ConvertType));
var argumentExpressions = arguments.Select(arg => arg.Expression);
var argumentExpressions = GetArgumentExpressions(arguments, argumentNames);
return new InvocationExpression(targetExpr, argumentExpressions)
.WithRR(new CSharpInvocationResolveResult(target.ResolveResult, foundMethod, argumentResolveResults, isExpandedForm: isExpandedForm));
}
}
}
private IEnumerable<Expression> GetArgumentExpressions(List<TranslatedExpression> arguments, string[] argumentNames)
{
if (argumentNames == null) {
return arguments.Select(arg => arg.Expression);
} else {
return arguments.Zip(argumentNames,
(arg, name) => {
if (name == null)
return arg.Expression;
else
return new NamedArgumentExpression(name, arg);
});
}
}
static bool IsNullConditional(Expression expr)
{
return expr is UnaryOperatorExpression uoe && uoe.Operator == UnaryOperatorType.NullConditional;
@ -381,11 +425,16 @@ namespace ICSharpCode.Decompiler.CSharp @@ -381,11 +425,16 @@ namespace ICSharpCode.Decompiler.CSharp
OverloadResolutionErrors IsUnambiguousCall(ExpectedTargetDetails expectedTargetDetails, IMethod method,
ResolveResult target, IType[] typeArguments, IList<TranslatedExpression> arguments,
string[] argumentNames,
out IParameterizedMember foundMember)
{
foundMember = null;
var lookup = new MemberLookup(resolver.CurrentTypeDefinition, resolver.CurrentTypeDefinition.ParentAssembly);
var or = new OverloadResolution(resolver.Compilation, arguments.SelectArray(a => a.ResolveResult), typeArguments: typeArguments);
var or = new OverloadResolution(resolver.Compilation,
arguments.SelectArray(a => a.ResolveResult),
argumentNames: argumentNames,
typeArguments: typeArguments,
conversions: expressionBuilder.resolver.conversions);
if (expectedTargetDetails.CallOpCode == OpCode.NewObj) {
foreach (IMethod ctor in method.DeclaringType.GetConstructors()) {
if (lookup.IsAccessible(ctor, allowProtectedAccess: resolver.CurrentTypeDefinition == method.DeclaringTypeDefinition)) {
@ -413,7 +462,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -413,7 +462,7 @@ namespace ICSharpCode.Decompiler.CSharp
return OverloadResolutionErrors.None;
}
static bool CanInferAnonymousTypePropertyNamesFromArguments(IList<Expression> args, IList<IParameter> parameters)
static bool CanInferAnonymousTypePropertyNamesFromArguments(IList<Expression> args, IReadOnlyList<IParameter> parameters)
{
for (int i = 0; i < args.Count; i++) {
string inferredName;
@ -624,5 +673,34 @@ namespace ICSharpCode.Decompiler.CSharp @@ -624,5 +673,34 @@ namespace ICSharpCode.Decompiler.CSharp
Conversion.MethodGroupConversion(method, func.OpCode == OpCode.LdVirtFtn, false)));
return oce;
}
internal TranslatedExpression CallWithNamedArgs(Block block)
{
Debug.Assert(block.Kind == BlockKind.CallWithNamedArgs);
var call = (CallInstruction)block.FinalInstruction;
var arguments = new ILInstruction[call.Arguments.Count];
var argumentToParameterMap = new int[arguments.Length];
int firstParamIndex = call.IsInstanceCall ? 1 : 0;
// Arguments from temporary variables (VariableKind.NamedArgument):
int pos = 0;
foreach (StLoc stloc in block.Instructions) {
Debug.Assert(stloc.Variable.LoadInstructions.Single().Parent == call);
arguments[pos] = stloc.Value;
argumentToParameterMap[pos] = stloc.Variable.LoadInstructions.Single().ChildIndex - firstParamIndex;
pos++;
}
// Remaining argument:
foreach (var arg in call.Arguments) {
if (arg.MatchLdLoc(out var v) && v.Kind == VariableKind.NamedArgument) {
continue; // already handled in loop above
}
arguments[pos] = arg;
argumentToParameterMap[pos] = arg.ChildIndex - firstParamIndex;
pos++;
}
Debug.Assert(pos == arguments.Length);
return Build(call.OpCode, call.Method, arguments, argumentToParameterMap, call.ConstrainedTo)
.WithILInstruction(call).WithILInstruction(block);
}
}
}

4
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -1933,6 +1933,10 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1933,6 +1933,10 @@ namespace ICSharpCode.Decompiler.CSharp
return TranslatePostfixOperator(block);
case BlockKind.CallInlineAssign:
return TranslateSetterCallAssignment(block);
case BlockKind.CallWithNamedArgs:
return WrapInRef(
new CallBuilder(this, typeSystem, settings).CallWithNamedArgs(block),
((CallInstruction)block.FinalInstruction).Method.ReturnType);
default:
return ErrorExpression("Unknown block type: " + block.Kind);
}

6
ICSharpCode.Decompiler/CSharp/Resolver/CSharpInvocationResolveResult.cs

@ -47,7 +47,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver @@ -47,7 +47,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
/// </summary>
public readonly bool IsExpandedForm;
readonly IList<int> argumentToParameterMap;
readonly IReadOnlyList<int> argumentToParameterMap;
/// <summary>
/// If IsExtensionMethodInvocation is true this property holds the reduced method.
@ -70,7 +70,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver @@ -70,7 +70,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
bool isExtensionMethodInvocation = false,
bool isExpandedForm = false,
bool isDelegateInvocation = false,
IList<int> argumentToParameterMap = null,
IReadOnlyList<int> argumentToParameterMap = null,
IList<ResolveResult> initializerStatements = null,
IType returnTypeOverride = null
)
@ -93,7 +93,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver @@ -93,7 +93,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
///
/// parameterIndex = ArgumentToParameterMap[argumentIndex]
/// </summary>
public IList<int> GetArgumentToParameterMap()
public IReadOnlyList<int> GetArgumentToParameterMap()
{
return argumentToParameterMap;
}

2
ICSharpCode.Decompiler/CSharp/Resolver/OverloadResolution.cs

@ -843,7 +843,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver @@ -843,7 +843,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
///
/// parameterIndex = GetArgumentToParameterMap()[argumentIndex]
/// </summary>
public IList<int> GetArgumentToParameterMap()
public IReadOnlyList<int> GetArgumentToParameterMap()
{
if (bestCandidate != null)
return bestCandidate.ArgumentToParameterMap;

2
ICSharpCode.Decompiler/DecompilerSettings.cs

@ -636,7 +636,7 @@ namespace ICSharpCode.Decompiler @@ -636,7 +636,7 @@ namespace ICSharpCode.Decompiler
}
}
bool namedArguments = false;
bool namedArguments = true;
/// <summary>
/// Gets/Sets whether named arguments should be used.

9
ICSharpCode.Decompiler/IL/ILVariable.cs

@ -57,7 +57,11 @@ namespace ICSharpCode.Decompiler.IL @@ -57,7 +57,11 @@ namespace ICSharpCode.Decompiler.IL
/// <summary>
/// Variable created from stack slot.
/// </summary>
StackSlot
StackSlot,
/// <summary>
/// Variable in BlockKind.CallWithNamedArgs
/// </summary>
NamedArgument,
}
public class ILVariable
@ -309,6 +313,9 @@ namespace ICSharpCode.Decompiler.IL @@ -309,6 +313,9 @@ namespace ICSharpCode.Decompiler.IL
case VariableKind.UsingLocal:
output.Write("using ");
break;
case VariableKind.NamedArgument:
output.Write("named_arg ");
break;
default:
throw new ArgumentOutOfRangeException();
}

4
ICSharpCode.Decompiler/IL/Instructions/Block.cs

@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
@ -119,6 +120,7 @@ namespace ICSharpCode.Decompiler.IL @@ -119,6 +120,7 @@ namespace ICSharpCode.Decompiler.IL
foreach (var inst in Instructions) {
var stloc = inst as StLoc;
Debug.Assert(stloc != null, "Instructions in CallWithNamedArgs must be assignments");
Debug.Assert(stloc.Variable.Kind == VariableKind.NamedArgument);
Debug.Assert(stloc.Variable.IsSingleDefinition && stloc.Variable.LoadCount == 1);
Debug.Assert(stloc.Variable.LoadInstructions.Single().Parent == finalInstruction);
}
@ -151,6 +153,8 @@ namespace ICSharpCode.Decompiler.IL @@ -151,6 +153,8 @@ namespace ICSharpCode.Decompiler.IL
ILRange.WriteTo(output, options);
output.Write("Block ");
output.WriteDefinition(Label, this);
if (Kind != BlockKind.ControlFlow)
output.Write($" ({Kind})");
if (Parent is BlockContainer)
output.Write(" (incoming: {0})", IncomingEdgeCount);
output.Write(' ');

10
ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs

@ -230,11 +230,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -230,11 +230,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms
thisVarType = new ByReferenceType(thisVarType);
}
var function = call.Ancestors.OfType<ILFunction>().First();
var thisArgVar = function.RegisterVariable(VariableKind.StackSlot, thisVarType, "this_arg");
var thisArgVar = function.RegisterVariable(VariableKind.NamedArgument, thisVarType, "this_arg");
namedArgBlock.Instructions.Add(new StLoc(thisArgVar, call.Arguments[0]));
call.Arguments[0] = new LdLoc(thisArgVar);
}
}
v.Kind = VariableKind.NamedArgument;
namedArgBlock.Instructions.Insert(call.IsInstanceCall ? 1 : 0, new StLoc(v, inlinedExpression));
return true;
}
@ -476,6 +477,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -476,6 +477,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return FindResult.Stop; // cannot use named arg to move expressionBeingMoved before this pointer
if (call.Method.IsOperator || call.Method.IsAccessor)
return FindResult.Stop; // cannot use named arg for operators or accessors
if (call.Method is VarArgInstanceMethod)
return FindResult.Stop; // CallBuilder doesn't support named args when using varargs
if (call.Method.IsConstructor) {
IType type = call.Method.DeclaringType;
if (type.Kind == TypeKind.Delegate || type.IsAnonymousType())
return FindResult.Stop;
}
for (int i = child.ChildIndex; i < call.Arguments.Count; i++) {
if (call.Arguments[i] is LdLoc ldloc && ldloc.Variable == v) {
loadInst = ldloc;

4
ICSharpCode.Decompiler/TypeSystem/VarArgInstanceMethod.cs

@ -44,7 +44,9 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -44,7 +44,9 @@ namespace ICSharpCode.Decompiler.TypeSystem
}
this.parameters = paramList.ToArray();
}
public IMethod BaseMethod => baseMethod;
public int RegularParameterCount {
get { return baseMethod.Parameters.Count - 1; }
}

Loading…
Cancel
Save