Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion solang-parser/src/lexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,11 @@ pub struct Lexer<'input> {
last_tokens: [Option<Token<'input>>; 2],
/// The mutable reference to the error vector.
pub errors: &'input mut Vec<LexicalError>,
/// 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].
Expand Down Expand Up @@ -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' {
Expand Down Expand Up @@ -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))
};
Expand Down
22 changes: 22 additions & 0 deletions solang-parser/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,33 @@ mod solidity {
pub fn parse(
src: &str,
file_no: usize,
) -> Result<(pt::SourceUnit, Vec<pt::Comment>), Vec<Diagnostic>> {
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<pt::Comment>), Vec<Diagnostic>> {
parse_internal(src, file_no, true)
}

fn parse_internal(
src: &str,
file_no: usize,
soroban: bool,
) -> Result<(pt::SourceUnit, Vec<pt::Comment>), Vec<Diagnostic>> {
// 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);
Expand Down
50 changes: 50 additions & 0 deletions solang-parser/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
);
}
10 changes: 8 additions & 2 deletions src/sema/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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);
Expand Down