Skip to content

Commit

Permalink
feat: require safety comments instead of safety doc comments (#7295)
Browse files Browse the repository at this point in the history
Co-authored-by: Tom French <tom@tomfren.ch>
Co-authored-by: Tom French <15848336+TomAFrench@users.noreply.github.com>
  • Loading branch information
3 people authored Feb 14, 2025
1 parent 38eeee3 commit e895feb
Show file tree
Hide file tree
Showing 84 changed files with 404 additions and 275 deletions.
10 changes: 5 additions & 5 deletions compiler/noirc_frontend/src/debug/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -523,7 +523,7 @@ pub fn build_debug_crate_file() -> String {
__debug_var_assign_oracle(var_id, value);
}
pub fn __debug_var_assign<T>(var_id: u32, value: T) {
/// Safety: debug context
// Safety: debug context
unsafe {
{
__debug_var_assign_inner(var_id, value);
Expand All @@ -536,7 +536,7 @@ pub fn build_debug_crate_file() -> String {
__debug_var_drop_oracle(var_id);
}
pub fn __debug_var_drop(var_id: u32) {
/// Safety: debug context
// Safety: debug context
unsafe {
{
__debug_var_drop_inner(var_id);
Expand All @@ -549,7 +549,7 @@ pub fn build_debug_crate_file() -> String {
__debug_fn_enter_oracle(fn_id);
}
pub fn __debug_fn_enter(fn_id: u32) {
/// Safety: debug context
// Safety: debug context
unsafe {
{
__debug_fn_enter_inner(fn_id);
Expand All @@ -562,7 +562,7 @@ pub fn build_debug_crate_file() -> String {
__debug_fn_exit_oracle(fn_id);
}
pub fn __debug_fn_exit(fn_id: u32) {
/// Safety: debug context
// Safety: debug context
unsafe {
{
__debug_fn_exit_inner(fn_id);
Expand All @@ -575,7 +575,7 @@ pub fn build_debug_crate_file() -> String {
__debug_dereference_assign_oracle(var_id, value);
}
pub fn __debug_dereference_assign<T>(var_id: u32, value: T) {
/// Safety: debug context
// Safety: debug context
unsafe {
{
__debug_dereference_assign_inner(var_id, value);
Expand Down
23 changes: 9 additions & 14 deletions compiler/noirc_frontend/src/parser/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,8 @@ pub enum ParserErrorReason {
WrongNumberOfAttributeArguments { name: String, min: usize, max: usize, found: usize },
#[error("The `deprecated` attribute expects a string argument")]
DeprecatedAttributeExpectsAStringArgument,
#[error("Unsafe block must have a safety doc comment above it")]
#[error("Unsafe block must have a safety comment above it")]
MissingSafetyComment,
#[error("Unsafe block must start with a safety comment")]
UnsafeDocCommentDoesNotStartWithSafety,
#[error("Missing parameters for function definition")]
MissingParametersForFunctionDefinition,
}
Expand Down Expand Up @@ -285,25 +283,22 @@ impl<'a> From<&'a ParserError> for Diagnostic {
error.span,
),
ParserErrorReason::MissingSafetyComment => Diagnostic::simple_warning(
"Unsafe block must have a safety doc comment above it".into(),
"The doc comment must start with the \"Safety: \" word".into(),
"Unsafe block must have a safety comment above it".into(),
"The comment must start with the \"Safety: \" word".into(),
error.span,
),
ParserErrorReason::UnsafeDocCommentDoesNotStartWithSafety => {
Diagnostic::simple_warning(
"Unsafe block must start with a safety comment".into(),
"The doc comment above this unsafe block must start with the \"Safety: \" word"
.into(),
error.span,
)
}
ParserErrorReason::MissingParametersForFunctionDefinition => {
Diagnostic::simple_error(
"Missing parameters for function definition".into(),
"Add a parameter list: `()`".into(),
error.span,
)
}
ParserErrorReason::DocCommentDoesNotDocumentAnything => {
let primary = "This doc comment doesn't document anything".to_string();
let secondary = "Consider changing it to a regular `//` comment".to_string();
Diagnostic::simple_warning(primary, secondary, error.span)
}
other => Diagnostic::simple_error(format!("{other}"), String::new(), error.span),
},
None => {
Expand All @@ -313,7 +308,7 @@ impl<'a> From<&'a ParserError> for Diagnostic {
) {
let primary = "This doc comment doesn't document anything".to_string();
let secondary = "Consider changing it to a regular `//` comment".to_string();
Diagnostic::simple_error(primary, secondary, error.span)
Diagnostic::simple_warning(primary, secondary, error.span)
} else {
let primary = error.to_string();
Diagnostic::simple_error(primary, String::new(), error.span)
Expand Down
62 changes: 39 additions & 23 deletions compiler/noirc_frontend/src/parser/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,31 +85,25 @@ pub struct Parser<'a> {
current_token_span: Span,
previous_token_span: Span,

/// The current statement's doc comments.
/// This is used to eventually know if an `unsafe { ... }` expression is documented
// We also keep track of comments that appear right before a token,
// because `unsafe { }` requires one before it.
current_token_comments: String,
next_token_comments: String,

/// The current statement's comments.
/// This is used to eventually know if an `unsafe { ... }` expression is commented
/// in its containing statement. For example:
///
/// ```noir
/// /// Safety: test
/// // Safety: test
/// let x = unsafe { call() };
/// ```
statement_doc_comments: Option<StatementDocComments>,
}

#[derive(Debug)]
pub(crate) struct StatementDocComments {
pub(crate) doc_comments: Vec<String>,
pub(crate) start_span: Span,
pub(crate) end_span: Span,

/// Were these doc comments "read" by an unsafe statement?
/// If not, these doc comments aren't documenting anything and they produce an error.
pub(crate) read: bool,
statement_comments: Option<String>,
}

impl<'a> Parser<'a> {
pub fn for_lexer(lexer: Lexer<'a>) -> Self {
Self::new(TokenStream::Lexer(lexer))
Self::new(TokenStream::Lexer(lexer.skip_comments(false)))
}

pub fn for_tokens(mut tokens: Tokens) -> Self {
Expand All @@ -129,7 +123,9 @@ impl<'a> Parser<'a> {
next_token: eof_spanned_token(),
current_token_span: Default::default(),
previous_token_span: Default::default(),
statement_doc_comments: None,
current_token_comments: String::new(),
next_token_comments: String::new(),
statement_comments: None,
};
parser.read_two_first_tokens();
parser
Expand Down Expand Up @@ -177,25 +173,45 @@ impl<'a> Parser<'a> {
/// Bumps this parser by one token. Returns the token that was previously the "current" token.
fn bump(&mut self) -> SpannedToken {
self.previous_token_span = self.current_token_span;
let next_next_token = self.read_token_internal();
let (next_next_token, next_next_token_comments) = self.read_token_internal();
let next_token = std::mem::replace(&mut self.next_token, next_next_token);
let token = std::mem::replace(&mut self.token, next_token);

let next_comments =
std::mem::replace(&mut self.next_token_comments, next_next_token_comments);
let _ = std::mem::replace(&mut self.current_token_comments, next_comments);

self.current_token_span = self.token.to_span();
token
}

fn read_two_first_tokens(&mut self) {
self.token = self.read_token_internal();
let (token, comments) = self.read_token_internal();
self.token = token;
self.current_token_comments = comments;
self.current_token_span = self.token.to_span();
self.next_token = self.read_token_internal();

let (token, comments) = self.read_token_internal();
self.next_token = token;
self.next_token_comments = comments;
}

fn read_token_internal(&mut self) -> SpannedToken {
fn read_token_internal(&mut self) -> (SpannedToken, String) {
let mut last_comments = String::new();

loop {
match self.tokens.next() {
Some(Ok(token)) => return token,
Some(Ok(token)) => match token.token() {
Token::LineComment(comment, None) | Token::BlockComment(comment, None) => {
last_comments.push_str(comment);
continue;
}
_ => {
return (token, last_comments);
}
},
Some(Err(lexer_error)) => self.errors.push(lexer_error.into()),
None => return eof_spanned_token(),
None => return (eof_spanned_token(), last_comments),
}
}
}
Expand Down
52 changes: 17 additions & 35 deletions compiler/noirc_frontend/src/parser/parser/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -269,19 +269,17 @@ impl<'a> Parser<'a> {
fn parse_atom_kind(&mut self, allow_constructors: bool) -> Option<ExpressionKind> {
let span_before_doc_comments = self.current_token_span;
let doc_comments = self.parse_outer_doc_comments();
let has_doc_comments = !doc_comments.is_empty();

if let Some(kind) = self.parse_unsafe_expr(&doc_comments, span_before_doc_comments) {
return Some(kind);
}

if has_doc_comments {
if !doc_comments.is_empty() {
self.push_error(
ParserErrorReason::DocCommentDoesNotDocumentAnything,
self.span_since(span_before_doc_comments),
span_before_doc_comments,
);
}

if let Some(kind) = self.parse_unsafe_expr() {
return Some(kind);
}

if let Some(literal) = self.parse_literal() {
return Some(literal);
}
Expand Down Expand Up @@ -387,41 +385,23 @@ impl<'a> Parser<'a> {
}

/// UnsafeExpression = 'unsafe' Block
fn parse_unsafe_expr(
&mut self,
doc_comments: &[String],
span_before_doc_comments: Span,
) -> Option<ExpressionKind> {
fn parse_unsafe_expr(&mut self) -> Option<ExpressionKind> {
let start_span = self.current_token_span;

if !self.eat_keyword(Keyword::Unsafe) {
return None;
}

if doc_comments.is_empty() {
if let Some(statement_doc_comments) = &mut self.statement_doc_comments {
statement_doc_comments.read = true;

let doc_comments = &statement_doc_comments.doc_comments;
let span_before_doc_comments = statement_doc_comments.start_span;
let span_after_doc_comments = statement_doc_comments.end_span;

if !doc_comments[0].trim().to_lowercase().starts_with("safety:") {
self.push_error(
ParserErrorReason::UnsafeDocCommentDoesNotStartWithSafety,
Span::from(
span_before_doc_comments.start()..span_after_doc_comments.start(),
),
);
if self.current_token_comments.is_empty() {
if let Some(statement_comments) = &mut self.statement_comments {
if !statement_comments.trim().to_lowercase().starts_with("safety:") {
self.push_error(ParserErrorReason::MissingSafetyComment, start_span);
}
} else {
self.push_error(ParserErrorReason::MissingSafetyComment, start_span);
}
} else if !doc_comments[0].trim().to_lowercase().starts_with("safety:") {
self.push_error(
ParserErrorReason::UnsafeDocCommentDoesNotStartWithSafety,
self.span_since(span_before_doc_comments),
);
} else if !self.current_token_comments.trim().to_lowercase().starts_with("safety:") {
self.push_error(ParserErrorReason::MissingSafetyComment, start_span);
}

if let Some(block) = self.parse_block() {
Expand Down Expand Up @@ -1097,7 +1077,7 @@ mod tests {
#[test]
fn parses_unsafe_expression() {
let src = "
/// Safety: test
// Safety: test
unsafe { 1 }";
let expr = parse_expression_no_errors(src);
let ExpressionKind::Unsafe(block, _) = expr.kind else {
Expand All @@ -1111,7 +1091,9 @@ mod tests {
let src = "
/// Safety: test
unsafe { 1 }";
let expr = parse_expression_no_errors(src);

let mut parser = Parser::for_str(src);
let expr = parser.parse_expression().unwrap();
let ExpressionKind::Unsafe(block, _) = expr.kind else {
panic!("Expected unsafe expression");
};
Expand Down
2 changes: 1 addition & 1 deletion compiler/noirc_frontend/src/parser/parser/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,6 @@ mod tests {
let (module, errors) = parse_program(&src);
assert_eq!(module.items.len(), 1);
let error = get_single_error(&errors, span);
assert!(error.to_string().contains("Documentation comment does not document anything"));
assert!(error.to_string().contains("This doc comment doesn't document anything"));
}
}
Loading

1 comment on commit e895feb

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark 'Compilation Time'.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.20.

Benchmark suite Current: e895feb Previous: 38eeee3 Ratio
rollup-base-public 7.138 s 5.33 s 1.34

This comment was automatically generated by workflow using github-action-benchmark.

CC: @TomAFrench

Please sign in to comment.