Skip to content

Commit

Permalink
chore: require safety doc comment for unsafe instead of //@safety (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
asterite authored Jan 9, 2025
1 parent 724547d commit 2abd98c
Show file tree
Hide file tree
Showing 73 changed files with 463 additions and 435 deletions.
12 changes: 6 additions & 6 deletions compiler/noirc_frontend/src/debug/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -523,8 +523,8 @@ 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
unsafe {
//@safety: debug context
{
__debug_var_assign_inner(var_id, value);
}}
Expand All @@ -536,8 +536,8 @@ pub fn build_debug_crate_file() -> String {
__debug_var_drop_oracle(var_id);
}
pub fn __debug_var_drop(var_id: u32) {
/// Safety: debug context
unsafe {
//@safety: debug context
{
__debug_var_drop_inner(var_id);
}}
Expand All @@ -549,8 +549,8 @@ pub fn build_debug_crate_file() -> String {
__debug_fn_enter_oracle(fn_id);
}
pub fn __debug_fn_enter(fn_id: u32) {
/// Safety: debug context
unsafe {
//@safety: debug context
{
__debug_fn_enter_inner(fn_id);
}}
Expand All @@ -562,8 +562,8 @@ pub fn build_debug_crate_file() -> String {
__debug_fn_exit_oracle(fn_id);
}
pub fn __debug_fn_exit(fn_id: u32) {
/// Safety: debug context
unsafe {
//@safety: debug context
{
__debug_fn_exit_inner(fn_id);
}}
Expand All @@ -575,8 +575,8 @@ 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
unsafe {
//@safety: debug context
{
__debug_dereference_assign_inner(var_id, value);
}}
Expand All @@ -603,8 +603,8 @@ pub fn build_debug_crate_file() -> String {
__debug_oracle_member_assign_{n}(var_id, value, {vars});
}}
pub fn __debug_member_assign_{n}<T, Index>(var_id: u32, value: T, {var_sig}) {{
/// Safety: debug context
unsafe {{
//@safety: debug context
__debug_inner_member_assign_{n}(var_id, value, {vars});
}}
}}
Expand Down
4 changes: 2 additions & 2 deletions compiler/noirc_frontend/src/elaborator/expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ impl<'context> Elaborator<'context> {
ExpressionKind::Comptime(comptime, _) => {
return self.elaborate_comptime_block(comptime, expr.span)
}
ExpressionKind::Unsafe(block_expression, _) => {
self.elaborate_unsafe_block(block_expression, expr.span)
ExpressionKind::Unsafe(block_expression, span) => {
self.elaborate_unsafe_block(block_expression, span)
}
ExpressionKind::Resolved(id) => return (id, self.interner.id_type(id)),
ExpressionKind::Interned(id) => {
Expand Down
22 changes: 3 additions & 19 deletions compiler/noirc_frontend/src/lexer/lexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -726,7 +726,7 @@ impl<'a> Lexer<'a> {
}

fn parse_comment(&mut self, start: u32) -> SpannedTokenResult {
let mut doc_style = match self.peek_char() {
let doc_style = match self.peek_char() {
Some('!') => {
self.next_char();
Some(DocStyle::Inner)
Expand All @@ -735,17 +735,9 @@ impl<'a> Lexer<'a> {
self.next_char();
Some(DocStyle::Outer)
}
Some('@') => Some(DocStyle::Safety),
_ => None,
};
let mut comment = self.eat_while(None, |ch| ch != '\n');
if doc_style == Some(DocStyle::Safety) {
if comment.starts_with("@safety") {
comment = comment["@safety".len()..].to_string();
} else {
doc_style = None;
}
}
let comment = self.eat_while(None, |ch| ch != '\n');

if !comment.is_ascii() {
let span = Span::from(start..self.position);
Expand All @@ -760,7 +752,7 @@ impl<'a> Lexer<'a> {
}

fn parse_block_comment(&mut self, start: u32) -> SpannedTokenResult {
let mut doc_style = match self.peek_char() {
let doc_style = match self.peek_char() {
Some('!') => {
self.next_char();
Some(DocStyle::Inner)
Expand All @@ -769,7 +761,6 @@ impl<'a> Lexer<'a> {
self.next_char();
Some(DocStyle::Outer)
}
Some('@') => Some(DocStyle::Safety),
_ => None,
};

Expand All @@ -796,13 +787,6 @@ impl<'a> Lexer<'a> {
ch => content.push(ch),
}
}
if doc_style == Some(DocStyle::Safety) {
if content.starts_with("@safety") {
content = content["@safety".len()..].to_string();
} else {
doc_style = None;
}
}
if depth == 0 {
if !content.is_ascii() {
let span = Span::from(start..self.position);
Expand Down
3 changes: 0 additions & 3 deletions compiler/noirc_frontend/src/lexer/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,6 @@ impl Display for FmtStrFragment {
pub enum DocStyle {
Outer,
Inner,
Safety,
}

#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
Expand Down Expand Up @@ -425,13 +424,11 @@ impl fmt::Display for Token {
Token::LineComment(ref s, style) => match style {
Some(DocStyle::Inner) => write!(f, "//!{s}"),
Some(DocStyle::Outer) => write!(f, "///{s}"),
Some(DocStyle::Safety) => write!(f, "//@safety{s}"),
None => write!(f, "//{s}"),
},
Token::BlockComment(ref s, style) => match style {
Some(DocStyle::Inner) => write!(f, "/*!{s}*/"),
Some(DocStyle::Outer) => write!(f, "/**{s}*/"),
Some(DocStyle::Safety) => write!(f, "/*@safety{s}*/"),
None => write!(f, "/*{s}*/"),
},
Token::Quote(ref stream) => {
Expand Down
16 changes: 13 additions & 3 deletions compiler/noirc_frontend/src/parser/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,10 @@ 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 start with a safety comment")]
#[error("Unsafe block must have a safety doc 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 @@ -283,10 +285,18 @@ impl<'a> From<&'a ParserError> for Diagnostic {
error.span,
),
ParserErrorReason::MissingSafetyComment => Diagnostic::simple_warning(
"Missing Safety Comment".into(),
"Unsafe block must start with a safety comment: //@safety".into(),
"Unsafe block must have a safety doc comment above it".into(),
"The doc 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(),
Expand Down
22 changes: 22 additions & 0 deletions compiler/noirc_frontend/src/parser/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,27 @@ pub struct Parser<'a> {
next_token: SpannedToken,
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
/// in its containing statement. For example:
///
/// ```noir
/// /// 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,
}

impl<'a> Parser<'a> {
Expand All @@ -107,6 +128,7 @@ impl<'a> Parser<'a> {
next_token: eof_spanned_token(),
current_token_span: Default::default(),
previous_token_span: Default::default(),
statement_doc_comments: None,
};
parser.read_two_first_tokens();
parser
Expand Down
83 changes: 62 additions & 21 deletions compiler/noirc_frontend/src/parser/parser/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::{
MemberAccessExpression, MethodCallExpression, Statement, TypePath, UnaryOp, UnresolvedType,
},
parser::{labels::ParsingRuleLabel, parser::parse_many::separated_by_comma, ParserErrorReason},
token::{DocStyle, Keyword, SpannedToken, Token, TokenKind},
token::{Keyword, Token, TokenKind},
};

use super::{
Expand Down Expand Up @@ -267,6 +267,21 @@ 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 {
self.push_error(
ParserErrorReason::DocCommentDoesNotDocumentAnything,
self.span_since(span_before_doc_comments),
);
}

if let Some(literal) = self.parse_literal() {
return Some(literal);
}
Expand All @@ -275,10 +290,6 @@ impl<'a> Parser<'a> {
return Some(kind);
}

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

if let Some(kind) = self.parse_path_expr(allow_constructors) {
return Some(kind);
}
Expand Down Expand Up @@ -370,26 +381,43 @@ impl<'a> Parser<'a> {
}

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

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

let next_token = self.next_token.token();
if matches!(
next_token,
Token::LineComment(_, Some(DocStyle::Safety))
| Token::BlockComment(_, Some(DocStyle::Safety))
) {
//Checks the safety comment is there, and skip it
let span = self.current_token_span;
self.eat_left_brace();
self.token = SpannedToken::new(Token::LeftBrace, span);
} else {
self.push_error(ParserErrorReason::MissingSafetyComment, self.current_token_span);
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(),
),
);
}
} 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),
);
}

let start_span = self.current_token_span;
if let Some(block) = self.parse_block() {
Some(ExpressionKind::Unsafe(block, self.span_since(start_span)))
} else {
Expand Down Expand Up @@ -971,8 +999,21 @@ mod tests {

#[test]
fn parses_unsafe_expression() {
let src = "unsafe { //@safety: test
1 }";
let src = "
/// Safety: test
unsafe { 1 }";
let expr = parse_expression_no_errors(src);
let ExpressionKind::Unsafe(block, _) = expr.kind else {
panic!("Expected unsafe expression");
};
assert_eq!(block.statements.len(), 1);
}

#[test]
fn parses_unsafe_expression_with_doc_comment() {
let src = "
/// Safety: test
unsafe { 1 }";
let expr = parse_expression_no_errors(src);
let ExpressionKind::Unsafe(block, _) = expr.kind else {
panic!("Expected unsafe expression");
Expand Down
Loading

0 comments on commit 2abd98c

Please sign in to comment.