From 243164ccd02e8114bc721b0924ceb2ae1894b2dd Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 19 Dec 2025 13:34:18 +0100 Subject: [PATCH 1/2] Fix method call on enum variant templates --- askama_derive/src/integration.rs | 93 ++++++++++++++++++++++---------- testing/tests/enum.rs | 22 ++++---- 2 files changed, 79 insertions(+), 36 deletions(-) diff --git a/askama_derive/src/integration.rs b/askama_derive/src/integration.rs index 45443443..eded850a 100644 --- a/askama_derive/src/integration.rs +++ b/askama_derive/src/integration.rs @@ -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(); diff --git a/testing/tests/enum.rs b/testing/tests/enum.rs index 7db93fe5..7e601fa5 100644 --- a/testing/tests/enum.rs +++ b/testing/tests/enum.rs @@ -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 | \ - __Askama__SimpleEnum__G(\ - PhantomData<&enum::test_simple_enum::SimpleEnum>\ - )" + "&enum::test_simple_enum::_::__Askama__SimpleEnum__G | 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" ); } From 87429c892f8a74d453aaf9d124ef26e67053e4e2 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sat, 20 Dec 2025 13:35:14 +0100 Subject: [PATCH 2/2] Fix `custom_ui` test on nightly --- testing/tests/custom_ui.rs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/testing/tests/custom_ui.rs b/testing/tests/custom_ui.rs index 18bc55c9..4e0e8a7d 100644 --- a/testing/tests/custom_ui.rs +++ b/testing/tests/custom_ui.rs @@ -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}") + } } }