use crate::{
language::{
promote_purity,
Purity::{self, *},
},
metadata::{MetadataManager, StorageOperation},
};
use sway_error::warning::{CompileWarning, Warning};
use sway_error::{error::CompileError, handler::Handler};
use sway_ir::{Context, FuelVmInstruction, Function, Instruction};
use sway_types::span::Span;
use std::collections::HashMap;
#[derive(Default)]
pub(crate) struct PurityEnv {
memos: HashMap<Function, (bool, bool)>,
}
pub(crate) fn check_function_purity(
handler: &Handler,
env: &mut PurityEnv,
context: &Context,
md_mgr: &mut MetadataManager,
function: &Function,
) -> (bool, bool) {
let (reads, writes) = function.instruction_iter(context).fold(
(false, false),
|(reads, writes), (_block, ins_value)| {
ins_value
.get_instruction(context)
.map(|instruction| {
match instruction {
Instruction::FuelVm(FuelVmInstruction::StateLoadQuadWord { .. })
| Instruction::FuelVm(FuelVmInstruction::StateLoadWord(_)) => {
(true, writes)
}
Instruction::FuelVm(FuelVmInstruction::StateClear { .. })
| Instruction::FuelVm(FuelVmInstruction::StateStoreQuadWord { .. })
| Instruction::FuelVm(FuelVmInstruction::StateStoreWord { .. }) => {
(reads, true)
}
Instruction::AsmBlock(asm_block, _args) => {
asm_block.get_content(context).body.iter().fold(
(reads, writes),
|(reads, writes), asm_op| match asm_op.name.as_str() {
"scwq" | "srw" | "srwq" => (true, writes),
"sww" | "swwq" => (reads, true),
_ => (reads, writes),
},
)
}
Instruction::Call(callee, _args) => {
let (called_fn_reads, called_fn_writes) =
env.memos.get(callee).copied().unwrap_or_else(|| {
let r_w = check_function_purity(
handler, env, context, md_mgr, callee,
);
env.memos.insert(*callee, r_w);
r_w
});
(reads || called_fn_reads, writes || called_fn_writes)
}
_otherwise => (reads, writes),
}
})
.unwrap_or_else(|| (reads, writes))
},
);
let attributed_purity = md_mgr.md_to_storage_op(context, function.get_metadata(context));
let span = md_mgr
.md_to_span(context, function.get_metadata(context))
.unwrap_or_else(Span::dummy);
let error = |span, storage_op, existing, needed| {
handler.emit_err(CompileError::ImpureInPureContext {
storage_op,
attrs: promote_purity(existing, needed).to_attribute_syntax(),
span,
});
};
let warn = |span, purity: Purity| {
handler.emit_warn(CompileWarning {
warning_content: Warning::DeadStorageDeclarationForFunction {
unneeded_attrib: purity.to_attribute_syntax(),
},
span,
});
};
match (attributed_purity, reads, writes) {
(None, true, false) => error(span, "read", Pure, Reads),
(None, false, true) => error(span, "write", Pure, Writes),
(None, true, true) => error(span, "read & write", Pure, ReadsWrites),
(Some(StorageOperation::Reads), _, true) => error(span, "write", Reads, Writes),
(Some(StorageOperation::Writes), true, _) => error(span, "read", Writes, Reads),
(Some(StorageOperation::ReadsWrites), false, true) => warn(span, Reads),
(Some(StorageOperation::ReadsWrites), true, false) => warn(span, Writes),
(Some(StorageOperation::ReadsWrites), false, false) => warn(span, ReadsWrites),
(Some(StorageOperation::Reads), false, false) => warn(span, Reads),
(Some(StorageOperation::Writes), false, false) => warn(span, Writes),
(None, false, false)
| (Some(StorageOperation::Reads), true, false)
| (Some(StorageOperation::Writes), false, true)
| (Some(StorageOperation::ReadsWrites), true, true) => (),
};
(reads, writes)
}