Better Function Component in docs + Macro-based Hooks (#2478)

* Fix const function component.

* Show Properties on the function definition.

* Filter certain doc attributes to be applied to type alias. More precise warnings.

* Implement macro-based hook.

* Add fail case.

* Function Component no longer a type alias.

* Clippy!

* Force 'static on generics.

* Fix clippy!

* Fix clippy and trybuild.

* Fix clippy and trybuild.

* Fix clippy.

* BaseComponent was not sealed properly.

* Adjust prelude.

* Public API should use IntoComponent for better ergonomics.

* Fix race condition.

* Fix trybuild.
This commit is contained in:
Kaede Hoshikawa 2022-03-08 22:03:40 +09:00 committed by GitHub
parent 6e425ff38b
commit 51238fb0e3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 500 additions and 274 deletions

View File

@ -1,10 +1,11 @@
use proc_macro2::{Span, TokenStream}; use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote, ToTokens}; use quote::{quote, ToTokens};
use syn::parse::{Parse, ParseStream}; use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated; use syn::punctuated::Punctuated;
use syn::token::{Comma, Fn}; use syn::token::{Comma, Fn};
use syn::{ use syn::{
visit_mut, Attribute, Block, FnArg, Generics, Ident, Item, ItemFn, ReturnType, Type, Visibility, parse_quote_spanned, visit_mut, Attribute, Block, FnArg, Generics, Ident, Item, ItemFn,
ReturnType, Type, Visibility,
}; };
use crate::hook::BodyRewriter; use crate::hook::BodyRewriter;
@ -20,6 +21,8 @@ pub struct FunctionComponent {
name: Ident, name: Ident,
return_type: Box<Type>, return_type: Box<Type>,
fn_token: Fn, fn_token: Fn,
component_name: Option<Ident>,
} }
impl Parse for FunctionComponent { impl Parse for FunctionComponent {
@ -144,10 +147,96 @@ impl Parse for FunctionComponent {
name: sig.ident, name: sig.ident,
return_type, return_type,
fn_token: sig.fn_token, fn_token: sig.fn_token,
component_name: None,
}) })
} }
} }
impl FunctionComponent {
/// Filters attributes that should be copied to component definition.
fn filter_attrs_for_component_struct(&self) -> Vec<Attribute> {
self.attrs
.iter()
.filter_map(|m| {
m.path
.get_ident()
.and_then(|ident| match ident.to_string().as_str() {
"doc" | "allow" => Some(m.clone()),
_ => None,
})
})
.collect()
}
/// Filters attributes that should be copied to the component impl block.
fn filter_attrs_for_component_impl(&self) -> Vec<Attribute> {
self.attrs
.iter()
.filter_map(|m| {
m.path
.get_ident()
.and_then(|ident| match ident.to_string().as_str() {
"allow" => Some(m.clone()),
_ => None,
})
})
.collect()
}
fn phantom_generics(&self) -> Punctuated<Ident, Comma> {
self.generics
.type_params()
.map(|ty_param| ty_param.ident.clone()) // create a new Punctuated sequence without any type bounds
.collect::<Punctuated<_, Comma>>()
}
fn merge_component_name(&mut self, name: FunctionComponentName) -> syn::Result<()> {
if let Some(ref m) = name.component_name {
if m == &self.name {
return Err(syn::Error::new_spanned(
m,
"the component must not have the same name as the function",
));
}
}
self.component_name = name.component_name;
Ok(())
}
fn inner_fn_ident(&self) -> Ident {
if self.component_name.is_some() {
self.name.clone()
} else {
Ident::new("inner", Span::mixed_site())
}
}
fn component_name(&self) -> Ident {
self.component_name
.clone()
.unwrap_or_else(|| self.name.clone())
}
// We need to cast 'static on all generics for into component.
fn create_into_component_generics(&self) -> Generics {
let mut generics = self.generics.clone();
let where_clause = generics.make_where_clause();
for ty_generic in self.generics.type_params() {
let ident = &ty_generic.ident;
let bound = parse_quote_spanned! { ident.span() =>
#ident: 'static
};
where_clause.predicates.push(bound);
}
generics
}
}
pub struct FunctionComponentName { pub struct FunctionComponentName {
component_name: Option<Ident>, component_name: Option<Ident>,
} }
@ -168,34 +257,30 @@ impl Parse for FunctionComponentName {
} }
} }
fn print_fn(func_comp: FunctionComponent, use_fn_name: bool) -> TokenStream { fn print_fn(func_comp: &FunctionComponent) -> TokenStream {
let name = func_comp.inner_fn_ident();
let FunctionComponent { let FunctionComponent {
fn_token, ref fn_token,
name, ref attrs,
attrs, ref block,
mut block, ref return_type,
return_type, ref generics,
generics, ref arg,
arg,
.. ..
} = func_comp; } = func_comp;
let mut block = *block.clone();
let (impl_generics, _ty_generics, where_clause) = generics.split_for_impl();
let (_impl_generics, ty_generics, where_clause) = generics.split_for_impl(); // We use _ctx here so if the component does not use any hooks, the usused_vars lint will not
// be triggered.
let ctx_ident = Ident::new("_ctx", Span::mixed_site());
let name = if use_fn_name { let mut body_rewriter = BodyRewriter::new(ctx_ident.clone());
name visit_mut::visit_block_mut(&mut body_rewriter, &mut block);
} else {
Ident::new("inner", Span::mixed_site())
};
let ctx_ident = Ident::new("ctx", Span::mixed_site());
let mut body_rewriter = BodyRewriter::default();
visit_mut::visit_block_mut(&mut body_rewriter, &mut *block);
quote! { quote! {
#(#attrs)* #(#attrs)*
#fn_token #name #ty_generics (#ctx_ident: &mut ::yew::functional::HookContext, #arg) -> #return_type #fn_token #name #impl_generics (#ctx_ident: &mut ::yew::functional::HookContext, #arg) -> #return_type
#where_clause #where_clause
{ {
#block #block
@ -205,74 +290,63 @@ fn print_fn(func_comp: FunctionComponent, use_fn_name: bool) -> TokenStream {
pub fn function_component_impl( pub fn function_component_impl(
name: FunctionComponentName, name: FunctionComponentName,
component: FunctionComponent, mut component: FunctionComponent,
) -> syn::Result<TokenStream> { ) -> syn::Result<TokenStream> {
let FunctionComponentName { component_name } = name; component.merge_component_name(name)?;
let has_separate_name = component_name.is_some(); let func = print_fn(&component);
let func = print_fn(component.clone(), has_separate_name); let into_comp_generics = component.create_into_component_generics();
let component_attrs = component.filter_attrs_for_component_struct();
let component_impl_attrs = component.filter_attrs_for_component_impl();
let phantom_generics = component.phantom_generics();
let component_name = component.component_name();
let fn_name = component.inner_fn_ident();
let FunctionComponent { let FunctionComponent {
props_type, props_type,
generics, generics,
vis, vis,
name: function_name,
.. ..
} = component; } = component;
let component_name = component_name.unwrap_or_else(|| function_name.clone());
let provider_name = format_ident!(
"{}FunctionProvider",
component_name,
span = Span::mixed_site()
);
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
if has_separate_name && function_name == component_name {
return Err(syn::Error::new_spanned(
component_name,
"the component must not have the same name as the function",
));
}
let phantom_generics = generics
.type_params()
.map(|ty_param| ty_param.ident.clone()) // create a new Punctuated sequence without any type bounds
.collect::<Punctuated<_, Comma>>();
let provider_props = Ident::new("props", Span::mixed_site());
let fn_generics = ty_generics.as_turbofish(); let fn_generics = ty_generics.as_turbofish();
let fn_name = if has_separate_name { let component_props = Ident::new("props", Span::mixed_site());
function_name
} else {
Ident::new("inner", Span::mixed_site())
};
let ctx_ident = Ident::new("ctx", Span::mixed_site()); let ctx_ident = Ident::new("ctx", Span::mixed_site());
let into_comp_impl = {
let (impl_generics, ty_generics, where_clause) = into_comp_generics.split_for_impl();
quote! {
impl #impl_generics ::yew::html::IntoComponent for #component_name #ty_generics #where_clause {
type Properties = #props_type;
type Component = ::yew::functional::FunctionComponent<Self>;
}
}
};
let quoted = quote! { let quoted = quote! {
#[doc(hidden)] #(#component_attrs)*
#[allow(non_camel_case_types)]
#[allow(unused_parens)] #[allow(unused_parens)]
#vis struct #provider_name #ty_generics { #vis struct #component_name #generics #where_clause {
_marker: ::std::marker::PhantomData<(#phantom_generics)>, _marker: ::std::marker::PhantomData<(#phantom_generics)>,
} }
#[automatically_derived] // we cannot disable any lints here because it will be applied to the function body
impl #impl_generics ::yew::functional::FunctionProvider for #provider_name #ty_generics #where_clause { // as well.
type TProps = #props_type; #(#component_impl_attrs)*
impl #impl_generics ::yew::functional::FunctionProvider for #component_name #ty_generics #where_clause {
type Properties = #props_type;
fn run(#ctx_ident: &mut ::yew::functional::HookContext, #provider_props: &Self::TProps) -> ::yew::html::HtmlResult { fn run(#ctx_ident: &mut ::yew::functional::HookContext, #component_props: &Self::Properties) -> ::yew::html::HtmlResult {
#func #func
::yew::html::IntoHtmlResult::into_html_result(#fn_name #fn_generics (#ctx_ident, #provider_props)) ::yew::html::IntoHtmlResult::into_html_result(#fn_name #fn_generics (#ctx_ident, #component_props))
} }
} }
#[allow(type_alias_bounds)] #into_comp_impl
#vis type #component_name #generics = ::yew::functional::FunctionComponent<#provider_name #ty_generics>;
}; };
Ok(quoted) Ok(quoted)

View File

@ -1,4 +1,3 @@
use proc_macro2::Span;
use proc_macro_error::emit_error; use proc_macro_error::emit_error;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use syn::spanned::Spanned; use syn::spanned::Spanned;
@ -8,12 +7,20 @@ use syn::{
ExprMatch, ExprWhile, Ident, Item, ExprMatch, ExprWhile, Ident, Item,
}; };
#[derive(Debug, Default)] #[derive(Debug)]
pub struct BodyRewriter { pub struct BodyRewriter {
branch_lock: Arc<Mutex<()>>, branch_lock: Arc<Mutex<()>>,
ctx_ident: Ident,
} }
impl BodyRewriter { impl BodyRewriter {
pub fn new(ctx_ident: Ident) -> Self {
Self {
branch_lock: Arc::default(),
ctx_ident,
}
}
fn is_branched(&self) -> bool { fn is_branched(&self) -> bool {
self.branch_lock.try_lock().is_err() self.branch_lock.try_lock().is_err()
} }
@ -30,7 +37,7 @@ impl BodyRewriter {
impl VisitMut for BodyRewriter { impl VisitMut for BodyRewriter {
fn visit_expr_call_mut(&mut self, i: &mut ExprCall) { fn visit_expr_call_mut(&mut self, i: &mut ExprCall) {
let ctx_ident = Ident::new("ctx", Span::mixed_site()); let ctx_ident = &self.ctx_ident;
// Only rewrite hook calls. // Only rewrite hook calls.
if let Expr::Path(ref m) = &*i.func { if let Expr::Path(ref m) = &*i.func {
@ -55,6 +62,32 @@ impl VisitMut for BodyRewriter {
visit_mut::visit_expr_call_mut(self, i); visit_mut::visit_expr_call_mut(self, i);
} }
fn visit_expr_mut(&mut self, i: &mut Expr) {
let ctx_ident = &self.ctx_ident;
match &mut *i {
Expr::Macro(m) => {
if let Some(ident) = m.mac.path.segments.last().as_ref().map(|m| &m.ident) {
if ident.to_string().starts_with("use_") {
if self.is_branched() {
emit_error!(
ident,
"hooks cannot be called at this position.";
help = "move hooks to the top-level of your function.";
note = "see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks"
);
} else {
*i = parse_quote_spanned! { i.span() => ::yew::functional::Hook::run(#i, #ctx_ident) };
}
} else {
visit_mut::visit_expr_macro_mut(self, m);
}
}
}
_ => visit_mut::visit_expr_mut(self, i),
}
}
fn visit_expr_closure_mut(&mut self, i: &mut ExprClosure) { fn visit_expr_closure_mut(&mut self, i: &mut ExprClosure) {
self.with_branch(move |m| visit_mut::visit_expr_closure_mut(m, i)) self.with_branch(move |m| visit_mut::visit_expr_closure_mut(m, i))
} }

View File

@ -1,9 +1,10 @@
use proc_macro2::{Span, TokenStream}; use proc_macro2::{Span, TokenStream};
use proc_macro_error::emit_error; use proc_macro_error::emit_error;
use quote::{quote, ToTokens}; use quote::quote;
use syn::parse::{Parse, ParseStream}; use syn::parse::{Parse, ParseStream};
use syn::visit_mut; use syn::{
use syn::{parse_file, GenericParam, Ident, ItemFn, LitStr, ReturnType, Signature}; parse_file, parse_quote, visit_mut, Attribute, Ident, ItemFn, LitStr, ReturnType, Signature,
};
mod body; mod body;
mod lifetime; mod lifetime;
@ -47,27 +48,22 @@ impl Parse for HookFn {
} }
} }
pub fn hook_impl(component: HookFn) -> syn::Result<TokenStream> { impl HookFn {
let HookFn { inner: original_fn } = component; fn doc_attr(&self) -> Attribute {
let vis = &self.inner.vis;
let sig = &self.inner.sig;
let ItemFn { let sig_s = quote! { #vis #sig {
vis, __yew_macro_dummy_function_body__
sig, } }
mut block, .to_string();
attrs,
} = original_fn.clone();
let sig_s = quote! { #vis #sig { let sig_file = parse_file(&sig_s).unwrap();
__yew_macro_dummy_function_body__ let sig_formatted = prettyplease::unparse(&sig_file);
} }
.to_string();
let sig_file = parse_file(&sig_s).unwrap(); let literal = LitStr::new(
let sig_formatted = prettyplease::unparse(&sig_file); &format!(
r#"
let doc_text = LitStr::new(
&format!(
r#"
# Note # Note
When used in function components and hooks, this hook is equivalent to: When used in function components and hooks, this hook is equivalent to:
@ -76,15 +72,32 @@ When used in function components and hooks, this hook is equivalent to:
{} {}
``` ```
"#, "#,
sig_formatted.replace( sig_formatted.replace(
"__yew_macro_dummy_function_body__", "__yew_macro_dummy_function_body__",
"/* implementation omitted */" "/* implementation omitted */"
) )
), ),
Span::mixed_site(), Span::mixed_site(),
); );
let hook_sig = HookSignature::rewrite(&sig); parse_quote!(#[doc = #literal])
}
}
pub fn hook_impl(hook: HookFn) -> syn::Result<TokenStream> {
let doc_attr = hook.doc_attr();
let HookFn { inner: original_fn } = hook;
let ItemFn {
ref vis,
ref sig,
ref block,
ref attrs,
} = original_fn;
let mut block = *block.clone();
let hook_sig = HookSignature::rewrite(sig);
let Signature { let Signature {
ref fn_token, ref fn_token,
@ -98,24 +111,13 @@ When used in function components and hooks, this hook is equivalent to:
let output_type = &hook_sig.output_type; let output_type = &hook_sig.output_type;
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let call_generics = { let call_generics = hook_sig.call_generics();
let mut generics = generics.clone();
// We need to filter out lifetimes. // We use _ctx so that if a hook does not use other hooks, it will not trigger unused_vars.
generics.params = generics let ctx_ident = Ident::new("_ctx", Span::mixed_site());
.params
.into_iter()
.filter(|m| !matches!(m, GenericParam::Lifetime(_)))
.collect();
let (_impl_generics, ty_generics, _where_clause) = generics.split_for_impl(); let mut body_rewriter = BodyRewriter::new(ctx_ident.clone());
ty_generics.as_turbofish().to_token_stream() visit_mut::visit_block_mut(&mut body_rewriter, &mut block);
};
let ctx_ident = Ident::new("ctx", Span::mixed_site());
let mut body_rewriter = BodyRewriter::default();
visit_mut::visit_block_mut(&mut body_rewriter, &mut *block);
let inner_fn_ident = Ident::new("inner_fn", Span::mixed_site()); let inner_fn_ident = Ident::new("inner_fn", Span::mixed_site());
let input_args = hook_sig.input_args(); let input_args = hook_sig.input_args();
@ -188,7 +190,7 @@ When used in function components and hooks, this hook is equivalent to:
let output = quote! { let output = quote! {
#[cfg(not(doctest))] #[cfg(not(doctest))]
#(#attrs)* #(#attrs)*
#[doc = #doc_text] #doc_attr
#vis #fn_token #ident #generics (#inputs) #hook_return_type #where_clause { #vis #fn_token #ident #generics (#inputs) #hook_return_type #where_clause {
#inner_fn #inner_fn

View File

@ -1,11 +1,11 @@
use proc_macro2::Span; use proc_macro2::{Span, TokenStream};
use proc_macro_error::emit_error; use proc_macro_error::emit_error;
use quote::quote; use quote::{quote, ToTokens};
use syn::spanned::Spanned; use syn::spanned::Spanned;
use syn::visit_mut::VisitMut; use syn::visit_mut::VisitMut;
use syn::{ use syn::{
parse_quote, parse_quote_spanned, token, visit_mut, FnArg, Ident, Lifetime, Pat, Receiver, parse_quote, parse_quote_spanned, token, visit_mut, FnArg, GenericParam, Ident, Lifetime, Pat,
ReturnType, Signature, Type, TypeImplTrait, TypeReference, WhereClause, Receiver, ReturnType, Signature, Type, TypeImplTrait, TypeReference, WhereClause,
}; };
use super::lifetime; use super::lifetime;
@ -180,4 +180,18 @@ impl HookSignature {
}) })
.collect() .collect()
} }
pub fn call_generics(&self) -> TokenStream {
let mut generics = self.sig.generics.clone();
// We need to filter out lifetimes.
generics.params = generics
.params
.into_iter()
.filter(|m| !matches!(m, GenericParam::Lifetime(_)))
.collect();
let (_impl_generics, ty_generics, _where_clause) = generics.split_for_impl();
ty_generics.as_turbofish().to_token_stream()
}
} }

View File

@ -92,7 +92,7 @@ impl ToTokens for HtmlComponent {
children, children,
} = self; } = self;
let props_ty = quote_spanned!(ty.span()=> <#ty as ::yew::html::BaseComponent>::Properties); let props_ty = quote_spanned!(ty.span()=> <#ty as ::yew::html::IntoComponent>::Properties);
let children_renderer = if children.is_empty() { let children_renderer = if children.is_empty() {
None None
} else { } else {

View File

@ -58,21 +58,20 @@ fn comp1<T1, T2>(_props: &()) -> ::yew::Html {
} }
} }
// no longer possible? #[::yew::function_component(ConstGenerics)]
// #[::yew::function_component(ConstGenerics)] fn const_generics<const N: ::std::primitive::i32>() -> ::yew::Html {
// fn const_generics<const N: ::std::primitive::i32>() -> ::yew::Html { ::yew::html! {
// ::yew::html! { <div>
// <div> { N }
// { N } </div>
// </div> }
// } }
// }
fn compile_pass() { fn compile_pass() {
::yew::html! { <Comp<Props> a=10 /> }; ::yew::html! { <Comp<Props> a=10 /> };
::yew::html! { <Comp1<::std::primitive::usize, ::std::primitive::usize> /> }; ::yew::html! { <Comp1<::std::primitive::usize, ::std::primitive::usize> /> };
// ::yew::html! { <ConstGenerics<10> /> }; ::yew::html! { <ConstGenerics<10> /> };
} }
fn main() {} fn main() {}

View File

@ -19,54 +19,49 @@ error[E0599]: no method named `build` found for struct `PropsBuilder<PropsBuilde
= note: the method was found for = note: the method was found for
- `PropsBuilder<PropsBuilderStepPropsBuilder>` - `PropsBuilder<PropsBuilderStepPropsBuilder>`
error[E0277]: the trait bound `FunctionComponent<CompFunctionProvider<MissingTypeBounds>>: BaseComponent` is not satisfied error[E0277]: the trait bound `Comp<MissingTypeBounds>: IntoComponent` is not satisfied
--> tests/function_component_attr/generic-props-fail.rs:27:14 --> tests/function_component_attr/generic-props-fail.rs:27:14
| |
27 | html! { <Comp<MissingTypeBounds> /> }; 27 | html! { <Comp<MissingTypeBounds> /> };
| ^^^^ the trait `BaseComponent` is not implemented for `FunctionComponent<CompFunctionProvider<MissingTypeBounds>>` | ^^^^ the trait `IntoComponent` is not implemented for `Comp<MissingTypeBounds>`
| |
= help: the following implementations were found: = help: the following implementations were found:
<FunctionComponent<T> as BaseComponent> <Comp<P> as IntoComponent>
error[E0599]: the function or associated item `new` exists for struct `VChild<FunctionComponent<CompFunctionProvider<MissingTypeBounds>>>`, but its trait bounds were not satisfied error[E0599]: the function or associated item `new` exists for struct `VChild<Comp<MissingTypeBounds>>`, but its trait bounds were not satisfied
--> tests/function_component_attr/generic-props-fail.rs:27:14 --> tests/function_component_attr/generic-props-fail.rs:27:14
| |
27 | html! { <Comp<MissingTypeBounds> /> }; 8 | #[function_component(Comp)]
| ^^^^ function or associated item cannot be called on `VChild<FunctionComponent<CompFunctionProvider<MissingTypeBounds>>>` due to unsatisfied trait bounds | --------------------------- doesn't satisfy `Comp<MissingTypeBounds>: IntoComponent`
| ...
::: $WORKSPACE/packages/yew/src/functional/mod.rs 27 | html! { <Comp<MissingTypeBounds> /> };
| | ^^^^ function or associated item cannot be called on `VChild<Comp<MissingTypeBounds>>` due to unsatisfied trait bounds
| pub struct FunctionComponent<T: FunctionProvider + 'static> { |
| ----------------------------------------------------------- doesn't satisfy `_: BaseComponent` = note: the following trait bounds were not satisfied:
| `Comp<MissingTypeBounds>: IntoComponent`
= note: the following trait bounds were not satisfied:
`FunctionComponent<CompFunctionProvider<MissingTypeBounds>>: BaseComponent`
error[E0277]: the trait bound `MissingTypeBounds: yew::Properties` is not satisfied error[E0277]: the trait bound `MissingTypeBounds: yew::Properties` is not satisfied
--> tests/function_component_attr/generic-props-fail.rs:27:14 --> tests/function_component_attr/generic-props-fail.rs:27:14
| |
27 | html! { <Comp<MissingTypeBounds> /> }; 27 | html! { <Comp<MissingTypeBounds> /> };
| ^^^^ the trait `yew::Properties` is not implemented for `MissingTypeBounds` | ^^^^ the trait `yew::Properties` is not implemented for `MissingTypeBounds`
| |
note: required because of the requirements on the impl of `FunctionProvider` for `CompFunctionProvider<MissingTypeBounds>` note: required by a bound in `Comp`
--> tests/function_component_attr/generic-props-fail.rs:8:1 --> tests/function_component_attr/generic-props-fail.rs:11:8
| |
8 | #[function_component(Comp)] 8 | #[function_component(Comp)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ---- required by a bound in this
note: required by a bound in `FunctionComponent` ...
--> $WORKSPACE/packages/yew/src/functional/mod.rs 11 | P: Properties + PartialEq,
| | ^^^^^^^^^^ required by this bound in `Comp`
| pub struct FunctionComponent<T: FunctionProvider + 'static> {
| ^^^^^^^^^^^^^^^^ required by this bound in `FunctionComponent`
= note: this error originates in the attribute macro `function_component` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0107]: missing generics for type alias `Comp` error[E0107]: missing generics for struct `Comp`
--> tests/function_component_attr/generic-props-fail.rs:30:14 --> tests/function_component_attr/generic-props-fail.rs:30:14
| |
30 | html! { <Comp /> }; 30 | html! { <Comp /> };
| ^^^^ expected 1 generic argument | ^^^^ expected 1 generic argument
| |
note: type alias defined here, with 1 generic parameter: `P` note: struct defined here, with 1 generic parameter: `P`
--> tests/function_component_attr/generic-props-fail.rs:8:22 --> tests/function_component_attr/generic-props-fail.rs:8:22
| |
8 | #[function_component(Comp)] 8 | #[function_component(Comp)]

View File

@ -0,0 +1,30 @@
use yew::prelude::*;
#[hook]
pub fn use_some_macro_inner(val: &str) -> String {
use_state(|| val.to_owned()).to_string()
}
macro_rules! use_some_macro {
() => {
use_some_macro_inner("default str")
};
($t: tt) => {
use_some_macro_inner($t)
};
}
#[function_component]
fn Comp() -> Html {
let content = if true {
use_some_macro!()
} else {
use_some_macro!("b")
};
html! {
<div>{content}</div>
}
}
fn main() {}

View File

@ -0,0 +1,33 @@
error: hooks cannot be called at this position.
= help: move hooks to the top-level of your function.
= note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks
--> tests/hook_attr/hook_macro-fail.rs:20:9
|
20 | use_some_macro!()
| ^^^^^^^^^^^^^^
error: hooks cannot be called at this position.
= help: move hooks to the top-level of your function.
= note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks
--> tests/hook_attr/hook_macro-fail.rs:22:9
|
22 | use_some_macro!("b")
| ^^^^^^^^^^^^^^
warning: unused macro definition
--> tests/hook_attr/hook_macro-fail.rs:8:1
|
8 | / macro_rules! use_some_macro {
9 | | () => {
10 | | use_some_macro_inner("default str")
11 | | };
... |
14 | | };
15 | | }
| |_^
|
= note: `#[warn(unused_macros)]` on by default

View File

@ -0,0 +1,27 @@
use yew::prelude::*;
#[hook]
pub fn use_some_macro_inner(val: &str) -> String {
use_state(|| val.to_owned()).to_string()
}
macro_rules! use_some_macro {
() => {
use_some_macro_inner("default str")
};
($t: tt) => {
use_some_macro_inner($t)
};
}
#[function_component]
fn Comp() -> Html {
let a = use_some_macro!();
let b = use_some_macro!("b");
html! {
<div>{a}{b}</div>
}
}
fn main() {}

View File

@ -10,10 +10,10 @@ error[E0599]: the function or associated item `new` exists for struct `VChild<Un
--> tests/html_macro/component-unimplemented-fail.rs:6:14 --> tests/html_macro/component-unimplemented-fail.rs:6:14
| |
3 | struct Unimplemented; 3 | struct Unimplemented;
| --------------------- doesn't satisfy `Unimplemented: BaseComponent` | --------------------- doesn't satisfy `Unimplemented: IntoComponent`
... ...
6 | html! { <Unimplemented /> }; 6 | html! { <Unimplemented /> };
| ^^^^^^^^^^^^^ function or associated item cannot be called on `VChild<Unimplemented>` due to unsatisfied trait bounds | ^^^^^^^^^^^^^ function or associated item cannot be called on `VChild<Unimplemented>` due to unsatisfied trait bounds
| |
= note: the following trait bounds were not satisfied: = note: the following trait bounds were not satisfied:
`Unimplemented: BaseComponent` `Unimplemented: IntoComponent`

View File

@ -1,27 +1,27 @@
//! [AppHandle] contains the state Yew keeps to bootstrap a component in an isolated scope. //! [AppHandle] contains the state Yew keeps to bootstrap a component in an isolated scope.
use super::{ComponentRenderState, Scoped}; use super::{ComponentRenderState, Scoped};
use crate::html::{BaseComponent, Scope}; use crate::html::{IntoComponent, NodeRef, Scope};
use crate::NodeRef; use std::ops::Deref;
use std::{ops::Deref, rc::Rc}; use std::rc::Rc;
use web_sys::Element; use web_sys::Element;
/// An instance of an application. /// An instance of an application.
#[derive(Debug)] #[derive(Debug)]
pub struct AppHandle<COMP: BaseComponent> { pub struct AppHandle<ICOMP: IntoComponent> {
/// `Scope` holder /// `Scope` holder
scope: Scope<COMP>, pub(crate) scope: Scope<<ICOMP as IntoComponent>::Component>,
} }
impl<COMP> AppHandle<COMP> impl<ICOMP> AppHandle<ICOMP>
where where
COMP: BaseComponent, ICOMP: IntoComponent,
{ {
/// The main entry point of a Yew program which also allows passing properties. It works /// The main entry point of a Yew program which also allows passing properties. It works
/// similarly to the `program` function in Elm. You should provide an initial model, `update` /// similarly to the `program` function in Elm. You should provide an initial model, `update`
/// function which will update the state of the model and a `view` function which /// function which will update the state of the model and a `view` function which
/// will render the model to a virtual DOM tree. /// will render the model to a virtual DOM tree.
pub(crate) fn mount_with_props(element: Element, props: Rc<COMP::Properties>) -> Self { pub(crate) fn mount_with_props(element: Element, props: Rc<ICOMP::Properties>) -> Self {
clear_element(&element); clear_element(&element);
let app = Self { let app = Self {
scope: Scope::new(None), scope: Scope::new(None),
@ -41,11 +41,11 @@ where
} }
} }
impl<COMP> Deref for AppHandle<COMP> impl<ICOMP> Deref for AppHandle<ICOMP>
where where
COMP: BaseComponent, ICOMP: IntoComponent,
{ {
type Target = Scope<COMP>; type Target = Scope<<ICOMP as IntoComponent>::Component>;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
&self.scope &self.scope

View File

@ -175,7 +175,7 @@ impl<COMP: BaseComponent> Mountable for PropsWrapper<COMP> {
} }
fn reuse(self: Box<Self>, node_ref: NodeRef, scope: &dyn Scoped, next_sibling: NodeRef) { fn reuse(self: Box<Self>, node_ref: NodeRef, scope: &dyn Scoped, next_sibling: NodeRef) {
let scope: Scope<COMP> = scope.to_any().downcast(); let scope: Scope<COMP> = scope.to_any().downcast::<COMP>();
scope.reuse(self.props, node_ref, next_sibling); scope.reuse(self.props, node_ref, next_sibling);
} }

View File

@ -31,7 +31,7 @@ pub use hooks::*;
use crate::html::Context; use crate::html::Context;
use crate::html::SealedBaseComponent; use crate::html::sealed::SealedBaseComponent;
/// This attribute creates a function component from a normal Rust function. /// This attribute creates a function component from a normal Rust function.
/// ///
@ -131,21 +131,27 @@ impl fmt::Debug for HookContext {
/// Trait that allows a struct to act as Function Component. /// Trait that allows a struct to act as Function Component.
pub trait FunctionProvider { pub trait FunctionProvider {
/// Properties for the Function Component. /// Properties for the Function Component.
type TProps: Properties + PartialEq; type Properties: Properties + PartialEq;
/// Render the component. This function returns the [`Html`](crate::Html) to be rendered for the component. /// Render the component. This function returns the [`Html`](crate::Html) to be rendered for the component.
/// ///
/// Equivalent of [`Component::view`](crate::html::Component::view). /// Equivalent of [`Component::view`](crate::html::Component::view).
fn run(ctx: &mut HookContext, props: &Self::TProps) -> HtmlResult; fn run(ctx: &mut HookContext, props: &Self::Properties) -> HtmlResult;
} }
/// Wrapper that allows a struct implementing [`FunctionProvider`] to be consumed as a component. /// Wrapper that allows a struct implementing [`FunctionProvider`] to be consumed as a component.
pub struct FunctionComponent<T: FunctionProvider + 'static> { pub struct FunctionComponent<T>
where
T: FunctionProvider + 'static,
{
_never: std::marker::PhantomData<T>, _never: std::marker::PhantomData<T>,
hook_ctx: RefCell<HookContext>, hook_ctx: RefCell<HookContext>,
} }
impl<T: FunctionProvider> fmt::Debug for FunctionComponent<T> { impl<T> fmt::Debug for FunctionComponent<T>
where
T: FunctionProvider + 'static,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("FunctionComponent<_>") f.write_str("FunctionComponent<_>")
} }
@ -156,7 +162,7 @@ where
T: FunctionProvider + 'static, T: FunctionProvider + 'static,
{ {
type Message = (); type Message = ();
type Properties = T::TProps; type Properties = T::Properties;
fn create(ctx: &Context<Self>) -> Self { fn create(ctx: &Context<Self>) -> Self {
let scope = AnyScope::from(ctx.link().clone()); let scope = AnyScope::from(ctx.link().clone());

View File

@ -70,9 +70,11 @@ impl<COMP: BaseComponent> Context<COMP> {
} }
} }
/// A Sealed trait that prevents direct implementation of pub(crate) mod sealed {
/// [BaseComponent]. /// A Sealed trait that prevents direct implementation of
pub trait SealedBaseComponent {} /// [BaseComponent].
pub trait SealedBaseComponent {}
}
/// The common base of both function components and struct components. /// The common base of both function components and struct components.
/// ///
@ -80,11 +82,11 @@ pub trait SealedBaseComponent {}
/// [`#[function_component]`](crate::functional::function_component). /// [`#[function_component]`](crate::functional::function_component).
/// ///
/// We provide a blanket implementation of this trait for every member that implements [`Component`]. /// We provide a blanket implementation of this trait for every member that implements [`Component`].
pub trait BaseComponent: SealedBaseComponent + Sized + 'static { pub trait BaseComponent: sealed::SealedBaseComponent + Sized + 'static {
/// The Component's Message. /// The Component's Message.
type Message: 'static; type Message: 'static;
/// The Component's properties. /// The Component's Properties.
type Properties: Properties; type Properties: Properties;
/// Creates a component. /// Creates a component.
@ -201,4 +203,24 @@ where
} }
} }
impl<T> SealedBaseComponent for T where T: Sized + Component + 'static {} impl<T> sealed::SealedBaseComponent for T where T: Sized + Component + 'static {}
/// A trait that indicates a type is able to be converted into a component.
///
/// You may want to use this trait if you want to accept both function components and struct
/// components as a generic parameter.
pub trait IntoComponent {
/// The Component's Properties.
type Properties: Properties;
/// The Component Type.
type Component: BaseComponent<Properties = Self::Properties> + 'static;
}
impl<T> IntoComponent for T
where
T: BaseComponent + 'static,
{
type Properties = T::Properties;
type Component = T;
}

View File

@ -10,9 +10,10 @@ use super::{
use crate::callback::Callback; use crate::callback::Callback;
use crate::context::{ContextHandle, ContextProvider}; use crate::context::{ContextHandle, ContextProvider};
use crate::dom_bundle::{ComponentRenderState, Scoped}; use crate::dom_bundle::{ComponentRenderState, Scoped};
use crate::html::IntoComponent;
use crate::html::NodeRef; use crate::html::NodeRef;
use crate::scheduler::{self, Shared}; use crate::scheduler::{self, Shared};
use std::any::TypeId; use std::any::{Any, TypeId};
use std::cell::{Ref, RefCell}; use std::cell::{Ref, RefCell};
use std::marker::PhantomData; use std::marker::PhantomData;
use std::ops::Deref; use std::ops::Deref;
@ -63,7 +64,7 @@ impl<Msg> Clone for MsgQueue<Msg> {
pub struct AnyScope { pub struct AnyScope {
type_id: TypeId, type_id: TypeId,
parent: Option<Rc<AnyScope>>, parent: Option<Rc<AnyScope>>,
state: Shared<Option<ComponentState>>, typed_scope: Rc<dyn Any>,
} }
impl fmt::Debug for AnyScope { impl fmt::Debug for AnyScope {
@ -76,8 +77,8 @@ impl<COMP: BaseComponent> From<Scope<COMP>> for AnyScope {
fn from(scope: Scope<COMP>) -> Self { fn from(scope: Scope<COMP>) -> Self {
AnyScope { AnyScope {
type_id: TypeId::of::<COMP>(), type_id: TypeId::of::<COMP>(),
parent: scope.parent, parent: scope.parent.clone(),
state: scope.state, typed_scope: Rc::new(scope),
} }
} }
} }
@ -88,7 +89,7 @@ impl AnyScope {
Self { Self {
type_id: TypeId::of::<()>(), type_id: TypeId::of::<()>(),
parent: None, parent: None,
state: Rc::new(RefCell::new(None)), typed_scope: Rc::new(()),
} }
} }
@ -107,37 +108,25 @@ impl AnyScope {
/// # Panics /// # Panics
/// ///
/// If the self value can't be cast into the target type. /// If the self value can't be cast into the target type.
pub fn downcast<COMP: BaseComponent>(self) -> Scope<COMP> { pub fn downcast<ICOMP: IntoComponent>(&self) -> Scope<ICOMP::Component> {
self.try_downcast::<COMP>().unwrap() self.try_downcast::<ICOMP>().unwrap()
} }
/// Attempts to downcast into a typed scope /// Attempts to downcast into a typed scope
/// ///
/// Returns [`None`] if the self value can't be cast into the target type. /// Returns [`None`] if the self value can't be cast into the target type.
pub fn try_downcast<COMP: BaseComponent>(self) -> Option<Scope<COMP>> { pub fn try_downcast<ICOMP: IntoComponent>(&self) -> Option<Scope<ICOMP::Component>> {
let state = self.state.borrow(); self.typed_scope
.downcast_ref::<Scope<ICOMP::Component>>()
state.as_ref().map(|m| { .cloned()
m.inner
.as_any()
.downcast_ref::<CompStateInner<COMP>>()
.unwrap()
.context
.link()
.clone()
})
} }
/// Attempts to find a parent scope of a certain type /// Attempts to find a parent scope of a certain type
/// ///
/// Returns [`None`] if no parent scope with the specified type was found. /// Returns [`None`] if no parent scope with the specified type was found.
pub fn find_parent_scope<C: BaseComponent>(&self) -> Option<Scope<C>> { pub fn find_parent_scope<ICOMP: IntoComponent>(&self) -> Option<Scope<ICOMP::Component>> {
let expected_type_id = TypeId::of::<C>();
iter::successors(Some(self), |scope| scope.get_parent()) iter::successors(Some(self), |scope| scope.get_parent())
.filter(|scope| scope.get_type_id() == &expected_type_id) .find_map(AnyScope::try_downcast::<ICOMP>)
.cloned()
.map(AnyScope::downcast::<C>)
.next()
} }
/// Accesses a value provided by a parent `ContextProvider` component of the /// Accesses a value provided by a parent `ContextProvider` component of the

View File

@ -7,6 +7,7 @@ mod error;
mod listener; mod listener;
pub use classes::*; pub use classes::*;
pub(crate) use component::sealed;
pub use component::*; pub use component::*;
pub use conversion::*; pub use conversion::*;
pub use error::*; pub use error::*;

View File

@ -300,7 +300,7 @@ pub mod events {
pub use crate::dom_bundle::AppHandle; pub use crate::dom_bundle::AppHandle;
use web_sys::Element; use web_sys::Element;
use crate::html::BaseComponent; use crate::html::IntoComponent;
thread_local! { thread_local! {
static PANIC_HOOK_IS_SET: Cell<bool> = Cell::new(false); static PANIC_HOOK_IS_SET: Cell<bool> = Cell::new(false);
@ -322,44 +322,44 @@ fn set_default_panic_hook() {
/// The main entry point of a Yew application. /// The main entry point of a Yew application.
/// If you would like to pass props, use the `start_app_with_props_in_element` method. /// If you would like to pass props, use the `start_app_with_props_in_element` method.
pub fn start_app_in_element<COMP>(element: Element) -> AppHandle<COMP> pub fn start_app_in_element<ICOMP>(element: Element) -> AppHandle<ICOMP>
where where
COMP: BaseComponent, ICOMP: IntoComponent,
COMP::Properties: Default, ICOMP::Properties: Default,
{ {
start_app_with_props_in_element::<COMP>(element, COMP::Properties::default()) start_app_with_props_in_element(element, ICOMP::Properties::default())
} }
/// Starts an yew app mounted to the body of the document. /// Starts an yew app mounted to the body of the document.
/// Alias to start_app_in_element(Body) /// Alias to start_app_in_element(Body)
pub fn start_app<COMP>() -> AppHandle<COMP> pub fn start_app<ICOMP>() -> AppHandle<ICOMP>
where where
COMP: BaseComponent, ICOMP: IntoComponent,
COMP::Properties: Default, ICOMP::Properties: Default,
{ {
start_app_with_props::<COMP>(COMP::Properties::default()) start_app_with_props(ICOMP::Properties::default())
} }
/// The main entry point of a Yew application. This function does the /// The main entry point of a Yew application. This function does the
/// same as `start_app_in_element(...)` but allows to start an Yew application with properties. /// same as `start_app_in_element(...)` but allows to start an Yew application with properties.
pub fn start_app_with_props_in_element<COMP>( pub fn start_app_with_props_in_element<ICOMP>(
element: Element, element: Element,
props: COMP::Properties, props: ICOMP::Properties,
) -> AppHandle<COMP> ) -> AppHandle<ICOMP>
where where
COMP: BaseComponent, ICOMP: IntoComponent,
{ {
set_default_panic_hook(); set_default_panic_hook();
AppHandle::<COMP>::mount_with_props(element, Rc::new(props)) AppHandle::<ICOMP>::mount_with_props(element, Rc::new(props))
} }
/// The main entry point of a Yew application. /// The main entry point of a Yew application.
/// This function does the same as `start_app(...)` but allows to start an Yew application with properties. /// This function does the same as `start_app(...)` but allows to start an Yew application with properties.
pub fn start_app_with_props<COMP>(props: COMP::Properties) -> AppHandle<COMP> pub fn start_app_with_props<ICOMP>(props: ICOMP::Properties) -> AppHandle<ICOMP>
where where
COMP: BaseComponent, ICOMP: IntoComponent,
{ {
start_app_with_props_in_element::<COMP>( start_app_with_props_in_element(
gloo_utils::document() gloo_utils::document()
.body() .body()
.expect("no body node found") .expect("no body node found")
@ -383,10 +383,11 @@ pub mod prelude {
pub use crate::events::*; pub use crate::events::*;
pub use crate::html::{ pub use crate::html::{
create_portal, BaseComponent, Children, ChildrenWithProps, Classes, Component, Context, create_portal, BaseComponent, Children, ChildrenWithProps, Classes, Component, Context,
Html, HtmlResult, NodeRef, Properties, Html, HtmlResult, IntoComponent, NodeRef, Properties,
}; };
pub use crate::macros::{classes, html, html_nested}; pub use crate::macros::{classes, html, html_nested};
pub use crate::suspense::Suspense; pub use crate::suspense::Suspense;
pub use crate::virtual_dom::AttrValue;
pub use crate::functional::*; pub use crate::functional::*;
} }

View File

@ -3,29 +3,29 @@ use super::*;
use crate::html::Scope; use crate::html::Scope;
/// A Yew Server-side Renderer. /// A Yew Server-side Renderer.
#[derive(Debug)]
#[cfg_attr(documenting, doc(cfg(feature = "ssr")))] #[cfg_attr(documenting, doc(cfg(feature = "ssr")))]
pub struct ServerRenderer<COMP> #[derive(Debug)]
pub struct ServerRenderer<ICOMP>
where where
COMP: BaseComponent, ICOMP: IntoComponent,
{ {
props: COMP::Properties, props: ICOMP::Properties,
} }
impl<COMP> Default for ServerRenderer<COMP> impl<ICOMP> Default for ServerRenderer<ICOMP>
where where
COMP: BaseComponent, ICOMP: IntoComponent,
COMP::Properties: Default, ICOMP::Properties: Default,
{ {
fn default() -> Self { fn default() -> Self {
Self::with_props(COMP::Properties::default()) Self::with_props(ICOMP::Properties::default())
} }
} }
impl<COMP> ServerRenderer<COMP> impl<ICOMP> ServerRenderer<ICOMP>
where where
COMP: BaseComponent, ICOMP: IntoComponent,
COMP::Properties: Default, ICOMP::Properties: Default,
{ {
/// Creates a [ServerRenderer] with default properties. /// Creates a [ServerRenderer] with default properties.
pub fn new() -> Self { pub fn new() -> Self {
@ -33,12 +33,12 @@ where
} }
} }
impl<COMP> ServerRenderer<COMP> impl<ICOMP> ServerRenderer<ICOMP>
where where
COMP: BaseComponent, ICOMP: IntoComponent,
{ {
/// Creates a [ServerRenderer] with custom properties. /// Creates a [ServerRenderer] with custom properties.
pub fn with_props(props: COMP::Properties) -> Self { pub fn with_props(props: ICOMP::Properties) -> Self {
Self { props } Self { props }
} }
@ -53,7 +53,7 @@ where
/// Renders Yew Application to a String. /// Renders Yew Application to a String.
pub async fn render_to_string(self, w: &mut String) { pub async fn render_to_string(self, w: &mut String) {
let scope = Scope::<COMP>::new(None); let scope = Scope::<<ICOMP as IntoComponent>::Component>::new(None);
scope.render_to_string(w, self.props.into()).await; scope.render_to_string(w, self.props.into()).await;
} }
} }

View File

@ -2,7 +2,7 @@
use super::Key; use super::Key;
use crate::dom_bundle::{Mountable, PropsWrapper}; use crate::dom_bundle::{Mountable, PropsWrapper};
use crate::html::{BaseComponent, NodeRef}; use crate::html::{BaseComponent, IntoComponent, NodeRef};
use std::any::TypeId; use std::any::TypeId;
use std::fmt; use std::fmt;
use std::rc::Rc; use std::rc::Rc;
@ -41,15 +41,15 @@ impl Clone for VComp {
} }
/// A virtual child component. /// A virtual child component.
pub struct VChild<COMP: BaseComponent> { pub struct VChild<ICOMP: IntoComponent> {
/// The component properties /// The component properties
pub props: Rc<COMP::Properties>, pub props: Rc<ICOMP::Properties>,
/// Reference to the mounted node /// Reference to the mounted node
node_ref: NodeRef, node_ref: NodeRef,
key: Option<Key>, key: Option<Key>,
} }
impl<COMP: BaseComponent> Clone for VChild<COMP> { impl<ICOMP: IntoComponent> Clone for VChild<ICOMP> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
VChild { VChild {
props: Rc::clone(&self.props), props: Rc::clone(&self.props),
@ -59,21 +59,21 @@ impl<COMP: BaseComponent> Clone for VChild<COMP> {
} }
} }
impl<COMP: BaseComponent> PartialEq for VChild<COMP> impl<ICOMP: IntoComponent> PartialEq for VChild<ICOMP>
where where
COMP::Properties: PartialEq, ICOMP::Properties: PartialEq,
{ {
fn eq(&self, other: &VChild<COMP>) -> bool { fn eq(&self, other: &VChild<ICOMP>) -> bool {
self.props == other.props self.props == other.props
} }
} }
impl<COMP> VChild<COMP> impl<ICOMP> VChild<ICOMP>
where where
COMP: BaseComponent, ICOMP: IntoComponent,
{ {
/// Creates a child component that can be accessed and modified by its parent. /// Creates a child component that can be accessed and modified by its parent.
pub fn new(props: COMP::Properties, node_ref: NodeRef, key: Option<Key>) -> Self { pub fn new(props: ICOMP::Properties, node_ref: NodeRef, key: Option<Key>) -> Self {
Self { Self {
props: Rc::new(props), props: Rc::new(props),
node_ref, node_ref,
@ -82,25 +82,25 @@ where
} }
} }
impl<COMP> From<VChild<COMP>> for VComp impl<ICOMP> From<VChild<ICOMP>> for VComp
where where
COMP: BaseComponent, ICOMP: IntoComponent,
{ {
fn from(vchild: VChild<COMP>) -> Self { fn from(vchild: VChild<ICOMP>) -> Self {
VComp::new::<COMP>(vchild.props, vchild.node_ref, vchild.key) VComp::new::<ICOMP>(vchild.props, vchild.node_ref, vchild.key)
} }
} }
impl VComp { impl VComp {
/// Creates a new `VComp` instance. /// Creates a new `VComp` instance.
pub fn new<COMP>(props: Rc<COMP::Properties>, node_ref: NodeRef, key: Option<Key>) -> Self pub fn new<ICOMP>(props: Rc<ICOMP::Properties>, node_ref: NodeRef, key: Option<Key>) -> Self
where where
COMP: BaseComponent, ICOMP: IntoComponent,
{ {
VComp { VComp {
type_id: TypeId::of::<COMP>(), type_id: TypeId::of::<ICOMP::Component>(),
node_ref, node_ref,
mountable: Box::new(PropsWrapper::<COMP>::new(props)), mountable: Box::new(PropsWrapper::<ICOMP::Component>::new(props)),
key, key,
} }
} }

View File

@ -1,7 +1,7 @@
//! This module contains the implementation of abstract virtual node. //! This module contains the implementation of abstract virtual node.
use super::{Key, VChild, VComp, VList, VPortal, VSuspense, VTag, VText}; use super::{Key, VChild, VComp, VList, VPortal, VSuspense, VTag, VText};
use crate::html::BaseComponent; use crate::html::IntoComponent;
use std::cmp::PartialEq; use std::cmp::PartialEq;
use std::fmt; use std::fmt;
use std::iter::FromIterator; use std::iter::FromIterator;
@ -93,11 +93,11 @@ impl From<VPortal> for VNode {
} }
} }
impl<COMP> From<VChild<COMP>> for VNode impl<ICOMP> From<VChild<ICOMP>> for VNode
where where
COMP: BaseComponent, ICOMP: IntoComponent,
{ {
fn from(vchild: VChild<COMP>) -> Self { fn from(vchild: VChild<ICOMP>) -> Self {
VNode::VComp(VComp::from(vchild)) VNode::VComp(VComp::from(vchild))
} }
} }

View File

@ -4,9 +4,9 @@ error[E0277]: the trait bound `Comp: yew::Component` is not satisfied
6 | impl BaseComponent for Comp { 6 | impl BaseComponent for Comp {
| ^^^^^^^^^^^^^ the trait `yew::Component` is not implemented for `Comp` | ^^^^^^^^^^^^^ the trait `yew::Component` is not implemented for `Comp`
| |
= note: required because of the requirements on the impl of `SealedBaseComponent` for `Comp` = note: required because of the requirements on the impl of `html::component::sealed::SealedBaseComponent` for `Comp`
note: required by a bound in `BaseComponent` note: required by a bound in `BaseComponent`
--> src/html/component/mod.rs --> src/html/component/mod.rs
| |
| pub trait BaseComponent: SealedBaseComponent + Sized + 'static { | pub trait BaseComponent: sealed::SealedBaseComponent + Sized + 'static {
| ^^^^^^^^^^^^^^^^^^^ required by this bound in `BaseComponent` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `BaseComponent`

View File

@ -23,6 +23,6 @@ impl BaseComponent for Comp {
fn destroy(&mut self, _ctx: &Context<Self>) {} fn destroy(&mut self, _ctx: &Context<Self>) {}
} }
impl yew::html::component::SealedBaseComponent for Comp {} impl yew::html::component::sealed::SealedBaseComponent for Comp {}
fn main() {} fn main() {}

View File

@ -1,7 +1,7 @@
error[E0603]: module `component` is private error[E0603]: module `component` is private
--> tests/failed_tests/sealed_base_component_impl-fail.rs:26:17 --> tests/failed_tests/sealed_base_component_impl-fail.rs:26:17
| |
26 | impl yew::html::component::SealedBaseComponent for Comp {} 26 | impl yew::html::component::sealed::SealedBaseComponent for Comp {}
| ^^^^^^^^^ private module | ^^^^^^^^^ private module
| |
note: the module `component` is defined here note: the module `component` is defined here

View File

@ -20,7 +20,7 @@ fn main() -> Result<()> {
let transformed_benchmarks: Vec<GhActionBenchmark> = input_json let transformed_benchmarks: Vec<GhActionBenchmark> = input_json
.into_iter() .into_iter()
.map(|v| GhActionBenchmark { .map(|v| GhActionBenchmark {
name: format!("{} {}", v["framework"], v["benchmark"]).replace('\"', ""), name: format!("{} {}", v["framework"], v["benchmark"]).replace('"', ""),
unit: String::default(), unit: String::default(),
value: v["median"].to_string(), value: v["median"].to_string(),
}) })