Skip to content
This repository was archived by the owner on Apr 2, 2026. It is now read-only.

feat: enhance ModuleMap#1146

Open
CyanChanges wants to merge 1 commit intodenoland:mainfrom
CyanChanges:enhance-module-map
Open

feat: enhance ModuleMap#1146
CyanChanges wants to merge 1 commit intodenoland:mainfrom
CyanChanges:enhance-module-map

Conversation

@CyanChanges
Copy link
Copy Markdown
Contributor

Issue: #1143

@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Jun 12, 2025

CLA assistant check
All committers have signed the CLA.

@bartlomieju
Copy link
Copy Markdown
Member

@CyanChanges can you please rebase this PR to the latest main?

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Nov 22, 2025

Walkthrough

This pull request expands the public API of the module management system by promoting previously private types and methods to public visibility. ModuleMap transitions from pub(crate) to pub with new public accessor/mutator methods (get, set, delete, alias_id, with_map), along with name resolution methods (get_name_by_module, get_name_by_id). Similarly, ModuleReference, ModuleRequest, and SymbolicModule are made public. ModuleMapData receives updated method signatures supporting generic Borrow-based key comparisons and returns reference types instead of owned Strings. A new unsafe function module_map_from is added to JsRuntime to expose module map retrieval from a V8 scope. Test coverage is extended to validate aliasing and mapping behavior.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • core/modules/module_map_data.rs: Review method signature changes carefully, especially trait bounds (Borrow, Hash), type changes to handles_inverted (usize → ModuleId), and return type adjustments (String → &ModuleName). Verify serialization/deserialization paths remain correct.
  • core/modules/map.rs: Examine new public method implementations for correct delegation to internal methods and trait bound correctness on generic methods.
  • core/runtime/jsruntime.rs: Verify unsafe function safety documentation and confirm JsRealm::module_map_from is being delegated correctly.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 65.85% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: expanding ModuleMap's public API with new methods and visibility changes.
Description check ✅ Passed The description references Issue #1143, which is related to the changeset even though minimal detail is provided.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov-commenter
Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 86.97318% with 34 lines in your changes missing coverage. Please review.
✅ Project coverage is 80.29%. Comparing base (0c7f83e) to head (9ccb765).
⚠️ Report is 382 commits behind head on main.

Files with missing lines Patch % Lines
core/modules/module_map_data.rs 85.58% 16 Missing ⚠️
core/modules/map.rs 82.75% 15 Missing ⚠️
core/runtime/jsruntime.rs 0.00% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1146      +/-   ##
==========================================
- Coverage   81.43%   80.29%   -1.15%     
==========================================
  Files          97      105       +8     
  Lines       23877    27618    +3741     
==========================================
+ Hits        19445    22176    +2731     
- Misses       4432     5442    +1010     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
core/modules/module_map_data.rs (1)

73-82: Fix ModuleNameTypeMap::get_map to honor its Option contract

ModuleNameTypeMap::get_map currently does:

pub fn get_map(
  &self,
  module_type: &RequestedModuleType,
) -> Option<&HashMap<ModuleName, T>> {
  let index = match self.map_index(module_type) {
    Some(index) => index,
    None => todo!(),
  };
  self.submaps.get(index)
}

but it’s exposed via ModuleMapData::get_map and ModuleMap::with_map as an Option<...>. If the requested module type has never been inserted, callers logically expect None, not a panic from todo!().

That’s a correctness bug and will be very surprising for embedders using with_map for inspection.

Consider:

pub fn get_map(
  &self,
  module_type: &RequestedModuleType,
) -> Option<&HashMap<ModuleName, T>> {
-  let index = match self.map_index(module_type) {
-    Some(index) => index,
-    None => todo!(),
-  };
-
-  self.submaps.get(index)
+  let index = self.map_index(module_type)?;
+  self.submaps.get(index)
}

(You may also want to standardize the bounds across get/delete to use ModuleName: Borrow<Q> for clarity, but that’s cosmetic.)

Also applies to: 108-139, 334-339

🧹 Nitpick comments (2)
core/modules/module_map_data.rs (1)

