chore: macro `panic` hygiene (#568)

This commit is contained in:
Remo 2023-02-24 22:36:05 +01:00 committed by GitHub
parent a985ae5660
commit 46e6e7629c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 128 additions and 188 deletions

View File

@ -348,9 +348,8 @@ impl Docs {
.iter()
.enumerate()
.map(|(idx, attr)| {
if let Meta::NameValue(MetaNameValue { lit: doc, .. }) =
attr.parse_meta().unwrap()
{
match attr.parse_meta() {
Ok(Meta::NameValue(MetaNameValue { lit: doc, .. })) => {
let doc_str = quote!(#doc);
// We need to remove the leading and trailing `"`"
@ -371,8 +370,8 @@ impl Docs {
} else {
quote! {}
}
} else {
unreachable!()
}
_ => abort!(attr, "could not parse attributes"),
}
})
.collect()
@ -384,9 +383,8 @@ impl Docs {
.0
.iter()
.map(|attr| {
if let Meta::NameValue(MetaNameValue { lit: doc, .. }) =
attr.parse_meta().unwrap()
{
match attr.parse_meta() {
Ok(Meta::NameValue(MetaNameValue { lit: doc, .. })) => {
let mut doc_str = quote!(#doc).to_string();
// Remove the leading and trailing `"`
@ -394,8 +392,8 @@ impl Docs {
doc_str.remove(0);
doc_str
} else {
unreachable!()
}
_ => abort!(attr, "could not parse attributes"),
}
})
.intersperse("\n".to_string())
@ -633,7 +631,7 @@ fn is_option(ty: &Type) -> bool {
}
}
fn unwrap_option(ty: &Type) -> Option<Type> {
fn unwrap_option(ty: &Type) -> Type {
const STD_OPTION_MSG: &str =
"make sure you're not shadowing the `std::option::Option` type that \
is automatically imported from the standard prelude";
@ -651,37 +649,19 @@ fn unwrap_option(ty: &Type) -> Option<Type> {
{
if let [first] = &args.iter().collect::<Vec<_>>()[..] {
if let GenericArgument::Type(ty) = first {
Some(ty.clone())
} else {
return ty.clone();
}
}
}
}
}
}
abort!(
first,
ty,
"`Option` must be `std::option::Option`";
help = STD_OPTION_MSG
);
}
} else {
abort!(
first,
"`Option` must be `std::option::Option`";
help = STD_OPTION_MSG
);
}
} else {
abort!(
first,
"`Option` must be `std::option::Option`";
help = STD_OPTION_MSG
);
}
} else {
None
}
} else {
None
}
} else {
None
}
}
#[derive(Clone, Copy)]
@ -703,7 +683,7 @@ fn prop_to_doc(
|| prop_opts.contains(&PropOpt::StripOption))
&& is_option(ty)
{
unwrap_option(ty).unwrap()
unwrap_option(ty)
} else {
ty.to_owned()
};

View File

