|
| 1 | +//! |
| 2 | +//! Revert statement lowering. |
| 3 | +//! |
| 4 | +
|
| 5 | +use std::collections::HashMap; |
| 6 | + |
| 7 | +use melior::ir::BlockLike; |
| 8 | +use melior::ir::BlockRef; |
| 9 | +use melior::ir::Value; |
| 10 | +use slang_solidity::backend::ir::ast::ArgumentsDeclaration; |
| 11 | +use slang_solidity::backend::ir::ast::Definition; |
| 12 | +use slang_solidity::backend::ir::ast::Expression; |
| 13 | +use slang_solidity::backend::ir::ast::NamedArguments; |
| 14 | +use slang_solidity::backend::ir::ast::Parameters; |
| 15 | +use slang_solidity::backend::ir::ast::RevertStatement; |
| 16 | + |
| 17 | +use super::StatementEmitter; |
| 18 | +use crate::ast::contract::function::expression::ExpressionEmitter; |
| 19 | + |
| 20 | +impl<'state, 'context, 'block> StatementEmitter<'state, 'context, 'block> { |
| 21 | + /// Emits a `sol.revert` with optional custom error signature and arguments. |
| 22 | + pub(super) fn emit_revert( |
| 23 | + &self, |
| 24 | + revert: &RevertStatement, |
| 25 | + block: BlockRef<'context, 'block>, |
| 26 | + ) -> anyhow::Result<Option<BlockRef<'context, 'block>>> { |
| 27 | + let Some((signature, argument_values, current_block)) = |
| 28 | + self.emit_custom_error_revert_data(revert, block)? |
| 29 | + else { |
| 30 | + self.emit_revert_terminator("", &[], false, block); |
| 31 | + return Ok(None); |
| 32 | + }; |
| 33 | + |
| 34 | + self.emit_revert_terminator(&signature, &argument_values, true, current_block); |
| 35 | + Ok(None) |
| 36 | + } |
| 37 | + |
| 38 | + /// Resolves and evaluates custom error data for a revert statement. |
| 39 | + fn emit_custom_error_revert_data( |
| 40 | + &self, |
| 41 | + revert: &RevertStatement, |
| 42 | + block: BlockRef<'context, 'block>, |
| 43 | + ) -> anyhow::Result< |
| 44 | + Option<( |
| 45 | + String, |
| 46 | + Vec<Value<'context, 'block>>, |
| 47 | + BlockRef<'context, 'block>, |
| 48 | + )>, |
| 49 | + > { |
| 50 | + let Some(Definition::Error(error)) = revert.error().resolve_to_definition() else { |
| 51 | + return Ok(None); |
| 52 | + }; |
| 53 | + |
| 54 | + let signature = error |
| 55 | + .compute_canonical_signature() |
| 56 | + .ok_or_else(|| anyhow::anyhow!("cannot compute canonical signature for error"))?; |
| 57 | + let arguments = revert.arguments(); |
| 58 | + let parameters = error.parameters(); |
| 59 | + let (argument_values, current_block) = |
| 60 | + self.emit_revert_arguments(&arguments, ¶meters, block)?; |
| 61 | + |
| 62 | + Ok(Some((signature, argument_values, current_block))) |
| 63 | + } |
| 64 | + |
| 65 | + /// Evaluates positional or named revert arguments in ABI order. |
| 66 | + fn emit_revert_arguments( |
| 67 | + &self, |
| 68 | + arguments: &ArgumentsDeclaration, |
| 69 | + error_parameters: &Parameters, |
| 70 | + block: BlockRef<'context, 'block>, |
| 71 | + ) -> anyhow::Result<(Vec<Value<'context, 'block>>, BlockRef<'context, 'block>)> { |
| 72 | + match arguments { |
| 73 | + ArgumentsDeclaration::PositionalArguments(positional) => { |
| 74 | + self.emit_revert_argument_values(positional.iter(), block) |
| 75 | + } |
| 76 | + ArgumentsDeclaration::NamedArguments(named) => { |
| 77 | + let arguments = Self::order_named_revert_arguments(named, error_parameters)?; |
| 78 | + self.emit_revert_argument_values(arguments, block) |
| 79 | + } |
| 80 | + } |
| 81 | + } |
| 82 | + |
| 83 | + /// Orders named revert arguments by the custom error's parameter order. |
| 84 | + fn order_named_revert_arguments( |
| 85 | + named_arguments: &NamedArguments, |
| 86 | + error_parameters: &Parameters, |
| 87 | + ) -> anyhow::Result<Vec<Expression>> { |
| 88 | + let mut arguments = HashMap::new(); |
| 89 | + for argument in named_arguments.iter() { |
| 90 | + let name = argument.name().name(); |
| 91 | + let previous = arguments.insert(name.clone(), argument.value()); |
| 92 | + anyhow::ensure!( |
| 93 | + previous.is_none(), |
| 94 | + "duplicate named revert argument `{name}`" |
| 95 | + ); |
| 96 | + } |
| 97 | + |
| 98 | + let mut ordered_arguments = Vec::new(); |
| 99 | + for parameter in error_parameters.iter() { |
| 100 | + let parameter_name = parameter |
| 101 | + .name() |
| 102 | + .ok_or_else(|| { |
| 103 | + anyhow::anyhow!("cannot match named revert argument to unnamed error parameter") |
| 104 | + })? |
| 105 | + .name(); |
| 106 | + let argument = arguments.remove(¶meter_name).ok_or_else(|| { |
| 107 | + anyhow::anyhow!("missing named revert argument `{parameter_name}`") |
| 108 | + })?; |
| 109 | + ordered_arguments.push(argument); |
| 110 | + } |
| 111 | + |
| 112 | + anyhow::ensure!( |
| 113 | + arguments.is_empty(), |
| 114 | + "unknown named revert argument(s): {}", |
| 115 | + arguments.keys().cloned().collect::<Vec<_>>().join(", ") |
| 116 | + ); |
| 117 | + |
| 118 | + Ok(ordered_arguments) |
| 119 | + } |
| 120 | + |
| 121 | + /// Evaluates revert argument expressions from left to right. |
| 122 | + fn emit_revert_argument_values( |
| 123 | + &self, |
| 124 | + arguments: impl IntoIterator<Item = Expression>, |
| 125 | + block: BlockRef<'context, 'block>, |
| 126 | + ) -> anyhow::Result<(Vec<Value<'context, 'block>>, BlockRef<'context, 'block>)> { |
| 127 | + let emitter = ExpressionEmitter::new( |
| 128 | + &self.semantic, |
| 129 | + self.state, |
| 130 | + self.environment, |
| 131 | + self.storage_layout, |
| 132 | + self.checked, |
| 133 | + ); |
| 134 | + let mut argument_values = Vec::new(); |
| 135 | + let mut current_block = block; |
| 136 | + for argument in arguments { |
| 137 | + let (value, next_block) = emitter.emit_value(&argument, current_block)?; |
| 138 | + argument_values.push(value); |
| 139 | + current_block = next_block; |
| 140 | + } |
| 141 | + |
| 142 | + Ok((argument_values, current_block)) |
| 143 | + } |
| 144 | + |
| 145 | + /// Emits a revert terminator followed by the temporary unreachable. |
| 146 | + fn emit_revert_terminator( |
| 147 | + &self, |
| 148 | + signature: &str, |
| 149 | + argument_values: &[Value<'context, 'block>], |
| 150 | + is_custom_error: bool, |
| 151 | + block: BlockRef<'context, 'block>, |
| 152 | + ) { |
| 153 | + self.state |
| 154 | + .builder |
| 155 | + .emit_sol_revert(signature, argument_values, is_custom_error, &block); |
| 156 | + // TODO(sol-dialect): remove once sol.revert is marked IsTerminator |
| 157 | + block.append_operation(melior::dialect::llvm::unreachable( |
| 158 | + self.state.builder.unknown_location, |
| 159 | + )); |
| 160 | + } |
| 161 | +} |
0 commit comments