co-authored by claude fable 5
Version Information
- vyper Version (output of
vyper --version): master (c1b6130 and later)
- OS: all
- Python Version: all
What's your issue about?
-f archive can silently produce an archive that fails to recompile, when a source file is reached via a relative import crossing above the cwd, and that file is covered by a search path above the cwd (e.g. -p ..).
Repro:
/tmp/esc/escaped.vy # x: constant(uint256) = 1
/tmp/esc/pkg/main.vy # from .. import escaped
# y: constant(uint256) = escaped.x
$ cd /tmp/esc/pkg
$ vyper -p .. -f archive -o out.zip main.vy # succeeds
$ vyper -f bytecode out.zip
vyper.exceptions.ModuleNotFound: escaped
contract "main.vy:1", line 1:0
---> 1 from .. import escaped
-------^
Mechanism:
- Archive member names are
_anonymize(safe_relpath(resolved_path)) (vyper/compiler/output_bundle.py), which rewrites .. segments to positional digits: ../escaped.vy is stored as 0/escaped.vy. Zip members can't carry .., so some rewrite is unavoidable.
- Anonymized search paths get the same rewrite (
.. → 0), so absolute imports resolved through an above-cwd search path round-trip fine (0/lib/foo.vy is found via search path 0/lib).
- Relative imports don't use search paths at all — they resolve from the importing module's parent (
vyper/semantics/analysis/imports.py, _load_file with level != 0). On recompile, main.vy's parent is the archive root, so the import requests ../escaped.vy, which doesn't exist in the zip.
Note the rewrite is not broken for all above-cwd sources: a relative import whose importer and importee share the same leading .. prefix still round-trips (e.g. ../a.vy and ../b.vy become 0/a.vy and 0/b.vy, and from . import b resolves from parent 0). The failure is an import edge that crosses the anonymized prefix — importer and importee at different .. depths, most commonly inside-cwd importing outside-cwd as above. Any fix should reject/repair only crossing edges, not all above-cwd bundles.
Related: this is the same failure mode that #4706's .-fallback would have produced. When the file is not covered by any search path, used_search_paths errors out at bundle time; the case here bypasses that check because the file is inside a search path.
How can it be fixed?
Options, roughly in order of preference:
- Re-root the archive at the common ancestor of all resolved source paths (record the compilation target relative to that root) instead of cwd. All member paths become
..-free, relative imports round-trip naturally, and _anonymize's .. rewriting becomes dead code. Changes member naming for existing above-cwd bundles, so downstream verify tooling should be checked.
- Detect and reject at bundle time: during import analysis, flag files loaded via a relative import whose resolution crosses above cwd, and raise a user-facing error (like
BundleError) instead of emitting a broken archive. Requires threading import-edge info into the output bundle; used_search_paths alone can't distinguish relative- from absolute-import reachability.
- Minimal stopgap: warn when any bundled member path contains rewritten
.. segments, noting the archive may not recompile if those files are imported relatively (false positives for absolute-import-only bundles).
co-authored by claude fable 5
Version Information
vyper --version): master (c1b6130 and later)What's your issue about?
-f archivecan silently produce an archive that fails to recompile, when a source file is reached via a relative import crossing above the cwd, and that file is covered by a search path above the cwd (e.g.-p ..).Repro:
Mechanism:
_anonymize(safe_relpath(resolved_path))(vyper/compiler/output_bundle.py), which rewrites..segments to positional digits:../escaped.vyis stored as0/escaped.vy. Zip members can't carry.., so some rewrite is unavoidable...→0), so absolute imports resolved through an above-cwd search path round-trip fine (0/lib/foo.vyis found via search path0/lib).vyper/semantics/analysis/imports.py,_load_filewithlevel != 0). On recompile,main.vy's parent is the archive root, so the import requests../escaped.vy, which doesn't exist in the zip.Note the rewrite is not broken for all above-cwd sources: a relative import whose importer and importee share the same leading
..prefix still round-trips (e.g.../a.vyand../b.vybecome0/a.vyand0/b.vy, andfrom . import bresolves from parent0). The failure is an import edge that crosses the anonymized prefix — importer and importee at different..depths, most commonly inside-cwd importing outside-cwd as above. Any fix should reject/repair only crossing edges, not all above-cwd bundles.Related: this is the same failure mode that #4706's
.-fallback would have produced. When the file is not covered by any search path,used_search_pathserrors out at bundle time; the case here bypasses that check because the file is inside a search path.How can it be fixed?
Options, roughly in order of preference:
..-free, relative imports round-trip naturally, and_anonymize's..rewriting becomes dead code. Changes member naming for existing above-cwd bundles, so downstream verify tooling should be checked.BundleError) instead of emitting a broken archive. Requires threading import-edge info into the output bundle;used_search_pathsalone can't distinguish relative- from absolute-import reachability...segments, noting the archive may not recompile if those files are imported relatively (false positives for absolute-import-only bundles).