Skip to content

Commit 431df46

Browse files
committed
Fix using the match_ignore_ascii_case macro inside another macro expansion
1 parent 47c25ec commit 431df46

File tree

5 files changed

+85
-83
lines changed

5 files changed

+85
-83
lines changed

Cargo.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "cssparser"
3-
version = "0.26.0"
3+
version = "0.27.0"
44
authors = [ "Simon Sapin <simon.sapin@exyr.org>" ]
55

66
description = "Rust implementation of CSS Syntax Level 3"
@@ -19,7 +19,7 @@ difference = "2.0"
1919
encoding_rs = "0.8"
2020

2121
[dependencies]
22-
cssparser-macros = {path = "./macros", version = "0.4"}
22+
cssparser-macros = {path = "./macros", version = "0.5"}
2323
dtoa-short = "0.3"
2424
heapsize = {version = ">= 0.3, < 0.5", optional = true}
2525
itoa = "0.4"

macros/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "cssparser-macros"
3-
version = "0.4.0"
3+
version = "0.5.0"
44
authors = ["Simon Sapin <simon.sapin@exyr.org>"]
55
description = "Procedural macros for cssparser"
66
documentation = "https://docs.rs/cssparser-macros/"

macros/lib.rs

+39-72
Original file line numberDiff line numberDiff line change
@@ -6,103 +6,72 @@ extern crate proc_macro;
66

77
use proc_macro::TokenStream;
88

