Skip to main content

oxilean_codegen/solidity_backend/
functions.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use std::collections::HashMap;
6
7use super::types::{
8    ContractKind, SolAnalysisCache, SolConstantFoldingHelper, SolDepGraph, SolDominatorTree,
9    SolExtCache, SolExtConstFolder, SolExtDepGraph, SolExtDomTree, SolExtLiveness,
10    SolExtPassConfig, SolExtPassPhase, SolExtPassRegistry, SolExtPassStats, SolExtWorklist,
11    SolLivenessInfo, SolPassConfig, SolPassPhase, SolPassRegistry, SolPassStats, SolWorklist,
12    SolidityBackend, SolidityContract, SolidityEnum, SolidityError, SolidityEvent, SolidityExpr,
13    SolidityFunction, SolidityParam, SolidityStateVar, SolidityStmt, SolidityStruct, SolidityType,
14    StateMutability, Visibility,
15};
16
17/// Standard library constants available in every generated Solidity contract.
18/// These are injected as comments / utility fragments for the emitter.
19pub const SOLIDITY_RUNTIME: &str = r#"// SPDX-License-Identifier: MIT
20// OxiLean Solidity Runtime Library (inlined)
21
22/// @dev SafeMath-equivalent operations (Solidity 0.8+ has overflow checks built in)
23library OxiLeanMath {
24    /// @notice Returns the minimum of two values.
25    function min(uint256 a, uint256 b) internal pure returns (uint256) {
26        return a < b ? a : b;
27    }
28
29    /// @notice Returns the maximum of two values.
30    function max(uint256 a, uint256 b) internal pure returns (uint256) {
31        return a > b ? a : b;
32    }
33
34    /// @notice Returns the absolute difference.
35    function absDiff(uint256 a, uint256 b) internal pure returns (uint256) {
36        return a >= b ? a - b : b - a;
37    }
38
39    /// @notice Saturating addition (clamps to uint256 max).
40    function saturatingAdd(uint256 a, uint256 b) internal pure returns (uint256) {
41        unchecked {
42            uint256 c = a + b;
43            return c < a ? type(uint256).max : c;
44        }
45    }
46
47    /// @notice Saturating subtraction (clamps to 0).
48    function saturatingSub(uint256 a, uint256 b) internal pure returns (uint256) {
49        return a > b ? a - b : 0;
50    }
51
52    /// @notice Integer square root (floor).
53    function sqrt(uint256 x) internal pure returns (uint256 r) {
54        if (x == 0) return 0;
55        r = 1;
56        uint256 xAux = x;
57        if (xAux >= 0x100000000000000000000000000000000) { r <<= 64; xAux >>= 128; }
58        if (xAux >= 0x10000000000000000) { r <<= 32; xAux >>= 64; }
59        if (xAux >= 0x100000000) { r <<= 16; xAux >>= 32; }
60        if (xAux >= 0x10000) { r <<= 8; xAux >>= 16; }
61        if (xAux >= 0x100) { r <<= 4; xAux >>= 8; }
62        if (xAux >= 0x10) { r <<= 2; xAux >>= 4; }
63        if (xAux >= 0x4) { r <<= 1; }
64        r = (r + x / r) >> 1;
65        r = (r + x / r) >> 1;
66        r = (r + x / r) >> 1;
67        r = (r + x / r) >> 1;
68        r = (r + x / r) >> 1;
69        r = (r + x / r) >> 1;
70        r = (r + x / r) >> 1;
71        return r > x / r ? r - 1 : r;
72    }
73}
74
75/// @dev Address utilities
76library OxiLeanAddress {
77    /// @notice Returns true if the address is the zero address.
78    function isZero(address addr) internal pure returns (bool) {
79        return addr == address(0);
80    }
81
82    /// @notice Converts an address to uint256.
83    function toUint256(address addr) internal pure returns (uint256) {
84        return uint256(uint160(addr));
85    }
86
87    /// @notice Converts uint256 to address.
88    function fromUint256(uint256 n) internal pure returns (address) {
89        return address(uint160(n));
90    }
91}
92
93/// @dev Bytes utilities
94library OxiLeanBytes {
95    /// @notice Converts bytes32 to bytes.
96    function toBytes(bytes32 b) internal pure returns (bytes memory) {
97        bytes memory result = new bytes(32);
98        assembly { mstore(add(result, 32), b) }
99        return result;
100    }
101
102    /// @notice Concatenates two byte arrays.
103    function concat(bytes memory a, bytes memory b) internal pure returns (bytes memory c) {
104        uint256 la = a.length;
105        uint256 lb = b.length;
106        c = new bytes(la + lb);
107        for (uint256 i = 0; i < la; i++) c[i] = a[i];
108        for (uint256 j = 0; j < lb; j++) c[la + j] = b[j];
109    }
110}
111"#;
112#[cfg(test)]
113mod tests {
114    use super::*;
115    #[test]
116    pub(super) fn test_uint256_display() {
117        assert_eq!(SolidityType::Uint256.to_string(), "uint256");
118    }
119    #[test]
120    pub(super) fn test_mapping_display() {
121        let ty = SolidityType::Mapping(
122            Box::new(SolidityType::Address),
123            Box::new(SolidityType::Uint256),
124        );
125        assert_eq!(ty.to_string(), "mapping(address => uint256)");
126    }
127    #[test]
128    pub(super) fn test_dyn_array_display() {
129        let ty = SolidityType::DynArray(Box::new(SolidityType::Uint256));
130        assert_eq!(ty.to_string(), "uint256[]");
131    }
132    #[test]
133    pub(super) fn test_fixed_array_display() {
134        let ty = SolidityType::FixedArray(Box::new(SolidityType::Bool), 10);
135        assert_eq!(ty.to_string(), "bool[10]");
136    }
137    #[test]
138    pub(super) fn test_string_type_display() {
139        assert_eq!(SolidityType::StringTy.to_string(), "string");
140    }
141    #[test]
142    pub(super) fn test_tuple_abi_canonical() {
143        let ty = SolidityType::Tuple(vec![
144            SolidityType::Uint256,
145            SolidityType::Address,
146            SolidityType::Bool,
147        ]);
148        assert_eq!(ty.abi_canonical(), "(uint256,address,bool)");
149    }
150    #[test]
151    pub(super) fn test_is_reference_type_string() {
152        assert!(SolidityType::StringTy.is_reference_type());
153    }
154    #[test]
155    pub(super) fn test_is_reference_type_uint256() {
156        assert!(!SolidityType::Uint256.is_reference_type());
157    }
158    #[test]
159    pub(super) fn test_is_reference_type_mapping() {
160        let ty = SolidityType::Mapping(
161            Box::new(SolidityType::Address),
162            Box::new(SolidityType::Uint256),
163        );
164        assert!(ty.is_reference_type());
165    }
166    #[test]
167    pub(super) fn test_param_new_value_type() {
168        let p = SolidityParam::new(SolidityType::Uint256, "amount");
169        assert!(p.location.is_none());
170        assert_eq!(p.to_string(), "uint256 amount");
171    }
172    #[test]
173    pub(super) fn test_param_new_reference_type() {
174        let p = SolidityParam::new(SolidityType::StringTy, "name");
175        assert_eq!(p.location.as_deref(), Some("memory"));
176        assert_eq!(p.to_string(), "string memory name");
177    }
178    #[test]
179    pub(super) fn test_param_calldata() {
180        let p = SolidityParam::calldata(SolidityType::Bytes, "data");
181        assert_eq!(p.location.as_deref(), Some("calldata"));
182        assert_eq!(p.to_string(), "bytes calldata data");
183    }
184    #[test]
185    pub(super) fn test_abi_signature() {
186        let mut func = SolidityFunction::new("transfer");
187        func.params
188            .push(SolidityParam::new(SolidityType::Address, "to"));
189        func.params
190            .push(SolidityParam::new(SolidityType::Uint256, "amount"));
191        assert_eq!(func.abi_signature(), "transfer(address,uint256)");
192    }
193    #[test]
194    pub(super) fn test_selector_length() {
195        let func = SolidityFunction::new("balanceOf");
196        let sel = func.selector();
197        assert_eq!(sel.len(), 4);
198    }
199    #[test]
200    pub(super) fn test_selector_deterministic() {
201        let f1 = SolidityFunction::new("approve");
202        let f2 = SolidityFunction::new("approve");
203        assert_eq!(f1.selector(), f2.selector());
204    }
205    #[test]
206    pub(super) fn test_selector_canonical_ethereum_values() {
207        // transfer(address,uint256) → 0xa9059cbb
208        let mut transfer = SolidityFunction::new("transfer");
209        transfer
210            .params
211            .push(SolidityParam::new(SolidityType::Address, "to"));
212        transfer
213            .params
214            .push(SolidityParam::new(SolidityType::Uint256, "amount"));
215        assert_eq!(
216            transfer.selector(),
217            [0xa9, 0x05, 0x9c, 0xbb],
218            "transfer(address,uint256) selector mismatch"
219        );
220
221        // balanceOf(address) → 0x70a08231
222        let mut balance_of = SolidityFunction::new("balanceOf");
223        balance_of
224            .params
225            .push(SolidityParam::new(SolidityType::Address, "account"));
226        assert_eq!(
227            balance_of.selector(),
228            [0x70, 0xa0, 0x82, 0x31],
229            "balanceOf(address) selector mismatch"
230        );
231
232        // approve(address,uint256) → 0x095ea7b3
233        let mut approve = SolidityFunction::new("approve");
234        approve
235            .params
236            .push(SolidityParam::new(SolidityType::Address, "spender"));
237        approve
238            .params
239            .push(SolidityParam::new(SolidityType::Uint256, "amount"));
240        assert_eq!(
241            approve.selector(),
242            [0x09, 0x5e, 0xa7, 0xb3],
243            "approve(address,uint256) selector mismatch"
244        );
245
246        // allowance(address,address) → 0xdd62ed3e
247        let mut allowance = SolidityFunction::new("allowance");
248        allowance
249            .params
250            .push(SolidityParam::new(SolidityType::Address, "owner"));
251        allowance
252            .params
253            .push(SolidityParam::new(SolidityType::Address, "spender"));
254        assert_eq!(
255            allowance.selector(),
256            [0xdd, 0x62, 0xed, 0x3e],
257            "allowance(address,address) selector mismatch"
258        );
259
260        // totalSupply() → 0x18160ddd
261        let total_supply = SolidityFunction::new("totalSupply");
262        assert_eq!(
263            total_supply.selector(),
264            [0x18, 0x16, 0x0d, 0xdd],
265            "totalSupply() selector mismatch"
266        );
267
268        // Additional well-known selector: decimals() → 0x313ce567
269        let decimals = SolidityFunction::new("decimals");
270        assert_eq!(
271            decimals.selector(),
272            [0x31, 0x3c, 0xe5, 0x67],
273            "decimals() selector mismatch"
274        );
275    }
276    #[test]
277    pub(super) fn test_msg_sender_display() {
278        assert_eq!(SolidityExpr::MsgSender.to_string(), "msg.sender");
279    }
280    #[test]
281    pub(super) fn test_binop_display() {
282        let expr = SolidityExpr::BinOp(
283            "+".into(),
284            Box::new(SolidityExpr::Var("a".into())),
285            Box::new(SolidityExpr::IntLit(1)),
286        );
287        assert_eq!(expr.to_string(), "(a + 1)");
288    }
289    #[test]
290    pub(super) fn test_ternary_display() {
291        let expr = SolidityExpr::Ternary(
292            Box::new(SolidityExpr::BoolLit(true)),
293            Box::new(SolidityExpr::IntLit(1)),
294            Box::new(SolidityExpr::IntLit(0)),
295        );
296        assert_eq!(expr.to_string(), "(true ? 1 : 0)");
297    }
298    #[test]
299    pub(super) fn test_keccak256_display() {
300        let expr = SolidityExpr::Keccak256(Box::new(SolidityExpr::Var("data".into())));
301        assert_eq!(expr.to_string(), "keccak256(data)");
302    }
303    #[test]
304    pub(super) fn test_type_max_display() {
305        let expr = SolidityExpr::TypeMax(SolidityType::Uint256);
306        assert_eq!(expr.to_string(), "type(uint256).max");
307    }
308    #[test]
309    pub(super) fn test_emit_empty_contract() {
310        let mut backend = SolidityBackend::new();
311        backend.add_contract(SolidityContract::new("Empty", ContractKind::Contract));
312        let src = backend.emit_contract();
313        assert!(src.contains("pragma solidity"));
314        assert!(src.contains("contract Empty {"));
315        assert!(src.contains("SPDX-License-Identifier: MIT"));
316    }
317    #[test]
318    pub(super) fn test_emit_interface() {
319        let mut backend = SolidityBackend::new();
320        let mut iface = SolidityContract::new("IERC20", ContractKind::Interface);
321        let mut func = SolidityFunction::new("totalSupply");
322        func.returns
323            .push(SolidityParam::new(SolidityType::Uint256, ""));
324        func.mutability = StateMutability::View;
325        func.body = vec![];
326        iface.functions.push(func);
327        backend.add_contract(iface);
328        let src = backend.emit_contract();
329        assert!(src.contains("interface IERC20 {"));
330        assert!(src.contains("function totalSupply()"));
331    }
332    #[test]
333    pub(super) fn test_emit_contract_with_state_var() {
334        let mut backend = SolidityBackend::new();
335        let mut contract = SolidityContract::new("Token", ContractKind::Contract);
336        contract.state_vars.push(SolidityStateVar {
337            ty: SolidityType::Mapping(
338                Box::new(SolidityType::Address),
339                Box::new(SolidityType::Uint256),
340            ),
341            name: "_balances".into(),
342            visibility: Visibility::Private,
343            is_immutable: false,
344            is_constant: false,
345            init: None,
346            doc: None,
347        });
348        backend.add_contract(contract);
349        let src = backend.emit_contract();
350        assert!(src.contains("mapping(address => uint256)"));
351        assert!(src.contains("_balances"));
352    }
353    #[test]
354    pub(super) fn test_emit_event() {
355        let mut backend = SolidityBackend::new();
356        let mut contract = SolidityContract::new("Token", ContractKind::Contract);
357        contract.events.push(SolidityEvent {
358            name: "Transfer".into(),
359            fields: vec![
360                (SolidityType::Address, true, "from".into()),
361                (SolidityType::Address, true, "to".into()),
362                (SolidityType::Uint256, false, "value".into()),
363            ],
364            anonymous: false,
365            doc: None,
366        });
367        backend.add_contract(contract);
368        let src = backend.emit_contract();
369        assert!(src.contains("event Transfer("));
370        assert!(src.contains("indexed from"));
371        assert!(src.contains("indexed to"));
372    }
373    #[test]
374    pub(super) fn test_emit_custom_error() {
375        let mut backend = SolidityBackend::new();
376        let mut contract = SolidityContract::new("Token", ContractKind::Contract);
377        contract.errors.push(SolidityError {
378            name: "InsufficientBalance".into(),
379            params: vec![
380                SolidityParam::new(SolidityType::Uint256, "available"),
381                SolidityParam::new(SolidityType::Uint256, "required"),
382            ],
383            doc: None,
384        });
385        backend.add_contract(contract);
386        let src = backend.emit_contract();
387        assert!(src.contains("error InsufficientBalance("));
388    }
389    #[test]
390    pub(super) fn test_emit_require_stmt() {
391        let stmt = SolidityStmt::Require(
392            SolidityExpr::BinOp(
393                ">".into(),
394                Box::new(SolidityExpr::Var("amount".into())),
395                Box::new(SolidityExpr::IntLit(0)),
396            ),
397            Some("Amount must be positive".into()),
398        );
399        let out = SolidityBackend::emit_stmt(&stmt, 2);
400        assert!(out.contains("require("));
401        assert!(out.contains("Amount must be positive"));
402    }
403    #[test]
404    pub(super) fn test_compile_decl() {
405        let mut backend = SolidityBackend::new();
406        let sv = backend.compile_decl("owner", SolidityType::Address);
407        assert_eq!(sv.name, "owner");
408        assert!(matches!(sv.ty, SolidityType::Address));
409    }
410    #[test]
411    pub(super) fn test_runtime_constant_not_empty() {
412        assert!(!SOLIDITY_RUNTIME.is_empty());
413        assert!(SOLIDITY_RUNTIME.contains("OxiLeanMath"));
414    }
415    #[test]
416    pub(super) fn test_visibility_display() {
417        assert_eq!(Visibility::Public.to_string(), "public");
418        assert_eq!(Visibility::External.to_string(), "external");
419        assert_eq!(Visibility::Private.to_string(), "private");
420        assert_eq!(Visibility::Internal.to_string(), "internal");
421    }
422    #[test]
423    pub(super) fn test_state_mutability_display() {
424        assert_eq!(StateMutability::View.to_string(), "view");
425        assert_eq!(StateMutability::Pure.to_string(), "pure");
426        assert_eq!(StateMutability::Payable.to_string(), "payable");
427        assert_eq!(StateMutability::NonPayable.to_string(), "");
428    }
429    #[test]
430    pub(super) fn test_emit_struct() {
431        let s = SolidityStruct {
432            name: "Position".into(),
433            fields: vec![
434                (SolidityType::Uint256, "x".into()),
435                (SolidityType::Uint256, "y".into()),
436            ],
437            doc: None,
438        };
439        let out = SolidityBackend::emit_struct(&s, 1);
440        assert!(out.contains("struct Position {"));
441        assert!(out.contains("uint256 x;"));
442        assert!(out.contains("uint256 y;"));
443    }
444    #[test]
445    pub(super) fn test_emit_enum() {
446        let e = SolidityEnum {
447            name: "State".into(),
448            variants: vec!["Created".into(), "Active".into(), "Closed".into()],
449            doc: None,
450        };
451        let out = SolidityBackend::emit_enum(&e, 1);
452        assert!(out.contains("enum State {"));
453        assert!(out.contains("Created"));
454        assert!(out.contains("Closed"));
455    }
456    #[test]
457    pub(super) fn test_emit_with_runtime() {
458        let mut backend = SolidityBackend::new().with_runtime();
459        backend.add_contract(SolidityContract::new("X", ContractKind::Contract));
460        let src = backend.emit_contract();
461        assert!(src.contains("OxiLeanMath"));
462        assert!(src.contains("saturatingAdd"));
463    }
464    #[test]
465    pub(super) fn test_emit_inheritance() {
466        let mut backend = SolidityBackend::new();
467        let mut contract = SolidityContract::new("MyToken", ContractKind::Contract);
468        contract.bases.push("ERC20".into());
469        contract.bases.push("Ownable".into());
470        backend.add_contract(contract);
471        let src = backend.emit_contract();
472        assert!(src.contains("contract MyToken is ERC20, Ownable {"));
473    }
474    #[test]
475    pub(super) fn test_address_payable_display() {
476        assert_eq!(SolidityType::AddressPayable.to_string(), "address payable");
477    }
478    #[test]
479    pub(super) fn test_payable_expr_display() {
480        let expr = SolidityExpr::Payable(Box::new(SolidityExpr::MsgSender));
481        assert_eq!(expr.to_string(), "payable(msg.sender)");
482    }
483}
484#[cfg(test)]
485mod Sol_infra_tests {
486    use super::*;
487    #[test]
488    pub(super) fn test_pass_config() {
489        let config = SolPassConfig::new("test_pass", SolPassPhase::Transformation);
490        assert!(config.enabled);
491        assert!(config.phase.is_modifying());
492        assert_eq!(config.phase.name(), "transformation");
493    }
494    #[test]
495    pub(super) fn test_pass_stats() {
496        let mut stats = SolPassStats::new();
497        stats.record_run(10, 100, 3);
498        stats.record_run(20, 200, 5);
499        assert_eq!(stats.total_runs, 2);
500        assert!((stats.average_changes_per_run() - 15.0).abs() < 0.01);
501        assert!((stats.success_rate() - 1.0).abs() < 0.01);
502        let s = stats.format_summary();
503        assert!(s.contains("Runs: 2/2"));
504    }
505    #[test]
506    pub(super) fn test_pass_registry() {
507        let mut reg = SolPassRegistry::new();
508        reg.register(SolPassConfig::new("pass_a", SolPassPhase::Analysis));
509        reg.register(SolPassConfig::new("pass_b", SolPassPhase::Transformation).disabled());
510        assert_eq!(reg.total_passes(), 2);
511        assert_eq!(reg.enabled_count(), 1);
512        reg.update_stats("pass_a", 5, 50, 2);
513        let stats = reg.get_stats("pass_a").expect("stats should exist");
514        assert_eq!(stats.total_changes, 5);
515    }
516    #[test]
517    pub(super) fn test_analysis_cache() {
518        let mut cache = SolAnalysisCache::new(10);
519        cache.insert("key1".to_string(), vec![1, 2, 3]);
520        assert!(cache.get("key1").is_some());
521        assert!(cache.get("key2").is_none());
522        assert!((cache.hit_rate() - 0.5).abs() < 0.01);
523        cache.invalidate("key1");
524        assert!(!cache.entries["key1"].valid);
525        assert_eq!(cache.size(), 1);
526    }
527    #[test]
528    pub(super) fn test_worklist() {
529        let mut wl = SolWorklist::new();
530        assert!(wl.push(1));
531        assert!(wl.push(2));
532        assert!(!wl.push(1));
533        assert_eq!(wl.len(), 2);
534        assert_eq!(wl.pop(), Some(1));
535        assert!(!wl.contains(1));
536        assert!(wl.contains(2));
537    }
538    #[test]
539    pub(super) fn test_dominator_tree() {
540        let mut dt = SolDominatorTree::new(5);
541        dt.set_idom(1, 0);
542        dt.set_idom(2, 0);
543        dt.set_idom(3, 1);
544        assert!(dt.dominates(0, 3));
545        assert!(dt.dominates(1, 3));
546        assert!(!dt.dominates(2, 3));
547        assert!(dt.dominates(3, 3));
548    }
549    #[test]
550    pub(super) fn test_liveness() {
551        let mut liveness = SolLivenessInfo::new(3);
552        liveness.add_def(0, 1);
553        liveness.add_use(1, 1);
554        assert!(liveness.defs[0].contains(&1));
555        assert!(liveness.uses[1].contains(&1));
556    }
557    #[test]
558    pub(super) fn test_constant_folding() {
559        assert_eq!(SolConstantFoldingHelper::fold_add_i64(3, 4), Some(7));
560        assert_eq!(SolConstantFoldingHelper::fold_div_i64(10, 0), None);
561        assert_eq!(SolConstantFoldingHelper::fold_div_i64(10, 2), Some(5));
562        assert_eq!(
563            SolConstantFoldingHelper::fold_bitand_i64(0b1100, 0b1010),
564            0b1000
565        );
566        assert_eq!(SolConstantFoldingHelper::fold_bitnot_i64(0), -1);
567    }
568    #[test]
569    pub(super) fn test_dep_graph() {
570        let mut g = SolDepGraph::new();
571        g.add_dep(1, 2);
572        g.add_dep(2, 3);
573        g.add_dep(1, 3);
574        assert_eq!(g.dependencies_of(2), vec![1]);
575        let topo = g.topological_sort();
576        assert_eq!(topo.len(), 3);
577        assert!(!g.has_cycle());
578        let pos: std::collections::HashMap<u32, usize> =
579            topo.iter().enumerate().map(|(i, &n)| (n, i)).collect();
580        assert!(pos[&1] < pos[&2]);
581        assert!(pos[&1] < pos[&3]);
582        assert!(pos[&2] < pos[&3]);
583    }
584}
585#[cfg(test)]
586mod solext_pass_tests {
587    use super::*;
588    #[test]
589    pub(super) fn test_solext_phase_order() {
590        assert_eq!(SolExtPassPhase::Early.order(), 0);
591        assert_eq!(SolExtPassPhase::Middle.order(), 1);
592        assert_eq!(SolExtPassPhase::Late.order(), 2);
593        assert_eq!(SolExtPassPhase::Finalize.order(), 3);
594        assert!(SolExtPassPhase::Early.is_early());
595        assert!(!SolExtPassPhase::Early.is_late());
596    }
597    #[test]
598    pub(super) fn test_solext_config_builder() {
599        let c = SolExtPassConfig::new("p")
600            .with_phase(SolExtPassPhase::Late)
601            .with_max_iter(50)
602            .with_debug(1);
603        assert_eq!(c.name, "p");
604        assert_eq!(c.max_iterations, 50);
605        assert!(c.is_debug_enabled());
606        assert!(c.enabled);
607        let c2 = c.disabled();
608        assert!(!c2.enabled);
609    }
610    #[test]
611    pub(super) fn test_solext_stats() {
612        let mut s = SolExtPassStats::new();
613        s.visit();
614        s.visit();
615        s.modify();
616        s.iterate();
617        assert_eq!(s.nodes_visited, 2);
618        assert_eq!(s.nodes_modified, 1);
619        assert!(s.changed);
620        assert_eq!(s.iterations, 1);
621        let e = s.efficiency();
622        assert!((e - 0.5).abs() < 1e-9);
623    }
624    #[test]
625    pub(super) fn test_solext_registry() {
626        let mut r = SolExtPassRegistry::new();
627        r.register(SolExtPassConfig::new("a").with_phase(SolExtPassPhase::Early));
628        r.register(SolExtPassConfig::new("b").disabled());
629        assert_eq!(r.len(), 2);
630        assert_eq!(r.enabled_passes().len(), 1);
631        assert_eq!(r.passes_in_phase(&SolExtPassPhase::Early).len(), 1);
632    }
633    #[test]
634    pub(super) fn test_solext_cache() {
635        let mut c = SolExtCache::new(4);
636        assert!(c.get(99).is_none());
637        c.put(99, vec![1, 2, 3]);
638        let v = c.get(99).expect("v should be present in map");
639        assert_eq!(v, &[1u8, 2, 3]);
640        assert!(c.hit_rate() > 0.0);
641        assert_eq!(c.live_count(), 1);
642    }
643    #[test]
644    pub(super) fn test_solext_worklist() {
645        let mut w = SolExtWorklist::new(10);
646        w.push(5);
647        w.push(3);
648        w.push(5);
649        assert_eq!(w.len(), 2);
650        assert!(w.contains(5));
651        let first = w.pop().expect("first should be available to pop");
652        assert!(!w.contains(first));
653    }
654    #[test]
655    pub(super) fn test_solext_dom_tree() {
656        let mut dt = SolExtDomTree::new(5);
657        dt.set_idom(1, 0);
658        dt.set_idom(2, 0);
659        dt.set_idom(3, 1);
660        dt.set_idom(4, 1);
661        assert!(dt.dominates(0, 3));
662        assert!(dt.dominates(1, 4));
663        assert!(!dt.dominates(2, 3));
664        assert_eq!(dt.depth_of(3), 2);
665    }
666    #[test]
667    pub(super) fn test_solext_liveness() {
668        let mut lv = SolExtLiveness::new(3);
669        lv.add_def(0, 1);
670        lv.add_use(1, 1);
671        assert!(lv.var_is_def_in_block(0, 1));
672        assert!(lv.var_is_used_in_block(1, 1));
673        assert!(!lv.var_is_def_in_block(1, 1));
674    }
675    #[test]
676    pub(super) fn test_solext_const_folder() {
677        let mut cf = SolExtConstFolder::new();
678        assert_eq!(cf.add_i64(3, 4), Some(7));
679        assert_eq!(cf.div_i64(10, 0), None);
680        assert_eq!(cf.mul_i64(6, 7), Some(42));
681        assert_eq!(cf.and_i64(0b1100, 0b1010), 0b1000);
682        assert_eq!(cf.fold_count(), 3);
683        assert_eq!(cf.failure_count(), 1);
684    }
685    #[test]
686    pub(super) fn test_solext_dep_graph() {
687        let mut g = SolExtDepGraph::new(4);
688        g.add_edge(0, 1);
689        g.add_edge(1, 2);
690        g.add_edge(2, 3);
691        assert!(!g.has_cycle());
692        assert_eq!(g.topo_sort(), Some(vec![0, 1, 2, 3]));
693        assert_eq!(g.reachable(0).len(), 4);
694        let sccs = g.scc();
695        assert_eq!(sccs.len(), 4);
696    }
697}