From 5050363d5bedc29a9556496f67119e2c848cffc7 Mon Sep 17 00:00:00 2001 From: ASDAlexander77 Date: Tue, 13 Oct 2026 17:37:32 +0100 Subject: [PATCH] A namespace member access looks in the namespace before the local scope `ns.value()` after a local `const value = ...` in the same function resolved `value` as an identifier with the namespace current, which finds locals first: the local wasn't callable ("not a function to call"). The access now resolves the name in the namespace first and falls back to the old lookup. Found in native_ts_graphics' nvrhi_direct.ts (TSLANG_ISSUES.md 18). Co-Authored-By: Claude Opus 5.5 --- tslang/lib/TypeScript/MLIRGenAccessCall.cpp | 9 +++- tslang/test/tester/CMakeLists.txt | 3 ++ .../test/tester/tests/00ns_member_shadowed.ts | 41 +++++++++++++++++++ 3 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 tslang/test/tester/tests/00ns_member_shadowed.ts diff --git a/tslang/lib/TypeScript/MLIRGenAccessCall.cpp b/tslang/lib/TypeScript/MLIRGenAccessCall.cpp index fe8214316..45a2155a8 100644 --- a/tslang/lib/TypeScript/MLIRGenAccessCall.cpp +++ b/tslang/lib/TypeScript/MLIRGenAccessCall.cpp @@ -343,7 +343,14 @@ namespace mlirgen MLIRNamespaceGuard ng(currentNamespace); currentNamespace = namespaceInfo; - return mlirGen(location, name, genContext); + // `ns.value` names the namespace's member: look there before the local scope, + // which resolved it to a local variable `value` of the calling function. + if (auto member = resolveIdentifierInNamespace(location, name, genContext)) + { + return member; + } + + return V(mlirGen(location, name, genContext)); }) .Case([&](auto classStorageType) { LLVM_DEBUG(llvm::dbgs() << "\n\t...field: \t" << cl.getName();); diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index 891d8b56e..4c09ed851 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -570,6 +570,7 @@ tslang_add_test(NAME test-compile-00-namespace-import COMMAND test-runner "${PRO tslang_add_test(NAME test-compile-00-namespace-globals COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00ns4.ts") tslang_add_test(NAME test-compile-00-namespace-class COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00ns5.ts") tslang_add_test(NAME test-compile-00-namespace-bug COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00ns_bug.ts") +tslang_add_test(NAME test-compile-00-namespace-member-shadowed COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00ns_member_shadowed.ts") tslang_add_test(NAME test-compile-00-interface-class COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface.ts") tslang_add_test(NAME test-compile-00-interface-class-2 COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface2.ts") tslang_add_test(NAME test-compile-00-interface-object COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_object.ts") @@ -1102,6 +1103,7 @@ tslang_add_test(NAME test-jit-00-namespace-import COMMAND test-runner -jit "${PR tslang_add_test(NAME test-jit-00-namespace-globals COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00ns4.ts") tslang_add_test(NAME test-jit-00-namespace-class COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00ns5.ts") tslang_add_test(NAME test-jit-00-namespace-bug COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00ns_bug.ts") +tslang_add_test(NAME test-jit-00-namespace-member-shadowed COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00ns_member_shadowed.ts") tslang_add_test(NAME test-jit-00-interface-class COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface.ts") tslang_add_test(NAME test-jit-00-interface-class-2 COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface2.ts") tslang_add_test(NAME test-jit-00-interface-object COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_object.ts") @@ -1974,6 +1976,7 @@ set(TSLANG_CORPUS 00ns3.ts 00ns4.ts 00ns5.ts + 00ns_member_shadowed.ts 00numbers.ts 00object_accessor.ts 00object_annotated_method_extends_interface_multilevel.ts diff --git a/tslang/test/tester/tests/00ns_member_shadowed.ts b/tslang/test/tester/tests/00ns_member_shadowed.ts new file mode 100644 index 000000000..e8cbd4de2 --- /dev/null +++ b/tslang/test/tester/tests/00ns_member_shadowed.ts @@ -0,0 +1,41 @@ +// regression test: `ns.name` is the namespace's member, whatever a local of the same name is. +// +// bug found and fixed: a local `const value = ns.value()`, then `ns.value()` further down the same +// function: the qualified name resolved to the local, which isn't callable ("not a function to +// call"). + +namespace ns { + export function value(): int { + return 42; + } + + export const k = 7; + + export namespace inner { + export function value(): int { + return 9; + } + } +} + +function main() { + const value = ns.value(); + assert(value == 42); + assert(ns.value() == 42); + + const k = ns.k + 1; + assert(k == 8); + assert(ns.k == 7); + + const inner = 3; + assert(ns.inner.value() == 9); + assert(inner == 3); + + { + // a local named as the namespace is the local + const ns = { value: () => 5 }; + assert(ns.value() == 5); + } + + print("done."); +}