9-
/// Input: a `match` expression.
10-
///
11-
/// Output: a `MAX_LENGTH` constant with the length of the longest string pattern.
12-
///
13-
/// Panic if the arms contain non-string patterns,
14-
/// or string patterns that contains ASCII uppercase letters.
9+
/// Implementation detail of the `match_ignore_ascii_case!` macro
1510
#[allow(non_snake_case)]
1611
#[proc_macro]
17-
pub fn cssparser_internal__assert_ascii_lowercase__max_len(input: TokenStream) -> TokenStream {
18-
let expr: syn::ExprMatch = syn::parse_macro_input!(input);
19-
let strings = expr
20-
.arms
21-
.iter()
22-
.flat_map(|arm| match arm.pat {
23-
syn::Pat::Or(ref p) => p.cases.iter().collect(),
24-
ref p => vec![p],
25-
})
26-
.filter_map(|pattern| {
27-
let expr = match pattern {
28-
syn::Pat::Lit(expr) => expr,
29-
syn::Pat::Wild(_) => return None,
30-
_ => panic!("expected string or wildcard pattern, got {:?}", pattern),
31-
};
32-
match *expr.expr {
33-
syn::Expr::Lit(syn::ExprLit {
34-
lit: syn::Lit::Str(ref lit),
35-
..
36-
}) => {
37-
assert_eq!(
38-
lit.value(),
39-
lit.value().to_ascii_lowercase(),
40-
"string patterns must be given in ASCII lowercase"
41-
);
42-
Some(lit)
43-
}
44-
_ => panic!("expected string pattern, got {:?}", expr),
45-
}
46-
});
47-
max_len(strings)
48-
}
49-
50-
/// Input: string literals with no separator
51-
///
52-
/// Output: a `MAX_LENGTH` constant with the length of the longest string.
53-
#[allow(non_snake_case)]
54-
#[proc_macro]
55-
pub fn cssparser_internal__max_len(input: TokenStream) -> TokenStream {
56-
struct Input(Vec<syn::LitStr>);
12+
pub fn cssparser_internal__match_ignore_ascii_case__support(input: TokenStream) -> TokenStream {
13+
struct Input {
14+
max_length: usize,
15+
}
5716

5817
impl syn::parse::Parse for Input {
5918
fn parse(input: syn::parse::ParseStream) -> syn::parse::Result<Self> {
60-
let mut strings = Vec::new();
19+
let mut max_length = 0;
6120
while !input.is_empty() {
62-
strings.push(input.parse()?)
21+
if input.peek(syn::Token![_]) {
22+
input.parse::<syn::Token![_]>().unwrap();
23+
continue;
24+
}
25+
let lit: syn::LitStr = input.parse()?;
26+
let value = lit.value();
27+
if value.to_ascii_lowercase() != value {
28+
return Err(syn::Error::new(lit.span(), "must be ASCII-lowercase"));
29+
}
30+
max_length = max_length.max(value.len());
6331
}
64-
Ok(Self(strings))
32+
Ok(Input { max_length })
6533
}
6634
}
6735

68-
let strings: Input = syn::parse_macro_input!(input);
69-
max_len(strings.0.iter())
70-
}
71-
72-
fn max_len<'a, I: Iterator<Item = &'a syn::LitStr>>(strings: I) -> TokenStream {
73-
let max_length = strings
74-
.map(|s| s.value().len())
75-
.max()
76-
.expect("expected at least one string");
77-
quote::quote!( pub(super) const MAX_LENGTH: usize = #max_length; ).into()
36+
let Input { max_length } = syn::parse_macro_input!(input);
37+
quote::quote!(
38+
pub(super) const MAX_LENGTH: usize = #max_length;
39+
)
40+
.into()
7841
}
7942

80-
/// Input: A type, followed by pairs of string literal keys and expression values. No separator.
81-
///
82-
/// Output: a rust-phf map, with keys ASCII-lowercased:
83-
/// ```text
84-
/// static MAP: &'static ::cssparser::phf::Map<&'static str, $ValueType> = …;
85-
/// ```
43+
/// Implementation detail of the `ascii_case_insensitive_phf_map!` macro
8644
#[allow(non_snake_case)]
8745
#[proc_macro]
88-
pub fn cssparser_internal__phf_map(input: TokenStream) -> TokenStream {
46+
pub fn cssparser_internal__ascii_case_insensitive_phf_map__support(
47+
input: TokenStream,
48+
) -> TokenStream {
8949
struct Input {
9050
value_type: syn::Type,
51+
max_key_length: usize,
9152
keys: Vec<syn::LitStr>,
9253
values: Vec<syn::Expr>,
9354
}
9455

9556
impl syn::parse::Parse for Input {
9657
fn parse(input: syn::parse::ParseStream) -> syn::parse::Result<Self> {
58+
let value_type = input.parse()?;
59+
let mut max_key_length = 0;
9760
let mut keys = Vec::new();
9861
let mut values = Vec::new();
99-
let value_type = input.parse()?;
10062
while !input.is_empty() {
101-
keys.push(input.parse()?);
63+
let key: syn::LitStr = input.parse()?;
64+
let key_value = key.value();
65+
max_key_length = max_key_length.max(key_value.len());
66+
keys.push(syn::LitStr::new(
67+
&key_value.to_ascii_lowercase(),
68+
key.span(),
69+
));
10270
values.push(input.parse()?);
10371
}
10472
Ok(Input {
10573
value_type,
74+
max_key_length,
10675
keys,
10776
values,
10877
})
@@ -111,14 +80,12 @@ pub fn cssparser_internal__phf_map(input: TokenStream) -> TokenStream {
11180

11281
let Input {
11382
value_type,
83+
max_key_length,
11484
keys,
11585
values,
11686
} = syn::parse_macro_input!(input);
117-
let keys = keys
118-
.iter()
119-
.map(|s| syn::LitStr::new(&s.value().to_ascii_lowercase(), s.span()));
120-
12187
quote::quote!(
88+
pub(super) const MAX_LENGTH: usize = #max_key_length;
12289
pub(super) static MAP: Map<&'static str, #value_type> = phf_map! {
12390
#(
12491
#keys => #values,

src/macros.rs

+13-7
Original file line numberDiff line numberDiff line change
@@ -32,23 +32,28 @@ use std::mem::MaybeUninit;
3232
/// ```
3333
#[macro_export]
3434
macro_rules! match_ignore_ascii_case {
35-
( $input:expr, $( $match_body:tt )* ) => {
35+
( $input:expr,
36+
$(
37+
$( $pattern: pat )|+ $( if $guard: expr )? => $then: expr
38+
),+
39+
$(,)?
40+
) => {
3641
{
3742
// This dummy module works around the feature gate
3843
// `error[E0658]: procedural macros cannot be expanded to statements`
3944
// by forcing the macro to be in an item context
4045
// rather than expression/statement context,
4146
// even though the macro only expands to items.
4247
mod cssparser_internal {
43-
cssparser_internal__assert_ascii_lowercase__max_len! {
44-
match x { $( $match_body )* }
45-
}
48+
cssparser_internal__match_ignore_ascii_case__support!( $( $( $pattern )+ )+ );
4649
}
4750
cssparser_internal__to_lowercase!($input, cssparser_internal::MAX_LENGTH => lowercase);
4851
// "A" is a short string that we know is different for every string pattern,
4952
// since we’ve verified that none of them include ASCII upper case letters.
5053
match lowercase.unwrap_or("A") {
51-
$( $match_body )*
54+
$(
55+
$( $pattern )|+ $( if $guard )? => $then,
56+
)+
5257
}
5358
}
5459
};
@@ -90,8 +95,9 @@ macro_rules! ascii_case_insensitive_phf_map {
9095
mod cssparser_internal {
9196
use $crate::_internal__phf::{Map, phf_map};
9297
#[allow(unused)] use super::*;
93-
cssparser_internal__max_len!( $( $key )+ );
94-
cssparser_internal__phf_map!( $ValueType $( $key $value )+ );
98+
cssparser_internal__ascii_case_insensitive_phf_map__support!(
99+
$ValueType $( $key $value )+
100+
);
95101
}
96102
cssparser_internal__to_lowercase!(input, cssparser_internal::MAX_LENGTH => lowercase);
97103
lowercase.and_then(|s| cssparser_internal::MAP.get(s))

src/tests.rs

+30-1
Original file line numberDiff line numberDiff line change
@@ -940,7 +940,7 @@ impl<'i> AtRuleParser<'i> for JsonParser {
940940
"media" | "foo-with-block" => Ok(AtRuleType::WithBlock(prelude)),
941941
"charset" => {
942942
Err(input.new_error(BasicParseErrorKind::AtRuleInvalid(name.clone()).into()))
943-
}
943+
},
944944
_ => Ok(AtRuleType::WithoutBlock(prelude)),
945945
}
946946
}
@@ -1397,3 +1397,32 @@ fn utf16_columns() {
13971397
assert_eq!(parser.current_source_location().column, test.1);
13981398
}
13991399
}
1400+
1401+
#[test]
1402+
fn servo_define_css_keyword_enum() {
1403+
macro_rules! define_css_keyword_enum {
1404+
(pub enum $name:ident { $($variant:ident = $css:expr,)+ }) => {
1405+
#[derive(PartialEq, Debug)]
1406+
pub enum $name {
1407+
$($variant),+
1408+
}
1409+
1410+
impl $name {
1411+
pub fn from_ident(ident: &str) -> Result<$name, ()> {
1412+
match_ignore_ascii_case! { ident,
1413+
$($css => Ok($name::$variant),)+
1414+
_ => Err(())
1415+
}
1416+
}
1417+
}
1418+
}
1419+
}
1420+
define_css_keyword_enum! {
1421+
pub enum UserZoom {
1422+
Zoom = "zoom",
1423+
Fixed = "fixed",
1424+
}
1425+
}
1426+
1427+
assert_eq!(UserZoom::from_ident("fixed"), Ok(UserZoom::Fixed));
1428+
}

0 commit comments

Comments
 (0)