Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions crates/perry-hir/src/lower/expr_member.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1337,6 +1337,20 @@ pub(super) fn stdlib_namespace_receiver(
};
let name = ident.sym.as_ref();

// #1701: a LOCAL binding (function param / `let` / `const`) that merely
// shares a name with a stdlib namespace is NOT the namespace — it shadows
// it. hono's trie-router has `path` (a URL-path string param) and does
// `path[0] === "/"`; treating that local as the `node:path` namespace
// false-fired the #503 refusal and blocked the whole package from
// compiling. A real stdlib namespace is never a local: it's the global
// (`process`) or an import, which the alias / namespace-import branches
// below resolve. So skip the direct name-match when `name` is shadowed by
// a local. (If a package shadows `process` with its own local, that local
// is genuinely theirs and likewise shouldn't be refused.)
if ctx.lookup_local(name).is_some() {
return None;
}

// Direct global / module specifier match.
if let Some(canon) = STDLIB_NAMESPACE_NAMES.iter().find(|n| **n == name) {
return Some(*canon);
Expand Down
34 changes: 34 additions & 0 deletions test-files/test_issue_1701_local_shadows_stdlib_namespace.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Issue #1701 — the #503 dynamic-stdlib-dispatch guard must NOT fire on a
// LOCAL variable that merely shares a name with a stdlib namespace.
//
// hono's trie-router has a `path` parameter (a URL-path string) and does
// `path[0] === "/"`. The #503 guard (which refuses `node:path`-namespace
// dynamic dispatch like `path[runtimeVar]()`) matched the local `path` by
// NAME, without checking it was actually the imported namespace, and refused
// to compile the whole `hono` package. Fix: a local binding shadowing a
// namespace name is the user's own variable, never the namespace — skip it.
//
// This exercises locals named after several stdlib namespaces, with both
// computed index reads and a computed method call (the exact shapes the guard
// targets, but on locals). All must compile and match Node.

function firstSlash(path: string): number {
const i = 0;
return path[i] === "/" ? 1 : 0;
}

// Local `fs` shadowing the node:fs namespace name, computed method call.
function pick(fs: Record<string, () => string>, key: string): string {
return fs[key]();
}

// Locals named `crypto`, `os`, `url` with computed index.
function joinFirst(crypto: string[], os: number, url: string): string {
const k = os;
return crypto[k] + ":" + url[0];
}

console.log("firstSlash /abc:", firstSlash("/abc"));
console.log("firstSlash abc:", firstSlash("abc"));
console.log("pick:", pick({ go: () => "went" }, "go"));
console.log("joinFirst:", joinFirst(["a", "b"], 1, "xyz"));