Skip to content

Commit 5434e9d

Browse files
committed
Fix imports
1 parent e02cca2 commit 5434e9d

File tree

4 files changed

+90
-95
lines changed

4 files changed

+90
-95
lines changed

coconut/compiler/compiler.py

Lines changed: 85 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -261,19 +261,6 @@ def strip_raw_and_b(string):
261261
return raw, has_b, string
262262

263263

264-
def ensure_module_or_create_fake(mod_name):
265-
"""Create a fake module if it does not already exist."""
266-
return handle_indentation("""
267-
try:
268-
{mod_name}
269-
except:
270-
{mod_name} = _coconut.types.ModuleType(_coconut_py_str("{mod_name}"))
271-
else:
272-
if not _coconut.isinstance({mod_name}, _coconut.types.ModuleType):
273-
{mod_name} = _coconut.types.ModuleType(_coconut_py_str("{mod_name}"))
274-
""").format(mod_name=mod_name)
275-
276-
277264
def get_imported_names(imports):
278265
"""Returns all the names imported by imports = [[imp1], [imp2, as], ...] and whether there is a star import."""
279266
saw_names = []
@@ -422,6 +409,60 @@ def call_decorators(decorators, func_name):
422409
return out
423410

424411

412+
def dedent_d_string(text, loc, placeholder=None):
413+
"""Apply PEP 822 dedentation to string contents.
414+
The text must start with a newline (the required newline after opening quotes).
415+
If placeholder is given, it is treated as non-whitespace for indentation calculation
416+
but preserved in the output."""
417+
418+
if not text.startswith("\n"):
419+
raise CoconutDeferredSyntaxError("d-string contents must start with a newline after opening quotes", loc)
420+
text = text[1:] # remove leading newline (not included in result)
421+
422+
lines = text.split("\n")
423+
424+
# determine common indentation
425+
# blank lines are ignored except the last line (closing quotes line)
426+
indent = None
427+
for i, line in enumerate(lines):
428+
is_last = i == len(lines) - 1
429+
# X is an arbitrary non-whitespace character
430+
check_line = line.replace(placeholder, "X") if placeholder else line
431+
if not is_last and check_line.strip() == "":
432+
continue
433+
stripped = check_line.lstrip()
434+
line_indent = check_line[:len(check_line) - len(stripped)]
435+
if indent is None:
436+
indent = line_indent
437+
else:
438+
common = ""
439+
for a, b in zip(indent, line_indent):
440+
if a == b:
441+
common += a
442+
else:
443+
break
444+
indent = common
445+
446+
if indent is None:
447+
indent = ""
448+
449+
# apply dedentation
450+
result_lines = []
451+
for i, line in enumerate(lines):
452+
is_last = i == len(lines) - 1
453+
check_line = line.replace(placeholder, "X") if placeholder else line
454+
if check_line.strip() == "" and not is_last:
455+
result_lines.append("")
456+
elif line.startswith(indent):
457+
result_lines.append(line[len(indent):])
458+
elif indent.startswith(check_line) and check_line.strip() == "":
459+
result_lines.append("")
460+
else:
461+
raise CoconutDeferredSyntaxError("inconsistent indentation in d-string", loc)
462+
463+
return "\n".join(result_lines)
464+
465+
425466
def get_cache_path(codepath):
426467
"""Get the cache filename to use for the given codepath."""
427468
code_dir, code_fname = os.path.split(codepath)
@@ -4002,6 +4043,21 @@ def anon_namedtuple_handle(self, original, loc, tokens):
40024043

40034044
return self.make_namedtuple_call(None, names, types, of_args=items)
40044045

