Skip to content

Commit a9fda34

Browse files
[mypyc] Raise ImportError instead of AttributeError when from-import fails (#10641)
Fixes mypyc/mypyc#707.
1 parent 790ab35 commit a9fda34

File tree

9 files changed

+392
-315
lines changed

9 files changed

+392
-315
lines changed

mypyc/irbuild/builder.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -288,20 +288,20 @@ def add_to_non_ext_dict(self, non_ext: NonExtClassInfo,
288288
key_unicode = self.load_str(key)
289289
self.call_c(dict_set_item_op, [non_ext.dict, key_unicode, val], line)
290290

291-
def gen_import_from(self, id: str, line: int, imported: List[str]) -> None:
291+
def gen_import_from(self, id: str, globals_dict: Value,
292+
imported: List[str], line: int) -> Value:
292293
self.imports[id] = None
293294

294-
globals_dict = self.load_globals_dict()
295-
null = Integer(0, dict_rprimitive, line)
295+
null_dict = Integer(0, dict_rprimitive, line)
296296
names_to_import = self.new_list_op([self.load_str(name) for name in imported], line)
297-
298-
level = Integer(0, c_int_rprimitive, line)
297+
zero_int = Integer(0, c_int_rprimitive, line)
299298
value = self.call_c(
300299
import_extra_args_op,
301-
[self.load_str(id), globals_dict, null, names_to_import, level],
300+
[self.load_str(id), globals_dict, null_dict, names_to_import, zero_int],
302301
line,
303302
)
304303
self.add(InitStatic(value, id, namespace=NAMESPACE_MODULE))
304+
return value
305305

306306
def gen_import(self, id: str, line: int) -> None:
307307
self.imports[id] = None

mypyc/irbuild/statement.py

+8-6
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
)
2323
from mypyc.ir.rtypes import RInstance, exc_rtuple
2424
from mypyc.primitives.generic_ops import py_delattr_op
25-
from mypyc.primitives.misc_ops import type_op
25+
from mypyc.primitives.misc_ops import type_op, import_from_op
2626
from mypyc.primitives.exc_ops import (
2727
raise_exception_op, reraise_exception_op, error_catch_op, exc_matches_op, restore_exc_info_op,
2828
get_exc_value_op, keep_propagating_op, get_exc_info_op
@@ -172,18 +172,20 @@ def transform_import_from(builder: IRBuilder, node: ImportFrom) -> None:
172172

173173
id = importlib.util.resolve_name('.' * node.relative + node.id, module_package)
174174

175-
imported = [name for name, _ in node.names]
176-
builder.gen_import_from(id, node.line, imported)
177-
module = builder.load_module(id)
175+
globals = builder.load_globals_dict()
176+
imported_names = [name for name, _ in node.names]
177+
module = builder.gen_import_from(id, globals, imported_names, node.line)
178178

179179
# Copy everything into our module's dict.
180180
# Note that we miscompile import from inside of functions here,
181181
# since that case *shouldn't* load it into the globals dict.
182182
# This probably doesn't matter much and the code runs basically right.
183-
globals = builder.load_globals_dict()
184183
for name, maybe_as_name in node.names:
185184
as_name = maybe_as_name or name
186-
obj = builder.py_get_attr(module, name, node.line)
185+
obj = builder.call_c(import_from_op,
186+
[module, builder.load_str(id),
187+
builder.load_str(name), builder.load_str(as_name)],
188+
node.line)
187189
builder.gen_method_call(
188190
globals, '__setitem__', [builder.load_str(as_name), obj],
189191
result_type=None, line=node.line)

mypyc/lib-rt/CPy.h

+3
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,9 @@ int CPyStatics_Initialize(PyObject **statics,
542542
const int *tuples);
543543
PyObject *CPy_Super(PyObject *builtins, PyObject *self);
544544

545+
PyObject *CPyImport_ImportFrom(PyObject *module, PyObject *package_name,
546+
PyObject *import_name, PyObject *as_name);
547+
545548
#ifdef __cplusplus
546549
}
547550
#endif

mypyc/lib-rt/misc_ops.c

+33
Original file line numberDiff line numberDiff line change
@@ -643,3 +643,36 @@ CPy_Super(PyObject *builtins, PyObject *self) {
643643
Py_DECREF(super_type);
644644
return result;
645645
}
646+
647+
// This helper function is a simplification of cpython/ceval.c/import_from()
648+
PyObject *CPyImport_ImportFrom(PyObject *module, PyObject *package_name,
649+
PyObject *import_name, PyObject *as_name) {
650+
// check if the imported module has an attribute by that name
651+
PyObject *x = PyObject_GetAttr(module, import_name);
652+
if (x == NULL) {
653+
// if not, attempt to import a submodule with that name
654+
PyObject *fullmodname = PyUnicode_FromFormat("%U.%U", package_name, import_name);
655+
if (fullmodname == NULL) {
656+
goto fail;
657+
}
658+
659+
// The following code is a simplification of cpython/import.c/PyImport_GetModule()
660+
x = PyObject_GetItem(module, fullmodname);
661+
Py_DECREF(fullmodname);
662+
if (x == NULL) {
663+
goto fail;
664+
}
665+
}
666+
return x;
667+
668+
fail:
669+
PyErr_Clear();
670+
PyObject *package_path = PyModule_GetFilenameObject(module);
671+
PyObject *errmsg = PyUnicode_FromFormat("cannot import name %R from %R (%S)",
672+
import_name, package_name, package_path);
673+
// NULL checks for errmsg and package_name done by PyErr_SetImportError.
674+
PyErr_SetImportError(errmsg, package_name, package_path);
675+
Py_DECREF(package_path);
676+
Py_DECREF(errmsg);
677+
return NULL;
678+
}

mypyc/primitives/misc_ops.py

+9
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,15 @@
123123
error_kind=ERR_MAGIC
124124
)
125125

126+
# Import-from helper op
127+
import_from_op = custom_op(
128+
arg_types=[object_rprimitive, str_rprimitive,
129+
str_rprimitive, str_rprimitive],
130+
return_type=object_rprimitive,
131+
c_function_name='CPyImport_ImportFrom',
132+
error_kind=ERR_MAGIC
133+
)
134+
126135
# Get the sys.modules dictionary
127136
get_module_dict_op = custom_op(
128137
arg_types=[],

mypyc/test-data/fixtures/ir.py

+2
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,8 @@ class ValueError(Exception): pass
229229

230230
class AttributeError(Exception): pass
231231

232+
class ImportError(Exception): pass
233+
232234
class NameError(Exception): pass
233235

234236
class LookupError(Exception): pass

0 commit comments

Comments
 (0)