Skip to content
Open
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
53 changes: 33 additions & 20 deletions mypyc/codegen/emitfunc.py
Original file line number Diff line number Diff line change
Expand Up @@ -413,19 +413,20 @@ def visit_get_attr(self, op: GetAttr) -> None:
):
# Generate code for the following branch here to avoid
# redundant branches in the generated code.
self.emit_attribute_error(branch, cl.name, op.attr)
self.emit_attribute_error(branch, cl, op.attr)
self.emit_line("goto %s;" % self.label(branch.true))
merged_branch = branch
self.emitter.emit_line("}")
if not merged_branch:
exc_class = "PyExc_AttributeError"
self.emitter.emit_line(
'PyErr_SetString({}, "attribute {} of {} undefined");'.format(
exc_class,
repr(op.attr.removeprefix(GENERATOR_ATTRIBUTE_PREFIX)),
repr(cl.name),
)
)
var_name = op.attr.removeprefix(GENERATOR_ATTRIBUTE_PREFIX)
if cl.is_environment:
# Environment classes represent locals, so missing attrs are unbound vars.
exc_class = "PyExc_UnboundLocalError"
exc_msg = f"local variable {var_name!r} referenced before assignment"
else:
exc_class = "PyExc_AttributeError"
exc_msg = f"attribute {var_name!r} of {cl.name!r} undefined"
self.emitter.emit_line(f'PyErr_SetString({exc_class}, "{exc_msg}");')

if attr_rtype.is_refcounted and not op.is_borrowed:
if not merged_branch and not always_defined:
Expand Down Expand Up @@ -919,20 +920,32 @@ def emit_traceback(self, op: Branch) -> None:
if op.traceback_entry is not None:
self.emitter.emit_traceback(self.source_path, self.module_name, op.traceback_entry)