4046+
def ensure_module_or_create_fake(self, mod_name):
4047+
"""Create a fake module if it does not already exist."""
4048+
return handle_indentation("""
4049+
try:
4050+
{mod_name} {type_ignore}
4051+
except:
4052+
{mod_name} = _coconut.types.ModuleType(_coconut_py_str("{mod_name}"))
4053+
else:
4054+
if not _coconut.isinstance({mod_name}, _coconut.types.ModuleType): {type_ignore}
4055+
{mod_name} = _coconut.types.ModuleType(_coconut_py_str("{mod_name}"))
4056+
""").format(
4057+
mod_name=mod_name,
4058+
type_ignore=self.type_ignore_comment(),
4059+
)
4060+
40054061
def _make_import_stmt(self, imp_from, imp, imp_as, raw=False, lazy=False):
40064062
"""Generate an import statement."""
40074063
if not raw and imp != "*":
@@ -4028,7 +4084,7 @@ def _make_import_stmt(self, imp_from, imp, imp_as, raw=False, lazy=False):
40284084
fake_mods = (imp_from if imp_from is not None else imp).split(".")
40294085
for i in range(1, len(fake_mods)):
40304086
mod_name = ".".join(fake_mods[:i])
4031-
out_lines.append(ensure_module_or_create_fake(mod_name))
4087+
out_lines.append(self.ensure_module_or_create_fake(mod_name))
40324088
bind_to = imp_as if imp_as is not None else imp
40334089
out_lines.append('{bind_to} = _coconut_lazy_module("{module}"){attr} {type_ignore}'.format(
40344090
bind_to=bind_to,
@@ -4069,7 +4125,7 @@ def single_import(self, loc, path, imp_as, type_ignore=False, lazy=False):
40694125
fake_mods = imp_as.split(".")
40704126
for i in range(1, len(fake_mods)):
40714127
mod_name = ".".join(fake_mods[:i])
4072-
out.append(ensure_module_or_create_fake(mod_name))
4128+
out.append(self.ensure_module_or_create_fake(mod_name))
40734129
out.append(".".join(fake_mods) + " = " + import_as_var)
40744130
else:
40754131
out.append(self._make_import_stmt(imp_from, imp, imp_as, lazy=lazy))
@@ -4131,22 +4187,14 @@ def universal_import(self, loc, imports, imp_from=None, lazy=False):
41314187
stmts.extend(more_stmts)
41324188
else:
41334189
old_imp, new_imp, version_check = paths
4134-
# we have to do this craziness to get mypy to statically handle the version check
4190+
# TODO: we have to do this craziness to get mypy to statically handle the version check
41354191
stmts.append(
41364192
handle_indentation("""
4137-
try:
4138-
{store_var} = sys {type_ignore}
4139-
except _coconut.NameError:
4140-
{store_var} = _coconut_sentinel
4141-
sys = _coconut_sys
4142-
if sys.version_info >= {version_check}:
4143-
{new_imp}
4193+
if _coconut.typing.TYPE_CHECKING or _coconut_sys.version_info >= {version_check}:
4194+
{new_imp} {type_ignore}
41444195
else:
41454196
{old_imp}
4146-
if {store_var} is not _coconut_sentinel:
4147-
sys = {store_var}
41484197
""").format(
4149-
store_var=self.get_temp_var("sys", loc),
41504198
version_check=version_check,
41514199
new_imp="\n".join(self.single_import(loc, new_imp, imp_as, lazy=lazy)),
41524200
# should only type: ignore the old import
@@ -4158,18 +4206,17 @@ def universal_import(self, loc, imports, imp_from=None, lazy=False):
41584206

41594207
def import_handle(self, original, loc, tokens):
41604208
"""Universalizes imports."""
4161-
# First token is always either "lazy" or "" (from Optional default)
4162-
internal_assert(tokens[0] in ("lazy", ""), original, loc, "invalid import type token", tokens[0])
4163-
lazy = tokens[0] == "lazy"
4164-
tokens = tokens[1:]
4165-
4166-
if len(tokens) == 1:
4167-
imp_from, imports = None, tokens[0]
4168-
elif len(tokens) == 2:
4169-
imp_from, imports = tokens
4209+
if len(tokens) == 2:
4210+
imp_from = None
4211+
lazy, imports = tokens
4212+
elif len(tokens) == 3:
4213+
lazy, imp_from, imports = tokens
41704214
else:
41714215
raise CoconutInternalException("invalid import tokens", tokens)
41724216

4217+
internal_assert(lazy in ("lazy", ""), original, loc, "invalid import type token", lazy)
4218+
lazy = lazy == "lazy"
4219+
41734220
if imp_from == "__future__":
41744221
if lazy:
41754222
raise self.make_err(CoconutSyntaxError, "cannot lazy import from __future__", original, loc)
@@ -4893,61 +4940,7 @@ def t_string_handle(self, original, loc, tokens):
48934940
"""Process Python 3.14 template strings."""
48944941
return self.f_string_handle(original, loc, tokens, is_t=True)
48954942

4896-
@staticmethod
4897-
def dedent_d_string(text, loc, placeholder=None):
4898-
"""Apply PEP 822 dedentation to string contents.
4899-
The text must start with a newline (the required newline after opening quotes).
4900-
If placeholder is given, it is treated as non-whitespace for indentation calculation
4901-
but preserved in the output."""
4902-
4903-
if not text.startswith("\n"):
4904-
raise CoconutDeferredSyntaxError("d-string contents must start with a newline after opening quotes", loc)
4905-
text = text[1:] # remove leading newline (not included in result)
4906-
4907-
lines = text.split("\n")
4908-
4909-
# determine common indentation
4910-
# blank lines are ignored except the last line (closing quotes line)
4911-
indent = None
4912-
for i, line in enumerate(lines):
4913-
is_last = i == len(lines) - 1
4914-
# X is an arbitrary non-whitespace character
4915-
check_line = line.replace(placeholder, "X") if placeholder else line
4916-
if not is_last and check_line.strip() == "":
4917-
continue
4918-
stripped = check_line.lstrip()
4919-
line_indent = check_line[:len(check_line) - len(stripped)]
4920-
if indent is None:
4921-
indent = line_indent
4922-
else:
4923-
common = ""
4924-
for a, b in zip(indent, line_indent):
4925-
if a == b:
4926-
common += a
4927-
else:
4928-
break
4929-
indent = common
4930-
4931-
if indent is None:
4932-
indent = ""
4933-
4934-
# apply dedentation
4935-
result_lines = []
4936-
for i, line in enumerate(lines):
4937-
is_last = i == len(lines) - 1
4938-
check_line = line.replace(placeholder, "X") if placeholder else line
4939-
if check_line.strip() == "" and not is_last:
4940-
result_lines.append("")
4941-
elif line.startswith(indent):
4942-
result_lines.append(line[len(indent):])
4943-
elif indent.startswith(check_line) and check_line.strip() == "":
4944-
result_lines.append("")
4945-
else:
4946-
raise CoconutDeferredSyntaxError("inconsistent indentation in d-string", loc)
4947-
4948-
return "\n".join(result_lines)
4949-
4950-
def d_string_handle(self, original, loc, tokens):
4943+
def d_string_handle(self, loc, tokens):
49514944
"""Process PEP 822 d-strings (dedented strings)."""
49524945
string, = tokens
49534946

@@ -4962,7 +4955,7 @@ def d_string_handle(self, original, loc, tokens):
49624955
raise CoconutDeferredSyntaxError("d-string prefix requires triple-quoted string", loc)
49634956

49644957
# apply dedentation
4965-
text = self.dedent_d_string(text, loc)
4958+
text = dedent_d_string(text, loc)
49664959

49674960
# Python 2 only supports br"..." not rb"..."
49684961
return ("b" if has_b else "") + ("r" if raw else "") + self.wrap_str(text, strchar)
@@ -4989,7 +4982,7 @@ def d_f_string_handle(self, original, loc, tokens, is_t=False):
49894982
placeholder = "\x00"
49904983
full_text = placeholder.join(string_parts)
49914984
internal_assert(lambda: full_text.count(placeholder) == len(string_parts) - 1, "placeholder character found in d-string contents", string_parts)
4992-
new_parts = self.dedent_d_string(full_text, loc, placeholder=placeholder).split(placeholder)
4985+
new_parts = dedent_d_string(full_text, loc, placeholder=placeholder).split(placeholder)
49934986

49944987
# re-wrap as f-string ref and delegate to f_string_handle
49954988
new_ref = self.wrap_f_str(strchar, new_parts, exprs)

coconut/compiler/header.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,6 @@ def process_header_args(which, use_hash, target, no_tco, strict, no_wrap):
288288
__coconut__=make_py_str("__coconut__", target, after_py_str_defined=False),
289289
_coconut_cached__coconut__=make_py_str("_coconut_cached__coconut__", target, after_py_str_defined=False),
290290
coconut_cache_dir=make_py_str(coconut_cache_dir, target, after_py_str_defined=False),
291-
py_str_module=make_py_str("module", target, after_py_str_defined=True),
292291
py_str_typing=make_py_str("typing", target, after_py_str_defined=True),
293292
py_str_MatchError=make_py_str("MatchError", target, after_py_str_defined=True),
294293
object="" if target.startswith("3") else "(object)",

coconut/compiler/templates/header.py_template

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ def _coconut_lazy_module(name, on_import=None, attr=None):
2222
return _coconut.getattr(load(), name)
2323
def __dir__(self):
2424
return _coconut.dir(load())
25-
return lazy_mod(name)
25+
return lazy_mod(_coconut_py_str(name))
2626
else:
2727
class meta(type):
2828
def __getattr__(cls, name):
@@ -31,7 +31,7 @@ def _coconut_lazy_module(name, on_import=None, attr=None):
3131
_coconut.setattr(load(), name, value)
3232
def __dir__(cls):
3333
return _coconut.dir(load())
34-
return meta({py_str_module}, (), {{}})
34+
return meta(_coconut_py_str(name), (), {{}})
3535
@_coconut_wraps(_coconut_py_super)
3636
def _coconut_super(type=None, object_or_type=None):
3737
if type is None:

coconut/tests/src/cocotest/agnostic/primary_2.coco

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -703,6 +703,9 @@ def primary_test_2() -> bool:
703703
assert l(d([3])) == [3]
704704
assert "OrderedDict" in dir(collections)
705705
assert OrderedDict([("a", 1)]) |> dict == {"a": 1}
706+
lazy import os.path
707+
lazy from os.path import join
708+
assert os.path.join("a", "b") == ("a/b" if sys.platform != "win32" else "a\\b") == join("a", "b")
706709
# test remapped imports (queue -> Queue on py2)
707710
lazy import queue
708711
assert hasattr(queue, "Queue")

0 commit comments

Comments
 (0)