artichoke_backend/convert/
maybe_to_int.rs
1use artichoke_core::debug::Debug as _;
2use artichoke_core::value::Value as _;
3use spinoso_exception::TypeError;
4
5use crate::Artichoke;
6use crate::error::Error;
7use crate::value::Value;
8
9#[derive(Debug)]
10pub enum MaybeToInt {
11 Int(i64),
12 NotApplicable,
13 Err(TypeError),
14 UncriticalReturn(Value),
15}
16
17pub fn maybe_to_int(interp: &mut Artichoke, value: Value) -> Result<MaybeToInt, Error> {
29 if let Ok(int) = value.try_convert_into::<i64>(interp) {
30 return Ok(MaybeToInt::Int(int));
31 }
32 if value.respond_to(interp, "to_int")? {
33 let to_int = value.funcall(interp, "to_int", &[], None)?;
34 if let Ok(int) = to_int.try_convert_into::<i64>(interp) {
35 return Ok(MaybeToInt::Int(int));
36 }
37 }
38 if !value.respond_to(interp, "to_i")? {
39 return Ok(MaybeToInt::NotApplicable);
40 }
41 let to_i = value.funcall(interp, "to_i", &[], None)?;
42 if to_i.is_nil() {
43 let message = format!(
44 "can't convert {class} to Integer ({class}#to_i gives NilClass)",
45 class = interp.class_name_for_value(value)
46 );
47 return Ok(MaybeToInt::Err(TypeError::from(message)));
48 }
49 Ok(MaybeToInt::UncriticalReturn(to_i))
51}
52
53#[cfg(test)]
54mod tests {
55 use super::{MaybeToInt, maybe_to_int};
56 use crate::test::prelude::*;
57
58 #[test]
59 fn integer_is_returned() {
60 let mut interp = interpreter();
61 let int = interp.eval(b"5").unwrap();
62 assert!(matches!(maybe_to_int(&mut interp, int).unwrap(), MaybeToInt::Int(5)));
63 let int = interp.eval(b"-5").unwrap();
64 assert!(matches!(maybe_to_int(&mut interp, int).unwrap(), MaybeToInt::Int(-5)));
65 }
66
67 #[test]
68 fn object_is_is_not_applicable() {
69 let mut interp = interpreter();
70 let int = interp.eval(b"BasicObject.new").unwrap();
71 assert!(matches!(
72 maybe_to_int(&mut interp, int).unwrap(),
73 MaybeToInt::NotApplicable
74 ));
75 let int = interp.eval(b"Object.new").unwrap();
76 assert!(matches!(
77 maybe_to_int(&mut interp, int).unwrap(),
78 MaybeToInt::NotApplicable
79 ));
80 let int = interp.eval(b"[1, 2, 3]").unwrap();
81 assert!(matches!(
82 maybe_to_int(&mut interp, int).unwrap(),
83 MaybeToInt::NotApplicable
84 ));
85 }
86
87 #[test]
88 fn conversion_with_to_int_not_applicable_to_i_nil_is_err() {
89 let mut interp = interpreter();
90 let int = interp
91 .eval(b"class A; def to_int; Object.new; end; def to_i; nil; end; end; A.new")
92 .unwrap();
93 assert!(matches!(
94 maybe_to_int(&mut interp, int).unwrap(),
95 MaybeToInt::Err(err) if err.message() == b"can't convert A to Integer (A#to_i gives NilClass)"
96 ));
97 }
98
99 #[test]
100 fn conversion_without_to_to_i_is_nil_is_err() {
101 let mut interp = interpreter();
102 let int = interp.eval(b"class A; def to_i; nil; end; end; A.new").unwrap();
103 assert!(matches!(
104 maybe_to_int(&mut interp, int).unwrap(),
105 MaybeToInt::Err(err) if err.message() == b"can't convert A to Integer (A#to_i gives NilClass)"
106 ));
107 }
108
109 #[test]
110 fn conversion_without_to_to_i_non_nil_is_uncritically_returned() {
111 let mut interp = interpreter();
112 let int = interp.eval(b"class A; def to_i; Object.new; end; end; A.new").unwrap();
113 assert!(matches!(
114 maybe_to_int(&mut interp, int).unwrap(),
115 MaybeToInt::UncriticalReturn(..)
116 ));
117 }
118
119 #[test]
120 fn conversion_with_to_int_is_returned() {
121 let mut interp = interpreter();
122 let int = interp.eval(b"class A; def to_int; 99; end; end; A.new").unwrap();
123 assert!(matches!(maybe_to_int(&mut interp, int).unwrap(), MaybeToInt::Int(99)));
124 }
125
126 #[test]
127 fn conversion_with_raising_to_int_is_error() {
128 let mut interp = interpreter();
129 let int = interp
130 .eval(b"class A; def to_int; raise 'bonk'; end; end; A.new")
131 .unwrap();
132 maybe_to_int(&mut interp, int).unwrap_err();
133 }
134
135 #[test]
136 fn conversion_with_unapplicable_to_int_is_not_applicable() {
137 let mut interp = interpreter();
138 let int = interp.eval(b"class A; def to_int; [1, 2, 3]; end; end; A.new").unwrap();
139 assert!(matches!(
140 maybe_to_int(&mut interp, int).unwrap(),
141 MaybeToInt::NotApplicable
142 ));
143 let int = interp.eval(b"class B; def to_int; 'rip'; end; end; B.new").unwrap();
144 assert!(matches!(
145 maybe_to_int(&mut interp, int).unwrap(),
146 MaybeToInt::NotApplicable
147 ));
148 }
149}