Skip to content

autosetup: resolve hoisted node_modules packages, and say which kind of unresolved import failed - #168

Open
shellygr wants to merge 2 commits into
masterfrom
shelly/package-resolution-ancestor-walk
Open

autosetup: resolve hoisted node_modules packages, and say which kind of unresolved import failed#168
shellygr wants to merge 2 commits into
masterfrom
shelly/package-resolution-ancestor-walk

Conversation

@shellygr

Copy link
Copy Markdown
Contributor

Two related defects, both measured on a 569-contract corpus sweep

1. Hoisted packages are invisible. build_packages_from_remapping_sources resolves a
node_modules-derived target against base_dir only. npm/yarn workspaces hoist dependencies to an
ancestor node_modules, and node's own resolution walks up the directory chain — ours did not. A
monorepo whose project dir is <repo>/pkg-a therefore fails to resolve a package installed at
<repo>/node_modules/..., and compilation dies with ParserError: Source "node_modules/…" not found
for a dependency that is on disk.

Of 54 rows classified missing-deps/submodule in that sweep, 19 belong to projects whose JS install
genuinely failed and 14 to projects with no build step at all — but 21 had node_modules present.
That last group is what this fixes.

The walk starts at base_dir and stops at the run root; it never escapes it. A package that resolves
under base_dir today resolves to exactly the same path — pinned by a test, since that is the
property that keeps flat projects (the overwhelming majority) unchanged.

2. Unresolved imports all looked alike. Whether the remapping target directory is absent, no
remapping covers the import at all, or the package is installed but the remapped subdirectory inside
it is missing, the failure read the same and the reactive workaround treated them the same. New
utils/import_diagnostics.py classifies what the compiler output plus the filesystem can actually
decide, and the classification is attached to the failure that produced it — cleared, not carried,
when the next attempt fails for an unrelated reason.

Tests

tests/test_import_diagnostics.py, plus additions to test_remappings.py,
test_compilation_workarounds.py and a new test_build_system_run_root.py: flat-project byte
equality, the hoisted case, the walk stopping at the run root, each diagnostic kind, and that a
diagnosis does not survive into an unrelated failure.

pytest -m "not expensive": 769 passed, 9 skipped. pyright: 0 errors.

Composes with #164's context rebasing (same project-dir-vs-run-root family).

shellygr and others added 2 commits August 15, 2026 20:34
… imports

npm/yarn hoist a dependency to the highest node_modules that satisfies every
consumer, so a sub-project's own node_modules/<pkg> frequently does not exist
while the repo root's does. solc has no such resolver: the packages list must
name a directory that exists. A bare `node_modules/...` remapping target is now
looked for in base_dir first and then in each ancestor up to the run root —
node's own order, bounded so the emitted path stays inside the tree certoraRun
uploads. A target that resolves under base_dir always wins, so the walk can only
change an entry whose target does not exist on disk. lib/ and dependencies/
targets are never walked: forge and soldeer do not hoist and a sibling project's
lib/<name> is routinely a different pin.

The run root now reaches the build-system managers as its own argument. It was
being passed as the build-config dir, so for a monorepo sub-project — the only
case where the two differ — both the remapping-context rebasing and the new walk
would have been no-ops.

utils/import_diagnostics.py classifies each `ParserError: Source "S" not found`
against the packages the conf carried. solc names the source unit after
remapping, so a target-prefix match on S decides whether a remapping fired and
one is_dir() decides the rest: the package target is missing, the file inside an
installed package is missing (rebuilding the list provably cannot help), the
import is unmapped, or a project file is absent. The classification never gates a
workaround — it names the class in the log, in the loop's giving-up message, and
in the terminal compilation error.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three fixes on the unresolved-import work, all in the same seam.

last_import_diagnostics was only ever written on an output that had a
source-not-found error, so a run whose imports were fixed and then died of
something unrelated still carried the earlier classification — which
setup_prover appends verbatim to CompilationAnalysisError and the no-progress
message prints. Every exit from the workaround loop now refreshes the field from
the output it is returning on, clearing it when that output has no
source-not-found at all.

The classifier contradicted the resolver for an installed package whose remapped
subdirectory is absent: it tested is_dir() on the full remapped target only, so
the case resolve_node_modules_target reports as `subpath_missing` (it found the
package directory) was described as "the dependency is not installed there and
was not found in any ancestor node_modules". That class now has its own kind,
decided by the node_modules/<pkg> root above the target.

_ancestor_roots took its step count from resolved paths while composing the
candidates textually. When a base_dir reaches the run root through a symlink the
two disagree, and the walk either climbs above the run root — emitting a package
path outside the tree certoraRun uploads — or stops short of it and misses a
hoisted package. The walk is now textual end to end and terminates on the run
root itself.

Tests: the ancestor-beats-nearer-package case now builds a real package
directory (@pkg/artifacts is two segments, so the old fixture created no package
at all and never reached the branch it named), plus coverage for
subpath_missing, both symlink shapes, the package-root split, and an unrelated
terminal failure after an import fix.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant