Skip to content

Commit 4b75732

Browse files
authored
Prefer local class namespace over lexical scoping (#1151)
* Prefer local class namespace over lexical scoping * Move some expression code to C# * Add failing metaclass scope test
1 parent 100a408 commit 4b75732

8 files changed

Lines changed: 43 additions & 10 deletions

File tree

Src/IronPython/Compiler/Ast/AstMethods.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ internal static class AstMethods {
6161
public static readonly MethodInfo MakeClosureCell = GetMethod((Func<ClosureCell>)PythonOps.MakeClosureCell);
6262
public static readonly MethodInfo MakeClosureCellWithValue = GetMethod((Func<object, ClosureCell>)PythonOps.MakeClosureCellWithValue);
6363
public static readonly MethodInfo LookupName = GetMethod((Func<CodeContext, string, object>)PythonOps.LookupName);
64+
public static readonly MethodInfo LookupLocalName = GetMethod((Func<CodeContext, string, object, object>)PythonOps.LookupLocalName);
6465
public static readonly MethodInfo RemoveName = GetMethod((Action<CodeContext, string>)PythonOps.RemoveName);
6566
public static readonly MethodInfo SetName = GetMethod((Func<CodeContext, string, object, object>)PythonOps.SetName);
6667
public static readonly MethodInfo KeepAlive = GetMethod((Action<object>)GC.KeepAlive);

Src/IronPython/Compiler/Ast/ClassDefinition.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,16 @@ internal override PythonVariable BindReference(PythonNameBinder binder, PythonRe
145145
return null;
146146
}
147147

148+
internal override Ast LookupVariableExpression(PythonVariable variable) {
149+
// Emulates opcode LOAD_CLASSDEREF
150+
return Ast.Call(
151+
AstMethods.LookupLocalName,
152+
LocalContext,
153+
Ast.Constant(variable.Name),
154+
GetVariableExpression(variable)
155+
);
156+
}
157+
148158
private static readonly MSAst.Expression NullLambda = AstUtils.Default(typeof(Func<CodeContext, CodeContext>));
149159

150160
public override MSAst.Expression Reduce() {

Src/IronPython/Compiler/Ast/NameExpression.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public override MSAst.Expression Reduce() {
3939
Ast.Constant(Name)
4040
);
4141
} else {
42-
read = Parent.GetVariableExpression(Reference.PythonVariable);
42+
read = Parent.LookupVariableExpression(Reference.PythonVariable);
4343
}
4444

4545
if (!Assigned && !(read is IPythonGlobalExpression)) {

Src/IronPython/Compiler/Ast/ScopeStatement.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -698,6 +698,9 @@ internal virtual MSAst.Expression GetVariableExpression(PythonVariable variable)
698698
return _variableMapping[variable.LimitVariable];
699699
}
700700

701+
internal virtual Ast LookupVariableExpression(PythonVariable variable)
702+
=> GetVariableExpression(variable);
703+
701704
internal void CreateVariables(ReadOnlyCollectionBuilder<MSAst.ParameterExpression> locals, List<MSAst.Expression> init) {
702705
if (Variables != null) {
703706
foreach (PythonVariable variable in Variables.Values) {

Src/IronPython/Runtime/Operations/PythonOps.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3360,6 +3360,10 @@ public static void RemoveName(CodeContext/*!*/ context, string name) {
33603360
throw PythonOps.NameError(name);
33613361
}
33623362

3363+
public static object? LookupLocalName(CodeContext context, string name, object? defaultValue) {
3364+
return context.TryGetVariable(name, out object? value) ? value : defaultValue;
3365+
}
3366+
33633367
/// <summary>
33643368
/// Called from generated code, helper to do name assignment
33653369
/// </summary>

Tests/test_class.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2749,6 +2749,27 @@ class Child(Parent, Mixin):
27492749

27502750
self.assertEqual(Child.attr_Child, 'attribute set on Child by MetaClass')
27512751

2752+
def test_metaclass_scope(self):
2753+
class MetaClass(type): pass
2754+
2755+
def foo():
2756+
outer = "lexically scoped"
2757+
class C(metaclass=MetaClass):
2758+
x = outer
2759+
return C.x
2760+
2761+
self.assertEqual(foo(), "lexically scoped")
2762+
2763+
class MetaClass(type):
2764+
def __prepare__(*args):
2765+
return dict(outer="from metaclass")
2766+
2767+
if is_cli:
2768+
# https://github.com/IronLanguages/ironpython3/issues/1154
2769+
self.assertEqual(foo(), "lexically scoped")
2770+
else:
2771+
self.assertEqual(foo(), "from metaclass")
2772+
27522773
def test_binary_operator_subclass(self):
27532774
"""subclassing but not overriding shouldn't call __radd__"""
27542775
class A(object):

Tests/test_namebinding.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -211,10 +211,7 @@ class C:
211211
locals()["xyz"] = True
212212
passed = xyz
213213

214-
if is_cli: # https://github.com/IronLanguages/ironpython3/issues/1030
215-
selph.assertTrue(C.passed == False)
216-
else:
217-
selph.assertTrue(C.passed == True)
214+
selph.assertTrue(C.passed == True)
218215

219216
def localsAfterExpr():
220217
exec("pass")
@@ -293,10 +290,7 @@ class c:
293290
abc = a
294291
return c
295292

296-
if is_cli: # https://github.com/IronLanguages/ironpython3/issues/1030
297-
self.assertEqual(f().abc, 2)
298-
else:
299-
self.assertEqual(f().abc, 42)
293+
self.assertEqual(f().abc, 42)
300294

301295
def test_DelBuiltin(self):
302296
# Check that "pow" is defined

Tests/test_scope_stdlib.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def load_tests(loader, standard_tests, pattern):
2020
suite.addTest(test.test_scope.ScopeTests('testCellIsKwonlyArg'))
2121
suite.addTest(test.test_scope.ScopeTests('testCellLeak'))
2222
suite.addTest(test.test_scope.ScopeTests('testClassAndGlobal'))
23-
suite.addTest(unittest.expectedFailure(test.test_scope.ScopeTests('testClassNamespaceOverridesClosure'))) # TODO: figure out
23+
suite.addTest(test.test_scope.ScopeTests('testClassNamespaceOverridesClosure'))
2424
suite.addTest(test.test_scope.ScopeTests('testComplexDefinitions'))
2525
suite.addTest(test.test_scope.ScopeTests('testEvalExecFreeVars'))
2626
suite.addTest(test.test_scope.ScopeTests('testEvalFreeVars'))

0 commit comments

Comments
 (0)