Skip to content

Fix imports whose letter case does not match the file on disk - #188

Open
shellygr wants to merge 3 commits into
masterfrom
shelly/import-filename-mismatch
Open

Fix imports whose letter case does not match the file on disk#188
shellygr wants to merge 3 commits into
masterfrom
shelly/import-filename-mismatch

Conversation

@shellygr

@shellygr shellygr commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

What this fixes

A Solidity project written on macOS or Windows can import a file by a path that does not match
the real filename, usually differing only in letter case. Those filesystems are case-insensitive,
so the project compiles for its authors and cannot compile anywhere case-sensitive. Compilation
analysis used to stop there, with a source-not-found error and nothing to try next.

Approach

New module certora_autosetup/setup/import_case_fix.py. It checks each component of an import
path against the real directory entries with os.scandir instead of asking whether the path
exists. An existence check on a case-insensitive filesystem resolves the mis-cased path happily,
which would make the defect invisible on the machine where the code is written and leave the fixer
firing only in production.

Handled: letter case in the basename, in any directory component, and in the extension, plus
Windows separators and doubled slashes. A path that names no file even case-insensitively is left
alone, because that is an absent file rather than a case problem.

Three bails are worth naming, since none of them is visible from the path alone:

  • An import that resolves through a remapping or package prefix. Where it leads is the conf's
    business, not the project tree's, and repairing it is the packages workaround's job.
  • A file inside a dependency checkout. Its sources are read, since an import may legitimately
    point into one, but never rewritten: a dependency install overwrites them.
  • Two directory entries matching the same component case-insensitively. That is legal on a
    case-sensitive filesystem, so an ambiguous match rewrites nothing.

Integration

In run_compilation_analysis the case fix gets a retry of its own. If it rewrote anything, the
compilation runs again, and on success the import patcher never runs at all. Only if that retry
still fails does the patcher go on top of the case fix rather than instead of it: the patcher
resolves each relative import against its map of real files and skips the ones that miss, so a
corrected spelling is one more import it can canonicalize. If the last attempt fails, both revert
in reverse order of application.

Rewrites are scoped to the quoted path literal, checked against the recorded bytes before anything
is written, kept single-line so the patcher's line-indexed revert stays correct, never applied
inside a dependency tree, and logged at WARNING with the old and new spelling.

Also adds a small package_prefixes() helper to utils/import_diagnostics.py so the fixer can
leave remapped prefixes alone.

Testing

28 unit tests. They assert on planned and applied rewrites built from one real file plus a
wrong-cased import string, rather than creating two files that differ only in case. The latter is
impossible on a case-insensitive filesystem and would make the suite behave differently per
developer machine.

Full suite: 1125 passed, 10 skipped, 11 deselected, with 21 errors from the testcontainers-backed
test_rag_db and test_indexed_tool cases that need a database this machine does not start.
pyright: 0 errors.

Known limitation

extract_imports_multiline accumulates lines until the next ;, so a commented-out import with
no semicolon can make the scan land on an ordinary string constant further down the file and
rewrite it. Comment masking closes the common shape of this, but the accumulation is still there.
Closing it properly wants a real parser; the typed solc AST is not an option at this point because
it only exists after a successful build, and here the build has failed.

🤖 Generated with Claude Code

shellygr and others added 2 commits August 24, 2026 22:43
A project written on a case-insensitive filesystem can import a path that
differs from the real filename, usually only in letter case. It compiles for
its authors and fails on Linux. Compilation analysis now checks each component
of an import path against the actual directory entries and rewrites the quoted
literal to the spelling the filesystem holds.

This runs before the existing import patcher. That patcher resolves relative
imports against a map of real files and skips the ones that miss, so a
misspelled import is precisely what it cannot canonicalize. As with the
patcher, a rewrite survives if the retry compiles and is reverted if it does
not.

Nothing is resolved by guessing. Two candidates, a name that matches only by
edit distance, or an import that goes through a package prefix all leave the
file alone and log the reason.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The import scan accumulates lines until the next semicolon, so a commented-out
import without one ran on to whatever semicolon came next and could land the
rewrite on an ordinary string constant further down the file.

Scanning now runs over a copy of the source with comment bytes replaced by
spaces. Each byte is replaced one for one, so columns still address the real
file and the rewrite is applied to it unchanged.

Comment starts are found by scanning characters in context rather than by
matching // or /* directly. A block comment spans lines, so recognising it at
all needs state carried between them, and the same pass keeps string literals
out of it for free.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@shellygr
shellygr requested a review from jar-ben September 2, 2026 16:34

- Fuzzy or edit-distance matching. A near-miss name is a different contract, and pointing an
import at it yields a project that compiles while verifying code nobody asked about.
- Guessing at name shape: CamelCase versus snake_case, singular versus plural.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you even include these "out of scope" here? They are obviously out of scope. Or are you saying that mixing CamelCase and snake_case works just fine on Windows/macOS?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we can rename the class/file to something obviously saying os-specifing/agnostic spelling issues

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude answers: Fair. They are not case problems at all, and listing them only made the module look like a general spelling fixer. Dropped. What is left is the three bails you cannot see from the path alone: a remapped prefix, a dependency checkout, and two entries matching one component case-insensitively.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude answers: Done. It is import_case_fix.py now, with ImportCaseRewrite and plan/apply/revert_import_case_fixes. I also dropped the right-basename-in-the-wrong-directory branch, which was the one part that was not an OS issue, so the name now covers everything the module does. No observed run ever needed it; the failure behind this PR is a plain basename case mismatch.

Comment thread certora_autosetup/setup/setup_prover.py Outdated
Comment on lines +473 to +477
spelling_fix_applied = self._run_import_spelling_fix(
output, compilation_config.get("packages") or []
)
import_patch_applied = self._run_import_patch()
import_patcher_applied = import_patch_applied

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't run both together? what if spelling fix helps (applies), but import pach fails (and let's suppose we need only the spelling fix).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude answers: Done. The case fix gets its own retry, and the patcher only runs if that retry still fails, on top of the case fix rather than instead of it. That costs a third compile only when the case fix applied and was not enough.

The name promised a general spelling repair, and the "out of scope" list in
its docstring mostly ruled out things nobody would expect it to do anyway.

The module is now import_case_fix, and it handles what a case-insensitive
filesystem actually breaks: letter case in any path component, and Windows
separators. The right-basename-in-the-wrong-directory branch is gone. An
import that resolves nowhere is an absent file, and no observed run needed
that branch.

Compilation analysis runs the case fix on its own and recompiles before
reaching for the import patcher. The patcher rewrites every relative import
in the project, and a project the case fix already repaired should not have
to survive that as well. If it is still needed it goes on top of the case
fix rather than instead of it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shellygr shellygr changed the title Fix imports whose spelling does not match the file on disk Fix imports whose letter case does not match the file on disk Sep 7, 2026
@shellygr
shellygr requested a review from jar-ben September 7, 2026 17:58
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.

2 participants