use indexmap::IndexMap;
use solang_parser::diagnostics::{ErrorType, Level, Note};
use std::collections::{HashMap, HashSet, LinkedList};
use std::str;
use std::sync::Arc;
use super::ast::{Diagnostic, Namespace, Type};
use crate::sema::ast::Expression;
use solang_parser::pt;
#[derive(Clone, Debug)]
pub struct Variable {
pub id: pt::Identifier,
pub ty: Type,
pub pos: usize,
pub slice: bool,
pub assigned: bool,
pub read: bool,
pub usage_type: VariableUsage,
pub initializer: VariableInitializer,
pub storage_location: Option<pt::StorageLocation>,
}
#[derive(Clone, Debug)]
pub enum VariableInitializer {
Solidity(Option<Arc<Expression>>),
Yul(bool),
}
impl VariableInitializer {
pub fn has_initializer(&self) -> bool {
match self {
VariableInitializer::Solidity(expr) => expr.is_some(),
VariableInitializer::Yul(initialized) => *initialized,
}
}
}
impl Variable {
pub fn is_reference(&self) -> bool {
if matches!(
self.storage_location,
Some(pt::StorageLocation::Memory(_)) | Some(pt::StorageLocation::Storage(_))
) {
if let VariableInitializer::Solidity(Some(expr)) = &self.initializer {
return !matches!(
**expr,
Expression::AllocDynamicBytes(..)
| Expression::ArrayLiteral(..)
| Expression::Constructor { .. }
| Expression::StructLiteral(..)
);
}
}
false
}
}
#[derive(Clone, Debug)]
pub enum VariableUsage {
Parameter,
ReturnVariable,
AnonymousReturnVariable,
LocalVariable,
DestructureVariable,
TryCatchReturns,
TryCatchErrorString,
TryCatchErrorBytes,
YulLocalVariable,
}
#[derive(Debug, Clone)]
struct VarScope(HashMap<String, usize>, Option<HashSet<usize>>);
#[derive(Default, Debug, Clone)]
pub struct Symtable {
pub vars: IndexMap<usize, Variable>,
names: LinkedList<VarScope>,
pub arguments: Vec<Option<usize>>,
pub returns: Vec<usize>,
}
impl Symtable {
pub fn new() -> Self {
let mut list = LinkedList::new();
list.push_front(VarScope(HashMap::new(), None));
Symtable {
vars: IndexMap::new(),
names: list,
arguments: Vec::new(),
returns: Vec::new(),
}
}
pub fn add(
&mut self,
id: &pt::Identifier,
ty: Type,
ns: &mut Namespace,
initializer: VariableInitializer,
usage_type: VariableUsage,
storage_location: Option<pt::StorageLocation>,
) -> Option<usize> {
let pos = ns.next_id;
ns.next_id += 1;
self.vars.insert(
pos,
Variable {
id: id.clone(),
ty,
pos,
slice: false,
initializer,
assigned: false,
usage_type,
read: false,
storage_location,
},
);
if !id.name.is_empty() {
if let Some(prev) = self.find(&id.name) {
ns.diagnostics.push(Diagnostic::error_with_note(
id.loc,
format!("{} is already declared", id.name),
prev.id.loc,
"location of previous declaration".to_string(),
));
return None;
}
self.names
.front_mut()
.unwrap()
.0
.insert(id.name.to_string(), pos);
}
Some(pos)
}
pub fn exclusive_add(
&mut self,
id: &pt::Identifier,
ty: Type,
ns: &mut Namespace,
initializer: VariableInitializer,
usage_type: VariableUsage,
storage_location: Option<pt::StorageLocation>,
) -> Option<usize> {
if let Some(var) = self.find(&id.name) {
ns.diagnostics.push(Diagnostic {
level: Level::Error,
ty: ErrorType::DeclarationError,
loc: id.loc,
message: format!("variable name '{}' already used in this scope", id.name),
notes: vec![Note {
loc: var.id.loc,
message: "found previous declaration here".to_string(),
}],
});
return None;
}
self.add(id, ty, ns, initializer, usage_type, storage_location)
}
pub fn find(&self, name: &str) -> Option<&Variable> {
for scope in &self.names {
if let Some(n) = scope.0.get(name) {
return self.vars.get(n);
}
}
None
}
pub fn new_scope(&mut self) {
self.names.push_front(VarScope(HashMap::new(), None));
}
pub fn leave_scope(&mut self) {
self.names.pop_front();
}
pub fn get_name(&self, pos: usize) -> &str {
&self.vars[&pos].id.name
}
}
pub struct LoopScope {
pub no_breaks: usize,
pub no_continues: usize,
}
pub struct LoopScopes(LinkedList<LoopScope>);
impl Default for LoopScopes {
fn default() -> Self {
LoopScopes::new()
}
}
impl LoopScopes {
pub fn new() -> Self {
LoopScopes(LinkedList::new())
}
pub fn new_scope(&mut self) {
self.0.push_front(LoopScope {
no_breaks: 0,
no_continues: 0,
})
}
pub fn leave_scope(&mut self) -> LoopScope {
self.0.pop_front().unwrap()
}
pub fn do_break(&mut self) -> bool {
match self.0.front_mut() {
Some(scope) => {
scope.no_breaks += 1;
true
}
None => false,
}
}
pub fn do_continue(&mut self) -> bool {
match self.0.front_mut() {
Some(scope) => {
scope.no_continues += 1;
true
}
None => false,
}
}
}