From 8d8510bc54f7045b14f97561485eb100aa62edd0 Mon Sep 17 00:00:00 2001 From: Ronald Tse Date: Thu, 1 Oct 2026 20:25:55 +0800 Subject: [PATCH] fix: library dependencies cannot be hijacked by same-name maps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A library dependency (posix, unicode) contributes aliases through the .iml harvest. When a load path also holds an emitted map of the same name — the Ruby bridge writes generated unicode.isc next to the maps it serves — the map-import branch shadowed the library and its plain-string aliases rendered as literal sequences instead of classes: alalc-ell's any(greek) guards compiled to the literal 300-character alphabet and gamma nasalization never fired. Library names now claim their dependency slot when the .iml exists; an imported alias resolving to a plain string inside any() also compiles as a class (the qualified 404 pins stay literal — bare references are untouched). --- src/interscript/isc.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/interscript/isc.py b/src/interscript/isc.py index 6c0bac4..f1e147e 100644 --- a/src/interscript/isc.py +++ b/src/interscript/isc.py @@ -673,15 +673,21 @@ def _render_item(item: dict, aliases: dict[str, str]) -> str: return f'any("{_escape("".join(sub["value"] for sub in items))}")' # any() is a character # class (measured against Ruby: the unicode library's greek); - # a bare alias reference stays a literal sequence. + # a bare alias reference stays a literal sequence. Imported + # aliases resolve through the qualified path. if ( len(items) == 1 and items[0]["type"] == "alias_ref" and not items[0].get("map") - and items[0]["name"] in aliases ): - resolved = aliases[items[0]["name"]] - if is_plain_string(resolved): + name = items[0]["name"] + if name in aliases: + resolved = aliases[name] + elif name in _imported: + resolved = _qualified_expr(None, name) + else: + resolved = None + if resolved is not None and is_plain_string(resolved): return f"any({resolved})" return "any([" + ", ".join(_render_item(sub, aliases) for sub in items) + "])" if kind == "range": @@ -1054,8 +1060,11 @@ def isc_to_tree(source: str, filename: str | None = None, on_unsupported: str = # this map's scope; system dependencies do not. libs_dir = Path(filename).parent.parent / "libs" if filename else Path("libs") library_aliases: dict[str, str] = {} + lib_names: set[str] = set() for dep in doc["dependencies"]: if "/" not in dep["target"] and "." not in dep["target"]: + if (libs_dir / f"{dep['target']}.iml").is_file(): + lib_names.add(dep["target"]) library_aliases.update(_library_aliases(dep["target"], libs_dir)) aliases: dict[str, str] = dict(library_aliases) @@ -1082,6 +1091,11 @@ def isc_to_tree(source: str, filename: str | None = None, on_unsupported: str = for d in doc["dependencies"]: if d.get("aliasName"): continue + if d["target"] in lib_names: + # A library dependency (posix, unicode) contributes through + # the harvest above; an emitted map of the same name in a + # load path must not hijack it into the map-import path. + continue from .interscript import _find_map if _find_map(d["target"]) is not None: