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
27 changes: 26 additions & 1 deletion solang-parser/src/lexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,10 @@ 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 (Soroban storage types). When false, they are treated as
/// regular identifiers to preserve compatibility with standard Solidity.
soroban: bool,
}

/// An error thrown by [Lexer].
Expand Down Expand Up @@ -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' {
Expand Down Expand Up @@ -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))
};
Expand Down
29 changes: 29 additions & 0 deletions solang-parser/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<pt::Comment>), Vec<Diagnostic>> {
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<pt::Comment>), Vec<Diagnostic>> {
parse_impl(src, file_no, true)
}

fn parse_impl(
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);

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);

Expand Down
40 changes: 40 additions & 0 deletions solang-parser/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"
);
}
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_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);
Expand Down