From 5024139e01f1a315198924d08da81fa512e3ce0d Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 7 Jun 2024 22:55:10 +0200 Subject: [PATCH] Detect components called as functions (#2461) * detect components called as functions * use the existing component name information instead of a thread local --- packages/core-macro/src/component.rs | 4 ++++ packages/core/src/lib.rs | 6 ++++++ packages/core/src/properties.rs | 28 ++++++++++++++++++++++++++++ 3 files changed, 38 insertions(+) diff --git a/packages/core-macro/src/component.rs b/packages/core-macro/src/component.rs index ed88c25e0..d239d00bc 100644 --- a/packages/core-macro/src/component.rs +++ b/packages/core-macro/src/component.rs @@ -84,6 +84,7 @@ impl ComponentBody { } = sig; let Generics { where_clause, .. } = generics; let (_, ty_generics, _) = generics.split_for_impl(); + let generics_turbofish = ty_generics.as_turbofish(); // We generate a struct with the same name as the component but called `Props` let struct_ident = Ident::new(&format!("{fn_ident}Props"), fn_ident.span()); @@ -108,6 +109,9 @@ impl ComponentBody { #(#attrs)* #(#props_docs)* #asyncness #vis fn #fn_ident #generics (#props_ident) #fn_output #where_clause { + // In debug mode we can detect if the user is calling the component like a function + dioxus_core::internal::verify_component_called_as_component(#fn_ident #generics_turbofish); + #expanded_struct #block } diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 7739d6894..422c2026d 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -24,6 +24,12 @@ mod scopes; mod tasks; mod virtual_dom; +/// Items exported from this module are used in macros and should not be used directly. +#[doc(hidden)] +pub mod internal { + pub use crate::properties::verify_component_called_as_component; +} + pub(crate) mod innerlude { pub(crate) use crate::any_props::*; pub use crate::arena::*; diff --git a/packages/core/src/properties.rs b/packages/core/src/properties.rs index ff2ce15c1..be979f58d 100644 --- a/packages/core/src/properties.rs +++ b/packages/core/src/properties.rs @@ -117,6 +117,34 @@ where P::builder() } +/// Make sure that this component is currently running as a component, not a function call +#[doc(hidden)] +#[allow(unused)] +pub fn verify_component_called_as_component, P, M>(component: C) { + #[cfg(debug_assertions)] + { + // We trim WithOwner from the end of the type name for component with a builder that include a special owner which may not match the function name directly + let mut type_name = std::any::type_name::(); + if let Some((_, after_colons)) = type_name.rsplit_once("::") { + type_name = after_colons; + } + let component_name = Runtime::with(|rt| { + current_scope_id() + .and_then(|id| rt.get_state(id)) + .map(|scope| scope.name) + }) + .flatten(); + + // If we are in a component, and the type name is the same as the active component name, then we can just return + if component_name == Some(type_name) { + return; + } + + // Otherwise the component was called like a function, so we should log an error + tracing::error!("It looks like you called the component {type_name} like a function instead of a component. Components should be called with braces like `{type_name} {{ prop: value }}` instead of as a function"); + } +} + /// Any component that implements the `ComponentFn` trait can be used as a component. /// /// This trait is automatically implemented for functions that are in one of the following forms: