From 9e2cf6f25f775d927b67c12aba1698c5635242e3 Mon Sep 17 00:00:00 2001 From: kek kek kek Date: Tue, 1 Aug 2023 01:57:31 -0700 Subject: [PATCH] feat: Add `deprecated` attribute (#2041) * impl deprecated attribute * add note * add tests * simplify * use secondary_message --- crates/noirc_frontend/src/ast/function.rs | 2 +- .../src/hir/type_check/errors.rs | 8 +++ .../noirc_frontend/src/hir/type_check/expr.rs | 23 ++++++- crates/noirc_frontend/src/lexer/lexer.rs | 23 +++++-- crates/noirc_frontend/src/lexer/token.rs | 65 +++++++++++++------ 5 files changed, 96 insertions(+), 25 deletions(-) diff --git a/crates/noirc_frontend/src/ast/function.rs b/crates/noirc_frontend/src/ast/function.rs index de4e4f6f4d2..02af960f7a8 100644 --- a/crates/noirc_frontend/src/ast/function.rs +++ b/crates/noirc_frontend/src/ast/function.rs @@ -82,7 +82,7 @@ impl From for NoirFunction { Some(Attribute::Foreign(_)) => FunctionKind::LowLevel, Some(Attribute::Test) => FunctionKind::Normal, Some(Attribute::Oracle(_)) => FunctionKind::Oracle, - None => FunctionKind::Normal, + Some(Attribute::Deprecated(_)) | None => FunctionKind::Normal, }; NoirFunction { def: fd, kind } diff --git a/crates/noirc_frontend/src/hir/type_check/errors.rs b/crates/noirc_frontend/src/hir/type_check/errors.rs index 3c7e34b5699..4f032503f3d 100644 --- a/crates/noirc_frontend/src/hir/type_check/errors.rs +++ b/crates/noirc_frontend/src/hir/type_check/errors.rs @@ -94,6 +94,8 @@ pub enum TypeCheckError { }, #[error("Cannot infer type of expression, type annotations needed before this point")] TypeAnnotationsNeeded { span: Span }, + #[error("use of deprecated function {name}")] + CallDeprecated { name: String, note: Option, span: Span }, #[error("{0}")] ResolverError(ResolverError), } @@ -205,6 +207,12 @@ impl From for Diagnostic { Diagnostic::simple_error(message, String::new(), span) } + TypeCheckError::CallDeprecated { span, ref note, .. } => { + let primary_message = error.to_string(); + let secondary_message = note.clone().unwrap_or_default(); + + Diagnostic::simple_warning(primary_message, secondary_message, span) + } } } } diff --git a/crates/noirc_frontend/src/hir/type_check/expr.rs b/crates/noirc_frontend/src/hir/type_check/expr.rs index 8c396ea6814..b19833fb311 100644 --- a/crates/noirc_frontend/src/hir/type_check/expr.rs +++ b/crates/noirc_frontend/src/hir/type_check/expr.rs @@ -10,13 +10,32 @@ use crate::{ }, types::Type, }, - node_interner::{ExprId, FuncId}, + node_interner::{DefinitionKind, ExprId, FuncId}, + token::Attribute::Deprecated, CompTime, Shared, TypeBinding, TypeVariableKind, UnaryOp, }; use super::{errors::TypeCheckError, TypeChecker}; impl<'interner> TypeChecker<'interner> { + fn check_if_deprecated(&mut self, expr: &ExprId) { + if let HirExpression::Ident(expr::HirIdent { location, id }) = + self.interner.expression(expr) + { + if let Some(DefinitionKind::Function(func_id)) = + self.interner.try_definition(id).map(|def| &def.kind) + { + let meta = self.interner.function_meta(func_id); + if let Some(Deprecated(note)) = meta.attributes { + self.errors.push(TypeCheckError::CallDeprecated { + name: self.interner.definition_name(id).to_string(), + note, + span: location.span, + }); + } + } + } + } /// Infers a type for a given expression, and return this type. /// As a side-effect, this function will also remember this type in the NodeInterner /// for the given expr_id key. @@ -112,6 +131,8 @@ impl<'interner> TypeChecker<'interner> { } HirExpression::Index(index_expr) => self.check_index_expression(index_expr), HirExpression::Call(call_expr) => { + self.check_if_deprecated(&call_expr.func); + let function = self.check_expression(&call_expr.func); let args = vecmap(&call_expr.arguments, |arg| { let typ = self.check_expression(arg); diff --git a/crates/noirc_frontend/src/lexer/lexer.rs b/crates/noirc_frontend/src/lexer/lexer.rs index 30866be52ce..e376d85ddf0 100644 --- a/crates/noirc_frontend/src/lexer/lexer.rs +++ b/crates/noirc_frontend/src/lexer/lexer.rs @@ -244,10 +244,7 @@ impl<'a> Lexer<'a> { } self.next_char(); - let (word, start, end) = self.eat_while(None, |ch| { - (ch.is_ascii_alphabetic() || ch.is_numeric() || ch == '_' || ch == '(' || ch == ')') - && (ch != ']') - }); + let (word, start, end) = self.eat_while(None, |ch| ch != ']'); if !self.peek_char_is(']') { return Err(LexerErrorKind::UnexpectedCharacter { @@ -427,6 +424,24 @@ fn invalid_attribute() { assert!(token.is_err()); } +#[test] +fn deprecated_attribute() { + let input = r#"#[deprecated]"#; + let mut lexer = Lexer::new(input); + + let token = lexer.next().unwrap().unwrap(); + assert_eq!(token.token(), &Token::Attribute(Attribute::Deprecated(None))); +} + +#[test] +fn deprecated_attribute_with_note() { + let input = r#"#[deprecated("hello")]"#; + let mut lexer = Lexer::new(input); + + let token = lexer.next().unwrap().unwrap(); + assert_eq!(token.token(), &Token::Attribute(Attribute::Deprecated("hello".to_string().into()))); +} + #[test] fn test_custom_gate_syntax() { let input = "#[foreign(sha256)]#[foreign(blake2s)]#[builtin(sum)]"; diff --git a/crates/noirc_frontend/src/lexer/token.rs b/crates/noirc_frontend/src/lexer/token.rs index a58a9cbe249..b39d1640c57 100644 --- a/crates/noirc_frontend/src/lexer/token.rs +++ b/crates/noirc_frontend/src/lexer/token.rs @@ -322,6 +322,7 @@ pub enum Attribute { Foreign(String), Builtin(String), Oracle(String), + Deprecated(Option), Test, } @@ -332,6 +333,8 @@ impl fmt::Display for Attribute { Attribute::Builtin(ref k) => write!(f, "#[builtin({k})]"), Attribute::Oracle(ref k) => write!(f, "#[oracle({k})]"), Attribute::Test => write!(f, "#[test]"), + Attribute::Deprecated(None) => write!(f, "#[deprecated]"), + Attribute::Deprecated(Some(ref note)) => write!(f, r#"#[deprecated("{note}")]"#), } } } @@ -345,29 +348,52 @@ impl Attribute { .filter(|string_segment| !string_segment.is_empty()) .collect(); - if word_segments.len() != 2 { - if word_segments.len() == 1 && word_segments[0] == "test" { - return Ok(Token::Attribute(Attribute::Test)); - } else { - return Err(LexerErrorKind::MalformedFuncAttribute { - span, - found: word.to_owned(), - }); - } - } - - let attribute_type = word_segments[0]; - let attribute_name = word_segments[1]; + let validate = |slice: &str| { + let is_valid = slice + .chars() + .all(|ch| { + ch.is_ascii_alphabetic() + || ch.is_numeric() + || ch == '_' + || ch == '(' + || ch == ')' + }) + .then_some(()); + + is_valid.ok_or(LexerErrorKind::MalformedFuncAttribute { span, found: word.to_owned() }) + }; - let tok = match attribute_type { - "foreign" => Token::Attribute(Attribute::Foreign(attribute_name.to_string())), - "builtin" => Token::Attribute(Attribute::Builtin(attribute_name.to_string())), - "oracle" => Token::Attribute(Attribute::Oracle(attribute_name.to_string())), + let attribute = match &word_segments[..] { + ["foreign", name] => { + validate(name)?; + Attribute::Foreign(name.to_string()) + } + ["builtin", name] => { + validate(name)?; + Attribute::Builtin(name.to_string()) + } + ["oracle", name] => { + validate(name)?; + Attribute::Oracle(name.to_string()) + } + ["deprecated"] => Attribute::Deprecated(None), + ["deprecated", name] => { + if !name.starts_with('"') && !name.ends_with('"') { + return Err(LexerErrorKind::MalformedFuncAttribute { + span, + found: word.to_owned(), + }); + } + + Attribute::Deprecated(name.trim_matches('"').to_string().into()) + } + ["test"] => Attribute::Test, _ => { return Err(LexerErrorKind::MalformedFuncAttribute { span, found: word.to_owned() }) } }; - Ok(tok) + + Ok(Token::Attribute(attribute)) } pub fn builtin(self) -> Option { @@ -399,7 +425,8 @@ impl AsRef for Attribute { Attribute::Foreign(string) => string, Attribute::Builtin(string) => string, Attribute::Oracle(string) => string, - Attribute::Test => "", + Attribute::Deprecated(Some(string)) => string, + Attribute::Test | Attribute::Deprecated(None) => "", } } }