mrubyedge 2.1.2

mruby/edge is yet another mruby that is specialized for running on WASM
Documentation
mod helpers;

use helpers::*;
use mrubyedge::yamrb::vm::VM;

fn check(code: &'static str) {
    let binary = mrbc_compile("block_arguments", code);
    let mut rite = mrubyedge::rite::load(&binary).unwrap();
    let mut vm = VM::open(&mut rite);
    let value = vm.run().unwrap();
    let result: bool = value.as_ref().try_into().unwrap();
    assert!(result, "unexpected result: {:?}", value.value);
}

#[test]
fn rest_arguments_and_named_block() {
    check(
        r#"
      def f(*args, &block)
        [args, block ? block.call : nil]
      end
      [f { 7 }, f(1) { 8 }, f(1, 2, 3) { 9 }, f(4, 5), f] ==
        [[[], 7], [[1], 8], [[1, 2, 3], 9], [[4, 5], nil], [[], nil]]
    "#,
    );
}

#[test]
fn optional_arguments_and_named_block() {
    check(
        r#"
      def f(a = 10, b = 20, &block)
        [a, b, block ? block.call : nil]
      end
      [f { 7 }, f(1) { 8 }, f(1, 2) { 9 }, f, f(3, 4)] ==
        [[10, 20, 7], [1, 20, 8], [1, 2, 9], [10, 20, nil], [3, 4, nil]]
    "#,
    );
}

#[test]
fn optional_rest_and_post_arguments() {
    check(
        r#"
      def f(first, option = 2, *rest, last, &block)
        [first, option, rest, last, block ? block.call : nil]
      end
      [f(1, 9) { 7 }, f(1, 3, 9) { 8 }, f(1, 3, 4, 5, 9) { 6 }, f(1, 9)] ==
        [[1, 2, [], 9, 7], [1, 3, [], 9, 8], [1, 3, [4, 5], 9, 6], [1, 2, [], 9, nil]]
    "#,
    );
}

#[test]
fn keywords_and_block() {
    check(
        r#"
      def f(a = 1, *rest, key: 2, **keywords, &block)
        [a, rest, key, keywords, block ? block.call : nil]
      end
      [f { 7 }, f(3, 4, 5, key: 6, other: 8) { 9 }, f(key: 10)] ==
        [[1, [], 2, {}, 7], [3, [4, 5], 6, {other: 8}, 9], [1, [], 10, {}, nil]]
    "#,
    );
}

#[test]
fn keywords_without_keyword_rest_and_block() {
    check(
        r#"
      def f(a = 1, key: 2, &block)
        [a, key, block ? block.call : nil]
      end
      [f { 7 }, f(3, key: 4) { 8 }, f(key: 5)] == [[1, 2, 7], [3, 4, 8], [1, 5, nil]]
    "#,
    );
}

#[test]
fn yield_after_argument_normalization() {
    check(
        r#"
      def f(a = 1, *rest, key: 2)
        yield(a, rest, key)
      end
      [f { |a, r, k| [a, r, k] }, f(3, 4, 5, key: 6) { |a, r, k| [a, r, k] }] ==
        [[1, [], 2], [3, [4, 5], 6]]
    "#,
    );
}

#[test]
fn nested_and_escaped_yield() {
    check(
        r#"
      def f(a = 1, *rest)
        Proc.new { Proc.new { yield(a, rest) }.call }
      end
      saved = f(3, 4, 5) { |a, r| [a, r] }
      saved.call == [3, [4, 5]]
    "#,
    );
}

#[test]
fn nil_block_and_block_given() {
    check(
        r#"
      def f(a = 1, *rest, &block)
        [block_given?, block.nil?]
      end
      [f { 1 }, f, f(2, 3, &nil)] == [[true, false], [false, true], [false, true]]
    "#,
    );
}

#[test]
fn yield_without_block_raises_local_jump_error() {
    check(
        r#"
      def f(a = 1, *rest)
        yield
      end
      begin
        f
        false
      rescue LocalJumpError
        true
      end
    "#,
    );
}

#[test]
fn forwarded_and_symbol_blocks() {
    check(
        r#"
      def target(a = 3, &block)
        block.call(a)
      end
      def forward(*args, &block)
        target(4, &block)
      end
      [forward(1, 2) { |a| a + 5 }, target(&:to_s)] == [9, '3']
    "#,
    );
}

#[test]
fn native_funcall_does_not_inherit_caller_block() {
    let code = r#"
      def target(a = 10, &block)
        [a, block.nil?, block_given?]
      end
      def outer(&block)
        [probe, block_given?, block.call]
      end
      outer { 7 } == [[10, true, false], true, 7]
    "#;
    let binary = mrbc_compile("native_block_arguments", code);
    let mut rite = mrubyedge::rite::load(&binary).unwrap();
    let mut vm = VM::open(&mut rite);
    let object = vm.object_class.clone();
    mrubyedge::yamrb::helpers::mrb_define_cmethod(
        &mut vm,
        object,
        "probe",
        Box::new(|vm, _| mrb_funcall(vm, None, "target", &[])),
    );
    let result = vm.run().unwrap();
    assert!(bool::try_from(result.as_ref()).unwrap());
}

#[test]
fn converted_block_preserves_keywords() {
    check(
        r#"
      class Callable
        def to_proc
          Proc.new { |n| n + 1 }
        end
      end
      def f(first, option = 3, *rest, key: 3, &block)
        [first, option, rest, block.call(key)]
      end
      f(1, 2, 4, 5, key: 8, &Callable.new) == [1, 2, [4, 5], 9]
    "#,
    );
}

#[test]
fn nil_block_is_allowed_by_no_block_parameter() {
    check(
        r#"
      def f(&nil)
        7
      end
      f(&nil) == 7
    "#,
    );
}

#[test]
fn ruby_each_receives_native_block_with_optional_arguments() {
    check(
        r#"
      class Collection
        include Enumerable
        def each(start = 1, *rest, &block)
          if block_given?
            block.call(start)
            yield(start + 1)
          end
        end
      end
      Collection.new.map { |n| n * 3 } == [3, 6]
    "#,
    );
}

#[test]
fn method_arity_does_not_count_the_block() {
    check(
        r#"
      def f(first, option = 2, last, &block)
        [first, option, last, block.call]
      end
      short = begin
        f(1) { 7 }
        false
      rescue ArgumentError
        true
      end
      long = begin
        f(1, 2, 3, 4) { 7 }
        false
      rescue ArgumentError
        true
      end
      [short, long, f(1, 9) { 7 }] == [true, true, [1, 2, 9, 7]]
    "#,
    );
}

#[test]
fn proc_positional_argument_is_not_a_block() {
    check(
        r#"
      def f(value, &block)
        [value.call, block.nil?, block_given?]
      end
      f(Proc.new { 7 }) == [7, true, false]
    "#,
    );
}

#[test]
fn invalid_block_conversion_raises_type_error() {
    check(
        r#"
      class InvalidBlock
        def to_proc
          nil
        end
      end
      def f(&block)
        false
      end
      begin
        f(&InvalidBlock.new)
      rescue TypeError
        true
      end
    "#,
    );
}