Skip to content

Commit

Permalink
Add IsBase macro for better error reporting
Browse files Browse the repository at this point in the history
  • Loading branch information
lilizoey committed Oct 18, 2024
1 parent 623aa41 commit 0700f9d
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 35 deletions.
15 changes: 15 additions & 0 deletions godot-core/src/obj/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,21 @@ impl GodotClass for NoBase {
const INIT_LEVEL: InitLevel = InitLevel::Core; // arbitrary; never read.
}

#[diagnostic::on_unimplemented(
message = "expected base `{Self}` found `{A}`",
label = "expected base `{Self}` found `{A}`"
)]
#[doc(hidden)]
pub trait IsBase<T: GodotClass, A> {
#[doc(hidden)]
fn conv(b: Base<T>) -> A;
}
impl<T: GodotClass> IsBase<T, Base<T>> for Base<T> {
fn conv(b: Base<T>) -> Base<T> {
b
}
}

unsafe impl Bounds for NoBase {
type Memory = bounds::MemManual;
type DynMemory = bounds::MemManual;
Expand Down
3 changes: 1 addition & 2 deletions godot-macros/src/class/data_models/field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
use crate::class::{FieldExport, FieldVar};
use proc_macro2::{Ident, Span, TokenStream};
use quote::ToTokens;
use venial::Error;

pub struct Field {
pub name: Ident,
Expand Down Expand Up @@ -54,7 +53,7 @@ pub struct Fields {
pub deprecations: Vec<TokenStream>,

/// Errors during macro evaluation that shouldn't abort the execution of the macro.
pub errors: Vec<Error>,
pub errors: Vec<venial::Error>,
}

#[derive(Clone)]
Expand Down
53 changes: 20 additions & 33 deletions godot-macros/src/class/derive_godot_class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,33 +63,21 @@ pub fn derive_godot_class(item: venial::Item) -> ParseResult<TokenStream> {
let prv = quote! { ::godot::private };
let godot_exports_impl = make_property_impl(class_name, &fields);

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

fn base_field(&self) -> &::godot::obj::Base<<Self as ::godot::obj::GodotClass>::Base> {
fn base_field(&self) -> &::godot::obj::Base<<#class_name as ::godot::obj::GodotClass>::Base> {
&self.#name
}
}
} else {
quote! {
fn to_gd(&self) -> ::godot::obj::Gd<Self> {
todo!()
}

fn base_field(&self) -> &::godot::obj::Base<<Self as ::godot::obj::GodotClass>::Base> {
todo!()
}
}
};

quote! {
impl ::godot::obj::WithBaseField for #class_name {
#implementation
}
}
} else {
TokenStream::new()
Expand Down Expand Up @@ -241,14 +229,15 @@ impl ClassAttributes {
}

fn make_godot_init_impl(class_name: &Ident, fields: &Fields) -> TokenStream {
let base_init = if let Some(Field { name, .. }) = &fields.base_field {
let base = if fields.well_formed_base {
quote! { base }
} else {
quote! { ::std::todo!("The base field is currently broken") }
let base_init = if let Some(Field { name, ty, .. }) = &fields.base_field {
let base_type =
quote_spanned! { ty.span()=> <#class_name as ::godot::obj::GodotClass>::Base };
let base_field_type = quote_spanned! { ty.span()=> ::godot::obj::Base<#base_type> };
let base = quote_spanned! { ty.span()=>
<#base_field_type as ::godot::obj::IsBase<#base_type, #ty>>::conv(base)
};

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

quote! {
impl ::godot::obj::cap::GodotDefault for #class_name {
fn __godot_user_init(base: ::godot::obj::Base<Self::Base>) -> Self {
// If the base field is broken then we may get unreachable code due to the `todo`.
#[allow(unreachable_code)]
fn __godot_user_init(base: ::godot::obj::Base<<#class_name as ::godot::obj::GodotClass>::Base>) -> Self {
Self {
#( #rest_init )*
#base_init
Expand Down Expand Up @@ -589,15 +576,15 @@ fn parse_fields(
}

if let Some(export) = field.export.as_ref() {
well_formed_base = false;
// well_formed_base = false;
errors.push(error!(
export.span,
"base field cannot have the attribute #[export]"
));
}

if let Some(default_val) = field.default_val.as_ref() {
well_formed_base = false;
// well_formed_base = false;
errors.push(error!(
default_val.span,
"base field cannot have the attribute #[init]"
Expand Down

0 comments on commit 0700f9d

Please sign in to comment.