Fix relative import resolution #402
Open
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.
This PR improves name resolution for relative
using
/import
with leading dots (.
,..
,...
) and forward references (imports that appear before the target module is defined in the same file tree). It also adds a diagnostic when the number of leading dots exceeds the available module nesting.Today, LanguageServer/StaticLint resolves relative imports only when the target module is already bound, and often treats top-level file scopes as “parents”, which makes sibling imports fragile. Common project layouts (one wrapper file including multiple modules) hit:
using ..Sibling
before Sibling is defined → unresolved references in the importing moduleimport ..Another
(no selectors) → module name “Another” is not bound locally, so qualified calls (Another.f()
) don’t resolveThis PR implements:
import Module
(no selectors) now binds the module’s identifier in the current scope (matches Julia semantics), so qualified calls likeAnother.f()
resolve without needing selectors.RelativeImportTooManyDots
with message: “Relative import has more leading dots than available module nesting.”Implementation details
imports.jl
:resolve_import_block
: walk leading dots up fromstate.scope
; setRelativeImportTooManyDots
when exceeding the root; when a segment is unresolved (cand === nothing
), push the import expr and enclosing module expr intostate.resolveonly
; return to retry later.resolve_import
: selector-head failure (root2 === nothing
) also schedules a retry._mark_import_arg
: whenusinged == false
(plain import), bind the imported module name intoscope.names
;using
keeps existing behavior (add module toscope.modules
).add_to_imported_modules
: ensurescope.modules
initialized when first used.references.jl
:resolve_ref_from_module(::Scope)
: resolve the module’s own name (bindingof(scope.expr)
) and exported names viascope_exports
.StaticLint.jl
:ResolveOnly
: callresolve_import(x, state)
before resolve_ref/traverse (idempotent; only updates unresolved imports).linting/checks.jl
:RelativeImportTooManyDots
toLintCodes
and descriptions.Tests
using
/import
: module C using..Sibling
bindsSibling
; calls likeSibling.g()
resolve even if Sibling is defined later in B.import ..Another
makesAnother
visible, soAnother.f()
resolves.import ....X
triggersRelativeImportTooManyDots
.Compatibility and performance