diff --git a/solang-parser/src/lexer.rs b/solang-parser/src/lexer.rs index 73afb7f59..1b80436e4 100644 --- a/solang-parser/src/lexer.rs +++ b/solang-parser/src/lexer.rs @@ -361,6 +361,10 @@ 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 (Soroban storage types). When false, they are treated as + /// regular identifiers to preserve compatibility with standard Solidity. + soroban: bool, } /// An error thrown by [Lexer]. @@ -593,9 +597,19 @@ impl<'input> Lexer<'input> { parse_semver: false, last_tokens: [None, None], errors, + soroban: false, } } + /// Enable Soroban-specific storage type keywords (`persistent`, `temporary`, + /// `instance`). By default these are treated as plain identifiers so that + /// standard Solidity code that uses these words as identifiers continues to + /// parse correctly. + pub fn with_soroban_keywords(mut self) -> Self { + self.soroban = true; + self + } + fn parse_number(&mut self, mut start: usize, ch: char) -> Result<'input> { let mut is_rational = false; if ch == '0' { @@ -860,7 +874,18 @@ impl<'input> Lexer<'input> { } return if let Some(w) = KEYWORDS.get(id) { - Some((start, *w, end)) + // Soroban-specific storage type keywords are only + // meaningful for the Soroban target. For all other + // targets they are treated as plain identifiers so + // that standard Solidity code using these words as + // variable or function names continues to compile. + 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..1e26c87b8 100644 --- a/solang-parser/src/lib.rs +++ b/solang-parser/src/lib.rs @@ -33,15 +33,44 @@ mod solidity { } /// Parses a Solidity file. +/// +/// Soroban-specific storage type keywords (`persistent`, `temporary`, +/// `instance`) are treated as plain identifiers by this function so that +/// standard Solidity code that uses these words as identifiers parses +/// correctly. Use [`parse_soroban`] when targeting the Soroban platform. pub fn parse( src: &str, file_no: usize, +) -> Result<(pt::SourceUnit, Vec), Vec> { + parse_impl(src, file_no, false) +} + +/// Parses a Solidity file targeting the Soroban platform. +/// +/// Enables Soroban-specific storage type keywords (`persistent`, `temporary`, +/// `instance`) so that contract state variables can carry storage-type +/// annotations. +pub fn parse_soroban( + src: &str, + file_no: usize, +) -> Result<(pt::SourceUnit, Vec), Vec> { + parse_impl(src, file_no, true) +} + +fn parse_impl( + 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); + if soroban { + lex = lex.with_soroban_keywords(); + } + 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..96a3e3b42 100644 --- a/solang-parser/src/tests.rs +++ b/solang-parser/src/tests.rs @@ -1349,3 +1349,43 @@ fn loc_union() { second.union(&other_first); assert_eq!(second, Loc::File(1, 4, 24)); } + +/// `persistent`, `temporary`, and `instance` are valid identifiers in +/// standard Solidity (non-Soroban). Regression test for +/// https://github.com/hyperledger-solang/solang/issues/1847 +#[test] +fn soroban_keywords_as_identifiers() { + // Each of the three words must be usable as a variable name when not + // targeting Soroban. + let cases = [ + "contract C { uint256 persistent = 1; }", + "contract C { uint256 temporary = 2; }", + "contract C { uint256 instance = 3; }", + "contract C { function persistent() public {} }", + "contract C { function temporary() public {} }", + "contract C { function instance() public {} }", + ]; + for src in &cases { + assert!( + crate::parse(src, 0).is_ok(), + "standard Solidity parse failed for: {src}" + ); + } +} + +/// `persistent`, `temporary`, and `instance` must still be recognised as +/// storage-type keywords when `parse_soroban` is used. +#[test] +fn soroban_keywords_in_soroban_mode() { + let src = r#" +contract C { + uint64 public persistent x = 1; + uint64 public temporary y = 2; + uint64 public instance z = 3; +} +"#; + assert!( + crate::parse_soroban(src, 0).is_ok(), + "Soroban parse failed for storage-type keywords" + ); +} diff --git a/src/sema/mod.rs b/src/sema/mod.rs index d6a33c678..f28f6838d 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_result = if ns.target == crate::Target::Soroban { + parse_soroban(&source_code, file_no) + } else { + parse(&source_code, file_no) + }; + + let (pt, comments) = match parse_result { Ok(s) => s, Err(mut errors) => { ns.diagnostics.append(&mut errors);