Skip to content

Commit 4c36354

Browse files
committed
Fix using the match_ignore_ascii_case macro inside another macro expansion
1 parent e31b38d commit 4c36354

File tree

5 files changed

+86
-82
lines changed

5 files changed

+86
-82
lines changed

Cargo.toml

Lines changed: 2 additions & 2 deletions
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 <[email protected]>" ]
55

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

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

macros/Cargo.toml

Lines changed: 1 addition & 1 deletion
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 <[email protected]>"]
55
description = "Procedural macros for cssparser"
66
documentation = "https://docs.rs/cssparser-macros/"

macros/lib.rs

Lines changed: 39 additions & 72 deletions
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

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,23 +33,30 @@ use std::mem::MaybeUninit;
3333
/// ```
3434
#[macro_export]
3535
macro_rules! match_ignore_ascii_case {
36-
( $input:expr, $( $match_body:tt )* ) => {
36+
( $input:expr,
37+
$(
38+
$( $pattern: pat )|+ $( if $guard: expr )? => $then: expr
39+
),+
40+
$(,)?
41+
) => {
3742
{
3843
// This dummy module works around the feature gate
3944
// `error[E0658]: procedural macros cannot be expanded to statements`
4045
// by forcing the macro to be in an item context
4146
// rather than expression/statement context,
4247
// even though the macro only expands to items.
4348
mod cssparser_internal {
44-
$crate::cssparser_internal__assert_ascii_lowercase__max_len! {
45-
match x { $( $match_body )* }
49+
$crate::cssparser_internal__match_ignore_ascii_case__support! {
50+
$( $( $pattern )+ )+
4651
}
4752
}
4853
cssparser_internal__to_lowercase!($input, cssparser_internal::MAX_LENGTH => lowercase);
4954
// "A" is a short string that we know is different for every string pattern,
5055
// since we’ve verified that none of them include ASCII upper case letters.
5156
match lowercase.unwrap_or("A") {
52-
$( $match_body )*
57+
$(
58+
$( $pattern )|+ $( if $guard )? => $then,
59+
)+
5360
}
5461
}
5562
};
@@ -91,8 +98,9 @@ macro_rules! ascii_case_insensitive_phf_map {
9198
mod cssparser_internal {
9299
use $crate::_internal__phf::{Map, phf_map};
93100
#[allow(unused)] use super::*;
94-
$crate::cssparser_internal__max_len!( $( $key )+ );
95-
$crate::cssparser_internal__phf_map!( $ValueType $( $key $value )+ );
101+
$crate::cssparser_internal__ascii_case_insensitive_phf_map__support! {
102+
$ValueType $( $key $value )+
103+
}
96104
}
97105
cssparser_internal__to_lowercase!(input, cssparser_internal::MAX_LENGTH => lowercase);
98106
lowercase.and_then(|s| cssparser_internal::MAP.get(s))

src/tests.rs

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

0 commit comments

Comments
 (0)