artichoke_backend/convert/
implicit.rs

1use core::mem;
2
3use artichoke_core::debug::Debug as _;
4use artichoke_core::value::Value as _;
5use spinoso_exception::TypeError;
6
7use crate::Artichoke;
8use crate::convert::{BoxUnboxVmValue, UnboxedValueGuard};
9use crate::error::Error;
10use crate::types::Ruby;
11use crate::value::Value;
12
13/// Attempt to implicitly convert a [`Value`] to an integer.
14///
15/// Attempt to extract an [`i64`] from the given `Value` by trying the following
16/// conversions:
17///
18/// - If the given value is a Ruby integer, return the inner integer.
19/// - If the given value is `nil`, return a [`TypeError`].
20/// - If the given value responds to the `:to_int` method, call `value.to_int`:
21///   - If `value.to_int` raises an exception, propagate that exception.
22///   - If `value.to_int` returns a Ruby integer, return the inner integer.
23///   - If `value.to_int` returs any other type, return a [`TypeError`].
24/// - If the given value does not respond to the `:to_int` method, return a
25///   [`TypeError`].
26///
27/// Floats and other numeric types are not coerced to integer by this implicit
28/// conversion.
29///
30/// # Examples
31///
32/// ```
33/// # use artichoke_backend::prelude::*;
34/// # use artichoke_backend::convert::implicitly_convert_to_int;
35/// # use artichoke_backend::value::Value;
36/// # fn example() -> Result<(), Error> {
37/// let mut interp = artichoke_backend::interpreter()?;
38/// // successful conversions
39/// let integer = interp.convert(17);
40/// let a = interp.eval(b"class A; def to_int; 3; end; end; A.new")?;
41///
42/// assert!(matches!(
43///     implicitly_convert_to_int(&mut interp, integer),
44///     Ok(17)
45/// ));
46/// assert!(matches!(implicitly_convert_to_int(&mut interp, a), Ok(3)));
47///
48/// // failed conversions
49/// let nil = Value::nil();
50/// let b = interp.eval(b"class B; end; B.new")?;
51/// let c = interp.eval(b"class C; def to_int; nil; end; end; C.new")?;
52/// let d = interp.eval(b"class D; def to_int; 'not an integer'; end; end; D.new")?;
53/// let e = interp.eval(b"class E; def to_int; 3.2; end; end; E.new")?;
54/// let f = interp
55///     .eval(b"class F; def to_int; raise ArgumentError, 'not an integer'; end; end; F.new")?;
56///
57/// assert!(implicitly_convert_to_int(&mut interp, nil).is_err());
58/// assert!(implicitly_convert_to_int(&mut interp, b).is_err());
59/// assert!(implicitly_convert_to_int(&mut interp, c).is_err());
60/// assert!(implicitly_convert_to_int(&mut interp, d).is_err());
61/// assert!(implicitly_convert_to_int(&mut interp, e).is_err());
62/// assert!(implicitly_convert_to_int(&mut interp, f).is_err());
63/// # Ok(())
64/// # }
65/// # example().unwrap();
66/// ```
67///
68/// # Errors
69///
70/// This function returns an error if:
71///
72/// - The given value is `nil`.
73/// - The given value is not an integer and does not respond to `:to_int`.
74/// - The given value is not an integer and returns a non-integer value from its
75///   `:to_int` method.
76/// - The given value is not an integer and raises an error in its `:to_int`
77///   method.
78pub fn implicitly_convert_to_int(interp: &mut Artichoke, value: Value) -> Result<i64, Error> {
79    match value.try_convert_into::<Option<i64>>(interp) {
80        // successful conversion: the given value is an integer.
81        Ok(Some(num)) => return Ok(num),
82        // `nil` does not implicitly convert to integer:
83        //
84        // ```console
85        // [2.6.6] > a = [1, 2, 3, 4, 5]
86        // => [1, 2, 3, 4, 5]
87        // [2.6.6] > a[nil]
88        // Traceback (most recent call last):
89        //         4: from /usr/local/var/rbenv/versions/2.6.6/bin/irb:23:in `<main>'
90        //         3: from /usr/local/var/rbenv/versions/2.6.6/bin/irb:23:in `load'
91        //         2: from /usr/local/var/rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
92        //         1: from (irb):2
93        // TypeError (no implicit conversion from nil to integer)
94        // ```
95        Ok(None) => return Err(TypeError::with_message("no implicit conversion from nil to integer").into()),
96        Err(_) => {}
97    }
98
99    let Ok(true) = value.respond_to(interp, "to_int") else {
100        // The given value is not an integer and cannot be converted with
101        // `#to_int`:
102        //
103        // ```console
104        // [2.6.6] > a = [1, 2, 3, 4, 5]
105        // => [1, 2, 3, 4, 5]
106        // [2.6.6] > a[true]
107        // Traceback (most recent call last):
108        //         5: from /usr/local/var/rbenv/versions/2.6.6/bin/irb:23:in `<main>'
109        //         4: from /usr/local/var/rbenv/versions/2.6.6/bin/irb:23:in `load'
110        //         3: from /usr/local/var/rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
111        //         2: from (irb):10
112        //         1: from (irb):10:in `rescue in irb_binding'
113        // TypeError (no implicit conversion of true into Integer)
114        // [2.6.6] > class A; end
115        // => nil
116        // [2.6.6] > a[A.new]
117        // Traceback (most recent call last):
118        //         4: from /usr/local/var/rbenv/versions/2.6.6/bin/irb:23:in `<main>'
119        //         3: from /usr/local/var/rbenv/versions/2.6.6/bin/irb:23:in `load'
120        //         2: from /usr/local/var/rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
121        //         1: from (irb):5
122        // TypeError (no implicit conversion of A into Integer)
123        // ```
124        let mut message = String::from("no implicit conversion of ");
125        message.push_str(interp.inspect_type_name_for_value(value));
126        message.push_str(" into Integer");
127        return Err(TypeError::from(message).into());
128    };
129
130    // Propagate exceptions raised in `#to_int`:
131    //
132    // ```console
133    // [2.6.6] > a = [1, 2, 3, 4, 5]
134    // => [1, 2, 3, 4, 5]
135    // [2.6.6] > class A; def to_int; raise ArgumentError, 'a message'; end; end
136    // => :to_int
137    // [2.6.6] > a[A.new]
138    // Traceback (most recent call last):
139    //         5: from /usr/local/var/rbenv/versions/2.6.6/bin/irb:23:in `<main>'
140    //         4: from /usr/local/var/rbenv/versions/2.6.6/bin/irb:23:in `load'
141    //         3: from /usr/local/var/rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
142    //         2: from (irb):3
143    //         1: from (irb):2:in `to_int'
144    // ArgumentError (a message)
145    // ```
146    let maybe = value.funcall(interp, "to_int", &[], None)?;
147
148    let Ok(num) = maybe.try_convert_into::<i64>(interp) else {
149        // Non integer types returned from `#to_int`, even other numerics,
150        // result in a `TypeError`:
151        //
152        // ```console
153        // [2.6.6] > a = [1, 2, 3, 4, 5]
154        // => [1, 2, 3, 4, 5]
155        // [2.6.6] > class A; def to_int; "another string"; end; end
156        // => :to_int
157        // [2.6.6] > a[A.new]
158        // Traceback (most recent call last):
159        //         4: from /usr/local/var/rbenv/versions/2.6.6/bin/irb:23:in `<main>'
160        //         3: from /usr/local/var/rbenv/versions/2.6.6/bin/irb:23:in `load'
161        //         2: from /usr/local/var/rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
162        //         1: from (irb):3
163        // TypeError (can't convert A to Integer (A#to_int gives String))
164        // [2.6.6] > class B; def to_int; 3.2; end; end
165        // => :to_int
166        // [2.6.6] > a[B.new]
167        // Traceback (most recent call last):
168        //         4: from /usr/local/var/rbenv/versions/2.6.6/bin/irb:23:in `<main>'
169        //         3: from /usr/local/var/rbenv/versions/2.6.6/bin/irb:23:in `load'
170        //         2: from /usr/local/var/rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
171        //         1: from (irb):5
172        // TypeError (can't convert B to Integer (B#to_int gives Float))
173        // [2.6.6] > class C; def to_int; nil; end; end
174        // => :to_int
175        // [2.6.6] > a[C.new]
176        // Traceback (most recent call last):
177        //         4: from /usr/local/var/rbenv/versions/2.6.6/bin/irb:23:in `<main>'
178        //         3: from /usr/local/var/rbenv/versions/2.6.6/bin/irb:23:in `load'
179        //         2: from /usr/local/var/rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
180        //         1: from (irb):7
181        // TypeError (can't convert C to Integer (C#to_int gives NilClass))
182        // [2.6.6] > module X; class Y; class Z; def to_int; "oh no"; end; end; end; end
183        // => :to_int
184        // [2.6.6] > a[X::Y::Z.new]
185        // Traceback (most recent call last):
186        //         4: from /usr/local/var/rbenv/versions/2.6.6/bin/irb:23:in `<main>'
187        //         3: from /usr/local/var/rbenv/versions/2.6.6/bin/irb:23:in `load'
188        //         2: from /usr/local/var/rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
189        //         1: from (irb):9
190        // TypeError (can't convert X::Y::Z to Integer (X::Y::Z#to_int gives String))
191        // ```
192        let mut message = String::from("can't convert ");
193        let name = interp.inspect_type_name_for_value(value);
194        message.push_str(name);
195        message.push_str(" to Integer (");
196        message.push_str(name);
197        message.push_str("#to_int gives ");
198        message.push_str(interp.class_name_for_value(maybe));
199        message.push(')');
200        return Err(TypeError::from(message).into());
201    };
202
203    // successful conversion: `#to_int` returned an integer.
204    Ok(num)
205}
206
207/// Attempt to implicitly convert a [`Value`] to a byte slice (Ruby `String`).
208///
209/// Attempt to extract a `&[u8]` from the given `Value` by trying the following
210/// conversions:
211///
212/// - If the given value is a Ruby string, return the inner byte slice.
213/// - If the given value is `nil`, return a [`TypeError`].
214/// - If the given value responds to the `:to_str` method, call `value.to_str`:
215///   - If `value.to_str` raises an exception, propagate that exception.
216///   - If `value.to_str` returns a Ruby string, return the inner byte slice.
217///   - If `value.to_str` returs any other type, return a [`TypeError`].
218/// - If the given value does not respond to the `:to_str` method, return a
219///   [`TypeError`].
220///
221/// [`Symbol`]s are not coerced to byte slice by this implicit conversion.
222///
223/// # Examples
224///
225/// ```
226/// # use artichoke_backend::prelude::*;
227/// # use artichoke_backend::convert::implicitly_convert_to_string;
228/// # use artichoke_backend::value::Value;
229/// # fn example() -> Result<(), Error> {
230/// let mut interp = artichoke_backend::interpreter()?;
231/// // successful conversions
232/// let mut string = interp.try_convert_mut("artichoke")?;
233/// let mut a = interp.eval(b"class A; def to_str; 'spinoso'; end; end; A.new")?;
234///
235/// # unsafe {
236/// assert!(matches!(implicitly_convert_to_string(&mut interp, &mut string), Ok(s) if *s == b"artichoke"[..]));
237/// assert!(matches!(implicitly_convert_to_string(&mut interp, &mut a), Ok(s) if *s == b"spinoso"[..]));
238///
239/// // failed conversions
240/// let mut nil = Value::nil();
241/// let mut b = interp.eval(b"class B; end; B.new")?;
242/// let mut c = interp.eval(b"class C; def to_str; nil; end; end; C.new")?;
243/// let mut d = interp.eval(b"class D; def to_str; 17; end; end; D.new")?;
244/// let mut e = interp.eval(b"class E; def to_str; :artichoke; end; end; E.new")?;
245/// let mut f = interp.eval(b"class F; def to_str; raise ArgumentError, 'not a string'; end; end; F.new")?;
246///
247/// assert!(implicitly_convert_to_string(&mut interp, &mut nil).is_err());
248/// assert!(implicitly_convert_to_string(&mut interp, &mut b).is_err());
249/// assert!(implicitly_convert_to_string(&mut interp, &mut c).is_err());
250/// assert!(implicitly_convert_to_string(&mut interp, &mut d).is_err());
251/// assert!(implicitly_convert_to_string(&mut interp, &mut e).is_err());
252/// assert!(implicitly_convert_to_string(&mut interp, &mut f).is_err());
253/// # }
254/// # Ok(())
255/// # }
256/// # example().unwrap();
257/// ```
258///
259/// # Errors
260///
261/// This function returns an error if:
262///
263/// - The given value is `nil`.
264/// - The given value is not a string and does not respond to `:to_str`.
265/// - The given value is not a string and returns a non-string value from its
266///   `:to_str` method.
267/// - The given value is not a string and raises an error in its `:to_str`
268///   method.
269///
270/// # Safety
271///
272/// Callers must ensure that `value` does not outlive the given interpreter.
273///
274/// If a garbage collection can possibly run between calling this function and
275/// using the returned slice, callers should convert the slice to an owned byte
276/// vec.
277///
278/// [`Symbol`]: crate::extn::core::symbol::Symbol
279pub unsafe fn implicitly_convert_to_string<'a>(
280    interp: &mut Artichoke,
281    value: &'a mut Value,
282) -> Result<&'a [u8], Error> {
283    match value.try_convert_into_mut::<Option<&'a [u8]>>(interp) {
284        // successful conversion: the given value is an string.
285        Ok(Some(s)) => return Ok(s),
286        // `nil` does not implicitly convert to string:
287        //
288        // ```console
289        // [2.6.6] > ENV[nil]
290        // Traceback (most recent call last):
291        //         6: from /usr/local/var/rbenv/versions/2.6.6/bin/irb:23:in `<main>'
292        //         5: from /usr/local/var/rbenv/versions/2.6.6/bin/irb:23:in `load'
293        //         4: from /usr/local/var/rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
294        //         3: from (irb):7
295        //         2: from (irb):7:in `rescue in irb_binding'
296        //         1: from (irb):7:in `[]'
297        // TypeError (no implicit conversion of nil into String)
298        // ```
299        Ok(None) => return Err(TypeError::with_message("no implicit conversion of nil into String").into()),
300        Err(_) => {}
301    }
302    if let Ruby::Symbol = value.ruby_type() {
303        return Err(TypeError::with_message("no implicit conversion of Symbol into String").into());
304    }
305    let Ok(true) = value.respond_to(interp, "to_str") else {
306        // The given value is not a string and cannot be converted with
307        // `#to_str`:
308        //
309        // ```console
310        // [2.6.6] > ENV[true]
311        // Traceback (most recent call last):
312        //         5: from /usr/local/var/rbenv/versions/2.6.6/bin/irb:23:in `<main>'
313        //         4: from /usr/local/var/rbenv/versions/2.6.6/bin/irb:23:in `load'
314        //         3: from /usr/local/var/rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
315        //         2: from (irb):1
316        //         1: from (irb):1:in `[]'
317        // TypeError (no implicit conversion of true into String)
318        // [2.6.6] > class A; end
319        // => nil
320        // [2.6.6] > ENV[A.new]
321        // Traceback (most recent call last):
322        //         5: from /usr/local/var/rbenv/versions/2.6.6/bin/irb:23:in `<main>'
323        //         4: from /usr/local/var/rbenv/versions/2.6.6/bin/irb:23:in `load'
324        //         3: from /usr/local/var/rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
325        //         2: from (irb):3
326        //         1: from (irb):3:in `[]'
327        // TypeError (no implicit conversion of A into String)
328        // ```
329        let mut message = String::from("no implicit conversion of ");
330        message.push_str(interp.inspect_type_name_for_value(*value));
331        message.push_str(" into String");
332        return Err(TypeError::from(message).into());
333    };
334
335    // Propagate exceptions raised in `#to_str`:
336    //
337    // ```console
338    // [2.6.6] >  class A; def to_str; raise ArgumentError, 'a message'; end; end
339    // => :to_str
340    // [2.6.6] > ENV[A.new]
341    // Traceback (most recent call last):
342    //         6: from /usr/local/var/rbenv/versions/2.6.6/bin/irb:23:in `<main>'
343    //         5: from /usr/local/var/rbenv/versions/2.6.6/bin/irb:23:in `load'
344    //         4: from /usr/local/var/rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
345    //         3: from (irb):10
346    //         2: from (irb):10:in `[]'
347    //         1: from (irb):9:in `to_str'
348    // ArgumentError (a message)
349    // ```
350    let maybe = value.funcall(interp, "to_str", &[], None)?;
351
352    let Ok(s) = maybe.try_convert_into_mut::<&[u8]>(interp) else {
353        // Non `String` types returned from `#to_str` result in a
354        // `TypeError`:
355        //
356        // ```console
357        // [2.6.6] > class A; def to_str; 17; end; end
358        // => :to_str
359        // [2.6.6] > ENV[A.new]
360        // Traceback (most recent call last):
361        //         5: from /usr/local/var/rbenv/versions/2.6.6/bin/irb:23:in `<main>'
362        //         4: from /usr/local/var/rbenv/versions/2.6.6/bin/irb:23:in `load'
363        //         3: from /usr/local/var/rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
364        //         2: from (irb):5
365        //         1: from (irb):5:in `[]'
366        // TypeError (can't convert A to String (A#to_str gives Integer))
367        // [2.6.6] > class B; def to_str; true; end; end
368        // => :to_str
369        // [2.6.6] > ENV[B.new]
370        // Traceback (most recent call last):
371        //         5: from /usr/local/var/rbenv/versions/2.6.6/bin/irb:23:in `<main>'
372        //         4: from /usr/local/var/rbenv/versions/2.6.6/bin/irb:23:in `load'
373        //         3: from /usr/local/var/rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
374        //         2: from (irb):7
375        //         1: from (irb):7:in `[]'
376        // TypeError (can't convert B to String (B#to_str gives TrueClass))
377        // [2.6.6] > class C; def to_str; nil; end; end
378        // => :to_str
379        // [2.6.6] > ENV[C.new]
380        // Traceback (most recent call last):
381        //         5: from /usr/local/var/rbenv/versions/2.6.6/bin/irb:23:in `<main>'
382        //         4: from /usr/local/var/rbenv/versions/2.6.6/bin/irb:23:in `load'
383        //         3: from /usr/local/var/rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
384        //         2: from (irb):9
385        //         1: from (irb):9:in `[]'
386        // TypeError (can't convert C to String (C#to_str gives NilClass))
387        // [2.6.6] > module X; class Y; class Z; def to_str; 92; end; end; end; end
388        // => :to_str
389        // [2.6.6] > ENV[X::Y::Z.new]
390        // Traceback (most recent call last):
391        //         5: from /usr/local/var/rbenv/versions/2.6.6/bin/irb:23:in `<main>'
392        //         4: from /usr/local/var/rbenv/versions/2.6.6/bin/irb:23:in `load'
393        //         3: from /usr/local/var/rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
394        //         2: from (irb):11
395        //         1: from (irb):11:in `[]'
396        // TypeError (can't convert X::Y::Z to String (X::Y::Z#to_str gives Integer))
397        // ```
398        let mut message = String::from("can't convert ");
399        let name = interp.inspect_type_name_for_value(*value);
400        message.push_str(name);
401        message.push_str(" to String (");
402        message.push_str(name);
403        message.push_str("#to_str gives ");
404        message.push_str(interp.class_name_for_value(maybe));
405        message.push(')');
406        return Err(TypeError::from(message).into());
407    };
408
409    // successful conversion: `#to_str` returned a string.
410    Ok(s)
411}
412
413/// Attempt to implicitly convert a [`Value`] to an optional byte slice (nilable
414/// Ruby `String`).
415///
416/// Attempt to extract a `Option<&[u8]>` from the given `Value` by trying the
417/// following conversions:
418///
419/// - If the given value is a Ruby string, return the inner byte slice.
420/// - If the given value is `nil`, return [`None`].
421/// - If the given value responds to the `:to_str` method, call `value.to_str`:
422///   - If `value.to_str` raises an exception, propagate that exception.
423///   - If `value.to_str` returns a Ruby string, return the inner byte slice.
424///   - If `value.to_str` returs any other type, including `nil`, return a
425///     [`TypeError`].
426/// - If the given value does not respond to the `:to_str` method, return a
427///   [`TypeError`].
428///
429/// [`Symbol`]s are not coerced to byte slice by this implicit conversion.
430///
431/// # Examples
432///
433/// ```
434/// # use artichoke_backend::prelude::*;
435/// # use artichoke_backend::convert::implicitly_convert_to_nilable_string;
436/// # use artichoke_backend::value::Value;
437/// # fn example() -> Result<(), Error> {
438/// let mut interp = artichoke_backend::interpreter()?;
439/// // successful conversions
440/// let mut string = interp.try_convert_mut("artichoke")?;
441/// let mut nil = Value::nil();
442/// let mut a = interp.eval(b"class A; def to_str; 'spinoso'; end; end; A.new")?;
443///
444/// # unsafe {
445/// assert!(matches!(implicitly_convert_to_nilable_string(&mut interp, &mut string), Ok(Some(s)) if *s == b"artichoke"[..]));
446/// assert!(matches!(implicitly_convert_to_nilable_string(&mut interp, &mut nil), Ok(None)));
447/// assert!(matches!(implicitly_convert_to_nilable_string(&mut interp, &mut a), Ok(Some(s)) if *s == b"spinoso"[..]));
448///
449/// // failed conversions
450/// let mut b = interp.eval(b"class B; end; B.new")?;
451/// let mut c = interp.eval(b"class C; def to_str; nil; end; end; C.new")?;
452/// let mut d = interp.eval(b"class D; def to_str; 17; end; end; D.new")?;
453/// let mut e = interp.eval(b"class E; def to_str; :artichoke; end; end; E.new")?;
454/// let mut f = interp.eval(b"class F; def to_str; raise ArgumentError, 'not a string'; end; end; F.new")?;
455/// let mut g = interp.eval(b"class G; def to_str; nil; end; end; G.new")?;
456///
457/// assert!(implicitly_convert_to_nilable_string(&mut interp, &mut b).is_err());
458/// assert!(implicitly_convert_to_nilable_string(&mut interp, &mut c).is_err());
459/// assert!(implicitly_convert_to_nilable_string(&mut interp, &mut d).is_err());
460/// assert!(implicitly_convert_to_nilable_string(&mut interp, &mut e).is_err());
461/// assert!(implicitly_convert_to_nilable_string(&mut interp, &mut f).is_err());
462/// assert!(implicitly_convert_to_nilable_string(&mut interp, &mut g).is_err());
463/// # }
464/// # Ok(())
465/// # }
466/// # example().unwrap();
467/// ```
468///
469/// # Errors
470///
471/// This function returns an error if:
472///
473/// - The given value is `nil`.
474/// - The given value is not a string and does not respond to `:to_str`.
475/// - The given value is not a string and returns a non-string value from its
476///   `:to_str` method.
477/// - The given value is not a string and raises an error in its `:to_str`
478///   method.
479///
480/// # Safety
481///
482/// Callers must ensure that `value` does not outlive the given interpreter.
483///
484/// [`Symbol`]: crate::extn::core::symbol::Symbol
485pub unsafe fn implicitly_convert_to_nilable_string<'a>(
486    interp: &mut Artichoke,
487    value: &'a mut Value,
488) -> Result<Option<&'a [u8]>, Error> {
489    if value.is_nil() {
490        Ok(None)
491    } else {
492        // SAFETY: `implicitly_convert_to_string` has the same safety contract
493        // as this function.
494        let string = unsafe { implicitly_convert_to_string(interp, value)? };
495        Ok(Some(string))
496    }
497}
498
499/// Attempt to implicitly convert a [`Value`] to a [`spinoso_string::String`]
500/// (Ruby `String`).
501///
502/// Attempt to extract a [`spinoso_string::String`] from the given `Value` by
503/// trying the following conversions:
504///
505/// - If the given value is a Ruby string, return the inner byte slice.
506/// - If the given value is `nil`, return a [`TypeError`].
507/// - If the given value responds to the `:to_str` method, call `value.to_str`:
508///   - If `value.to_str` raises an exception, propagate that exception.
509///   - If `value.to_str` returns a Ruby string, return the inner byte slice.
510///   - If `value.to_str` returs any other type, return a [`TypeError`].
511/// - If the given value does not respond to the `:to_str` method, return a
512///   [`TypeError`].
513///
514/// [`Symbol`]s are not coerced to byte slice by this implicit conversion.
515///
516/// # Examples
517///
518/// ```
519/// # use artichoke_backend::prelude::*;
520/// # use artichoke_backend::convert::implicitly_convert_to_spinoso_string;
521/// # use artichoke_backend::value::Value;
522/// # fn example() -> Result<(), Error> {
523/// let mut interp = artichoke_backend::interpreter()?;
524/// // successful conversions
525/// let mut string = interp.try_convert_mut("artichoke")?;
526/// let mut a = interp.eval(b"class A; def to_str; 'spinoso'; end; end; A.new")?;
527///
528/// # unsafe {
529/// assert!(matches!(implicitly_convert_to_spinoso_string(&mut interp, &mut string), Ok(s) if *s == b"artichoke"[..]));
530/// assert!(matches!(implicitly_convert_to_spinoso_string(&mut interp, &mut a), Ok(s) if *s == b"spinoso"[..]));
531///
532/// // failed conversions
533/// let mut nil = Value::nil();
534/// let mut b = interp.eval(b"class B; end; B.new")?;
535/// let mut c = interp.eval(b"class C; def to_str; nil; end; end; C.new")?;
536/// let mut d = interp.eval(b"class D; def to_str; 17; end; end; D.new")?;
537/// let mut e = interp.eval(b"class E; def to_str; :artichoke; end; end; E.new")?;
538/// let mut f = interp.eval(b"class F; def to_str; raise ArgumentError, 'not a string'; end; end; F.new")?;
539///
540/// assert!(implicitly_convert_to_spinoso_string(&mut interp, &mut nil).is_err());
541/// assert!(implicitly_convert_to_spinoso_string(&mut interp, &mut b).is_err());
542/// assert!(implicitly_convert_to_spinoso_string(&mut interp, &mut c).is_err());
543/// assert!(implicitly_convert_to_spinoso_string(&mut interp, &mut d).is_err());
544/// assert!(implicitly_convert_to_spinoso_string(&mut interp, &mut e).is_err());
545/// assert!(implicitly_convert_to_spinoso_string(&mut interp, &mut f).is_err());
546/// # }
547/// # Ok(())
548/// # }
549/// # example().unwrap();
550/// ```
551///
552/// # Errors
553///
554/// This function returns an error if:
555///
556/// - The given value is `nil`.
557/// - The given value is not a string and does not respond to `:to_str`.
558/// - The given value is not a string and returns a non-string value from its
559///   `:to_str` method.
560/// - The given value is not a string and raises an error in its `:to_str`
561///   method.
562///
563/// # Safety
564///
565/// Callers must ensure that `value` does not outlive the given interpreter.
566///
567/// If a garbage collection can possibly run between calling this function and
568/// using the returned slice, callers should convert the slice to an owned byte
569/// vec.
570///
571/// [`Symbol`]: crate::extn::core::symbol::Symbol
572pub unsafe fn implicitly_convert_to_spinoso_string<'a>(
573    interp: &mut Artichoke,
574    value: &'a mut Value,
575) -> Result<UnboxedValueGuard<'a, spinoso_string::String>, Error> {
576    let value_copy = *value;
577    if value.is_nil() {
578        // `nil` does not implicitly convert to string:
579        //
580        // ```console
581        // [2.6.6] > ENV[nil]
582        // Traceback (most recent call last):
583        //         6: from /usr/local/var/rbenv/versions/2.6.6/bin/irb:23:in `<main>'
584        //         5: from /usr/local/var/rbenv/versions/2.6.6/bin/irb:23:in `load'
585        //         4: from /usr/local/var/rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
586        //         3: from (irb):7
587        //         2: from (irb):7:in `rescue in irb_binding'
588        //         1: from (irb):7:in `[]'
589        // TypeError (no implicit conversion of nil into String)
590        // ```
591        return Err(TypeError::with_message("no implicit conversion of nil into String").into());
592    }
593
594    if let Ruby::Symbol = value.ruby_type() {
595        return Err(TypeError::with_message("no implicit conversion of Symbol into String").into());
596    }
597
598    // SAFETY: caller must uphold that the value is bound to the same
599    // Artichoke interpreter.
600    if let Ok(s) = unsafe { spinoso_string::String::unbox_from_value(value, interp) } {
601        return Ok(s);
602    }
603
604    let value = value_copy;
605
606    let Ok(true) = value.respond_to(interp, "to_str") else {
607        // The given value is not a string and cannot be converted with
608        // `#to_str`:
609        //
610        // ```console
611        // [2.6.6] > ENV[true]
612        // Traceback (most recent call last):
613        //         5: from /usr/local/var/rbenv/versions/2.6.6/bin/irb:23:in `<main>'
614        //         4: from /usr/local/var/rbenv/versions/2.6.6/bin/irb:23:in `load'
615        //         3: from /usr/local/var/rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
616        //         2: from (irb):1
617        //         1: from (irb):1:in `[]'
618        // TypeError (no implicit conversion of true into String)
619        // [2.6.6] > class A; end
620        // => nil
621        // [2.6.6] > ENV[A.new]
622        // Traceback (most recent call last):
623        //         5: from /usr/local/var/rbenv/versions/2.6.6/bin/irb:23:in `<main>'
624        //         4: from /usr/local/var/rbenv/versions/2.6.6/bin/irb:23:in `load'
625        //         3: from /usr/local/var/rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
626        //         2: from (irb):3
627        //         1: from (irb):3:in `[]'
628        // TypeError (no implicit conversion of A into String)
629        // ```
630        let mut message = String::from("no implicit conversion of ");
631        message.push_str(interp.inspect_type_name_for_value(value));
632        message.push_str(" into String");
633        return Err(TypeError::from(message).into());
634    };
635
636    // Propagate exceptions raised in `#to_str`:
637    //
638    // ```console
639    // [2.6.6] >  class A; def to_str; raise ArgumentError, 'a message'; end; end
640    // => :to_str
641    // [2.6.6] > ENV[A.new]
642    // Traceback (most recent call last):
643    //         6: from /usr/local/var/rbenv/versions/2.6.6/bin/irb:23:in `<main>'
644    //         5: from /usr/local/var/rbenv/versions/2.6.6/bin/irb:23:in `load'
645    //         4: from /usr/local/var/rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
646    //         3: from (irb):10
647    //         2: from (irb):10:in `[]'
648    //         1: from (irb):9:in `to_str'
649    // ArgumentError (a message)
650    // ```
651    let mut maybe = value.funcall(interp, "to_str", &[], None)?;
652
653    // SAFETY: caller must uphold that the value is bound to the same Artichoke
654    // interpreter.
655    let Ok(s) = (unsafe { spinoso_string::String::unbox_from_value(&mut maybe, interp) }) else {
656        // Non `String` types returned from `#to_str` result in a
657        // `TypeError`:
658        //
659        // ```console
660        // [2.6.6] > class A; def to_str; 17; end; end
661        // => :to_str
662        // [2.6.6] > ENV[A.new]
663        // Traceback (most recent call last):
664        //         5: from /usr/local/var/rbenv/versions/2.6.6/bin/irb:23:in `<main>'
665        //         4: from /usr/local/var/rbenv/versions/2.6.6/bin/irb:23:in `load'
666        //         3: from /usr/local/var/rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
667        //         2: from (irb):5
668        //         1: from (irb):5:in `[]'
669        // TypeError (can't convert A to String (A#to_str gives Integer))
670        // [2.6.6] > class B; def to_str; true; end; end
671        // => :to_str
672        // [2.6.6] > ENV[B.new]
673        // Traceback (most recent call last):
674        //         5: from /usr/local/var/rbenv/versions/2.6.6/bin/irb:23:in `<main>'
675        //         4: from /usr/local/var/rbenv/versions/2.6.6/bin/irb:23:in `load'
676        //         3: from /usr/local/var/rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
677        //         2: from (irb):7
678        //         1: from (irb):7:in `[]'
679        // TypeError (can't convert B to String (B#to_str gives TrueClass))
680        // [2.6.6] > class C; def to_str; nil; end; end
681        // => :to_str
682        // [2.6.6] > ENV[C.new]
683        // Traceback (most recent call last):
684        //         5: from /usr/local/var/rbenv/versions/2.6.6/bin/irb:23:in `<main>'
685        //         4: from /usr/local/var/rbenv/versions/2.6.6/bin/irb:23:in `load'
686        //         3: from /usr/local/var/rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
687        //         2: from (irb):9
688        //         1: from (irb):9:in `[]'
689        // TypeError (can't convert C to String (C#to_str gives NilClass))
690        // [2.6.6] > module X; class Y; class Z; def to_str; 92; end; end; end; end
691        // => :to_str
692        // [2.6.6] > ENV[X::Y::Z.new]
693        // Traceback (most recent call last):
694        //         5: from /usr/local/var/rbenv/versions/2.6.6/bin/irb:23:in `<main>'
695        //         4: from /usr/local/var/rbenv/versions/2.6.6/bin/irb:23:in `load'
696        //         3: from /usr/local/var/rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
697        //         2: from (irb):11
698        //         1: from (irb):11:in `[]'
699        // TypeError (can't convert X::Y::Z to String (X::Y::Z#to_str gives Integer))
700        // ```
701        let mut message = String::from("can't convert ");
702        let name = interp.inspect_type_name_for_value(value);
703        message.push_str(name);
704        message.push_str(" to String (");
705        message.push_str(name);
706        message.push_str("#to_str gives ");
707        message.push_str(interp.class_name_for_value(maybe));
708        message.push(')');
709        return Err(TypeError::from(message).into());
710    };
711
712    // successful conversion: `#to_str` returned a string.
713    //
714    // XXX: this transmute does a lifetime extension and is probably unsound.
715    let s = unsafe {
716        mem::transmute::<UnboxedValueGuard<'_, spinoso_string::String>, UnboxedValueGuard<'a, spinoso_string::String>>(
717            s,
718        )
719    };
720    Ok(s)
721}