fix(compiler): dynamic-import lowering resolves bare project specifiers - #200
Open
techfreaque wants to merge 1 commit into
Open
fix(compiler): dynamic-import lowering resolves bare project specifiers#200techfreaque wants to merge 1 commit into
techfreaque wants to merge 1 commit into
Conversation
Contributor
|
@mabr-pcvisit is attempting to deploy a commit to the Vercel Labs Team on Vercel. A member of the Team first needs to authorize it. |
Two bugs in the dynamic-import codegen path's own resolver
(resolveProjectImport in resolve.ts), independent from the type-checker's
resolution but expected to agree with it:
- dynamicImportProgramTargetOf only ever tried the checker-based
resolveImport for relative/absolute specifiers, never for bare ones. A
package importing its own name (`import("my-package/foo")` from within
my-package, resolved via its package.json self-name "exports") is
something the checker resolves fine, but the lowering path returned null
unconditionally and reported "dynamic import of the program's own module
... is not part of the compiled module graph" even though the exact
specifier had just resolved moments earlier.
- resolveProjectImport's final fallback only tried loadAsFile then a bare
isFile check, never loadAsDirectory (unlike every other resolver in this
file) — so an exports/imports target landing on a directory answered
null instead of falling back to its index file.
Also adds a tsconfig `paths` fallback registry (setTsconfigPaths /
resolveViaTsconfigPaths), consulted only when the package.json-exports walk
finds nothing. It pairs with a separate, independent PR adopting a
project's `paths` into the type-checker; until that lands the registry
stays empty and this fallback is inert.
techfreaque
force-pushed
the
fix-dynamic-import-resolution
branch
from
August 22, 2026 09:29
8ff9d75 to
b4eb36b
Compare
techfreaque
added a commit
to techfreaque/scriptc
that referenced
this pull request
Aug 22, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
scriptc has two independent resolvers: the type-checker (a spawned tsgo process) resolves imports one way, and
packages/compiler/src/frontend/resolve.ts's hand-writtenresolveProjectImport()resolves them again, separately, for the dynamic-import codegen/lowering path (packages/compiler/src/frontend/lowering/lower-modules.ts, used to decide whether a dynamicimport()targets a compiled program module). These two resolvers can disagree, producing two distinct bugs.Bug A.
dynamicImportProgramTargetOf()only calls the checker-basedresolveImport()when the specifier is relative (./foo) or absolute (/foo). For a bare specifier that resolves via a project's ownpackage.jsonself-name "exports" (a package importing its own name, e.g.import("my-package/foo")from withinmy-packageitself) — which the type-checker resolves successfully — the lowering path returnednullunconditionally, producing:even though the checker resolved the exact same specifier moments earlier.
Bug B.
resolveProjectImport()'s final fallback only triedloadAsFile(path)then a bareisFile(path)check — neverloadAsDirectory(path)(i.e.path/index.ts), even thoughloadAsDirectoryalready exists in this same file and is used by every other resolver here (seeresolveRelativeModule). So apackage.json"exports"/"imports" target that itself points at a directory (e.g. a wildcard subpath landing on"./src/foo", meant to be answered by"./src/foo/index.ts") failed to resolve through this path, while the identical directory resolves fine one character away as a plain relative import.Root cause
Both bugs stem from
resolveProjectImport(resolve.ts) and the checker's own resolution independently reimplementing bundler-style resolution and drifting apart on two specific cases: dynamic-import lowering never tried the project resolver for bare specifiers at all (Bug A), and the project resolver itself was missing a fallback arm every sibling resolver in the file already has (Bug B).Fix
lower-modules.ts:dynamicImportProgramTargetOfnow falls back toresolveProjectImportfor non-relative/non-absolute specifiers, mapping the resolved path to a programts.SourceFileviaprogram.getSourceFile.resolve.ts:resolveProjectImport's final fallback now triesloadAsDirectorybetweenloadAsFileand the bareisFilecheck, matching the pattern used elsewhere in this file.resolve.ts/program.ts: adds apaths-alias fallback registry (setTsconfigPaths/resolveViaTsconfigPaths), consulted only when the package.json-exports walk finds nothing, and wired fromprogram.ts'sloadProgram7. This makes the same resolver used here also answer tsconfigpathsaliases, which is useful once a project'spathsare adopted into the type-checker (a separate, independent PR). Until that lands,config.options["paths"]is typically undefined here, so the registry stays empty and this fallback is inert — safe to land standalone.Verification
cd packages/compiler && node node_modules/typescript5/bin/tsc -p tsconfig.json— 0 errors.node_modules/.bin/vitest run packages/compiler/test/ts7/resolver-parity.test.ts— same 4 pre-existing failures with and without this change (confirmed viagit stash), all a Windows path-separator artifact unrelated to this fix (\vs/in resolved paths); theresolveProjectImport-specific tests in this file pass.node_modules/.bin/vitest run tests/harness/diagnostics.test.ts(full 114-program diagnostics corpus, which exercises the frontend/lowering pipeline without requiring a native toolchain) — same 114/114 failures with and without this change (also the identical Windows path-separator artifact, confirmed viagit stash); no diagnostic content changed.aretc.) not available in this environment, so they were not run end-to-end; the change is scoped to the frontend resolution layer covered above.