Skip to content

Commit 3a1c918

Browse files
committed
Auto-document interface trait changes (virtual method presence)
1 parent 5245044 commit 3a1c918

File tree

1 file changed

+55
-9
lines changed

1 file changed

+55
-9
lines changed

godot-codegen/src/generator/virtual_traits.rs

Lines changed: 55 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use crate::special_cases;
1414
use crate::util::ident;
1515
use proc_macro2::{Ident, TokenStream};
1616
use quote::quote;
17+
use std::fmt::Write;
1718

1819
pub fn make_virtual_methods_trait(
1920
class: &Class,
@@ -26,19 +27,20 @@ pub fn make_virtual_methods_trait(
2627
let trait_name = ident(trait_name_str);
2728
let class_name = &class.name().rust_ty;
2829

29-
let virtual_method_fns = make_all_virtual_methods(class, all_base_names, view);
30+
let (virtual_method_fns, extra_docs) = make_all_virtual_methods(class, all_base_names, view);
3031
let special_virtual_methods = make_special_virtual_methods(notification_enum_name);
3132

3233
let trait_doc = docs::make_virtual_trait_doc(trait_name_str, class.name());
3334

3435
quote! {
3536
#[doc = #trait_doc]
37+
#[doc = #extra_docs]
3638
#[allow(unused_variables)]
3739
#[allow(clippy::unimplemented)]
3840
#cfg_attributes
3941
pub trait #trait_name: crate::obj::GodotClass<Base = #class_name> + crate::private::You_forgot_the_attribute__godot_api {
4042
#special_virtual_methods
41-
#( #virtual_method_fns )*
43+
#virtual_method_fns
4244
}
4345
}
4446
}
@@ -193,16 +195,18 @@ fn make_all_virtual_methods(
193195
class: &Class,
194196
all_base_names: &[TyName],
195197
view: &ApiView,
196-
) -> Vec<TokenStream> {
197-
let mut all_tokens = vec![];
198+
) -> (TokenStream, String) {
199+
let mut all_tokens = TokenStream::new();
198200

199201
for method in class.methods.iter() {
200202
// Assumes that inner function filters on is_virtual.
201203
if let Some(tokens) = make_virtual_method(method, VirtualMethodPresence::Inherit) {
202-
all_tokens.push(tokens);
204+
all_tokens.extend(tokens);
203205
}
204206
}
205207

208+
let mut changes_from_base = String::new();
209+
206210
for base_name in all_base_names {
207211
let base_class = view.get_engine_class(base_name);
208212
for method in base_class.methods.iter() {
@@ -211,14 +215,56 @@ fn make_all_virtual_methods(
211215
let derived_presence =
212216
special_cases::get_derived_virtual_method_presence(class.name(), method.name());
213217

218+
// Collect all changes in a Markdown table.
219+
let new = match derived_presence {
220+
VirtualMethodPresence::Inherit => None,
221+
VirtualMethodPresence::Override { is_required } => {
222+
Some(format_required(is_required))
223+
}
224+
VirtualMethodPresence::Remove => Some("removed"),
225+
};
226+
if let Some(new) = new {
227+
let orig = format_required(method.is_virtual_required());
228+
let base_name = &base_name.rust_ty;
229+
let method_name = format_method_name(method);
230+
231+
write!(
232+
changes_from_base,
233+
"\n| [`I{base_name}::{method_name}`](crate::classes::I{base_name}::{method_name}) | {orig} | {new} |"
234+
)
235+
.unwrap();
236+
}
237+
214238
if let Some(tokens) = make_virtual_method(method, derived_presence) {
215-
all_tokens.push(tokens);
239+
all_tokens.extend(tokens);
216240
}
217241
}
218242
}
219243

220-
all_tokens
244+
let extra_docs = if changes_from_base.is_empty() {
245+
String::new()
246+
} else {
247+
let class_name = &class.name().rust_ty;
248+
format!(
249+
"\n\n# Changes from base interface traits\n\
250+
The following virtual methods originally declared in direct/indirect base classes have their presence (required/optional) changed \
251+
in `I{class_name}`. This can happen if Godot already overrides virtual methods, discouraging the user from further overriding them.\n\
252+
\n\n| Base method | Original | New |\n| --- | --- | --- |{changes_from_base}"
253+
)
254+
};
255+
256+
(all_tokens, extra_docs)
221257
}
222258

223-
// ----------------------------------------------------------------------------------------------------------------------------------------------
224-
// Helper types
259+
fn format_required(is_required: bool) -> &'static str {
260+
if is_required {
261+
"required"
262+
} else {
263+
"optional"
264+
}
265+
}
266+
267+
fn format_method_name(method: &ClassMethod) -> &str {
268+
// TODO when we have `_unsafe` or similar postfix with raw pointers, update this here.
269+
method.name()
270+
}

0 commit comments

Comments
 (0)