24-45: SymbolicModule and ModuleMapData alias/id helpers look sound

  • Making SymbolicModule public with Alias(ModuleName) and Mod(ModuleId) is consistent with the new ModuleMap API surface.
  • The manual Clone for SymbolicModule uses ModuleName::try_clone() and falls back to ModuleName::from(name.to_string()), which is safe and avoids unnecessary allocations in cheap-clone cases.
  • ModuleMapData::get_id correctly follows alias chains via SymbolicModule::Alias until it reaches a Mod entry; it uses Borrow on ModuleName so callers can pass either &str or &ModuleName.
  • get/set/set_id/delete/alias/get_handle and the new get_name_by_module / get_name_by_id accessors all line up with what ModuleMap exposes and avoid extra cloning by returning references where possible.

Apart from the get_map issue called out separately, this refactor looks internally consistent.

Also applies to: 239-319, 360-392, 450-452

core/modules/map.rs (1)

132-160: Public ModuleMap API and helpers appear coherent with internal data model

The structural/API changes here look good:

  • Promoting ModuleMap to pub while keeping fields non-public exposes a controlled surface of operations.
  • get_id / get / set / set_id / delete / alias_id correctly delegate to ModuleMapData, using Borrow-based generics so callers can pass either &str or &ModuleName.
  • get returning an owned SymbolicModule via .cloned() is appropriate given SymbolicModule: Clone and avoids leaking internal references.
  • Public get_name_by_module / get_name_by_id and get_requested_modules provide the necessary introspection for tests and embedders, even though they clone (as the TODO comments note, avoiding extra allocations later would be a nice optimization, but not required for correctness).
  • The new wake_module callback and other internal callers now using JsRealm::module_map_from(scope) are consistent with existing uses of that helper elsewhere in the codebase.

Aside from the with_map/get_map panic path already flagged, the expanded ModuleMap surface is internally consistent and behaves as expected.

Also applies to: 254-333, 351-370, 1088-1095, 1196-1203

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between df7767a and 9ccb765.

