mirror of
https://github.com/yewstack/yew.git
synced 2025-12-08 21:26:25 +00:00
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:
parent
6e425ff38b
commit
51238fb0e3
@ -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)
|
||||||
|
|||||||
@ -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))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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() {}
|
||||||
|
|||||||
@ -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)]
|
||||||
|
|||||||
30
packages/yew-macro/tests/hook_attr/hook_macro-fail.rs
Normal file
30
packages/yew-macro/tests/hook_attr/hook_macro-fail.rs
Normal 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() {}
|
||||||
33
packages/yew-macro/tests/hook_attr/hook_macro-fail.stderr
Normal file
33
packages/yew-macro/tests/hook_attr/hook_macro-fail.stderr
Normal 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
|
||||||
27
packages/yew-macro/tests/hook_attr/hook_macro-pass.rs
Normal file
27
packages/yew-macro/tests/hook_attr/hook_macro-pass.rs
Normal 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() {}
|
||||||
@ -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`
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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());
|
||||||
|
|||||||
@ -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;
|
||||||
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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::*;
|
||||||
|
|||||||
@ -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::*;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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`
|
||||||
|
|||||||
@ -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() {}
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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(),
|
||||||
})
|
})
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user