Skip to content

Commit 86ecc24

Browse files
committed
feat: detect simple cases of empty intersections
1 parent 06ae013 commit 86ecc24

File tree

9 files changed

+48
-14
lines changed

9 files changed

+48
-14
lines changed

pomsky-lib/src/diagnose/compile_error.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ pub(crate) enum CompileErrorKind {
9696
},
9797
NestedTest,
9898
BadIntersection,
99+
EmptyIntersection,
99100
}
100101

101102
impl CompileErrorKind {
@@ -114,11 +115,6 @@ impl core::fmt::Display for CompileErrorKind {
114115
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
115116
match self {
116117
CompileErrorKind::ParseError(kind) => write!(f, "Parse error: {kind}"),
117-
CompileErrorKind::BadIntersection => write!(
118-
f,
119-
"Intersecting these expressions is not supported. Only character sets \
120-
can be intersected."
121-
),
122118
CompileErrorKind::Unsupported(feature, flavor) => match feature {
123119
Feature::SpecificUnicodeProp => write!(
124120
f,
@@ -223,6 +219,14 @@ impl core::fmt::Display for CompileErrorKind {
223219
),
224220
_ => write!(f, "This kind of lookbehind is not supported in the {flavor:?} flavor"),
225221
},
222+
CompileErrorKind::BadIntersection => write!(
223+
f,
224+
"Intersecting these expressions is not supported. Only character sets \
225+
can be intersected."
226+
),
227+
CompileErrorKind::EmptyIntersection => {
228+
write!(f, "Intersection of expressions that do not overlap")
229+
}
226230
}
227231
}
228232
}

pomsky-lib/src/diagnose/diagnostic_code.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ diagnostic_code! {
9999
UnsupportedInLookbehind = 320,
100100
LookbehindNotConstantLength = 321,
101101
BadIntersection = 322,
102+
EmptyIntersection = 323,
102103

103104
// Warning indicating something might not be supported
104105
PossiblyUnsupported = 400,
@@ -234,6 +235,7 @@ impl<'a> From<&'a CompileErrorKind> for DiagnosticCode {
234235
C::LookbehindNotConstantLength { .. } => Self::LookbehindNotConstantLength,
235236
C::NestedTest => Self::NestedTest,
236237
C::BadIntersection => Self::BadIntersection,
238+
C::EmptyIntersection => Self::EmptyIntersection,
237239
}
238240
}
239241
}

pomsky-lib/src/diagnose/diagnostic_kind.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@ impl From<&CompileErrorKind> for DiagnosticKind {
4040
| K::NameUsedMultipleTimes(_)
4141
| K::UnknownVariable { .. }
4242
| K::RelativeRefZero => DiagnosticKind::Resolve,
43-
K::EmptyClassNegated { .. } | K::IllegalNegation { .. } => DiagnosticKind::Invalid,
43+
K::EmptyClassNegated { .. } | K::IllegalNegation { .. } | K::EmptyIntersection => {
44+
DiagnosticKind::Invalid
45+
}
4446
K::CaptureInLet
4547
| K::ReferenceInLet
4648
| K::RecursiveVariable

pomsky-lib/src/exprs/char_class/char_set_item.rs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ impl RegexCompoundCharSet {
2121
self
2222
}
2323

24-
pub(crate) fn add(mut self, other: RegexCharSet) -> Regex {
24+
pub(crate) fn add(mut self, other: RegexCharSet) -> Option<Regex> {
2525
if other.negative && self.intersections.iter().all(|i| i.negative) {
2626
let mut intersections = self.intersections.into_iter();
2727
let mut char_set = intersections.next().expect("Intersection is empty");
@@ -32,13 +32,19 @@ impl RegexCompoundCharSet {
3232
if self.negative {
3333
char_set = char_set.negate();
3434
}
35-
Regex::CharSet(char_set)
36-
} else {
35+
Some(Regex::CharSet(char_set))
36+
} else if self.may_intersect(&other) {
3737
self.intersections.push(other);
38-
Regex::CompoundCharSet(self)
38+
Some(Regex::CompoundCharSet(self))
39+
} else {
40+
None
3941
}
4042
}
4143

44+
fn may_intersect(&self, other: &RegexCharSet) -> bool {
45+
self.intersections.iter().any(|set| set.may_intersect(other))
46+
}
47+
4248
pub(crate) fn codegen(&self, buf: &mut String, flavor: RegexFlavor) {
4349
if self.negative {
4450
buf.push_str("[^");
@@ -76,6 +82,10 @@ impl RegexCharSet {
7682
self
7783
}
7884

85+
pub(crate) fn may_intersect(&self, other: &Self) -> bool {
86+
self.negative || other.negative || self.set.may_intersect(&other.set)
87+
}
88+
7989
pub(crate) fn codegen(&self, buf: &mut String, flavor: RegexFlavor, inside_compound: bool) {
8090
if self.set.len() == 1 {
8191
if let Some(range) = self.set.ranges().next() {

pomsky-lib/src/exprs/intersection.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ impl Compile for Intersection {
2424
let (first_span, first) = rules.next().expect("Intersection is empty");
2525

2626
let regex = rules.try_fold(first?, |a, (right_span, b)| match as_sets(a, b?) {
27-
Ok((left, right)) => Ok(left.add(right)),
27+
Ok((left, right)) => left
28+
.add(right)
29+
.ok_or_else(|| CompileErrorKind::EmptyIntersection.at(first_span.join(right_span))),
2830
Err(kind) => Err(kind.at(first_span.join(right_span))),
2931
})?;
3032

pomsky-lib/src/unicode_set.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,12 @@ impl UnicodeSet {
177177
self.add(range);
178178
}
179179
}
180+
181+
pub(crate) fn may_intersect(&self, other: &UnicodeSet) -> bool {
182+
!self.props.is_empty()
183+
|| !other.props.is_empty()
184+
|| other.ranges.iter().any(|range| self.ranges.contains(range))
185+
}
180186
}
181187

182188
struct MaxTwoArray<T> {
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#! expect=error
2+
![s !s]
3+
-----
4+
ERROR: This negated character class matches nothing
5+
HELP: The group is empty because it contains both `space` and `!space`, which together match every code point
6+
SPAN: 1..7
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#! expect=error
2+
'a' & 'b'
3+
-----
4+
ERROR: Intersection of expressions that do not overlap
5+
SPAN: 0..9

pomsky-lib/tests/testcases/intersections/strings.txt

Lines changed: 0 additions & 3 deletions
This file was deleted.

0 commit comments

Comments
 (0)