Fix Call Generics & FnOnce macro hygiene (#2437)

* Fix Call Generics & FnOnce macro hygiene.

* Add a failure case as well.

* Remove a unused struct in test case.

* Update packages/yew-macro/tests/hook_attr/hook-const-generic-pass.rs

Co-authored-by: Muhammad Hamza <muhammadhamza1311@gmail.com>
This commit is contained in:
Kaede Hoshikawa 2022-02-12 22:26:23 +09:00 committed by GitHub
parent 95fb5dc8fb
commit fc067ab56c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 200 additions and 7 deletions

View File

@ -1,9 +1,9 @@
use proc_macro2::{Span, TokenStream};
use proc_macro_error::emit_error;
use quote::quote;
use quote::{quote, ToTokens};
use syn::parse::{Parse, ParseStream};
use syn::visit_mut;
use syn::{parse_file, Ident, ItemFn, LitStr, ReturnType, Signature};
use syn::{parse_file, GenericParam, Ident, ItemFn, LitStr, ReturnType, Signature};
mod body;
mod lifetime;
@ -98,6 +98,19 @@ When used in function components and hooks, this hook is equivalent to:
let output_type = &hook_sig.output_type;
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let call_generics = {
let mut generics = 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()
};
let ctx_ident = Ident::new("ctx", Span::mixed_site());
@ -120,13 +133,13 @@ When used in function components and hooks, this hook is equivalent to:
let hook_lifetime_plus = quote! { #hook_lifetime + };
let boxed_inner_ident = Ident::new("boxed_inner", Span::mixed_site());
let boxed_fn_type = quote! { ::std::boxed::Box<dyn #hook_lifetime_plus FnOnce(&mut ::yew::functional::HookContext) #inner_fn_rt> };
let boxed_fn_type = quote! { ::std::boxed::Box<dyn #hook_lifetime_plus ::std::ops::FnOnce(&mut ::yew::functional::HookContext) #inner_fn_rt> };
// We need boxing implementation for `impl Trait` arguments.
quote! {
let #boxed_inner_ident = ::std::boxed::Box::new(
move |#ctx_ident: &mut ::yew::functional::HookContext| #inner_fn_rt {
#inner_fn_ident (#ctx_ident, #(#input_args,)*)
#inner_fn_ident #call_generics (#ctx_ident, #(#input_args,)*)
}
) as #boxed_fn_type;
@ -138,8 +151,6 @@ When used in function components and hooks, this hook is equivalent to:
let args_ident = Ident::new("args", Span::mixed_site());
let hook_struct_name = Ident::new("HookProvider", Span::mixed_site());
let call_generics = ty_generics.as_turbofish();
let phantom_types = hook_sig.phantom_types();
let phantom_lifetimes = hook_sig.phantom_lifetimes();
@ -155,7 +166,7 @@ When used in function components and hooks, this hook is equivalent to:
fn run(mut self, #ctx_ident: &mut ::yew::functional::HookContext) -> Self::Output {
let (#(#input_args,)*) = self.#args_ident;
#inner_fn_ident(#ctx_ident, #(#input_args,)*)
#inner_fn_ident #call_generics (#ctx_ident, #(#input_args,)*)
}
}

View File

@ -0,0 +1,12 @@
use yew::prelude::*;
#[hook]
fn use_reducer_default_action<T>() -> T::Action
where
T: Reducible + 'static,
T::Action: Default + 'static,
{
T::Action::default()
}
fn main() {}

View File

@ -0,0 +1,8 @@
#![no_implicit_prelude]
#[::yew::prelude::hook]
fn use_a_const<const N: u32>() -> u32 {
N
}
fn main() {}

View File

@ -0,0 +1,9 @@
// we need to re-test the macro hygiene here as it uses a different implementation for impl traits.
#![no_implicit_prelude]
#[::yew::prelude::hook]
fn use_some_string(a: impl ::std::convert::Into<::std::string::String>) -> ::std::string::String {
a.into()
}
fn main() {}

View File

@ -0,0 +1,8 @@
use yew::prelude::*;
#[hook]
fn use_as_is<'a>(input: &'a ()) -> &'a () {
input
}
fn main() {}

View File

@ -0,0 +1,39 @@
use yew::prelude::*;
#[derive(Debug, PartialEq, Clone)]
struct Ctx;
#[hook]
fn use_some_html() -> Html {
if let Some(_m) = use_context::<Ctx>() {
use_context::<Ctx>().unwrap();
todo!()
}
let _ = || {
use_context::<Ctx>().unwrap();
todo!()
};
for _ in 0..10 {
use_context::<Ctx>().unwrap();
}
while let Some(_m) = use_context::<Ctx>() {
use_context::<Ctx>().unwrap();
}
match use_context::<Ctx>() {
Some(_) => use_context::<Ctx>(),
None => {
todo!()
}
}
loop {
use_context::<Ctx>().unwrap();
todo!()
}
}
fn main() {}

View File

@ -0,0 +1,69 @@
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_location-fail.rs:9:9
|
9 | use_context::<Ctx>().unwrap();
| ^^^^^^^^^^^
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_location-fail.rs:14:9
|
14 | use_context::<Ctx>().unwrap();
| ^^^^^^^^^^^
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_location-fail.rs:19:9
|
19 | use_context::<Ctx>().unwrap();
| ^^^^^^^^^^^
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_location-fail.rs:22:26
|
22 | while let Some(_m) = use_context::<Ctx>() {
| ^^^^^^^^^^^
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_location-fail.rs:23:9
|
23 | use_context::<Ctx>().unwrap();
| ^^^^^^^^^^^
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_location-fail.rs:27:20
|
27 | Some(_) => use_context::<Ctx>(),
| ^^^^^^^^^^^
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_location-fail.rs:34:9
|
34 | use_context::<Ctx>().unwrap();
| ^^^^^^^^^^^

View File

@ -0,0 +1,30 @@
#![no_implicit_prelude]
#[derive(
::std::prelude::rust_2021::Debug,
::std::prelude::rust_2021::PartialEq,
::std::prelude::rust_2021::Clone,
)]
struct Ctx;
#[::yew::prelude::hook]
fn use_some_html() -> ::yew::prelude::Html {
::yew::prelude::use_context::<Ctx>().unwrap();
if let ::std::prelude::rust_2021::Some(_m) = ::yew::prelude::use_context::<Ctx>() {
::std::todo!()
}
let _ctx = { ::yew::prelude::use_context::<Ctx>() };
match ::yew::prelude::use_context::<Ctx>() {
::std::prelude::rust_2021::Some(_) => {
::std::todo!()
}
::std::prelude::rust_2021::None => {
::std::todo!()
}
}
}
fn main() {}

View File

@ -0,0 +1,7 @@
#[allow(dead_code)]
#[rustversion::attr(stable(1.56), test)]
fn tests() {
let t = trybuild::TestCases::new();
t.pass("tests/hook_attr/*-pass.rs");
t.compile_fail("tests/hook_attr/*-fail.rs");
}