diff --git a/src/lexer/mod.rs b/src/lexer/mod.rs index 699e90b..dc04382 100644 --- a/src/lexer/mod.rs +++ b/src/lexer/mod.rs @@ -800,6 +800,10 @@ impl<'a> Lexer<'a> { fn lex_symbol(&mut self, start: usize) -> Result<(), LexError> { let c = self.peek().unwrap(); + if c == b'&' && self.peek2() == Some(b'&') { + self.pos += 2; + return Err(self.err(start, "use `and` instead of `&&`")); + } // Two-character operators first. if c == b'|' && self.peek2() == Some(b'>') { self.pos += 2; diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 4cbc229..d226ed1 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -1744,6 +1744,9 @@ impl Parser { } Tok::LBrace => return self.parse_record(start), Tok::LBracket => return self.parse_list(start), + Tok::Bar if matches!(self.peek2(), Tok::Bar) => { + return Err(self.error("use `or` instead of `||`")); + } _ => return Err(self.error("expected an expression")), }; Ok(self.mk(start, kind)) diff --git a/tests/roundtrip.rs b/tests/roundtrip.rs index 8f0464e..187dc41 100644 --- a/tests/roundtrip.rs +++ b/tests/roundtrip.rs @@ -888,3 +888,18 @@ fn an_active_pattern_is_top_level_only() { err.message() ); } + +#[test] +fn suggests_keyword_operators_for_symbolic_logical_operator_typos() { + for (source, expected) in [ + ("let ok = ready && valid", "use `and` instead of `&&`"), + ("let ok = ready || valid", "use `or` instead of `||`"), + ] { + let err = parse(source).unwrap_err(); + assert!( + err.message().contains(expected), + "{source} -> {}", + err.message() + ); + } +}