Skip to content

Commit f0b690a

Browse files
authored
Support starred arguments as class bases (#1170)
1 parent 59b462b commit f0b690a

File tree

9 files changed

+137
-69
lines changed

9 files changed

+137
-69
lines changed

Src/IronPython/Compiler/Ast/AstMethods.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ internal static class AstMethods {
2020
public static readonly MethodInfo IsTrue = GetMethod((Func<object, bool>)PythonOps.IsTrue);
2121
public static readonly MethodInfo RaiseAssertionError = GetMethod((Action<CodeContext, object>)PythonOps.RaiseAssertionError);
2222
public static readonly MethodInfo RaiseAssertionErrorNoMessage = GetMethod((Action<CodeContext>)PythonOps.RaiseAssertionError);
23-
public static readonly MethodInfo MakeClass = GetMethod((Func<FunctionCode, Func<CodeContext, CodeContext>, CodeContext, string, object[], object, string, object>)PythonOps.MakeClass);
23+
public static readonly MethodInfo MakeClass = GetMethod((Func<FunctionCode, Func<CodeContext, CodeContext>, CodeContext, string, PythonTuple, object, string, object>)PythonOps.MakeClass);
2424
public static readonly MethodInfo PrintExpressionValue = GetMethod((Action<CodeContext, object>)PythonOps.PrintExpressionValue);
2525
public static readonly MethodInfo ImportWithNames = GetMethod((Func<CodeContext, string, string[], int, object>)PythonOps.ImportWithNames);
2626
public static readonly MethodInfo ImportFrom = GetMethod((Func<CodeContext, object, string, object>)PythonOps.ImportFrom);
@@ -45,6 +45,7 @@ internal static class AstMethods {
4545
public static readonly MethodInfo CheckException = GetMethod((Func<CodeContext, object, object, object>)PythonOps.CheckException);
4646
public static readonly MethodInfo SetCurrentException = GetMethod((Func<CodeContext, Exception, object>)PythonOps.SetCurrentException);
4747
public static readonly MethodInfo MakeTuple = GetMethod((Func<object[], PythonTuple>)PythonOps.MakeTuple);
48+
public static readonly MethodInfo MakeEmptyTuple = GetMethod((Func<PythonTuple>)PythonOps.MakeEmptyTuple);
4849
public static readonly MethodInfo IsNot = GetMethod((Func<object, object, object>)PythonOps.IsNot);
4950
public static readonly MethodInfo Is = GetMethod((Func<object, object, object>)PythonOps.Is);
5051
public static readonly MethodInfo ImportTop = GetMethod((Func<CodeContext, string, int, object>)PythonOps.ImportTop);

Src/IronPython/Compiler/Ast/CallExpression.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ static void ScanArgs(IReadOnlyList<Arg> args, ArgumentType scanForType, out int
134134
}
135135
}
136136

137+
// Compare to: ClassDefinition.Reduce.__UnpackBasesHelper
137138
static MSAst.Expression UnpackListHelper(IReadOnlyList<Arg> args, int firstListPos) {
138139
Debug.Assert(args.Count > 0);
139140
Debug.Assert(args[firstListPos].ArgumentInfo.Kind == ArgumentType.List);

Src/IronPython/Compiler/Ast/ClassDefinition.cs

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using IronPython.Runtime;
1313

1414
using Microsoft.Scripting;
15+
using Microsoft.Scripting.Actions;
1516
using Microsoft.Scripting.Utils;
1617

1718
using AstUtils = Microsoft.Scripting.Ast.Utils;
@@ -23,7 +24,7 @@ namespace IronPython.Compiler.Ast {
2324

2425
public class ClassDefinition : ScopeStatement {
2526
private readonly string _name;
26-
private readonly Expression[] _bases;
27+
private readonly Arg[] _bases;
2728
private readonly Arg[] _keywords;
2829

2930
private LightLambdaExpression _dlrBody; // the transformed body including all of our initialization, etc...
@@ -33,15 +34,12 @@ public class ClassDefinition : ScopeStatement {
3334
private static readonly MSAst.ParameterExpression _parentContextParam = Ast.Parameter(typeof(CodeContext), "$parentContext");
3435
private static readonly MSAst.Expression _tupleExpression = MSAst.Expression.Call(AstMethods.GetClosureTupleFromContext, _parentContextParam);
3536

36-
public ClassDefinition(string name, Expression[] bases, Arg[] keywords, Statement body = null) {
37-
ContractUtils.RequiresNotNullItems(bases, nameof(bases));
38-
ContractUtils.RequiresNotNullItems(keywords, nameof(keywords));
39-
37+
public ClassDefinition(string name, IReadOnlyList<Arg> bases, IReadOnlyList<Arg> keywords, Statement body = null) {
4038
_name = name;
41-
_bases = bases;
42-
_keywords = keywords;
39+
_bases = bases?.ToArray() ?? Array.Empty<Arg>();
40+
_keywords = keywords?.ToArray() ?? Array.Empty<Arg>();
4341
Body = body;
44-
Metaclass = keywords.Where(arg => arg.Name == "metaclass").Select(arg => arg.Expression).FirstOrDefault();
42+
Metaclass = _keywords.Where(arg => arg.Name == "metaclass").Select(arg => arg.Expression).FirstOrDefault();
4543
}
4644

4745
public SourceLocation Header => GlobalParent.IndexToLocation(HeaderIndex);
@@ -50,7 +48,7 @@ public ClassDefinition(string name, Expression[] bases, Arg[] keywords, Statemen
5048

5149
public override string Name => _name;
5250

53-
public IReadOnlyList<Expression> Bases => _bases;
51+
public IReadOnlyList<Arg> Bases => _bases;
5452

5553
public IReadOnlyList<Arg> Keywords => _keywords;
5654

@@ -181,10 +179,7 @@ public override MSAst.Expression Reduce() {
181179
lambda,
182180
Parent.LocalContext,
183181
AstUtils.Constant(_name),
184-
Ast.NewArrayInit(
185-
typeof(object),
186-
ToObjectArray(_bases)
187-
),
182+
UnpackBasesHelper(_bases),
188183
Metaclass is null ? AstUtils.Constant(null, typeof(object)) : AstUtils.Convert(Metaclass, typeof(object)),
189184
AstUtils.Constant(FindSelfNames())
190185
);
@@ -198,6 +193,33 @@ public override MSAst.Expression Reduce() {
198193
GlobalParent.IndexToLocation(HeaderIndex)
199194
)
200195
);
196+
197+
// Compare to: CallExpression.Reduce.__UnpackListHelper
198+
static MSAst.Expression UnpackBasesHelper(IReadOnlyList<Arg> bases) {
199+
if (bases.Count == 0) {
200+
return Expression.Call(AstMethods.MakeEmptyTuple);
201+
} else if (bases.All(arg => arg.ArgumentInfo.Kind is ArgumentType.Simple)) {
202+
return Expression.Call(AstMethods.MakeTuple,
203+
Expression.NewArrayInit(
204+
typeof(object),
205+
ToObjectArray(bases.Select(arg => arg.Expression).ToList())
206+
)
207+
);
208+
} else {
209+
var expressions = new ReadOnlyCollectionBuilder<MSAst.Expression>(bases.Count + 2);
210+
var varExpr = Expression.Variable(typeof(PythonList), "$coll");
211+
expressions.Add(Expression.Assign(varExpr, Expression.Call(AstMethods.MakeEmptyList)));
212+
foreach (var arg in bases) {
213+
if (arg.ArgumentInfo.Kind == ArgumentType.List) {
214+
expressions.Add(Expression.Call(AstMethods.ListExtend, varExpr, AstUtils.Convert(arg.Expression, typeof(object))));
215+
} else {
216+
expressions.Add(Expression.Call(AstMethods.ListAppend, varExpr, AstUtils.Convert(arg.Expression, typeof(object))));
217+
}
218+
}
219+
expressions.Add(Expression.Call(AstMethods.ListToTuple, varExpr));
220+
return Expression.Block(typeof(PythonTuple), new MSAst.ParameterExpression[] { varExpr }, expressions);
221+
}
222+
}
201223
}
202224

203225
private Microsoft.Scripting.Ast.LightExpression<Func<CodeContext, CodeContext>> MakeClassBody() {
@@ -297,10 +319,11 @@ public override void Walk(PythonWalker walker) {
297319
decorator.Walk(walker);
298320
}
299321
}
300-
if (_bases != null) {
301-
foreach (Expression b in _bases) {
302-
b.Walk(walker);
303-
}
322+
foreach (Arg b in _bases) {
323+
b.Walk(walker);
324+
}
325+
foreach (Arg b in _keywords) {
326+
b.Walk(walker);
304327
}
305328
Body?.Walk(walker);
306329
}

Src/IronPython/Compiler/Ast/FlowChecker.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,10 @@ public override bool Walk(ClassDefinition node) {
328328
} else {
329329
// analyze the class definition itself (it is visited while analyzing parent scope):
330330
Define(node.Name);
331-
foreach (Expression e in node.Bases) {
331+
foreach (Arg e in node.Bases) {
332+
e.Walk(this);
333+
}
334+
foreach (Arg e in node.Keywords) {
332335
e.Walk(this);
333336
}
334337
return false;

Src/IronPython/Compiler/Ast/PythonNameBinder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ public override bool Walk(ClassDefinition node) {
206206
node.PythonVariable = DefineName(node.Name);
207207

208208
// Base references are in the outer context
209-
foreach (Expression b in node.Bases) b.Walk(this);
209+
foreach (Arg b in node.Bases) b.Walk(this);
210210

211211
foreach (Arg a in node.Keywords) a.Walk(this);
212212

Src/IronPython/Compiler/Parser.cs

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1006,27 +1006,21 @@ private ClassDefinition ParseClassDef() {
10061006
string name = ReadName();
10071007
if (name == null) {
10081008
// no name, assume there's no class.
1009-
return new ClassDefinition(null, new Expression[0], new Arg[0], ErrorStmt());
1009+
return new ClassDefinition(null, null, null, ErrorStmt());
10101010
}
10111011

1012-
var bases = new List<Expression>();
1013-
var keywords = new List<Arg>();
1012+
List<Arg> bases = null;
1013+
List<Arg> keywords = null;
10141014
if (MaybeEat(TokenKind.LeftParenthesis)) {
1015-
foreach (var arg in FinishArgumentList(null)) {
1016-
var info = arg.ArgumentInfo;
1017-
if (info.Kind == Microsoft.Scripting.Actions.ArgumentType.Simple) {
1018-
bases.Add(arg.Expression);
1019-
} else if (info.Kind == Microsoft.Scripting.Actions.ArgumentType.Named) {
1020-
keywords.Add(arg);
1021-
}
1022-
}
1015+
IReadOnlyList<Arg> args = FinishArgumentList(null);
1016+
SplitAndValidateArguments(args, out bases, out keywords);
10231017
}
10241018
var mid = GetEnd();
10251019

10261020
// Save private prefix
10271021
string savedPrefix = SetPrivatePrefix(name);
10281022

1029-
var ret = new ClassDefinition(name, bases.ToArray(), keywords.ToArray());
1023+
var ret = new ClassDefinition(name, bases, keywords);
10301024
PushClass(ret);
10311025

10321026
// Parse the class body
@@ -1989,13 +1983,7 @@ private Expression AddTrailers(Expression ret, bool allowGeneratorExpression) {
19891983

19901984
NextToken();
19911985
IReadOnlyList<Arg> args = FinishArgListOrGenExpr();
1992-
CallExpression call;
1993-
if (args != null) {
1994-
call = FinishCallExpr(ret, args);
1995-
} else {
1996-
call = new CallExpression(ret, null, null);
1997-
}
1998-
1986+
CallExpression call = FinishCallExpr(ret, args);
19991987
call.SetLoc(_globalParent, ret.StartIndex, GetEnd());
20001988
ret = call;
20011989
break;
@@ -2900,11 +2888,22 @@ private void PushFunction(FunctionDefinition function) {
29002888
}
29012889

29022890
private CallExpression FinishCallExpr(Expression target, IEnumerable<Arg> args) {
2903-
bool hasKeyword = false;
2904-
bool hasKeywordUnpacking = false;
29052891
List<Arg> posargs = null;
29062892
List<Arg> kwargs = null;
29072893

2894+
if (args is not null) {
2895+
SplitAndValidateArguments(args, out posargs, out kwargs);
2896+
}
2897+
2898+
return new CallExpression(target, posargs, kwargs);
2899+
}
2900+
2901+
private void SplitAndValidateArguments(IEnumerable<Arg> args, out List<Arg> posargs, out List<Arg> kwargs) {
2902+
bool hasKeyword = false;
2903+
bool hasKeywordUnpacking = false;
2904+
2905+
posargs = kwargs = null;
2906+
29082907
foreach (Arg arg in args) {
29092908
if (arg.Name == null) {
29102909
if (hasKeywordUnpacking) {
@@ -2930,8 +2929,6 @@ private CallExpression FinishCallExpr(Expression target, IEnumerable<Arg> args)
29302929
kwargs.Add(arg);
29312930
}
29322931
}
2933-
2934-
return new CallExpression(target, posargs, kwargs);
29352932
}
29362933

29372934
#endregion

Src/IronPython/Modules/_ast.cs

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1064,10 +1064,10 @@ internal override AstExpression Revert() {
10641064
newArgs.Add(new Arg(expr.Revert(ex)));
10651065
if (null != starargs)
10661066
newArgs.Add(new Arg("*", expr.Revert(starargs)));
1067-
if (null != kwargs)
1068-
newKwargs.Add(new Arg("**", expr.Revert(kwargs)));
10691067
foreach (keyword kw in keywords)
10701068
newKwargs.Add(new Arg(kw.arg, expr.Revert(kw.value)));
1069+
if (null != kwargs)
1070+
newKwargs.Add(new Arg("**", expr.Revert(kwargs)));
10711071
return new CallExpression(target, newArgs, newKwargs);
10721072
}
10731073

@@ -1106,8 +1106,19 @@ internal ClassDef(ClassDefinition def)
11061106
: this() {
11071107
name = def.Name;
11081108
bases = new PythonList(def.Bases.Count);
1109-
foreach (AstExpression expr in def.Bases)
1110-
bases.Add(Convert(expr));
1109+
foreach (Arg arg in def.Bases) {
1110+
if (arg.Name == null)
1111+
bases.Add(Convert(arg.Expression));
1112+
else // name == "*"
1113+
starargs = Convert(arg.Expression);
1114+
}
1115+
keywords = new PythonList(def.Keywords.Count);
1116+
foreach (Arg arg in def.Keywords) {
1117+
if (arg.Name == "**")
1118+
kwargs = Convert(arg.Expression);
1119+
else // name is proper
1120+
keywords.Add(new keyword(arg));
1121+
}
11111122
body = ConvertStatements(def.Body);
11121123
if (def.Decorators != null) {
11131124
decorator_list = new PythonList(def.Decorators.Count);
@@ -1116,18 +1127,20 @@ internal ClassDef(ClassDefinition def)
11161127
} else {
11171128
decorator_list = new PythonList(0);
11181129
}
1119-
if (def.Keywords != null) {
1120-
keywords = new PythonList(def.Keywords.Count);
1121-
foreach (Arg arg in def.Keywords)
1122-
keywords.AddNoLock(new keyword(arg));
1123-
} else {
1124-
keywords = new PythonList(0);
1125-
}
11261130
}
11271131

11281132
internal override Statement Revert() {
1129-
var newBases = expr.RevertExprs(bases);
1130-
var newKeywords = keywords.Cast<keyword>().Select(kw => new Arg(kw.arg, expr.Revert(kw.value))).ToArray();
1133+
List<Arg> newBases = new List<Arg>();
1134+
List<Arg> newKeywords = new List<Arg>();
1135+
foreach (expr ex in bases)
1136+
newBases.Add(new Arg(expr.Revert(ex)));
1137+
if (null != starargs)
1138+
newBases.Add(new Arg("*", expr.Revert(starargs)));
1139+
foreach (keyword kw in keywords)
1140+
newKeywords.Add(new Arg(kw.arg, expr.Revert(kw.value)));
1141+
if (null != kwargs)
1142+
newKeywords.Add(new Arg("**", expr.Revert(kwargs)));
1143+
11311144
ClassDefinition cd = new ClassDefinition(name, newBases, newKeywords, RevertStmts(body));
11321145
if (decorator_list.Count != 0)
11331146
cd.Decorators = expr.RevertExprs(decorator_list);

Src/IronPython/Runtime/Operations/PythonOps.cs

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1321,7 +1321,7 @@ public static void InitializeForFinalization(CodeContext/*!*/ context, object ne
13211321
return classdict;
13221322
}
13231323

1324-
public static object MakeClass(FunctionCode funcCode, Func<CodeContext, CodeContext> body, CodeContext/*!*/ parentContext, string name, object[] bases, object metaclass, string selfNames) {
1324+
public static object MakeClass(FunctionCode funcCode, Func<CodeContext, CodeContext> body, CodeContext/*!*/ parentContext, string name, PythonTuple bases, object metaclass, string selfNames) {
13251325
Func<CodeContext, CodeContext> func = GetClassCode(parentContext, funcCode, body);
13261326

13271327
return MakeClass(parentContext, name, bases, metaclass, selfNames, func(parentContext).Dict);
@@ -1342,11 +1342,11 @@ private static Func<CodeContext, CodeContext> GetClassCode(CodeContext/*!*/ cont
13421342
}
13431343
}
13441344

1345-
private static object MakeClass(CodeContext/*!*/ context, string name, object[] bases, object metaclass, string selfNames, PythonDictionary vars) {
1346-
foreach (object dt in bases) {
1345+
private static object MakeClass(CodeContext/*!*/ context, string name, PythonTuple bases, object metaclass, string selfNames, PythonDictionary vars) {
1346+
foreach (object? dt in bases) {
13471347
if (dt is TypeGroup) {
1348-
object[] newBases = new object[bases.Length];
1349-
for (int i = 0; i < bases.Length; i++) {
1348+
object?[] newBases = new object[bases.Count];
1349+
for (int i = 0; i < bases.Count; i++) {
13501350
if (bases[i] is TypeGroup tc) {
13511351
if (!tc.TryGetNonGenericType(out Type nonGenericType)) {
13521352
throw PythonOps.TypeError("cannot derive from open generic types {0}", Repr(context, tc));
@@ -1356,7 +1356,7 @@ private static object MakeClass(CodeContext/*!*/ context, string name, object[]
13561356
newBases[i] = bases[i];
13571357
}
13581358
}
1359-
bases = newBases;
1359+
bases = PythonTuple.MakeTuple(newBases);
13601360
break;
13611361
} else if (dt is PythonType pt) {
13621362
if (pt.Equals(PythonType.GetPythonType(typeof(Enum))) || pt.Equals(PythonType.GetPythonType(typeof(Array)))
@@ -1367,20 +1367,18 @@ private static object MakeClass(CodeContext/*!*/ context, string name, object[]
13671367
}
13681368
}
13691369

1370-
PythonTuple tupleBases = PythonTuple.MakeTuple(bases);
1371-
13721370
if (metaclass is null) {
13731371
// this makes sure that object is a base
1374-
if (tupleBases.Count == 0) {
1375-
tupleBases = PythonTuple.MakeTuple(DynamicHelpers.GetPythonTypeFromType(typeof(object)));
1372+
if (bases.Count == 0) {
1373+
bases = PythonTuple.MakeTuple(DynamicHelpers.GetPythonTypeFromType(typeof(object)));
13761374
}
1377-
return PythonType.__new__(context, TypeCache.PythonType, name, tupleBases, vars, selfNames);
1375+
return PythonType.__new__(context, TypeCache.PythonType, name, bases, vars, selfNames);
13781376
}
13791377

13801378
object? classdict = vars;
13811379

13821380
if (metaclass is PythonType) {
1383-
classdict = CallPrepare(context, (PythonType)metaclass, name, tupleBases, vars);
1381+
classdict = CallPrepare(context, (PythonType)metaclass, name, bases, vars);
13841382
}
13851383

13861384
// eg:
@@ -1395,7 +1393,7 @@ private static object MakeClass(CodeContext/*!*/ context, string name, object[]
13951393
context,
13961394
metaclass,
13971395
name,
1398-
tupleBases,
1396+
bases,
13991397
classdict
14001398
);
14011399

@@ -1486,6 +1484,15 @@ public static PythonTuple MakeTupleFromSequence(object items) {
14861484
return PythonTuple.Make(items);
14871485
}
14881486

1487+
/// <summary>
1488+
/// Python runtime helper to create an instance of an empty Tuple
1489+
/// </summary>
1490+
[NoSideEffects]
1491+
[EditorBrowsable(EditorBrowsableState.Never)]
1492+
public static PythonTuple MakeEmptyTuple() {
1493+
return PythonTuple.MakeTuple();
1494+
}
1495+
14891496
/// <summary>
14901497
/// DICT_MERGE
14911498
/// </summary>

0 commit comments

Comments
 (0)