Skip to content

Commit 894df46

Browse files
authored
Resolve obscure super errors (#1535)
* Resolve obscure super errors * Add classcell generation tests * Add obscure super tests * Fix some typos * Improve perfomance * Trigger class cell creation on any super reference
1 parent bad9298 commit 894df46

12 files changed

Lines changed: 411 additions & 66 deletions

Src/IronPython/Compiler/Ast/AstMethods.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ internal static class AstMethods {
7272
public static readonly MethodInfo MakeSet = GetMethod((Func<object[], SetCollection>)PythonOps.MakeSet);
7373
public static readonly MethodInfo MakeEmptySet = GetMethod((Func<SetCollection>)PythonOps.MakeEmptySet);
7474
public static readonly MethodInfo MakeHomogeneousDictFromItems = GetMethod((Func<object[], PythonDictionary>)PythonOps.MakeHomogeneousDictFromItems);
75-
public static readonly MethodInfo CreateLocalContext = GetMethod((Func<CodeContext, MutableTuple, string[], CodeContext>)PythonOps.CreateLocalContext);
75+
public static readonly MethodInfo CreateLocalContext = GetMethod((Func<CodeContext, MutableTuple, string[], int, int, CodeContext>)PythonOps.CreateLocalContext);
7676
public static readonly MethodInfo UpdateStackTrace = GetMethod((Action<Exception, CodeContext, FunctionCode, int>)PythonOps.UpdateStackTrace);
7777
public static readonly MethodInfo ForLoopDispose = GetMethod((Action<KeyValuePair<IEnumerator, IDisposable>>)PythonOps.ForLoopDispose);
7878
public static readonly MethodInfo GetClosureTupleFromContext = GetMethod((Func<CodeContext, MutableTuple>)PythonOps.GetClosureTupleFromContext);

Src/IronPython/Compiler/Ast/CallExpression.cs

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ namespace IronPython.Compiler.Ast {
2424
public class CallExpression : Expression, IInstructionProvider {
2525
private readonly Expression[] _args;
2626
private readonly Keyword[] _kwargs;
27-
private Expression[]? _implicitArgs;
2827

2928
public CallExpression(Expression target, IReadOnlyList<Expression>? args, IReadOnlyList<Keyword>? kwargs) {
3029
Target = target;
@@ -38,10 +37,6 @@ public CallExpression(Expression target, IReadOnlyList<Expression>? args, IReadO
3837

3938
public IReadOnlyList<Keyword> Kwargs => _kwargs;
4039

41-
internal void SetImplicitArgs(params Expression[] args) {
42-
_implicitArgs = args;
43-
}
44-
4540
public bool NeedsLocalsDictionary() {
4641
if (!(Target is NameExpression nameExpr)) return false;
4742

@@ -61,10 +56,6 @@ public override MSAst.Expression Reduce() {
6156
ReadOnlySpan<Expression> args = _args;
6257
ReadOnlySpan<Keyword> kwargs = _kwargs;
6358

64-
if (args.Length == 0 && _implicitArgs != null) {
65-
args = _implicitArgs;
66-
}
67-
6859
// count splatted list args and find the lowest index of a list argument, if any
6960
ScanArgs(args, out var numList, out int firstListPos);
7061
Debug.Assert(numList == 0 || firstListPos < args.Length);
@@ -188,9 +179,6 @@ static MSAst.Expression UnpackDictHelper(MSAst.Expression context, ReadOnlySpan<
188179

189180
void IInstructionProvider.AddInstructions(LightCompiler compiler) {
190181
ReadOnlySpan<Expression> args = _args;
191-
if (args.Length == 0 && _implicitArgs != null) {
192-
args = _implicitArgs;
193-
}
194182

195183
if (Kwargs.Count > 0) {
196184
compiler.Compile(Reduce());
@@ -430,11 +418,6 @@ public override int Run(InterpretedFrame frame) {
430418
public override void Walk(PythonWalker walker) {
431419
if (walker.Walk(this)) {
432420
Target?.Walk(walker);
433-
if (_implicitArgs is not null) {
434-
foreach (var arg in _implicitArgs) {
435-
arg.Walk(walker);
436-
}
437-
}
438421
foreach (var arg in _args) {
439422
arg.Walk(walker);
440423
}

Src/IronPython/Compiler/Ast/FunctionDefinition.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,10 @@ internal override int KwOnlyArgCount {
145145
internal PythonVariable PythonVariable { get; set; }
146146

147147
internal override bool ExposesLocalVariable(PythonVariable variable) {
148-
return NeedsLocalsDictionary;
148+
return NeedsLocalsDictionary
149+
|| (ContainsSuperCall && variable.Kind is VariableKind.Parameter
150+
&& _parameters is not null && _parameters.Length > 0
151+
&& _parameters[0].PythonVariable == variable);
149152
}
150153

151154
internal override FunctionAttributes Flags {
@@ -258,7 +261,7 @@ internal override void Bind(PythonNameBinder binder) {
258261

259262
internal override void FinishBind(PythonNameBinder binder) {
260263
foreach (var param in _parameters) {
261-
_variableMapping[param.PythonVariable] = param.FinishBind(NeedsLocalsDictionary);
264+
_variableMapping[param.PythonVariable] = param.FinishBind(forceClosureCell: ExposesLocalVariable(param.PythonVariable));
262265
}
263266
base.FinishBind(binder);
264267
}

Src/IronPython/Compiler/Ast/Parameter.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ public Expression DefaultValue {
6060

6161
internal PythonVariable PythonVariable { get; set; }
6262

63-
internal MSAst.Expression FinishBind(bool needsLocalsDictionary) {
64-
if (PythonVariable.AccessedInNestedScope || needsLocalsDictionary) {
63+
internal MSAst.Expression FinishBind(bool forceClosureCell) {
64+
if (PythonVariable.AccessedInNestedScope || forceClosureCell) {
6565
ParameterExpression = Expression.Parameter(typeof(object), Name);
6666
var cell = Ast.Parameter(typeof(ClosureCell), Name);
6767
return new ClosureExpression(PythonVariable, cell, ParameterExpression);

Src/IronPython/Compiler/Ast/PythonNameBinder.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -851,13 +851,13 @@ public override bool Walk(ComprehensionFor node) {
851851
public override bool Walk(CallExpression node) {
852852
node.Parent = _currentScope;
853853

854-
if (node.Target is NameExpression nameExpr && nameExpr.Name == "super" && _currentScope is FunctionDefinition func) {
855-
_currentScope.Reference("__class__");
856-
if (node.Args.Count == 0 && node.Kwargs.Count == 0 && func.ParameterNames.Length > 0) {
857-
node.SetImplicitArgs(new NameExpression("__class__"), new NameExpression(func.ParameterNames[0]));
858-
}
854+
if (node.Target is NameExpression nameExpr && nameExpr.Name == "super"
855+
&& _currentScope is not ClassDefinition
856+
&& node.Args.Count == 0 && node.Kwargs.Count == 0) {
857+
858+
_currentScope.ContainsSuperCall = true;
859859
}
860-
return base.Walk(node);
860+
return true;
861861
}
862862

863863
#endregion

Src/IronPython/Compiler/Ast/PythonReference.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
33
// See the LICENSE file in the project root for more information.
44

5+
#nullable enable
6+
57
namespace IronPython.Compiler.Ast {
68
/// <summary>
79
/// Represents a reference to a name. A PythonReference is created for each name
@@ -14,6 +16,6 @@ public PythonReference(string name) {
1416

1517
public string Name { get; }
1618

17-
internal PythonVariable PythonVariable { get; set; }
19+
internal PythonVariable? PythonVariable { get; set; }
1820
}
1921
}

Src/IronPython/Compiler/Ast/ScopeStatement.cs

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,13 @@ internal bool IsClosure {
9393
/// </summary>
9494
internal bool NeedsLocalsDictionary { get; set; }
9595

96+
/// <summary>
97+
/// True if this scope contains a parameterless call to super().
98+
///
99+
/// It is used to ensure that the first argument is accessible through a closure cell.
100+
/// </summary>
101+
internal bool ContainsSuperCall{ get; set; }
102+
96103
public virtual string Name {
97104
get {
98105
return "<unknown>";
@@ -124,7 +131,7 @@ internal virtual bool IsGlobal {
124131

125132
internal bool NeedsLocalContext {
126133
get {
127-
return NeedsLocalsDictionary || ContainsNestedFreeVariables;
134+
return NeedsLocalsDictionary || ContainsNestedFreeVariables || ContainsSuperCall;
128135
}
129136
}
130137

@@ -350,6 +357,7 @@ internal virtual void FinishBind(PythonNameBinder binder) {
350357
Debug.Assert(parentClosure != null);
351358

352359
foreach (var variable in FreeVariables) {
360+
Debug.Assert(!HasClosureVariable(closureVariables, variable));
353361
for (int i = 0; i < parentClosure.Length; i++) {
354362
if (parentClosure[i].Variable == variable) {
355363
Ast prop = LocalParentTuple;
@@ -362,22 +370,19 @@ internal virtual void FinishBind(PythonNameBinder binder) {
362370
}
363371
Debug.Assert(_variableMapping.ContainsKey(variable));
364372

365-
if (closureVariables == null) {
366-
closureVariables = new List<ClosureInfo>();
367-
}
368-
closureVariables.Add(new ClosureInfo(variable, !(this is ClassDefinition)));
373+
closureVariables ??= new List<ClosureInfo>();
374+
closureVariables.Add(new ClosureInfo(variable, this is not ClassDefinition));
369375
}
370376
}
371377

372378
if (Variables != null) {
373379
foreach (PythonVariable variable in Variables.Values) {
374-
if (!HasClosureVariable(closureVariables, variable) &&
375-
variable.Kind is VariableKind.Local or VariableKind.Parameter &&
380+
if (variable.Kind is VariableKind.Local or VariableKind.Parameter &&
376381
(variable.AccessedInNestedScope || ExposesLocalVariable(variable))) {
377382

378-
if (closureVariables == null) {
379-
closureVariables = new List<ClosureInfo>();
380-
}
383+
Debug.Assert(!HasClosureVariable(closureVariables, variable));
384+
385+
closureVariables ??= new List<ClosureInfo>();
381386
closureVariables.Add(new ClosureInfo(variable, true));
382387
}
383388

@@ -390,7 +395,9 @@ variable.Kind is VariableKind.Local or VariableKind.Parameter &&
390395
_variableMapping[variable] = Ast.Parameter(typeof(object), variable.Name);
391396
}
392397
} else if (variable.Kind == VariableKind.Attribute) {
393-
_variableMapping[variable] = new LookupGlobalVariable(LocalContext, variable.Name, isLocal: true); // TODO: If no user-supplied dictionary is in place, optimize to use more efficient access, see CollectableCompilationMode
398+
// If no user-supplied dictionary is in place, a more efficient access is possible, see CollectableCompilationMode
399+
// However, this would probably have a negligible effect in this case.
400+
_variableMapping[variable] = new LookupGlobalVariable(LocalContext, variable.Name, isLocal: true);
394401
}
395402
}
396403
}
@@ -430,12 +437,12 @@ internal void AddGlobalVariable(PythonVariable variable) {
430437
}
431438

432439
internal PythonReference Reference(string name) {
433-
if (_references == null) {
434-
_references = new Dictionary<string, PythonReference>(StringComparer.Ordinal);
435-
}
436-
PythonReference reference;
437-
if (!_references.TryGetValue(name, out reference)) {
440+
_references ??= new Dictionary<string, PythonReference>(StringComparer.Ordinal);
441+
if (!_references.TryGetValue(name, out PythonReference reference)) {
438442
_references[name] = reference = new PythonReference(name);
443+
if (name == "super" && this is not ClassDefinition) {
444+
Reference("__class__");
445+
}
439446
}
440447
return reference;
441448
}
@@ -671,15 +678,26 @@ internal MSAst.Expression FuncCodeExpr {
671678
}
672679

673680
internal MSAst.MethodCallExpression CreateLocalContext(MSAst.Expression parentContext) {
674-
var closureVariables = _closureVariables;
675-
if (_closureVariables == null) {
676-
closureVariables = new ClosureInfo[0];
681+
var closureVariables = _closureVariables ?? Array.Empty<ClosureInfo>();
682+
683+
int numFreeVars = FreeVariables?.Count ?? 0;
684+
int firstArgIdx = -1;
685+
if ((NeedsLocalsDictionary || ContainsSuperCall) && ArgCount > 0) {
686+
for (int idx = numFreeVars; idx < closureVariables.Length; idx++) {
687+
if (closureVariables[idx].Variable.Kind == VariableKind.Parameter) {
688+
firstArgIdx = idx;
689+
break;
690+
}
691+
}
677692
}
693+
678694
return Ast.Call(
679695
AstMethods.CreateLocalContext,
680696
parentContext,
681697
MutableTuple.Create(ArrayUtils.ConvertAll(closureVariables, x => GetClosureCell(x))),
682-
Ast.Constant(ArrayUtils.ConvertAll(closureVariables, x => x.AccessedInScope ? x.Variable.Name : null))
698+
Ast.Constant(ArrayUtils.ConvertAll(closureVariables, x => x.AccessedInScope ? x.Variable.Name : null)),
699+
AstUtils.Constant(numFreeVars),
700+
AstUtils.Constant(firstArgIdx)
683701
);
684702
}
685703

Src/IronPython/Runtime/Operations/PythonOps.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3622,10 +3622,10 @@ public static object SetName(CodeContext/*!*/ context, string name, object value
36223622

36233623
#region Global Access
36243624

3625-
public static CodeContext/*!*/ CreateLocalContext(CodeContext/*!*/ outerContext, MutableTuple boxes, string[] args) {
3625+
public static CodeContext/*!*/ CreateLocalContext(CodeContext/*!*/ outerContext, MutableTuple boxes, string[] args, int numFreeVars, int arg0Idx) {
36263626
return new CodeContext(
36273627
new PythonDictionary(
3628-
new RuntimeVariablesDictionaryStorage(boxes, args)
3628+
new RuntimeVariablesDictionaryStorage(boxes, args, numFreeVars, arg0Idx)
36293629
),
36303630
outerContext.ModuleContext
36313631
);

Src/IronPython/Runtime/RuntimeVariablesDictionaryStorage.cs

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,30 +4,54 @@
44

55
using System;
66
using System.Collections.Generic;
7+
using System.Diagnostics;
8+
79
using Microsoft.Scripting;
810
using Microsoft.Scripting.Runtime;
911

1012
namespace IronPython.Runtime {
1113
internal class RuntimeVariablesDictionaryStorage : CustomDictionaryStorage {
1214
private readonly MutableTuple _boxes;
1315
private readonly string[] _args;
16+
private readonly int _numFreeVars;
17+
private readonly int _arg0Idx;
18+
19+
public RuntimeVariablesDictionaryStorage(MutableTuple boxes, string[] args, int numFreeVars, int arg0Idx) {
20+
Debug.Assert(0 <= numFreeVars && numFreeVars <= args.Length);
21+
Debug.Assert(arg0Idx == -1 || numFreeVars <= arg0Idx && arg0Idx < args.Length);
1422

15-
public RuntimeVariablesDictionaryStorage(MutableTuple boxes, string[] args) {
1623
_boxes = boxes;
1724
_args = args;
25+
_numFreeVars = numFreeVars;
26+
_arg0Idx = arg0Idx;
1827
}
1928

20-
internal MutableTuple Tuple {
21-
get {
22-
return _boxes;
23-
}
24-
}
29+
/// <summary>
30+
/// Closure tuple.
31+
/// </summary>
32+
internal MutableTuple Tuple => _boxes;
2533

26-
internal string[] Names {
27-
get {
28-
return _args;
29-
}
30-
}
34+
/// <summary>
35+
/// Names of the variables in the closure.
36+
/// Cell variables (not accessed in current scope) have null names.
37+
/// </summary>
38+
internal string[] Names => _args;
39+
40+
/// <summary>
41+
/// Number of free variables in the closure.
42+
/// If non-zero, cells for free variables are at the beginning of <see cref="Tuple"/>.
43+
/// </summary>
44+
internal int NumFreeVars => _numFreeVars;
45+
46+
/// <summary>
47+
/// Index of the cell of the first positional parameter of the function call.
48+
/// Value -1 means that information is not available.
49+
/// </summary>
50+
/// <remarks>
51+
/// This information is intended to be consumed by a parameterless super() call.
52+
/// For performance reasons, Arg0Idx is only tracked when deemed necessary to support super().
53+
/// </remarks>
54+
internal int Arg0Idx => _arg0Idx;
3155

3256
protected override IEnumerable<KeyValuePair<string, object>> GetExtraItems() {
3357
for (int i = 0; i < _args.Length; i++) {

Src/IronPython/Runtime/Super.cs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,34 @@ public Super() {
2929

3030
#region Python Public API Surface
3131

32-
public void __init__() {
33-
throw PythonOps.RuntimeError("super(): no arguments");
32+
public void __init__(CodeContext context) {
33+
var vars = context.Dict._storage as RuntimeVariablesDictionaryStorage;
34+
35+
// Step 1: access arg[0]
36+
if (vars is null || vars.Arg0Idx < 0) {
37+
throw PythonOps.RuntimeError("super(): no arguments");
38+
}
39+
40+
object? arg0 = vars.GetCell(vars.Arg0Idx).Value;
41+
if (arg0 == Uninitialized.Instance) {
42+
throw PythonOps.RuntimeError("super(): arg[0] deleted");
43+
}
44+
45+
// Step 2: access __class__ cell
46+
int idx = Array.IndexOf(vars.Names, "__class__");
47+
if (idx < 0 || idx >= vars.NumFreeVars) {
48+
throw PythonOps.RuntimeError("super(): __class__ cell not found");
49+
}
50+
51+
object? cls = vars.GetCell(idx).Value;
52+
if (cls == Uninitialized.Instance) {
53+
throw PythonOps.RuntimeError("super(): empty __class__ cell");
54+
}
55+
if (cls is not PythonType type) {
56+
throw PythonOps.RuntimeError("super(): __class__ is not a type ({0})", PythonOps.GetPythonTypeName(cls));
57+
}
58+
59+
__init__(type, arg0);
3460
}
3561

3662
public void __init__([NotNone] PythonType type) {

0 commit comments

Comments
 (0)