Skip to content

Commit 8cd4482

Browse files
authored
Use CommonDictionaryStorage for class variables (#1531)
1 parent 8b1c977 commit 8cd4482

File tree

5 files changed

+58
-33
lines changed

5 files changed

+58
-33
lines changed

Src/IronPython/Compiler/Ast/ClassDefinition.cs

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System;
66
using System.Collections.Generic;
77
using System.Collections.ObjectModel;
8+
using System.Diagnostics;
89
using System.Diagnostics.CodeAnalysis;
910
using System.Linq;
1011
using System.Runtime.CompilerServices;
@@ -97,13 +98,16 @@ internal override bool HasLateBoundVariableSets {
9798
}
9899
}
99100

100-
internal override bool ExposesLocalVariable(PythonVariable variable) => true;
101+
internal override bool ExposesLocalVariable(PythonVariable variable) {
102+
Debug.Assert(variable.Name == "__class__");
103+
return true;
104+
}
101105

102106

103107
internal override bool TryBindOuter(ScopeStatement from, PythonReference reference, out PythonVariable variable) {
104108
if (reference.Name == "__class__") {
109+
ClassVariable = variable = EnsureClassVariable();
105110
ClassCellVariable = EnsureVariable("__classcell__");
106-
ClassVariable = variable = EnsureVariable(reference.Name);
107111
variable.AccessedInNestedScope = true;
108112
from.AddFreeVariable(variable, true);
109113
for (ScopeStatement scope = from.Parent; scope != this; scope = scope.Parent) {
@@ -122,18 +126,19 @@ internal override bool TryBindOuter(ScopeStatement from, PythonReference referen
122126
// Python semantics: The variables bound local in the class
123127
// scope are accessed by name - the dictionary behavior of classes
124128
if (TryGetVariable(reference.Name, out variable)) {
125-
// TODO: This results in doing a dictionary lookup to get/set the local,
126-
// when it should probably be an uninitialized check / global lookup for gets
127-
// and a direct set
128-
if (variable.Kind == VariableKind.Global) {
129+
if (variable.Kind is VariableKind.Global) {
129130
AddReferencedGlobal(reference.Name);
130-
} else if (variable.Kind == VariableKind.Local) {
131+
} else if (variable.Kind is VariableKind.Local) {
132+
// TODO: This results in doing a dictionary lookup to get/set the local,
133+
// when it should probably be an uninitialized check / global lookup for gets
134+
// and a direct set
131135
return null;
132-
}
133-
134-
if (variable.Kind != VariableKind.Nonlocal) {
136+
} else if (variable.Kind is VariableKind.Attribute) {
137+
return null; // fall back on LookupName/SetName in local context dict, which is faster than LookupGlobalVariable
138+
} else if (variable.Kind is VariableKind.Parameter) {
135139
return variable;
136140
}
141+
// else NonLocal: continue binding
137142
}
138143

139144
// Try to bind in outer scopes, if we have an unqualified exec we need to leave the
@@ -148,6 +153,20 @@ internal override bool TryBindOuter(ScopeStatement from, PythonReference referen
148153
return null;
149154
}
150155

156+
internal override PythonVariable EnsureVariable(string name) {
157+
if (TryGetVariable(name, out PythonVariable variable)) {
158+
return variable;
159+
}
160+
return CreateVariable(name, VariableKind.Attribute);
161+
}
162+
163+
internal PythonVariable EnsureClassVariable() {
164+
if (TryGetVariable("$__class__", out PythonVariable variable)) {
165+
return variable;
166+
}
167+
return CreateVariable("__class__", VariableKind.Local, "$__class__");
168+
}
169+
151170
internal override Ast LookupVariableExpression(PythonVariable variable) {
152171
// Emulates opcode LOAD_CLASSDEREF
153172
return Ast.Call(
@@ -240,6 +259,9 @@ static MSAst.Expression UnpackKeywordsHelper(MSAst.Expression context, ReadOnlyS
240259
}
241260
}
242261

262+
private MSAst.Expression SetLocalName(string name, MSAst.Expression expression)
263+
=> Ast.Call(AstMethods.SetName, LocalContext, Ast.Constant(name), expression);
264+
243265
private Microsoft.Scripting.Ast.LightExpression<Func<CodeContext, CodeContext>> MakeClassBody() {
244266
// we always need to create a nested context for class defs
245267

@@ -260,15 +282,15 @@ private Microsoft.Scripting.Ast.LightExpression<Func<CodeContext, CodeContext>>
260282
init.Add(Ast.Assign(LocalCodeContextVariable, createLocal));
261283

262284
// __module__ = __name__
263-
MSAst.Expression modStmt = AssignValue(GetVariableExpression(ModVariable!), GetVariableExpression(ModuleNameVariable!));
285+
MSAst.Expression modStmt = SetLocalName("__module__", AstUtils.Convert(GetVariableExpression(ModuleNameVariable!), typeof(object)));
264286

265287
// TODO: set __qualname__
266288

267289
// __doc__ = """..."""
268290
MSAst.Expression? docStmt = null;
269291
string doc = GetDocumentation(Body);
270292
if (doc is not null) {
271-
docStmt = AssignValue(GetVariableExpression(DocVariable!), AstUtils.Constant(doc));
293+
docStmt = SetLocalName("__doc__", Ast.Constant(doc, typeof(object)));
272294
}
273295

274296
// Create the body
@@ -280,9 +302,9 @@ private Microsoft.Scripting.Ast.LightExpression<Func<CodeContext, CodeContext>>
280302

281303
// __classcell__ == ClosureCell(__class__)
282304
MSAst.Expression? assignClassCellStmt = null;
283-
if (ClassCellVariable is not null) {
284-
var exp = (ClosureExpression)GetVariableExpression(ClassVariable!);
285-
assignClassCellStmt = AssignValue(GetVariableExpression(ClassCellVariable), exp.ClosureCell);
305+
if (ClassVariable is not null) {
306+
var exp = (ClosureExpression)GetVariableExpression(ClassVariable);
307+
assignClassCellStmt = AssignValue(GetVariableExpression(ClassCellVariable!), exp.ClosureCell);
286308
}
287309

288310
bodyStmt = WrapScopeStatements(

Src/IronPython/Compiler/Ast/ScopeStatement.cs

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ internal virtual MSAst.Expression GetParentClosureTuple() {
311311
throw new NotSupportedException();
312312
}
313313

314-
private bool TryGetAnyVariable(string name, out PythonVariable variable) {
314+
internal bool TryGetVariable(string name, out PythonVariable variable) {
315315
if (Variables != null) {
316316
return Variables.TryGetValue(name, out variable);
317317
} else {
@@ -320,15 +320,6 @@ private bool TryGetAnyVariable(string name, out PythonVariable variable) {
320320
}
321321
}
322322

323-
internal bool TryGetVariable(string name, out PythonVariable variable) {
324-
if (TryGetAnyVariable(name, out variable)) {
325-
return true;
326-
} else {
327-
variable = null;
328-
return false;
329-
}
330-
}
331-
332323
internal virtual bool TryBindOuter(ScopeStatement from, PythonReference reference, out PythonVariable variable) {
333324
// Hide scope contents by default (only functions expose their locals)
334325
variable = null;
@@ -381,7 +372,7 @@ internal virtual void FinishBind(PythonNameBinder binder) {
381372
if (Variables != null) {
382373
foreach (PythonVariable variable in Variables.Values) {
383374
if (!HasClosureVariable(closureVariables, variable) &&
384-
variable.Kind is not VariableKind.Global and not VariableKind.Nonlocal &&
375+
variable.Kind is VariableKind.Local or VariableKind.Parameter &&
385376
(variable.AccessedInNestedScope || ExposesLocalVariable(variable))) {
386377

387378
if (closureVariables == null) {
@@ -398,6 +389,8 @@ variable.Kind is not VariableKind.Global and not VariableKind.Nonlocal &&
398389
} else {
399390
_variableMapping[variable] = Ast.Parameter(typeof(object), variable.Name);
400391
}
392+
} 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
401394
}
402395
}
403396
}
@@ -461,15 +454,15 @@ private bool TryGetNonlocalStatement(string name, out NonlocalStatement node) {
461454
return _nonlocalVars?.TryGetValue(name, out node) ?? false;
462455
}
463456

464-
internal PythonVariable/*!*/ CreateVariable(string name, VariableKind kind) {
457+
internal PythonVariable/*!*/ CreateVariable(string name, VariableKind kind, string key = null) {
465458
EnsureVariables();
466-
Debug.Assert(!Variables.ContainsKey(name));
459+
Debug.Assert(!Variables.ContainsKey(key ?? name));
467460
PythonVariable variable;
468-
Variables[name] = variable = new PythonVariable(name, kind, this);
461+
Variables[key ?? name] = variable = new PythonVariable(name, kind, this);
469462
return variable;
470463
}
471464

472-
internal PythonVariable/*!*/ EnsureVariable(string name) {
465+
internal virtual PythonVariable/*!*/ EnsureVariable(string name) {
473466
PythonVariable variable;
474467
if (!TryGetVariable(name, out variable)) {
475468
return CreateVariable(name, VariableKind.Local);

Src/IronPython/Compiler/Ast/VariableKind.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,15 @@ public enum VariableKind {
3535
///
3636
/// Provides a by-reference access to a local variable in an outer scope.
3737
/// </summary>
38-
Nonlocal
38+
Nonlocal,
39+
40+
/// <summary>
41+
/// Attrribute variable.
42+
///
43+
/// Like a local variable, but is stored directly in the context dictionary,
44+
/// rather than a closure cell.
45+
/// Should only appear in a class lambda.
46+
/// </summary>
47+
Attribute
3948
}
4049
}

Tests/test_metaclass.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ def getclass(self):
323323

324324
if is_cli:
325325
# TODO: Use MyDict as the namespace for the class body lambda
326-
self.assertEqual(attributes, ['__doc__', '__module__', 'getclass', '__classcell__'])
326+
self.assertEqual(set(attributes), {'__module__', '__doc__', 'getclass', '__classcell__'})
327327
else:
328328
self.assertEqual(attributes, ['__module__', '__qualname__', '__doc__', 'getclass', '__classcell__'])
329329

Tests/test_super.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
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-
# Copyright (c) Michael van der Kolff
5+
# Copyright (c) Michael van der Kolff and contributors
66
#
77

88
##
99
## Test whether super() behaves as expected
1010
##
1111

12+
import sys
1213
import warnings
1314
from test.support import check_warnings
1415

0 commit comments

Comments
 (0)