Skip to content

Commit be92a8f

Browse files
authored
Update name binding code and comments (#1066)
1 parent 9498c0d commit be92a8f

9 files changed

Lines changed: 69 additions & 99 deletions

File tree

Src/IronPython/Compiler/Ast/Comprehension.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ internal override PythonVariable BindReference(PythonNameBinder binder, PythonRe
251251
}
252252

253253
internal override Ast GetVariableExpression(PythonVariable variable) {
254-
if (variable.IsGlobal) {
254+
if (variable.Kind is VariableKind.Global) {
255255
return GlobalParent.ModuleVariables[variable];
256256
}
257257

Src/IronPython/Compiler/Ast/FlowChecker.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
*
5757
* The bit arrays in the flow checker hold the state and upon encountering NameExpr we figure
5858
* out whether the name has not yet been initialized at all (in which case we need to emit the
59-
* first explicit assignment to Uninitialized.instance and guard the use with an inlined check
59+
* first explicit assignment to Uninitialized.instance and guard the use with an inlined check)
6060
* or whether it is definitely assigned (we don't need to inline the check)
6161
* or whether it may be uninitialized, in which case we must only guard the use by inlining the Uninitialized check
6262
*
@@ -97,7 +97,7 @@ public override bool Walk(IndexExpression node) {
9797
node.Walk(_fc);
9898
return false;
9999
}
100-
100+
101101
public override bool Walk(ParenthesisExpression node) {
102102
return true;
103103
}
@@ -109,14 +109,13 @@ public override bool Walk(TupleExpression node) {
109109
public override bool Walk(ListExpression node) {
110110
return true;
111111
}
112-
112+
113113
public override bool Walk(Parameter node) {
114114
_fc.Define(node.Name);
115115
return true;
116116
}
117117
}
118118

119-
// TODO: probably obsolete, PythonVariable.Deleted does the job better (across scopes)
120119
internal class FlowDeleter : PythonWalkerNonRecursive {
121120
private readonly FlowChecker _fc;
122121

Src/IronPython/Compiler/Ast/PythonAst.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,13 +276,20 @@ internal ModuleContext ModuleContext {
276276
/// </summary>
277277
internal PythonVariable/*!*/ EnsureGlobalVariable(string name) {
278278
PythonVariable variable;
279-
if (!TryGetVariable(name, out variable)) {
279+
if (TryGetVariable(name, out variable)) {
280+
variable.LiftToGlobal();
281+
} else {
280282
variable = CreateVariable(name, VariableKind.Global);
281283
}
282284

283285
return variable;
284286
}
285287

288+
internal override MSAst.Expression GetVariableExpression(PythonVariable variable) {
289+
Debug.Assert(_globalVariables.ContainsKey(variable));
290+
return _globalVariables[variable];
291+
}
292+
286293
#endregion
287294

288295
#region MSASt.Expression Overrides

Src/IronPython/Compiler/Ast/PythonNameBinder.cs

Lines changed: 30 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -11,28 +11,35 @@
1111
using Microsoft.Scripting.Runtime;
1212
using Microsoft.Scripting.Utils;
1313

14+
using IronPython.Runtime;
15+
1416
/*
1517
* The name binding:
1618
*
17-
* The name binding happens in 2 passes.
18-
* In the first pass (full recursive walk of the AST) we resolve locals.
19-
* The second pass uses the "processed" list of all context statements (functions and class
20-
* bodies) and has each context statement resolve its free variables to determine whether
21-
* they are globals or references to lexically enclosing scopes.
19+
* The name binding happens in 4 passes.
20+
*
21+
* The first pass is a full recursive walk of the AST. During this walk, all scopes are established
22+
* (created by functions, classes, comprehensions, generators, and the AST root),
23+
* and in each scope all variables are collected that are defined (local or parameter)
24+
* or declared (global or nonlocal) there. Also, for each scope, all references used in the
25+
* scope are collected but kept unresolved as yet.
26+
*
27+
* The second pass uses the collected stack of all scopes and has each scope resolve its references.
28+
* The references can be resolved locally within the scope or as free variables,
29+
* either as globals or as references to lexically enclosing scopes.
2230
*
23-
* The second pass happens in post-order (the context statement is added into the "processed"
24-
* list after processing its nested functions/statements). This way, when the function is
25-
* processing its free variables, it also knows already which of its locals are being lifted
26-
* to the closure and can report error if such closure variable is being deleted.
31+
* The second pass happens in post-order (a scope is processed after processing all its nested scopes).
32+
* Consequently, when the scope is processing its free variables, it also knows already
33+
* which of its locals are being lifted to the closure.
2734
*
28-
* This is illegal in Python:
35+
* The third pass goes over all the scopes again, this time in pre-order (except for the root AST).
36+
* In this pass, all scopes build theirs closures, if needed. Becasue nested scopes are processed
37+
* after their encompassing scope, scopes may use the already prepared closure
38+
* from their parent.
2939
*
30-
* def f():
31-
* x = 10
32-
* if (cond): del x # illegal because x is a closure variable
33-
* def g():
34-
* print x
35-
* TODO: the example above is no longer valid; also the description of the name binding algorithm does not match the code
40+
* During the fourth pass, each scope is being inspected by the FlowChecker,
41+
* which analyzes the data flow in the scope. Information collected during this analysis
42+
* allows for some optimizations later on, when the expression trees are being constructed.
3643
*/
3744

3845
namespace IronPython.Compiler.Ast {
@@ -58,37 +65,6 @@ public override bool Walk(ListExpression node) {
5865
}
5966
}
6067

61-
internal class ParameterBinder : PythonWalkerNonRecursive {
62-
private PythonNameBinder _binder;
63-
public ParameterBinder(PythonNameBinder binder) {
64-
_binder = binder;
65-
}
66-
67-
public override bool Walk(Parameter node) {
68-
node.Parent = _binder._currentScope;
69-
node.PythonVariable = _binder.DefineParameter(node.Name);
70-
return false;
71-
}
72-
73-
// TODO: obsolete?
74-
private void WalkTuple(TupleExpression tuple) {
75-
tuple.Parent = _binder._currentScope;
76-
foreach (Expression innerNode in tuple.Items) {
77-
if (innerNode is NameExpression name) {
78-
_binder.DefineName(name.Name);
79-
name.Parent = _binder._currentScope;
80-
name.Reference = _binder.Reference(name.Name);
81-
} else {
82-
WalkTuple((TupleExpression)innerNode);
83-
}
84-
}
85-
}
86-
public override bool Walk(TupleExpression node) {
87-
node.Parent = _binder._currentScope;
88-
return true;
89-
}
90-
}
91-
9268
internal class DeleteBinder : PythonWalkerNonRecursive {
9369
private PythonNameBinder _binder;
9470
public DeleteBinder(PythonNameBinder binder) {
@@ -111,7 +87,6 @@ internal class PythonNameBinder : PythonWalker {
11187

11288
private readonly DefineBinder _define;
11389
private readonly DeleteBinder _delete;
114-
private readonly ParameterBinder _parameter;
11590

11691
#endregion
11792

@@ -120,7 +95,6 @@ internal class PythonNameBinder : PythonWalker {
12095
private PythonNameBinder(CompilerContext context) {
12196
_define = new DefineBinder(this);
12297
_delete = new DeleteBinder(this);
123-
_parameter = new ParameterBinder(this);
12498
Context = context;
12599
}
126100

@@ -197,12 +171,11 @@ internal PythonVariable DefineDeleted(string name) {
197171
}
198172

199173
internal void ReportSyntaxWarning(string message, Node node) {
200-
Context.Errors.Add(Context.SourceUnit, message, node.Span, -1, Severity.Warning);
174+
Context.Errors.Add(Context.SourceUnit, message, node.Span, ErrorCodes.SyntaxError, Severity.Warning);
201175
}
202176

203177
internal void ReportSyntaxError(string message, Node node) {
204-
// TODO: Change the error code (-1)
205-
Context.Errors.Add(Context.SourceUnit, message, node.Span, -1, Severity.FatalError);
178+
Context.Errors.Add(Context.SourceUnit, message, node.Span, ErrorCodes.SyntaxError, Severity.FatalError);
206179
}
207180

208181
#region AstBinder Overrides
@@ -635,12 +608,12 @@ public override bool Walk(FromImportStatement node) {
635608
// FunctionDefinition
636609
public override bool Walk(FunctionDefinition node) {
637610
node._nameVariable = _globalScope.EnsureGlobalVariable("__name__");
638-
611+
639612
// Name is defined in the enclosing context
640613
if (!node.IsLambda) {
641614
node.PythonVariable = DefineName(node.Name);
642615
}
643-
616+
644617
// process the default arg values in the outer context
645618
foreach (Parameter p in node.Parameters) {
646619
p.DefaultValue?.Walk(this);
@@ -658,7 +631,8 @@ public override bool Walk(FunctionDefinition node) {
658631
PushScope(node);
659632

660633
foreach (Parameter p in node.Parameters) {
661-
p.Walk(_parameter);
634+
p.Parent = _currentScope;
635+
p.PythonVariable = DefineParameter(p.Name);
662636
}
663637

664638
node.Body.Walk(this);
@@ -705,9 +679,8 @@ public override bool Walk(GlobalStatement node) {
705679
ReportSyntaxError($"name '{n}' is used prior to global declaration", node);
706680
}
707681

708-
// Create the variable in the global context and mark it as global
682+
// Create the variable in the global context or mark it as global
709683
PythonVariable variable = _globalScope.EnsureGlobalVariable(n);
710-
variable.Kind = VariableKind.Global;
711684

712685
if (conflict == null) {
713686
// no previously defined variables, add it to the current scope

Src/IronPython/Compiler/Ast/PythonVariable.cs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public class PythonVariable {
2121

2222
public PythonVariable(string name, VariableKind kind, ScopeStatement/*!*/ scope) {
2323
Assert.NotNull(scope);
24+
Debug.Assert(kind != VariableKind.Global || scope.IsGlobal);
2425
Debug.Assert(kind != VariableKind.Nonlocal || !scope.IsGlobal);
2526
Name = name;
2627
Kind = kind;
@@ -32,18 +33,22 @@ public PythonVariable(string name, VariableKind kind, ScopeStatement/*!*/ scope)
3233
/// </summary>
3334
public string Name { get; }
3435

35-
public bool IsGlobal {
36-
get {
37-
return Kind == VariableKind.Global || Scope.IsGlobal;
38-
}
39-
}
40-
4136
/// <summary>
4237
/// The original scope in which the variable is defined.
4338
/// </summary>
4439
public ScopeStatement Scope { get; }
4540

46-
public VariableKind Kind { get; set; } // TODO: make readonly
41+
public VariableKind Kind { get; private set; }
42+
43+
/// <summary>
44+
/// Transform a local kind of variable in a global scope (yes, they can be created)
45+
/// into a global kind, hence explicitly accessible to nested local scopes.
46+
/// </summary>
47+
internal void LiftToGlobal() {
48+
Debug.Assert(Scope.IsGlobal);
49+
Debug.Assert(Kind is not VariableKind.Parameter and not VariableKind.Nonlocal);
50+
Kind = VariableKind.Global;
51+
}
4752

4853
/// <summary>
4954
/// The actual variable represented by this variable instance.

Src/IronPython/Compiler/Ast/ScopeStatement.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -384,8 +384,7 @@ internal virtual void FinishBind(PythonNameBinder binder) {
384384
if (Variables != null) {
385385
foreach (PythonVariable variable in Variables.Values) {
386386
if (!HasClosureVariable(closureVariables, variable) &&
387-
!variable.IsGlobal &&
388-
variable.Kind != VariableKind.Nonlocal &&
387+
variable.Kind is not VariableKind.Global and not VariableKind.Nonlocal &&
389388
(variable.AccessedInNestedScope || ExposesLocalVariable(variable))) {
390389

391390
if (closureVariables == null) {
@@ -700,7 +699,7 @@ private MSAst.Expression GetClosureCell(ClosureInfo variable) {
700699

701700
internal virtual MSAst.Expression GetVariableExpression(PythonVariable variable) {
702701
Assert.NotNull(variable?.LimitVariable);
703-
if (variable.IsGlobal) {
702+
if (variable.Kind is VariableKind.Global) {
704703
return GlobalParent.ModuleVariables[variable];
705704
}
706705

Src/IronPython/Compiler/Ast/SerializedScopeStatement.cs

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
namespace IronPython.Compiler.Ast {
1616
/// <summary>
1717
/// Fake ScopeStatement for FunctionCode's to hold on to after we have deserialized pre-compiled code.
18+
/// Will never be a subject of name binding.
1819
/// </summary>
1920
internal class SerializedScopeStatement : ScopeStatement {
2021
private readonly string _name;
@@ -74,29 +75,15 @@ public override void Walk(PythonWalker walker) {
7475
throw new InvalidOperationException();
7576
}
7677

77-
public override string Name {
78-
get {
79-
return _name;
80-
}
81-
}
78+
public override string Name => _name;
8279

83-
internal override string Filename {
84-
get {
85-
return _filename;
86-
}
87-
}
80+
internal override string Filename => _filename;
8881

89-
internal override FunctionAttributes Flags {
90-
get {
91-
return _flags;
92-
}
93-
}
82+
internal override FunctionAttributes Flags => _flags;
9483

95-
internal override string[] ParameterNames {
96-
get {
97-
return _parameterNames;
98-
}
99-
}
84+
internal override bool IsGlobal => true;
85+
86+
internal override string[] ParameterNames => _parameterNames;
10087

10188
internal override int ArgCount => _argCount;
10289

Tests/hosting/editor_svcs/test_errorlistener.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ def test_multiple_erroneous_statements(self):
137137

138138
def test_warning(self):
139139
expected = [
140-
("name 'a' is assigned to before global declaration", "global a", -1, self.FatalError), # reports as error since 3.6
140+
("name 'a' is assigned to before global declaration", "global a", 0x10, self.FatalError), # reports as error since 3.6
141141
]
142142
code = """\
143143
def foo():
@@ -148,8 +148,8 @@ def foo():
148148
def test_should_report_multiple_warnings_negative(self):
149149
"Bug #17541, http://www.codeplex.com/IronPython/WorkItem/View.aspx?WorkItemId=17541"
150150
expected = [
151-
("Variable a assigned before global declaration", "global a", -1, self.Warning),
152-
("Variable b assigned before global declaration", "global b", -1, self.Warning),
151+
("Variable a assigned before global declaration", "global a", 0x10, self.Warning),
152+
("Variable b assigned before global declaration", "global b", 0x10, self.Warning),
153153
]
154154
code = """\
155155
def foo():
@@ -164,8 +164,8 @@ def bar():
164164
def test_should_report_both_errors_and_warnings_negative(self):
165165
"Bug #17541, http://www.codeplex.com/IronPython/WorkItem/View.aspx?WorkItemId=17541"
166166
expected = [
167-
("can't assign to keyword", "None", -1, self.Error),
168-
("Variable a assigned before global declaration", "global a", -1, self.Warning),
167+
("can't assign to keyword", "None", 0x10, self.Error),
168+
("Variable a assigned before global declaration", "global a", 0x10, self.Warning),
169169
]
170170
code = """\
171171
None = 2

WhatsNewInPython30.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ New Syntax
4646
- [ ] [PEP 3107][]: Function argument and return value annotations. This provides a standardized way of annotating a function's parameters and return value. There are no semantics attached to such annotations except that they can be introspected at runtime using the `__annotations__` attribute. The intent is to encourage experimentation through metaclasses, decorators or frameworks.
4747
- [ ] [PEP 3102][]: Keyword-only arguments. Named parameters occurring after `*args` in the parameter list must be specified using keyword syntax in the call. You can also use a bare `*` in the parameter list to indicate that you don't accept a variable-length argument list, but you do have keyword-only arguments.
4848
- [ ] Keyword arguments are allowed after the list of base classes in a class definition. This is used by the new convention for specifying a metaclass (see next section), but can be used for other purposes as well, as long as the metaclass supports it.
49-
- [ ] [PEP 3104][]: `nonlocal` statement. Using `nonlocal x` you can now assign directly to a variable in an outer (but non-`global`) scope. `nonlocal` is a new reserved word.
49+
- [x] [PEP 3104][]: `nonlocal` statement. Using `nonlocal x` you can now assign directly to a variable in an outer (but non-`global`) scope. `nonlocal` is a new reserved word.
5050
- [ ] [PEP 3132][]: Extended Iterable Unpacking. You can now write things like `a, b, *rest = some_sequence`. And even `*rest, a = stuff`. The `rest` object is always a (possibly empty) list; the right-hand side may be any iterable
5151
- [x] Dictionary comprehensions: `{k: v for k, v in stuff}` means the same thing as `dict(stuff)` but is more flexible. (This is [PEP 0274][] vindicated. :-)
5252
- [x] Set literals, e.g. `{1, 2}`. Note that `{}` is an empty dictionary; use `set()` for an empty set. Set comprehensions are also supported; e.g., `{x for x in stuff}` means the same thing as `set(stuff)` but is more flexible.

0 commit comments

Comments
 (0)