def emit_attribute_error(self, op: Branch, class_name: str, attr: str) -> None:
def emit_attribute_error(self, op: Branch, class_ir: ClassIR, attr: str) -> None:
assert op.traceback_entry is not None
globals_static = self.emitter.static_name("globals", self.module_name)
self.emit_line(
'CPy_AttributeError("%s", "%s", "%s", "%s", %d, %s);'
% (
self.source_path.replace("\\", "\\\\"),
op.traceback_entry[0],
class_name,
attr.removeprefix(GENERATOR_ATTRIBUTE_PREFIX),
op.traceback_entry[1],
globals_static,
if class_ir.is_environment:
self.emit_line(
'CPy_UnboundLocalError("%s", "%s", "%s", %d, %s);'
% (
self.source_path.replace("\\", "\\\\"),
op.traceback_entry[0],
attr.removeprefix(GENERATOR_ATTRIBUTE_PREFIX),
op.traceback_entry[1],
globals_static,
)
)
else:
self.emit_line(
'CPy_AttributeError("%s", "%s", "%s", "%s", %d, %s);'
% (
self.source_path.replace("\\", "\\\\"),
op.traceback_entry[0],
class_ir.name,
attr.removeprefix(GENERATOR_ATTRIBUTE_PREFIX),
op.traceback_entry[1],
globals_static,
)
)
)
if DEBUG_ERRORS:
self.emit_line('assert(PyErr_Occurred() != NULL && "failure w/o err!");')

Expand Down
6 changes: 6 additions & 0 deletions mypyc/ir/class_ir.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ def __init__(
module_name: str,
is_trait: bool = False,
is_generated: bool = False,
is_environment: bool = False,
is_abstract: bool = False,
is_ext_class: bool = True,
is_final_class: bool = False,
Expand All @@ -99,6 +100,8 @@ def __init__(
self.module_name = module_name
self.is_trait = is_trait
self.is_generated = is_generated
# Environment classes represent locals and should emit UnboundLocalError for missing vars.
self.is_environment = is_environment
self.is_abstract = is_abstract
self.is_ext_class = is_ext_class
self.is_final_class = is_final_class
Expand Down Expand Up @@ -231,6 +234,7 @@ def __repr__(self) -> str:
"ClassIR("
"name={self.name}, module_name={self.module_name}, "
"is_trait={self.is_trait}, is_generated={self.is_generated}, "
"is_environment={self.is_environment}, "
"is_abstract={self.is_abstract}, is_ext_class={self.is_ext_class}, "
"is_final_class={self.is_final_class}"
")".format(self=self)
Expand Down Expand Up @@ -380,6 +384,7 @@ def serialize(self) -> JsonDict:
"is_ext_class": self.is_ext_class,
"is_abstract": self.is_abstract,
"is_generated": self.is_generated,
"is_environment": self.is_environment,
"is_augmented": self.is_augmented,
"is_final_class": self.is_final_class,
"inherits_python": self.inherits_python,
Expand Down Expand Up @@ -438,6 +443,7 @@ def deserialize(cls, data: JsonDict, ctx: DeserMaps) -> ClassIR:

ir.is_trait = data["is_trait"]
ir.is_generated = data["is_generated"]
ir.is_environment = data.get("is_environment", False)
ir.is_abstract = data["is_abstract"]
ir.is_ext_class = data["is_ext_class"]
ir.is_augmented = data["is_augmented"]
Expand Down
1 change: 1 addition & 0 deletions mypyc/irbuild/env_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ class is generated, the function environment has not yet been
is_generated=True,
is_final_class=True,
)
env_class.is_environment = True
env_class.reuse_freed_instance = True
env_class.attributes[SELF_NAME] = RInstance(env_class)
if builder.fn_info.is_nested:
Expand Down
3 changes: 2 additions & 1 deletion mypyc/irbuild/function.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,8 @@ def c() -> None:
assert isinstance(fitem, FuncDef), fitem
generator_class_ir = builder.mapper.fdef_to_generator[fitem]
builder.fn_info.generator_class = GeneratorClass(generator_class_ir)
if builder.fn_info.can_merge_generator_and_env_classes():
builder.fn_info.generator_class.ir.is_environment = True

# Functions that contain nested functions need an environment class to store variables that
# are free in their nested functions. Generator functions need an environment class to
Expand Down Expand Up @@ -959,7 +961,6 @@ def gen_native_func_call_and_return(fdef: FuncDef) -> None:
typ, src = builtin_names["builtins.int"]
int_type_obj = builder.add(LoadAddress(typ, src, line))
is_int = builder.builder.type_is_op(impl_to_use, int_type_obj, line)

native_call, non_native_call = BasicBlock(), BasicBlock()
builder.add_bool_branch(is_int, native_call, non_native_call)
builder.activate_block(native_call)
Expand Down
1 change: 1 addition & 0 deletions mypyc/irbuild/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ def setup_generator_class(builder: IRBuilder) -> ClassIR:
generator_class_ir = mapper.fdef_to_generator[builder.fn_info.fitem]
if builder.fn_info.can_merge_generator_and_env_classes():
builder.fn_info.env_class = generator_class_ir
generator_class_ir.is_environment = True
else:
generator_class_ir.attributes[ENV_ATTR_NAME] = RInstance(builder.fn_info.env_class)

Expand Down
2 changes: 2 additions & 0 deletions mypyc/lib-rt/CPy.h
Original file line number Diff line number Diff line change
Expand Up @@ -864,6 +864,8 @@ void CPy_TypeErrorTraceback(const char *filename, const char *funcname, int line
PyObject *globals, const char *expected, PyObject *value);
void CPy_AttributeError(const char *filename, const char *funcname, const char *classname,
const char *attrname, int line, PyObject *globals);
void CPy_UnboundLocalError(const char *filename, const char *funcname, const char *attrname,
int line, PyObject *globals);


// Misc operations
Expand Down
8 changes: 8 additions & 0 deletions mypyc/lib-rt/exc_ops.c
Original file line number Diff line number Diff line change
Expand Up @@ -259,3 +259,11 @@ void CPy_AttributeError(const char *filename, const char *funcname, const char *
PyErr_SetString(PyExc_AttributeError, buf);
CPy_AddTraceback(filename, funcname, line, globals);
}

void CPy_UnboundLocalError(const char *filename, const char *funcname, const char *attrname,
int line, PyObject *globals) {
char buf[500];
snprintf(buf, sizeof(buf), "local variable '%.200s' referenced before assignment", attrname);
PyErr_SetString(PyExc_UnboundLocalError, buf);
CPy_AddTraceback(filename, funcname, line, globals);
}
11 changes: 5 additions & 6 deletions mypyc/test-data/run-generators.test
Original file line number Diff line number Diff line change
Expand Up @@ -866,7 +866,7 @@ def test_bitmap_is_cleared_when_object_is_reused() -> None:
list(gen(True))

# Ensure bitmap has been cleared.
with assertRaises(AttributeError): # TODO: Should be UnboundLocalError
with assertRaises(UnboundLocalError):
list(gen(False))

def gen2(set: bool) -> Iterator[int]:
Expand All @@ -878,7 +878,7 @@ def gen2(set: bool) -> Iterator[int]:
def test_undefined_int_in_environment() -> None:
list(gen2(True))

with assertRaises(AttributeError): # TODO: Should be UnboundLocalError
with assertRaises(UnboundLocalError):
list(gen2(False))

[case testVariableWithSameNameAsHelperMethod]
Expand All @@ -902,10 +902,9 @@ def test_same_names() -> None:
assert list(gen_send()) == [2]
assert list(gen_throw()) == [84]

with assertRaises(AttributeError, "attribute 'send' of 'undefined_gen' undefined"):
# TODO: Should be UnboundLocalError, this test verifies that the attribute name
# matches the variable name in the input code, since internally it's generated
# with a prefix.
with assertRaises(UnboundLocalError, "local variable 'send' referenced before assignment"):
# this test verifies that the attribute name matches the variable name
# in the input code, since internally it's generated with a prefix.
list(undefined())

[case testGeneratorInheritance]
Expand Down