From b598255f74694e3c663ca0c85696f7dcb5f6f0f7 Mon Sep 17 00:00:00 2001 From: Ivan FB Date: Mon, 11 May 2026 22:39:41 +0200 Subject: [PATCH] =?UTF-8?q?fix=20Lib-name=20derivation=20breaks=20on=20nam?= =?UTF-8?q?es=20with=20underscores=20(rust.nim:54,=20cpp.nim:38=E2=80=9342?= =?UTF-8?q?).=20parts[0]=20of=20nim=5Ftimer=5Fcreate=20is=20"nim",=20not?= =?UTF-8?q?=20"nim=5Ftimer".=20The=20example=20library=20happens=20to=20be?= =?UTF-8?q?=20=20=20=20nimtimer,=20so=20this=20never=20bites.=20The=20curr?= =?UTF-8?q?entLibName=20short-circuit=20covers=20the=20registered=20case,?= =?UTF-8?q?=20but=20the=20fallback=20path=20is=20wrong,=20and=20silent.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ffi/codegen/rust.nim | 42 ++++++++++++++------------------------ ffi/internal/ffi_macro.nim | 7 +++++++ 2 files changed, 22 insertions(+), 27 deletions(-) diff --git a/ffi/codegen/rust.nim b/ffi/codegen/rust.nim index 0e17b77..98cbdfc 100644 --- a/ffi/codegen/rust.nim +++ b/ffi/codegen/rust.nim @@ -43,17 +43,13 @@ proc nimTypeToRust*(typeName: string): string = else: toPascalCase(t) proc deriveLibName*(procs: seq[FFIProcMeta]): string = - ## Extracts the common prefix before the first `_` from proc names. - ## e.g. ["nimtimer_create", "nimtimer_echo"] → "nimtimer" - if currentLibName.len > 0: - return currentLibName - if procs.len == 0: - return "unknown" - let first = procs[0].procName - let parts = first.split('_') - if parts.len > 0: - return parts[0] - return "unknown" + ## Returns the library name registered by declareLibrary("", T). + ## Caller must guarantee currentLibName is set (genBindings errors otherwise); + ## there is no sound heuristic to recover it from proc names alone -- + ## "nim_timer_create" is ambiguous between "nim" and "nim_timer". + doAssert currentLibName.len > 0, + "deriveLibName called before declareLibrary; this is an internal bug" + currentLibName proc stripLibPrefix*(procName: string, libName: string): string = ## Strips the library prefix from a proc name. @@ -178,22 +174,14 @@ proc generateFfiRs*(procs: seq[FFIProcMeta]): string = lines.add(");") lines.add("") - # Collect unique lib names for #[link(...)] - var libNames: seq[string] = @[] - for p in procs: - if p.libName notin libNames: - libNames.add(p.libName) - - # Derive lib name from proc names if not set - var linkLibName = "" - if libNames.len > 0 and libNames[0].len > 0: - linkLibName = libNames[0] - else: - # derive from first proc name - if procs.len > 0: - let parts = procs[0].procName.split('_') - if parts.len > 0: - linkLibName = parts[0] + # Lib name for #[link(...)]. The macro bakes `libName: currentLibName` into + # every proc meta at registration time; if the registry has procs at all, + # they all share the same libName. No heuristic fallback -- genBindings() + # enforces that declareLibrary() was called. + doAssert procs.len > 0, "generateFfiRs called with empty proc registry" + let linkLibName = procs[0].libName + doAssert linkLibName.len > 0, + "proc meta missing libName; this is an internal bug (declareLibrary unset?)" lines.add("#[link(name = \"$1\")]" % [linkLibName]) lines.add("extern \"C\" {") diff --git a/ffi/internal/ffi_macro.nim b/ffi/internal/ffi_macro.nim index efa4c5b..464644c 100644 --- a/ffi/internal/ffi_macro.nim +++ b/ffi/internal/ffi_macro.nim @@ -1634,6 +1634,13 @@ macro genBindings*( "genBindings: output directory is empty." & " Pass it as an argument or set -d:ffiOutputDir=path/to/output" ) + if currentLibName.len == 0: + error( + "genBindings: library name is empty. Call declareLibrary(\"\", )" & + " BEFORE any {.ffi.} / {.ffiCtor.} annotations so the library name is" & + " registered. The library name cannot be safely derived from proc names" & + " (e.g. \"nim_timer_create\" is ambiguous between \"nim\" and \"nim_timer\")." + ) let lang = targetLang.toLowerAscii() let libName = deriveLibName(ffiProcRegistry) case lang