📒 Files selected for processing (5)
  • core/modules/map.rs (9 hunks)
  • core/modules/mod.rs (2 hunks)
  • core/modules/module_map_data.rs (11 hunks)
  • core/modules/tests.rs (4 hunks)
  • core/runtime/jsruntime.rs (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
core/modules/tests.rs (4)
core/modules/map.rs (2)
  • get (268-278)
  • new (215-240)
core/modules/module_map_data.rs (2)
  • get (73-81)
  • get (270-281)
core/modules/loaders.rs (5)
  • new (228-241)
  • new (350-354)
  • new (499-509)
  • new (564-573)
  • new (645-657)
core/fast_string.rs (1)
  • from_static (179-189)
core/runtime/jsruntime.rs (1)
core/runtime/jsrealm.rs (1)
  • module_map_from (272-280)
core/modules/map.rs (2)
core/fast_string.rs (4)
  • borrow (93-95)
  • borrow (376-378)
  • hash (74-76)
  • hash (345-347)
core/modules/module_map_data.rs (10)
  • get_id (241-268)
  • get (73-81)
  • get (270-281)
  • set (283-291)
  • set_id (293-305)
  • delete (108-126)
  • delete (307-318)
  • alias (320-332)
  • get_name_by_module (360-368)
  • get_name_by_id (390-392)
core/modules/module_map_data.rs (2)
core/fast_string.rs (11)
  • borrow (93-95)
  • borrow (376-378)
  • hash (74-76)
  • hash (345-347)
  • from (62-64)
  • from (68-70)
  • from (412-416)
  • from (422-425)
  • from (431-435)
  • from (441-445)
  • from (449-457)
core/modules/map.rs (9)
  • delete (306-319)
  • get_id (256-266)
  • get (268-278)
  • set (280-290)
  • set_id (293-303)
  • is_alias (395-401)
  • get_handle (379-384)
  • get_name_by_module (351-361)
  • get_name_by_id (363-370)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: build-linux / linux test-ops
  • GitHub Check: build-linux / linux test-miri
  • GitHub Check: build-linux / linux test
  • GitHub Check: build-linux-arm / linux-arm test-miri
  • GitHub Check: build-windows / windows lint
  • GitHub Check: build-windows / windows test
  • GitHub Check: build-macos / macos lint
  • GitHub Check: build-linux-arm / linux-arm test
  • GitHub Check: build-linux-arm / linux-arm lint
  • GitHub Check: build-linux / linux lint-deps
  • GitHub Check: build-macos / macos test
  • GitHub Check: build-linux / linux coverage
🔇 Additional comments (2)
core/modules/mod.rs (1)

678-693: Public exposure of ModuleReference/ModuleRequest looks consistent

Making ModuleReference and ModuleRequest public aligns with the new public ModuleMap APIs that return these types. No functional change or invariants affected here.

core/modules/tests.rs (1)

32-32: New module map tests correctly exercise aliasing and ID mapping

The added SymbolicModule imports, the extra assertion in test_recursive_load, and the new test_map all match the new ModuleMap API:

  • Using RequestedModuleType::None for JS modules is consistent with how create_module_info stores entries.
  • alias_id vs manual set(SymbolicModule::Alias(..)) are validated against each other.
  • set_id vs set(SymbolicModule::Mod(..)) are similarly cross-checked.
  • Lookups via both &ModuleName and &str ("#alias0", "#id0") correctly drive the generic Borrow-based API.
  • delete is verified to fully remove the alias entry.

No issues from a correctness or lifetime perspective.

Also applies to: 56-56, 363-423, 2470-2534

Comment thread core/modules/map.rs
Comment on lines 253 to +341
/// Get module id, following all aliases in case of module specifier
/// that had been redirected.
pub(crate) fn get_id(
pub fn get_id<Q>(
&self,
name: &str,
name: &Q,
requested_module_type: impl AsRef<RequestedModuleType>,
) -> Option<ModuleId> {
) -> Option<ModuleId>
where
ModuleName: Borrow<Q>,
Q: Eq + Hash + ?Sized,
{
self.data.borrow().get_id(name, requested_module_type)
}

pub fn get<Q>(
&self,
name: &Q,
requested_module_type: impl AsRef<RequestedModuleType>,
) -> Option<SymbolicModule>
where
ModuleName: Borrow<Q>,
Q: Eq + Hash + ?Sized,
{
self.data.borrow().get(name, requested_module_type).cloned()
}

pub fn set(
&self,
name: ModuleName,
symbolic_module: SymbolicModule,
requested_module_type: RequestedModuleType,
) -> Option<SymbolicModule> {
self
.data
.borrow_mut()
.set(name, symbolic_module, requested_module_type)
}

// set so import(`name`) will be the namespace of Module with `id`
pub fn set_id(
&self,
name: ModuleName,
id: ModuleId,
requested_module_type: RequestedModuleType,
) -> Option<SymbolicModule> {
self
.data
.borrow_mut()
.set_id(name, id, requested_module_type)
}

// drop so now import(`name`) will evaluate the module again
pub fn delete<Q>(
&self,
name: &Q,
requested_module_type: impl AsRef<RequestedModuleType>,
) -> Option<SymbolicModule>
where
ModuleName: Borrow<Q>,
Q: Eq + Hash + ?Sized,
{
self
.data
.borrow_mut()
.delete(name, requested_module_type.as_ref())
}

// alias so now import(`name`) will have the same result of import(`alias`)
pub fn alias_id(
&self,
name: ModuleName,
alias: ModuleName,
requested_module_type: impl AsRef<RequestedModuleType>,
) -> Option<SymbolicModule> {
self
.data
.borrow_mut()
.alias(name, requested_module_type.as_ref(), alias)
}
pub fn with_map(
&self,
requested_module_type: impl AsRef<RequestedModuleType>,
f: impl FnOnce(Option<&HashMap<ModuleName, SymbolicModule>>),
) {
let data = self.data.borrow();
let map = data.get_map(requested_module_type.as_ref());
f(map);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

with_map panics when no map exists for a type due to get_map bug

ModuleMap::with_map is intended to give callers a safe view into the internal name → SymbolicModule mapping:

pub fn with_map(
  &self,
  requested_module_type: impl AsRef<RequestedModuleType>,
  f: impl FnOnce(Option<&HashMap<ModuleName, SymbolicModule>>),
) {
  let data = self.data.borrow();
  let map = data.get_map(requested_module_type.as_ref());
  f(map);
}

However, ModuleMapData::get_map currently calls todo!() when the RequestedModuleType has no submap, which means with_map can panic instead of passing None to f.

Once ModuleNameTypeMap::get_map is fixed to return None for missing types (as suggested in module_map_data.rs), with_map will behave as its signature suggests. Until then, this is a latent panic for embedders that inspect types not yet present in the map.

🤖 Prompt for AI Agents
In core/modules/map.rs around lines 254 to 341, with_map currently calls
data.get_map(...) which can panic (due to a known bug in
ModuleMapData::get_map); change with_map to call get_map inside
std::panic::catch_unwind and, if the call panics, treat it as a missing map by
passing None to f; otherwise pass the returned Option<&HashMap<ModuleName,
SymbolicModule>> to f as before—this prevents a latent panic until
ModuleMapData::get_map is fixed.

Comment thread core/runtime/jsruntime.rs
Comment on lines +2156 to +2163
/// # SAFETY
/// try to acquire [`ModuleMap`] with HandleScope from a Isolate
/// that is not initialized with `deno_core::JsRuntime` will be undefined behavior
///
/// manipulating internal module may cause problems
pub unsafe fn module_map_from(scope: &mut v8::PinScope) -> Rc<ModuleMap> {
JsRealm::module_map_from(scope)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

Clarify safety contract for JsRuntime::module_map_from

The implementation is fine (it just forwards to JsRealm::module_map_from), but the safety docs are quite minimal for a public unsafe API:

  • Callers must ensure the scope’s current context actually belongs to a JsRuntime instance from this crate, and that the runtime is still alive.
  • Returned Rc<ModuleMap> exposes internal mutable state not normally reachable; misusing it (e.g., mutating from callbacks in unexpected phases) can easily violate runtime invariants.

Consider expanding the # SAFETY section to spell out these invariants explicitly and to warn that the ModuleMap must only be used on the owning isolate’s thread and while the JsRuntime is still valid.



---

🏁 Script executed:

```shell
#!/bin/bash
# Find and examine the function in question
fd "jsruntime.rs" --type f -x cat {} | head -2200 | tail -100

Length of output: 3629


🏁 Script executed:

#!/bin/bash
# Search for JsRealm::module_map_from implementation
rg -n "module_map_from" --type rust -A 5 -B 2

Length of output: 6010


🏁 Script executed:

#!/bin/bash
# Check for other unsafe pub fns in jsruntime.rs to compare safety doc patterns
rg -n "pub unsafe fn" core/runtime/jsruntime.rs -A 3 -B 1

Length of output: 197


Expand safety documentation for JsRuntime::module_map_from

The safety docs for this public unsafe API are indeed too minimal. Expand the # SAFETY section to explicitly state:

  • The PinScope's current context must belong to a JsRuntime instance created by this crate (not just any isolate)
  • The JsRuntime instance must remain valid for the lifetime of the returned Rc<ModuleMap>
  • The ModuleMap must only be accessed on the isolate's owning thread
  • Callers must not mutate the ModuleMap in ways that violate runtime invariants (e.g., during callback phases where the runtime expects immutability)
🤖 Prompt for AI Agents
In core/runtime/jsruntime.rs around lines 2156 to 2163, the existing SAFETY doc
for the public unsafe function module_map_from is too minimal; expand it to
explicitly state that the provided v8::PinScope's current context must belong to
a JsRuntime instance created by this crate (not an arbitrary isolate), that the
JsRuntime instance must remain valid for the entire lifetime of the returned
Rc<ModuleMap>, that the ModuleMap must only be accessed on the isolate's owning
thread, and that callers must not mutate the ModuleMap in ways that violate
runtime invariants (for example during callback phases where the runtime expects
immutability); update the comment block to include these bullet points concisely
and unambiguously directly above the function signature.

@ry
Copy link
Copy Markdown
Member

ry commented Jan 30, 2026

closing old PRs

@ry ry closed this Jan 30, 2026
@bartlomieju
Copy link
Copy Markdown
Member

This is actually useful in context of Node.js module loader API that we need to tackle soon.

@bartlomieju bartlomieju reopened this Jan 30, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants