Skip to content

Commit f971050

Browse files
committed
Add IsBase macro for better error reporting
1 parent 623aa41 commit f971050

File tree

4 files changed

+35
-47
lines changed

4 files changed

+35
-47
lines changed

godot-core/src/obj/traits.rs

+15
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,21 @@ impl GodotClass for NoBase {
7676
const INIT_LEVEL: InitLevel = InitLevel::Core; // arbitrary; never read.
7777
}
7878

79+
#[diagnostic::on_unimplemented(
80+
message = "expected base `{Self}` found `{A}`",
81+
label = "expected base `{Self}` found `{A}`"
82+
)]
83+
#[doc(hidden)]
84+
pub trait IsBase<T: GodotClass, A> {
85+
#[doc(hidden)]
86+
fn conv(b: Base<T>) -> A;
87+
}
88+
impl<T: GodotClass> IsBase<T, Base<T>> for Base<T> {
89+
fn conv(b: Base<T>) -> Base<T> {
90+
b
91+
}
92+
}
93+
7994
unsafe impl Bounds for NoBase {
8095
type Memory = bounds::MemManual;
8196
type DynMemory = bounds::MemManual;

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

+1-7
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
use crate::class::{FieldExport, FieldVar};
99
use proc_macro2::{Ident, Span, TokenStream};
1010
use quote::ToTokens;
11-
use venial::Error;
1211

1312
pub struct Field {
1413
pub name: Ident,
@@ -45,16 +44,11 @@ pub struct Fields {
4544
/// The field with type `Base<T>`, if available.
4645
pub base_field: Option<Field>,
4746

48-
/// The base field is either absent or is correctly formatted.
49-
///
50-
/// When this is false, there will always be a compile error ensuring the program fails to compile.
51-
pub well_formed_base: bool,
52-
5347
/// Deprecation warnings.
5448
pub deprecations: Vec<TokenStream>,
5549

5650
/// Errors during macro evaluation that shouldn't abort the execution of the macro.
57-
pub errors: Vec<Error>,
51+
pub errors: Vec<venial::Error>,
5852
}
5953

6054
#[derive(Clone)]

godot-macros/src/class/derive_godot_class.rs

+18-39
Original file line numberDiff line numberDiff line change
@@ -63,33 +63,21 @@ pub fn derive_godot_class(item: venial::Item) -> ParseResult<TokenStream> {
6363
let prv = quote! { ::godot::private };
6464
let godot_exports_impl = make_property_impl(class_name, &fields);
6565

66-
let godot_withbase_impl = if let Some(Field { name, .. }) = &fields.base_field {
67-
let implementation = if fields.well_formed_base {
68-
quote! {
69-
fn to_gd(&self) -> ::godot::obj::Gd<Self> {
70-
self.#name.to_gd().cast()
66+
let godot_withbase_impl = if let Some(Field { name, ty, .. }) = &fields.base_field {
67+
// Apply the span of the field's type so that errors show up on the field's type.
68+
quote_spanned! { ty.span()=>
69+
impl ::godot::obj::WithBaseField for #class_name {
70+
fn to_gd(&self) -> ::godot::obj::Gd<#class_name> {
71+
// By not referencing the base field directly here we ensure that the user only gets one error when the base
72+
// field's type is wrong.
73+
let base = <#class_name as ::godot::obj::WithBaseField>::base_field(self);
74+
base.to_gd().cast()
7175
}
7276

73-
fn base_field(&self) -> &::godot::obj::Base<<Self as ::godot::obj::GodotClass>::Base> {
77+
fn base_field(&self) -> &::godot::obj::Base<<#class_name as ::godot::obj::GodotClass>::Base> {
7478
&self.#name
7579
}
7680
}
77-
} else {
78-
quote! {
79-
fn to_gd(&self) -> ::godot::obj::Gd<Self> {
80-
todo!()
81-
}
82-
83-
fn base_field(&self) -> &::godot::obj::Base<<Self as ::godot::obj::GodotClass>::Base> {
84-
todo!()
85-
}
86-
}
87-
};
88-
89-
quote! {
90-
impl ::godot::obj::WithBaseField for #class_name {
91-
#implementation
92-
}
9381
}
9482
} else {
9583
TokenStream::new()
@@ -241,14 +229,15 @@ impl ClassAttributes {
241229
}
242230

243231
fn make_godot_init_impl(class_name: &Ident, fields: &Fields) -> TokenStream {
244-
let base_init = if let Some(Field { name, .. }) = &fields.base_field {
245-
let base = if fields.well_formed_base {
246-
quote! { base }
247-
} else {
248-
quote! { ::std::todo!("The base field is currently broken") }
232+
let base_init = if let Some(Field { name, ty, .. }) = &fields.base_field {
233+
let base_type =
234+
quote_spanned! { ty.span()=> <#class_name as ::godot::obj::GodotClass>::Base };
235+
let base_field_type = quote_spanned! { ty.span()=> ::godot::obj::Base<#base_type> };
236+
let base = quote_spanned! { ty.span()=>
237+
<#base_field_type as ::godot::obj::IsBase<#base_type, #ty>>::conv(base)
249238
};
250239

251-
quote! { #name: #base, }
240+
quote_spanned! { ty.span()=> #name: #base, }
252241
} else {
253242
TokenStream::new()
254243
};
@@ -267,9 +256,7 @@ fn make_godot_init_impl(class_name: &Ident, fields: &Fields) -> TokenStream {
267256

268257
quote! {
269258
impl ::godot::obj::cap::GodotDefault for #class_name {
270-
fn __godot_user_init(base: ::godot::obj::Base<Self::Base>) -> Self {
271-
// If the base field is broken then we may get unreachable code due to the `todo`.
272-
#[allow(unreachable_code)]
259+
fn __godot_user_init(base: ::godot::obj::Base<<#class_name as ::godot::obj::GodotClass>::Base>) -> Self {
273260
Self {
274261
#( #rest_init )*
275262
#base_init
@@ -451,8 +438,6 @@ fn parse_fields(
451438
let mut base_field = Option::<Field>::None;
452439
let mut deprecations = vec![];
453440
let mut errors = vec![];
454-
// Base field is either absent or exists and has no errors.
455-
let mut well_formed_base = true;
456441

457442
// Attributes on struct fields
458443
for (named_field, _punct) in named_fields {
@@ -573,39 +558,34 @@ fn parse_fields(
573558
// Extra validation; eventually assign to base_fields or all_fields.
574559
if is_base {
575560
if field.is_onready {
576-
well_formed_base = false;
577561
errors.push(error!(
578562
field.ty.clone(),
579563
"base field cannot have type `OnReady<T>`"
580564
));
581565
}
582566

583567
if let Some(var) = field.var.as_ref() {
584-
well_formed_base = false;
585568
errors.push(error!(
586569
var.span,
587570
"base field cannot have the attribute #[var]"
588571
));
589572
}
590573

591574
if let Some(export) = field.export.as_ref() {
592-
well_formed_base = false;
593575
errors.push(error!(
594576
export.span,
595577
"base field cannot have the attribute #[export]"
596578
));
597579
}
598580

599581
if let Some(default_val) = field.default_val.as_ref() {
600-
well_formed_base = false;
601582
errors.push(error!(
602583
default_val.span,
603584
"base field cannot have the attribute #[init]"
604585
));
605586
}
606587

607588
if let Some(prev_base) = base_field.replace(field) {
608-
well_formed_base = false;
609589
// Ensure at most one Base<T>.
610590
errors.push(error!(
611591
// base_field.unwrap().name,
@@ -621,7 +601,6 @@ fn parse_fields(
621601
Ok(Fields {
622602
all_fields,
623603
base_field,
624-
well_formed_base,
625604
deprecations,
626605
errors,
627606
})

godot-macros/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ mod itest;
2020
mod util;
2121

2222
use proc_macro::TokenStream;
23-
use proc_macro2::TokenStream as TokenStream2;
23+
use proc_macro2::{Span, TokenStream as TokenStream2};
2424
use quote::quote;
2525

2626
use crate::util::{bail, ident, KvParser};

0 commit comments

Comments
 (0)