neo_solidity/
semantic_model.rs

1//! Semantic model extracted from Solidity metadata.
2
3use crate::frontend::VisibilityKind;
4use crate::solidity::{
5    ContractMetadata, Diagnostic, DiagnosticSeverity, FunctionKind, FunctionMetadata,
6    ParameterMetadata, StateMutability, StateVariableMetadata,
7};
8use crate::type_system::NeoType;
9
10#[derive(Debug, Clone)]
11pub struct SemanticModel {
12    pub functions: Vec<FunctionSymbol>,
13    pub state_variables: Vec<StateVariableSymbol>,
14}
15
16impl SemanticModel {
17    /// Get all public functions
18    pub fn public_functions(&self) -> Vec<&FunctionSymbol> {
19        self.functions
20            .iter()
21            .filter(|f| {
22                matches!(
23                    f.visibility,
24                    VisibilityKind::Public | VisibilityKind::External
25                )
26            })
27            .collect()
28    }
29
30    /// Get a function by name
31    pub fn get_function(&self, name: &str) -> Option<&FunctionSymbol> {
32        self.functions.iter().find(|f| f.name == name)
33    }
34
35    /// Get all state variables
36    pub fn get_state_variables(&self) -> &[StateVariableSymbol] {
37        &self.state_variables
38    }
39
40    /// Check if this is a payable contract
41    pub fn is_payable(&self) -> bool {
42        self.functions
43            .iter()
44            .any(|f| f.state_mutability == StateMutability::Payable)
45    }
46}
47
48#[derive(Debug, Clone)]
49pub struct FunctionSymbol {
50    pub name: String,
51    pub kind: FunctionKind,
52    pub parameters: Vec<ParameterSymbol>,
53    pub returns: Vec<ParameterSymbol>,
54    pub state_mutability: StateMutability,
55    pub visibility: VisibilityKind,
56}
57
58#[derive(Debug, Clone)]
59pub struct ParameterSymbol {
60    pub name: Option<String>,
61    pub ty: NeoType,
62    pub storage: Option<String>,
63}
64
65#[derive(Debug, Clone)]
66pub struct StateVariableSymbol {
67    pub name: Option<String>,
68    pub ty: NeoType,
69    pub is_constant: bool,
70    pub is_immutable: bool,
71    pub visibility: Option<String>,
72}
73
74pub fn build_semantic_model(metadata: &ContractMetadata) -> Result<SemanticModel, Vec<Diagnostic>> {
75    let mut diagnostics = Vec::new();
76
77    let mut functions = Vec::new();
78    for function in &metadata.methods {
79        match convert_function(function) {
80            Ok(symbol) => functions.push(symbol),
81            Err(mut diags) => diagnostics.append(&mut diags),
82        }
83    }
84
85    let mut state_variables = Vec::new();
86    for state in &metadata.state_variables {
87        match convert_state_variable(state) {
88            Ok(symbol) => state_variables.push(symbol),
89            Err(mut diags) => diagnostics.append(&mut diags),
90        }
91    }
92
93    let has_error = diagnostics
94        .iter()
95        .any(|diag| matches!(diag.severity, DiagnosticSeverity::Error));
96
97    if has_error {
98        Err(diagnostics)
99    } else {
100        Ok(SemanticModel {
101            functions,
102            state_variables,
103        })
104    }
105}
106
107fn convert_function(function: &FunctionMetadata) -> Result<FunctionSymbol, Vec<Diagnostic>> {
108    let mut diagnostics = Vec::new();
109    let allow_unsupported_internal_types = !matches!(
110        function.visibility,
111        VisibilityKind::Public | VisibilityKind::External
112    );
113
114    let mut parameters = Vec::new();
115    for param in &function.parameters {
116        match convert_parameter(
117            param,
118            FunctionSide::Parameter,
119            &function.name,
120            allow_unsupported_internal_types,
121        ) {
122            Ok(symbol) => parameters.push(symbol),
123            Err(diag) => diagnostics.push(diag),
124        }
125    }
126
127    let mut returns = Vec::new();
128    for param in &function.return_parameters {
129        match convert_parameter(
130            param,
131            FunctionSide::Return,
132            &function.name,
133            allow_unsupported_internal_types,
134        ) {
135            Ok(symbol) => returns.push(symbol),
136            Err(diag) => diagnostics.push(diag),
137        }
138    }
139
140    if diagnostics
141        .iter()
142        .any(|diag| matches!(diag.severity, DiagnosticSeverity::Error))
143    {
144        Err(diagnostics)
145    } else {
146        Ok(FunctionSymbol {
147            name: function.name.clone(),
148            kind: function.kind,
149            parameters,
150            returns,
151            state_mutability: function.state_mutability,
152            visibility: function.visibility,
153        })
154    }
155}
156
157fn convert_state_variable(
158    state: &StateVariableMetadata,
159) -> Result<StateVariableSymbol, Vec<Diagnostic>> {
160    match &state.neo_type {
161        Some(neo_type) => Ok(StateVariableSymbol {
162            name: state.name.clone(),
163            ty: neo_type.clone(),
164            is_constant: state.is_constant,
165            is_immutable: state.is_immutable,
166            visibility: state.visibility.clone(),
167        }),
168        None => Err(vec![Diagnostic::error(format!(
169            "state variable '{}' has unsupported type '{}'",
170            state.name.as_deref().unwrap_or("<unnamed>"),
171            state.ty
172        ))]),
173    }
174}
175
176enum FunctionSide {
177    Parameter,
178    Return,
179}
180
181fn convert_parameter(
182    param: &ParameterMetadata,
183    side: FunctionSide,
184    function_name: &str,
185    allow_unsupported_internal_types: bool,
186) -> Result<ParameterSymbol, Diagnostic> {
187    match &param.neo_type {
188        Some(neo_type) => Ok(ParameterSymbol {
189            name: param.name.clone(),
190            ty: neo_type.clone(),
191            storage: param.storage.clone(),
192        }),
193        None if allow_unsupported_internal_types => Ok(ParameterSymbol {
194            name: param.name.clone(),
195            ty: NeoType::Any,
196            storage: param.storage.clone(),
197        }),
198        None => Err(Diagnostic::error(match side {
199            FunctionSide::Parameter => format!(
200                "function '{}' parameter '{}' uses unsupported type '{}'",
201                function_name,
202                param
203                    .name
204                    .clone()
205                    .unwrap_or_else(|| "<unnamed>".to_string()),
206                param.ty
207            ),
208            FunctionSide::Return => format!(
209                "function '{}' return type '{}' is unsupported",
210                function_name, param.ty
211            ),
212        })),
213    }
214}