Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement UnboundLocalError #30

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/interpreted/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class Constant(Expression):
@dataclass
class Name(Expression):
value: str
ctx: Literal["load", "store"] # Added context


@dataclass
Expand All @@ -47,12 +48,14 @@ class Dict(Expression):
class Attribute(Expression):
value: Expression
attr: str
ctx: Literal["load", "store"]


@dataclass
class Subscript(Expression):
value: Expression
key: Expression
ctx: Literal["load", "store"]


@dataclass
Expand All @@ -73,6 +76,7 @@ class Call(Expression):
@dataclass
class Name(Expression):
id: str
ctx: Literal["load", "store"]


@dataclass
Expand Down
48 changes: 47 additions & 1 deletion src/interpreted/parser.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from __future__ import annotations
from _ast import FunctionDef

import ast
import sys
from keyword import iskeyword
from typing import Any

from interpreted.nodes import (
Assign,
Expand Down Expand Up @@ -716,7 +718,51 @@ def parse_literal(self) -> Expression:

raise ParseError(f"Unexpected token {self.peek().string!r}", self.index)


class ScopeConsistencyChecker(ast.NodeVisitor):
def __init__(self):
self.inconsistent_nodes = []
self.loaded_names = set()

def check_consistency(self, node, variable_name):
if isinstance(node.ctx, nodes.Store):
if variable_name in self.loaded_names:
self.inconsistent_nodes.append(node)

else:
self.loaded_names.add(variable_name)

def visit_Name(self, node):
self.check_consistency(node, node.id)
def visit_Name(self, node):
self.check_consistency(node, node._attributes)

class UnboundLocalErrorChecker(ast.NodeVisitor):

def __init__(self):
self.incosistent_nodes = []

def visit_scope(self, node):
checker = ScopeConsistencyChecker()
checker.visit(node)
self.incosistent_nodes.update(checker.inconsistent_nodes)

def visit_FunctionDef(self, node):
self.visit(node)

def visit_AsyncFunctionDef(self, node):
self.visit(node)

def visit_classDef(self, node):
self.visit(node)

# main driver code for UnboundLocalEerror checker
checker = UnboundLocalErrorChecker()
checker.visit(Module)
if checker.incosistent_nodes:
first_incon = checker.incosistent_nodes[0]
raise ParseError(first_incon.line, first_incon.column)


def assert_expressions_are_targets(expressions: list[Expression], index) -> None:
for target in expressions:
if not isinstance(target, (Name, Subscript)):
Expand Down
Loading