Skip to content

Commit 5444c39

Browse files
authored
Execute class body lambda in outer context (#1542)
* Create class context as argument for class body lambda * Execute class body lambda in outer context
1 parent 16d383a commit 5444c39

9 files changed

Lines changed: 57 additions & 20 deletions

File tree

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[], int, int, CodeContext>)PythonOps.CreateLocalContext);
75+
public static readonly MethodInfo CreateLocalContext = GetMethod((Func<CodeContext, MutableTuple, string[], int, int, bool, 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/ClassDefinition.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ public class ClassDefinition : ScopeStatement {
3535

3636
private static int _classId;
3737

38-
private static readonly MSAst.ParameterExpression _parentContextParam = Ast.Parameter(typeof(CodeContext), "$parentContext");
39-
private static readonly MSAst.Expression _tupleExpression = MSAst.Expression.Call(AstMethods.GetClosureTupleFromContext, _parentContextParam);
38+
private static readonly MSAst.ParameterExpression _outerContextParam = Ast.Parameter(typeof(CodeContext), "$outerContext");
39+
private static readonly MSAst.Expression _tupleExpression = MSAst.Expression.Call(AstMethods.GetClosureTupleFromContext, _outerContextParam);
4040

4141
public ClassDefinition(string name, IReadOnlyList<Expression>? bases, IReadOnlyList<Keyword>? keywords, Statement? body = null) {
4242
_name = name;
@@ -272,13 +272,13 @@ private Microsoft.Scripting.Ast.LightExpression<Func<CodeContext, CodeContext>>
272272
locals.Add(LocalCodeContextVariable);
273273
locals.Add(PythonAst._globalContext);
274274

275-
init.Add(Ast.Assign(PythonAst._globalContext, new GetGlobalContextExpression(_parentContextParam)));
275+
init.Add(Ast.Assign(PythonAst._globalContext, new GetGlobalContextExpression(_outerContextParam)));
276276

277277
GlobalParent.PrepareScope(locals, init);
278278

279279
CreateVariables(locals, init);
280280

281-
var createLocal = CreateLocalContext(_parentContextParam);
281+
var createLocal = CreateLocalContext(_outerContextParam, newNamespace: false);
282282

283283
init.Add(Ast.Assign(LocalCodeContextVariable, createLocal));
284284

@@ -328,7 +328,7 @@ private Microsoft.Scripting.Ast.LightExpression<Func<CodeContext, CodeContext>>
328328
bodyStmt
329329
),
330330
Name + "$" + Interlocked.Increment(ref _classId),
331-
new[] { _parentContextParam }
331+
new[] { _outerContextParam }
332332
);
333333

334334
return lambda;

Src/IronPython/Compiler/Ast/ScopeStatement.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -677,7 +677,7 @@ internal MSAst.Expression FuncCodeExpr {
677677
}
678678
}
679679

680-
internal MSAst.MethodCallExpression CreateLocalContext(MSAst.Expression parentContext) {
680+
internal MSAst.MethodCallExpression CreateLocalContext(MSAst.Expression parentContext, bool newNamespace = true) {
681681
var closureVariables = _closureVariables ?? Array.Empty<ClosureInfo>();
682682

683683
int numFreeVars = FreeVariables?.Count ?? 0;
@@ -697,7 +697,8 @@ internal MSAst.MethodCallExpression CreateLocalContext(MSAst.Expression parentCo
697697
MutableTuple.Create(ArrayUtils.ConvertAll(closureVariables, x => GetClosureCell(x))),
698698
Ast.Constant(ArrayUtils.ConvertAll(closureVariables, x => x.AccessedInScope ? x.Variable.Name : null)),
699699
AstUtils.Constant(numFreeVars),
700-
AstUtils.Constant(firstArgIdx)
700+
AstUtils.Constant(firstArgIdx),
701+
AstUtils.Constant(newNamespace)
701702
);
702703
}
703704

Src/IronPython/Runtime/CommonDictionaryStorage.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ internal class CommonDictionaryStorage : DictionaryStorage, ISerializable, IDese
6767
public CommonDictionaryStorage() { }
6868

6969
/// <summary>
70-
/// Creates a new dictionary storage with no buckets
70+
/// Creates a new dictionary storage with buckets
7171
/// </summary>
7272
public CommonDictionaryStorage(int count) {
7373
_buckets = new Bucket[(int)(count / Load + 2)];

Src/IronPython/Runtime/CustomDictionaryStorage.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,17 @@
1111

1212
namespace IronPython.Runtime {
1313
internal abstract class CustomDictionaryStorage : DictionaryStorage {
14-
private readonly CommonDictionaryStorage/*!*/ _storage = new CommonDictionaryStorage();
14+
private readonly CommonDictionaryStorage/*!*/ _storage;
15+
16+
public CustomDictionaryStorage() {
17+
_storage = new();
18+
}
19+
20+
protected CustomDictionaryStorage(CommonDictionaryStorage storage) {
21+
_storage = storage;
22+
}
23+
24+
public CommonDictionaryStorage Storage => _storage;
1525

1626
public override void Add(ref DictionaryStorage storage, object key, object value) {
1727
Add(key, value);

Src/IronPython/Runtime/FunctionCode.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -585,7 +585,7 @@ internal object Call(CodeContext/*!*/ context) {
585585
throw PythonOps.TypeError("cannot exec code object that contains free variables: {0}", co_freevars.__repr__(context));
586586
}
587587

588-
if (Target == null || (Target.GetMethodInfo() != null && Target.GetMethodInfo().DeclaringType == typeof(PythonCallTargets))) {
588+
if (Target == null || (Target.GetMethodInfo()?.DeclaringType == typeof(PythonCallTargets))) {
589589
UpdateDelegate(context.LanguageContext, true);
590590
}
591591

Src/IronPython/Runtime/Operations/PythonOps.cs

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1458,9 +1458,23 @@ public static void InitializeForFinalization(CodeContext/*!*/ context, object ne
14581458
}
14591459
} // else metaclass is expected to be a callable and overrides any inherited metaclass through any bases
14601460

1461+
// Prepare class context
1462+
// The class context is like the parent context but with its own attribute dict.
1463+
// Or another way: the class context is like the local context of the class lambda but without the __class__ variable.
1464+
var attrStorage = new CommonDictionaryStorage();
1465+
PythonDictionary attrDict = parentContext.Dict._storage switch {
1466+
// If the parent context dict is backed by RuntimeVariablesDictionaryStorage,
1467+
// the class context dict also has to be backed by RuntimeVariablesDictionaryStorage so that the closure is preserved.
1468+
RuntimeVariablesDictionaryStorage parentStorage =>
1469+
new PythonDictionary(new RuntimeVariablesDictionaryStorage(parentStorage, attrStorage)),
1470+
// Otherwise a standard dict suffices.
1471+
_ => new PythonDictionary(attrStorage),
1472+
};
1473+
CodeContext classContext = new CodeContext(attrDict, parentContext.ModuleContext);
1474+
14611475
// Call class body lambda
1462-
CodeContext classContext = func(parentContext);
1463-
PythonDictionary vars = classContext.Dict;
1476+
CodeContext localContext = func(classContext);
1477+
PythonDictionary vars = localContext.Dict;
14641478

14651479
// Prepare classdict
14661480
// TODO: prepared classdict should be used by `func` (PEP 3115)
@@ -3622,10 +3636,19 @@ public static object SetName(CodeContext/*!*/ context, string name, object value
36223636

36233637
#region Global Access
36243638

3625-
public static CodeContext/*!*/ CreateLocalContext(CodeContext/*!*/ outerContext, MutableTuple boxes, string[] args, int numFreeVars, int arg0Idx) {
3639+
public static CodeContext/*!*/ CreateLocalContext(CodeContext/*!*/ outerContext, MutableTuple boxes, string[] args, int numFreeVars, int arg0Idx, bool newAttribStorage) {
3640+
CommonDictionaryStorage? attribs = null;
3641+
if (!newAttribStorage) {
3642+
attribs = outerContext.Dict._storage switch {
3643+
CustomDictionaryStorage vars => vars.Storage,
3644+
CommonDictionaryStorage commonStorage => commonStorage,
3645+
_ => new()
3646+
};
3647+
}
3648+
36263649
return new CodeContext(
36273650
new PythonDictionary(
3628-
new RuntimeVariablesDictionaryStorage(boxes, args, numFreeVars, arg0Idx)
3651+
new RuntimeVariablesDictionaryStorage(boxes, args, numFreeVars, arg0Idx, attribs ?? new())
36293652
),
36303653
outerContext.ModuleContext
36313654
);

Src/IronPython/Runtime/RuntimeVariablesDictionaryStorage.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,13 @@ internal class RuntimeVariablesDictionaryStorage : CustomDictionaryStorage {
1616
private readonly int _numFreeVars;
1717
private readonly int _arg0Idx;
1818

19-
public RuntimeVariablesDictionaryStorage(MutableTuple boxes, string[] args, int numFreeVars, int arg0Idx) {
19+
public RuntimeVariablesDictionaryStorage(RuntimeVariablesDictionaryStorage vars, CommonDictionaryStorage storage)
20+
: this(vars._boxes, vars._args, vars._numFreeVars, vars._arg0Idx, storage) {
21+
}
22+
23+
public RuntimeVariablesDictionaryStorage(MutableTuple boxes, string[] args, int numFreeVars, int arg0Idx, CommonDictionaryStorage storage)
24+
: base(storage) {
25+
2026
Debug.Assert(0 <= numFreeVars && numFreeVars <= args.Length);
2127
Debug.Assert(arg0Idx == -1 || numFreeVars <= arg0Idx && arg0Idx < args.Length);
2228

Tests/modules/system_related/test_sys_getframe.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -126,10 +126,7 @@ class x(object):
126126
abc = get_odd_code()
127127
defined = 42
128128

129-
if is_cli:
130-
verify(x.abc, False)
131-
else:
132-
verify(x.abc, True)
129+
verify(x.abc, True)
133130

134131
class x(object):
135132
abc = get_odd_code()

0 commit comments

Comments
 (0)