Skip to content

Commit 9740f54

Browse files
chore(parser)!: deprecate constrain keyword for assert (#1286)
* chore(parser)!: deprecate constrain * chore(parser): typo in error * add shorter message * chore(parser): constrain tests expect deprecated * chore(parser): rm type annotation --------- Co-authored-by: Kevaundray Wedderburn <kevtheappdev@gmail.com>
1 parent 7bac22b commit 9740f54

File tree

5 files changed

+69
-36
lines changed

5 files changed

+69
-36
lines changed

crates/noirc_frontend/src/ast/mod.rs

+7-3
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@ use noirc_errors::Span;
1616
pub use statement::*;
1717
pub use structure::*;
1818

19-
use crate::{parser::ParserError, token::IntType, BinaryTypeOperator, CompTime};
19+
use crate::{
20+
parser::{ParserError, ParserErrorReason},
21+
token::IntType,
22+
BinaryTypeOperator, CompTime,
23+
};
2024
use iter_extended::vecmap;
2125

2226
/// The parser parses types as 'UnresolvedType's which
@@ -152,9 +156,9 @@ impl UnresolvedTypeExpression {
152156
expr: Expression,
153157
span: Span,
154158
) -> Result<UnresolvedTypeExpression, ParserError> {
155-
Self::from_expr_helper(expr).map_err(|err| {
159+
Self::from_expr_helper(expr).map_err(|err_expr| {
156160
ParserError::with_reason(
157-
format!("Expression is invalid in an array-length type: '{err}'. Only unsigned integer constants, globals, generics, +, -, *, /, and % may be used in this context."),
161+
ParserErrorReason::InvalidArrayLengthExpression(err_expr),
158162
span,
159163
)
160164
})

crates/noirc_frontend/src/ast/statement.rs

+9-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::fmt::Display;
22

33
use crate::lexer::token::SpannedToken;
4-
use crate::parser::ParserError;
4+
use crate::parser::{ParserError, ParserErrorReason};
55
use crate::token::Token;
66
use crate::{Expression, ExpressionKind, IndexExpression, MemberAccessExpression, UnresolvedType};
77
use iter_extended::vecmap;
@@ -59,8 +59,10 @@ impl Statement {
5959
| Statement::Error => {
6060
// To match rust, statements always require a semicolon, even at the end of a block
6161
if semi.is_none() {
62-
let reason = "Expected a ; separating these two statements".to_string();
63-
emit_error(ParserError::with_reason(reason, span));
62+
emit_error(ParserError::with_reason(
63+
ParserErrorReason::MissingSeparatingSemi,
64+
span,
65+
));
6466
}
6567
self
6668
}
@@ -83,8 +85,10 @@ impl Statement {
8385
// for unneeded expressions like { 1 + 2; 3 }
8486
(_, Some(_), false) => Statement::Expression(expr),
8587
(_, None, false) => {
86-
let reason = "Expected a ; separating these two statements".to_string();
87-
emit_error(ParserError::with_reason(reason, span));
88+
emit_error(ParserError::with_reason(
89+
ParserErrorReason::MissingSeparatingSemi,
90+
span,
91+
));
8892
Statement::Expression(expr)
8993
}
9094

crates/noirc_frontend/src/parser/errors.rs

+31-14
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,33 @@
11
use std::collections::BTreeSet;
22

33
use crate::lexer::token::Token;
4-
use crate::BinaryOp;
4+
use crate::Expression;
5+
use thiserror::Error;
56

67
use iter_extended::vecmap;
78
use noirc_errors::CustomDiagnostic as Diagnostic;
89
use noirc_errors::Span;
910

11+
#[derive(Debug, Clone, PartialEq, Eq, Error)]
12+
pub enum ParserErrorReason {
13+
#[error("Arrays must have at least one element")]
14+
ZeroSizedArray,
15+
#[error("Unexpected '{0}', expected a field name")]
16+
ExpectedFieldName(Token),
17+
#[error("Expected a ; separating these two statements")]
18+
MissingSeparatingSemi,
19+
#[error("constrain keyword is deprecated")]
20+
ConstrainDeprecated,
21+
#[error("Expression is invalid in an array-length type: '{0}'. Only unsigned integer constants, globals, generics, +, -, *, /, and % may be used in this context.")]
22+
InvalidArrayLengthExpression(Expression),
23+
}
24+
1025
#[derive(Debug, Clone, PartialEq, Eq)]
1126
pub struct ParserError {
1227
expected_tokens: BTreeSet<Token>,
1328
expected_labels: BTreeSet<String>,
1429
found: Token,
15-
reason: Option<String>,
30+
reason: Option<ParserErrorReason>,
1631
span: Span,
1732
}
1833

@@ -39,21 +54,11 @@ impl ParserError {
3954
error
4055
}
4156

42-
pub fn with_reason(reason: String, span: Span) -> ParserError {
57+
pub fn with_reason(reason: ParserErrorReason, span: Span) -> ParserError {
4358
let mut error = ParserError::empty(Token::EOF, span);
4459
error.reason = Some(reason);
4560
error
4661
}
47-
48-
pub fn invalid_constrain_operator(operator: BinaryOp) -> ParserError {
49-
let message = format!(
50-
"Cannot use the {} operator in a constraint statement.",
51-
operator.contents.as_string()
52-
);
53-
let mut error = ParserError::empty(operator.contents.as_token(), operator.span());
54-
error.reason = Some(message);
55-
error
56-
}
5762
}
5863

5964
impl std::fmt::Display for ParserError {
@@ -84,7 +89,19 @@ impl std::fmt::Display for ParserError {
8489
impl From<ParserError> for Diagnostic {
8590
fn from(error: ParserError) -> Diagnostic {
8691
match &error.reason {
87-
Some(reason) => Diagnostic::simple_error(reason.clone(), String::new(), error.span),
92+
Some(reason) => {
93+
match reason {
94+
ParserErrorReason::ConstrainDeprecated => Diagnostic::simple_warning(
95+
"Use of deprecated keyword 'constrain'".into(),
96+
"The 'constrain' keyword has been deprecated. Please use the 'assert' function instead.".into(),
97+
error.span,
98+
),
99+
other => {
100+
101+
Diagnostic::simple_error(format!("{other}"), String::new(), error.span)
102+
}
103+
}
104+
}
88105
None => {
89106
let primary = error.to_string();
90107
Diagnostic::simple_error(primary, String::new(), error.span)

crates/noirc_frontend/src/parser/mod.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ use acvm::FieldElement;
2424
use chumsky::prelude::*;
2525
use chumsky::primitive::Container;
2626
pub use errors::ParserError;
27+
pub use errors::ParserErrorReason;
2728
use noirc_errors::Span;
2829
pub use parser::parse_program;
2930

@@ -176,7 +177,7 @@ where
176177
.try_map(move |peek, span| {
177178
if too_far.get_iter().any(|t| t == peek) {
178179
// This error will never be shown to the user
179-
Err(ParserError::with_reason(String::new(), span))
180+
Err(ParserError::empty(Token::EOF, span))
180181
} else {
181182
Ok(Recoverable::error(span))
182183
}

crates/noirc_frontend/src/parser/parser.rs

+20-13
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
use super::{
2727
foldl_with_span, parameter_name_recovery, parameter_recovery, parenthesized, then_commit,
2828
then_commit_ignore, top_level_statement_recovery, ExprParser, ForRange, NoirParser,
29-
ParsedModule, ParserError, Precedence, SubModule, TopLevelStatement,
29+
ParsedModule, ParserError, ParserErrorReason, Precedence, SubModule, TopLevelStatement,
3030
};
3131
use crate::ast::{Expression, ExpressionKind, LetStatement, Statement, UnresolvedType};
3232
use crate::lexer::Lexer;
@@ -448,6 +448,10 @@ where
448448
{
449449
ignore_then_commit(keyword(Keyword::Constrain).labelled("statement"), expr_parser)
450450
.map(|expr| Statement::Constrain(ConstrainStatement(expr)))
451+
.validate(|expr, span, emit| {
452+
emit(ParserError::with_reason(ParserErrorReason::ConstrainDeprecated, span));
453+
expr
454+
})
451455
}
452456

453457
fn assertion<'a, P>(expr_parser: P) -> impl NoirParser<Statement> + 'a
@@ -877,10 +881,7 @@ where
877881
.delimited_by(just(Token::LeftBracket), just(Token::RightBracket))
878882
.validate(|elements, span, emit| {
879883
if elements.is_empty() {
880-
emit(ParserError::with_reason(
881-
"Arrays must have at least one element".to_owned(),
882-
span,
883-
));
884+
emit(ParserError::with_reason(ParserErrorReason::ZeroSizedArray, span));
884885
}
885886
ExpressionKind::array(elements)
886887
})
@@ -966,8 +967,7 @@ fn field_name() -> impl NoirParser<Ident> {
966967
ident().or(token_kind(TokenKind::Literal).validate(|token, span, emit| match token {
967968
Token::Int(_) => Ident::from(Spanned::from(span, token.to_string())),
968969
other => {
969-
let reason = format!("Unexpected '{other}', expected a field name");
970-
emit(ParserError::with_reason(reason, span));
970+
emit(ParserError::with_reason(ParserErrorReason::ExpectedFieldName(other), span));
971971
Ident::error(span)
972972
}
973973
}))
@@ -1196,10 +1196,12 @@ mod test {
11961196
);
11971197
}
11981198

1199-
/// This is the standard way to declare a constrain statement
1199+
/// Deprecated constrain usage test
12001200
#[test]
12011201
fn parse_constrain() {
1202-
parse_with(constrain(expression()), "constrain x == y").unwrap();
1202+
let errors = parse_with(constrain(expression()), "constrain x == y").unwrap_err();
1203+
assert_eq!(errors.len(), 1);
1204+
assert!(format!("{}", errors.first().unwrap()).contains("deprecated"));
12031205

12041206
// Currently we disallow constrain statements where the outer infix operator
12051207
// produces a value. This would require an implicit `==` which
@@ -1217,7 +1219,9 @@ mod test {
12171219

12181220
for operator in disallowed_operators {
12191221
let src = format!("constrain x {} y;", operator.as_string());
1220-
parse_with(constrain(expression()), &src).unwrap_err();
1222+
let errors = parse_with(constrain(expression()), &src).unwrap_err();
1223+
assert_eq!(errors.len(), 2);
1224+
assert!(format!("{}", errors.first().unwrap()).contains("deprecated"));
12211225
}
12221226

12231227
// These are general cases which should always work.
@@ -1226,7 +1230,7 @@ mod test {
12261230
// The first (inner) `==` is a predicate which returns 0/1
12271231
// The outer layer is an infix `==` which is
12281232
// associated with the Constrain statement
1229-
parse_all(
1233+
let errors = parse_all_failing(
12301234
constrain(expression()),
12311235
vec![
12321236
"constrain ((x + y) == k) + z == y",
@@ -1236,8 +1240,11 @@ mod test {
12361240
"constrain x + x ^ x == y | m",
12371241
],
12381242
);
1243+
assert_eq!(errors.len(), 5);
1244+
assert!(errors.iter().all(|err| { format!("{}", err).contains("deprecated") }));
12391245
}
12401246

1247+
/// This is the standard way to declare an assert statement
12411248
#[test]
12421249
fn parse_assert() {
12431250
parse_with(assertion(expression()), "assert(x == y)").unwrap();
@@ -1533,9 +1540,9 @@ mod test {
15331540
("let = ", 2, "let $error: unspecified = Error"),
15341541
("let", 3, "let $error: unspecified = Error"),
15351542
("foo = one two three", 1, "foo = plain::one"),
1536-
("constrain", 1, "constrain Error"),
1543+
("constrain", 2, "constrain Error"),
15371544
("assert", 1, "constrain Error"),
1538-
("constrain x ==", 1, "constrain (plain::x == Error)"),
1545+
("constrain x ==", 2, "constrain (plain::x == Error)"),
15391546
("assert(x ==)", 1, "constrain (plain::x == Error)"),
15401547
];
15411548

0 commit comments

Comments
 (0)