Skip to main content

detect_duplicate_wrapper_defs

Function detect_duplicate_wrapper_defs 

Source
fn detect_duplicate_wrapper_defs(
    content: &str,
    accessor_producers: &HashMap<String, String>,
) -> Result<(), String>
Expand description

Detect conflicting top-level function and class/namespace definitions in the assembled R wrapper text.

This is the write-time counterpart to the compile-time check_s7_shortcut_collisions (in miniextendr-macros): it catches name collisions that span separate macro expansions and are therefore invisible to any single proc-macro invocation. The two motivating cases (#991):

  1. An S7 fast-path shortcut <Class>_<method> colliding with a #[derive(ExternalPtr)] sidecar accessor <Class>_get_<field> / <Class>_set_<field> – the sidecar field names live in the MX_S7_SIDECAR_PROPS slice, which the impl-block macro can’t see.
  2. Two #[miniextendr(s7)] impl blocks on the same type each emitting a <Class>_<method> shortcut – cross-impl-block, also invisible to a single expansion.

Without this check the second definition silently clobbers the first (last-write-wins in R), so a call dispatches to the wrong implementation.

accessor_producers maps a known sidecar-accessor name to a description of its producing derive; when a duplicate matches one we name the derive in the message, otherwise we report the generic “defined more than once” form.

Class and namespace bindings (new.env, R6::R6Class, S7::new_class) occupy the same namespace as functions. Include them so a free function cannot silently replace a class object (#1677). S4’s setClass() is not a binding assignment: its same-named constructor function remains valid. Guarded S3/S4/S7 generic declarations may repeat across classes, but must not share their binding with an ordinary function or class object (#1677). A class object defined twice would replace the first together with the methods bound to it. The impl blocks of one Rust type are merged into one class before this scan, and two types claiming one class name are refused there (merge_class_blocks, #1776), so this is the last line of defence.

Every message names both producers, read from the # Generated from Rust … header that opens each wrapper fragment.

Returns Err(message) naming the offending binding on the first collision found; Ok(()) when names are unique apart from shared generic declarations.