let maybe_fn = self.inner.dollar("{{ method_name }}");
let fn_robj = match maybe_fn {
    Ok(v) if !v.is_null() && !v.is_na() => v,
{% if has_error %}
    _ => return Err({{ missing_method_error }}),
{% else %}
    _ => return Default::default(),
{% endif %}
};

{% if empty_args %}
let result = fn_robj.call(extendr_api::Pairlist::new());
{% else %}
let args = extendr_api::Pairlist::from_pairs(&[{{ args_pairs }}]);
let result = fn_robj.call(args);
{% endif %}

{% if is_primitive_return %}
// Primitive return: extract directly from Robj without JSON deserialization
match result {
    Err(_) => {
{% if has_error %}
        Err({{ failed_method_error }})
{% else %}
        Default::default()
{% endif %}
    }
    Ok(val) => {
{% if return_type == "bool" %}
        let int_val = val.as_integer()
            .map(|i| i != 0)
            .or_else(|| val.as_real().map(|r| r != 0.0))
            .unwrap_or(false);
{% else %}
        let int_val = val.as_integer()
            .map(|i| i as {{ return_type }})
            .or_else(|| val.as_real().map(|r| r as {{ return_type }}))
            .unwrap_or_default();
{% endif %}
{% if has_error %}
        Ok(int_val)
{% else %}
        int_val
{% endif %}
    }
}
{% else %}
// R callbacks return JSON-encoded strings (e.g. via `jsonlite::toJSON`). Parse the string
// payload back into the trait return type. Note: `extendr_api::Robj` does not implement
// `serde::Serialize`, so we cannot serialize it directly — the caller must serialize on
// the R side.
match result {
    Err(_) => {
{% if has_error %}
        Err({{ failed_method_error }})
{% else %}
        Default::default()
{% endif %}
    }
    Ok(val) => {
        let payload = val.as_str().unwrap_or("");
        match serde_json::from_str::<{{ return_type }}>(payload) {
{% if has_error %}
            Ok(parsed) => Ok(parsed),
            Err(_) => Err({{ deserialization_error }}),
{% else %}
            Ok(parsed) => parsed,
            Err(_) => Default::default(),
{% endif %}
        }
    }
}
{% endif %}
