Merge pull request #635 from GuillaumeGomez/method-call-enum-variants

Fix method call on enum variant templates
This commit is contained in:
Guillaume Gomez 2025-12-21 00:18:42 +01:00 committed by GitHub
commit 4e85c108df
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 85 additions and 45 deletions

View File

@ -1,5 +1,6 @@
use std::fmt::Display;
use std::mem::take;
use std::str::FromStr;
use parser::PathComponent;
use proc_macro2::{Literal, TokenStream, TokenTree};
@ -331,8 +332,8 @@ pub(crate) fn build_template_enum(
continue;
};
let var_ast = type_for_enum_variant(enum_ast, &generics, var);
quote_into!(buf, span, { #var_ast });
let (var_ast, deref_impl) = type_for_enum_variant(enum_ast, &generics, var);
quote_into!(buf, span, { #var_ast #deref_impl });
// not inherited: template, meta_docs, block, print
if let Some(enum_args) = &mut enum_args {
@ -467,28 +468,42 @@ fn type_for_enum_variant(
enum_ast: &DeriveInput,
enum_generics: &Generics,
var: &Variant,
) -> DeriveInput {
) -> (DeriveInput, TokenStream) {
let enum_id = &enum_ast.ident;
let (_, ty_generics, _) = enum_ast.generics.split_for_impl();
let lt = enum_generics.params.first().unwrap();
let (_, ty_generics, _) = enum_ast.generics.split_for_impl();
let mut generics = enum_ast.generics.clone();
generics.params.insert(0, lt.clone());
let id = &var.ident;
let span = id.span();
let id = Ident::new(&format!("__Askama__{enum_id}__{id}"), span);
let phantom: Type = parse_quote! {
askama::helpers::core::marker::PhantomData < &#lt #enum_id #ty_generics >
let field: Type = parse_quote! {
&#lt #enum_id #ty_generics
};
let fields = match &var.fields {
let (impl_generics, ty_generics, _) = generics.split_for_impl();
let (fields, deref_impl) = match &var.fields {
Fields::Named(fields) => {
let mut fields = fields.clone();
for f in fields.named.iter_mut() {
let ty = &f.ty;
f.ty = parse_quote!(&#lt #ty);
}
let id = Ident::new(&format!("__Askama__{enum_id}__phantom"), span);
fields.named.push(parse_quote!(#id: #phantom));
Fields::Named(fields)
let field_name = Ident::new(&format!("__Askama__{enum_id}__phantom"), span);
fields.named.push(parse_quote!(#field_name: #field));
let deref_impl = quote_spanned! {
span=>
impl #impl_generics askama::helpers::core::ops::Deref for #id #ty_generics {
type Target = #field;
fn deref(&self) -> &Self::Target {
&self.#field_name
}
}
};
(Fields::Named(fields), deref_impl)
}
Fields::Unnamed(fields) => {
let mut fields = fields.clone();
@ -496,10 +511,34 @@ fn type_for_enum_variant(
let ty = &f.ty;
f.ty = parse_quote!(&#lt #ty);
}
fields.unnamed.push(parse_quote!(#phantom));
Fields::Unnamed(fields)
fields.unnamed.push(parse_quote!(#field));
let idx = TokenStream::from_str(&format!("self.{}", fields.unnamed.len() - 1)).unwrap();
let deref_impl = quote_spanned! {
span=>
impl #impl_generics askama::helpers::core::ops::Deref for #id #ty_generics {
type Target = #field;
fn deref(&self) -> &Self::Target {
&#idx
}
}
};
(Fields::Unnamed(fields), deref_impl)
}
Fields::Unit => {
let fields = Fields::Unnamed(parse_quote!((#field)));
let deref_impl = quote_spanned! {
span=>
impl #impl_generics askama::helpers::core::ops::Deref for #id #ty_generics {
type Target = #field;
fn deref(&self) -> &Self::Target {
&self.0
}
}
};
(fields, deref_impl)
}
Fields::Unit => Fields::Unnamed(parse_quote!((#phantom))),
};
let semicolon = match &var.fields {
Fields::Named(_) => None,
@ -507,15 +546,17 @@ fn type_for_enum_variant(
};
let span = enum_ast.span().resolved_at(proc_macro2::Span::call_site());
syn::parse_quote_spanned! {
span=>
#[askama::helpers::core::prelude::rust_2021::derive(
askama::helpers::core::prelude::rust_2021::Clone,
askama::helpers::core::prelude::rust_2021::Copy,
askama::helpers::core::prelude::rust_2021::Debug
)]
struct #id #enum_generics #fields #semicolon
}
(
syn::parse_quote_spanned! {
span=>
#[askama::helpers::core::prelude::rust_2021::derive(
askama::helpers::core::prelude::rust_2021::Clone,
askama::helpers::core::prelude::rust_2021::Copy,
)]
struct #id #enum_generics #fields #semicolon
},
deref_impl,
)
}
/// Generates a `match` arm for an `enum` variant, that calls `<_ as EnumVariantTemplate>::render_into()`
@ -571,7 +612,7 @@ fn variant_as_arm(
Fields::Unnamed(_) | Fields::Unit => unreachable!(),
};
this.extend(quote_spanned!(
span => #phantom: askama::helpers::core::marker::PhantomData {},
span => #phantom: &self,
));
}
@ -584,13 +625,11 @@ fn variant_as_arm(
this.extend(quote_spanned!(span => #idx: #arg,));
}
let idx = syn::LitInt::new(&format!("{}", fields.unnamed.len()), span);
this.extend(
quote_spanned!(span => #idx: askama::helpers::core::marker::PhantomData {},),
);
this.extend(quote_spanned!(span => #idx: &self,));
}
Fields::Unit => {
this.extend(quote_spanned!(span => 0: askama::helpers::core::marker::PhantomData {},));
this.extend(quote_spanned!(span => 0: &self,));
}
};
let var_writer = crate::var_writer();

View File

@ -7,7 +7,7 @@ use std::process::Command;
#[test]
fn test_custom_ui() {
if !cfg!(unix) {
if !cfg!(target_os = "linux") {
return;
}
let Ok(cargo_home) = std::env::var("CARGO_MANIFEST_DIR") else {
@ -58,16 +58,10 @@ path = "main.rs"
)
.unwrap();
let mut nb_run_tests = 0;
for entry in read_dir(custom_ui_folder).unwrap() {
for entry in read_dir(&custom_ui_folder).unwrap() {
let entry = entry.unwrap();
let test_path = entry.path();
if !test_path.is_dir() && test_path.extension() == Some(OsStr::new("rs")) {
if nb_run_tests == 0 {
// To prevent having build logs in tests output, we run the build a first time.
run_cargo(&test_dir);
}
nb_run_tests += 1;
print!("> {}...", test_path.file_name().unwrap().display());
if !run_test(bless, &test_path, &test_dir) {
*errors += 1;
@ -142,7 +136,10 @@ fn run_cargo(test_dir: &Path) -> String {
match (start, end) {
(Some(start), None) => stderr[start..].to_string(),
(Some(start), Some(end)) => stderr[start..end].to_string(),
_ => panic!("didn't find `askama_test` start line, full output:\n{stderr}"),
_ => {
let stdout = String::from_utf8_lossy(&out.stdout);
panic!("didn't find `askama_test` start line, stderr:\n{stderr}\n\nstdout:\n{stdout}")
}
}
}

View File

@ -102,7 +102,7 @@ fn test_simple_enum() {
#[derive(Template, Debug)]
#[template(
ext = "txt",
source = "{{ self::type_name_of_val(self) }} | {{ self|fmt(\"{:?}\") }}"
source = "{{ self::type_name_of_val(self) }} | {{ self.bar() }}"
)]
enum SimpleEnum<'a, B: Display + Debug> {
#[template(source = "A")]
@ -125,6 +125,16 @@ fn test_simple_enum() {
G,
}
impl<'a, B: Display + Debug> SimpleEnum<'a, B> {
fn bar(&self) -> &str {
match self {
Self::F => "F",
Self::G => "G",
_ => "",
}
}
}
let tmpl: SimpleEnum<'_, X> = SimpleEnum::A;
assert_eq!(tmpl.render().unwrap(), "A");
@ -154,15 +164,9 @@ fn test_simple_enum() {
let tmpl: SimpleEnum<'_, X> = SimpleEnum::G;
assert_matches!(
tmpl.render().unwrap().as_str(),
"&enum::test_simple_enum::_::__Askama__SimpleEnum__G<enum::X> | \
__Askama__SimpleEnum__G(\
PhantomData<&enum::test_simple_enum::SimpleEnum<enum::X>>\
)"
"&enum::test_simple_enum::_::__Askama__SimpleEnum__G<enum::X> | G"
// output since rustc 1.91.0-nightly 2025-08-17
| "&enum::test_simple_enum::_::__Askama__SimpleEnum__G<'_, '_, enum::X> | \
__Askama__SimpleEnum__G(\
PhantomData<&enum::test_simple_enum::SimpleEnum<'_, enum::X>>\
)"
| "&enum::test_simple_enum::_::__Askama__SimpleEnum__G<'_, '_, enum::X> | G"
);
}