Skip to content

Commit f25e70f

Browse files
authored
Merge pull request #1075 from godot-rust/bugfix/signal-visibility
Explicit signal visibility
2 parents de44884 + 084f764 commit f25e70f

File tree

7 files changed

+99
-72
lines changed

7 files changed

+99
-72
lines changed

godot-macros/src/class/data_models/func.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ pub struct SignatureInfo {
188188
pub receiver_type: ReceiverType,
189189
pub param_idents: Vec<Ident>,
190190
pub param_types: Vec<venial::TypeExpr>,
191-
pub ret_type: TokenStream,
191+
pub return_type: TokenStream,
192192
}
193193

194194
impl SignatureInfo {
@@ -198,7 +198,7 @@ impl SignatureInfo {
198198
receiver_type: ReceiverType::Mut,
199199
param_idents: vec![],
200200
param_types: vec![],
201-
ret_type: quote! { () },
201+
return_type: quote! { () },
202202
}
203203
}
204204

@@ -207,7 +207,7 @@ impl SignatureInfo {
207207

208208
pub fn tuple_type(&self) -> TokenStream {
209209
// Note: for GdSelf receivers, first parameter is not even part of SignatureInfo anymore.
210-
util::make_signature_tuple_type(&self.ret_type, &self.param_types)
210+
util::make_signature_tuple_type(&self.return_type, &self.param_types)
211211
}
212212
}
213213

@@ -392,7 +392,7 @@ pub(crate) fn into_signature_info(
392392
receiver_type,
393393
param_idents,
394394
param_types,
395-
ret_type,
395+
return_type: ret_type,
396396
}
397397
}
398398

godot-macros/src/class/data_models/inherent_impl.rs

+4-8
Original file line numberDiff line numberDiff line change
@@ -319,18 +319,14 @@ fn process_godot_fns(
319319
"#[signal] must not have a body; declare the function with a semicolon"
320320
);
321321
}
322-
if function.vis_marker.is_some() {
323-
return bail!(
324-
&function.vis_marker,
325-
"#[signal] must not have a visibility specifier; signals are always public"
326-
);
327-
}
328322

329323
let external_attributes = function.attributes.clone();
330-
let sig = util::reduce_to_signature(function);
324+
325+
let mut fn_signature = util::reduce_to_signature(function);
326+
fn_signature.vis_marker = function.vis_marker.clone();
331327

332328
signal_definitions.push(SignalDefinition {
333-
signature: sig,
329+
fn_signature,
334330
external_attributes,
335331
has_builder: !signal.no_builder,
336332
});

godot-macros/src/class/data_models/signal.rs

+27-17
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ use quote::{format_ident, quote};
1212

