diff --git a/solang-parser/src/lexer.rs b/solang-parser/src/lexer.rs index 73afb7f59..4f66ae856 100644 --- a/solang-parser/src/lexer.rs +++ b/solang-parser/src/lexer.rs @@ -361,6 +361,11 @@ pub struct Lexer<'input> { last_tokens: [Option>; 2], /// The mutable reference to the error vector. pub errors: &'input mut Vec, + /// When true, `persistent`, `temporary`, and `instance` are lexed as + /// keywords. When false (the default), they are treated as plain + /// identifiers so that non-Soroban targets remain compatible with + /// standard Solidity. + soroban: bool, } /// An error thrown by [Lexer]. @@ -593,9 +598,16 @@ impl<'input> Lexer<'input> { parse_semver: false, last_tokens: [None, None], errors, + soroban: false, } } + /// Enable Soroban keyword mode. When enabled, `persistent`, `temporary`, + /// and `instance` are lexed as keywords rather than identifiers. + pub fn set_soroban_keywords(&mut self, enable: bool) { + self.soroban = enable; + } + fn parse_number(&mut self, mut start: usize, ch: char) -> Result<'input> { let mut is_rational = false; if ch == '0' { @@ -860,7 +872,15 @@ impl<'input> Lexer<'input> { } return if let Some(w) = KEYWORDS.get(id) { - Some((start, *w, end)) + // Soroban storage-type keywords should only be + // recognised when we are targeting Soroban. + if !self.soroban + && matches!(w, Token::Persistent | Token::Temporary | Token::Instance) + { + Some((start, Token::Identifier(id), end)) + } else { + Some((start, *w, end)) + } } else { Some((start, Token::Identifier(id), end)) }; diff --git a/solang-parser/src/lib.rs b/solang-parser/src/lib.rs index 6d36e92f3..15c73b68d 100644 --- a/solang-parser/src/lib.rs +++ b/solang-parser/src/lib.rs @@ -36,11 +36,33 @@ mod solidity { pub fn parse( src: &str, file_no: usize, +) -> Result<(pt::SourceUnit, Vec), Vec> { + parse_internal(src, file_no, false) +} + +/// Parses a Solidity file with Soroban storage-type keywords enabled. +/// +/// When using this function, the keywords `persistent`, `temporary`, and +/// `instance` are recognised as Soroban storage-type annotations. Use the +/// regular [`parse`] function for all other compilation targets so that +/// these words can be used as ordinary identifiers. +pub fn parse_soroban( + src: &str, + file_no: usize, +) -> Result<(pt::SourceUnit, Vec), Vec> { + parse_internal(src, file_no, true) +} + +fn parse_internal( + src: &str, + file_no: usize, + soroban: bool, ) -> Result<(pt::SourceUnit, Vec), Vec> { // parse phase let mut comments = Vec::new(); let mut lexer_errors = Vec::new(); let mut lex = lexer::Lexer::new(src, file_no, &mut comments, &mut lexer_errors); + lex.set_soroban_keywords(soroban); let mut parser_errors = Vec::new(); let res = solidity::SourceUnitParser::new().parse(src, file_no, &mut parser_errors, &mut lex); diff --git a/solang-parser/src/tests.rs b/solang-parser/src/tests.rs index dea05de0f..54f8eef68 100644 --- a/solang-parser/src/tests.rs +++ b/solang-parser/src/tests.rs @@ -1349,3 +1349,53 @@ fn loc_union() { second.union(&other_first); assert_eq!(second, Loc::File(1, 4, 24)); } + +#[test] +fn soroban_keywords_are_identifiers_in_default_parse() { + // Issue #1847: `persistent`, `temporary`, and `instance` should be + // usable as regular identifiers when not targeting Soroban. + let src = r#" + contract C { + uint256 persistent = 1; + uint256 temporary = 2; + uint256 instance = 3; + + function persistent() public pure returns (uint256) { + return 1; + } + + function temporary() public pure returns (uint256) { + return 2; + } + + function instance() public pure returns (uint256) { + return 3; + } + } + "#; + + let result = crate::parse(src, 0); + assert!( + result.is_ok(), + "default parse() should accept persistent/temporary/instance as identifiers, got: {:?}", + result.err() + ); +} + +#[test] +fn soroban_keywords_are_keywords_in_soroban_parse() { + // Issue #1847: `persistent`, `temporary`, and `instance` should be + // recognised as Soroban storage-type keywords when using parse_soroban(). + let src = r#" + contract C { + uint256 persistent x = 1; + } + "#; + + let result = crate::parse_soroban(src, 0); + assert!( + result.is_ok(), + "parse_soroban() should accept persistent as a keyword, got: {:?}", + result.err() + ); +} diff --git a/src/sema/mod.rs b/src/sema/mod.rs index d6a33c678..9721804c8 100644 --- a/src/sema/mod.rs +++ b/src/sema/mod.rs @@ -12,7 +12,7 @@ use crate::file_resolver::{FileResolver, ResolvedFile}; use num_bigint::BigInt; use solang_parser::{ doccomment::{parse_doccomments, DocComment}, - parse, + parse, parse_soroban, pt::{self, CodeLocation}, }; use std::{ffi::OsString, str}; @@ -105,7 +105,13 @@ fn sema_file(file: &ResolvedFile, resolver: &mut FileResolver, ns: &mut ast::Nam file.import_no, )); - let (pt, comments) = match parse(&source_code, file_no) { + let parse_fn = if ns.target == crate::Target::Soroban { + parse_soroban + } else { + parse + }; + + let (pt, comments) = match parse_fn(&source_code, file_no) { Ok(s) => s, Err(mut errors) => { ns.diagnostics.append(&mut errors);