diff --git a/Cargo.lock b/Cargo.lock index 53ab1f8130e..8e304e3c92c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2497,6 +2497,50 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "phf" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +dependencies = [ + "phf_macros", + "phf_shared", + "proc-macro-hack", +] + +[[package]] +name = "phf_generator" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +dependencies = [ + "phf_shared", + "rand 0.8.5", +] + +[[package]] +name = "phf_macros" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project" version = "1.0.10" @@ -3395,6 +3439,12 @@ dependencies = [ "event-listener", ] +[[package]] +name = "siphasher" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" + [[package]] name = "sized-chunks" version = "0.6.5" @@ -3681,6 +3731,7 @@ dependencies = [ "extension-trait", "num-bigint", "num-traits", + "phf", "sway-types", "thiserror", "unicode-xid", diff --git a/sway-core/src/semantic_analysis/ast_node/declaration/variable.rs b/sway-core/src/semantic_analysis/ast_node/declaration/variable.rs index 43017159b81..07ca16e9ab5 100644 --- a/sway-core/src/semantic_analysis/ast_node/declaration/variable.rs +++ b/sway-core/src/semantic_analysis/ast_node/declaration/variable.rs @@ -82,7 +82,7 @@ pub fn check_if_name_is_invalid(name: &Ident) -> CompileResult<()> { INVALID_NAMES .iter() .find_map(|x| { - if *x == name.as_str() { + if !name.is_raw_ident() && *x == name.as_str() { Some(err( vec![], [CompileError::InvalidVariableName { name: name.clone() }].to_vec(), diff --git a/sway-lib-std/src/lib.sw b/sway-lib-std/src/lib.sw index e3b9be440d2..2f9e59b46a6 100644 --- a/sway-lib-std/src/lib.sw +++ b/sway-lib-std/src/lib.sw @@ -12,7 +12,7 @@ dep constants; dep contract_id; dep context; dep hash; -dep storage; +dep r#storage; dep b512; dep address; dep identity; diff --git a/sway-lib-std/src/storage.sw b/sway-lib-std/src/storage.sw index b7cca103729..f48b71534c3 100644 --- a/sway-lib-std/src/storage.sw +++ b/sway-lib-std/src/storage.sw @@ -1,4 +1,4 @@ -library storage; +library r#storage; use ::hash::sha256; use ::context::registers::stack_ptr; diff --git a/sway-parse/Cargo.toml b/sway-parse/Cargo.toml index 0c867c4489a..05712406532 100644 --- a/sway-parse/Cargo.toml +++ b/sway-parse/Cargo.toml @@ -15,4 +15,4 @@ num-traits = "0.2.14" sway-types = { version = "0.16.1", path = "../sway-types" } thiserror = "1.0" unicode-xid = "0.2.2" - + phf = { version = "0.10.1", features = ["macros"] } diff --git a/sway-parse/src/attribute.rs b/sway-parse/src/attribute.rs index d3851ece214..851e911cede 100644 --- a/sway-parse/src/attribute.rs +++ b/sway-parse/src/attribute.rs @@ -71,7 +71,12 @@ impl Spanned for Attribute { impl Parse for Attribute { fn parse(parser: &mut Parser) -> ParseResult { - let name = parser.parse()?; + let storage = parser.take::(); + let name = if let Some(storage) = storage { + Ident::from(storage) + } else { + parser.parse()? + }; let args = Parens::try_parse(parser)?; Ok(Attribute { name, args }) } diff --git a/sway-parse/src/error.rs b/sway-parse/src/error.rs index ac22b27ec87..07cf0d0d5f0 100644 --- a/sway-parse/src/error.rs +++ b/sway-parse/src/error.rs @@ -66,6 +66,8 @@ pub enum ParseErrorKind { InvalidDoubleUnderscore, #[error("Unexpected rest token, must be at the end of pattern.")] UnexpectedRestPattern, + #[error("Identifiers cannot be a reserved keyword.")] + ReservedKeywordIdentifier, } #[derive(Debug, Error, Clone, PartialEq, Hash)] diff --git a/sway-parse/src/expr/mod.rs b/sway-parse/src/expr/mod.rs index 67b322c418d..38864df33bd 100644 --- a/sway-parse/src/expr/mod.rs +++ b/sway-parse/src/expr/mod.rs @@ -1056,14 +1056,14 @@ fn parse_atom(parser: &mut Parser, ctx: ParseExprCtx) -> ParseResult { ); } if parser.peek::().is_some() { - let ident = parser.parse::()?; + let ident = parser.parse::()?; return Ok(Expr::Literal(Literal::Bool(LitBool { span: ident.span(), kind: LitBoolType::True, }))); } if parser.peek::().is_some() { - let ident = parser.parse::()?; + let ident = parser.parse::()?; return Ok(Expr::Literal(Literal::Bool(LitBool { span: ident.span(), kind: LitBoolType::False, diff --git a/sway-parse/src/keywords.rs b/sway-parse/src/keywords.rs index a1572358dce..9cafb2fba1d 100644 --- a/sway-parse/src/keywords.rs +++ b/sway-parse/src/keywords.rs @@ -13,6 +13,18 @@ macro_rules! define_keyword ( } } + impl $ty_name { + pub fn ident(&self) -> Ident { + Ident::new(self.span()) + } + } + + impl From<$ty_name> for Ident { + fn from(o: $ty_name) -> Ident { + o.ident() + } + } + impl Peek for $ty_name { fn peek(peeker: Peeker<'_>) -> Option<$ty_name> { let ident = peeker.peek_ident().ok()?; @@ -119,6 +131,42 @@ macro_rules! define_token ( }; ); +// Keep this in sync with the list above defined by define_keyword! +pub(crate) const RESERVED_KEYWORDS: phf::Set<&'static str> = phf::phf_set! { + "script", + "contract", + "predicate", + "library", + "dep", + "pub", + "use", + "as", + "struct", + "enum", + "self", + "fn", + "trait", + "impl", + "for", + "abi", + "const", + "storage", + "str", + "asm", + "return", + "if", + "else", + "match", + "mut", + "let", + "while", + "where", + "ref", + "deref", + "true", + "false", +}; + define_token!(SemicolonToken, "a semicolon", [Semicolon], []); define_token!( ForwardSlashToken, diff --git a/sway-parse/src/parse.rs b/sway-parse/src/parse.rs index d91023aeb9a..c3aaa5c4bb6 100644 --- a/sway-parse/src/parse.rs +++ b/sway-parse/src/parse.rs @@ -97,6 +97,13 @@ impl Parse for Ident { )); } + if !ident.is_raw_ident() && RESERVED_KEYWORDS.contains(ident_str) { + return Err(parser.emit_error_with_span( + ParseErrorKind::ReservedKeywordIdentifier, + ident.span(), + )); + } + Ok(ident) } None => Err(parser.emit_error(ParseErrorKind::ExpectedIdent)), diff --git a/sway-parse/src/path.rs b/sway-parse/src/path.rs index 60e73d809c1..1dbc2b9e51e 100644 --- a/sway-parse/src/path.rs +++ b/sway-parse/src/path.rs @@ -90,10 +90,20 @@ impl Parse for PathExpr { } } +fn parse_ident(parser: &mut Parser) -> ParseResult { + if parser.peek::().is_some() { + let token = parser.parse::()?; + let ident: Ident = Ident::from(token); + Ok(ident) + } else { + parser.parse::() + } +} + impl Parse for PathExprSegment { fn parse(parser: &mut Parser) -> ParseResult { let fully_qualified = parser.take(); - let name = parser.parse()?; + let name = parse_ident(parser)?; let generics_opt = if parser .peek2::() .is_some() @@ -198,7 +208,7 @@ impl Parse for PathType { impl Parse for PathTypeSegment { fn parse(parser: &mut Parser) -> ParseResult { let fully_qualified = parser.take(); - let name = parser.parse()?; + let name = parse_ident(parser)?; let generics_opt = if parser.peek::().is_some() { let generics = parser.parse()?; Some((None, generics)) diff --git a/sway-parse/src/token.rs b/sway-parse/src/token.rs index 3c036f4aa29..964a8e7cc87 100644 --- a/sway-parse/src/token.rs +++ b/sway-parse/src/token.rs @@ -284,7 +284,7 @@ pub fn lex( .peekable(); let mut parent_token_trees = Vec::new(); let mut token_trees = Vec::new(); - while let Some((index, character)) = char_indices.next() { + while let Some((mut index, mut character)) = char_indices.next() { if character.is_whitespace() { continue; } @@ -395,6 +395,16 @@ pub fn lex( continue; } if character.is_xid_start() || character == '_' { + let is_raw_ident = character == 'r' && matches!(char_indices.peek(), Some((_, '#'))); + if is_raw_ident { + char_indices.next(); + if let Some((_, next_character)) = char_indices.peek() { + character = *next_character; + if let Some((next_index, _)) = char_indices.next() { + index = next_index; + } + } + } let is_single_underscore = character == '_' && match char_indices.peek() { Some((_, next_character)) => !next_character.is_xid_continue(), @@ -408,7 +418,7 @@ pub fn lex( let _ = char_indices.next(); } let span = span_until(src, index, &mut char_indices, &path); - let ident = Ident::new(span); + let ident = Ident::new_with_raw(span, is_raw_ident); token_trees.push(TokenTree::Ident(ident)); continue; } diff --git a/sway-types/src/ident.rs b/sway-types/src/ident.rs index fda2a7c466e..c0382ab21ac 100644 --- a/sway-types/src/ident.rs +++ b/sway-types/src/ident.rs @@ -11,6 +11,7 @@ use std::{ pub struct Ident { name_override_opt: Option<&'static str>, span: Span, + is_raw_ident: bool, } // custom implementation of Hash so that namespacing isn't reliant on the span itself, which will @@ -61,11 +62,25 @@ impl Ident { } } + pub fn is_raw_ident(&self) -> bool { + self.is_raw_ident + } + pub fn new(span: Span) -> Ident { let span = span.trim(); Ident { name_override_opt: None, span, + is_raw_ident: false, + } + } + + pub fn new_with_raw(span: Span, is_raw_ident: bool) -> Ident { + let span = span.trim(); + Ident { + name_override_opt: None, + span, + is_raw_ident, } } @@ -73,6 +88,7 @@ impl Ident { Ident { name_override_opt: Some(name_override), span, + is_raw_ident: false, } } @@ -80,6 +96,7 @@ impl Ident { Ident { name_override_opt: Some(name), span: Span::dummy(), + is_raw_ident: false, } } } diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/reserved_identifiers/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_fail/reserved_identifiers/Forc.lock new file mode 100644 index 00000000000..d85ecc65184 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/reserved_identifiers/Forc.lock @@ -0,0 +1,4 @@ +[[package]] +name = 'reserved_identifiers' +source = 'root' +dependencies = [] diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/reserved_identifiers/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_fail/reserved_identifiers/Forc.toml new file mode 100644 index 00000000000..a5c653793d8 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/reserved_identifiers/Forc.toml @@ -0,0 +1,6 @@ +[project] +authors = ["Fuel Labs "] +license = "Apache-2.0" +name = "reserved_identifiers" +entry = "main.sw" +implicit-std = false diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/reserved_identifiers/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/reserved_identifiers/src/main.sw new file mode 100644 index 00000000000..00f81f2f5a9 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/reserved_identifiers/src/main.sw @@ -0,0 +1,39 @@ +script; + +// this should fail with an `Identifiers cannot be a reserved keyword.` error + +fn main() { + let mut script = 0; + let mut contract = 0; + let mut predicate = 0; + let mut library = 0; + let mut dep = 0; + let mut pub = 0; + let mut use = 0; + let mut as = 0; + let mut struct = 0; + let mut enum = 0; + let mut self = 0; + let mut fn = 0; + let mut trait = 0; + let mut impl = 0; + let mut for = 0; + let mut abi = 0; + let mut const = 0; + let mut storage = 0; + let mut str = 0; + let mut asm = 0; + let mut return = 0; + let mut if = 0; + let mut else = 0; + let mut match = 0; + let mut mut = 0; + let mut let = 0; + let mut while = 0; + let mut where = 0; + let mut ref = 0; + let mut deref = 0; + let mut true = 0; + let mut false = 0; +} + diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/reserved_identifiers/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/reserved_identifiers/test.toml new file mode 100644 index 00000000000..eb3634e032c --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/reserved_identifiers/test.toml @@ -0,0 +1 @@ +category = "fail" diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/raw_identifiers/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_pass/language/raw_identifiers/Forc.lock new file mode 100644 index 00000000000..71928112b44 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/raw_identifiers/Forc.lock @@ -0,0 +1,9 @@ +[[package]] +name = 'core' +source = 'path+from-root-D97961F846794409' +dependencies = [] + +[[package]] +name = 'simple' +source = 'root' +dependencies = ['core'] diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/raw_identifiers/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/raw_identifiers/Forc.toml new file mode 100644 index 00000000000..fada1e39e43 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/raw_identifiers/Forc.toml @@ -0,0 +1,8 @@ +[project] +authors = ["Fuel Labs "] +license = "Apache-2.0" +name = "simple" +entry = "main.sw" + +[dependencies] +core = { path = "../../../../../../../sway-lib-core" } diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/raw_identifiers/json_abi_oracle.json b/test/src/e2e_vm_tests/test_programs/should_pass/language/raw_identifiers/json_abi_oracle.json new file mode 100644 index 00000000000..905c3bd1034 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/raw_identifiers/json_abi_oracle.json @@ -0,0 +1,14 @@ +[ + { + "inputs": [], + "name": "main", + "outputs": [ + { + "components": null, + "name": "", + "type": "u64" + } + ], + "type": "function" + } +] \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/raw_identifiers/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/language/raw_identifiers/src/main.sw new file mode 100644 index 00000000000..d14ad95ede4 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/raw_identifiers/src/main.sw @@ -0,0 +1,38 @@ +script; + +fn main() -> u64 { + let mut r#script = 0; + let mut r#contract = 0; + let mut r#predicate = 0; + let mut r#library = 0; + let mut r#dep = 0; + let mut r#pub = 0; + let mut r#use = 0; + let mut r#as = 0; + let mut r#struct = 0; + let mut r#enum = 0; + let mut r#self = 0; + let mut r#fn = 0; + let mut r#trait = 0; + let mut r#impl = 0; + let mut r#for = 0; + let mut r#abi = 0; + let mut r#const = 0; + let mut r#storage = 0; + let mut r#str = 0; + let mut r#asm = 0; + let mut r#return = 0; + let mut r#if = 0; + let mut r#else = 0; + let mut r#match = 0; + let mut r#mut = 0; + let mut r#let = 0; + let mut r#while = 0; + let mut r#where = 0; + let mut r#ref = 0; + let mut r#deref = 0; + let mut r#true = 0; + let mut r#false = 0; + + 0 +} diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/raw_identifiers/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/raw_identifiers/test.toml new file mode 100644 index 00000000000..44ca8ea93c4 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/raw_identifiers/test.toml @@ -0,0 +1,3 @@ +category = "run" +expected_result = { action = "return", value = 0 } +validate_abi = true