@ -9,7 +9,7 @@ use proc_macro2::TokenTree;
use quote::ToTokens;
use server::server_macro_impl;
use syn::parse_macro_input;
use syn_rsx::{parse, NodeElement};
use syn_rsx::{parse, NodeAttribute, NodeElement};
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub(crate) enum Mode {
@ -304,13 +304,10 @@ pub fn view(tokens: TokenStream) -> TokenStream {
third.clone()
}
_ => {
let error_msg = concat!(
"To create a scope class with the view! macro \
you must put a comma `,` after the value.\n",
"e.g., view!{cx, class=\"my-class\", \
<div>...</div>}"
);
panic!("{error_msg}")
abort!(
punct, "To create a scope class with the view! macro you must put a comma `,` after the value";
help = r#"e.g., view!{cx, class="my-class", <div>...</div>}"#
)
}
}
}
@ -338,7 +335,7 @@ pub fn view(tokens: TokenStream) -> TokenStream {
.into()
}
_ => {
panic!(
abort_call_site!(
"view! macro needs a context and RSX: e.g., view! {{ cx, \
<div>...</div> }}"
)
@ -372,7 +369,7 @@ pub fn template(tokens: TokenStream) -> TokenStream {
.into()
}
_ => {
panic!(
abort_call_site!(
"view! macro needs a context and RSX: e.g., view! {{ cx, \
<div>...</div> }}"
)
@ -693,8 +690,10 @@ pub fn server(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
pub fn params_derive(
input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let ast = syn::parse(input).unwrap();
params::impl_params(&ast)
match syn::parse(input) {
Ok(ast) => params::impl_params(&ast),
Err(err) => err.to_compile_error().into(),
}
}
pub(crate) fn is_component_node(node: &NodeElement) -> bool {
@ -702,3 +701,10 @@ pub(crate) fn is_component_node(node: &NodeElement) -> bool {
.to_string()
.starts_with(|c: char| c.is_ascii_uppercase())
}
pub(crate) fn attribute_value(attr: &NodeAttribute) -> &syn::Expr {
match &attr.value {
Some(value) => value.as_ref(),
None => abort!(attr.key, "attribute should have value"),
}
}

View File

@ -13,10 +13,10 @@ pub fn impl_params(ast: &syn::DeriveInput) -> proc_macro::TokenStream {
.named
.iter()
.map(|field| {
let field_name_string = &field.ident.as_ref().unwrap().to_string();
let field_name_string = &field.ident.as_ref().expect("expected named struct fields").to_string();
let ident = &field.ident;
let ty = &field.ty;
let span = field.span().unwrap();
let span = field.span();
quote_spanned! {
span.into() => #ident: <#ty>::into_param(map.get(#field_name_string).map(|n| n.as_str()), #field_name_string)?

View File

@ -61,7 +61,7 @@ pub fn server_macro_impl(
let fields = body.inputs.iter().filter(|f| !fn_arg_is_cx(f)).map(|f| {
let typed_arg = match f {
FnArg::Receiver(_) => {
panic!("cannot use receiver types in server function macro")
abort!(f, "cannot use receiver types in server function macro")
}
FnArg::Typed(t) => t,
};
@ -96,7 +96,7 @@ pub fn server_macro_impl(
let fn_args = body.inputs.iter().map(|f| {
let typed_arg = match f {
FnArg::Receiver(_) => {
panic!("cannot use receiver types in server function macro")
abort!(f, "cannot use receiver types in server function macro")
}
FnArg::Typed(t) => t,
};
@ -131,22 +131,21 @@ pub fn server_macro_impl(
let output_arrow = body.output_arrow;
let return_ty = body.return_ty;
let output_ty = if let syn::Type::Path(pat) = &return_ty {
let output_ty = 'output_ty: {
if let syn::Type::Path(pat) = &return_ty {
if pat.path.segments[0].ident == "Result" {
if let PathArguments::AngleBracketed(args) =
&pat.path.segments[0].arguments
{
&args.args[0]
} else {
panic!(
break 'output_ty &args.args[0];
}
}
}
abort!(
return_ty,
"server functions should return Result<T, ServerFnError>"
);
}
} else {
panic!("server functions should return Result<T, ServerFnError>");
}
} else {
panic!("server functions should return Result<T, ServerFnError>");
};
Ok(quote::quote! {

View File

@ -1,4 +1,4 @@
use crate::is_component_node;
use crate::{attribute_value, is_component_node};
use proc_macro2::{Ident, Span, TokenStream};
use quote::{quote, quote_spanned};
use syn::spanned::Spanned;
@ -11,21 +11,11 @@ pub(crate) fn render_template(cx: &Ident, nodes: &[Node]) -> TokenStream {
Span::call_site(),
);
if nodes.len() == 1 {
first_node_to_tokens(cx, &template_uid, &nodes[0])
} else {
panic!("template! takes a single root element.")
match nodes.first() {
Some(Node::Element(node)) => {
root_element_to_tokens(cx, &template_uid, node)
}
}
fn first_node_to_tokens(
cx: &Ident,
template_uid: &Ident,
node: &Node,
) -> TokenStream {
match node {
Node::Element(node) => root_element_to_tokens(cx, template_uid, node),
_ => panic!("template! takes a single root element."),
_ => abort!(cx, "template! takes a single root element."),
}
}
@ -205,7 +195,11 @@ fn element_to_tokens(
let mut prev_sib = prev_sib;
for (idx, child) in node.children.iter().enumerate() {
// set next sib (for any insertions)
let next_sib = next_sibling_node(&node.children, idx + 1, next_el_id);
let next_sib =
match next_sibling_node(&node.children, idx + 1, next_el_id) {
Ok(next_sib) => next_sib,
Err(err) => abort!(span, "{}", err),
};
let curr_id = child_to_tokens(
cx,
@ -239,9 +233,9 @@ fn next_sibling_node(
children: &[Node],
idx: usize,
next_el_id: &mut usize,
) -> Option<Ident> {
) -> Result<Option<Ident>, String> {
if children.len() <= idx {
None
Ok(None)
} else {
let sibling = &children[idx];
@ -250,16 +244,16 @@ fn next_sibling_node(
if is_component_node(sibling) {
next_sibling_node(children, idx + 1, next_el_id)
} else {
Some(child_ident(*next_el_id + 1, sibling.name.span()))
Ok(Some(child_ident(*next_el_id + 1, sibling.name.span())))
}
}
Node::Block(sibling) => {
Some(child_ident(*next_el_id + 1, sibling.value.span()))
Ok(Some(child_ident(*next_el_id + 1, sibling.value.span())))
}
Node::Text(sibling) => {
Some(child_ident(*next_el_id + 1, sibling.value.span()))
Ok(Some(child_ident(*next_el_id + 1, sibling.value.span())))
}
_ => panic!("expected either an element or a block"),
_ => Err("expected either an element or a block".to_string()),
}
}
}
@ -272,16 +266,9 @@ fn attr_to_tokens(
expressions: &mut Vec<TokenStream>,
) {
let name = node.key.to_string();
let name = if name.starts_with('_') {
name.replacen('_', "", 1)
} else {
name
};
let name = if name.starts_with("attr:") {
name.replacen("attr:", "", 1)
} else {
name
};
let name = name.strip_prefix("_").unwrap_or(&name);
let name = name.strip_prefix("attr:").unwrap_or(&name);
let value = match &node.value {
Some(expr) => match expr.as_ref() {
syn::Expr::Lit(expr_lit) => {
@ -300,7 +287,7 @@ fn attr_to_tokens(
// refs
if name == "ref" {
panic!("node_ref not yet supported in template! macro")
abort!(span, "node_ref not yet supported in template! macro")
}
// Event Handlers
else if name.starts_with("on:") {
@ -311,25 +298,17 @@ fn attr_to_tokens(
})
}
// Properties
else if name.starts_with("prop:") {
let name = name.replacen("prop:", "", 1);
let value = node
.value
.as_ref()
.expect("prop: blocks need values")
.as_ref();
else if let Some(name) = name.strip_prefix("prop:") {
let value = attribute_value(&node);
expressions.push(quote_spanned! {
span => leptos_dom::property(#cx, #el_id.unchecked_ref(), #name, #value.into_property(#cx))
});
}
// Classes
else if name.starts_with("class:") {
let name = name.replacen("class:", "", 1);
let value = node
.value
.as_ref()
.expect("class: attributes need values")
.as_ref();
else if let Some(name) = name.strip_prefix("class:") {
let value = attribute_value(&node);
expressions.push(quote_spanned! {
span => leptos::leptos_dom::class_helper(#el_id.unchecked_ref(), #name.into(), #value.into_class(#cx))
});
@ -429,7 +408,7 @@ fn child_to_tokens(
expressions,
navigations,
),
_ => panic!("unexpected child node type"),
_ => abort!(cx, "unexpected child node type"),
}
}

View File

@ -1,4 +1,4 @@
use crate::{is_component_node, Mode};
use crate::{attribute_value, is_component_node, Mode};
use proc_macro2::{Ident, Span, TokenStream, TokenTree};
use quote::{format_ident, quote, quote_spanned};
use syn::{spanned::Spanned, Expr, ExprLit, ExprPath, Lit};
@ -576,11 +576,7 @@ fn set_class_attribute_ssr(
} else {
name
};
let value = node
.value
.as_ref()
.expect("class: attributes need values")
.as_ref();
let value = attribute_value(node);
let span = node.key.span();
Some((span, name, value))
} else {
@ -802,23 +798,14 @@ fn attribute_to_tokens(cx: &Ident, node: &NodeAttribute) -> TokenStream {
let span = node.key.span();
let name = node.key.to_string();
if name == "ref" || name == "_ref" || name == "ref_" || name == "node_ref" {
let value = node
.value
.as_ref()
.and_then(|expr| expr_to_ident(expr))
.expect("'_ref' needs to be passed a variable name");
let value = expr_to_ident(attribute_value(node));
let node_ref = quote_spanned! { span => node_ref };
quote! {
.#node_ref(#value)
}
} else if let Some(name) = name.strip_prefix("on:") {
let handler = node
.value
.as_ref()
.expect("event listener attributes need a value")
.as_ref();
let handler = attribute_value(node);
let (name, is_force_undelegated) = parse_event(name);
let event_type = TYPED_EVENTS
@ -827,9 +814,10 @@ fn attribute_to_tokens(cx: &Ident, node: &NodeAttribute) -> TokenStream {
.copied()
.unwrap_or("Custom");
let is_custom = event_type == "Custom";
let event_type = event_type
.parse::<TokenStream>()
.expect("couldn't parse event name");
let Ok(event_type) = event_type.parse::<TokenStream>() else {
abort!(event_type, "couldn't parse event name");
};
let event_type = if is_custom {
quote! { leptos::ev::Custom::new(#name) }
@ -896,11 +884,7 @@ fn attribute_to_tokens(cx: &Ident, node: &NodeAttribute) -> TokenStream {
#on(#event_type, #handler)
}
} else if let Some(name) = name.strip_prefix("prop:") {
let value = node
.value
.as_ref()
.expect("prop: attributes need a value")
.as_ref();
let value = attribute_value(node);
let prop = match &node.key {
NodeName::Punctuated(parts) => &parts[0],
_ => unreachable!(),
@ -915,11 +899,7 @@ fn attribute_to_tokens(cx: &Ident, node: &NodeAttribute) -> TokenStream {
#prop(#name, (#cx, #[allow(unused_braces)] #value))
}
} else if let Some(name) = name.strip_prefix("class:") {
let value = node
.value
.as_ref()
.expect("class: attributes need a value")
.as_ref();
let value = attribute_value(node);
let class = match &node.key {
NodeName::Punctuated(parts) => &parts[0],
_ => unreachable!(),
@ -1012,16 +992,11 @@ pub(crate) fn component_to_tokens(
let items_to_clone = attrs
.clone()
.filter(|attr| attr.key.to_string().starts_with("clone:"))
.map(|attr| {
let ident = attr
.key
.filter_map(|attr| {
attr.key
.to_string()
.strip_prefix("clone:")
.unwrap()
.to_owned();
format_ident!("{ident}", span = attr.key.span())
.map(|ident| format_ident!("{ident}", span = attr.key.span()))
})
.collect::<Vec<_>>();
@ -1085,14 +1060,14 @@ pub(crate) fn event_from_attribute_node(
attr: &NodeAttribute,
force_undelegated: bool,
) -> (TokenStream, &Expr) {
let event_name =
attr.key.to_string().strip_prefix("on:").unwrap().to_owned();
let event_name = attr
.key
.to_string()
.strip_prefix("on:")
.expect("expected `on:` directive")
.to_owned();
let handler = attr
.value
.as_ref()
.expect("event listener attributes need a value")
.as_ref();
let handler = attribute_value(attr);
#[allow(unused_variables)]
let (name, name_undelegated) = parse_event(&event_name);
@ -1102,9 +1077,10 @@ pub(crate) fn event_from_attribute_node(
.find(|e| **e == name)
.copied()
.unwrap_or("Custom");
let event_type = event_type
.parse::<TokenStream>()
.expect("couldn't parse event name");
let Ok(event_type) = event_type.parse::<TokenStream>() else {
abort!(attr.key, "couldn't parse event name");
};
let event_type = if force_undelegated || name_undelegated {
quote! { ::leptos::leptos_dom::ev::undelegated(::leptos::leptos_dom::ev::#event_type) }