1313
/// Holds information known from a signal's definition
1414
pub struct SignalDefinition {
15-
/// The signal's function signature.
16-
pub signature: venial::Function,
15+
/// The signal's function signature (simplified, not original declaration).
16+
pub fn_signature: venial::Function,
1717

1818
/// The signal's non-gdext attributes (all except #[signal]).
1919
pub external_attributes: Vec<venial::Attribute>,
@@ -24,8 +24,8 @@ pub struct SignalDefinition {
2424

2525
/// Extracted syntax info for a declared signal.
2626
struct SignalDetails<'a> {
27-
/// `fn my_signal(i: i32, s: GString)`
28-
original_decl: &'a venial::Function,
27+
/// `fn my_signal(i: i32, s: GString)` -- simplified from original declaration.
28+
fn_signature: &'a venial::Function,
2929
/// `MyClass`
3030
class_name: &'a Ident,
3131
/// `i32`, `GString`
@@ -44,19 +44,21 @@ struct SignalDetails<'a> {
4444
signal_cfg_attrs: Vec<&'a venial::Attribute>,
4545
/// `MyClass_MySignal`
4646
individual_struct_name: Ident,
47+
/// Visibility, e.g. `pub(crate)`
48+
vis_marker: Option<venial::VisMarker>,
4749
}
4850

4951
impl<'a> SignalDetails<'a> {
5052
pub fn extract(
51-
original_decl: &'a venial::Function,
53+
fn_signature: &'a venial::Function, // *Not* the original #[signal], just the signature part (no attributes, body, etc).
5254
class_name: &'a Ident,
5355
external_attributes: &'a [venial::Attribute],
5456
) -> ParseResult<SignalDetails<'a>> {
5557
let mut param_types = vec![];
5658
let mut param_names = vec![];
5759
let mut param_names_str = vec![];
5860

59-
for (param, _punct) in original_decl.params.inner.iter() {
61+
for (param, _punct) in fn_signature.params.inner.iter() {
6062
match param {
6163
venial::FnParam::Typed(param) => {
6264
param_types.push(param.ty.clone());
@@ -76,20 +78,21 @@ impl<'a> SignalDetails<'a> {
7678
.collect();
7779

7880
let param_tuple = quote! { ( #( #param_types, )* ) };
79-
let signal_name = &original_decl.name;
81+
let signal_name = &fn_signature.name;
8082
let individual_struct_name = format_ident!("__godot_Signal_{}_{}", class_name, signal_name);
8183

8284
Ok(Self {
83-
original_decl,
85+
fn_signature,
8486
class_name,
8587
param_types,
8688
param_names,
8789
param_names_str,
8890
param_tuple,
8991
signal_name,
90-
signal_name_str: original_decl.name.to_string(),
92+
signal_name_str: fn_signature.name.to_string(),
9193
signal_cfg_attrs,
9294
individual_struct_name,
95+
vis_marker: fn_signature.vis_marker.clone(),
9396
})
9497
}
9598
}
@@ -104,12 +107,12 @@ pub fn make_signal_registrations(
104107

105108
for signal in signals {
106109
let SignalDefinition {
107-
signature,
110+
fn_signature,
108111
external_attributes,
109112
has_builder,
110113
} = signal;
111114

112-
let details = SignalDetails::extract(signature, class_name, external_attributes)?;
115+
let details = SignalDetails::extract(fn_signature, class_name, external_attributes)?;
113116

114117
// Callable custom functions are only supported in 4.2+, upon which custom signals rely.
115118
#[cfg(since_api = "4.2")]
@@ -196,13 +199,19 @@ impl SignalCollection {
196199
signal_name_str,
197200
signal_cfg_attrs,
198201
individual_struct_name,
202+
vis_marker,
199203
..
200204
} = details;
201205

202206
self.collection_methods.push(quote! {
203207
// Deliberately not #[doc(hidden)] for IDE completion.
204208
#(#signal_cfg_attrs)*
205-
fn #signal_name(self) -> #individual_struct_name<'a> {
209+
// Note: this could be `pub` always and would still compile (maybe warning with the following message).
210+
// associated function `SignalCollection::my_signal` is reachable at visibility `pub(crate)`
211+
//
212+
// However, it would still lead to a compile error when declaring the individual signal struct `pub` (or any other
213+
// visibility that exceeds the class visibility). So, we can as well declare the visibility here.
214+
#vis_marker fn #signal_name(self) -> #individual_struct_name<'a> {
206215
#individual_struct_name {
207216
typed: ::godot::register::TypedSignal::new(self.__internal_obj, #signal_name_str)
208217
}
@@ -219,14 +228,15 @@ impl SignalCollection {
219228
}
220229

221230
fn make_signal_individual_struct(details: &SignalDetails) -> TokenStream {
222-
let emit_params = &details.original_decl.params;
231+
let emit_params = &details.fn_signature.params;
223232

224233
let SignalDetails {
225234
class_name,
226235
param_names,
227236
param_tuple,
228237
signal_cfg_attrs,
229238
individual_struct_name,
239+
vis_marker,
230240
..
231241
} = details;
232242

@@ -235,9 +245,9 @@ fn make_signal_individual_struct(details: &SignalDetails) -> TokenStream {
235245
// #(#signal_cfg_attrs)* pub mod #module_name { use super::*; ... }
236246
// #(#signal_cfg_attrs)* pub(crate) use #module_name::#individual_struct_name;
237247
// However, there are some challenges:
238-
// - Visibility becomes a pain to handle (rustc doesn't like re-exporting private symbols as pub, and we can't know the visibility of the
239-
// surrounding class struct). Having signals always-public is much less of a headache, requires less choice on the user side
240-
// (pub/pub(crate)/nothing on #[signal]), and likely good enough for the moment.
248+
// - Visibility is a pain to handle: rustc doesn't like re-exporting private symbols as pub, and we can't know the visibility of the
249+
// surrounding class struct. Users must explicitly declare #[signal]s `pub` if they want wider visibility; this must not exceed the
250+
// visibility of the class itself.
241251
// - Not yet clear if we should have each signal + related types in separate module. If #[signal] is supported in #[godot_api(secondary)]
242252
// impl blocks, then we would have to group them by the impl block. Rust doesn't allow partial modules, so they'd need to have individual
243253
// names as well, possibly explicitly chosen by the user.
@@ -248,7 +258,7 @@ fn make_signal_individual_struct(details: &SignalDetails) -> TokenStream {
248258
#(#signal_cfg_attrs)*
249259
#[allow(non_camel_case_types)]
250260
#[doc(hidden)] // Signal struct is hidden, but the method returning it is not (IDE completion).
251-
struct #individual_struct_name<'a> {
261+
#vis_marker struct #individual_struct_name<'a> {
252262
#[doc(hidden)]
253263
typed: ::godot::register::TypedSignal<'a, #class_name, #param_tuple>,
254264
}

godot-macros/src/docs.rs

+16-6
Original file line numberDiff line numberDiff line change
@@ -154,11 +154,17 @@ fn make_docs_from_attributes(doc: &[Attribute]) -> Option<String> {
154154
}
155155

156156
fn make_signal_docs(signal: &SignalDefinition) -> Option<String> {
157-
let name = &signal.signature.name;
158-
let params = params(signal.signature.params.iter().filter_map(|(x, _)| match x {
159-
FnParam::Receiver(_) => None,
160-
FnParam::Typed(y) => Some((&y.name, &y.ty)),
161-
}));
157+
let name = &signal.fn_signature.name;
158+
let params = params(
159+
signal
160+
.fn_signature
161+
.params
162+
.iter()
163+
.filter_map(|(x, _)| match x {
164+
FnParam::Receiver(_) => None,
165+
FnParam::Typed(y) => Some((&y.name, &y.ty)),
166+
}),
167+
);
162168
let desc = make_docs_from_attributes(&signal.external_attributes)?;
163169
Some(format!(
164170
r#"
@@ -251,7 +257,11 @@ pub fn make_method_docs(method: &FuncDefinition) -> Option<String> {
251257
.registered_name
252258
.clone()
253259
.unwrap_or_else(|| method.rust_ident().to_string());
254-
let ret = method.signature_info.ret_type.to_token_stream().to_string();
260+
let ret = method
261+
.signature_info
262+
.return_type
263+
.to_token_stream()
264+
.to_string();
255265
let params = params(
256266
method
257267
.signature_info

godot-macros/src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -774,6 +774,9 @@ pub fn derive_godot_class(input: TokenStream) -> TokenStream {
774774
/// method, you can access all declared signals in `self.signals().some_signal()` or `gd.signals().some_signal()`. The returned object is
775775
/// of type [`TypedSignal`], which provides further APIs for emitting and connecting, among others.
776776
///
777+
/// Visibility of signals **must not exceed class visibility**. If your class is private (as above) and you declare your signal as `pub fn`,
778+
/// you will get a compile error "can't leak private type".
779+
///
777780
/// A detailed explanation with examples is available in the [book chapter _Registering signals_](https://godot-rust.github.io/book/register/signals.html).
778781
///
779782
/// [`WithSignals`]: ../obj/trait.WithSignals.html

godot-macros/src/util/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ pub(crate) use require_api_version;
8383

8484
pub fn reduce_to_signature(function: &venial::Function) -> venial::Function {
8585
let mut reduced = function.clone();
86-
reduced.vis_marker = None; // TODO needed?
86+
reduced.vis_marker = None; // retained outside in the case of #[signal].
8787
reduced.attributes.clear();
8888
reduced.tk_semicolon = None;
8989
reduced.body = None;

itest/rust/src/builtin_tests/containers/signal_test.rs

+44-36
Original file line numberDiff line numberDiff line change
@@ -245,51 +245,59 @@ fn signal_construction_and_id() {
245245
/// Global sets the value of the received argument and whether it was a static function.
246246
static LAST_STATIC_FUNCTION_ARG: Global<i64> = Global::default();
247247

248-
#[derive(GodotClass)]
249-
#[class(init, base=Object)]
250-
struct Emitter {
251-
_base: Base<Object>,
252-
#[cfg(since_api = "4.2")]
253-
last_received_int: i64,
254-
}
248+
// Separate module to test signal visibility.
249+
use emitter::Emitter;
255250

256-
#[godot_api]
257-
impl Emitter {
258-
#[signal]
259-
fn signal_unit();
251+
mod emitter {
252+
use super::*;
253+
254+
#[derive(GodotClass)]
255+
#[class(init, base=Object)]
256+
pub struct Emitter {
257+
_base: Base<Object>,
258+
#[cfg(since_api = "4.2")]
259+
pub last_received_int: i64,
260+
}
260261

261-
#[signal]
262-
fn signal_int(arg1: i64);
262+
#[godot_api]
263+
impl Emitter {
264+
#[signal]
265+
fn signal_unit();
263266

264-
#[signal]
265-
fn signal_obj(arg1: Gd<Object>, arg2: GString);
267+
// Public to demonstrate usage inside module.
268+
#[signal]
269+
pub fn signal_int(arg1: i64);
266270

267-
#[func]
268-
fn self_receive(&mut self, arg1: i64) {
269-
#[cfg(since_api = "4.2")]
270-
{
271-
self.last_received_int = arg1;
271+
#[signal]
272+
fn signal_obj(arg1: Gd<Object>, arg2: GString);
273+
274+
#[func]
275+
pub fn self_receive(&mut self, arg1: i64) {
276+
#[cfg(since_api = "4.2")]
277+
{
278+
self.last_received_int = arg1;
279+
}
272280
}
273-
}
274281

275-
#[func]
276-
fn self_receive_static(arg1: i64) {
277-
*LAST_STATIC_FUNCTION_ARG.lock() = arg1;
278-
}
282+
#[func]
283+
fn self_receive_static(arg1: i64) {
284+
*LAST_STATIC_FUNCTION_ARG.lock() = arg1;
285+
}
279286

280-
// "Internal" means connect/emit happens from within the class (via &mut self).
287+
// "Internal" means connect/emit happens from within the class (via &mut self).
281288

282-
#[cfg(since_api = "4.2")]
283-
fn connect_signals_internal(&mut self, tracker: Rc<Cell<i64>>) {
284-
let mut sig = self.signals().signal_int();
285-
sig.connect_self(Self::self_receive);
286-
sig.connect(Self::self_receive_static);
287-
sig.connect(move |i| tracker.set(i));
288-
}
289+
#[cfg(since_api = "4.2")]
290+
pub fn connect_signals_internal(&mut self, tracker: Rc<Cell<i64>>) {
291+
let mut sig = self.signals().signal_int();
292+
sig.connect_self(Self::self_receive);
293+
sig.connect(Self::self_receive_static);
294+
sig.connect(move |i| tracker.set(i));
295+
}
289296

290-
#[cfg(since_api = "4.2")]
291-
fn emit_signals_internal(&mut self) {
292-
self.signals().signal_int().emit(1234);
297+
#[cfg(since_api = "4.2")]
298+
pub fn emit_signals_internal(&mut self) {
299+
self.signals().signal_int().emit(1234);
300+
}
293301
}
294302
}
295303

0 commit comments

Comments
 (0)