Skip to content

Commit 172898a

Browse files
christianrondeauBurntSushi
authored andcommitted
syntax: better errors missing repetition quantifier
This change causes a better error message to surface when a repetition quantifier is used with a missing number. Closes rust-lang#545
1 parent 9921922 commit 172898a

File tree

4 files changed

+78
-6
lines changed

4 files changed

+78
-6
lines changed

regex-syntax/src/ast/mod.rs

+10-1
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,10 @@ pub enum ErrorKind {
9595
ClassRangeLiteral,
9696
/// An opening `[` was found with no corresponding closing `]`.
9797
ClassUnclosed,
98-
/// An empty decimal number was given where one was expected.
98+
/// Note that this error variant is no longer used. Namely, a decimal
99+
/// number can only appear as a repetition quantifier. When the number
100+
/// in a repetition quantifier is empty, then it gets its own specialized
101+
/// error, `RepetitionCountDecimalEmpty`.
99102
DecimalEmpty,
100103
/// An invalid decimal number was given where one was expected.
101104
DecimalInvalid,
@@ -153,6 +156,9 @@ pub enum ErrorKind {
153156
/// The range provided in a counted repetition operator is invalid. The
154157
/// range is invalid if the start is greater than the end.
155158
RepetitionCountInvalid,
159+
/// An opening `{` was not followed by a valid decimal value.
160+
/// For example, `x{}` or `x{]}` would fail.
161+
RepetitionCountDecimalEmpty,
156162
/// An opening `{` was found with no corresponding closing `}`.
157163
RepetitionCountUnclosed,
158164
/// A repetition operator was applied to a missing sub-expression. This
@@ -307,6 +313,9 @@ impl fmt::Display for ErrorKind {
307313
write!(f, "invalid repetition count range, \
308314
the start must be <= the end")
309315
}
316+
RepetitionCountDecimalEmpty => {
317+
write!(f, "repetition quantifier expects a valid decimal")
318+
}
310319
RepetitionCountUnclosed => {
311320
write!(f, "unclosed counted repetition")
312321
}

regex-syntax/src/ast/parse.rs

+48-5
Original file line numberDiff line numberDiff line change
@@ -1113,7 +1113,11 @@ impl<'s, P: Borrow<Parser>> ParserI<'s, P> {
11131113
ast::ErrorKind::RepetitionCountUnclosed,
11141114
));
11151115
}
1116-
let count_start = self.parse_decimal()?;
1116+
let count_start = specialize_err(
1117+
self.parse_decimal(),
1118+
ast::ErrorKind::DecimalEmpty,
1119+
ast::ErrorKind::RepetitionCountDecimalEmpty,
1120+
)?;
11171121
let mut range = ast::RepetitionRange::Exactly(count_start);
11181122
if self.is_eof() {
11191123
return Err(self.error(
@@ -1129,7 +1133,11 @@ impl<'s, P: Borrow<Parser>> ParserI<'s, P> {
11291133
));
11301134
}
11311135
if self.char() != '}' {
1132-
let count_end = self.parse_decimal()?;
1136+
let count_end = specialize_err(
1137+
self.parse_decimal(),
1138+
ast::ErrorKind::DecimalEmpty,
1139+
ast::ErrorKind::RepetitionCountDecimalEmpty,
1140+
)?;
11331141
range = ast::RepetitionRange::Bounded(count_start, count_end);
11341142
} else {
11351143
range = ast::RepetitionRange::AtLeast(count_start);
@@ -2260,6 +2268,29 @@ impl<'p, 's, P: Borrow<Parser>> ast::Visitor for NestLimiter<'p, 's, P> {
22602268
}
22612269
}
22622270

2271+
/// When the result is an error, transforms the ast::ErrorKind from the source
2272+
/// Result into another one. This function is used to return clearer error
2273+
/// messages when possible.
2274+
fn specialize_err<T>(
2275+
result: Result<T>,
2276+
from: ast::ErrorKind,
2277+
to: ast::ErrorKind,
2278+
) -> Result<T> {
2279+
if let Err(e) = result {
2280+
if e.kind == from {
2281+
Err(ast::Error {
2282+
kind: to,
2283+
pattern: e.pattern,
2284+
span: e.span,
2285+
})
2286+
} else {
2287+
Err(e)
2288+
}
2289+
} else {
2290+
result
2291+
}
2292+
}
2293+
22632294
#[cfg(test)]
22642295
mod tests {
22652296
use std::ops::Range;
@@ -3143,6 +3174,18 @@ bar
31433174
span: span(4..4),
31443175
kind: ast::ErrorKind::RepetitionMissing,
31453176
});
3177+
assert_eq!(
3178+
parser(r"a{]}").parse().unwrap_err(),
3179+
TestError {
3180+
span: span(2..2),
3181+
kind: ast::ErrorKind::RepetitionCountDecimalEmpty,
3182+
});
3183+
assert_eq!(
3184+
parser(r"a{1,]}").parse().unwrap_err(),
3185+
TestError {
3186+
span: span(4..4),
3187+
kind: ast::ErrorKind::RepetitionCountDecimalEmpty,
3188+
});
31463189
assert_eq!(
31473190
parser(r"a{").parse().unwrap_err(),
31483191
TestError {
@@ -3153,13 +3196,13 @@ bar
31533196
parser(r"a{}").parse().unwrap_err(),
31543197
TestError {
31553198
span: span(2..2),
3156-
kind: ast::ErrorKind::DecimalEmpty,
3199+
kind: ast::ErrorKind::RepetitionCountDecimalEmpty,
31573200
});
31583201
assert_eq!(
31593202
parser(r"a{a").parse().unwrap_err(),
31603203
TestError {
31613204
span: span(2..2),
3162-
kind: ast::ErrorKind::DecimalEmpty,
3205+
kind: ast::ErrorKind::RepetitionCountDecimalEmpty,
31633206
});
31643207
assert_eq!(
31653208
parser(r"a{9999999999}").parse().unwrap_err(),
@@ -3177,7 +3220,7 @@ bar
31773220
parser(r"a{9,a").parse().unwrap_err(),
31783221
TestError {
31793222
span: span(4..4),
3180-
kind: ast::ErrorKind::DecimalEmpty,
3223+
kind: ast::ErrorKind::RepetitionCountDecimalEmpty,
31813224
});
31823225
assert_eq!(
31833226
parser(r"a{9,9999999999}").parse().unwrap_err(),

tests/error_messages.rs

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// See: https://github.com/rust-lang/regex/issues/545
2+
#[test]
3+
fn repetition_quantifier_expects_a_valid_decimal() {
4+
assert_panic_message(r"\\u{[^}]*}", r#"
5+
regex parse error:
6+
\\u{[^}]*}
7+
^
8+
error: repetition quantifier expects a valid decimal
9+
"#);
10+
}
11+
12+
fn assert_panic_message(regex: &str, expected_msg: &str) -> () {
13+
let result = regex_new!(regex);
14+
match result {
15+
Ok(_) => panic!("Regular expression should have panicked"),
16+
Err(regex::Error::Syntax(msg)) => assert_eq!(msg, expected_msg.trim()),
17+
_ => panic!("Unexpected error received")
18+
}
19+
}

tests/test_default.rs

+1
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ mod suffix_reverse;
6767
mod unicode;
6868
mod word_boundary;
6969
mod word_boundary_unicode;
70+
mod error_messages;
7071

7172
#[test]
7273
fn disallow_non_utf8() {

0 commit comments

Comments
 (0)