Skip to content

Commit 1e9cffd

Browse files
authored
Separate positional and keyword arguments in CallExpression (#1167)
1 parent d8b1a33 commit 1e9cffd

4 files changed

Lines changed: 106 additions & 105 deletions

File tree

Src/IronPython/Compiler/Ast/CallExpression.cs

Lines changed: 77 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -22,40 +22,50 @@
2222
namespace IronPython.Compiler.Ast {
2323

2424
public class CallExpression : Expression, IInstructionProvider {
25-
public CallExpression(Expression target, Arg[] args) {
26-
// TODO: use two arrays (args/keywords) instead
27-
if (args == null) throw new ArgumentNullException(nameof(args));
25+
public CallExpression(Expression target, IReadOnlyList<Arg>? args, IReadOnlyList<Arg>? kwargs) {
2826
Target = target;
29-
Args = args;
27+
Args = args?.ToArray() ?? Array.Empty<Arg>();
28+
Kwargs = kwargs?.ToArray() ?? Array.Empty<Arg>();
3029
}
3130

3231
public Expression Target { get; }
3332

34-
public Arg[] Args { get; }
33+
public IReadOnlyList<Arg> Args { get; }
3534

36-
internal IList<Arg> ImplicitArgs { get; } = new List<Arg>();
35+
public IReadOnlyList<Arg> Kwargs { get; }
36+
37+
internal IList<Arg> ImplicitArgs => _implicitArgs ??= new List<Arg>();
38+
private List<Arg>? _implicitArgs;
3739

3840
public bool NeedsLocalsDictionary() {
3941
if (!(Target is NameExpression nameExpr)) return false;
4042

4143
if (nameExpr.Name == "eval" || nameExpr.Name == "exec") return true;
4244
if (nameExpr.Name == "dir" || nameExpr.Name == "vars" || nameExpr.Name == "locals") {
4345
// could be splatting empty list or dict resulting in 0-param call which needs context
44-
return Args.All(arg => arg.Name == "*" || arg.Name == "**");
46+
return Args.All(arg => arg.Name == "*") && Kwargs.All(arg => arg.Name == "**");
4547
}
4648

4749
return false;
4850
}
4951

5052
public override MSAst.Expression Reduce() {
51-
Arg[] args = Args;
52-
if (Args.Length == 0 && ImplicitArgs.Count > 0) {
53-
args = ImplicitArgs.ToArray();
53+
IReadOnlyList<Arg> args = Args;
54+
if (Args.Count == 0 && _implicitArgs != null && _implicitArgs.Count > 0) {
55+
args = _implicitArgs;
5456
}
5557

56-
SplitArgs(args, out var simpleArgs, out var listArgs, out var namedArgs, out var dictArgs, out var numDict);
58+
// count splatted list args and find the lowest index of a list argument, if any
59+
ScanArgs(args, ArgumentType.List, out var numList, out int firstListPos);
60+
Debug.Assert(numList == 0 || firstListPos < args.Count);
61+
62+
// count splatted dictionary args and find the lowest index of a dict argument, if any
63+
ScanArgs(Kwargs, ArgumentType.Dictionary, out var numDict, out int firstDictPos);
64+
Debug.Assert(numDict == 0 || firstDictPos < Kwargs.Count);
5765

58-
Argument[] kinds = new Argument[simpleArgs.Length + Math.Min(listArgs.Length, 1) + namedArgs.Length + (dictArgs.Length - numDict) + Math.Min(numDict, 1)];
66+
// all list arguments and all simple arguments after the first list will be collated into a single list for the actual call
67+
// all dictionary arguments will be merged into a single dictionary for the actual call
68+
Argument[] kinds = new Argument[firstListPos + Math.Min(numList, 1) + (Kwargs.Count - numDict) + Math.Min(numDict, 1)];
5969
MSAst.Expression[] values = new MSAst.Expression[2 + kinds.Length];
6070

6171
values[0] = Parent.LocalContext;
@@ -64,31 +74,28 @@ public override MSAst.Expression Reduce() {
6474
int i = 0;
6575

6676
// add simple arguments
67-
foreach (var arg in simpleArgs) {
68-
kinds[i] = arg.GetArgumentInfo();
69-
values[i + 2] = arg.Expression;
70-
i++;
77+
if (firstListPos > 0) {
78+
foreach (var arg in args) {
79+
if (i == firstListPos) break;
80+
Debug.Assert(arg.GetArgumentInfo().Kind == ArgumentType.Simple);
81+
kinds[i] = arg.GetArgumentInfo();
82+
values[i + 2] = arg.Expression;
83+
i++;
84+
}
7185
}
7286

7387
// unpack list arguments
74-
if (listArgs.Length > 0) {
75-
var arg = listArgs[0];
88+
if (numList > 0) {
89+
var arg = args[firstListPos];
7690
Debug.Assert(arg.GetArgumentInfo().Kind == ArgumentType.List);
7791
kinds[i] = arg.GetArgumentInfo();
78-
values[i + 2] = UnpackListHelper(listArgs);
92+
values[i + 2] = UnpackListHelper(args, firstListPos);
7993
i++;
8094
}
8195

8296
// add named arguments
83-
foreach (var arg in namedArgs) {
84-
kinds[i] = arg.GetArgumentInfo();
85-
values[i + 2] = arg.Expression;
86-
i++;
87-
}
88-
89-
// add named arguments specified after a dict unpack
90-
if (dictArgs.Length != numDict) {
91-
foreach (var arg in dictArgs) {
97+
if (Kwargs.Count != numDict) {
98+
foreach (var arg in Kwargs) {
9299
var info = arg.GetArgumentInfo();
93100
if (info.Kind == ArgumentType.Named) {
94101
kinds[i] = info;
@@ -99,74 +106,45 @@ public override MSAst.Expression Reduce() {
99106
}
100107

101108
// unpack dict arguments
102-
if (dictArgs.Length > 0) {
103-
var arg = dictArgs[0];
109+
if (numDict > 0) {
110+
var arg = Kwargs[firstDictPos];
104111
Debug.Assert(arg.GetArgumentInfo().Kind == ArgumentType.Dictionary);
105112
kinds[i] = arg.GetArgumentInfo();
106-
values[i + 2] = UnpackDictHelper(Parent.LocalContext, dictArgs);
113+
values[i + 2] = UnpackDictHelper(Parent.LocalContext, Kwargs, numDict, firstDictPos);
107114
}
108115

109116
return Parent.Invoke(
110117
new CallSignature(kinds),
111118
values
112119
);
113120

114-
static void SplitArgs(Arg[] args, out ReadOnlySpan<Arg> simpleArgs, out ReadOnlySpan<Arg> listArgs, out ReadOnlySpan<Arg> namedArgs, out ReadOnlySpan<Arg> dictArgs, out int numDict) {
115-
if (args.Length == 0) {
116-
simpleArgs = default;
117-
listArgs = default;
118-
namedArgs = default;
119-
dictArgs = default;
120-
numDict = 0;
121-
return;
122-
}
121+
static void ScanArgs(IReadOnlyList<Arg> args, ArgumentType scanForType, out int numArgs, out int firstArgPos) {
122+
numArgs = 0;
123+
firstArgPos = args.Count;
123124

124-
int idxSimple = args.Length;
125-
int idxList = args.Length;
126-
int idxNamed = args.Length;
127-
int idxDict = args.Length;
128-
numDict = 0;
125+
if (args.Count == 0) return;
129126

130-
// we want idxSimple <= idxList <= idxNamed <= idxDict
131-
for (var i = args.Length - 1; i >= 0; i--) {
127+
for (var i = args.Count - 1; i >= 0; i--) {
132128
var arg = args[i];
133129
var info = arg.GetArgumentInfo();
134-
switch (info.Kind) {
135-
case ArgumentType.Simple:
136-
idxSimple = i;
137-
break;
138-
case ArgumentType.List:
139-
idxList = i;
140-
break;
141-
case ArgumentType.Named:
142-
idxNamed = i;
143-
break;
144-
case ArgumentType.Dictionary:
145-
idxDict = i;
146-
numDict++;
147-
break;
148-
default:
149-
throw new InvalidOperationException();
130+
if (info.Kind == scanForType) {
131+
firstArgPos = i;
132+
numArgs++;
150133
}
151134
}
152-
dictArgs = args.AsSpan(idxDict);
153-
if (idxNamed > idxDict) idxNamed = idxDict;
154-
namedArgs = args.AsSpan(idxNamed, idxDict - idxNamed);
155-
if (idxList > idxNamed) idxList = idxNamed;
156-
listArgs = args.AsSpan(idxList, idxNamed - idxList);
157-
if (idxSimple > idxList) idxSimple = idxList;
158-
simpleArgs = args.AsSpan(idxSimple, idxList - idxSimple);
159135
}
160136

161-
static MSAst.Expression UnpackListHelper(ReadOnlySpan<Arg> args) {
162-
Debug.Assert(args.Length > 0);
163-
Debug.Assert(args[0].GetArgumentInfo().Kind == ArgumentType.List);
164-
if (args.Length == 1) return args[0].Expression;
137+
static MSAst.Expression UnpackListHelper(IReadOnlyList<Arg> args, int firstListPos) {
138+
Debug.Assert(args.Count > 0);
139+
Debug.Assert(args[firstListPos].GetArgumentInfo().Kind == ArgumentType.List);
140+
141+
if (args.Count - firstListPos == 1) return args[firstListPos].Expression;
165142

166-
var expressions = new ReadOnlyCollectionBuilder<MSAst.Expression>(args.Length + 2);
143+
var expressions = new ReadOnlyCollectionBuilder<MSAst.Expression>(args.Count - firstListPos + 2);
167144
var varExpr = Expression.Variable(typeof(PythonList), "$coll");
168145
expressions.Add(Expression.Assign(varExpr, Expression.Call(AstMethods.MakeEmptyList)));
169-
foreach (var arg in args) {
146+
for (int i = firstListPos; i < args.Count; i++) {
147+
var arg = args[i];
170148
if (arg.GetArgumentInfo().Kind == ArgumentType.List) {
171149
expressions.Add(Expression.Call(AstMethods.ListExtend, varExpr, AstUtils.Convert(arg.Expression, typeof(object))));
172150
} else {
@@ -177,15 +155,18 @@ static MSAst.Expression UnpackListHelper(ReadOnlySpan<Arg> args) {
177155
return Expression.Block(typeof(PythonList), new MSAst.ParameterExpression[] { varExpr }, expressions);
178156
}
179157

180-
static MSAst.Expression UnpackDictHelper(MSAst.Expression context, ReadOnlySpan<Arg> args) {
181-
Debug.Assert(args.Length > 0);
182-
Debug.Assert(args[0].GetArgumentInfo().Kind == ArgumentType.Dictionary);
183-
if (args.Length == 1) return args[0].Expression;
158+
static MSAst.Expression UnpackDictHelper(MSAst.Expression context, IReadOnlyList<Arg> kwargs, int numDict, int firstDictPos) {
159+
Debug.Assert(kwargs.Count > 0);
160+
Debug.Assert(0 < numDict && numDict <= kwargs.Count);
161+
Debug.Assert(kwargs[firstDictPos].GetArgumentInfo().Kind == ArgumentType.Dictionary);
184162

185-
var expressions = new List<MSAst.Expression>(args.Length + 2);
163+
if (numDict == 1) return kwargs[firstDictPos].Expression;
164+
165+
var expressions = new ReadOnlyCollectionBuilder<MSAst.Expression>(numDict + 2);
186166
var varExpr = Expression.Variable(typeof(PythonDictionary), "$dict");
187167
expressions.Add(Expression.Assign(varExpr, Expression.Call(AstMethods.MakeEmptyDict)));
188-
foreach (var arg in args) {
168+
for (int i = firstDictPos; i < kwargs.Count; i++) {
169+
var arg = kwargs[i];
189170
if (arg.GetArgumentInfo().Kind == ArgumentType.Dictionary) {
190171
expressions.Add(Expression.Call(AstMethods.DictMerge, context, varExpr, arg.Expression));
191172
}
@@ -198,19 +179,24 @@ static MSAst.Expression UnpackDictHelper(MSAst.Expression context, ReadOnlySpan<
198179
#region IInstructionProvider Members
199180

200181
void IInstructionProvider.AddInstructions(LightCompiler compiler) {
201-
Arg[] args = Args;
202-
if (args.Length == 0 && ImplicitArgs.Count > 0) {
203-
args = ImplicitArgs.ToArray();
182+
IReadOnlyList<Arg> args = Args;
183+
if (args.Count == 0 && _implicitArgs != null && _implicitArgs.Count > 0) {
184+
args = _implicitArgs;
204185
}
205186

206-
for (int i = 0; i < args.Length; i++) {
187+
if (Kwargs.Count > 0) {
188+
compiler.Compile(Reduce());
189+
return;
190+
}
191+
192+
for (int i = 0; i < args.Count; i++) {
207193
if (!args[i].GetArgumentInfo().IsSimple) {
208194
compiler.Compile(Reduce());
209195
return;
210196
}
211197
}
212198

213-
switch (args.Length) {
199+
switch (args.Count) {
214200
#region Generated Python Call Expression Instruction Switch
215201

216202
// *** BEGIN GENERATED CODE ***
@@ -441,11 +427,14 @@ public override void Walk(PythonWalker walker) {
441427
arg.Walk(walker);
442428
}
443429
}
444-
if (ImplicitArgs.Count > 0) {
430+
if (_implicitArgs != null && _implicitArgs.Count > 0) {
445431
foreach (Arg arg in ImplicitArgs) {
446432
arg.Walk(walker);
447433
}
448434
}
435+
foreach (Arg arg in Kwargs) {
436+
arg.Walk(walker);
437+
}
449438
}
450439
walker.PostWalk(this);
451440
}

Src/IronPython/Compiler/Ast/PythonNameBinder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -835,7 +835,7 @@ public override bool Walk(CallExpression node) {
835835

836836
if (node.Target is NameExpression nameExpr && nameExpr.Name == "super" && _currentScope is FunctionDefinition func) {
837837
_currentScope.Reference("__class__");
838-
if (node.Args.Length == 0 && func.ParameterNames.Length > 0) {
838+
if (node.Args.Count == 0 && node.Kwargs.Count == 0 && func.ParameterNames.Length > 0) {
839839
node.ImplicitArgs.Add(new Arg(new NameExpression("__class__")));
840840
node.ImplicitArgs.Add(new Arg(new NameExpression(func.ParameterNames[0])));
841841
}

Src/IronPython/Compiler/Parser.cs

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1064,7 +1064,7 @@ private List<Expression> ParseDecorators() {
10641064

10651065
if (MaybeEat(TokenKind.LeftParenthesis)) {
10661066
ParserSink?.StartParameters(GetSourceSpan());
1067-
Arg[] args = FinishArgumentList(null);
1067+
IReadOnlyList<Arg> args = FinishArgumentList(null);
10681068
decorator = FinishCallExpr(decorator, args);
10691069
}
10701070
decorator.SetLoc(_globalParent, start, GetEnd());
@@ -1988,12 +1988,12 @@ private Expression AddTrailers(Expression ret, bool allowGeneratorExpression) {
19881988
if (!allowGeneratorExpression) return ret;
19891989

19901990
NextToken();
1991-
Arg[] args = FinishArgListOrGenExpr();
1991+
IReadOnlyList<Arg> args = FinishArgListOrGenExpr();
19921992
CallExpression call;
19931993
if (args != null) {
19941994
call = FinishCallExpr(ret, args);
19951995
} else {
1996-
call = new CallExpression(ret, new Arg[0]);
1996+
call = new CallExpression(ret, null, null);
19971997
}
19981998

19991999
call.SetLoc(_globalParent, ret.StartIndex, GetEnd());
@@ -2132,7 +2132,7 @@ private List<Expression> ParseExprList(out bool trailingComma) {
21322132
// expression "=" expression rest_of_arguments
21332133
// expression "for" gen_expr_rest
21342134
//
2135-
private Arg[] FinishArgListOrGenExpr() {
2135+
private IReadOnlyList<Arg> FinishArgListOrGenExpr() {
21362136
Arg a = null;
21372137

21382138
ParserSink?.StartParameters(GetSourceSpan());
@@ -2204,7 +2204,7 @@ private void CheckUniqueArgument(List<Arg> names, Arg arg) {
22042204

22052205
//arglist: (argument ',')* (argument [',']| '*' expression [',' '**' expression] | '**' expression)
22062206
//argument: [expression '='] expression # Really [keyword '='] expression
2207-
private Arg[] FinishArgumentList(Arg first) {
2207+
private IReadOnlyList<Arg> FinishArgumentList(Arg first) {
22082208
const TokenKind terminator = TokenKind.RightParenthesis;
22092209
List<Arg> l = new List<Arg>();
22102210

@@ -2246,8 +2246,7 @@ private Arg[] FinishArgumentList(Arg first) {
22462246

22472247
ParserSink?.EndParameters(GetSourceSpan());
22482248

2249-
Arg[] ret = l.ToArray();
2250-
return ret;
2249+
return l;
22512250
}
22522251

22532252
// testlist: test (',' test)* [',']
@@ -2900,9 +2899,11 @@ private void PushFunction(FunctionDefinition function) {
29002899
_functions.Push(function);
29012900
}
29022901

2903-
private CallExpression FinishCallExpr(Expression target, params Arg[] args) {
2902+
private CallExpression FinishCallExpr(Expression target, IEnumerable<Arg> args) {
29042903
bool hasKeyword = false;
29052904
bool hasKeywordUnpacking = false;
2905+
List<Arg> posargs = null;
2906+
List<Arg> kwargs = null;
29062907

29072908
foreach (Arg arg in args) {
29082909
if (arg.Name == null) {
@@ -2911,18 +2912,26 @@ private CallExpression FinishCallExpr(Expression target, params Arg[] args) {
29112912
} else if (hasKeyword) {
29122913
ReportSyntaxError(arg.StartIndex, arg.EndIndex, "positional argument follows keyword argument");
29132914
}
2915+
posargs ??= new List<Arg>();
2916+
posargs.Add(arg);
29142917
} else if (arg.Name == "*") {
29152918
if (hasKeywordUnpacking) {
29162919
ReportSyntaxError(arg.StartIndex, arg.EndIndex, "iterable argument unpacking follows keyword argument unpacking");
29172920
}
2921+
posargs ??= new List<Arg>();
2922+
posargs.Add(arg);
29182923
} else if (arg.Name == "**") {
29192924
hasKeywordUnpacking = true;
2925+
kwargs ??= new List<Arg>();
2926+
kwargs.Add(arg);
29202927
} else {
29212928
hasKeyword = true;
2929+
kwargs ??= new List<Arg>();
2930+
kwargs.Add(arg);
29222931
}
29232932
}
29242933

2925-
return new CallExpression(target, args);
2934+
return new CallExpression(target, posargs, kwargs);
29262935
}
29272936

29282937
#endregion

0 commit comments

Comments
 (0)