From ec4e96f6188d96740fae799599bbe262a6edd85e Mon Sep 17 00:00:00 2001 From: Kevin Menard Date: Tue, 23 Jun 2026 12:31:39 -0400 Subject: [PATCH 1/8] ZJIT: Remove the unused `num_returns` field from `AddIseqResult` The `num_returns` field is dead code leftover from when the inliner disqualified candidates for inlining based on the number of return paths it had. --- zjit/src/hir.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index de6f5e2ccccc4b..3cde8fbb274732 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -7237,10 +7237,6 @@ struct AddIseqResult { /// to assign it to `fun.profiles` (top-level) or append it to an existing /// oracle (inliner). profiles: ProfileOracle, - /// Number of return paths emitted. Counts `YARVINSN_leave` translations, - /// whether they became `Insn::Return` or `Insn::Jump(return_block, ...)`. - /// Lets callers tell whether the ISEQ has any reachable return paths. - num_returns: usize, } /// Compile ISEQ into High-level IR @@ -7287,7 +7283,6 @@ fn add_iseq_to_hir( ) -> Result { let payload = get_or_create_iseq_payload(iseq); let mut profiles = ProfileOracle::new(); - let mut num_returns: usize = 0; // Build the initial FrameState for a block being translated. In inlined // mode it carries the caller's call-site Snapshot and this frame's depth; @@ -8502,7 +8497,6 @@ fn add_iseq_to_hir( YARVINSN_leave => { fun.push_insn(block, Insn::CheckInterrupts { state: exit_id }); let val = state.stack_pop()?; - num_returns += 1; match mode { AddIseqMode::Standalone => fun.push_insn(block, Insn::Return { val }), AddIseqMode::Inlined { return_block, .. } => { fun.push_insn(block, Insn::Jump(BranchEdge { target: return_block, args: vec![val] })) } @@ -9162,7 +9156,7 @@ fn add_iseq_to_hir( } } - Ok(AddIseqResult { body_entry_blocks, profiles, num_returns }) + Ok(AddIseqResult { body_entry_blocks, profiles }) } /// Compile an entry_block for the interpreter From acc3586f5b44f65bfd75cc26d2ad41e3ac64a954 Mon Sep 17 00:00:00 2001 From: Kevin Menard Date: Tue, 23 Jun 2026 12:32:49 -0400 Subject: [PATCH 2/8] ZJIT: Inline only the callee's selected JIT-entry block When the inliner translates a callee with optional parameters, the callee body is preceded by a chain of JIT-entry blocks, one per opt-table entry. Entry index k is where execution begins when the call site fills k optionals: it runs the default-init code for the remaining optionals and falls through into the post-default body. Since each inlined callee is integrated into the caller and the call site argument count is fixed, we know exactly which JIT entry block we need. Previously, we translated all JIT entry blocks and the inliner picked which one entry it needed, allowing the others to be cleaned up as dead code. Handling just the one case we need avoids passing those known-dead blocks through the rest of the optimization pipeline and cleans up `AddIseqResult`, simplifying the handling of standalone vs inlined method compilation. --- zjit/src/hir.rs | 115 +++++++++++++++++++++++++------------- zjit/src/hir/opt_tests.rs | 54 +++++++++--------- 2 files changed, 103 insertions(+), 66 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 3cde8fbb274732..4d9fcc7157cb46 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -4810,6 +4810,27 @@ impl Function { let pre_insn_types_len = self.insn_types.len(); let pre_blocks_len = self.blocks.len(); + // Pick the callee body entry matching how many optional parameters + // the caller actually filled. Entry index `k` is where execution + // begins when `lead_num + k + post_num + kw_num` arguments are + // passed: it runs the default-init code for the remaining + // `opt_num - k` optionals (if any) before falling through into the + // post-default body. SendDirect emission already guarantees + // args.len() lies in `lead_num + post_num + kw_num..=lead_num + + // opt_num + post_num + kw_num`, with `kw_num` being the callee's + // full keyword count (zero when the callee has no keywords). After + // `prepare_direct_send_args` runs, the caller's arg list has a slot + // for every callee keyword (filled in callee table order, padded + // with defaults for omitted optional keywords), so the keyword tail + // is a fixed-size addition we subtract off before recovering the + // optional-positional count. + let callee_params = unsafe { iseq.params() }; + let lead_num = callee_params.lead_num as usize; + let opt_num = callee_params.opt_num as usize; + let post_num = callee_params.post_num as usize; + let kw_num = callee_kw_num(iseq); + let passed_opt_num = args.len() - lead_num - post_num - kw_num; + // Create the continuation block before translating the callee so it // can serve as the return_block argument; the callee's leaves become // Jumps to this block directly during translation. The continuation @@ -4825,7 +4846,12 @@ impl Function { // callee sits one level deeper (caller_depth + 1). let caller_depth = self.frame_depth(state); - let mode = AddIseqMode::Inlined { return_block: continuation, caller: state, depth: caller_depth + 1 }; + let mode = AddIseqMode::Inlined { + return_block: continuation, + caller: state, + depth: caller_depth + 1, + jit_entry_idx: passed_opt_num, + }; let add_result = match add_iseq_to_hir(self, iseq, mode) { Ok(r) => r, Err(_) => { @@ -4854,31 +4880,11 @@ impl Function { let tail = self.blocks[block.0].insns.split_off(send_pos); debug_assert!(matches!(self.find(tail[0]), Insn::SendDirect { .. })); - // Pick the callee body entry block matching how many optional - // parameters the caller actually filled. add_iseq_to_hir returns one - // body entry block per `jit_entry_idx`; entry index `k` is where - // execution begins when `lead_num + k + post_num + kw_num` arguments - // are passed: it runs the default-init code for the remaining - // `opt_num - k` optionals (if any) before falling through into the - // post-default body. SendDirect emission already guarantees - // args.len() lies in `lead_num + post_num + kw_num..=lead_num + - // opt_num + post_num + kw_num`, with `kw_num` being the callee's - // full keyword count (zero when the callee has no keywords). After - // `prepare_direct_send_args` runs, the caller's arg list has a slot - // for every callee keyword (filled in callee table order, padded - // with defaults for omitted optional keywords), so the keyword tail - // is a fixed-size addition we subtract off before recovering the - // optional-positional count. - let callee_params = unsafe { iseq.params() }; - let lead_num = callee_params.lead_num as usize; - let opt_num = callee_params.opt_num as usize; - let post_num = callee_params.post_num as usize; - let kw_num = callee_kw_num(iseq); - let passed_opt_num = args.len() - lead_num - post_num - kw_num; let omitted_opt_num = opt_num - passed_opt_num; let positional_kw_end = lead_num + opt_num + post_num + kw_num; let kw_bits_local_idx = callee_kw_bits_local_idx(iseq); - let callee_entry_body_block = add_result.body_entry_blocks[passed_opt_num]; + let callee_entry_body_block = add_result.body_entry_block + .expect("inlined compilation always produces a body entry block"); // Map callee body entry params to caller values: // @@ -4891,9 +4897,7 @@ impl Function { // * lead locals (indices 0..lead_num) and filled optional locals // (lead_num..lead_num + passed_opt_num) take args in order. // * unfilled optional locals (lead_num + passed_opt_num..lead_num + opt_num) - // are nil-initialized; the body's default-init code (entered via - // the chosen body_entry_blocks[passed_opt_num] target) overwrites - // them. + // are nil-initialized; the body's default-init code overwrites them. // * post-required and keyword locals // (lead_num + opt_num..lead_num + opt_num + post_num + kw_num) take the // trailing args, but their position in the local table leaves a gap @@ -7222,17 +7226,21 @@ enum AddIseqMode { caller: InsnId, /// Inlining depth of every frame emitted for the callee. depth: InlineDepth, + /// The JIT entry index selected by the call site's argument count. + jit_entry_idx: usize, }, } /// Result of populating a Function with HIR for an ISEQ. struct AddIseqResult { - /// Body entry block per `jit_entry_idx`. Indexed by the same `passed_opt_num` - /// the JIT entry scaffolding uses, this is the body block the scaffolding - /// (when generated) targets after running default-init code for missing - /// optionals. Callers that pass `make_entry_blocks=false` (e.g. the inliner) - /// use this to pick the right entry point without needing the scaffolding. - body_entry_blocks: Vec, + /// The callee body entry block where the inlined callee body begins + /// executing, populated only in inlined mode. + /// + /// `add_iseq_to_hir` slices the opt table to begin at the entry the call + /// site's argument count selects. This is that entry's block. + /// The value is `None` in standalone mode, as standalone has no single body entry + /// block and wires its JIT entry blocks through `fun.jit_entry_blocks` instead. + body_entry_block: Option, /// Profile oracle populated during compilation. The caller decides whether /// to assign it to `fun.profiles` (top-level) or append it to an existing /// oracle (inliner). @@ -7296,8 +7304,23 @@ fn add_iseq_to_hir( } } - // Compute a map of PC->Block by finding jump targets - let jit_entry_insns = unsafe { iseq.params() }.opt_table_slice().iter().copied().map(VALUE::as_u32).collect::>(); + // Compute a map of PC->Block by finding jump targets. + // + // Standalone compilation translates every opt-table entry because each is a + // reachable JIT-to-JIT entrypoint. The inliner, by contrast, enters at a + // single entry fixed by the call site's argument count. So, the entries before + // it would run default-init code for optionals the caller already supplied. + // Those entries are known to be unreachable so slicing them off here avoids + // translating prologue blocks that would only be discarded later, rather + // than emitting them and relying on a downstream pass to prune the dead CFG. + let jit_entry_start = match mode { + AddIseqMode::Standalone => 0, + AddIseqMode::Inlined { jit_entry_idx, .. } => jit_entry_idx, + }; + let jit_entry_insns = unsafe { iseq.params() }.opt_table_slice() + .get(jit_entry_start..) + .expect("JIT entry index must be within the callee opt table") + .iter().copied().map(VALUE::as_u32).collect::>(); let BytecodeInfo { jump_targets } = compute_bytecode_info(iseq, &jit_entry_insns); let compile_jit_entries = matches!(mode, AddIseqMode::Standalone) && iseq_supports_jit_entry(iseq); @@ -7305,15 +7328,18 @@ fn add_iseq_to_hir( // Make all empty basic blocks. The ordering of the BBs matters for getting fallthrough jumps // in good places, but it's not necessary for correctness. TODO: Higher quality scheduling during lowering. let mut insn_idx_to_block = HashMap::new(); - let mut body_entry_blocks = Vec::with_capacity(jit_entry_insns.len()); - // Make blocks for optionals first, and put them right next to their JIT entrypoint + // Materialize a block at each opt-table entry PC, placing each right after + // its JIT-to-JIT entry block so fallthrough jumps land in good places. + // Standalone mode emits a real JIT entry block per entry. Inlined mode emits + // none here; only `body_entry_block` (the first sliced entry) is the inlined + // body's entry, and the rest are default-init body blocks reached by + // fallthrough. for insn_idx in jit_entry_insns.iter().copied() { if compile_jit_entries { let jit_entry_block = fun.new_block(insn_idx); fun.jit_entry_blocks.push(jit_entry_block); } - let body_entry = *insn_idx_to_block.entry(insn_idx).or_insert_with(|| fun.new_block(insn_idx)); - body_entry_blocks.push(body_entry); + insn_idx_to_block.entry(insn_idx).or_insert_with(|| fun.new_block(insn_idx)); } // Make blocks for the rest of the jump targets for insn_idx in jump_targets { @@ -7322,6 +7348,17 @@ fn add_iseq_to_hir( // Done, drop `mut`. let insn_idx_to_block = insn_idx_to_block; + // The callee body entry block where the inlined callee body begins + // executing. `jit_entry_insns` starts at the entry the call site selected + // (since `opt_table_slice` was sliced above), so its first element is that entry. + // Any later entries are the default-init blocks for the remaining unfilled + // optionals, reached from this one by fallthrough rather than targeted directly. + // Standalone compilation has no single body entry block, so it produces none. + let body_entry_block = match mode { + AddIseqMode::Standalone => None, + AddIseqMode::Inlined { .. } => Some(insn_idx_to_block[&jit_entry_insns[0]]), + }; + if matches!(mode, AddIseqMode::Standalone) { // Compile an entry_block for the interpreter compile_entry_block(fun, jit_entry_insns.as_slice(), &insn_idx_to_block); @@ -9156,7 +9193,7 @@ fn add_iseq_to_hir( } } - Ok(AddIseqResult { body_entry_blocks, profiles }) + Ok(AddIseqResult { body_entry_block, profiles }) } /// Compile an entry_block for the interpreter diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index bebf7d693dceb0..b82417d44aa7e7 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -1800,12 +1800,12 @@ mod hir_opt_tests { PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v20:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile PushInlineFrame v20 (0x1038), v11 - v35:Fixnum[2] = Const Value(2) + v27:Fixnum[2] = Const Value(2) PatchPoint MethodRedefined(Integer@0x1040, +@0x1048, cme:0x1050) - v61:Fixnum[5] = Const Value(5) + v53:Fixnum[5] = Const Value(5) CheckInterrupts PopInlineFrame - Return v61 + Return v53 "); } @@ -1833,10 +1833,10 @@ mod hir_opt_tests { v22:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile PushInlineFrame v22 (0x1038), v11, v13 PatchPoint MethodRedefined(Integer@0x1040, +@0x1048, cme:0x1050) - v62:Fixnum[7] = Const Value(7) + v46:Fixnum[7] = Const Value(7) CheckInterrupts PopInlineFrame - Return v62 + Return v46 "); } @@ -1873,8 +1873,8 @@ mod hir_opt_tests { v16:Fixnum[20] = Const Value(20) v18:Fixnum[30] = Const Value(30) PushInlineFrame v44 (0x1038), v14, v16, v18 - v151:Fixnum[4] = Const Value(4) - v166:ArrayExact = NewArray v14, v16, v18, v151 + v121:Fixnum[4] = Const Value(4) + v136:ArrayExact = NewArray v14, v16, v18, v121 PopInlineFrame v24:Fixnum[10] = Const Value(10) v26:Fixnum[20] = Const Value(20) @@ -1882,7 +1882,7 @@ mod hir_opt_tests { v30:Fixnum[40] = Const Value(40) v32:Fixnum[50] = Const Value(50) v34:BasicObject = Send v44, :target, v24, v26, v28, v30, v32 # SendFallbackReason: Argument count does not match parameter count - v37:ArrayExact = NewArray v101, v166, v34 + v37:ArrayExact = NewArray v101, v136, v34 CheckInterrupts Return v37 "); @@ -4191,11 +4191,11 @@ mod hir_opt_tests { v22:Fixnum[2] = Const Value(2) v24:Fixnum[4] = Const Value(4) v26:Fixnum[3] = Const Value(3) - v103:Fixnum[0] = Const Value(0) + v92:Fixnum[0] = Const Value(0) PushInlineFrame v37 (0x1038), v20, v22, v26, v24 - v98:ArrayExact = NewArray v22, v26 + v87:ArrayExact = NewArray v22, v26 PopInlineFrame - v30:ArrayExact = NewArray v65, v98 + v30:ArrayExact = NewArray v65, v87 Return v30 "); } @@ -4234,11 +4234,11 @@ mod hir_opt_tests { v20:Fixnum[2] = Const Value(2) v22:Fixnum[40] = Const Value(40) v24:Fixnum[30] = Const Value(30) - v107:Fixnum[0] = Const Value(0) + v96:Fixnum[0] = Const Value(0) PushInlineFrame v37 (0x1038), v18, v20, v24, v22 - v102:ArrayExact = NewArray v18, v20, v24, v22 + v91:ArrayExact = NewArray v18, v20, v24, v22 PopInlineFrame - v28:ArrayExact = NewArray v67, v102 + v28:ArrayExact = NewArray v67, v91 Return v28 "); } @@ -18363,14 +18363,14 @@ mod hir_opt_tests { PatchPoint MethodRedefined(Object@0x1008, add_opts@0x1010, cme:0x1018) v25:ObjectSubclass[class_exact*:Object@VALUE(0x1008)] = GuardType v9, ObjectSubclass[class_exact*:Object@VALUE(0x1008)] recompile PushInlineFrame v25 (0x1040), v10, v16 - v42:Fixnum[100] = Const Value(100) + v33:Fixnum[100] = Const Value(100) PatchPoint MethodRedefined(Integer@0x1048, +@0x1050, cme:0x1058) - v71:Fixnum = GuardType v10, Fixnum recompile - v72:Fixnum = FixnumAdd v71, v16 - v76:Fixnum = FixnumAdd v72, v42 + v62:Fixnum = GuardType v10, Fixnum recompile + v63:Fixnum = FixnumAdd v62, v16 + v67:Fixnum = FixnumAdd v63, v33 CheckInterrupts PopInlineFrame - Return v76 + Return v67 "); } @@ -18585,12 +18585,12 @@ mod hir_opt_tests { v27:ObjectSubclass[class_exact*:Object@VALUE(0x1008)] = GuardType v9, ObjectSubclass[class_exact*:Object@VALUE(0x1008)] recompile PushInlineFrame v27 (0x1040), v10, v16, v18 PatchPoint MethodRedefined(Integer@0x1048, +@0x1050, cme:0x1058) - v72:Fixnum = GuardType v10, Fixnum recompile - v73:Fixnum = FixnumAdd v72, v16 - v77:Fixnum = FixnumAdd v73, v18 + v54:Fixnum = GuardType v10, Fixnum recompile + v55:Fixnum = FixnumAdd v54, v16 + v59:Fixnum = FixnumAdd v55, v18 CheckInterrupts PopInlineFrame - Return v77 + Return v59 "); } @@ -19033,12 +19033,12 @@ mod hir_opt_tests { v27:ObjectSubclass[class_exact*:Object@VALUE(0x1008)] = GuardType v9, ObjectSubclass[class_exact*:Object@VALUE(0x1008)] recompile PushInlineFrame v27 (0x1040), v10, v16, v18 PatchPoint MethodRedefined(Integer@0x1048, +@0x1050, cme:0x1058) - v63:Fixnum = GuardType v10, Fixnum recompile - v64:Fixnum = FixnumAdd v63, v16 - v68:Fixnum = FixnumAdd v64, v18 + v54:Fixnum = GuardType v10, Fixnum recompile + v55:Fixnum = FixnumAdd v54, v16 + v59:Fixnum = FixnumAdd v55, v18 CheckInterrupts PopInlineFrame - Return v68 + Return v59 "); } From 0d68ef6b77f1fdf8bc0ae6ef499ccbabc5aa7df8 Mon Sep 17 00:00:00 2001 From: Andrii Konchyn Date: Tue, 7 Jul 2026 21:25:22 +0300 Subject: [PATCH 3/8] Update to ruby/spec@ceaa79d --- spec/ruby/command_line/dash_r_spec.rb | 5 + spec/ruby/command_line/dash_upper_e_spec.rb | 7 +- spec/ruby/command_line/dash_x_spec.rb | 6 + .../bin/embedded_ruby_change_directory.txt | 3 + .../fixtures/freeze_flag_one_literal.rb | 4 +- spec/ruby/command_line/rubyopt_spec.rb | 11 + spec/ruby/core/array/detect_spec.rb | 9 + spec/ruby/core/array/find_spec.rb | 90 ++++ spec/ruby/core/array/fixtures/classes.rb | 2 +- spec/ruby/core/array/pack/a_spec.rb | 3 - spec/ruby/core/array/pack/b_spec.rb | 3 - spec/ruby/core/array/pack/h_spec.rb | 3 - spec/ruby/core/array/pack/m_spec.rb | 3 - spec/ruby/core/array/pack/p_spec.rb | 3 - spec/ruby/core/array/pack/shared/taint.rb | 2 - spec/ruby/core/array/pack/u_spec.rb | 2 - spec/ruby/core/array/pack/z_spec.rb | 2 - spec/ruby/core/array/rfind_spec.rb | 90 ++++ .../implicit_parameter_defined_spec.rb | 84 ++++ .../binding/implicit_parameter_get_spec.rb | 90 ++++ .../core/binding/implicit_parameters_spec.rb | 64 +++ .../binding/local_variable_defined_spec.rb | 6 + .../core/binding/local_variable_get_spec.rb | 6 + .../core/binding/local_variable_set_spec.rb | 5 + spec/ruby/core/complex/to_s_spec.rb | 7 +- spec/ruby/core/enumerable/find_spec.rb | 37 +- spec/ruby/core/file/birthtime_spec.rb | 15 + spec/ruby/core/file/stat/birthtime_spec.rb | 1 + .../core/hash/compare_by_identity_spec.rb | 10 + spec/ruby/core/io/advise_spec.rb | 2 +- spec/ruby/core/io/binread_spec.rb | 17 +- spec/ruby/core/io/foreach_spec.rb | 17 +- spec/ruby/core/io/read_spec.rb | 21 +- spec/ruby/core/io/readlines_spec.rb | 17 +- spec/ruby/core/io/seek_spec.rb | 15 + spec/ruby/core/io/shared/new.rb | 8 +- spec/ruby/core/io/sysread_spec.rb | 2 +- spec/ruby/core/io/sysseek_spec.rb | 15 + spec/ruby/core/io/ungetc_spec.rb | 2 +- spec/ruby/core/io/write_spec.rb | 13 + spec/ruby/core/kernel/Array_spec.rb | 44 +- spec/ruby/core/kernel/Complex_spec.rb | 45 +- spec/ruby/core/kernel/Float_spec.rb | 246 +++++----- spec/ruby/core/kernel/Hash_spec.rb | 32 +- spec/ruby/core/kernel/Integer_spec.rb | 106 ++--- spec/ruby/core/kernel/Rational_spec.rb | 23 +- spec/ruby/core/kernel/String_spec.rb | 42 +- spec/ruby/core/kernel/__callee___spec.rb | 12 +- spec/ruby/core/kernel/__dir___spec.rb | 10 + spec/ruby/core/kernel/__method___spec.rb | 12 +- spec/ruby/core/kernel/abort_spec.rb | 4 +- spec/ruby/core/kernel/at_exit_spec.rb | 8 +- .../core/kernel/autoload_relative_spec.rb | 8 +- spec/ruby/core/kernel/autoload_spec.rb | 8 + spec/ruby/core/kernel/binding_spec.rb | 16 +- spec/ruby/core/kernel/block_given_spec.rb | 44 +- .../ruby/core/kernel/caller_locations_spec.rb | 23 + spec/ruby/core/kernel/caller_spec.rb | 23 + spec/ruby/core/kernel/catch_spec.rb | 12 +- spec/ruby/core/kernel/chomp_spec.rb | 44 +- spec/ruby/core/kernel/chop_spec.rb | 38 +- spec/ruby/core/kernel/eval_spec.rb | 15 +- spec/ruby/core/kernel/exec_spec.rb | 4 +- spec/ruby/core/kernel/exit_spec.rb | 8 +- spec/ruby/core/kernel/fixtures/chomp.rb | 2 +- spec/ruby/core/kernel/fixtures/chomp_f.rb | 4 - spec/ruby/core/kernel/fixtures/chop.rb | 2 +- spec/ruby/core/kernel/fixtures/chop_f.rb | 4 - spec/ruby/core/kernel/fixtures/classes.rb | 116 +---- spec/ruby/core/kernel/fork_spec.rb | 4 +- spec/ruby/core/kernel/gets_spec.rb | 4 +- .../ruby/core/kernel/global_variables_spec.rb | 8 +- spec/ruby/core/kernel/lambda_spec.rb | 8 +- spec/ruby/core/kernel/load_spec.rb | 14 +- spec/ruby/core/kernel/local_variables_spec.rb | 6 + spec/ruby/core/kernel/loop_spec.rb | 8 +- spec/ruby/core/kernel/open_spec.rb | 21 +- spec/ruby/core/kernel/p_spec.rb | 4 +- spec/ruby/core/kernel/print_spec.rb | 4 +- spec/ruby/core/kernel/printf_spec.rb | 28 +- spec/ruby/core/kernel/proc_spec.rb | 8 +- spec/ruby/core/kernel/putc_spec.rb | 27 +- spec/ruby/core/kernel/puts_spec.rb | 4 +- spec/ruby/core/kernel/raise_spec.rb | 5 +- spec/ruby/core/kernel/rand_spec.rb | 4 +- spec/ruby/core/kernel/readline_spec.rb | 4 +- spec/ruby/core/kernel/readlines_spec.rb | 4 +- .../ruby/core/kernel/require_relative_spec.rb | 10 + spec/ruby/core/kernel/require_spec.rb | 11 +- spec/ruby/core/kernel/select_spec.rb | 8 +- spec/ruby/core/kernel/set_trace_func_spec.rb | 4 +- spec/ruby/core/kernel/shared/sprintf.rb | 57 +++ spec/ruby/core/kernel/sleep_spec.rb | 6 +- spec/ruby/core/kernel/spawn_spec.rb | 6 +- spec/ruby/core/kernel/sprintf_spec.rb | 46 +- spec/ruby/core/kernel/srand_spec.rb | 4 +- spec/ruby/core/kernel/syscall_spec.rb | 4 +- spec/ruby/core/kernel/system_spec.rb | 48 +- spec/ruby/core/kernel/test_spec.rb | 32 +- spec/ruby/core/kernel/throw_spec.rb | 12 +- spec/ruby/core/kernel/trace_var_spec.rb | 6 + spec/ruby/core/kernel/trap_spec.rb | 6 + spec/ruby/core/kernel/untrace_var_spec.rb | 4 +- spec/ruby/core/kernel/warn_spec.rb | 14 +- spec/ruby/core/proc/parameters_spec.rb | 16 + spec/ruby/core/process/spawn_spec.rb | 6 + spec/ruby/core/range/fixtures/classes.rb | 71 +++ spec/ruby/core/range/include_spec.rb | 294 ++++++++++++ spec/ruby/core/range/max_spec.rb | 103 ++++- spec/ruby/core/range/min_spec.rb | 88 +++- spec/ruby/core/range/overlap_spec.rb | 28 ++ spec/ruby/core/range/shared/cover.rb | 422 +++++++++++++++--- .../core/range/shared/cover_and_include.rb | 10 + spec/ruby/core/rational/exponent_spec.rb | 62 ++- spec/ruby/core/rational/inspect_spec.rb | 5 +- spec/ruby/core/rational/integer_spec.rb | 7 +- spec/ruby/core/rational/round_spec.rb | 7 +- spec/ruby/core/rational/to_s_spec.rb | 7 +- spec/ruby/core/string/fixtures/classes.rb | 2 +- spec/ruby/core/string/modulo_spec.rb | 57 --- spec/ruby/core/string/to_f_spec.rb | 33 +- spec/ruby/core/string/unpack/a_spec.rb | 3 - spec/ruby/core/string/unpack/b_spec.rb | 3 - spec/ruby/core/string/unpack/h_spec.rb | 3 - spec/ruby/core/string/unpack/m_spec.rb | 3 - spec/ruby/core/string/unpack/p_spec.rb | 3 - spec/ruby/core/string/unpack/shared/taint.rb | 2 - spec/ruby/core/string/unpack/u_spec.rb | 3 - spec/ruby/core/string/unpack/z_spec.rb | 2 - .../core/thread/backtrace_locations_spec.rb | 17 + spec/ruby/core/thread/backtrace_spec.rb | 18 + spec/ruby/core/thread/to_s_spec.rb | 15 +- .../core/unboundmethod/equal_value_spec.rb | 7 + spec/ruby/language/it_parameter_spec.rb | 21 +- .../ruby/language/numbered_parameters_spec.rb | 34 ++ spec/ruby/language/precedence_spec.rb | 5 +- spec/ruby/language/rescue_spec.rb | 15 +- .../library/delegate/delegator/taint_spec.rb | 8 - .../library/delegate/delegator/trust_spec.rb | 8 - .../delegate/delegator/untaint_spec.rb | 8 - .../delegate/delegator/untrust_spec.rb | 8 - spec/ruby/library/matrix/divide_spec.rb | 13 +- spec/ruby/library/matrix/real_spec.rb | 7 +- spec/ruby/library/pathname/pathname_spec.rb | 12 +- .../library/socket/ipsocket/inspect_spec.rb | 18 + spec/ruby/library/stringio/ungetc_spec.rb | 34 ++ spec/ruby/optional/capi/array_spec.rb | 6 +- spec/ruby/optional/capi/ext/array_spec.c | 49 +- spec/ruby/optional/capi/ext/fiber_spec.c | 20 + spec/ruby/optional/capi/ext/io_spec.c | 10 + spec/ruby/optional/capi/ext/kernel_spec.c | 43 ++ spec/ruby/optional/capi/ext/proc_spec.c | 20 + spec/ruby/optional/capi/ext/rbasic_spec.c | 5 - spec/ruby/optional/capi/fiber_spec.rb | 4 + spec/ruby/optional/capi/io_spec.rb | 80 ++++ spec/ruby/optional/capi/kernel_spec.rb | 8 + spec/ruby/optional/capi/object_spec.rb | 12 - spec/ruby/optional/capi/proc_spec.rb | 4 + spec/ruby/security/cve_2018_16396_spec.rb | 7 - spec/ruby/shared/basicobject/send.rb | 6 + spec/ruby/shared/kernel/fixtures/classes.rb | 9 + spec/ruby/shared/kernel/raise.rb | 38 ++ 162 files changed, 2879 insertions(+), 1030 deletions(-) create mode 100644 spec/ruby/command_line/fixtures/bin/embedded_ruby_change_directory.txt create mode 100644 spec/ruby/core/array/detect_spec.rb create mode 100644 spec/ruby/core/array/find_spec.rb delete mode 100644 spec/ruby/core/array/pack/shared/taint.rb create mode 100644 spec/ruby/core/array/rfind_spec.rb create mode 100644 spec/ruby/core/binding/implicit_parameter_defined_spec.rb create mode 100644 spec/ruby/core/binding/implicit_parameter_get_spec.rb create mode 100644 spec/ruby/core/binding/implicit_parameters_spec.rb delete mode 100644 spec/ruby/core/kernel/fixtures/chomp_f.rb delete mode 100644 spec/ruby/core/kernel/fixtures/chop_f.rb delete mode 100644 spec/ruby/core/string/unpack/shared/taint.rb delete mode 100644 spec/ruby/library/delegate/delegator/taint_spec.rb delete mode 100644 spec/ruby/library/delegate/delegator/trust_spec.rb delete mode 100644 spec/ruby/library/delegate/delegator/untaint_spec.rb delete mode 100644 spec/ruby/library/delegate/delegator/untrust_spec.rb delete mode 100644 spec/ruby/security/cve_2018_16396_spec.rb create mode 100644 spec/ruby/shared/kernel/fixtures/classes.rb diff --git a/spec/ruby/command_line/dash_r_spec.rb b/spec/ruby/command_line/dash_r_spec.rb index 0de9ba2e24e920..052ad5e638003b 100644 --- a/spec/ruby/command_line/dash_r_spec.rb +++ b/spec/ruby/command_line/dash_r_spec.rb @@ -25,4 +25,9 @@ out.should_not.include?("REQUIRED") out.should.include?("No such file or directory") end + + it "requires in order when given multiple times" do + ruby_exe("", options: "-r#{fixture(__FILE__, "test_file.rb")} -r#{fixture(__FILE__, "verbose.rb")}"). + should == "REQUIRED\nfalse\n" + end end diff --git a/spec/ruby/command_line/dash_upper_e_spec.rb b/spec/ruby/command_line/dash_upper_e_spec.rb index 5a83962583e9c3..e260a4ec4763cd 100644 --- a/spec/ruby/command_line/dash_upper_e_spec.rb +++ b/spec/ruby/command_line/dash_upper_e_spec.rb @@ -32,6 +32,11 @@ ruby_exe("p 1", options: '-Eascii:ascii -U', args: '2>&1', - exit_status: 1).should =~ /RuntimeError/ + exit_status: 1).should =~ /already set to ascii \(RuntimeError\)/ + end + + it "doesn't raise a RuntimeError if used with -U in RUBYOPT" do + ruby_exe("puts Encoding.default_external, Encoding.default_internal", options: '-E ascii:ascii', env: { 'RUBYOPT' => '-U' }). + should == "US-ASCII\nUS-ASCII\n" end end diff --git a/spec/ruby/command_line/dash_x_spec.rb b/spec/ruby/command_line/dash_x_spec.rb index 38f97a5ab1b2f7..ecd74e6d4a5ab0 100644 --- a/spec/ruby/command_line/dash_x_spec.rb +++ b/spec/ruby/command_line/dash_x_spec.rb @@ -7,6 +7,12 @@ result.should == "success\n" end + it "changes the working directory when given" do + dir = fixture __FILE__, "bin" + result = ruby_exe(nil, options: "-x#{dir} embedded_ruby_change_directory.txt") + result.should == "#{dir}\n" + end + it "fails when /\#!.*ruby.*/-ish line in target file is not found" do bad_embedded_ruby = fixture __FILE__, "bin/bad_embedded_ruby.txt" result = ruby_exe(bad_embedded_ruby, options: '-x', args: '2>&1', exit_status: 1) diff --git a/spec/ruby/command_line/fixtures/bin/embedded_ruby_change_directory.txt b/spec/ruby/command_line/fixtures/bin/embedded_ruby_change_directory.txt new file mode 100644 index 00000000000000..338f1491c72650 --- /dev/null +++ b/spec/ruby/command_line/fixtures/bin/embedded_ruby_change_directory.txt @@ -0,0 +1,3 @@ +@@@This line is not valid Ruby +#!ruby +puts Dir.pwd diff --git a/spec/ruby/command_line/fixtures/freeze_flag_one_literal.rb b/spec/ruby/command_line/fixtures/freeze_flag_one_literal.rb index 3718899d614c97..8e7714572bece7 100644 --- a/spec/ruby/command_line/fixtures/freeze_flag_one_literal.rb +++ b/spec/ruby/command_line/fixtures/freeze_flag_one_literal.rb @@ -1,2 +1,2 @@ -ids = Array.new(2) { "abc".object_id } -p ids.first == ids.last +ids = Array.new(2) { "abc" } +p ids.first.equal? ids.last diff --git a/spec/ruby/command_line/rubyopt_spec.rb b/spec/ruby/command_line/rubyopt_spec.rb index eb297cd6fe37e5..490e6a809a310b 100644 --- a/spec/ruby/command_line/rubyopt_spec.rb +++ b/spec/ruby/command_line/rubyopt_spec.rb @@ -59,6 +59,11 @@ ruby_exe("p $VERBOSE").chomp.should == "true" end + it "is overwritten by a cli switch" do + ENV["RUBYOPT"] = "-W2" + ruby_exe("p $VERBOSE", options: "-W0").chomp.should == "nil" + end + it "suppresses deprecation warnings for '-W:no-deprecated'" do ENV["RUBYOPT"] = '-W:no-deprecated' result = ruby_exe('$; = ""', args: '2>&1') @@ -83,6 +88,12 @@ ruby_exe("0", args: '2>&1').should =~ /^rubyopt.rb required/ end + it "requires from CLI -r first and then from RUBYOPT -r" do + ENV["RUBYOPT"] = "-r#{fixture(__FILE__, "rubyopt.rb")}" + ruby_exe("", options: "-r#{fixture(__FILE__, "test_file.rb")}"). + should == "REQUIRED\nrubyopt.rb required\n" + end + it "raises a RuntimeError for '-a'" do ENV["RUBYOPT"] = '-a' ruby_exe("", args: '2>&1', exit_status: 1).should =~ /RuntimeError/ diff --git a/spec/ruby/core/array/detect_spec.rb b/spec/ruby/core/array/detect_spec.rb new file mode 100644 index 00000000000000..b8a98062ec3765 --- /dev/null +++ b/spec/ruby/core/array/detect_spec.rb @@ -0,0 +1,9 @@ +require_relative '../../spec_helper' + +ruby_version_is "4.0" do + describe "Array#detect" do + it "is an alias of Array#find" do + Array.instance_method(:detect).should == Array.instance_method(:find) + end + end +end diff --git a/spec/ruby/core/array/find_spec.rb b/spec/ruby/core/array/find_spec.rb new file mode 100644 index 00000000000000..d9cb1a0e32ce09 --- /dev/null +++ b/spec/ruby/core/array/find_spec.rb @@ -0,0 +1,90 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative '../enumerable/shared/enumeratorized' + +# Modifying a collection while the contents are being iterated +# gives undefined behavior. See +# https://blade.ruby-lang.org/ruby-core/23633 + +ruby_version_is "4.0" do + describe "Array#find" do + it "returns the first element for which the block is not false" do + [1, 2, 3, 4, 5].find { |x| x % 2 == 0 }.should == 2 + end + + it "returns nil when the block is false and there is no ifnone proc given" do + [1, 2, 3].find { |x| false }.should == nil + end + + it "returns the value of the ifnone proc if the block is false" do + fail_proc = -> { "cheeseburgers" } + [1, 2, 3].find(fail_proc) { |x| false }.should == "cheeseburgers" + end + + it "doesn't call the ifnone proc if an element is found" do + fail_proc = -> { raise "This shouldn't have been called" } + [1, 2, 3].find(fail_proc) { |x| x == 1 }.should == 1 + end + + it "calls the ifnone proc only once when the block is false" do + times = 0 + fail_proc = -> { times += 1; raise if times > 1; "cheeseburgers" } + [1, 2, 3].find(fail_proc) { |x| false }.should == "cheeseburgers" + end + + it "calls the ifnone proc when there are no elements" do + fail_proc = -> { "yay" } + [].find(fail_proc) { |x| true }.should == "yay" + end + + it "ignores the ifnone argument when nil" do + [1, 2, 3].find(nil) { |x| false }.should == nil + end + + it "raises a NoMethodError if the ifnone argument does not respond to #call and no element is found" do + -> { [1, 2, 3].find(42) { |x| false } }.should.raise(NoMethodError) + end + + it "iterates elements in forward order" do + visited = [] + [1, 2, 3].find { |element| visited << element; false } + visited.should == [1, 2, 3] + end + + it "passes through the values yielded by #each_with_index" do + ScratchPad.record [] + [:a, :b].each_with_index.to_a.find { |x, i| ScratchPad << [x, i]; nil } + ScratchPad.recorded.should == [[:a, 0], [:b, 1]] + end + + it "stops iterating as soon as an element is found" do + visited = [] + [1, 2, 3, 4, 5].find { |x| visited << x; x == 3 } + visited.should == [1, 2, 3] + end + + it "returns an enumerator when no block given" do + [1, 2, 3].find.should.instance_of?(Enumerator) + end + + it "passes the ifnone proc to the enumerator" do + fail_proc = -> { "cheeseburgers" } + enum = [1, 2, 3].find(fail_proc) + enum.each { |x| false }.should == "cheeseburgers" + end + + it "does not destructure elements" do + multi = [[1, 2], [3, 4, 5], [6, 7, 8, 9]] + multi.find { |e| e == [1, 2] }.should == [1, 2] + end + + it "rechecks the array size during iteration" do + ary = [4, 2, 1, 5, 1, 3] + seen = [] + ary.find { |x| seen << x; ary.clear; false } + seen.should == [4] + end + + it_behaves_like :enumeratorized_with_unknown_size, :find, [1, 2, 3] + end +end diff --git a/spec/ruby/core/array/fixtures/classes.rb b/spec/ruby/core/array/fixtures/classes.rb index 05283c0f7447cb..7389cb98558640 100644 --- a/spec/ruby/core/array/fixtures/classes.rb +++ b/spec/ruby/core/array/fixtures/classes.rb @@ -5,7 +5,7 @@ def pack_format(count=nil, repeat=nil) format = instance_variable_get(:@method) format += count.to_s unless format == 'P' || format == 'p' format *= repeat if repeat - format.dup # because it may then become tainted + format end end diff --git a/spec/ruby/core/array/pack/a_spec.rb b/spec/ruby/core/array/pack/a_spec.rb index 03bfd8214c8ee8..491b8af9c2c4b6 100644 --- a/spec/ruby/core/array/pack/a_spec.rb +++ b/spec/ruby/core/array/pack/a_spec.rb @@ -3,14 +3,12 @@ require_relative '../fixtures/classes' require_relative 'shared/basic' require_relative 'shared/string' -require_relative 'shared/taint' describe "Array#pack with format 'A'" do it_behaves_like :array_pack_basic, 'A' it_behaves_like :array_pack_basic_non_float, 'A' it_behaves_like :array_pack_no_platform, 'A' it_behaves_like :array_pack_string, 'A' - it_behaves_like :array_pack_taint, 'A' it "calls #to_str to convert an Object to a String" do obj = mock("pack A string") @@ -49,7 +47,6 @@ it_behaves_like :array_pack_basic_non_float, 'a' it_behaves_like :array_pack_no_platform, 'a' it_behaves_like :array_pack_string, 'a' - it_behaves_like :array_pack_taint, 'a' it "adds all the bytes to the output when passed the '*' modifier" do ["abc"].pack("a*").should == "abc" diff --git a/spec/ruby/core/array/pack/b_spec.rb b/spec/ruby/core/array/pack/b_spec.rb index f7576846ef9062..ab50bd5af8cfb0 100644 --- a/spec/ruby/core/array/pack/b_spec.rb +++ b/spec/ruby/core/array/pack/b_spec.rb @@ -3,14 +3,12 @@ require_relative '../fixtures/classes' require_relative 'shared/basic' require_relative 'shared/encodings' -require_relative 'shared/taint' describe "Array#pack with format 'B'" do it_behaves_like :array_pack_basic, 'B' it_behaves_like :array_pack_basic_non_float, 'B' it_behaves_like :array_pack_arguments, 'B' it_behaves_like :array_pack_hex, 'B' - it_behaves_like :array_pack_taint, 'B' it "calls #to_str to convert an Object to a String" do obj = mock("pack B string") @@ -66,7 +64,6 @@ it_behaves_like :array_pack_basic_non_float, 'b' it_behaves_like :array_pack_arguments, 'b' it_behaves_like :array_pack_hex, 'b' - it_behaves_like :array_pack_taint, 'b' it "calls #to_str to convert an Object to a String" do obj = mock("pack H string") diff --git a/spec/ruby/core/array/pack/h_spec.rb b/spec/ruby/core/array/pack/h_spec.rb index 1492d02b1fb2c6..389295cb0b043a 100644 --- a/spec/ruby/core/array/pack/h_spec.rb +++ b/spec/ruby/core/array/pack/h_spec.rb @@ -3,14 +3,12 @@ require_relative '../fixtures/classes' require_relative 'shared/basic' require_relative 'shared/encodings' -require_relative 'shared/taint' describe "Array#pack with format 'H'" do it_behaves_like :array_pack_basic, 'H' it_behaves_like :array_pack_basic_non_float, 'H' it_behaves_like :array_pack_arguments, 'H' it_behaves_like :array_pack_hex, 'H' - it_behaves_like :array_pack_taint, 'H' it "calls #to_str to convert an Object to a String" do obj = mock("pack H string") @@ -112,7 +110,6 @@ it_behaves_like :array_pack_basic_non_float, 'h' it_behaves_like :array_pack_arguments, 'h' it_behaves_like :array_pack_hex, 'h' - it_behaves_like :array_pack_taint, 'h' it "calls #to_str to convert an Object to a String" do obj = mock("pack H string") diff --git a/spec/ruby/core/array/pack/m_spec.rb b/spec/ruby/core/array/pack/m_spec.rb index fb670d120e2f14..090ae6a7021aa2 100644 --- a/spec/ruby/core/array/pack/m_spec.rb +++ b/spec/ruby/core/array/pack/m_spec.rb @@ -2,13 +2,11 @@ require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' -require_relative 'shared/taint' describe "Array#pack with format 'M'" do it_behaves_like :array_pack_basic, 'M' it_behaves_like :array_pack_basic_non_float, 'M' it_behaves_like :array_pack_arguments, 'M' - it_behaves_like :array_pack_taint, 'M' it "encodes an empty string as an empty string" do [""].pack("M").should == "" @@ -202,7 +200,6 @@ it_behaves_like :array_pack_basic, 'm' it_behaves_like :array_pack_basic_non_float, 'm' it_behaves_like :array_pack_arguments, 'm' - it_behaves_like :array_pack_taint, 'm' it "encodes an empty string as an empty string" do [""].pack("m").should == "" diff --git a/spec/ruby/core/array/pack/p_spec.rb b/spec/ruby/core/array/pack/p_spec.rb index b023bf91106829..c9ca5f7174451c 100644 --- a/spec/ruby/core/array/pack/p_spec.rb +++ b/spec/ruby/core/array/pack/p_spec.rb @@ -1,11 +1,9 @@ require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' -require_relative 'shared/taint' describe "Array#pack with format 'P'" do it_behaves_like :array_pack_basic_non_float, 'P' - it_behaves_like :array_pack_taint, 'P' it "produces as many bytes as there are in a pointer" do ["hello"].pack("P").size.should == [0].pack("J").size @@ -22,7 +20,6 @@ describe "Array#pack with format 'p'" do it_behaves_like :array_pack_basic_non_float, 'p' - it_behaves_like :array_pack_taint, 'p' it "produces as many bytes as there are in a pointer" do ["hello"].pack("p").size.should == [0].pack("J").size diff --git a/spec/ruby/core/array/pack/shared/taint.rb b/spec/ruby/core/array/pack/shared/taint.rb deleted file mode 100644 index 2c2b011c34120a..00000000000000 --- a/spec/ruby/core/array/pack/shared/taint.rb +++ /dev/null @@ -1,2 +0,0 @@ -describe :array_pack_taint, shared: true do -end diff --git a/spec/ruby/core/array/pack/u_spec.rb b/spec/ruby/core/array/pack/u_spec.rb index c6a0d77eb2d937..633410404d6f36 100644 --- a/spec/ruby/core/array/pack/u_spec.rb +++ b/spec/ruby/core/array/pack/u_spec.rb @@ -3,7 +3,6 @@ require_relative '../fixtures/classes' require_relative 'shared/basic' require_relative 'shared/unicode' -require_relative 'shared/taint' describe "Array#pack with format 'U'" do it_behaves_like :array_pack_basic, 'U' @@ -16,7 +15,6 @@ it_behaves_like :array_pack_basic, 'u' it_behaves_like :array_pack_basic_non_float, 'u' it_behaves_like :array_pack_arguments, 'u' - it_behaves_like :array_pack_taint, 'u' it "calls #to_str to convert an Object to a String" do obj = mock("pack u string") diff --git a/spec/ruby/core/array/pack/z_spec.rb b/spec/ruby/core/array/pack/z_spec.rb index 5cd084c8251212..8df5c42bef3e03 100644 --- a/spec/ruby/core/array/pack/z_spec.rb +++ b/spec/ruby/core/array/pack/z_spec.rb @@ -3,14 +3,12 @@ require_relative '../fixtures/classes' require_relative 'shared/basic' require_relative 'shared/string' -require_relative 'shared/taint' describe "Array#pack with format 'Z'" do it_behaves_like :array_pack_basic, 'Z' it_behaves_like :array_pack_basic_non_float, 'Z' it_behaves_like :array_pack_no_platform, 'Z' it_behaves_like :array_pack_string, 'Z' - it_behaves_like :array_pack_taint, 'Z' it "calls #to_str to convert an Object to a String" do obj = mock("pack Z string") diff --git a/spec/ruby/core/array/rfind_spec.rb b/spec/ruby/core/array/rfind_spec.rb new file mode 100644 index 00000000000000..cf5f3d8b01ec48 --- /dev/null +++ b/spec/ruby/core/array/rfind_spec.rb @@ -0,0 +1,90 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative '../enumerable/shared/enumeratorized' + +# Modifying a collection while the contents are being iterated +# gives undefined behavior. See +# https://blade.ruby-lang.org/ruby-core/23633 + +ruby_version_is "4.0" do + describe "Array#rfind" do + it "returns the last element for which the block is not false" do + [1, 2, 3, 4, 5].rfind { |x| x % 2 == 0 }.should == 4 + end + + it "returns nil when the block is false and there is no ifnone proc given" do + [1, 2, 3].rfind { |x| false }.should == nil + end + + it "returns the value of the ifnone proc if the block is false" do + fail_proc = -> { "cheeseburgers" } + [1, 2, 3].rfind(fail_proc) { |x| false }.should == "cheeseburgers" + end + + it "doesn't call the ifnone proc if an element is found" do + fail_proc = -> { raise "This shouldn't have been called" } + [1, 2, 3].rfind(fail_proc) { |x| x == 3 }.should == 3 + end + + it "calls the ifnone proc only once when the block is false" do + times = 0 + fail_proc = -> { times += 1; raise if times > 1; "cheeseburgers" } + [1, 2, 3].rfind(fail_proc) { |x| false }.should == "cheeseburgers" + end + + it "calls the ifnone proc when there are no elements" do + fail_proc = -> { "yay" } + [].rfind(fail_proc) { |x| true }.should == "yay" + end + + it "ignores the ifnone argument when nil" do + [1, 2, 3].rfind(nil) { |x| false }.should == nil + end + + it "raises a NoMethodError if the ifnone argument does not respond to #call and no element is found" do + -> { [1, 2, 3].rfind(42) { |x| false } }.should.raise(NoMethodError) + end + + it "iterates elements in reverse order" do + visited = [] + [1, 2, 3].rfind { |x| visited << x; false } + visited.should == [3, 2, 1] + end + + it "passes through the values yielded by #each_with_index" do + ScratchPad.record [] + [:a, :b].each_with_index.to_a.rfind { |x, i| ScratchPad << [x, i]; nil } + ScratchPad.recorded.should == [[:b, 1], [:a, 0]] + end + + it "stops iterating as soon as a matching element is found from the end" do + visited = [] + [1, 2, 3, 4, 5].rfind { |x| visited << x; x == 3 } + visited.should == [5, 4, 3] + end + + it "returns an enumerator when no block given" do + [1, 2, 3].rfind.should.instance_of?(Enumerator) + end + + it "passes the ifnone proc to the enumerator" do + fail_proc = -> { "cheeseburgers" } + enum = [1, 2, 3].rfind(fail_proc) + enum.each { |x| false }.should == "cheeseburgers" + end + + it "does not destructure elements" do + multi = [[1, 2], [3, 4, 5], [6, 7, 8, 9]] + multi.rfind { |e| e == [1, 2] }.should == [1, 2] + end + + it "rechecks the array size during iteration" do + ary = [4, 2, 1, 5, 1, 3] + seen = [] + ary.rfind { |x| seen << x; ary.clear; false } + seen.should == [3] + end + + it_behaves_like :enumeratorized_with_unknown_size, :rfind, [1, 2, 3] + end +end diff --git a/spec/ruby/core/binding/implicit_parameter_defined_spec.rb b/spec/ruby/core/binding/implicit_parameter_defined_spec.rb new file mode 100644 index 00000000000000..2b3844dff7b206 --- /dev/null +++ b/spec/ruby/core/binding/implicit_parameter_defined_spec.rb @@ -0,0 +1,84 @@ +require_relative '../../spec_helper' + +ruby_version_is "4.0" do + eval <<-RUBY, binding, __FILE__, __LINE__ + 1 # use eval to avoid warnings on Ruby 3.3 + describe 'Binding#implicit_parameter_defined?' do + it 'returns false when a numbered parameters or "it" does not exist' do + binding.implicit_parameter_defined?(:it).should == false + binding.implicit_parameter_defined?(:_1).should == false + end + + it 'returns true when a numbered parameter exists' do + proc { _1; binding.implicit_parameter_defined?(:_1) }.call.should == true + proc { r = binding.implicit_parameter_defined?(:_1); _1; r }.call.should == true + end + + it 'returns true for all numbered parameters up to the maximum referenced one' do + _3 + binding.implicit_parameter_defined?(:_1).should == true + binding.implicit_parameter_defined?(:_2).should == true + binding.implicit_parameter_defined?(:_3).should == true + binding.implicit_parameter_defined?(:_4).should == false + end + + it 'returns true when "it" parameter exists' do + proc { it; binding.implicit_parameter_defined?(:it) }.call.should == true + proc { r = binding.implicit_parameter_defined?(:it); it; r }.call.should == true + end + + it 'returns false when a numbered parameter is defined in a parent scope' do + foo = _1 + -> { + binding.implicit_parameter_defined?(:_1) + }.call.should == false + end + + it 'returns false when "it" parameter is defined in a parent scope' do + foo = it + -> { + binding.implicit_parameter_defined?(:it) + }.call.should == false + end + + it 'returns false when a numbered parameter is defined in a nested scope' do + foo = -> { _1 } + binding.implicit_parameter_defined?(:_1).should == false + end + + it 'returns false when "it" parameter is defined in a nested scope' do + foo = -> { it } + binding.implicit_parameter_defined?(:it).should == false + end + + it 'allows usage of a String as a numbered parameter name' do + _1 + binding.implicit_parameter_defined?('_1').should == true + end + + it 'allows usage of a String as "it" parameter name' do + it + binding.implicit_parameter_defined?('it').should == true + end + + it 'allows usage of an object responding to #to_str as the variable name' do + foo = _1 + name = mock(:obj) + name.stub!(:to_str).and_return('_1') + + binding.implicit_parameter_defined?(name).should == true + end + + it 'raises a NameError when given neither a numbered parameter nor "it" parameter' do + -> { + binding.implicit_parameter_defined?(:a) + }.should.raise(NameError, "'a' is not an implicit parameter") + end + + it 'raises a TypeError when given non-String/Symbol as the variable name' do + -> { + binding.implicit_parameter_defined?(1) + }.should.raise(TypeError, '1 is not a symbol nor a string') + end + end + RUBY +end diff --git a/spec/ruby/core/binding/implicit_parameter_get_spec.rb b/spec/ruby/core/binding/implicit_parameter_get_spec.rb new file mode 100644 index 00000000000000..314746db2f85ad --- /dev/null +++ b/spec/ruby/core/binding/implicit_parameter_get_spec.rb @@ -0,0 +1,90 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +ruby_version_is "4.0" do + eval <<-RUBY, binding, __FILE__, __LINE__ + 1 # use eval to avoid warnings on Ruby 3.3 + describe 'Binding#implicit_parameter_get' do + it 'reads a numbered parameter value when it exists' do + -> { _1; binding.implicit_parameter_get(:_1) }.call(:a).should == :a + -> { r = binding.implicit_parameter_get(:_1); _1; r }.call(:a).should == :a + end + + it 'reads any numbered parameter value up to the maximum referenced one' do + proc { + _3 + [ + binding.implicit_parameter_get(:_1), + binding.implicit_parameter_get(:_2), + binding.implicit_parameter_get(:_3) + ] + }.call(:a, :b, :c, :d).should == [:a, :b, :c] + end + + it 'reads "it" parameter value when it exists' do + -> { it; binding.implicit_parameter_get(:it) }.call(:a).should == :a + -> { r = binding.implicit_parameter_get(:it); it; r }.call(:a).should == :a + end + + it 'raises a NameError for not existing numbered parameter' do + proc { binding.implicit_parameter_get(:_1) }.should.raise(NameError, /implicit parameter '_1' is not defined for/) + end + + it 'raises a NameError for not existing "it" parameter' do + proc { binding.implicit_parameter_get(:it) }.should.raise(NameError, /implicit parameter 'it' is not defined for/) + end + + it 'raises a NameError when a numbered parameter is defined in a parent scope' do + proc { + foo = _1 + proc { binding.implicit_parameter_get(:_1) }.call + }.should.raise(NameError, /implicit parameter '_1' is not defined for/) + end + + it 'raises a NameError when "it" parameter is defined in a parent scope' do + proc { + foo = it + proc { binding.implicit_parameter_get(:it) }.call + }.should.raise(NameError, /implicit parameter 'it' is not defined for/) + end + + it 'raises a NameError when a numbered parameter is defined in a nested scope' do + proc { + foo = -> { _1 } + binding.implicit_parameter_get(:_1) + }.should.raise(NameError, /implicit parameter '_1' is not defined for/) + end + + it 'raises a NameError when "it" parameter is defined in a nested scope' do + proc { + foo = -> { it } + binding.implicit_parameter_get(:it) + }.should.raise(NameError, /implicit parameter 'it' is not defined for/) + end + + it 'allows usage of a String as a numbered parameter name' do + -> { _1; binding.implicit_parameter_get('_1') }.call(:a).should == :a + end + + it 'allows usage of a String as "it" parameter name' do + -> { it; binding.implicit_parameter_get('it') }.call(:a).should == :a + end + + it 'allows usage of an object responding to #to_str as the variable name' do + name = mock(:obj) + name.stub!(:to_str).and_return('_1') + + -> { _1; binding.implicit_parameter_get(name) }.call(:a).should == :a + end + + it 'raises a NameError when given neither a numbered parameter nor "it" parameter' do + -> { binding.implicit_parameter_get(:a) }.should.raise(NameError, "'a' is not an implicit parameter") + end + + it 'raises a TypeError when given non-String/Symbol as the variable name' do + -> { + binding.implicit_parameter_get(1) + }.should.raise(TypeError, '1 is not a symbol nor a string') + end + end + RUBY +end diff --git a/spec/ruby/core/binding/implicit_parameters_spec.rb b/spec/ruby/core/binding/implicit_parameters_spec.rb new file mode 100644 index 00000000000000..5fd2ca0ac1edc2 --- /dev/null +++ b/spec/ruby/core/binding/implicit_parameters_spec.rb @@ -0,0 +1,64 @@ +require_relative '../../spec_helper' + +ruby_version_is "4.0" do + eval <<-RUBY, binding, __FILE__, __LINE__ + 1 # use eval to avoid warnings on Ruby 3.3 + describe 'Binding#implicit_parameters' do + it 'returns an Array' do + binding.implicit_parameters.should.is_a?(Array) + end + + it 'includes "it" parameter when defined in the current scope' do + a = it + binding.implicit_parameters.should == [:it] + end + + it 'includes numbered parameters when defined in the current scope' do + a = _1 + binding.implicit_parameters.should == [:_1] + end + + it 'includes all the numbered parameter names up to the maximum referenced one' do + proc { _3; binding.implicit_parameters }.call(:a, :b, :c, :d).should == [:_1, :_2, :_3] + end + + it 'returns [] when neither "it" parameter nor numbered parameters are defined in the current scope' do + a = 1 + binding.implicit_parameters.should == [] + end + + it "includes implicit parameters defined after calling binding.implicit_parameters" do + proc { + r = binding.implicit_parameters + a = it + r + }.call.should == [:it] + + proc { + r = binding.implicit_parameters + a = _1 + r + }.call.should == [:_1] + end + + it 'ignores "it" parameter defined in a parent scope' do + foo = it + proc { binding.implicit_parameters }.call.should == [] + end + + it 'ignores numbered parameters defined in a parent scope' do + foo = _1 + proc { binding.implicit_parameters }.call.should == [] + end + + it 'ignores "it" parameter defined in a nested scope' do + foo = -> { it } + binding.implicit_parameters.should == [] + end + + it 'ignores numbered parameters defined in a nested scope' do + foo = -> { _1 } + binding.implicit_parameters.should == [] + end + end + RUBY +end diff --git a/spec/ruby/core/binding/local_variable_defined_spec.rb b/spec/ruby/core/binding/local_variable_defined_spec.rb index 2fc6504ee51699..15f565108c9c04 100644 --- a/spec/ruby/core/binding/local_variable_defined_spec.rb +++ b/spec/ruby/core/binding/local_variable_defined_spec.rb @@ -43,4 +43,10 @@ binding.local_variable_defined?(name).should == true end + + it 'raises a TypeError when given non-String/Symbol as the variable name' do + -> { + binding.local_variable_defined?(1) + }.should.raise(TypeError, '1 is not a symbol nor a string') + end end diff --git a/spec/ruby/core/binding/local_variable_get_spec.rb b/spec/ruby/core/binding/local_variable_get_spec.rb index d97100deda3da6..9f5da405924a54 100644 --- a/spec/ruby/core/binding/local_variable_get_spec.rb +++ b/spec/ruby/core/binding/local_variable_get_spec.rb @@ -53,4 +53,10 @@ -> { bind.local_variable_get(:$~) }.should.raise(NameError) -> { bind.local_variable_get(:$_) }.should.raise(NameError) end + + it 'raises a TypeError when given non-String/Symbol as the variable name' do + -> { + binding.local_variable_get(1) + }.should.raise(TypeError, '1 is not a symbol nor a string') + end end diff --git a/spec/ruby/core/binding/local_variable_set_spec.rb b/spec/ruby/core/binding/local_variable_set_spec.rb index 3e4f407fc3292c..06c8fea9483e42 100644 --- a/spec/ruby/core/binding/local_variable_set_spec.rb +++ b/spec/ruby/core/binding/local_variable_set_spec.rb @@ -68,4 +68,9 @@ -> { bind.local_variable_set(:$_, "") }.should.raise(NameError) end + it 'raises a TypeError when given non-String/Symbol as the variable name' do + -> { + binding.local_variable_set(1, 2) + }.should.raise(TypeError, '1 is not a symbol nor a string') + end end diff --git a/spec/ruby/core/complex/to_s_spec.rb b/spec/ruby/core/complex/to_s_spec.rb index ceccffe4703779..ddd6dccf7d3fdd 100644 --- a/spec/ruby/core/complex/to_s_spec.rb +++ b/spec/ruby/core/complex/to_s_spec.rb @@ -16,11 +16,8 @@ Complex(1, -5).to_s.should == "1-5i" Complex(-2.5, -1.5).to_s.should == "-2.5-1.5i" - # Guard against the Mathn library - guard -> { !defined?(Math.rsqrt) } do - Complex(1, 0).to_s.should == "1+0i" - Complex(1, -0).to_s.should == "1+0i" - end + Complex(1, 0).to_s.should == "1+0i" + Complex(1, -0).to_s.should == "1+0i" end it "returns 1+0.0i for Complex(1, 0.0)" do diff --git a/spec/ruby/core/enumerable/find_spec.rb b/spec/ruby/core/enumerable/find_spec.rb index 4ac4b75c4735de..d3b8cd9d73e9ed 100644 --- a/spec/ruby/core/enumerable/find_spec.rb +++ b/spec/ruby/core/enumerable/find_spec.rb @@ -10,25 +10,16 @@ @empty = [] end - it "passes each entry in enum to block while block when block is false" do - visited_elements = [] - @numerous.find do |element| - visited_elements << element - false + it "returns the first element for which the block is not false" do + @elements.each do |element| + @numerous.find {|e| e > element - 1 }.should == element end - visited_elements.should == @elements end it "returns nil when the block is false and there is no ifnone proc given" do @numerous.find {|e| false }.should == nil end - it "returns the first element for which the block is not false" do - @elements.each do |element| - @numerous.find {|e| e > element - 1 }.should == element - end - end - it "returns the value of the ifnone proc if the block is false" do fail_proc = -> { "cheeseburgers" } @numerous.find(fail_proc) {|e| false }.should == "cheeseburgers" @@ -54,19 +45,35 @@ @numerous.find(nil) {|e| false }.should == nil end + it "raises a NoMethodError if the ifnone argument does not respond to #call and no element is found" do + -> { @numerous.find(42) {|e| false } }.should.raise(NoMethodError) + end + + it "iterates elements in forward order" do + visited = [] + @numerous.find { |element| visited << element; false } + visited.should == @elements + end + it "passes through the values yielded by #each_with_index" do [:a, :b].each_with_index.find { |x, i| ScratchPad << [x, i]; nil } ScratchPad.recorded.should == [[:a, 0], [:b, 1]] end + it "stops iterating as soon as an element is found" do + visited = [] + @numerous.find { |x| visited << x; x == 6 } + visited.should == [2, 4, 6] + end + it "returns an enumerator when no block given" do @numerous.find.should.instance_of?(Enumerator) end it "passes the ifnone proc to the enumerator" do - times = 0 - fail_proc = -> { times += 1; raise if times > 1; "cheeseburgers" } - @numerous.find(fail_proc).each {|e| false }.should == "cheeseburgers" + fail_proc = -> { "cheeseburgers" } + enum = @numerous.find(fail_proc) + enum.each { |e| false }.should == "cheeseburgers" end it "gathers whole arrays as elements when each yields multiple" do diff --git a/spec/ruby/core/file/birthtime_spec.rb b/spec/ruby/core/file/birthtime_spec.rb index 039fd7572c2fc5..f439970c308483 100644 --- a/spec/ruby/core/file/birthtime_spec.rb +++ b/spec/ruby/core/file/birthtime_spec.rb @@ -34,6 +34,21 @@ rescue NotImplementedError => e e.message.should.start_with?(*not_implemented_messages) end + + platform_is :linux do + guard -> { File.directory?('/proc') } do + it "raises NotImplementedError for a filesystem that does not support birthtime" do + # check if birthtime works on a regular file first + begin + File.birthtime(__FILE__) + rescue NotImplementedError + skip + end + + -> { File.birthtime('/proc') }.should.raise(NotImplementedError, "birthtime is unimplemented on this filesystem") + end + end + end end describe "File#birthtime" do diff --git a/spec/ruby/core/file/stat/birthtime_spec.rb b/spec/ruby/core/file/stat/birthtime_spec.rb index 728f6353971199..6cee468eab4057 100644 --- a/spec/ruby/core/file/stat/birthtime_spec.rb +++ b/spec/ruby/core/file/stat/birthtime_spec.rb @@ -22,6 +22,7 @@ st = File.stat(@file) st.birthtime.should.is_a?(Time) st.birthtime.should <= Time.now + st.birthtime.should > Time.now - 24*60*60 rescue NotImplementedError => e e.message.should.start_with?(*not_implemented_messages) end diff --git a/spec/ruby/core/hash/compare_by_identity_spec.rb b/spec/ruby/core/hash/compare_by_identity_spec.rb index 1abf9d5666222b..31a8ca1240b2d1 100644 --- a/spec/ruby/core/hash/compare_by_identity_spec.rb +++ b/spec/ruby/core/hash/compare_by_identity_spec.rb @@ -80,6 +80,16 @@ def o.hash; 123; end @h[o].should == :o end + it "regards two empty hashes equal, even if one isn't compare_by_identity" do + @h.should == @idh + end + + it "doesn't regard two hashes equal when they differ only by compare_by_identity" do + @h[1] = 2 + @idh[1] = 2 + @h.should_not == @idh + end + it "raises a FrozenError on frozen hashes" do @h = @h.freeze -> { @h.compare_by_identity }.should.raise(FrozenError) diff --git a/spec/ruby/core/io/advise_spec.rb b/spec/ruby/core/io/advise_spec.rb index b77ed53ad4d0dd..8253f641bea1cc 100644 --- a/spec/ruby/core/io/advise_spec.rb +++ b/spec/ruby/core/io/advise_spec.rb @@ -44,7 +44,7 @@ it "raises a NotImplementedError if advise is not recognized" do ->{ @io.advise(:foo) - }.should.raise(NotImplementedError) + }.should.raise(NotImplementedError, "Unsupported advice: :foo") end it "supports the normal advice type" do diff --git a/spec/ruby/core/io/binread_spec.rb b/spec/ruby/core/io/binread_spec.rb index 3023c7f177a7af..200fa05abfb853 100644 --- a/spec/ruby/core/io/binread_spec.rb +++ b/spec/ruby/core/io/binread_spec.rb @@ -48,10 +48,23 @@ ruby_version_is ""..."4.0" do # https://bugs.ruby-lang.org/issues/19630 it "warns about deprecation given a path with a pipe" do - cmd = "|echo ok" -> { - IO.binread(cmd) + IO.binread("|echo ok") }.should complain(/IO process creation with a leading '\|'/) end end + + ruby_version_is "4.0" do + platform_is_not :windows do + it "raises Errno::ENOENT when path starts with a pipe" do + -> { IO.binread("|echo ok") }.should.raise(Errno::ENOENT) + end + end + + platform_is :windows do + it "raises Errno::EINVAL when path starts with a pipe" do + -> { IO.binread("|echo ok") }.should.raise(Errno::EINVAL) + end + end + end end diff --git a/spec/ruby/core/io/foreach_spec.rb b/spec/ruby/core/io/foreach_spec.rb index ccd2f255170485..9d28f080ff34c9 100644 --- a/spec/ruby/core/io/foreach_spec.rb +++ b/spec/ruby/core/io/foreach_spec.rb @@ -49,12 +49,25 @@ # https://bugs.ruby-lang.org/issues/19630 it "warns about deprecation given a path with a pipe" do - cmd = "|echo ok" -> { - IO.foreach(cmd).to_a + IO.foreach("|echo ok").to_a }.should complain(/IO process creation with a leading '\|'/) end end + + ruby_version_is "4.0" do + platform_is_not :windows do + it "raises Errno::ENOENT when path starts with a pipe" do + -> { IO.foreach("|echo ok").to_a }.should.raise(Errno::ENOENT) + end + end + + platform_is :windows do + it "raises Errno::EINVAL when path starts with a pipe" do + -> { IO.foreach("|echo ok").to_a }.should.raise(Errno::EINVAL) + end + end + end end describe "IO.foreach" do diff --git a/spec/ruby/core/io/read_spec.rb b/spec/ruby/core/io/read_spec.rb index 5be969e2801248..0c165814c74194 100644 --- a/spec/ruby/core/io/read_spec.rb +++ b/spec/ruby/core/io/read_spec.rb @@ -150,8 +150,8 @@ end end -ruby_version_is ""..."4.0" do - describe "IO.read from a pipe" do +describe "IO.read from a pipe" do + ruby_version_is ""..."4.0" do it "runs the rest as a subprocess and returns the standard output" do cmd = "|sh -c 'echo hello'" platform_is :windows do @@ -216,12 +216,25 @@ # https://bugs.ruby-lang.org/issues/19630 it "warns about deprecation" do - cmd = "|echo ok" -> { - IO.read(cmd) + IO.read("|echo ok") }.should complain(/IO process creation with a leading '\|'/) end end + + ruby_version_is "4.0" do + platform_is_not :windows do + it "raises Errno::ENOENT when path starts with a pipe" do + -> { IO.read("|echo ok") }.should.raise(Errno::ENOENT) + end + end + + platform_is :windows do + it "raises Errno::EINVAL when path starts with a pipe" do + -> { IO.read("|echo ok") }.should.raise(Errno::EINVAL) + end + end + end end describe "IO.read on an empty file" do diff --git a/spec/ruby/core/io/readlines_spec.rb b/spec/ruby/core/io/readlines_spec.rb index d41d24d7d17b95..d872fb6b99d709 100644 --- a/spec/ruby/core/io/readlines_spec.rb +++ b/spec/ruby/core/io/readlines_spec.rb @@ -209,13 +209,26 @@ # https://bugs.ruby-lang.org/issues/19630 it "warns about deprecation given a path with a pipe" do - cmd = "|echo ok" -> { - IO.readlines(cmd) + IO.readlines("|echo ok") }.should complain(/IO process creation with a leading '\|'/) end end + ruby_version_is "4.0" do + platform_is_not :windows do + it "raises Errno::ENOENT when path starts with a pipe" do + -> { IO.readlines("|echo ok") }.should.raise(Errno::ENOENT) + end + end + + platform_is :windows do + it "raises Errno::EINVAL when path starts with a pipe" do + -> { IO.readlines("|echo ok") }.should.raise(Errno::EINVAL) + end + end + end + it_behaves_like :io_readlines, :readlines it_behaves_like :io_readlines_options_19, :readlines end diff --git a/spec/ruby/core/io/seek_spec.rb b/spec/ruby/core/io/seek_spec.rb index d26629fb892988..d6e553bae2ce58 100644 --- a/spec/ruby/core/io/seek_spec.rb +++ b/spec/ruby/core/io/seek_spec.rb @@ -24,6 +24,11 @@ @io.readline.should == "une.\n" end + it "accepts the symbol :CUR for SEEK_CUR" do + @io.seek(10, :CUR) + @io.readline.should == "igne une.\n" + end + it "moves the read position relative to the start with SEEK_SET" do @io.seek(1) @io.pos.should == 1 @@ -34,6 +39,11 @@ @io.readline.should == " la ligne une.\n" end + it "accepts the symbol :SET for SEEK_SET" do + @io.seek(43, :SET) + @io.readline.should == "Aquí está la línea tres.\n" + end + it "moves the read position relative to the end with SEEK_END" do @io.seek(0, IO::SEEK_END) @io.tell.should == 137 @@ -41,6 +51,11 @@ @io.readline.should == "cinco.\n" end + it "accepts the symbol :END for SEEK_END" do + @io.seek(0, :END) + @io.tell.should == 137 + end + it "moves the read position and clears EOF with SEEK_SET" do value = @io.read @io.seek(0, IO::SEEK_SET) diff --git a/spec/ruby/core/io/shared/new.rb b/spec/ruby/core/io/shared/new.rb index 6f318ddee50951..4952041a6f7f86 100644 --- a/spec/ruby/core/io/shared/new.rb +++ b/spec/ruby/core/io/shared/new.rb @@ -127,15 +127,15 @@ it "ignores the :encoding option when the :external_encoding option is present" do -> { - @io = IO.send(@method, @fd, 'w', external_encoding: 'utf-8', encoding: 'iso-8859-1:iso-8859-1') - }.should complain(/Ignoring encoding parameter/) - @io.external_encoding.to_s.should == 'UTF-8' + @io = IO.send(@method, @fd, 'w', external_encoding: 'ibm866', encoding: 'iso-8859-1:iso-8859-1') + }.should complain(/Ignoring encoding parameter 'iso-8859-1:iso-8859-1': external_encoding is used/) + @io.external_encoding.to_s.should == 'IBM866' end it "ignores the :encoding option when the :internal_encoding option is present" do -> { @io = IO.send(@method, @fd, 'w', internal_encoding: 'ibm866', encoding: 'iso-8859-1:iso-8859-1') - }.should complain(/Ignoring encoding parameter/) + }.should complain(/Ignoring encoding parameter 'iso-8859-1:iso-8859-1': internal_encoding is used/) @io.internal_encoding.to_s.should == 'IBM866' end diff --git a/spec/ruby/core/io/sysread_spec.rb b/spec/ruby/core/io/sysread_spec.rb index 2d58db22504137..078e97934a7668 100644 --- a/spec/ruby/core/io/sysread_spec.rb +++ b/spec/ruby/core/io/sysread_spec.rb @@ -103,7 +103,7 @@ it "discards the existing buffer content upon error" do buffer = +"existing content" - @file.seek(0, :END) + @file.seek(0, IO::SEEK_END) -> { @file.sysread(1, buffer) }.should.raise(EOFError) buffer.should.empty? end diff --git a/spec/ruby/core/io/sysseek_spec.rb b/spec/ruby/core/io/sysseek_spec.rb index 2384895dc5d8fb..e513d189aec0ff 100644 --- a/spec/ruby/core/io/sysseek_spec.rb +++ b/spec/ruby/core/io/sysseek_spec.rb @@ -21,6 +21,11 @@ @io.readline.should == "igne une.\n" end + it "accepts the symbol :CUR for SEEK_CUR" do + @io.sysseek(10, :CUR) + @io.readline.should == "igne une.\n" + end + it "raises an error when called after buffered reads" do @io.readline -> { @io.sysseek(-5, IO::SEEK_CUR) }.should.raise(IOError) @@ -36,6 +41,11 @@ @io.readline.should == "Aquí está la línea tres.\n" end + it "accepts the symbol :SET for SEEK_SET" do + @io.sysseek(43, :SET) + @io.readline.should == "Aquí está la línea tres.\n" + end + it "moves the read position relative to the end with SEEK_END" do @io.sysseek(1, IO::SEEK_END) @@ -46,4 +56,9 @@ @io.sysseek(-25, IO::SEEK_END) @io.sysread(7).should == "cinco.\n" end + + it "accepts the symbol :END for SEEK_END" do + @io.sysseek(-25, :END) + @io.sysread(7).should == "cinco.\n" + end end diff --git a/spec/ruby/core/io/ungetc_spec.rb b/spec/ruby/core/io/ungetc_spec.rb index 4a9e67f126b505..413ea2aa71c65d 100644 --- a/spec/ruby/core/io/ungetc_spec.rb +++ b/spec/ruby/core/io/ungetc_spec.rb @@ -105,7 +105,7 @@ it "raises TypeError if passed nil" do @io.getc.should == ?V - proc{@io.ungetc(nil)}.should.raise(TypeError) + proc{@io.ungetc(nil)}.should raise_consistent_error(TypeError, /no implicit conversion of nil into String/) end it "puts one or more characters back in the stream" do diff --git a/spec/ruby/core/io/write_spec.rb b/spec/ruby/core/io/write_spec.rb index 1a745ba0124fce..469f9a89ee316b 100644 --- a/spec/ruby/core/io/write_spec.rb +++ b/spec/ruby/core/io/write_spec.rb @@ -237,6 +237,19 @@ }.should complain(/IO process creation with a leading '\|'/) end end + + ruby_version_is "4.0" do + it "writes to that literal file when path starts with a pipe" do + Dir.chdir(tmp("")) do + begin + IO.write("|cat", "xxx") + File.read("|cat").should == "xxx" + ensure + File.unlink("|cat") + end + end + end + end end end diff --git a/spec/ruby/core/kernel/Array_spec.rb b/spec/ruby/core/kernel/Array_spec.rb index 063faf7097134d..ef54f57778731d 100644 --- a/spec/ruby/core/kernel/Array_spec.rb +++ b/spec/ruby/core/kernel/Array_spec.rb @@ -1,20 +1,18 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -describe "Kernel" do - it "has private instance method Array()" do - Kernel.private_instance_methods(false).should.include?(:Array) - end -end - -describe :kernel_Array, shared: true do +describe "Kernel#Array" do before :each do @array = [1, 2, 3] end + it "is a private method" do + Kernel.private_instance_methods(false).should.include?(:Array) + end + it "does not call #to_ary on an Array" do @array.should_not_receive(:to_ary) - @object.send(@method, @array).should == @array + Array(@array).should == @array end it "calls #to_ary to convert the argument to an Array" do @@ -22,19 +20,19 @@ obj.should_receive(:to_ary).and_return(@array) obj.should_not_receive(:to_a) - @object.send(@method, obj).should == @array + Array(obj).should == @array end it "does not call #to_a on an Array" do @array.should_not_receive(:to_a) - @object.send(@method, @array).should == @array + Array(@array).should == @array end it "calls #to_a if the argument does not respond to #to_ary" do obj = mock("Array([1,2,3])") obj.should_receive(:to_a).and_return(@array) - @object.send(@method, obj).should == @array + Array(obj).should == @array end it "calls #to_a if #to_ary returns nil" do @@ -42,56 +40,54 @@ obj.should_receive(:to_ary).and_return(nil) obj.should_receive(:to_a).and_return(@array) - @object.send(@method, obj).should == @array + Array(obj).should == @array end it "returns an Array containing the argument if #to_a returns nil" do obj = mock("Array([1,2,3])") obj.should_receive(:to_a).and_return(nil) - @object.send(@method, obj).should == [obj] + Array(obj).should == [obj] end it "calls #to_ary first, even if it's private" do obj = KernelSpecs::PrivateToAry.new - @object.send(@method, obj).should == [1, 2] + Array(obj).should == [1, 2] end it "calls #to_a if #to_ary is not defined, even if it's private" do obj = KernelSpecs::PrivateToA.new - @object.send(@method, obj).should == [3, 4] + Array(obj).should == [3, 4] end it "returns an Array containing the argument if it responds to neither #to_ary nor #to_a" do obj = mock("Array(x)") - @object.send(@method, obj).should == [obj] + Array(obj).should == [obj] end it "returns an empty Array when passed nil" do - @object.send(@method, nil).should == [] + Array(nil).should == [] end it "raises a TypeError if #to_ary does not return an Array" do obj = mock("Array() string") obj.should_receive(:to_ary).and_return("string") - -> { @object.send(@method, obj) }.should.raise(TypeError) + -> { Array(obj) }.should.raise(TypeError) end it "raises a TypeError if #to_a does not return an Array" do obj = mock("Array() string") obj.should_receive(:to_a).and_return("string") - -> { @object.send(@method, obj) }.should.raise(TypeError) + -> { Array(obj) }.should.raise(TypeError) end end describe "Kernel.Array" do - it_behaves_like :kernel_Array, :Array_method, KernelSpecs -end - -describe "Kernel#Array" do - it_behaves_like :kernel_Array, :Array_function, KernelSpecs + it "is a public method" do + Kernel.public_methods(false).should.include?(:Array) + end end diff --git a/spec/ruby/core/kernel/Complex_spec.rb b/spec/ruby/core/kernel/Complex_spec.rb index 92ce183cc8bef0..a3887953de2a05 100644 --- a/spec/ruby/core/kernel/Complex_spec.rb +++ b/spec/ruby/core/kernel/Complex_spec.rb @@ -2,7 +2,11 @@ require_relative '../../shared/kernel/complex' require_relative 'fixtures/Complex' -describe "Kernel.Complex()" do +describe "Kernel#Complex" do + it "is a private method" do + Kernel.private_instance_methods(false).should.include?(:Complex) + end + describe "when passed [Complex, Complex]" do it "returns a new Complex number based on the two given numbers" do Complex(Complex(3, 4), Complex(5, 6)).should == Complex(3 - 6, 4 + 5) @@ -39,24 +43,21 @@ describe "when passed [Integer/Float]" do it "returns a new Complex number with 0 as the imaginary component" do - # Guard against the Mathn library - guard -> { !defined?(Math.rsqrt) } do - Complex(1).should.instance_of?(Complex) - Complex(1).imag.should == 0 - Complex(1).real.should == 1 - - Complex(-3).should.instance_of?(Complex) - Complex(-3).imag.should == 0 - Complex(-3).real.should == -3 - - Complex(-4.5).should.instance_of?(Complex) - Complex(-4.5).imag.should == 0 - Complex(-4.5).real.should == -4.5 - - Complex(bignum_value).should.instance_of?(Complex) - Complex(bignum_value).imag.should == 0 - Complex(bignum_value).real.should == bignum_value - end + Complex(1).should.instance_of?(Complex) + Complex(1).imag.should == 0 + Complex(1).real.should == 1 + + Complex(-3).should.instance_of?(Complex) + Complex(-3).imag.should == 0 + Complex(-3).real.should == -3 + + Complex(-4.5).should.instance_of?(Complex) + Complex(-4.5).imag.should == 0 + Complex(-4.5).real.should == -4.5 + + Complex(bignum_value).should.instance_of?(Complex) + Complex(bignum_value).imag.should == 0 + Complex(bignum_value).real.should == bignum_value end end @@ -274,3 +275,9 @@ Complex(1).frozen?.should == true end end + +describe "Kernel.Complex" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:Complex) + end +end diff --git a/spec/ruby/core/kernel/Float_spec.rb b/spec/ruby/core/kernel/Float_spec.rb index f5566067ba8173..4c5d783095cfca 100644 --- a/spec/ruby/core/kernel/Float_spec.rb +++ b/spec/ruby/core/kernel/Float_spec.rb @@ -1,41 +1,45 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -describe :kernel_float, shared: true do +describe "Kernel#Float" do + it "is a instance method" do + Kernel.private_instance_methods(false).should.include?(:Float) + end + it "returns the identical Float for numeric Floats" do float = 1.12 - float2 = @object.send(:Float, float) + float2 = Float(float) float2.should == float float2.should.equal? float end it "returns a Float for Fixnums" do - @object.send(:Float, 1).should == 1.0 + Float(1).should == 1.0 end it "returns a Float for Complex with only a real part" do - @object.send(:Float, Complex(1)).should == 1.0 + Float(Complex(1)).should == 1.0 end it "returns a Float for Bignums" do - @object.send(:Float, 1000000000000).should == 1000000000000.0 + Float(1000000000000).should == 1000000000000.0 end it "raises an ArgumentError for nil" do - -> { @object.send(:Float, nil) }.should.raise(TypeError) + -> { Float(nil) }.should.raise(TypeError) end it "returns the identical NaN for NaN" do nan = nan_value nan.nan?.should == true - nan2 = @object.send(:Float, nan) + nan2 = Float(nan) nan2.nan?.should == true nan2.should.equal?(nan) end it "returns the same Infinity for Infinity" do infinity = infinity_value - infinity2 = @object.send(:Float, infinity) + infinity2 = Float(infinity) infinity2.should == infinity_value infinity.should.equal?(infinity2) end @@ -43,38 +47,38 @@ it "converts Strings to floats without calling #to_f" do string = +"10" string.should_not_receive(:to_f) - @object.send(:Float, string).should == 10.0 + Float(string).should == 10.0 end it "converts Strings with decimal points into Floats" do - @object.send(:Float, "10.0").should == 10.0 + Float("10.0").should == 10.0 end it "raises an ArgumentError for a String of word characters" do - -> { @object.send(:Float, "float") }.should.raise(ArgumentError) + -> { Float("float") }.should.raise(ArgumentError) end it "raises an ArgumentError for a String with string in error message" do - -> { @object.send(:Float, "foo") }.should.raise(ArgumentError) { |e| + -> { Float("foo") }.should.raise(ArgumentError) { |e| e.message.should == 'invalid value for Float(): "foo"' } end it "raises an ArgumentError if there are two decimal points in the String" do - -> { @object.send(:Float, "10.0.0") }.should.raise(ArgumentError) + -> { Float("10.0.0") }.should.raise(ArgumentError) end it "raises an ArgumentError for a String of numbers followed by word characters" do - -> { @object.send(:Float, "10D") }.should.raise(ArgumentError) + -> { Float("10D") }.should.raise(ArgumentError) end it "raises an ArgumentError for a String of word characters followed by numbers" do - -> { @object.send(:Float, "D10") }.should.raise(ArgumentError) + -> { Float("D10") }.should.raise(ArgumentError) end it "is strict about the string form even across newlines" do - -> { @object.send(:Float, "not a number\n10") }.should.raise(ArgumentError) - -> { @object.send(:Float, "10\nnot a number") }.should.raise(ArgumentError) + -> { Float("not a number\n10") }.should.raise(ArgumentError) + -> { Float("10\nnot a number") }.should.raise(ArgumentError) end it "converts String subclasses to floats without calling #to_f" do @@ -82,268 +86,274 @@ def to_f() 1.2 end end - @object.send(:Float, my_string.new("10")).should == 10.0 + Float(my_string.new("10")).should == 10.0 end it "returns a positive Float if the string is prefixed with +" do - @object.send(:Float, "+10").should == 10.0 - @object.send(:Float, " +10").should == 10.0 + Float("+10").should == 10.0 + Float(" +10").should == 10.0 end it "returns a negative Float if the string is prefixed with +" do - @object.send(:Float, "-10").should == -10.0 - @object.send(:Float, " -10").should == -10.0 + Float("-10").should == -10.0 + Float(" -10").should == -10.0 end it "raises an ArgumentError if a + or - is embedded in a String" do - -> { @object.send(:Float, "1+1") }.should.raise(ArgumentError) - -> { @object.send(:Float, "1-1") }.should.raise(ArgumentError) + -> { Float("1+1") }.should.raise(ArgumentError) + -> { Float("1-1") }.should.raise(ArgumentError) end it "raises an ArgumentError if a String has a trailing + or -" do - -> { @object.send(:Float, "11+") }.should.raise(ArgumentError) - -> { @object.send(:Float, "11-") }.should.raise(ArgumentError) + -> { Float("11+") }.should.raise(ArgumentError) + -> { Float("11-") }.should.raise(ArgumentError) end it "raises an ArgumentError for a String with a leading _" do - -> { @object.send(:Float, "_1") }.should.raise(ArgumentError) + -> { Float("_1") }.should.raise(ArgumentError) end it "returns a value for a String with an embedded _" do - @object.send(:Float, "1_000").should == 1000.0 + Float("1_000").should == 1000.0 end it "raises an ArgumentError for a String with a trailing _" do - -> { @object.send(:Float, "10_") }.should.raise(ArgumentError) + -> { Float("10_") }.should.raise(ArgumentError) end it "raises an ArgumentError for a String of \\0" do - -> { @object.send(:Float, "\0") }.should.raise(ArgumentError) + -> { Float("\0") }.should.raise(ArgumentError) end it "raises an ArgumentError for a String with a leading \\0" do - -> { @object.send(:Float, "\01") }.should.raise(ArgumentError) + -> { Float("\01") }.should.raise(ArgumentError) end it "raises an ArgumentError for a String with an embedded \\0" do - -> { @object.send(:Float, "1\01") }.should.raise(ArgumentError) + -> { Float("1\01") }.should.raise(ArgumentError) end it "raises an ArgumentError for a String with a trailing \\0" do - -> { @object.send(:Float, "1\0") }.should.raise(ArgumentError) + -> { Float("1\0") }.should.raise(ArgumentError) end it "raises an ArgumentError for a String that is just an empty space" do - -> { @object.send(:Float, " ") }.should.raise(ArgumentError) + -> { Float(" ") }.should.raise(ArgumentError) end it "raises an ArgumentError for a String that with an embedded space" do - -> { @object.send(:Float, "1 2") }.should.raise(ArgumentError) + -> { Float("1 2") }.should.raise(ArgumentError) end it "returns a value for a String with a leading space" do - @object.send(:Float, " 1").should == 1.0 + Float(" 1").should == 1.0 end it "returns a value for a String with a trailing space" do - @object.send(:Float, "1 ").should == 1.0 + Float("1 ").should == 1.0 end it "returns a value for a String with any leading whitespace" do - @object.send(:Float, "\t\n1").should == 1.0 + Float("\t\n1").should == 1.0 end it "returns a value for a String with any trailing whitespace" do - @object.send(:Float, "1\t\n").should == 1.0 + Float("1\t\n").should == 1.0 end ruby_version_is ""..."3.4" do it "raises ArgumentError if a fractional part is missing" do - -> { @object.send(:Float, "1.") }.should.raise(ArgumentError) - -> { @object.send(:Float, "+1.") }.should.raise(ArgumentError) - -> { @object.send(:Float, "-1.") }.should.raise(ArgumentError) - -> { @object.send(:Float, "1.e+0") }.should.raise(ArgumentError) - -> { @object.send(:Float, "1.e-2") }.should.raise(ArgumentError) + -> { Float("1.") }.should.raise(ArgumentError) + -> { Float("+1.") }.should.raise(ArgumentError) + -> { Float("-1.") }.should.raise(ArgumentError) + -> { Float("1.e+0") }.should.raise(ArgumentError) + -> { Float("1.e-2") }.should.raise(ArgumentError) end end ruby_version_is "3.4" do it "allows String representation without a fractional part" do - @object.send(:Float, "1.").should == 1.0 - @object.send(:Float, "+1.").should == 1.0 - @object.send(:Float, "-1.").should == -1.0 - @object.send(:Float, "1.e+0").should == 1.0 - @object.send(:Float, "1.e-2").should be_close(0.01, TOLERANCE) + Float("1.").should == 1.0 + Float("+1.").should == 1.0 + Float("-1.").should == -1.0 + Float("1.e+0").should == 1.0 + Float("1.e-2").should be_close(0.01, TOLERANCE) end end %w(e E).each do |e| it "raises an ArgumentError if #{e} is the trailing character" do - -> { @object.send(:Float, "2#{e}") }.should.raise(ArgumentError) + -> { Float("2#{e}") }.should.raise(ArgumentError) end it "raises an ArgumentError if #{e} is the leading character" do - -> { @object.send(:Float, "#{e}2") }.should.raise(ArgumentError) + -> { Float("#{e}2") }.should.raise(ArgumentError) end it "returns Infinity for '2#{e}1000'" do - @object.send(:Float, "2#{e}1000").should == Float::INFINITY + Float("2#{e}1000").should == Float::INFINITY end it "returns 0 for '2#{e}-1000'" do - @object.send(:Float, "2#{e}-1000").should == 0 + Float("2#{e}-1000").should == 0 end it "allows embedded _ in a number on either side of the #{e}" do - @object.send(:Float, "2_0#{e}100").should == 20e100 - @object.send(:Float, "20#{e}1_00").should == 20e100 - @object.send(:Float, "2_0#{e}1_00").should == 20e100 + Float("2_0#{e}100").should == 20e100 + Float("20#{e}1_00").should == 20e100 + Float("2_0#{e}1_00").should == 20e100 end it "raises an exception if a space is embedded on either side of the '#{e}'" do - -> { @object.send(:Float, "2 0#{e}100") }.should.raise(ArgumentError) - -> { @object.send(:Float, "20#{e}1 00") }.should.raise(ArgumentError) + -> { Float("2 0#{e}100") }.should.raise(ArgumentError) + -> { Float("20#{e}1 00") }.should.raise(ArgumentError) end it "raises an exception if there's a leading _ on either side of the '#{e}'" do - -> { @object.send(:Float, "_20#{e}100") }.should.raise(ArgumentError) - -> { @object.send(:Float, "20#{e}_100") }.should.raise(ArgumentError) + -> { Float("_20#{e}100") }.should.raise(ArgumentError) + -> { Float("20#{e}_100") }.should.raise(ArgumentError) end it "raises an exception if there's a trailing _ on either side of the '#{e}'" do - -> { @object.send(:Float, "20_#{e}100") }.should.raise(ArgumentError) - -> { @object.send(:Float, "20#{e}100_") }.should.raise(ArgumentError) + -> { Float("20_#{e}100") }.should.raise(ArgumentError) + -> { Float("20#{e}100_") }.should.raise(ArgumentError) end it "allows decimal points on the left side of the '#{e}'" do - @object.send(:Float, "2.0#{e}2").should == 2e2 + Float("2.0#{e}2").should == 2e2 end it "raises an ArgumentError if there's a decimal point on the right side of the '#{e}'" do - -> { @object.send(:Float, "20#{e}2.0") }.should.raise(ArgumentError) + -> { Float("20#{e}2.0") }.should.raise(ArgumentError) + end + + it "raises an ArgumentError if there's a trailing _ after '#{e}'" do + -> { @object.send(:Float, "20#{e}_") }.should.raise(ArgumentError) + -> { @object.send(:Float, "20#{e}+_") }.should.raise(ArgumentError) + -> { @object.send(:Float, "20#{e}-_") }.should.raise(ArgumentError) end end context "for hexadecimal literals" do it "interprets the 0x prefix as hexadecimal" do - @object.send(:Float, "0x10").should == 16.0 - @object.send(:Float, "0x0F").should == 15.0 - @object.send(:Float, "0x0f").should == 15.0 + Float("0x10").should == 16.0 + Float("0x0F").should == 15.0 + Float("0x0f").should == 15.0 end it "interprets negative hex value" do - @object.send(:Float, "-0x10").should == -16.0 + Float("-0x10").should == -16.0 end it "accepts embedded _ if the number does not contain a-f" do - @object.send(:Float, "0x1_0").should == 16.0 + Float("0x1_0").should == 16.0 end ruby_version_is ""..."3.4.3" do it "does not accept embedded _ if the number contains a-f" do - -> { @object.send(:Float, "0x1_0a") }.should.raise(ArgumentError) - @object.send(:Float, "0x1_0a", exception: false).should == nil + -> { Float("0x1_0a") }.should.raise(ArgumentError) + Float("0x1_0a", exception: false).should == nil end end ruby_version_is "3.4.3" do it "accepts embedded _ if the number contains a-f" do - @object.send(:Float, "0x1_0a").should == 0x10a.to_f + Float("0x1_0a").should == 0x10a.to_f end end it "does not accept _ before, after or inside the 0x prefix" do - -> { @object.send(:Float, "_0x10") }.should.raise(ArgumentError) - -> { @object.send(:Float, "0_x10") }.should.raise(ArgumentError) - -> { @object.send(:Float, "0x_10") }.should.raise(ArgumentError) - @object.send(:Float, "_0x10", exception: false).should == nil - @object.send(:Float, "0_x10", exception: false).should == nil - @object.send(:Float, "0x_10", exception: false).should == nil + -> { Float("_0x10") }.should.raise(ArgumentError) + -> { Float("0_x10") }.should.raise(ArgumentError) + -> { Float("0x_10") }.should.raise(ArgumentError) + Float("_0x10", exception: false).should == nil + Float("0_x10", exception: false).should == nil + Float("0x_10", exception: false).should == nil end it "parses negative hexadecimal string as negative float" do - @object.send(:Float, "-0x7b").should == -123.0 + Float("-0x7b").should == -123.0 end ruby_version_is "3.4" do it "accepts a fractional part" do - @object.send(:Float, "0x0.8").should == 0.5 + Float("0x0.8").should == 0.5 end end describe "with binary exponent" do %w(p P).each do |p| it "interprets the fractional part (on the left side of '#{p}') in hexadecimal" do - @object.send(:Float, "0x10#{p}0").should == 16.0 + Float("0x10#{p}0").should == 16.0 end it "interprets the exponent (on the right of '#{p}') in decimal" do - @object.send(:Float, "0x1#{p}10").should == 1024.0 + Float("0x1#{p}10").should == 1024.0 end it "raises an ArgumentError if #{p} is the trailing character" do - -> { @object.send(:Float, "0x1#{p}") }.should.raise(ArgumentError) + -> { Float("0x1#{p}") }.should.raise(ArgumentError) end it "raises an ArgumentError if #{p} is the leading character" do - -> { @object.send(:Float, "0x#{p}1") }.should.raise(ArgumentError) + -> { Float("0x#{p}1") }.should.raise(ArgumentError) end it "returns Infinity for '0x1#{p}10000'" do - @object.send(:Float, "0x1#{p}10000").should == Float::INFINITY + Float("0x1#{p}10000").should == Float::INFINITY end it "returns 0 for '0x1#{p}-10000'" do - @object.send(:Float, "0x1#{p}-10000").should == 0 + Float("0x1#{p}-10000").should == 0 end it "allows embedded _ in a number on either side of the #{p}" do - @object.send(:Float, "0x1_0#{p}10").should == 16384.0 - @object.send(:Float, "0x10#{p}1_0").should == 16384.0 - @object.send(:Float, "0x1_0#{p}1_0").should == 16384.0 + Float("0x1_0#{p}10").should == 16384.0 + Float("0x10#{p}1_0").should == 16384.0 + Float("0x1_0#{p}1_0").should == 16384.0 end it "raises an exception if a space is embedded on either side of the '#{p}'" do - -> { @object.send(:Float, "0x1 0#{p}10") }.should.raise(ArgumentError) - -> { @object.send(:Float, "0x10#{p}1 0") }.should.raise(ArgumentError) + -> { Float("0x1 0#{p}10") }.should.raise(ArgumentError) + -> { Float("0x10#{p}1 0") }.should.raise(ArgumentError) end it "raises an exception if there's a leading _ on either side of the '#{p}'" do - -> { @object.send(:Float, "0x_10#{p}10") }.should.raise(ArgumentError) - -> { @object.send(:Float, "0x10#{p}_10") }.should.raise(ArgumentError) + -> { Float("0x_10#{p}10") }.should.raise(ArgumentError) + -> { Float("0x10#{p}_10") }.should.raise(ArgumentError) end it "raises an exception if there's a trailing _ on either side of the '#{p}'" do - -> { @object.send(:Float, "0x10_#{p}10") }.should.raise(ArgumentError) - -> { @object.send(:Float, "0x10#{p}10_") }.should.raise(ArgumentError) + -> { Float("0x10_#{p}10") }.should.raise(ArgumentError) + -> { Float("0x10#{p}10_") }.should.raise(ArgumentError) end it "allows hexadecimal points on the left side of the '#{p}'" do - @object.send(:Float, "0x1.8#{p}0").should == 1.5 + Float("0x1.8#{p}0").should == 1.5 end it "raises an ArgumentError if there's a decimal point on the right side of the '#{p}'" do - -> { @object.send(:Float, "0x1#{p}1.0") }.should.raise(ArgumentError) + -> { Float("0x1#{p}1.0") }.should.raise(ArgumentError) end end end end it "returns a Float that can be a parameter to #Float again" do - float = @object.send(:Float, "10") - @object.send(:Float, float).should == 10.0 + float = Float("10") + Float(float).should == 10.0 end it "otherwise, converts the given argument to a Float by calling #to_f" do (obj = mock('1.2')).should_receive(:to_f).once.and_return(1.2) obj.should_not_receive(:to_i) - @object.send(:Float, obj).should == 1.2 + Float(obj).should == 1.2 end it "returns the identical NaN if to_f is called and it returns NaN" do nan = nan_value (nan_to_f = mock('NaN')).should_receive(:to_f).once.and_return(nan) - nan2 = @object.send(:Float, nan_to_f) + nan2 = Float(nan_to_f) nan2.nan?.should == true nan2.should.equal?(nan) end @@ -351,63 +361,55 @@ def to_f() 1.2 end it "returns the identical Infinity if #to_f is called and it returns Infinity" do infinity = infinity_value (infinity_to_f = mock('Infinity')).should_receive(:to_f).once.and_return(infinity) - infinity2 = @object.send(:Float, infinity_to_f) + infinity2 = Float(infinity_to_f) infinity2.should.equal?(infinity) end it "raises a TypeError if #to_f is not provided" do - -> { @object.send(:Float, mock('x')) }.should.raise(TypeError) + -> { Float(mock('x')) }.should.raise(TypeError) end it "raises a TypeError if #to_f returns a String" do (obj = mock('ha!')).should_receive(:to_f).once.and_return('ha!') - -> { @object.send(:Float, obj) }.should.raise(TypeError) + -> { Float(obj) }.should.raise(TypeError) end it "raises a TypeError if #to_f returns an Integer" do (obj = mock('123')).should_receive(:to_f).once.and_return(123) - -> { @object.send(:Float, obj) }.should.raise(TypeError) + -> { Float(obj) }.should.raise(TypeError) end it "raises a RangeError when passed a Complex argument" do c = Complex(2, 3) - -> { @object.send(:Float, c) }.should.raise(RangeError) + -> { Float(c) }.should.raise(RangeError) end describe "when passed exception: false" do describe "and valid input" do it "returns a Float number" do - @object.send(:Float, 1, exception: false).should == 1.0 - @object.send(:Float, "1", exception: false).should == 1.0 - @object.send(:Float, "1.23", exception: false).should == 1.23 + Float(1, exception: false).should == 1.0 + Float("1", exception: false).should == 1.0 + Float("1.23", exception: false).should == 1.23 end end describe "and invalid input" do it "swallows an error" do - @object.send(:Float, "abc", exception: false).should == nil - @object.send(:Float, :sym, exception: false).should == nil + Float("abc", exception: false).should == nil + Float(:sym, exception: false).should == nil end end describe "and nil" do it "swallows it" do - @object.send(:Float, nil, exception: false).should == nil + Float(nil, exception: false).should == nil end end end end describe "Kernel.Float" do - it_behaves_like :kernel_float, :Float, Kernel -end - -describe "Kernel#Float" do - it_behaves_like :kernel_float, :Float, Object.new -end - -describe "Kernel#Float" do - it "is a private method" do - Kernel.private_instance_methods(false).should.include?(:Float) + it "is a public method" do + Kernel.public_methods(false).should.include?(:Float) end end diff --git a/spec/ruby/core/kernel/Hash_spec.rb b/spec/ruby/core/kernel/Hash_spec.rb index ee16f56a90e8d9..8d3319a98c93b1 100644 --- a/spec/ruby/core/kernel/Hash_spec.rb +++ b/spec/ruby/core/kernel/Hash_spec.rb @@ -11,53 +11,49 @@ end end -describe "Kernel" do - it "has private instance method Hash()" do - Kernel.private_instance_methods(false).should.include?(:Hash) - end -end - -describe :kernel_Hash, shared: true do +describe "Kernel#Hash" do before :each do @hash = { a: 1} end + it "is a private method" do + Kernel.private_instance_methods(false).should.include?(:Hash) + end + it "converts nil to a Hash" do - @object.send(@method, nil).should == {} + Hash(nil).should == {} end it "converts an empty array to a Hash" do - @object.send(@method, []).should == {} + Hash([]).should == {} end it "does not call #to_hash on an Hash" do @hash.should_not_receive(:to_hash) - @object.send(@method, @hash).should == @hash + Hash(@hash).should == @hash end it "calls #to_hash to convert the argument to an Hash" do obj = mock("Hash(a: 1)") obj.should_receive(:to_hash).and_return(@hash) - @object.send(@method, obj).should == @hash + Hash(obj).should == @hash end it "raises a TypeError if it doesn't respond to #to_hash" do - -> { @object.send(@method, mock("")) }.should.raise(TypeError) + -> { Hash(mock("")) }.should.raise(TypeError) end it "raises a TypeError if #to_hash does not return an Hash" do obj = mock("Hash() string") obj.should_receive(:to_hash).and_return("string") - -> { @object.send(@method, obj) }.should.raise(TypeError) + -> { Hash(obj) }.should.raise(TypeError) end end describe "Kernel.Hash" do - it_behaves_like :kernel_Hash, :Hash_method, KernelSpecs -end - -describe "Kernel#Hash" do - it_behaves_like :kernel_Hash, :Hash_function, KernelSpecs + it "is a public method" do + Kernel.public_methods(false).should.include?(:Hash) + end end diff --git a/spec/ruby/core/kernel/Integer_spec.rb b/spec/ruby/core/kernel/Integer_spec.rb index 978fd8ef08c427..1c216ec54fcdab 100644 --- a/spec/ruby/core/kernel/Integer_spec.rb +++ b/spec/ruby/core/kernel/Integer_spec.rb @@ -1,7 +1,11 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -describe :kernel_integer, shared: true do +describe "Kernel#Integer" do + it "is a private method" do + Kernel.private_instance_methods(false).should.include?(:Integer) + end + it "returns a Bignum for a Bignum" do Integer(2e100).should == 2e100 end @@ -174,9 +178,7 @@ end end end -end -describe :kernel_integer_string, shared: true do it "raises an ArgumentError if the String is a null byte" do -> { Integer("\0") }.should.raise(ArgumentError) end @@ -377,9 +379,7 @@ -> { Integer("0#{d}a") }.should.raise(ArgumentError) end end -end -describe :kernel_integer_string_base, shared: true do it "raises an ArgumentError if the String is a null byte" do -> { Integer("\0", 2) }.should.raise(ArgumentError) end @@ -638,208 +638,184 @@ end end end -end -describe :kernel_Integer, shared: true do it "raises an ArgumentError when the String contains digits out of range of radix 2" do str = "23456789abcdefghijklmnopqrstuvwxyz" - -> { @object.send(@method, str, 2) }.should.raise(ArgumentError) + -> { Integer(str, 2) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 3" do str = "3456789abcdefghijklmnopqrstuvwxyz" - -> { @object.send(@method, str, 3) }.should.raise(ArgumentError) + -> { Integer(str, 3) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 4" do str = "456789abcdefghijklmnopqrstuvwxyz" - -> { @object.send(@method, str, 4) }.should.raise(ArgumentError) + -> { Integer(str, 4) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 5" do str = "56789abcdefghijklmnopqrstuvwxyz" - -> { @object.send(@method, str, 5) }.should.raise(ArgumentError) + -> { Integer(str, 5) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 6" do str = "6789abcdefghijklmnopqrstuvwxyz" - -> { @object.send(@method, str, 6) }.should.raise(ArgumentError) + -> { Integer(str, 6) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 7" do str = "789abcdefghijklmnopqrstuvwxyz" - -> { @object.send(@method, str, 7) }.should.raise(ArgumentError) + -> { Integer(str, 7) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 8" do str = "89abcdefghijklmnopqrstuvwxyz" - -> { @object.send(@method, str, 8) }.should.raise(ArgumentError) + -> { Integer(str, 8) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 9" do str = "9abcdefghijklmnopqrstuvwxyz" - -> { @object.send(@method, str, 9) }.should.raise(ArgumentError) + -> { Integer(str, 9) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 10" do str = "abcdefghijklmnopqrstuvwxyz" - -> { @object.send(@method, str, 10) }.should.raise(ArgumentError) + -> { Integer(str, 10) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 11" do str = "bcdefghijklmnopqrstuvwxyz" - -> { @object.send(@method, str, 11) }.should.raise(ArgumentError) + -> { Integer(str, 11) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 12" do str = "cdefghijklmnopqrstuvwxyz" - -> { @object.send(@method, str, 12) }.should.raise(ArgumentError) + -> { Integer(str, 12) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 13" do str = "defghijklmnopqrstuvwxyz" - -> { @object.send(@method, str, 13) }.should.raise(ArgumentError) + -> { Integer(str, 13) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 14" do str = "efghijklmnopqrstuvwxyz" - -> { @object.send(@method, str, 14) }.should.raise(ArgumentError) + -> { Integer(str, 14) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 15" do str = "fghijklmnopqrstuvwxyz" - -> { @object.send(@method, str, 15) }.should.raise(ArgumentError) + -> { Integer(str, 15) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 16" do str = "ghijklmnopqrstuvwxyz" - -> { @object.send(@method, str, 16) }.should.raise(ArgumentError) + -> { Integer(str, 16) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 17" do str = "hijklmnopqrstuvwxyz" - -> { @object.send(@method, str, 17) }.should.raise(ArgumentError) + -> { Integer(str, 17) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 18" do str = "ijklmnopqrstuvwxyz" - -> { @object.send(@method, str, 18) }.should.raise(ArgumentError) + -> { Integer(str, 18) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 19" do str = "jklmnopqrstuvwxyz" - -> { @object.send(@method, str, 19) }.should.raise(ArgumentError) + -> { Integer(str, 19) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 20" do str = "klmnopqrstuvwxyz" - -> { @object.send(@method, str, 20) }.should.raise(ArgumentError) + -> { Integer(str, 20) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 21" do str = "lmnopqrstuvwxyz" - -> { @object.send(@method, str, 21) }.should.raise(ArgumentError) + -> { Integer(str, 21) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 22" do str = "mnopqrstuvwxyz" - -> { @object.send(@method, str, 22) }.should.raise(ArgumentError) + -> { Integer(str, 22) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 23" do str = "nopqrstuvwxyz" - -> { @object.send(@method, str, 23) }.should.raise(ArgumentError) + -> { Integer(str, 23) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 24" do str = "opqrstuvwxyz" - -> { @object.send(@method, str, 24) }.should.raise(ArgumentError) + -> { Integer(str, 24) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 25" do str = "pqrstuvwxyz" - -> { @object.send(@method, str, 25) }.should.raise(ArgumentError) + -> { Integer(str, 25) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 26" do str = "qrstuvwxyz" - -> { @object.send(@method, str, 26) }.should.raise(ArgumentError) + -> { Integer(str, 26) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 27" do str = "rstuvwxyz" - -> { @object.send(@method, str, 27) }.should.raise(ArgumentError) + -> { Integer(str, 27) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 28" do str = "stuvwxyz" - -> { @object.send(@method, str, 28) }.should.raise(ArgumentError) + -> { Integer(str, 28) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 29" do str = "tuvwxyz" - -> { @object.send(@method, str, 29) }.should.raise(ArgumentError) + -> { Integer(str, 29) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 30" do str = "uvwxyz" - -> { @object.send(@method, str, 30) }.should.raise(ArgumentError) + -> { Integer(str, 30) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 31" do str = "vwxyz" - -> { @object.send(@method, str, 31) }.should.raise(ArgumentError) + -> { Integer(str, 31) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 32" do str = "wxyz" - -> { @object.send(@method, str, 32) }.should.raise(ArgumentError) + -> { Integer(str, 32) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 33" do str = "xyz" - -> { @object.send(@method, str, 33) }.should.raise(ArgumentError) + -> { Integer(str, 33) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 34" do str = "yz" - -> { @object.send(@method, str, 34) }.should.raise(ArgumentError) + -> { Integer(str, 34) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 35" do str = "z" - -> { @object.send(@method, str, 35) }.should.raise(ArgumentError) + -> { Integer(str, 35) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 36" do - -> { @object.send(@method, "{", 36) }.should.raise(ArgumentError) + -> { Integer("{", 36) }.should.raise(ArgumentError) end end describe "Kernel.Integer" do - it_behaves_like :kernel_Integer, :Integer_method, KernelSpecs - - # TODO: fix these specs - it_behaves_like :kernel_integer, :Integer, Kernel - it_behaves_like :kernel_integer_string, :Integer - - it_behaves_like :kernel_integer_string_base, :Integer - it "is a public method" do - Kernel.Integer(10).should == 10 - end -end - -describe "Kernel#Integer" do - it_behaves_like :kernel_Integer, :Integer_function, KernelSpecs - - # TODO: fix these specs - it_behaves_like :kernel_integer, :Integer, Object.new - it_behaves_like :kernel_integer_string, :Integer - - it_behaves_like :kernel_integer_string_base, :Integer - - it "is a private method" do - Kernel.private_instance_methods(false).should.include?(:Integer) + Kernel.public_methods(false).should.include?(:Integer) end end diff --git a/spec/ruby/core/kernel/Rational_spec.rb b/spec/ruby/core/kernel/Rational_spec.rb index b13ab4cf551f7c..7ffa314a535267 100644 --- a/spec/ruby/core/kernel/Rational_spec.rb +++ b/spec/ruby/core/kernel/Rational_spec.rb @@ -1,15 +1,16 @@ require_relative '../../spec_helper' require_relative '../rational/fixtures/rational' -describe "Kernel.Rational" do +describe "Kernel#Rational" do + it "is a private method" do + Kernel.private_instance_methods(false).should.include?(:Rational) + end + describe "passed Integer" do - # Guard against the Mathn library - guard -> { !defined?(Math.rsqrt) } do - it "returns a new Rational number with 1 as the denominator" do - Rational(1).should.eql?(Rational(1, 1)) - Rational(-3).should.eql?(Rational(-3, 1)) - Rational(bignum_value).should.eql?(Rational(bignum_value, 1)) - end + it "returns a new Rational number with 1 as the denominator" do + Rational(1).should.eql?(Rational(1, 1)) + Rational(-3).should.eql?(Rational(-3, 1)) + Rational(bignum_value).should.eql?(Rational(bignum_value, 1)) end end @@ -234,3 +235,9 @@ def obj.to_int; raise; end Rational(1).frozen?.should == true end end + +describe "Kernel.Rational" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:Rational) + end +end diff --git a/spec/ruby/core/kernel/String_spec.rb b/spec/ruby/core/kernel/String_spec.rb index a9b0758ad70b3f..6c86745ee8e17f 100644 --- a/spec/ruby/core/kernel/String_spec.rb +++ b/spec/ruby/core/kernel/String_spec.rb @@ -1,29 +1,33 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -describe :kernel_String, shared: true do +describe "Kernel#String" do + it "is a private method" do + Kernel.private_instance_methods(false).should.include?(:String) + end + it "converts nil to a String" do - @object.send(@method, nil).should == "" + String(nil).should == "" end it "converts a Float to a String" do - @object.send(@method, 1.12).should == "1.12" + String(1.12).should == "1.12" end it "converts a boolean to a String" do - @object.send(@method, false).should == "false" - @object.send(@method, true).should == "true" + String(false).should == "false" + String(true).should == "true" end it "converts a constant to a String" do - @object.send(@method, Object).should == "Object" + String(Object).should == "Object" end it "calls #to_s to convert an arbitrary object to a String" do obj = mock('test') obj.should_receive(:to_s).and_return("test") - @object.send(@method, obj).should == "test" + String(obj).should == "test" end it "raises a TypeError if #to_s does not exist" do @@ -32,7 +36,7 @@ class << obj undef_method :to_s end - -> { @object.send(@method, obj) }.should.raise(TypeError) + -> { String(obj) }.should.raise(TypeError) end # #5158 @@ -44,7 +48,7 @@ def respond_to?(meth, include_private=false) end end - -> { @object.send(@method, obj) }.should.raise(TypeError) + -> { String(obj) }.should.raise(TypeError) end it "raises a TypeError if #to_s is not defined, even though #respond_to?(:to_s) returns true" do @@ -57,7 +61,7 @@ def respond_to?(meth, include_private=false) end end - -> { @object.send(@method, obj) }.should.raise(TypeError) + -> { String(obj) }.should.raise(TypeError) end it "calls #to_s if #respond_to?(:to_s) returns true" do @@ -69,18 +73,18 @@ def method_missing(meth, *args) end end - @object.send(@method, obj).should == "test" + String(obj).should == "test" end it "raises a TypeError if #to_s does not return a String" do (obj = mock('123')).should_receive(:to_s).and_return(123) - -> { @object.send(@method, obj) }.should.raise(TypeError) + -> { String(obj) }.should.raise(TypeError) end it "returns the same object if it is already a String" do string = +"Hello" string.should_not_receive(:to_s) - string2 = @object.send(@method, string) + string2 = String(string) string.should.equal?(string2) end @@ -88,19 +92,13 @@ def method_missing(meth, *args) subklass = Class.new(String) string = subklass.new("Hello") string.should_not_receive(:to_s) - string2 = @object.send(@method, string) + string2 = String(string) string.should.equal?(string2) end end describe "Kernel.String" do - it_behaves_like :kernel_String, :String, Kernel -end - -describe "Kernel#String" do - it_behaves_like :kernel_String, :String, Object.new - - it "is a private method" do - Kernel.private_instance_methods(false).should.include?(:String) + it "is a public method" do + Kernel.public_methods(false).should.include?(:String) end end diff --git a/spec/ruby/core/kernel/__callee___spec.rb b/spec/ruby/core/kernel/__callee___spec.rb index 3059ea8b5778b3..db104835f89cab 100644 --- a/spec/ruby/core/kernel/__callee___spec.rb +++ b/spec/ruby/core/kernel/__callee___spec.rb @@ -1,7 +1,11 @@ require_relative '../../spec_helper' require_relative 'fixtures/__callee__' -describe "Kernel.__callee__" do +describe "Kernel#__callee__" do + it "is a private method" do + Kernel.private_instance_methods(false).should.include?(:__callee__) + end + it "returns the current method, even when aliased" do KernelSpecs::CalleeTest.new.f.should == :f end @@ -46,3 +50,9 @@ def g; f end c.new.g.should == :f end end + +describe "Kernel.__callee__" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:__callee__) + end +end diff --git a/spec/ruby/core/kernel/__dir___spec.rb b/spec/ruby/core/kernel/__dir___spec.rb index 242adbf48b5a1c..87de578ca7193e 100644 --- a/spec/ruby/core/kernel/__dir___spec.rb +++ b/spec/ruby/core/kernel/__dir___spec.rb @@ -1,6 +1,10 @@ require_relative '../../spec_helper' describe "Kernel#__dir__" do + it "is a private method" do + Kernel.private_instance_methods(false).should.include?(:__dir__) + end + it "returns the real name of the directory containing the currently-executing file" do __dir__.should == File.realpath(File.dirname(__FILE__)) end @@ -25,3 +29,9 @@ end end end + +describe "Kernel.__dir__" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:__dir__) + end +end diff --git a/spec/ruby/core/kernel/__method___spec.rb b/spec/ruby/core/kernel/__method___spec.rb index 578d25640d2846..cc485a397c6ef9 100644 --- a/spec/ruby/core/kernel/__method___spec.rb +++ b/spec/ruby/core/kernel/__method___spec.rb @@ -1,7 +1,11 @@ require_relative '../../spec_helper' require_relative 'fixtures/__method__' -describe "Kernel.__method__" do +describe "Kernel#__method__" do + it "is a private method" do + Kernel.private_instance_methods(false).should.include?(:__method__) + end + it "returns the current method, even when aliased" do KernelSpecs::MethodTest.new.f.should == :f end @@ -38,3 +42,9 @@ __method__.should == nil end end + +describe "Kernel.__method__" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:__method__) + end +end diff --git a/spec/ruby/core/kernel/abort_spec.rb b/spec/ruby/core/kernel/abort_spec.rb index b75138182d43ab..2daa674a7eea28 100644 --- a/spec/ruby/core/kernel/abort_spec.rb +++ b/spec/ruby/core/kernel/abort_spec.rb @@ -11,5 +11,7 @@ end describe "Kernel.abort" do - it_behaves_like :process_abort, :abort, Kernel + it "is a public method" do + Kernel.public_methods(false).should.include?(:abort) + end end diff --git a/spec/ruby/core/kernel/at_exit_spec.rb b/spec/ruby/core/kernel/at_exit_spec.rb index fdb0eaf34c4c35..4f3bbe128f36de 100644 --- a/spec/ruby/core/kernel/at_exit_spec.rb +++ b/spec/ruby/core/kernel/at_exit_spec.rb @@ -2,7 +2,7 @@ require_relative 'fixtures/classes' require_relative '../../shared/kernel/at_exit' -describe "Kernel.at_exit" do +describe "Kernel#at_exit" do it_behaves_like :kernel_at_exit, :at_exit it "is a private method" do @@ -14,6 +14,8 @@ end end -describe "Kernel#at_exit" do - it "needs to be reviewed for spec completeness" +describe "Kernel.at_exit" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:at_exit) + end end diff --git a/spec/ruby/core/kernel/autoload_relative_spec.rb b/spec/ruby/core/kernel/autoload_relative_spec.rb index 7d03da8a4073ae..88ea1c33db5682 100644 --- a/spec/ruby/core/kernel/autoload_relative_spec.rb +++ b/spec/ruby/core/kernel/autoload_relative_spec.rb @@ -1,8 +1,6 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -# Specs for Kernel#autoload_relative - ruby_version_is "4.1" do describe "Kernel#autoload_relative" do before :each do @@ -111,4 +109,10 @@ end end end + + describe "Kernel.autoload_relative" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:autoload_relative) + end + end end diff --git a/spec/ruby/core/kernel/autoload_spec.rb b/spec/ruby/core/kernel/autoload_spec.rb index 46783734c43a0a..c2ac7e556ac31e 100644 --- a/spec/ruby/core/kernel/autoload_spec.rb +++ b/spec/ruby/core/kernel/autoload_spec.rb @@ -131,6 +131,10 @@ def go $".replace @loaded_features end + it "is a public method" do + Kernel.public_methods(false).should.include?(:autoload) + end + it "registers a file to load the first time the toplevel constant is accessed" do Kernel.autoload :KSAutoloadAA, @non_existent Kernel.autoload?(:KSAutoloadAA).should == @non_existent @@ -167,6 +171,10 @@ def go end describe "Kernel.autoload?" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:autoload?) + end + it "returns the name of the file that will be autoloaded" do Kernel.autoload :KSAutoload, "autoload.rb" Kernel.autoload?(:KSAutoload).should == "autoload.rb" diff --git a/spec/ruby/core/kernel/binding_spec.rb b/spec/ruby/core/kernel/binding_spec.rb index 6f27b26f379ba7..56432338e3e6f1 100644 --- a/spec/ruby/core/kernel/binding_spec.rb +++ b/spec/ruby/core/kernel/binding_spec.rb @@ -1,12 +1,6 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -describe "Kernel.binding" do - it "returns a binding for the caller" do - Kernel.binding.eval("self").should == self - end -end - describe "Kernel#binding" do it "is a private method" do Kernel.private_instance_methods(false).should.include?(:binding) @@ -49,3 +43,13 @@ ScratchPad.recorded.should == cls end end + +describe "Kernel.binding" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:binding) + end + + it "returns a binding for the caller" do + Kernel.binding.eval("self").should == self + end +end diff --git a/spec/ruby/core/kernel/block_given_spec.rb b/spec/ruby/core/kernel/block_given_spec.rb index 20bb90ae96d5cc..e4f17c6db91f16 100644 --- a/spec/ruby/core/kernel/block_given_spec.rb +++ b/spec/ruby/core/kernel/block_given_spec.rb @@ -1,43 +1,37 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -describe :kernel_block_given, shared: true do - it "returns true if and only if a block is supplied" do - @object.accept_block {}.should == true - @object.accept_block_as_argument {}.should == true - @object.accept_block_inside_block {}.should == true - @object.accept_block_as_argument_inside_block {}.should == true +describe "Kernel#block_given?" do + it "is a private method" do + Kernel.private_instance_methods(false).should.include?(:block_given?) + end - @object.accept_block.should == false - @object.accept_block_as_argument.should == false - @object.accept_block_inside_block.should == false - @object.accept_block_as_argument_inside_block.should == false + it "returns true if and only if a block is supplied" do + KernelSpecs::BlockGiven.accept_block {}.should == true + KernelSpecs::BlockGiven.accept_block_as_argument {}.should == true + KernelSpecs::BlockGiven.accept_block_inside_block {}.should == true + KernelSpecs::BlockGiven.accept_block_as_argument_inside_block {}.should == true + + KernelSpecs::BlockGiven.accept_block.should == false + KernelSpecs::BlockGiven.accept_block_as_argument.should == false + KernelSpecs::BlockGiven.accept_block_inside_block.should == false + KernelSpecs::BlockGiven.accept_block_as_argument_inside_block.should == false end # Clarify: Based on http://www.ruby-forum.com/topic/137822 it appears # that Matz wanted this to be true in 1.9. it "returns false when a method defined by define_method is called with a block" do - @object.defined_block {}.should == false - @object.defined_block_inside_block {}.should == false + KernelSpecs::BlockGiven.defined_block {}.should == false + KernelSpecs::BlockGiven.defined_block_inside_block {}.should == false end -end - -describe "Kernel#block_given?" do - it_behaves_like :kernel_block_given, :block_given?, KernelSpecs::BlockGiven it "returns false outside of a method" do block_given?.should == false end - - it "is a private method" do - Kernel.private_instance_methods(false).should.include?(:block_given?) - end end describe "Kernel.block_given?" do - it_behaves_like :kernel_block_given, :block_given?, KernelSpecs::KernelBlockGiven -end - -describe "self.send(:block_given?)" do - it_behaves_like :kernel_block_given, :block_given?, KernelSpecs::SelfBlockGiven + it "is a public method" do + Kernel.public_methods(false).should.include?(:block_given?) + end end diff --git a/spec/ruby/core/kernel/caller_locations_spec.rb b/spec/ruby/core/kernel/caller_locations_spec.rb index 04cf806ef4bdf8..99392ebfb6fd6d 100644 --- a/spec/ruby/core/kernel/caller_locations_spec.rb +++ b/spec/ruby/core/kernel/caller_locations_spec.rb @@ -22,6 +22,18 @@ locations.length.should == 1 end + it "raises for negative start" do + -> { caller_locations(-1) }.should.raise(ArgumentError, "negative level (-1)") + end + + it "raises for negative length" do + -> { caller_locations(0, -1) }.should.raise(ArgumentError, "negative size (-1)") + end + + it "can be called with `nil` length" do + caller_locations(0, nil).map(&:to_s).should == caller_locations(0).map(&:to_s) + end + it "can be called with a range" do locations1 = caller_locations(0) locations2 = caller_locations(2..4) @@ -70,6 +82,11 @@ caller_locations.map(&:to_s).should == caller_locations(1..-1).map(&:to_s) end + it "coerces the arguments to integers" do + caller_locations(1.1, 1.1).map(&:to_s).should == caller(1, 1).map(&:to_s) + caller_locations(1.1..1.1).map(&:to_s).should == caller(1..1).map(&:to_s) + end + guard -> { Kernel.instance_method(:tap).source_location } do ruby_version_is ""..."3.4" do it "includes core library methods defined in Ruby" do @@ -111,3 +128,9 @@ end end end + +describe "Kernel.caller_locations" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:caller_locations) + end +end diff --git a/spec/ruby/core/kernel/caller_spec.rb b/spec/ruby/core/kernel/caller_spec.rb index 225f6fc4582c4b..4491d7e8acd487 100644 --- a/spec/ruby/core/kernel/caller_spec.rb +++ b/spec/ruby/core/kernel/caller_spec.rb @@ -43,6 +43,18 @@ lines[1].should =~ /\A#{path}:2:in [`']block in
'\n\z/ end + it "raises for negative start" do + -> { caller(-1) }.should.raise(ArgumentError, "negative level (-1)") + end + + it "raises for negative length" do + -> { caller(0, -1) }.should.raise(ArgumentError, "negative size (-1)") + end + + it "can be called with `nil` length" do + caller(0, nil).should == caller(0) + end + it "can be called with a range" do locations1 = caller(0) locations2 = caller(2..4) @@ -83,6 +95,11 @@ caller.should == caller(1..-1) end + it "coerces the arguments to integers" do + caller(1.1, 1.1).should == caller(1, 1) + caller(1.1..1.1).should == caller(1..1) + end + guard -> { Kernel.instance_method(:tap).source_location } do ruby_version_is ""..."3.4" do it "includes core library methods defined in Ruby" do @@ -119,3 +136,9 @@ end end end + +describe "Kernel.caller" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:caller) + end +end diff --git a/spec/ruby/core/kernel/catch_spec.rb b/spec/ruby/core/kernel/catch_spec.rb index 9f023036782827..9f31429ce2fde8 100644 --- a/spec/ruby/core/kernel/catch_spec.rb +++ b/spec/ruby/core/kernel/catch_spec.rb @@ -1,11 +1,15 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -describe "Kernel.catch" do +describe "Kernel#catch" do before :each do ScratchPad.clear end + it "is a private method" do + Kernel.private_instance_methods(false).should.include?(:catch) + end + it "executes its block and catches a thrown value matching its argument" do catch :thrown_key do ScratchPad.record :catch_block @@ -120,8 +124,8 @@ def self.catching_method end end -describe "Kernel#catch" do - it "is a private method" do - Kernel.private_instance_methods(false).should.include?(:catch) +describe "Kernel.catch" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:catch) end end diff --git a/spec/ruby/core/kernel/chomp_spec.rb b/spec/ruby/core/kernel/chomp_spec.rb index 434bf652f98c89..2de6690b11f10a 100644 --- a/spec/ruby/core/kernel/chomp_spec.rb +++ b/spec/ruby/core/kernel/chomp_spec.rb @@ -2,45 +2,34 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -describe :kernel_chomp, shared: true do +describe "Kernel#chomp" do + it "is a private method only when -n is passed" do + Kernel.private_instance_methods(false).should_not.include?(:chomp) + KernelSpecs.private_instance_method_with_dash_n?(:chomp).should == true + end + it "removes the final newline of $_" do - KernelSpecs.chomp("abc\n", @method).should == "abc" + KernelSpecs.chomp_with_dash_n("abc\n").should == "abc" end it "removes the final carriage return of $_" do - KernelSpecs.chomp("abc\r", @method).should == "abc" + KernelSpecs.chomp_with_dash_n("abc\r").should == "abc" end it "removes the final carriage return, newline of $_" do - KernelSpecs.chomp("abc\r\n", @method).should == "abc" + KernelSpecs.chomp_with_dash_n("abc\r\n").should == "abc" end it "removes only the final newline of $_" do - KernelSpecs.chomp("abc\n\n", @method).should == "abc\n" + KernelSpecs.chomp_with_dash_n("abc\n\n").should == "abc\n" end it "removes the value of $/ from the end of $_" do - KernelSpecs.chomp("abcde", @method, "cde").should == "ab" - end -end - -describe :kernel_chomp_private, shared: true do - it "is a private method" do - KernelSpecs.has_private_method(@method).should == true + KernelSpecs.chomp_with_dash_n("abcde", "cde").should == "ab" end end -describe "Kernel.chomp" do - it_behaves_like :kernel_chomp, "Kernel.chomp" -end - describe "Kernel#chomp" do - it_behaves_like :kernel_chomp, "chomp" - - it_behaves_like :kernel_chomp_private, :chomp -end - -describe :kernel_chomp_encoded, shared: true do before :each do @external = Encoding.default_external Encoding.default_external = Encoding::UTF_8 @@ -51,15 +40,14 @@ end it "removes the final carriage return, newline from a multi-byte $_" do - script = fixture __FILE__, "#{@method}.rb" + script = fixture __FILE__, "chomp.rb" KernelSpecs.run_with_dash_n(script).should == "あれ" end end describe "Kernel.chomp" do - it_behaves_like :kernel_chomp_encoded, "chomp" -end - -describe "Kernel#chomp" do - it_behaves_like :kernel_chomp_encoded, "chomp_f" + it "is a public method only when -n is passed" do + Kernel.public_methods(false).should_not.include?(:chomp) + KernelSpecs.public_singleton_method_with_dash_n?(:chomp).should == true + end end diff --git a/spec/ruby/core/kernel/chop_spec.rb b/spec/ruby/core/kernel/chop_spec.rb index bbf3c3f7248c98..ae9e0c5572bfd1 100644 --- a/spec/ruby/core/kernel/chop_spec.rb +++ b/spec/ruby/core/kernel/chop_spec.rb @@ -2,33 +2,22 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -describe :kernel_chop, shared: true do - it "removes the final character of $_" do - KernelSpecs.chop("abc", @method).should == "ab" +describe "Kernel#chop" do + it "is a private method only when -n is passed" do + Kernel.private_instance_methods(false).should_not.include?(:chop) + KernelSpecs.private_instance_method_with_dash_n?(:chop).should == true end - it "removes the final carriage return, newline of $_" do - KernelSpecs.chop("abc\r\n", @method).should == "abc" + it "removes the final character of $_" do + KernelSpecs.chop_with_dash_n("abc").should == "ab" end -end -describe :kernel_chop_private, shared: true do - it "is a private method" do - KernelSpecs.has_private_method(@method).should == true + it "removes the final carriage return, newline of $_" do + KernelSpecs.chop_with_dash_n("abc\r\n").should == "abc" end end -describe "Kernel.chop" do - it_behaves_like :kernel_chop, "Kernel.chop" -end - describe "Kernel#chop" do - it_behaves_like :kernel_chop_private, :chop - - it_behaves_like :kernel_chop, "chop" -end - -describe :kernel_chop_encoded, shared: true do before :each do @external = Encoding.default_external Encoding.default_external = Encoding::UTF_8 @@ -39,15 +28,14 @@ end it "removes the final multi-byte character from $_" do - script = fixture __FILE__, "#{@method}.rb" + script = fixture __FILE__, "chop.rb" KernelSpecs.run_with_dash_n(script).should == "あ" end end describe "Kernel.chop" do - it_behaves_like :kernel_chop_encoded, "chop" -end - -describe "Kernel#chop" do - it_behaves_like :kernel_chop_encoded, "chop_f" + it "is a public method only when -n is passed" do + Kernel.public_methods(false).should_not.include?(:chop) + KernelSpecs.public_singleton_method_with_dash_n?(:chop).should == true + end end diff --git a/spec/ruby/core/kernel/eval_spec.rb b/spec/ruby/core/kernel/eval_spec.rb index f9ca47866e0fe1..fb021332acf49f 100644 --- a/spec/ruby/core/kernel/eval_spec.rb +++ b/spec/ruby/core/kernel/eval_spec.rb @@ -8,10 +8,6 @@ Kernel.private_instance_methods(false).should.include?(:eval) end - it "is a module function" do - Kernel.respond_to?(:eval).should == true - end - it "evaluates the code within" do eval("2 + 3").should == 5 end @@ -264,7 +260,6 @@ def foo(a, b:, &block) # See http://jira.codehaus.org/browse/JRUBY-5163 it "uses the receiver as self inside the eval" do eval("self").should.equal?(self) - Kernel.eval("self").should.equal?(Kernel) end it "does not pass the block to the method being eval'ed" do @@ -550,3 +545,13 @@ def foo end end end + +describe "Kernel.eval" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:Array) + end + + it "uses the receiver as self inside the eval" do + Kernel.eval("self").should.equal?(Kernel) + end +end diff --git a/spec/ruby/core/kernel/exec_spec.rb b/spec/ruby/core/kernel/exec_spec.rb index fd8485791a3602..6b611fa806579b 100644 --- a/spec/ruby/core/kernel/exec_spec.rb +++ b/spec/ruby/core/kernel/exec_spec.rb @@ -12,7 +12,7 @@ end describe "Kernel.exec" do - it "runs the specified command, replacing current process" do - ruby_exe('Kernel.exec "echo hello"; puts "fail"').should == "hello\n" + it "is a public method" do + Kernel.public_methods(false).should.include?(:exec) end end diff --git a/spec/ruby/core/kernel/exit_spec.rb b/spec/ruby/core/kernel/exit_spec.rb index 864ff84449e104..8de9bff588b467 100644 --- a/spec/ruby/core/kernel/exit_spec.rb +++ b/spec/ruby/core/kernel/exit_spec.rb @@ -11,7 +11,9 @@ end describe "Kernel.exit" do - it_behaves_like :process_exit, :exit, Kernel + it "is a public method" do + Kernel.public_methods(false).should.include?(:exit) + end end describe "Kernel#exit!" do @@ -23,5 +25,7 @@ end describe "Kernel.exit!" do - it_behaves_like :process_exit!, :exit!, "Kernel" + it "is a public method" do + Kernel.public_methods(false).should.include?(:exit!) + end end diff --git a/spec/ruby/core/kernel/fixtures/chomp.rb b/spec/ruby/core/kernel/fixtures/chomp.rb index f08dbadce5f591..3c22cb21e75594 100644 --- a/spec/ruby/core/kernel/fixtures/chomp.rb +++ b/spec/ruby/core/kernel/fixtures/chomp.rb @@ -1,4 +1,4 @@ # -*- encoding: utf-8 -*- $_ = "あれ\r\n" -print Kernel.chomp +print chomp diff --git a/spec/ruby/core/kernel/fixtures/chomp_f.rb b/spec/ruby/core/kernel/fixtures/chomp_f.rb deleted file mode 100644 index 3c22cb21e75594..00000000000000 --- a/spec/ruby/core/kernel/fixtures/chomp_f.rb +++ /dev/null @@ -1,4 +0,0 @@ -# -*- encoding: utf-8 -*- - -$_ = "あれ\r\n" -print chomp diff --git a/spec/ruby/core/kernel/fixtures/chop.rb b/spec/ruby/core/kernel/fixtures/chop.rb index dfd06267239ff7..4ec89eb9ec69a4 100644 --- a/spec/ruby/core/kernel/fixtures/chop.rb +++ b/spec/ruby/core/kernel/fixtures/chop.rb @@ -1,4 +1,4 @@ # -*- encoding: utf-8 -*- $_ = "あれ" -print Kernel.chop +print chop diff --git a/spec/ruby/core/kernel/fixtures/chop_f.rb b/spec/ruby/core/kernel/fixtures/chop_f.rb deleted file mode 100644 index 4ec89eb9ec69a4..00000000000000 --- a/spec/ruby/core/kernel/fixtures/chop_f.rb +++ /dev/null @@ -1,4 +0,0 @@ -# -*- encoding: utf-8 -*- - -$_ = "あれ" -print chop diff --git a/spec/ruby/core/kernel/fixtures/classes.rb b/spec/ruby/core/kernel/fixtures/classes.rb index ad82f3cef571e4..3559b0fbe538f4 100644 --- a/spec/ruby/core/kernel/fixtures/classes.rb +++ b/spec/ruby/core/kernel/fixtures/classes.rb @@ -1,54 +1,30 @@ module KernelSpecs - def self.Array_function(arg) - Array(arg) - end - - def self.Array_method(arg) - Kernel.Array(arg) - end - - def self.Hash_function(arg) - Hash(arg) - end - - def self.Hash_method(arg) - Kernel.Hash(arg) - end - - def self.Integer_function(arg) - Integer(arg) - end - - def self.Integer_method(arg) - Kernel.Integer(arg) - end - - def self.putc_function(arg) - putc arg - end - - def self.putc_method(arg) - Kernel.putc arg + def self.private_instance_method_with_dash_n?(name) + IO.popen([*ruby_exe, "-n", "-e", "print Kernel.private_instance_methods(false).include?(#{name.inspect})"], "r+") do |io| + io.puts + io.close_write + io.read + end == "true" end - def self.has_private_method(name) - IO.popen([*ruby_exe, "-n", "-e", "print Kernel.private_method_defined?(#{name.inspect})"], "r+") do |io| + def self.public_singleton_method_with_dash_n?(name) + IO.popen([*ruby_exe, "-n", "-e", "print Kernel.public_methods(false).include?(#{name.inspect})"], "r+") do |io| io.puts io.close_write io.read end == "true" end - def self.chop(str, method) - IO.popen([*ruby_exe, "-n", "-e", "$_ = #{str.inspect}; #{method}; print $_"], "r+") do |io| + def self.chop_with_dash_n(str) + IO.popen([*ruby_exe, "-n", "-e", "$_ = #{str.inspect}; chop; print $_"], "r+") do |io| io.puts io.close_write io.read end end - def self.chomp(str, method, sep="\n") - code = "$_ = #{str.inspect}; $/ = #{sep.inspect}; #{method}; print $_" + def self.chomp_with_dash_n(str, sep="\n") + code = "$_ = #{str.inspect}; $/ = #{sep.inspect}; chomp; print $_" IO.popen([*ruby_exe, "-W0", "-n", "-e", code], "r+") do |io| io.puts io.close_write @@ -263,74 +239,6 @@ class << self end end - module SelfBlockGiven - def self.accept_block - self.send(:block_given?) - end - - def self.accept_block_as_argument(&block) - self.send(:block_given?) - end - - def self.accept_block_inside_block - yield_self { - self.send(:block_given?) - } - end - - def self.accept_block_as_argument_inside_block(&block) - yield_self { - self.send(:block_given?) - } - end - - class << self - define_method(:defined_block) do - self.send(:block_given?) - end - - define_method(:defined_block_inside_block) do - yield_self { - self.send(:block_given?) - } - end - end - end - - module KernelBlockGiven - def self.accept_block - Kernel.block_given? - end - - def self.accept_block_as_argument(&block) - Kernel.block_given? - end - - def self.accept_block_inside_block - yield_self { - Kernel.block_given? - } - end - - def self.accept_block_as_argument_inside_block(&block) - yield_self { - Kernel.block_given? - } - end - - class << self - define_method(:defined_block) do - Kernel.block_given? - end - - define_method(:defined_block_inside_block) do - yield_self { - Kernel.block_given? - } - end - end - end - class EvalTest def self.eval_yield_with_binding eval("yield", binding) diff --git a/spec/ruby/core/kernel/fork_spec.rb b/spec/ruby/core/kernel/fork_spec.rb index 4a2c848202095a..f0a8f3729cc641 100644 --- a/spec/ruby/core/kernel/fork_spec.rb +++ b/spec/ruby/core/kernel/fork_spec.rb @@ -11,5 +11,7 @@ end describe "Kernel.fork" do - it_behaves_like :process_fork, :fork, Kernel + it "is a public method" do + Kernel.public_methods(false).should.include?(:fork) + end end diff --git a/spec/ruby/core/kernel/gets_spec.rb b/spec/ruby/core/kernel/gets_spec.rb index 1b2b36fbb96457..6a9764cfb5fb6c 100644 --- a/spec/ruby/core/kernel/gets_spec.rb +++ b/spec/ruby/core/kernel/gets_spec.rb @@ -13,5 +13,7 @@ end describe "Kernel.gets" do - it "needs to be reviewed for spec completeness" + it "is a public method" do + Kernel.public_methods(false).should.include?(:gets) + end end diff --git a/spec/ruby/core/kernel/global_variables_spec.rb b/spec/ruby/core/kernel/global_variables_spec.rb index d41bdff49a42f7..3a7cff4b2af701 100644 --- a/spec/ruby/core/kernel/global_variables_spec.rb +++ b/spec/ruby/core/kernel/global_variables_spec.rb @@ -1,7 +1,7 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -describe "Kernel.global_variables" do +describe "Kernel#global_variables" do it "is a private method" do Kernel.private_instance_methods(false).should.include?(:global_variables) end @@ -21,6 +21,8 @@ end end -describe "Kernel#global_variables" do - it "needs to be reviewed for spec completeness" +describe "Kernel.global_variables" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:global_variables) + end end diff --git a/spec/ruby/core/kernel/lambda_spec.rb b/spec/ruby/core/kernel/lambda_spec.rb index 6ab89c2bbb9e75..bfeaaf93f734af 100644 --- a/spec/ruby/core/kernel/lambda_spec.rb +++ b/spec/ruby/core/kernel/lambda_spec.rb @@ -4,7 +4,7 @@ # The functionality of lambdas is specified in core/proc -describe "Kernel.lambda" do +describe "Kernel#lambda" do it_behaves_like :kernel_lambda, :lambda it "is a private method" do @@ -108,3 +108,9 @@ def ret end end end + +describe "Kernel.lambda" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:lambda) + end +end diff --git a/spec/ruby/core/kernel/load_spec.rb b/spec/ruby/core/kernel/load_spec.rb index 890aab8c27b9fe..05433bfcf33650 100644 --- a/spec/ruby/core/kernel/load_spec.rb +++ b/spec/ruby/core/kernel/load_spec.rb @@ -24,17 +24,7 @@ end describe "Kernel.load" do - before :each do - CodeLoadingSpecs.spec_setup - end - - after :each do - CodeLoadingSpecs.spec_cleanup + it "is a public method" do + Kernel.public_methods(false).should.include?(:load) end - - it_behaves_like :kernel_require_basic, :load, Kernel -end - -describe "Kernel.load" do - it_behaves_like :kernel_load, :load, Kernel end diff --git a/spec/ruby/core/kernel/local_variables_spec.rb b/spec/ruby/core/kernel/local_variables_spec.rb index 40c343f7e46f41..8654081ea0e76a 100644 --- a/spec/ruby/core/kernel/local_variables_spec.rb +++ b/spec/ruby/core/kernel/local_variables_spec.rb @@ -43,3 +43,9 @@ def local_var_method local_var_method.should == [:a] end end + +describe "Kernel.local_variables" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:local_variables) + end +end diff --git a/spec/ruby/core/kernel/loop_spec.rb b/spec/ruby/core/kernel/loop_spec.rb index c2976e5cc5bc24..63589c0eab050b 100644 --- a/spec/ruby/core/kernel/loop_spec.rb +++ b/spec/ruby/core/kernel/loop_spec.rb @@ -1,7 +1,7 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -describe "Kernel.loop" do +describe "Kernel#loop" do it "is a private method" do Kernel.private_instance_methods(false).should.include?(:loop) end @@ -77,3 +77,9 @@ end end end + +describe "Kernel.loop" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:loop) + end +end diff --git a/spec/ruby/core/kernel/open_spec.rb b/spec/ruby/core/kernel/open_spec.rb index 14a3c43cad246b..4af4a52e502cbe 100644 --- a/spec/ruby/core/kernel/open_spec.rb +++ b/spec/ruby/core/kernel/open_spec.rb @@ -81,13 +81,26 @@ # https://bugs.ruby-lang.org/issues/19630 it "warns about deprecation given a path with a pipe" do - cmd = "|echo ok" -> { - open(cmd) { |f| f.read } + open("|echo ok") { |f| f.read } }.should complain(/Kernel#open with a leading '\|'/) end end + ruby_version_is "4.0" do + platform_is_not :windows do + it "raises Errno::ENOENT when path starts with a pipe" do + -> { open("|echo ok") { } }.should.raise(Errno::ENOENT) + end + end + + platform_is :windows do + it "raises Errno::EINVAL when path starts with a pipe" do + -> { open("|echo ok") { } }.should.raise(Errno::EINVAL) + end + end + end + it "raises an ArgumentError if not passed one argument" do -> { open }.should.raise(ArgumentError) end @@ -188,5 +201,7 @@ def obj.to_open(*args, **kw) end describe "Kernel.open" do - it "needs to be reviewed for spec completeness" + it "is a public method" do + Kernel.public_methods(false).should.include?(:open) + end end diff --git a/spec/ruby/core/kernel/p_spec.rb b/spec/ruby/core/kernel/p_spec.rb index f89716899a847d..9abacbfdb32999 100644 --- a/spec/ruby/core/kernel/p_spec.rb +++ b/spec/ruby/core/kernel/p_spec.rb @@ -81,5 +81,7 @@ end describe "Kernel.p" do - it "needs to be reviewed for spec completeness" + it "is a public method" do + Kernel.public_methods(false).should.include?(:p) + end end diff --git a/spec/ruby/core/kernel/print_spec.rb b/spec/ruby/core/kernel/print_spec.rb index 5473d22f71c8de..2bebbe75869998 100644 --- a/spec/ruby/core/kernel/print_spec.rb +++ b/spec/ruby/core/kernel/print_spec.rb @@ -20,5 +20,7 @@ end describe "Kernel.print" do - it "needs to be reviewed for spec completeness" + it "is a public method" do + Kernel.public_methods(false).should.include?(:print) + end end diff --git a/spec/ruby/core/kernel/printf_spec.rb b/spec/ruby/core/kernel/printf_spec.rb index 50939b3794be0e..af916c3eb3463d 100644 --- a/spec/ruby/core/kernel/printf_spec.rb +++ b/spec/ruby/core/kernel/printf_spec.rb @@ -3,12 +3,6 @@ require_relative 'shared/sprintf' describe "Kernel#printf" do - it "is a private method" do - Kernel.private_instance_methods(false).should.include?(:printf) - end -end - -describe "Kernel.printf" do before :each do @stdout = $stdout @name = tmp("kernel_puts.txt") @@ -21,26 +15,30 @@ rm_r @name end + it "is a private method" do + Kernel.private_instance_methods(false).should.include?(:printf) + end + it "writes to stdout when a string is the first argument" do $stdout.should_receive(:write).with("string") - Kernel.printf("%s", "string") + printf("%s", "string") end it "calls write on the first argument when it is not a string" do object = mock('io') object.should_receive(:write).with("string") - Kernel.printf(object, "%s", "string") + printf(object, "%s", "string") end it "calls #to_str to convert the format object to a String" do object = mock('format string') object.should_receive(:to_str).and_return("to_str: %i") $stdout.should_receive(:write).with("to_str: 42") - Kernel.printf($stdout, object, 42) + printf($stdout, object, 42) end end -describe "Kernel.printf" do +describe "Kernel#printf" do describe "formatting" do before :each do require "stringio" @@ -49,7 +47,7 @@ context "io is specified" do it_behaves_like :kernel_sprintf, -> format, *args { io = StringIO.new(+"") - Kernel.printf(io, format, *args) + printf(io, format, *args) io.string } end @@ -59,7 +57,7 @@ stdout = $stdout begin $stdout = io = StringIO.new(+"") - Kernel.printf(format, *args) + printf(format, *args) io.string ensure $stdout = stdout @@ -68,3 +66,9 @@ end end end + +describe "Kernel.printf" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:printf) + end +end diff --git a/spec/ruby/core/kernel/proc_spec.rb b/spec/ruby/core/kernel/proc_spec.rb index 1ba662177b2a17..eb7f98989ebd1f 100644 --- a/spec/ruby/core/kernel/proc_spec.rb +++ b/spec/ruby/core/kernel/proc_spec.rb @@ -4,7 +4,7 @@ # The functionality of Proc objects is specified in core/proc -describe "Kernel.proc" do +describe "Kernel#proc" do it "is a private method" do Kernel.private_instance_methods(false).should.include?(:proc) end @@ -46,3 +46,9 @@ def some_method }.should.raise(ArgumentError, 'tried to create Proc object without a block') end end + +describe "Kernel.proc" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:proc) + end +end diff --git a/spec/ruby/core/kernel/putc_spec.rb b/spec/ruby/core/kernel/putc_spec.rb index e6a20a9af184b7..622524cd31ad47 100644 --- a/spec/ruby/core/kernel/putc_spec.rb +++ b/spec/ruby/core/kernel/putc_spec.rb @@ -3,12 +3,6 @@ require_relative '../../shared/io/putc' describe "Kernel#putc" do - it "is a private instance method" do - Kernel.private_instance_methods(false).should.include?(:putc) - end -end - -describe "Kernel.putc" do before :each do @name = tmp("kernel_putc.txt") @io = new_io @name @@ -20,20 +14,15 @@ $stdout = @stdout end - it_behaves_like :io_putc, :putc_method, KernelSpecs -end - -describe "Kernel#putc" do - before :each do - @name = tmp("kernel_putc.txt") - @io = new_io @name - @io_object = @object - @stdout, $stdout = $stdout, @io + it "is a private method" do + Kernel.private_instance_methods(false).should.include?(:putc) end - after :each do - $stdout = @stdout - end + it_behaves_like :io_putc, :putc, Object.new +end - it_behaves_like :io_putc, :putc_function, KernelSpecs +describe "Kernel.putc" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:putc) + end end diff --git a/spec/ruby/core/kernel/puts_spec.rb b/spec/ruby/core/kernel/puts_spec.rb index eed18fcd50ecc0..7e02d356ee922e 100644 --- a/spec/ruby/core/kernel/puts_spec.rb +++ b/spec/ruby/core/kernel/puts_spec.rb @@ -25,5 +25,7 @@ end describe "Kernel.puts" do - it "needs to be reviewed for spec completeness" + it "is a public method" do + Kernel.public_methods(false).should.include?(:puts) + end end diff --git a/spec/ruby/core/kernel/raise_spec.rb b/spec/ruby/core/kernel/raise_spec.rb index 6162677e170e4e..c1642d5b43163a 100644 --- a/spec/ruby/core/kernel/raise_spec.rb +++ b/spec/ruby/core/kernel/raise_spec.rb @@ -214,9 +214,6 @@ class << public_raiser describe "Kernel.raise" do it "is a public method" do - Kernel.singleton_class.should.public_method_defined?(:raise) + Kernel.public_methods(false).should.include?(:Array) end - - it_behaves_like :kernel_raise, :raise, Kernel - it_behaves_like :kernel_raise_with_cause, :raise, Kernel end diff --git a/spec/ruby/core/kernel/rand_spec.rb b/spec/ruby/core/kernel/rand_spec.rb index 1bf5e225d9f9de..092883410f8515 100644 --- a/spec/ruby/core/kernel/rand_spec.rb +++ b/spec/ruby/core/kernel/rand_spec.rb @@ -193,5 +193,7 @@ end describe "Kernel.rand" do - it "needs to be reviewed for spec completeness" + it "is a public method" do + Kernel.public_methods(false).should.include?(:rand) + end end diff --git a/spec/ruby/core/kernel/readline_spec.rb b/spec/ruby/core/kernel/readline_spec.rb index 86899d78d26639..b8ab3be02b9f4d 100644 --- a/spec/ruby/core/kernel/readline_spec.rb +++ b/spec/ruby/core/kernel/readline_spec.rb @@ -8,5 +8,7 @@ end describe "Kernel.readline" do - it "needs to be reviewed for spec completeness" + it "is a public method" do + Kernel.public_methods(false).should.include?(:readline) + end end diff --git a/spec/ruby/core/kernel/readlines_spec.rb b/spec/ruby/core/kernel/readlines_spec.rb index c758014aabe884..51de0dff8ef3be 100644 --- a/spec/ruby/core/kernel/readlines_spec.rb +++ b/spec/ruby/core/kernel/readlines_spec.rb @@ -8,5 +8,7 @@ end describe "Kernel.readlines" do - it "needs to be reviewed for spec completeness" + it "is a public method" do + Kernel.public_methods(false).should.include?(:readlines) + end end diff --git a/spec/ruby/core/kernel/require_relative_spec.rb b/spec/ruby/core/kernel/require_relative_spec.rb index b3ac1fc64c5eff..e5bb862e6e93c7 100644 --- a/spec/ruby/core/kernel/require_relative_spec.rb +++ b/spec/ruby/core/kernel/require_relative_spec.rb @@ -14,6 +14,10 @@ CodeLoadingSpecs.spec_cleanup end + it "is a private method" do + Kernel.private_instance_methods(false).should.include?(:require_relative) + end + platform_is_not :windows do describe "when file is a symlink" do before :each do @@ -435,3 +439,9 @@ end end end + +describe "Kernel.require_relative" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:require_relative) + end +end diff --git a/spec/ruby/core/kernel/require_spec.rb b/spec/ruby/core/kernel/require_spec.rb index 28d94b0515121a..90b2e15a8b60fc 100644 --- a/spec/ruby/core/kernel/require_spec.rb +++ b/spec/ruby/core/kernel/require_spec.rb @@ -54,14 +54,7 @@ end describe "Kernel.require" do - before :each do - CodeLoadingSpecs.spec_setup + it "is a public method" do + Kernel.public_methods(false).should.include?(:require) end - - after :each do - CodeLoadingSpecs.spec_cleanup - end - - it_behaves_like :kernel_require_basic, :require, Kernel - it_behaves_like :kernel_require, :require, Kernel end diff --git a/spec/ruby/core/kernel/select_spec.rb b/spec/ruby/core/kernel/select_spec.rb index 58c963c1a4a974..d46e5fca68b19f 100644 --- a/spec/ruby/core/kernel/select_spec.rb +++ b/spec/ruby/core/kernel/select_spec.rb @@ -5,9 +5,7 @@ it "is a private method" do Kernel.private_instance_methods(false).should.include?(:select) end -end -describe "Kernel.select" do it 'does not block when timeout is 0' do IO.pipe do |read, write| select([read], [], [], 0).should == nil @@ -16,3 +14,9 @@ end end end + +describe "Kernel.select" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:select) + end +end diff --git a/spec/ruby/core/kernel/set_trace_func_spec.rb b/spec/ruby/core/kernel/set_trace_func_spec.rb index 5201812f2f5234..5da4d3f54a9b7a 100644 --- a/spec/ruby/core/kernel/set_trace_func_spec.rb +++ b/spec/ruby/core/kernel/set_trace_func_spec.rb @@ -8,5 +8,7 @@ end describe "Kernel.set_trace_func" do - it "needs to be reviewed for spec completeness" + it "is a public method" do + Kernel.public_methods(false).should.include?(:set_trace_func) + end end diff --git a/spec/ruby/core/kernel/shared/sprintf.rb b/spec/ruby/core/kernel/shared/sprintf.rb index e57334c5abb4fd..955d8ca36ebdea 100644 --- a/spec/ruby/core/kernel/shared/sprintf.rb +++ b/spec/ruby/core/kernel/shared/sprintf.rb @@ -460,6 +460,51 @@ def obj.to_str }.should.raise(ArgumentError) end + ruby_version_is ""..."3.4" do + it "formats single % character before a newline as literal %" do + @method.call("%\n").should == "%\n" + @method.call("foo%\n").should == "foo%\n" + @method.call("%\n.3f").should == "%\n.3f" + end + + it "formats single % character before a NUL as literal %" do + @method.call("%\0").should == "%\0" + @method.call("foo%\0").should == "foo%\0" + @method.call("%\0.3f").should == "%\0.3f" + end + + it "raises an error if single % appears anywhere else" do + -> { @method.call(" % ") }.should.raise(ArgumentError) + -> { @method.call("foo%quux") }.should.raise(ArgumentError) + end + + it "raises an error if NULL or \\n appear anywhere else in the format string" do + begin + old_debug, $DEBUG = $DEBUG, false + + -> { @method.call("%.\n3f", 1.2) }.should.raise(ArgumentError) + -> { @method.call("%.3\nf", 1.2) }.should.raise(ArgumentError) + -> { @method.call("%.\03f", 1.2) }.should.raise(ArgumentError) + -> { @method.call("%.3\0f", 1.2) }.should.raise(ArgumentError) + ensure + $DEBUG = old_debug + end + end + end + + ruby_version_is "3.4" do + it "raises ArgumentError if not followed by a conversion specifier" do + -> { @method.call("%") }.should.raise(ArgumentError, /incomplete format specifier/) + -> { @method.call("%\n") }.should.raise(ArgumentError, /malformed format string/) + -> { @method.call("%\0") }.should.raise(ArgumentError, /malformed format string/) + -> { @method.call(" % ") }.should.raise(ArgumentError, /malformed format string/) + -> { @method.call("%.\n3f") }.should.raise(ArgumentError, /malformed format string/) + -> { @method.call("%.3\nf") }.should.raise(ArgumentError, /malformed format string/) + -> { @method.call("%.\03f") }.should.raise(ArgumentError, /malformed format string/) + -> { @method.call("%.3\0f") }.should.raise(ArgumentError, /malformed format string/) + end + end + it "is escaped by %" do @method.call("%%").should == "%" @method.call("%%d").should == "%d" @@ -844,6 +889,18 @@ def obj.to_str @method.call("%*10d", 10, 112) }.should.raise(ArgumentError) end + + ruby_version_is ""..."3.4" do + it "replaces trailing absolute argument specifier without type with percent sign" do + @method.call("hello %1$", "foo").should == "hello %" + end + end + + ruby_version_is "3.4" do + it "raises ArgumentError if absolute argument specifier is followed by a conversion specifier" do + -> { @method.call("hello %1$", "foo") }.should.raise(ArgumentError, /malformed format string/) + end + end end end diff --git a/spec/ruby/core/kernel/sleep_spec.rb b/spec/ruby/core/kernel/sleep_spec.rb index 61d8cc23804ee0..1433d829e0b79c 100644 --- a/spec/ruby/core/kernel/sleep_spec.rb +++ b/spec/ruby/core/kernel/sleep_spec.rb @@ -78,7 +78,7 @@ def o.divmod(*); [0, 0.001]; end t.value.should == 5 end - context "Kernel.sleep with Fiber scheduler" do + context "Kernel#sleep with Fiber scheduler" do before :each do Fiber.set_scheduler(FiberSpecs::LoggingScheduler.new) end @@ -114,5 +114,7 @@ def o.divmod(*); [0, 0.001]; end end describe "Kernel.sleep" do - it "needs to be reviewed for spec completeness" + it "is a public method" do + Kernel.public_methods(false).should.include?(:sleep) + end end diff --git a/spec/ruby/core/kernel/spawn_spec.rb b/spec/ruby/core/kernel/spawn_spec.rb index 3432fc31da7b45..a87f7735014f01 100644 --- a/spec/ruby/core/kernel/spawn_spec.rb +++ b/spec/ruby/core/kernel/spawn_spec.rb @@ -17,9 +17,7 @@ end describe "Kernel.spawn" do - it "executes the given command" do - -> { - Process.wait Kernel.spawn("echo spawn") - }.should output_to_fd("spawn\n") + it "is a public method" do + Kernel.public_methods(false).should.include?(:spawn) end end diff --git a/spec/ruby/core/kernel/sprintf_spec.rb b/spec/ruby/core/kernel/sprintf_spec.rb index 5a4a90ff7a2860..8faa30a814e96e 100644 --- a/spec/ruby/core/kernel/sprintf_spec.rb +++ b/spec/ruby/core/kernel/sprintf_spec.rb @@ -3,14 +3,6 @@ require_relative 'shared/sprintf' require_relative 'shared/sprintf_encoding' -describe :kernel_sprintf_to_str, shared: true do - it "calls #to_str to convert the format object to a String" do - obj = mock('format string') - obj.should_receive(:to_str).and_return("to_str: %i") - @method.call(obj, 42).should == "to_str: 42" - end -end - describe "Kernel#sprintf" do it_behaves_like :kernel_sprintf, -> format, *args { r = nil @@ -28,37 +20,15 @@ r } - it_behaves_like :kernel_sprintf_to_str, -> format, *args { - r = nil - -> { - r = sprintf(format, *args) - }.should_not complain(verbose: true) - r - } + it "calls #to_str to convert the format object to a String" do + obj = mock('format string') + obj.should_receive(:to_str).and_return("to_str: %i") + @method.call(obj, 42).should == "to_str: 42" + end end describe "Kernel.sprintf" do - it_behaves_like :kernel_sprintf, -> format, *args { - r = nil - -> { - r = Kernel.sprintf(format, *args) - }.should_not complain(verbose: true) - r - } - - it_behaves_like :kernel_sprintf_encoding, -> format, *args { - r = nil - -> { - r = Kernel.sprintf(format, *args) - }.should_not complain(verbose: true) - r - } - - it_behaves_like :kernel_sprintf_to_str, -> format, *args { - r = nil - -> { - r = Kernel.sprintf(format, *args) - }.should_not complain(verbose: true) - r - } + it "is a public method" do + Kernel.public_methods(false).should.include?(:sprintf) + end end diff --git a/spec/ruby/core/kernel/srand_spec.rb b/spec/ruby/core/kernel/srand_spec.rb index cbc3a7f4b8048b..a74e64a65ad770 100644 --- a/spec/ruby/core/kernel/srand_spec.rb +++ b/spec/ruby/core/kernel/srand_spec.rb @@ -69,5 +69,7 @@ end describe "Kernel.srand" do - it "needs to be reviewed for spec completeness" + it "is a public method" do + Kernel.public_methods(false).should.include?(:srand) + end end diff --git a/spec/ruby/core/kernel/syscall_spec.rb b/spec/ruby/core/kernel/syscall_spec.rb index 63943cdad5d2b1..344e4b5b74482b 100644 --- a/spec/ruby/core/kernel/syscall_spec.rb +++ b/spec/ruby/core/kernel/syscall_spec.rb @@ -8,5 +8,7 @@ end describe "Kernel.syscall" do - it "needs to be reviewed for spec completeness" + it "is a public method" do + Kernel.public_methods(false).should.include?(:syscall) + end end diff --git a/spec/ruby/core/kernel/system_spec.rb b/spec/ruby/core/kernel/system_spec.rb index b24956104ab2c4..0f55fc938fd4d0 100644 --- a/spec/ruby/core/kernel/system_spec.rb +++ b/spec/ruby/core/kernel/system_spec.rb @@ -1,16 +1,20 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -describe :kernel_system, shared: true do +describe "Kernel#system" do + it "is a private method" do + Kernel.private_instance_methods(false).should.include?(:system) + end + it "executes the specified command in a subprocess" do - -> { @object.system("echo a") }.should output_to_fd("a\n") + -> { system("echo a") }.should output_to_fd("a\n") $?.should.instance_of? Process::Status $?.should.success? end it "returns true when the command exits with a zero exit status" do - @object.system(ruby_cmd('exit 0')).should == true + system(ruby_cmd('exit 0')).should == true $?.should.instance_of? Process::Status $?.should.success? @@ -18,7 +22,7 @@ end it "returns false when the command exits with a non-zero exit status" do - @object.system(ruby_cmd('exit 1')).should == false + system(ruby_cmd('exit 1')).should == false $?.should.instance_of? Process::Status $?.should_not.success? @@ -26,15 +30,15 @@ end it "raises RuntimeError when `exception: true` is given and the command exits with a non-zero exit status" do - -> { @object.system(ruby_cmd('exit 1'), exception: true) }.should.raise(RuntimeError) + -> { system(ruby_cmd('exit 1'), exception: true) }.should.raise(RuntimeError) end it "raises Errno::ENOENT when `exception: true` is given and the specified command does not exist" do - -> { @object.system('feature_14386', exception: true) }.should.raise(Errno::ENOENT) + -> { system('feature_14386', exception: true) }.should.raise(Errno::ENOENT) end it "returns nil when command execution fails" do - @object.system("sad").should == nil + system("sad").should == nil $?.should.instance_of? Process::Status $?.pid.should.is_a?(Integer) @@ -42,7 +46,7 @@ end it "does not write to stderr when command execution fails" do - -> { @object.system("sad") }.should output_to_fd("", STDERR) + -> { system("sad") }.should output_to_fd("", STDERR) end platform_is_not :windows do @@ -55,12 +59,12 @@ end it "executes with `sh` if the command contains shell characters" do - -> { @object.system("echo $0") }.should output_to_fd("sh\n") + -> { system("echo $0") }.should output_to_fd("sh\n") end it "ignores SHELL env var and always uses `sh`" do ENV['SHELL'] = "/bin/fakeshell" - -> { @object.system("echo $0") }.should output_to_fd("sh\n") + -> { system("echo $0") }.should output_to_fd("sh\n") end end @@ -77,7 +81,7 @@ end it "executes with `sh` if the command is executable but not binary and there is no shebang" do - -> { @object.system(@shell_command) }.should output_to_fd(ENV['PATH'] + "\n") + -> { system(@shell_command) }.should output_to_fd(ENV['PATH'] + "\n") end end @@ -94,39 +98,33 @@ end it "expands shell variables when given a single string argument" do - -> { @object.system("echo #{@shell_var}") }.should output_to_fd("foo\n") + -> { system("echo #{@shell_var}") }.should output_to_fd("foo\n") end platform_is_not :windows do it "does not expand shell variables when given multiples arguments" do - -> { @object.system("echo", @shell_var) }.should output_to_fd("#{@shell_var}\n") + -> { system("echo", @shell_var) }.should output_to_fd("#{@shell_var}\n") end end platform_is :windows do it "does expand shell variables when given multiples arguments" do # See https://bugs.ruby-lang.org/issues/12231 - -> { @object.system("echo", @shell_var) }.should output_to_fd("foo\n") + -> { system("echo", @shell_var) }.should output_to_fd("foo\n") end end platform_is :windows do it "runs commands starting with any number of @ using shell" do `#{ruby_cmd("p system 'does_not_exist'")} 2>NUL`.chomp.should == "nil" - @object.system('@does_not_exist 2>NUL').should == false - @object.system("@@@#{ruby_cmd('exit 0')}").should == true + system('@does_not_exist 2>NUL').should == false + system("@@@#{ruby_cmd('exit 0')}").should == true end end end -describe "Kernel#system" do - it "is a private method" do - Kernel.private_instance_methods(false).should.include?(:system) - end - - it_behaves_like :kernel_system, :system, KernelSpecs::Method.new -end - describe "Kernel.system" do - it_behaves_like :kernel_system, :system, Kernel + it "is a public method" do + Kernel.public_methods(false).should.include?(:system) + end end diff --git a/spec/ruby/core/kernel/test_spec.rb b/spec/ruby/core/kernel/test_spec.rb index 30717dfd98e352..d1e55920dd221c 100644 --- a/spec/ruby/core/kernel/test_spec.rb +++ b/spec/ruby/core/kernel/test_spec.rb @@ -12,15 +12,15 @@ end it "returns true when passed ?f if the argument is a regular file" do - Kernel.test(?f, @file).should == true + test(?f, @file).should == true end it "returns true when passed ?e if the argument is a file" do - Kernel.test(?e, @file).should == true + test(?e, @file).should == true end it "returns true when passed ?d if the argument is a directory" do - Kernel.test(?d, @dir).should == true + test(?d, @dir).should == true end platform_is_not :windows do @@ -28,7 +28,7 @@ link = tmp("file_symlink.lnk") File.symlink(@file, link) begin - Kernel.test(?l, link).should == true + test(?l, link).should == true ensure rm_r link end @@ -36,11 +36,11 @@ end it "returns true when passed ?r if the argument is readable by the effective uid" do - Kernel.test(?r, @file).should == true + test(?r, @file).should == true end it "returns true when passed ?R if the argument is readable by the real uid" do - Kernel.test(?R, @file).should == true + test(?R, @file).should == true end context "writable test" do @@ -54,11 +54,11 @@ end it "returns true when passed ?w if the argument is readable by the effective uid" do - Kernel.test(?w, @tmp_file).should == true + test(?w, @tmp_file).should == true end it "returns true when passed ?W if the argument is readable by the real uid" do - Kernel.test(?W, @tmp_file).should == true + test(?W, @tmp_file).should == true end end @@ -73,37 +73,39 @@ end it "returns the last access time for the provided file when passed ?A" do - Kernel.test(?A, @tmp_file).should == @tmp_file.atime + test(?A, @tmp_file).should == @tmp_file.atime end it "returns the time at which the file was created when passed ?C" do - Kernel.test(?C, @tmp_file).should == @tmp_file.ctime + test(?C, @tmp_file).should == @tmp_file.ctime end it "returns the time at which the file was modified when passed ?M" do - Kernel.test(?M, @tmp_file).should == @tmp_file.mtime + test(?M, @tmp_file).should == @tmp_file.mtime end end it "calls #to_path on second argument when passed ?f and a filename" do p = mock('path') p.should_receive(:to_path).and_return @file - Kernel.test(?f, p) + test(?f, p) end it "calls #to_path on second argument when passed ?e and a filename" do p = mock('path') p.should_receive(:to_path).and_return @file - Kernel.test(?e, p) + test(?e, p) end it "calls #to_path on second argument when passed ?d and a directory" do p = mock('path') p.should_receive(:to_path).and_return @dir - Kernel.test(?d, p) + test(?d, p) end end describe "Kernel.test" do - it "needs to be reviewed for spec completeness" + it "is a public method" do + Kernel.public_methods(false).should.include?(:test) + end end diff --git a/spec/ruby/core/kernel/throw_spec.rb b/spec/ruby/core/kernel/throw_spec.rb index 1086126176fe93..5c14c6591e05b5 100644 --- a/spec/ruby/core/kernel/throw_spec.rb +++ b/spec/ruby/core/kernel/throw_spec.rb @@ -1,7 +1,11 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -describe "Kernel.throw" do +describe "Kernel#throw" do + it "is a private method" do + Kernel.private_instance_methods(false).should.include?(:throw) + end + it "transfers control to the end of the active catch block waiting for symbol" do catch(:blah) do :value @@ -73,8 +77,8 @@ end end -describe "Kernel#throw" do - it "is a private method" do - Kernel.private_instance_methods(false).should.include?(:throw) +describe "Kernel.throw" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:throw) end end diff --git a/spec/ruby/core/kernel/trace_var_spec.rb b/spec/ruby/core/kernel/trace_var_spec.rb index 150c3da266106c..3396409b74062e 100644 --- a/spec/ruby/core/kernel/trace_var_spec.rb +++ b/spec/ruby/core/kernel/trace_var_spec.rb @@ -52,3 +52,9 @@ end.should.raise(ArgumentError) end end + +describe "Kernel.trace_var" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:trace_var) + end +end diff --git a/spec/ruby/core/kernel/trap_spec.rb b/spec/ruby/core/kernel/trap_spec.rb index 8f44f3af4b6b35..42277b6e20b183 100644 --- a/spec/ruby/core/kernel/trap_spec.rb +++ b/spec/ruby/core/kernel/trap_spec.rb @@ -7,3 +7,9 @@ # Behaviour is specified for Signal.trap end + +describe "Kernel.trap" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:trap) + end +end diff --git a/spec/ruby/core/kernel/untrace_var_spec.rb b/spec/ruby/core/kernel/untrace_var_spec.rb index 8b219801c84933..26aa166bfb33d5 100644 --- a/spec/ruby/core/kernel/untrace_var_spec.rb +++ b/spec/ruby/core/kernel/untrace_var_spec.rb @@ -8,5 +8,7 @@ end describe "Kernel.untrace_var" do - it "needs to be reviewed for spec completeness" + it "is a public method" do + Kernel.public_methods(false).should.include?(:untrace_var) + end end diff --git a/spec/ruby/core/kernel/warn_spec.rb b/spec/ruby/core/kernel/warn_spec.rb index 189129dd313778..5bb0fd3813c1d6 100644 --- a/spec/ruby/core/kernel/warn_spec.rb +++ b/spec/ruby/core/kernel/warn_spec.rb @@ -228,10 +228,10 @@ def warn(message) begin ScratchPad.clear - Kernel.warn("Chunky bacon!") + warn("Chunky bacon!") ScratchPad.recorded.should == "Chunky bacon!\n" - Kernel.warn("Deprecated bacon!", category: :deprecated) + warn("Deprecated bacon!", category: :deprecated) ScratchPad.recorded.should == "Deprecated bacon!\n" ensure class << Warning @@ -248,7 +248,7 @@ class << Warning verbose = $VERBOSE $VERBOSE = false begin - Kernel.warn("Chunky bacon!") + warn("Chunky bacon!") ensure $VERBOSE = verbose end @@ -259,7 +259,7 @@ class << Warning verbose = $VERBOSE $VERBOSE = false begin - Kernel.warn("Chunky bacon!", category: 'deprecated') + warn("Chunky bacon!", category: 'deprecated') ensure $VERBOSE = verbose end @@ -296,3 +296,9 @@ def warn(*args, **kwargs) $?.should.success? end end + +describe "Kernel.warn" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:warn) + end +end diff --git a/spec/ruby/core/proc/parameters_spec.rb b/spec/ruby/core/proc/parameters_spec.rb index ac8c6e35601da4..31cbe2fe217459 100644 --- a/spec/ruby/core/proc/parameters_spec.rb +++ b/spec/ruby/core/proc/parameters_spec.rb @@ -180,4 +180,20 @@ RUBY end end + + ruby_version_is ""..."4.0" do + it "regards a destructured parameter in proc as an optional one" do + proc { |(a)| }.parameters.should == [[:opt, nil]] + end + end + + ruby_version_is "4.0" do + it "regards a destructured parameter in proc as an optional one" do + proc { |(a)| }.parameters.should == [[:opt]] + end + end + + it "regards a destructured parameter in lambda as an required" do + -> ((a)) { }.parameters.should == [[:req]] # rubocop:disable Style/StabbyLambdaParentheses + end end diff --git a/spec/ruby/core/process/spawn_spec.rb b/spec/ruby/core/process/spawn_spec.rb index fb619dce4278d5..8f005f8dee1f78 100644 --- a/spec/ruby/core/process/spawn_spec.rb +++ b/spec/ruby/core/process/spawn_spec.rb @@ -413,6 +413,12 @@ Process.wait Process.spawn(ruby_cmd("print Dir.pwd"), chdir: dir) end.should output_to_fd(@dir) end + + it "raises an Errno::ENOENT when a given path doesn't exist" do + -> do + Process.wait Process.spawn(ruby_cmd("print Dir.pwd"), chdir: "nonexistent") + end.should.raise(Errno::ENOENT, "No such file or directory - nonexistent") + end end # chdir diff --git a/spec/ruby/core/range/fixtures/classes.rb b/spec/ruby/core/range/fixtures/classes.rb index 3a1df010b2abfe..bdabfb0d1c8176 100644 --- a/spec/ruby/core/range/fixtures/classes.rb +++ b/spec/ruby/core/range/fixtures/classes.rb @@ -58,10 +58,81 @@ def inspect end def <=>(other) + return nil unless other.is_a?(WithoutSucc) @n <=> other.n end end + class WithSucc + attr_reader :value + + def initialize(value) + @value = value + end + + def <=>(other) + return nil unless other.is_a?(WithSucc) + @value <=> other.value + end + + def succ + WithSucc.new(@value + 1) + end + + def ==(other) + return false unless other.is_a?(WithSucc) + @value == other.value + end + end + + class Number < Numeric + attr_reader :value + + def initialize(value) + @value = value + end + + def <=>(other) + return nil unless other.is_a?(Number) + @value <=> other.value + end + + def +(other) + raise "supported Integer only" unless other.is_a?(Integer) + Number.new(@value + other) + end + + def ==(other) + return false unless other.is_a?(Number) + @value == other.value + end + + # to prevent type conversion + undef_method :coerce + end + + # supports only interface required by Range#cover? + #succ + class CoverElementWithSucc + attr_reader :n + + def initialize(n) + @n = n + end + + def <=>(other) + return nil unless other.is_a?(CoverElementWithSucc) + @n <=> other.n + end + + def succ + CoverElementWithSucc.new(@n + 1) + end + + def inspect + "CoverElementWithSucc(#{@n})" + end + end + class Xs < Custom # represent a string of 'x's def succ Xs.new(@length + 1) diff --git a/spec/ruby/core/range/include_spec.rb b/spec/ruby/core/range/include_spec.rb index e5cc0dc234f395..6e21eb76507778 100644 --- a/spec/ruby/core/range/include_spec.rb +++ b/spec/ruby/core/range/include_spec.rb @@ -16,6 +16,76 @@ ('a'..'c').include?('bc').should == false ('a'...'c').include?('bc').should == false end + + it "returns whether object is an element of self using #== to compare" do + range = 'a'..'ab' + range.include?('b').should == true + range.include?('aa').should == true + range.include?('ac').should == false + end + + it "ignores self.end if excluded end" do + range = 'a'...'aa' + range.include?('aa').should == false + end + + it "returns false if backward range" do + range = 'aa'..'a' + range.include?('b').should == false + end + + it "returns false if empty range" do + range = 'aa'...'aa' + range.include?('aa').should == false + end + + it "raises TypeError for beginningless ranges" do + -> { (..'aa').include?('a') }.should.raise(TypeError) + -> { (..'aa').include?(Object.new) }.should.raise(TypeError) + end + + it "raises TypeError for endless ranges" do + -> { + ('aa'..).include?(Object.new) + }.should.raise(TypeError) + -> { + ('aa'..).include?('a') + }.should.raise(TypeError) + end + + it "returns false if an argument isn't comparable with range boundaries" do + range = 'a'..'aa' + range.include?(Object.new).should == false + end + + it "returns false if an argument is empty" do + range = 'a'..'aa' + range.include?('').should == false + end + + describe "argument conversion to String" do + it "converts the passed argument to a String using #to_str" do + range = 'a'..'aa' + object = Object.new + def object.to_str; 'b'; end + range.include?(object).should == true + end + + it "returns false if the passed argument does not respond to #to_str" do + range = 'a'..'aa' + range.include?(nil).should == false + range.include?([]).should == false + end + + it "raises a TypeError if the passed argument responds to #to_str but it returns non-String value" do + range = 'a'..'aa' + object = Object.new + def object.to_str; 1; end + -> { + range.include?(object) + }.should.raise(TypeError) + end + end end describe "with weird succ" do @@ -95,4 +165,228 @@ it "does not include U+9995 in the range U+0999..U+9999" do ("\u{999}".."\u{9999}").include?("\u{9995}").should == false end + + describe "with custom Comparable objects (with #succ)" do + it "returns whether object is an element of self using #== to compare" do + range = RangeSpecs::WithSucc.new(1)..RangeSpecs::WithSucc.new(4) + range.include?(RangeSpecs::WithSucc.new(2)).should == true + range.include?(RangeSpecs::WithSucc.new(5)).should == false + end + + it "ignores self.end if excluded end" do + range = RangeSpecs::WithSucc.new(1)...RangeSpecs::WithSucc.new(4) + range.include?(RangeSpecs::WithSucc.new(4)).should == false + end + + it "returns false if backward range" do + range = RangeSpecs::WithSucc.new(4)..RangeSpecs::WithSucc.new(1) + range.include?(RangeSpecs::WithSucc.new(2)).should == false + end + + it "returns false if empty range" do + range = RangeSpecs::WithSucc.new(1)...RangeSpecs::WithSucc.new(1) + range.include?(RangeSpecs::WithSucc.new(1)).should == false + end + + it "returns false if an argument isn't comparable with range boundaries" do + range = RangeSpecs::WithSucc.new(0)..RangeSpecs::WithSucc.new(6) + range.include?(Object.new).should == false + end + + it "raises TypeError for beginningless ranges" do + -> { + (..RangeSpecs::WithSucc.new(10)).include?(RangeSpecs::WithSucc.new(5)) + }.should.raise(TypeError) + end + + it "raises TypeError for endless ranges" do + -> { + (RangeSpecs::WithSucc.new(0)..).include?(RangeSpecs::WithSucc.new(5)) + }.should.raise(TypeError) + end + end + + describe "with custom Comparable objects (without #succ)" do + it "raises TypeError for beginningless ranges" do + -> { + (..RangeSpecs::WithoutSucc.new(10)).include?(RangeSpecs::WithoutSucc.new(5)) + }.should.raise(TypeError) + + -> { + (..RangeSpecs::WithoutSucc.new(10)).include?(Object.new) + }.should.raise(TypeError) + end + + it "raises TypeError for endless ranges" do + -> { + (RangeSpecs::WithoutSucc.new(0)..).include?(RangeSpecs::WithoutSucc.new(5)) + }.should.raise(TypeError) + + -> { + (RangeSpecs::WithoutSucc.new(0)..).include?(Object.new) + }.should.raise(TypeError) + end + + it "raises TypeError for (nil..nil)" do + -> { + (nil..nil).include?(Object.new) + }.should.raise(TypeError) + end + end + + describe "with Numeric subclass elements" do + it "returns true if object is between self.begin and self.end" do + range = RangeSpecs::Number.new(0)..RangeSpecs::Number.new(6) + range.include?(RangeSpecs::Number.new(5)).should == true + end + + it "returns false if object is smaller than self.begin" do + range = RangeSpecs::Number.new(0)..RangeSpecs::Number.new(6) + range.include?(RangeSpecs::Number.new(-5)).should == false + end + + it "returns false if object is greater than self.end" do + range = RangeSpecs::Number.new(0)..RangeSpecs::Number.new(6) + range.include?(RangeSpecs::Number.new(10)).should == false + end + + it "ignores end if excluded end" do + range = RangeSpecs::Number.new(0)...RangeSpecs::Number.new(6) + range.include?(RangeSpecs::Number.new(6)).should == false + end + + it "returns true if argument is a single element in the range" do + range = RangeSpecs::Number.new(0)..RangeSpecs::Number.new(0) + range.include?(RangeSpecs::Number.new(0)).should == true + end + + it "returns false if range is empty" do + range = RangeSpecs::Number.new(0)...RangeSpecs::Number.new(0) + range.include?(RangeSpecs::Number.new(0)).should == false + end + + it "returns false if an argument isn't comparable with range boundaries" do + range = RangeSpecs::Number.new(0)..RangeSpecs::Number.new(6) + range.include?(Object.new).should == false + end + + describe "beginningless range" do + it "returns false if object is greater than self.end" do + range = ..RangeSpecs::Number.new(6) + range.include?(RangeSpecs::Number.new(10)).should == false + end + + it "returns true if object is smaller than self.end" do + range = ..RangeSpecs::Number.new(6) + range.include?(RangeSpecs::Number.new(0)).should == true + end + end + + describe "endless range" do + it "returns true if object is greater than self.begin" do + range = (RangeSpecs::Number.new(0)..) + range.include?(RangeSpecs::Number.new(10)).should == true + end + + it "returns false if object is smaller than self.begin" do + range = (RangeSpecs::Number.new(0)..) + range.include?(RangeSpecs::Number.new(-10)).should == false + end + end + + it "returns false if object isn't comparable with self.begin and self.end (that's #<=> returns nil)" do + range = RangeSpecs::Number.new(0)..RangeSpecs::Number.new(6) + object = Object.new + def object.<=>(other); nil; end + range.include?(object).should == false + end + end + + describe "with Time elements" do + it "returns true if object is between self.begin and self.end" do + range = Time.at(1_700_000_000)..Time.at(1_700_000_000 + 6) + range.include?(Time.at(1_700_000_000 + 5)).should == true + end + + it "returns false if object is smaller than self.begin" do + range = Time.at(1_700_000_000)..Time.at(1_700_000_000 + 6) + range.include?(Time.at(1_700_000_000 - 5)).should == false + end + + it "returns false if object is greater than self.end" do + range = Time.at(1_700_000_000)..Time.at(1_700_000_000 + 6) + range.include?(Time.at(1_700_000_000 + 10)).should == false + end + + it "ignores end if excluded end" do + range = Time.at(1_700_000_000)...Time.at(1_700_000_000 + 6) + range.include?(Time.at(1_700_000_000 + 6)).should == false + end + + it "returns true if argument is a single element in the range" do + range = Time.at(1_700_000_000)..Time.at(1_700_000_000) + range.include?(Time.at(1_700_000_000)).should == true + end + + it "returns false if range is empty" do + range = Time.at(1_700_000_000)...Time.at(1_700_000_000) + range.include?(Time.at(1_700_000_000)).should == false + end + + it "returns false if an argument isn't comparable with range boundaries" do + range = Time.at(1_700_000_000)..Time.at(1_700_000_000 + 6) + range.include?(Object.new).should == false + end + + describe "beginningless range" do + it "returns false if object is greater than self.end" do + range = ..Time.at(1_700_000_000 + 6) + range.include?(Time.at(1_700_000_000 + 10)).should == false + end + + it "returns true if object is smaller than self.end" do + range = ..Time.at(1_700_000_000 + 6) + range.include?(Time.at(1_700_000_000)).should == true + end + end + + describe "endless range" do + it "returns true if object is greater than self.begin" do + range = (Time.at(1_700_000_000)..) + range.include?(Time.at(1_700_000_000 + 10)).should == true + end + + it "returns false if object is smaller than self.begin" do + range = (Time.at(1_700_000_000 + 10)..) + range.include?(Time.at(1_700_000_000)).should == false + end + end + + it "returns false if object isn't comparable with self.begin and self.end (that's #<=> returns nil)" do + range = Time.at(1_700_000_000)..Time.at(1_700_000_000 + 6) + object = Object.new + def object.<=>(other); nil; end + range.include?(object).should == false + end + end + + describe 'with beginless and endless range' do + it "return true for any Numeric value" do + (nil..nil).include?(1).should == true + (nil..nil).include?(1.0).should == true + (nil..nil).include?(1r).should == true + (nil..nil).include?(Complex(1)).should == true + (nil..nil).include?(RangeSpecs::Number.new(1)).should == true + end + + it "return true for any Time value" do + (nil..nil).include?(Time.now).should == true + end + + it "raises TypeError for non-Numeric/Time values" do + -> { (nil..nil).include?("a") }.should.raise(TypeError) + -> { (nil..nil).include?(:a) }.should.raise(TypeError) + -> { (nil..nil).include?([]) }.should.raise(TypeError) + end + end end diff --git a/spec/ruby/core/range/max_spec.rb b/spec/ruby/core/range/max_spec.rb index 57714967ce0afe..93630c3d926a93 100644 --- a/spec/ruby/core/range/max_spec.rb +++ b/spec/ruby/core/range/max_spec.rb @@ -1,7 +1,7 @@ require_relative '../../spec_helper' describe "Range#max" do - it "returns the maximum value in the range when called with no arguments" do + it "returns the maximum value in the range" do (1..10).max.should == 10 (1...10).max.should == 9 (0...2**64).max.should == 18446744073709551615 @@ -9,7 +9,7 @@ ('a'...'f').max.should == 'e' end - it "returns the maximum value in the Float range when called with no arguments" do + it "returns the maximum value in the Float range" do (303.20..908.1111).max.should == 908.1111 end @@ -47,7 +47,7 @@ end it "raises RangeError when called on an endless range" do - -> { eval("(1..)").max }.should.raise(RangeError) + -> { (1..).max }.should.raise(RangeError) end it "returns the end point for beginless ranges" do @@ -76,6 +76,97 @@ end end +describe "Range#max given an integer argument" do + it "returns the n maximum values in the range" do + (1..10).max(2).should == [10, 9] + (1...10).max(2).should == [9, 8] + ('f'..'l').max(2).should == ['l', 'k'] + ('a'...'f').max(2).should == ['e', 'd'] + end + + ruby_version_is "4.0" do + it "returns the n maximum values in a very large Integer range" do + (0...2**64).max(2).should == [18446744073709551615, 18446744073709551615-1] + end + end + + it "raise a TypeError for non Integer ranges" do + -> { (303.20..908.1111).max(2) }.should.raise(TypeError) + -> { (303.20...908.1111).max(2) }.should.raise(TypeError) + + -> { (['a']..['f']).max(2) }.should.raise(TypeError) + -> { (['a']...['f']).max(2) }.should.raise(TypeError) + + -> { (Time.now..Time.now).max(2) }.should.raise(TypeError) + -> { (Time.now...Time.now).max(2) }.should.raise(TypeError) + end + + it "returns [] when the endpoint is less than the start point" do + (100..10).max(2).should == [] + ('z'..'l').max(2).should == [] + end + + it "returns [] when the endpoint equals the start point and the Integer range is exclusive" do + (5...5).max(2).should == [] + end + + it "returns the endpoint when the endpoint equals the start point and the Integer range is inclusive" do + (5..5).max(2).should == [5] + end + + it "returns all the elements in the range when given n larger that elements count" do + (1..3).max(10).should == [3, 2, 1] + (1...3).max(10).should == [2, 1] + end + + it "raises RangeError when called on an endless range" do + -> { (1..).max(2) }.should.raise(RangeError) + end + + ruby_version_is "4.0" do + it "returns the n maximum values for beginless Integer ranges" do + (..1).max(2).should == [1, 0] + end + + it "returns the n maximum values (except the end point) for exclusive beginless Integer ranges" do + (...1).max(2).should == [0, -1] + end + end + + ruby_version_is ""..."4.0" do + it "raises a RangeError for an beginless non-Integer range" do + -> { (..1.0).max(2) }.should.raise(RangeError) + -> { (..'f').max(2) }.should.raise(RangeError) + end + end + + ruby_version_is "4.0" do + it "raises a TypeError for an beginless non-Integer range" do + -> { (..1.0).max(2) }.should.raise(TypeError) + -> { (..'f').max(2) }.should.raise(TypeError) + end + end + + it "raises an ArgumentError when given negative value" do + -> { (0..2).max(-1) }.should.raise(ArgumentError) + end + + it "converts the passed argument to an Integer using #to_int'" do + obj = mock_int(2) + (1..10).max(obj).should == [10, 9] + end + + it "raises a TypeError if the passed argument does not respond to #to_int" do + -> { (0..2).max(Object.new) }.should.raise(TypeError) + end + + it "raises a TypeError if #to_int does not return an Integer" do + obj = mock("to_int") + obj.should_receive(:to_int).and_return("1") + -> { (0..2).max(obj) }.should.raise(TypeError) + end +end + describe "Range#max given a block" do it "passes each pair of values in the range to the block" do acc = [] @@ -113,3 +204,9 @@ -> { (..1).max {|a, b| a} }.should.raise(RangeError) end end + +describe "Range#max given a block and an integer argument" do + it "returns n elements the block determines to be the maximum" do + (1..10).max(2) {|a,b| a <=> b }.should == [10, 9] + end +end diff --git a/spec/ruby/core/range/min_spec.rb b/spec/ruby/core/range/min_spec.rb index 9c83d3decaec71..b39bfa5e90fec7 100644 --- a/spec/ruby/core/range/min_spec.rb +++ b/spec/ruby/core/range/min_spec.rb @@ -1,12 +1,12 @@ require_relative '../../spec_helper' describe "Range#min" do - it "returns the minimum value in the range when called with no arguments" do + it "returns the minimum value in the range" do (1..10).min.should == 1 ('f'..'l').min.should == 'f' end - it "returns the minimum value in the Float range when called with no arguments" do + it "returns the minimum value in the Float range" do (303.20..908.1111).min.should == 303.20 end @@ -40,8 +40,8 @@ end it "returns the start point for endless ranges" do - eval("(1..)").min.should == 1 - eval("(1.0...)").min.should == 1.0 + (1..).min.should == 1 + (1.0...).min.should == 1.0 end it "raises RangeError when called on an beginless range" do @@ -49,6 +49,78 @@ end end +describe "Range#min given an integer argument" do + it "returns the n minimum values in the range" do + (1..10).min(2).should == [1, 2] + (1...10).min(2).should == [1, 2] + (0...2**64).min(2).should == [0, 1] + ('f'..'l').min(2).should == ['f', 'g'] + ('a'...'f').min(2).should == ['a', 'b'] + end + + it "raise a TypeError for non Integer ranges" do + -> { (303.20..908.1111).min(2) }.should.raise(TypeError) + -> { (303.20...908.1111).min(2) }.should.raise(TypeError) + + -> { (['a']..['f']).min(2) }.should.raise(TypeError) + -> { (['a']...['f']).min(2) }.should.raise(TypeError) + + -> { (Time.now..Time.now).min(2) }.should.raise(TypeError) + -> { (Time.now...Time.now).min(2) }.should.raise(TypeError) + end + + it "returns [] when the endpoint is less than the start point" do + (100..10).min(2).should == [] + ('z'..'l').min(2).should == [] + end + + it "returns [] when the endpoint equals the start point and the Integer range is exclusive" do + (5...5).min(2).should == [] + end + + it "returns the endpoint when the endpoint equals the start point and the Integer range is inclusive" do + (5..5).min(2).should == [5] + end + + it "returns all the elements in the range when given n larger that elements count" do + (1..3).min(10).should == [1, 2, 3] + (1...3).min(10).should == [1, 2] + end + + it "raises RangeError when called on a beginless range" do + -> { (..1).min(2) }.should.raise(RangeError) + -> { (...1).min(2) }.should.raise(RangeError) + end + + it "returns the n minimum values for endless Integer ranges" do + (1..).min(2).should == [1, 2] + (1...).min(2).should == [1, 2] + end + + it "raises a TypeError for an endless non-Integer range" do + -> { (1.0..).min(2) }.should.raise(TypeError) + end + + it "raises an ArgumentError when given negative value" do + -> { (0..2).min(-1) }.should.raise(ArgumentError) + end + + it "converts the passed argument to an Integer using #to_int" do + obj = mock_int(2) + (1..10).min(obj).should == [1, 2] + end + + it "raises a TypeError if the passed argument does not respond to #to_int" do + -> { (0..2).min(Object.new) }.should.raise(TypeError) + end + + it "raises a TypeError if #to_int does not return an Integer" do + obj = mock("to_int") + obj.should_receive(:to_int).and_return("1") + -> { (0..2).min(obj) }.should.raise(TypeError) + end +end + describe "Range#min given a block" do it "passes each pair of values in the range to the block" do acc = [] @@ -83,6 +155,12 @@ end it "raises RangeError when called with custom comparison method on an endless range" do - -> { eval("(1..)").min {|a, b| a} }.should.raise(RangeError) + -> { (1..).min {|a, b| a} }.should.raise(RangeError) + end +end + +describe "Range#max given a block and an integer argument" do + it "returns n elements the block determines to be the minimum" do + (1..10).min(2) { |a,b| a <=> b }.should == [1, 2] end end diff --git a/spec/ruby/core/range/overlap_spec.rb b/spec/ruby/core/range/overlap_spec.rb index 201cd2b1ff7e0f..c86936597fa1fd 100644 --- a/spec/ruby/core/range/overlap_spec.rb +++ b/spec/ruby/core/range/overlap_spec.rb @@ -9,6 +9,9 @@ (1..2).overlap?(0..3).should == true ('a'..'c').overlap?('b'..'d').should == true + + (0..4).overlap?(4..10).should == true + (4..10).overlap?(0..10).should == true end it "returns false if other Range does not overlap self" do @@ -43,6 +46,20 @@ (0..).overlap?(2..).should == true (..0).overlap?(..2).should == true + + (nil..nil).overlap?(4..6).should == true + (4..6).overlap?(nil..nil).should == true + + ruby_version_is "4.0" do + (nil..nil).overlap?(..6).should == true + (..6).overlap?(nil..nil).should == true + end + + (nil..nil).overlap?(4..).should == true + (4..).overlap?(nil..nil).should == true + + (..10).overlap?(10..).should == true + (10..).overlap?(..10).should == true end it "returns false when beginningless and endless Ranges do not overlap" do @@ -51,6 +68,8 @@ (..-1).overlap?(0..2).should == false (3..).overlap?(0..2).should == false + + (..0).overlap?(10..).should == false end it "returns false when Ranges are not compatible" do @@ -83,5 +102,14 @@ ('a'...'c').overlap?('c'..'e').should == false ('c'..'e').overlap?('a'...'c').should == false + + (0...10).overlap?(6..10).should == true + (nil...6).overlap?(4..6).should == true + (nil...10).overlap?(nil..10).should == true + + (nil...4).overlap?(4..10).should == false + (nil...10).overlap?(10..).should == false + (10..).overlap?(nil...10).should == false + (6..).overlap?(0...6).should == false end end diff --git a/spec/ruby/core/range/shared/cover.rb b/spec/ruby/core/range/shared/cover.rb index 189f3da4bf5185..1a5b197597b122 100644 --- a/spec/ruby/core/range/shared/cover.rb +++ b/spec/ruby/core/range/shared/cover.rb @@ -90,104 +90,390 @@ end end end -end -describe :range_cover_subrange, shared: true do - context "range argument" do - it "accepts range argument" do - (0..10).send(@method, (3..7)).should == true - (0..10).send(@method, (3..15)).should == false - (0..10).send(@method, (-2..7)).should == false + describe "with non-Range argument" do + it "returns false if the object is smaller than self.begin" do + (0..6).send(@method, -5).should == false + end - (1.1..7.9).send(@method, (2.5..6.5)).should == true - (1.1..7.9).send(@method, (2.5..8.5)).should == false - (1.1..7.9).send(@method, (0.5..6.5)).should == false + it "returns false if the object is greater than self.end" do + (0..6).send(@method, 10).should == false + end - ('c'..'i').send(@method, ('d'..'f')).should == true - ('c'..'i').send(@method, ('d'..'z')).should == false - ('c'..'i').send(@method, ('a'..'f')).should == false + it "returns false if the range is empty" do + (0...0).send(@method, 0).should == false + end - range_10_100 = RangeSpecs::TenfoldSucc.new(10)..RangeSpecs::TenfoldSucc.new(100) - range_20_90 = RangeSpecs::TenfoldSucc.new(20)..RangeSpecs::TenfoldSucc.new(90) - range_20_110 = RangeSpecs::TenfoldSucc.new(20)..RangeSpecs::TenfoldSucc.new(110) - range_0_90 = RangeSpecs::TenfoldSucc.new(0)..RangeSpecs::TenfoldSucc.new(90) + it "returns false if the argument is not comparable with the range elements (returns nil from <=>)" do + (0..6).send(@method, Object.new).should == false + end - range_10_100.send(@method, range_20_90).should == true - range_10_100.send(@method, range_20_110).should == false - range_10_100.send(@method, range_0_90).should == false + it "returns false if the argument is greater than self.end on a beginless range" do + (...6).send(@method, 10).should == false end - it "supports boundaries of different comparable types" do - (0..10).send(@method, (3.1..7.9)).should == true - (0..10).send(@method, (3.1..15.9)).should == false - (0..10).send(@method, (-2.1..7.9)).should == false + it "returns true if the argument is smaller than self.end on a beginless range" do + (...6).send(@method, 0).should == true end - it "returns false if types are not comparable" do - (0..10).send(@method, ('a'..'z')).should == false - (0..10).send(@method, (RangeSpecs::TenfoldSucc.new(0)..RangeSpecs::TenfoldSucc.new(100))).should == false + it "returns true if the argument is greater than self.begin on an endless range" do + (0..).send(@method, 10).should == true end - it "honors exclusion of right boundary (:exclude_end option)" do - # Integer - (0..10).send(@method, (0..10)).should == true - (0...10).send(@method, (0...10)).should == true + it "returns false if the argument is smaller than self.begin on an endless range" do + (0..).send(@method, -10).should == false + end - (0..10).send(@method, (0...10)).should == true - (0...10).send(@method, (0..10)).should == false + it "returns true on any value for (nil..nil)" do + (nil..nil).send(@method, Object.new).should == true + end - (0...11).send(@method, (0..10)).should == true - (0..10).send(@method, (0...11)).should == true + it "returns true on any value for (nil...nil)" do + (nil...nil).send(@method, Object.new).should == true + end + end +end - # Float - (0..10.1).send(@method, (0..10.1)).should == true - (0...10.1).send(@method, (0...10.1)).should == true +describe :range_cover_subrange, shared: true do + it "accepts range argument" do + (0..10).send(@method, (3..7)).should == true + (0..10).send(@method, (3..15)).should == false + (0..10).send(@method, (-2..7)).should == false + + (1.1..7.9).send(@method, (2.5..6.5)).should == true + (1.1..7.9).send(@method, (2.5..8.5)).should == false + (1.1..7.9).send(@method, (0.5..6.5)).should == false + + ('c'..'i').send(@method, ('d'..'f')).should == true + ('c'..'i').send(@method, ('d'..'z')).should == false + ('c'..'i').send(@method, ('a'..'f')).should == false + + range_10_100 = RangeSpecs::TenfoldSucc.new(10)..RangeSpecs::TenfoldSucc.new(100) + range_20_90 = RangeSpecs::TenfoldSucc.new(20)..RangeSpecs::TenfoldSucc.new(90) + range_20_110 = RangeSpecs::TenfoldSucc.new(20)..RangeSpecs::TenfoldSucc.new(110) + range_0_90 = RangeSpecs::TenfoldSucc.new(0)..RangeSpecs::TenfoldSucc.new(90) + + range_10_100.send(@method, range_20_90).should == true + range_10_100.send(@method, range_20_110).should == false + range_10_100.send(@method, range_0_90).should == false + end - (0..10.1).send(@method, (0...10.1)).should == true - (0...10.1).send(@method, (0..10.1)).should == false + it "supports boundaries of different comparable types" do + (0..10).send(@method, (3.1..7.9)).should == true + (0..10).send(@method, (3.1..15.9)).should == false + (0..10).send(@method, (-2.1..7.9)).should == false + end - (0...11.1).send(@method, (0..10.1)).should == true - (0..10.1).send(@method, (0...11.1)).should == false - end + it "returns false if types are not comparable" do + (0..10).send(@method, ('a'..'z')).should == false + (0..10).send(@method, (RangeSpecs::TenfoldSucc.new(0)..RangeSpecs::TenfoldSucc.new(100))).should == false end - it "allows self to be a beginless range" do - (...10).send(@method, (3..7)).should == true - (...10).send(@method, (3..15)).should == false + it "honors exclusion of right boundary (:exclude_end option)" do + # Integer + (0..10).send(@method, (0..10)).should == true + (0...10).send(@method, (0...10)).should == true - (..7.9).send(@method, (2.5..6.5)).should == true - (..7.9).send(@method, (2.5..8.5)).should == false + (0..10).send(@method, (0...10)).should == true + (0...10).send(@method, (0..10)).should == false - (..'i').send(@method, ('d'..'f')).should == true - (..'i').send(@method, ('d'..'z')).should == false - end + (0...11).send(@method, (0..10)).should == true + (0..10).send(@method, (0...11)).should == true - it "allows self to be a endless range" do - eval("(0...)").send(@method, (3..7)).should == true - eval("(5...)").send(@method, (3..15)).should == false + # Float + (0..10.1).send(@method, (0..10.1)).should == true + (0...10.1).send(@method, (0...10.1)).should == true - eval("(1.1..)").send(@method, (2.5..6.5)).should == true - eval("(3.3..)").send(@method, (2.5..8.5)).should == false + (0..10.1).send(@method, (0...10.1)).should == true + (0...10.1).send(@method, (0..10.1)).should == false - eval("('a'..)").send(@method, ('d'..'f')).should == true - eval("('p'..)").send(@method, ('d'..'z')).should == false + (0...11.1).send(@method, (0..10.1)).should == true + (0..10.1).send(@method, (0...11.1)).should == false end - it "accepts beginless range argument" do - (..10).send(@method, (...10)).should == true - (0..10).send(@method, (...10)).should == false + context "range argument with Integer boundaries" do + context "when other range is completely inside self" do + it "returns true if self.begin < other.begin and self.end > other.end" do + (0..10).send(@method, 4..6).should == true + end + + it "returns true if self.begin == other.begin and self.end > other.end" do + (0..10).send(@method, 0..6).should == true + end + + it "returns true if self.begin < other.begin and self.end == other.end" do + (0..10).send(@method, 4..10).should == true + end + + it "returns true if self.begin < other.begin and self.end < other.end but other.exclude_end? is true and the rightmost other value <= self.end" do + (0..10).send(@method, 4...11).should == true + end + + it "returns true if self.begin == other.begin and self.end == other.end" do + (0..10).send(@method, 0..10).should == true + end + + it "returns true if self.begin == other.begin and self.end == other.end and self.exclude_end? is true and other.exclude_end? is true" do + (0...10).send(@method, 0...10).should == true + end + + it "returns true if self is beginless and self.end > other.end" do + (...10).send(@method, 4..6).should == true + end + + it "returns true if self is beginless and self.end == other.end" do + (..10).send(@method, 4..10).should == true + end + + it "returns true if self is beginless and self.end == other.end and self.exclude_end? is true and other.exclude_end? is true" do + (...10).send(@method, 4...10).should == true + end + + it "returns true if self and other are beginless and self.end > other.end" do + (...10).send(@method, (...6)).should == true + end + + it "returns true if self and other are beginless and self.end == other.end and self.exclude_end? is true and other.exclude_end? is true" do + (...10).send(@method, (...10)).should == true + end + + it "returns true if self is beginless and self.end < other.end but other.exclude_end? is true and the rightmost other value <= self.end" do + (..10).send(@method, 4...11).should == true + end + + it "returns true if self is endless and self.begin < other.begin" do + (0..).send(@method, 4..6).should == true + end + + it "returns true if self is endless and self.begin == other.begin" do + (0..).send(@method, 0..6).should == true + end + + it "returns true if self and other are endless and self.begin < other.begin" do + (0..).send(@method, (4..)).should == true + end + + it "returns true if self and other are endless and self.begin == other.begin" do + (0..).send(@method, (0..)).should == true + end + + it "returns true if self and other are endless and self.begin < other.begin and self.exclude_end? is true and other.exclude_end? is true" do + (0...).send(@method, (4...)).should == true + end + + it "returns true if self and other are endless and self.begin == other.begin and self.exclude_end? is true and other.exclude_end? is true" do + (0...).send(@method, (0...)).should == true + end + + it "returns true if self is (nil..nil) and other is finite" do + (nil..nil).send(@method, 4..6).should == true + end + + it "returns true if self is (nil..nil) and other is beginless" do + (nil..nil).send(@method, (...6)).should == true + end + + it "returns true if self is (nil..nil) and other is endless" do + (nil..nil).send(@method, (4..)).should == true + end + + it "returns true if self is (nil...nil) and other is finite" do + (nil...nil).send(@method, 4..6).should == true + end + + it "returns true if self is (nil...nil) and other is beginless" do + (nil...nil).send(@method, (...6)).should == true + end + + it "returns true if self is (nil...nil) and other is endless and other.exclude_end? is true" do + (nil...nil).send(@method, (4...)).should == true + end + end + + context "when other range is partially interleaved" do + it "returns false if self.begin > other.begin, self.begin < other.end and self.end > other.end" do + (4..10).send(@method, 0..6).should == false + end + + it "returns false if self.begin > other.begin, self.begin < other.end and self.end == other.end" do + (4..10).send(@method, 0..10).should == false + end + + it "returns false if self.begin > other.begin, self.begin < other.end and self.end < other.end" do + (4..6).send(@method, 0..10).should == false + end + + it "returns false if self.begin < other.begin and self.end > other.begin, self.end < other.end" do + (0..6).send(@method, 4..10).should == false + end + + it "returns false if self.begin < other.begin and self.end == other.end and self.exclude_end? is true" do + (0...10).send(@method, 6..10).should == false + end + + it "returns false if self.begin < other.begin and self.end == other.begin" do + (0..4).send(@method, 4..10).should == false + end + + it "returns false if self is beginless and self.end > other.begin but self.end < other.end" do + (...6).send(@method, 4..10).should == false + end + + it "returns false if self is beginless and self.end == other.end but self.exclude_end? is true" do + (...6).send(@method, 4..6).should == false + end + + it "returns false if self and other are beginless but self.end < other.end" do + (...6).send(@method, (...10)).should == false + end + + it "returns false if self and other are beginless and self.end == other.end but self.exclude_end? is true" do + (...10).send(@method, (..10)).should == false + end + + it "returns false if self is endless and self.begin > other.begin" do + (4..).send(@method, 0..6).should == false + end + + it "returns false if self and other are endless and self.begin > other.begin" do + (4..).send(@method, (0..)).should == false + end + + it "returns false if self and other are endless and self.begin == other.begin but self.exclude_end? is true" do + (0...).send(@method, (0..)).should == false + end + + it "returns false if self and other are endless and self.begin < other.begin but self.exclude_end? is true" do + (0...).send(@method, (4..)).should == false + end + + it "returns false if self is (nil...nil) and other is endless" do + (nil...nil).send(@method, (4..)).should == false + end + + it "returns false if self is beginless and other is endless and self.end == other.begin" do + (..10).send(@method, (10..)).should == false + end + + it "returns false if other is beginless and self is endless and other.end == self.begin" do + (10..).send(@method, (..10)).should == false + end + + it "returns false if self is finite and other is beginless and they overlap" do + (0..10).send(@method, ...10).should == false + end + + it "returns false if self is finite and other is endless and they overlap" do + (0..10).send(@method, 0..).should == false + end + end + + context "when other range does not interleave" do + it "returns false if self.begin > other.end" do + (6..10).send(@method, 0..4).should == false + end + + it "returns false if self.end < other.begin" do + (0..4).send(@method, 6..10).should == false + end + + it "returns false if self is beginless but self.end < other.begin" do + (...4).send(@method, 6..10).should == false + end + + it "returns false if self is beginless and self.end == other.begin but self.exclude_end? is true" do + (...4).send(@method, 4..10).should == false + end + + it "returns false if self is beginless and other is endless but self.end < other.begin" do + (...0).send(@method, (10..)).should == false + end + + it "returns false if self is beginless and other is endless and self.end == other.begin but self.exclude_end? is true" do + (...10).send(@method, (10..)).should == false + end + + it "returns false if self is endless but self.begin > other.end" do + (10..).send(@method, 0..6).should == false + end + + it "returns false if self is endless but self.begin == other.end but other.exclude_end? is true" do + (6..).send(@method, (0...6)).should == false + end + + it "returns false if other is beginless but self.begin > other.end" do + (4..10).send(@method, (...0)).should == false + end + + it "returns false if other is beginless and self.begin == other.end but other.exclude_end? is true" do + (6..10).send(@method, (...6)).should == false + end + + it "returns false if other is beginless and self is endless and other.end == self.begin but other.exclude_end? is true" do + (10..).send(@method, (...10)).should == false + end + + it "returns false if other is endless but self.end < other.begin" do + (0..4).send(@method, (10..)).should == false + end + + it "returns false if other is endless and self.end == other.begin but self.exclude_end? is true" do + (0...10).send(@method, (10..)).should == false + end + end + + context "when comparing with backward ranges" do + it "returns false if other is backward and fits into self" do + (0..10).send(@method, 6..4).should == false + end + + it "returns false if self is backward and other fits into self" do + (10..0).send(@method, 4..6).should == false + end + end - (1.1..7.9).send(@method, (...10.5)).should == false + context "when comparing with empty ranges" do + it "returns false if other is empty and fits into self" do + (0..10).send(@method, 4...4).should == false + end - ('c'..'i').send(@method, (..'i')).should == false + it "returns false if self is empty and equals other" do + (0...0).send(@method, 0...0).should == false + end + end + + it "returns false if other boundaries are not comparable with self boundaries" do + (0..10).send(@method, ("a".."z")).should == false + end end - it "accepts endless range argument" do - eval("(0..)").send(@method, eval("(0...)")).should == true - (0..10).send(@method, eval("(0...)")).should == false + context "range argument with boundaries of generic type (that implements only #<=>)" do + context "when other range is completely inside self" do + context "when a range can be iterated (that's it responds to #succ)" do + it "returns true if self.begin < other.begin and self.end < other.end but other.exclude_end? is true and the rightmost other value <= self.end" do + a = RangeSpecs::CoverElementWithSucc.new(0)..RangeSpecs::CoverElementWithSucc.new(10) + b = RangeSpecs::CoverElementWithSucc.new(4)...RangeSpecs::CoverElementWithSucc.new(11) + (a).send(@method, b).should == true + end + + it "returns true if self is beginless and self.end < other.end but other.exclude_end? is true and the rightmost other value <= self.end" do + a = ..RangeSpecs::CoverElementWithSucc.new(10) + b = RangeSpecs::CoverElementWithSucc.new(4)...RangeSpecs::CoverElementWithSucc.new(11) + (a).send(@method, b).should == true + end + end + end - (1.1..7.9).send(@method, eval("(0.8...)")).should == false + context "when other range is partially interleaved" do + context "when a range cannot be iterated (that's it does not responds to #succ)" do + it "returns false if self.begin < other.begin, self.end < other.end but other.exclude_end? is true and the rightmost other value < self.end" do + (0..10.0).send(@method, 5...11.0).should == false + end - ('c'..'i').send(@method, eval("('a'..)")).should == false + it "returns false if self is beginless and self.end < other.end but other.exclude_end? is true and the rightmost other value < self.end" do + (..10.0).send(@method, ...11.0).should == false + end + end + end end end diff --git a/spec/ruby/core/range/shared/cover_and_include.rb b/spec/ruby/core/range/shared/cover_and_include.rb index 97721a7307304f..b68dcfeb6ff740 100644 --- a/spec/ruby/core/range/shared/cover_and_include.rb +++ b/spec/ruby/core/range/shared/cover_and_include.rb @@ -83,4 +83,14 @@ ('A'..'C').send(@method, 20.9).should == false ('A'...'C').send(@method, 'C').should == false end + + it "returns false if other is not an element of self for endless ranges" do + (1..).send(@method, 0.4).should == false + (0.5...).send(@method, 0.4).should == false + end + + it "returns false if other is not an element of self for beginless ranges" do + (..10).send(@method, 12.4).should == false + (...10.5).send(@method, 12.4).should == false + end end diff --git a/spec/ruby/core/rational/exponent_spec.rb b/spec/ruby/core/rational/exponent_spec.rb index 88b4a4379666ea..f8582f0aabdcb0 100644 --- a/spec/ruby/core/rational/exponent_spec.rb +++ b/spec/ruby/core/rational/exponent_spec.rb @@ -2,34 +2,31 @@ describe "Rational#**" do describe "when passed Rational" do - # Guard against the Mathn library - guard -> { !defined?(Math.rsqrt) } do - it "returns Rational(1) if the exponent is Rational(0)" do - (Rational(0) ** Rational(0)).should.eql?(Rational(1)) - (Rational(1) ** Rational(0)).should.eql?(Rational(1)) - (Rational(3, 4) ** Rational(0)).should.eql?(Rational(1)) - (Rational(-1) ** Rational(0)).should.eql?(Rational(1)) - (Rational(-3, 4) ** Rational(0)).should.eql?(Rational(1)) - (Rational(bignum_value) ** Rational(0)).should.eql?(Rational(1)) - (Rational(-bignum_value) ** Rational(0)).should.eql?(Rational(1)) - end + it "returns Rational(1) if the exponent is Rational(0)" do + (Rational(0) ** Rational(0)).should.eql?(Rational(1)) + (Rational(1) ** Rational(0)).should.eql?(Rational(1)) + (Rational(3, 4) ** Rational(0)).should.eql?(Rational(1)) + (Rational(-1) ** Rational(0)).should.eql?(Rational(1)) + (Rational(-3, 4) ** Rational(0)).should.eql?(Rational(1)) + (Rational(bignum_value) ** Rational(0)).should.eql?(Rational(1)) + (Rational(-bignum_value) ** Rational(0)).should.eql?(Rational(1)) + end - it "returns self raised to the argument as a Rational if the exponent's denominator is 1" do - (Rational(3, 4) ** Rational(1, 1)).should.eql?(Rational(3, 4)) - (Rational(3, 4) ** Rational(2, 1)).should.eql?(Rational(9, 16)) - (Rational(3, 4) ** Rational(-1, 1)).should.eql?(Rational(4, 3)) - (Rational(3, 4) ** Rational(-2, 1)).should.eql?(Rational(16, 9)) - end + it "returns self raised to the argument as a Rational if the exponent's denominator is 1" do + (Rational(3, 4) ** Rational(1, 1)).should.eql?(Rational(3, 4)) + (Rational(3, 4) ** Rational(2, 1)).should.eql?(Rational(9, 16)) + (Rational(3, 4) ** Rational(-1, 1)).should.eql?(Rational(4, 3)) + (Rational(3, 4) ** Rational(-2, 1)).should.eql?(Rational(16, 9)) + end - it "returns self raised to the argument as a Float if the exponent's denominator is not 1" do - (Rational(3, 4) ** Rational(4, 3)).should be_close(0.681420222312052, TOLERANCE) - (Rational(3, 4) ** Rational(-4, 3)).should be_close(1.46752322173095, TOLERANCE) - (Rational(3, 4) ** Rational(4, -3)).should be_close(1.46752322173095, TOLERANCE) - end + it "returns self raised to the argument as a Float if the exponent's denominator is not 1" do + (Rational(3, 4) ** Rational(4, 3)).should be_close(0.681420222312052, TOLERANCE) + (Rational(3, 4) ** Rational(-4, 3)).should be_close(1.46752322173095, TOLERANCE) + (Rational(3, 4) ** Rational(4, -3)).should be_close(1.46752322173095, TOLERANCE) + end - it "returns a complex number when self is negative and the passed argument is not 0" do - (Rational(-3, 4) ** Rational(-4, 3)).should be_close(Complex(-0.7337616108654732, 1.2709123906625817), TOLERANCE) - end + it "returns a complex number when self is negative and the passed argument is not 0" do + (Rational(-3, 4) ** Rational(-4, 3)).should be_close(Complex(-0.7337616108654732, 1.2709123906625817), TOLERANCE) end end @@ -46,16 +43,13 @@ (Rational(3, -bignum_value) ** -4).should == Rational(115792089237316195423570985008687907853269984665640564039457584007913129639936, 81) end - # Guard against the Mathn library - guard -> { !defined?(Math.rsqrt) } do - it "returns Rational(1, 1) when the passed argument is 0" do - (Rational(3, 4) ** 0).should.eql?(Rational(1, 1)) - (Rational(-3, 4) ** 0).should.eql?(Rational(1, 1)) - (Rational(3, -4) ** 0).should.eql?(Rational(1, 1)) + it "returns Rational(1, 1) when the passed argument is 0" do + (Rational(3, 4) ** 0).should.eql?(Rational(1, 1)) + (Rational(-3, 4) ** 0).should.eql?(Rational(1, 1)) + (Rational(3, -4) ** 0).should.eql?(Rational(1, 1)) - (Rational(bignum_value, 4) ** 0).should.eql?(Rational(1, 1)) - (Rational(3, -bignum_value) ** 0).should.eql?(Rational(1, 1)) - end + (Rational(bignum_value, 4) ** 0).should.eql?(Rational(1, 1)) + (Rational(3, -bignum_value) ** 0).should.eql?(Rational(1, 1)) end end diff --git a/spec/ruby/core/rational/inspect_spec.rb b/spec/ruby/core/rational/inspect_spec.rb index edc5cffee94887..40eeb7159d765f 100644 --- a/spec/ruby/core/rational/inspect_spec.rb +++ b/spec/ruby/core/rational/inspect_spec.rb @@ -6,9 +6,6 @@ Rational(-5, 8).inspect.should == "(-5/8)" Rational(-1, -2).inspect.should == "(1/2)" - # Guard against the Mathn library - guard -> { !defined?(Math.rsqrt) } do - Rational(bignum_value, 1).inspect.should == "(#{bignum_value}/1)" - end + Rational(bignum_value, 1).inspect.should == "(#{bignum_value}/1)" end end diff --git a/spec/ruby/core/rational/integer_spec.rb b/spec/ruby/core/rational/integer_spec.rb index cd7fa97fcf4e1f..443940126a3a29 100644 --- a/spec/ruby/core/rational/integer_spec.rb +++ b/spec/ruby/core/rational/integer_spec.rb @@ -1,10 +1,7 @@ require_relative "../../spec_helper" describe "Rational#integer?" do - # Guard against the Mathn library - guard -> { !defined?(Math.rsqrt) } do - it "returns false for a rational with a numerator and no denominator" do - Rational(20).integer?.should == false - end + it "returns false for a rational with a numerator and no denominator" do + Rational(20).integer?.should == false end it "returns false for a rational with a numerator and a denominator" do diff --git a/spec/ruby/core/rational/round_spec.rb b/spec/ruby/core/rational/round_spec.rb index dd6f66f408a9ff..2b991aa2ec7a53 100644 --- a/spec/ruby/core/rational/round_spec.rb +++ b/spec/ruby/core/rational/round_spec.rb @@ -47,11 +47,8 @@ it "returns a Rational" do @rational.round(1).should.is_a?(Rational) @rational.round(2).should.is_a?(Rational) - # Guard against the Mathn library - guard -> { !defined?(Math.rsqrt) } do - Rational(0, 1).round(1).should.is_a?(Rational) - Rational(2, 1).round(1).should.is_a?(Rational) - end + Rational(0, 1).round(1).should.is_a?(Rational) + Rational(2, 1).round(1).should.is_a?(Rational) end it "moves the truncation point n decimal places right" do diff --git a/spec/ruby/core/rational/to_s_spec.rb b/spec/ruby/core/rational/to_s_spec.rb index 24e30778e5a6c5..0720a122189343 100644 --- a/spec/ruby/core/rational/to_s_spec.rb +++ b/spec/ruby/core/rational/to_s_spec.rb @@ -2,11 +2,8 @@ describe "Rational#to_s" do it "returns a string representation of self" do - # Guard against the Mathn library - guard -> { !defined?(Math.rsqrt) } do - Rational(1, 1).to_s.should == "1/1" - Rational(2, 1).to_s.should == "2/1" - end + Rational(1, 1).to_s.should == "1/1" + Rational(2, 1).to_s.should == "2/1" Rational(1, 2).to_s.should == "1/2" Rational(-1, 3).to_s.should == "-1/3" Rational(1, -3).to_s.should == "-1/3" diff --git a/spec/ruby/core/string/fixtures/classes.rb b/spec/ruby/core/string/fixtures/classes.rb index 26fcd51b5d5fce..edc8f3ee3e0096 100644 --- a/spec/ruby/core/string/fixtures/classes.rb +++ b/spec/ruby/core/string/fixtures/classes.rb @@ -4,7 +4,7 @@ class Object def unpack_format(count=nil, repeat=nil) format = "#{instance_variable_get(:@method)}#{count}" format *= repeat if repeat - format.dup # because it may then become tainted + format end end diff --git a/spec/ruby/core/string/modulo_spec.rb b/spec/ruby/core/string/modulo_spec.rb index f93ec4bcf8fbe3..45381fd92f5cb0 100644 --- a/spec/ruby/core/string/modulo_spec.rb +++ b/spec/ruby/core/string/modulo_spec.rb @@ -55,51 +55,6 @@ -> { ("foo%" % [])}.should.raise(ArgumentError) end - ruby_version_is ""..."3.4" do - it "formats single % character before a newline as literal %" do - ("%\n" % []).should == "%\n" - ("foo%\n" % []).should == "foo%\n" - ("%\n.3f" % 1.2).should == "%\n.3f" - end - - it "formats single % character before a NUL as literal %" do - ("%\0" % []).should == "%\0" - ("foo%\0" % []).should == "foo%\0" - ("%\0.3f" % 1.2).should == "%\0.3f" - end - - it "raises an error if single % appears anywhere else" do - -> { (" % " % []) }.should.raise(ArgumentError) - -> { ("foo%quux" % []) }.should.raise(ArgumentError) - end - - it "raises an error if NULL or \\n appear anywhere else in the format string" do - begin - old_debug, $DEBUG = $DEBUG, false - - -> { "%.\n3f" % 1.2 }.should.raise(ArgumentError) - -> { "%.3\nf" % 1.2 }.should.raise(ArgumentError) - -> { "%.\03f" % 1.2 }.should.raise(ArgumentError) - -> { "%.3\0f" % 1.2 }.should.raise(ArgumentError) - ensure - $DEBUG = old_debug - end - end - end - - ruby_version_is "3.4" do - it "raises an ArgumentError if % is not followed by a conversion specifier" do - -> { "%" % [] }.should.raise(ArgumentError) - -> { "%\n" % [] }.should.raise(ArgumentError) - -> { "%\0" % [] }.should.raise(ArgumentError) - -> { " % " % [] }.should.raise(ArgumentError) - -> { "%.\n3f" % 1.2 }.should.raise(ArgumentError) - -> { "%.3\nf" % 1.2 }.should.raise(ArgumentError) - -> { "%.\03f" % 1.2 }.should.raise(ArgumentError) - -> { "%.3\0f" % 1.2 }.should.raise(ArgumentError) - end - end - it "ignores unused arguments when $DEBUG is false" do begin old_debug = $DEBUG @@ -140,18 +95,6 @@ end end - ruby_version_is ""..."3.4" do - it "replaces trailing absolute argument specifier without type with percent sign" do - ("hello %1$" % "foo").should == "hello %" - end - end - - ruby_version_is "3.4" do - it "raises an ArgumentError if absolute argument specifier is followed by a conversion specifier" do - -> { "hello %1$" % "foo" }.should.raise(ArgumentError) - end - end - it "raises an ArgumentError when given invalid argument specifiers" do -> { "%1" % [] }.should.raise(ArgumentError) -> { "%+" % [] }.should.raise(ArgumentError) diff --git a/spec/ruby/core/string/to_f_spec.rb b/spec/ruby/core/string/to_f_spec.rb index bb09e4f2f31975..d3c6fa8e0c53e0 100644 --- a/spec/ruby/core/string/to_f_spec.rb +++ b/spec/ruby/core/string/to_f_spec.rb @@ -46,16 +46,39 @@ "_9".to_f.should == 0 end - it "stops if the underscore is not followed or preceded by a number" do + it "stops if the underscore is not followed by a number" do "1__2".to_f.should == 1.0 - "1_.2".to_f.should == 1.0 - "1._2".to_f.should == 1.0 - "1.2_e2".to_f.should == 1.2 - "1.2e_2".to_f.should == 1.2 "1_x2".to_f.should == 1.0 + + "1.2__3".to_f.should == 1.2 + "1.2_x3".to_f.should == 1.2 + + "1.2e__3".to_f.should == 1.2 + "1.2e_x3".to_f.should == 1.2 + end + + it "stops if the number contains invalid characters" do "1x_2".to_f.should == 1.0 + "1.2x_3".to_f.should == 1.2 + "1.2ex_3".to_f.should == 1.2 + end + + it "stops if +/- is not followed by a number" do "+_1".to_f.should == 0.0 "-_1".to_f.should == 0.0 + "+a1".to_f.should == 0.0 + + "1.1e+_1".to_f.should == 1.1 + "1.1e-_1".to_f.should == 1.1 + "1.1e-a1".to_f.should == 1.1 + end + + it "stops if the underscore appears at boundaries" do + "1_.2".to_f.should == 1.0 + "1._2".to_f.should == 1.0 + + "1.2_e3".to_f.should == 1.2 + "1.2e_3".to_f.should == 1.2 end it "does not allow prefixes to autodetect the base" do diff --git a/spec/ruby/core/string/unpack/a_spec.rb b/spec/ruby/core/string/unpack/a_spec.rb index a68e842e15200e..925962fddbabb2 100644 --- a/spec/ruby/core/string/unpack/a_spec.rb +++ b/spec/ruby/core/string/unpack/a_spec.rb @@ -3,14 +3,12 @@ require_relative '../fixtures/classes' require_relative 'shared/basic' require_relative 'shared/string' -require_relative 'shared/taint' describe "String#unpack with format 'A'" do it_behaves_like :string_unpack_basic, 'A' it_behaves_like :string_unpack_no_platform, 'A' it_behaves_like :string_unpack_string, 'A' it_behaves_like :string_unpack_Aa, 'A' - it_behaves_like :string_unpack_taint, 'A' it "removes trailing space and NULL bytes from the decoded string" do [ ["a\x00 b \x00", ["a\x00 b", ""]], @@ -42,7 +40,6 @@ it_behaves_like :string_unpack_no_platform, 'a' it_behaves_like :string_unpack_string, 'a' it_behaves_like :string_unpack_Aa, 'a' - it_behaves_like :string_unpack_taint, 'a' it "does not remove trailing whitespace or NULL bytes from the decoded string" do [ ["a\x00 b \x00", ["a\x00 b \x00"]], diff --git a/spec/ruby/core/string/unpack/b_spec.rb b/spec/ruby/core/string/unpack/b_spec.rb index fac6ef5151ef9e..79ee6163e52e9d 100644 --- a/spec/ruby/core/string/unpack/b_spec.rb +++ b/spec/ruby/core/string/unpack/b_spec.rb @@ -2,12 +2,10 @@ require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' -require_relative 'shared/taint' describe "String#unpack with format 'B'" do it_behaves_like :string_unpack_basic, 'B' it_behaves_like :string_unpack_no_platform, 'B' - it_behaves_like :string_unpack_taint, 'B' it "decodes one bit from each byte for each format character starting with the most significant bit" do [ ["\x00", "B", ["0"]], @@ -105,7 +103,6 @@ describe "String#unpack with format 'b'" do it_behaves_like :string_unpack_basic, 'b' it_behaves_like :string_unpack_no_platform, 'b' - it_behaves_like :string_unpack_taint, 'b' it "decodes one bit from each byte for each format character starting with the least significant bit" do [ ["\x00", "b", ["0"]], diff --git a/spec/ruby/core/string/unpack/h_spec.rb b/spec/ruby/core/string/unpack/h_spec.rb index 0cf8d943a7f458..b8f7d9ca31a92a 100644 --- a/spec/ruby/core/string/unpack/h_spec.rb +++ b/spec/ruby/core/string/unpack/h_spec.rb @@ -2,12 +2,10 @@ require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' -require_relative 'shared/taint' describe "String#unpack with format 'H'" do it_behaves_like :string_unpack_basic, 'H' it_behaves_like :string_unpack_no_platform, 'H' - it_behaves_like :string_unpack_taint, 'H' it "decodes one nibble from each byte for each format character starting with the most significant bit" do [ ["\x8f", "H", ["8"]], @@ -74,7 +72,6 @@ describe "String#unpack with format 'h'" do it_behaves_like :string_unpack_basic, 'h' it_behaves_like :string_unpack_no_platform, 'h' - it_behaves_like :string_unpack_taint, 'h' it "decodes one nibble from each byte for each format character starting with the least significant bit" do [ ["\x8f", "h", ["f"]], diff --git a/spec/ruby/core/string/unpack/m_spec.rb b/spec/ruby/core/string/unpack/m_spec.rb index c1c1eea629c7c8..e4b26c764da349 100644 --- a/spec/ruby/core/string/unpack/m_spec.rb +++ b/spec/ruby/core/string/unpack/m_spec.rb @@ -2,12 +2,10 @@ require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' -require_relative 'shared/taint' describe "String#unpack with format 'M'" do it_behaves_like :string_unpack_basic, 'M' it_behaves_like :string_unpack_no_platform, 'M' - it_behaves_like :string_unpack_taint, 'M' it "decodes an empty string" do "".unpack("M").should == [""] @@ -107,7 +105,6 @@ describe "String#unpack with format 'm'" do it_behaves_like :string_unpack_basic, 'm' it_behaves_like :string_unpack_no_platform, 'm' - it_behaves_like :string_unpack_taint, 'm' it "decodes an empty string" do "".unpack("m").should == [""] diff --git a/spec/ruby/core/string/unpack/p_spec.rb b/spec/ruby/core/string/unpack/p_spec.rb index 4103730269b6f5..98e10df6bb5c2c 100644 --- a/spec/ruby/core/string/unpack/p_spec.rb +++ b/spec/ruby/core/string/unpack/p_spec.rb @@ -1,11 +1,9 @@ require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' -require_relative 'shared/taint' describe "String#unpack with format 'P'" do it_behaves_like :string_unpack_basic, 'P' - it_behaves_like :string_unpack_taint, 'P' it "round-trips a string through pack and unpack" do ["hello"].pack("P").unpack("P5").should == ["hello"] @@ -29,7 +27,6 @@ describe "String#unpack with format 'p'" do it_behaves_like :string_unpack_basic, 'p' - it_behaves_like :string_unpack_taint, 'p' it "round-trips a string through pack and unpack" do ["hello"].pack("p").unpack("p").should == ["hello"] diff --git a/spec/ruby/core/string/unpack/shared/taint.rb b/spec/ruby/core/string/unpack/shared/taint.rb deleted file mode 100644 index 79c7251f01af27..00000000000000 --- a/spec/ruby/core/string/unpack/shared/taint.rb +++ /dev/null @@ -1,2 +0,0 @@ -describe :string_unpack_taint, shared: true do -end diff --git a/spec/ruby/core/string/unpack/u_spec.rb b/spec/ruby/core/string/unpack/u_spec.rb index 720c1b85832687..d620b70b714ef5 100644 --- a/spec/ruby/core/string/unpack/u_spec.rb +++ b/spec/ruby/core/string/unpack/u_spec.rb @@ -3,13 +3,11 @@ require_relative '../fixtures/classes' require_relative 'shared/basic' require_relative 'shared/unicode' -require_relative 'shared/taint' describe "String#unpack with format 'U'" do it_behaves_like :string_unpack_basic, 'U' it_behaves_like :string_unpack_no_platform, 'U' it_behaves_like :string_unpack_unicode, 'U' - it_behaves_like :string_unpack_taint, 'U' it "raises ArgumentError on a malformed byte sequence" do -> { "\xE3".unpack('U') }.should.raise(ArgumentError) @@ -23,7 +21,6 @@ describe "String#unpack with format 'u'" do it_behaves_like :string_unpack_basic, 'u' it_behaves_like :string_unpack_no_platform, 'u' - it_behaves_like :string_unpack_taint, 'u' it "decodes an empty string as an empty string" do "".unpack("u").should == [""] diff --git a/spec/ruby/core/string/unpack/z_spec.rb b/spec/ruby/core/string/unpack/z_spec.rb index 10303905508704..7d24687b2d3816 100644 --- a/spec/ruby/core/string/unpack/z_spec.rb +++ b/spec/ruby/core/string/unpack/z_spec.rb @@ -3,13 +3,11 @@ require_relative '../fixtures/classes' require_relative 'shared/basic' require_relative 'shared/string' -require_relative 'shared/taint' describe "String#unpack with format 'Z'" do it_behaves_like :string_unpack_basic, 'Z' it_behaves_like :string_unpack_no_platform, 'Z' it_behaves_like :string_unpack_string, 'Z' - it_behaves_like :string_unpack_taint, 'Z' it "stops decoding at NULL bytes when passed the '*' modifier" do "a\x00\x00 b \x00c".unpack('Z*Z*Z*Z*').should == ["a", "", " b ", "c"] diff --git a/spec/ruby/core/thread/backtrace_locations_spec.rb b/spec/ruby/core/thread/backtrace_locations_spec.rb index 28a488f3116458..e7bec2b461437a 100644 --- a/spec/ruby/core/thread/backtrace_locations_spec.rb +++ b/spec/ruby/core/thread/backtrace_locations_spec.rb @@ -32,6 +32,18 @@ locations2.map(&:to_s).should == locations1[2..4].map(&:to_s) end + it "raises for negative start" do + -> { Thread.current.backtrace_locations(-1) }.should.raise(ArgumentError, "negative level (-1)") + end + + it "raises for negative length" do + -> { Thread.current.backtrace_locations(0, -1) }.should.raise(ArgumentError, "negative size (-1)") + end + + it "can be called with `nil` length" do + Thread.current.backtrace_locations(0, nil).map(&:to_s).should == Thread.current.backtrace_locations(0).map(&:to_s) + end + it "can be called with a range" do locations1 = Thread.current.backtrace_locations locations2 = Thread.current.backtrace_locations(2..4) @@ -76,4 +88,9 @@ it "[1..-1] is the same as #caller_locations(0..-1) for Thread.current" do Thread.current.backtrace_locations(1..-1).map(&:to_s).should == caller_locations(0..-1).map(&:to_s) end + + it "coerces the arguments to integers" do + Thread.current.backtrace_locations(1.1, 1.1).map(&:to_s).should == Thread.current.backtrace_locations(1, 1).map(&:to_s) + Thread.current.backtrace_locations(1.1..1.1).map(&:to_s).should == Thread.current.backtrace_locations(1..1).map(&:to_s) + end end diff --git a/spec/ruby/core/thread/backtrace_spec.rb b/spec/ruby/core/thread/backtrace_spec.rb index 770c300f06a9a6..4a04a371b17709 100644 --- a/spec/ruby/core/thread/backtrace_spec.rb +++ b/spec/ruby/core/thread/backtrace_spec.rb @@ -46,6 +46,19 @@ locations1[2..4].map(&:to_s).should == locations2.map(&:to_s) end + it "raises for negative start" do + -> { Thread.current.backtrace(-1) }.should.raise(ArgumentError, "negative level (-1)") + end + + it "raises for negative length" do + -> { Thread.current.backtrace(0, -1) }.should.raise(ArgumentError, "negative size (-1)") + end + + + it "can be called with `nil` length" do + Thread.current.backtrace(0, nil).should == Thread.current.backtrace(0) + end + it "can be called with a range" do locations1 = Thread.current.backtrace locations2 = Thread.current.backtrace(2..4) @@ -66,4 +79,9 @@ omit = Thread.current.backtrace.length Thread.current.backtrace(omit).should == [] end + + it "coerces the arguments to integers" do + Thread.current.backtrace(1.1, 1.1).should == Thread.current.backtrace(1, 1) + Thread.current.backtrace(1.1..1.1).should == Thread.current.backtrace(1..1) + end end diff --git a/spec/ruby/core/thread/to_s_spec.rb b/spec/ruby/core/thread/to_s_spec.rb index 2aef426de86d2c..2830fb8f4114b4 100644 --- a/spec/ruby/core/thread/to_s_spec.rb +++ b/spec/ruby/core/thread/to_s_spec.rb @@ -8,8 +8,21 @@ thread.to_s.should =~ /^#$/ end + it "returns a description including a class name" do + thread = Thread.new { "hello" }.join + thread.to_s.should.include?("Thread") + end + + it "returns a description including a thread name if given any" do + thread = Thread.new { "hello" }.join + thread.name = "平仮名" + thread.to_s.should.include?("@平仮名") + thread.to_s.encoding.should == Encoding::UTF_8 + end + it "has a binary encoding" do - ThreadSpecs.status_of_current_thread.to_s.encoding.should == Encoding::BINARY + thread = Thread.new { "hello" }.join + thread.to_s.encoding.should == Encoding::BINARY end it "can check it's own status" do diff --git a/spec/ruby/core/unboundmethod/equal_value_spec.rb b/spec/ruby/core/unboundmethod/equal_value_spec.rb index 24d5233299fc6a..fc7548a05486ec 100644 --- a/spec/ruby/core/unboundmethod/equal_value_spec.rb +++ b/spec/ruby/core/unboundmethod/equal_value_spec.rb @@ -129,6 +129,13 @@ def discard_1; :discard; end (@discard_1 == UnboundMethodSpecs::Methods.instance_method(:discard_1)).should == false end + it "returns false for two attr_reader methods" do + Class.new do + attr_reader :a, :b + instance_method(:a).should_not == instance_method(:b) + end + end + it "considers methods through aliasing equal" do c = Class.new do class << self diff --git a/spec/ruby/language/it_parameter_spec.rb b/spec/ruby/language/it_parameter_spec.rb index 6ba67cbb4f26a3..2542eb70fe8766 100644 --- a/spec/ruby/language/it_parameter_spec.rb +++ b/spec/ruby/language/it_parameter_spec.rb @@ -1,7 +1,7 @@ require_relative '../spec_helper' ruby_version_is "3.4" do - eval <<-RUBY # use eval to avoid warnings on Ruby 3.3 + eval <<-RUBY, binding, __FILE__, __LINE__ + 1 # use eval to avoid warnings on Ruby 3.3 describe "The `it` parameter" do it "provides it in a block" do -> { it }.call("a").should == "a" @@ -86,6 +86,25 @@ def o.it -> { it; binding.local_variables }.call("a").should == [] end + it "does not affect binding local variables getting" do + proc { + a = it; binding.local_variable_get(:it) + }.should.raise(NameError, /local variable 'it' is not defined for/) + end + + it "does not affect binding local variables setting" do + -> { + a = it + binding.local_variable_set(:it, :b) + [a, it] + }.call(:a).should == [:a, :a] + end + + it "does not affect binding local variables definition check" do + a = it + binding.local_variable_defined?(:it).should == false + end + it "does not work in methods" do obj = Object.new def obj.foo; it; end diff --git a/spec/ruby/language/numbered_parameters_spec.rb b/spec/ruby/language/numbered_parameters_spec.rb index 23e89a14be9065..17258c11f89d4c 100644 --- a/spec/ruby/language/numbered_parameters_spec.rb +++ b/spec/ruby/language/numbered_parameters_spec.rb @@ -95,6 +95,18 @@ -> { _1; binding.local_variables }.call("a").should == [:_1] -> { _2; binding.local_variables }.call("a", "b").should == [:_1, :_2] end + + it "affects binding local variables getting" do + -> { _1; binding.local_variable_get(:_1) }.call("a").should == "a" + end + + it "affects binding local variables setting" do + -> { _1; binding.local_variable_set(:_1, "b"); _1 }.call("a").should == "b" + end + + it "affects binding local variables definition check" do + -> { _1; binding.local_variable_defined?(:_1) }.call("a").should == true + end end ruby_version_is "4.0" do @@ -102,6 +114,24 @@ -> { _1; binding.local_variables }.call("a").should == [] -> { _2; binding.local_variables }.call("a", "b").should == [] end + + it "does not affect binding local variables getting" do + proc { + _1; binding.local_variable_get(:_1) + }.should.raise(NameError, "numbered parameter '_1' is not a local variable") + end + + it "does not affect binding local variables setting" do + proc { + _1; binding.local_variable_set(:_1, "b") + }.should.raise(NameError, "numbered parameter '_1' is not a local variable") + end + + it "does not affect binding local variables definition check" do + proc { + _1; binding.local_variable_defined?(:_1) + }.should.raise(NameError, "numbered parameter '_1' is not a local variable") + end end it "does not work in methods" do @@ -110,4 +140,8 @@ def obj.foo; _1 end -> { obj.foo("a") }.should.raise(ArgumentError, /wrong number of arguments/) end + + it "cannot be accessed using eval()" do + -> { proc { binding.eval('_1') }.call(1) }.should.raise(NameError, /undefined local variable or method ._1'/) + end end diff --git a/spec/ruby/language/precedence_spec.rb b/spec/ruby/language/precedence_spec.rb index edb990525eef14..a4de9fac1740d7 100644 --- a/spec/ruby/language/precedence_spec.rb +++ b/spec/ruby/language/precedence_spec.rb @@ -131,12 +131,9 @@ class UnaryMinusTest; def -@; 50; end; end it "* / % are left-associative" do (2*1/2).should == (2*1)/2 - # Guard against the Mathn library # TODO: Make these specs not rely on specific behaviour / result values # by using mocks. - guard -> { !defined?(Math.rsqrt) } do - (2*1/2).should_not == 2*(1/2) - end + (2*1/2).should_not == 2*(1/2) (10/7/5).should == (10/7)/5 (10/7/5).should_not == 10/(7/5) diff --git a/spec/ruby/language/rescue_spec.rb b/spec/ruby/language/rescue_spec.rb index cf16d8f6f85718..673976f8bf58e5 100644 --- a/spec/ruby/language/rescue_spec.rb +++ b/spec/ruby/language/rescue_spec.rb @@ -98,6 +98,19 @@ def a ScratchPad.recorded.should == ["message"] end + it 'captures successfully in a begin/end block' do + ScratchPad.record [] + + begin + raise "message" + rescue => e + ScratchPad << e.message + end + + ScratchPad.recorded.should == ["message"] + e.should.is_a?(RuntimeError) + end + it 'captures successfully in a class' do ScratchPad.record [] @@ -122,7 +135,7 @@ module RescueSpecs::M ScratchPad.recorded.should == ["message"] end - it 'captures sucpcessfully in a singleton class' do + it 'captures successfully in a singleton class' do ScratchPad.record [] class << Object.new diff --git a/spec/ruby/library/delegate/delegator/taint_spec.rb b/spec/ruby/library/delegate/delegator/taint_spec.rb deleted file mode 100644 index 6bf13bb73db3f2..00000000000000 --- a/spec/ruby/library/delegate/delegator/taint_spec.rb +++ /dev/null @@ -1,8 +0,0 @@ -require_relative '../../../spec_helper' -require_relative '../fixtures/classes' - -describe "Delegator#taint" do - before :each do - @delegate = DelegateSpecs::Delegator.new("") - end -end diff --git a/spec/ruby/library/delegate/delegator/trust_spec.rb b/spec/ruby/library/delegate/delegator/trust_spec.rb deleted file mode 100644 index f1b81814c56907..00000000000000 --- a/spec/ruby/library/delegate/delegator/trust_spec.rb +++ /dev/null @@ -1,8 +0,0 @@ -require_relative '../../../spec_helper' -require_relative '../fixtures/classes' - -describe "Delegator#trust" do - before :each do - @delegate = DelegateSpecs::Delegator.new([]) - end -end diff --git a/spec/ruby/library/delegate/delegator/untaint_spec.rb b/spec/ruby/library/delegate/delegator/untaint_spec.rb deleted file mode 100644 index 4051fd2629b1f3..00000000000000 --- a/spec/ruby/library/delegate/delegator/untaint_spec.rb +++ /dev/null @@ -1,8 +0,0 @@ -require_relative '../../../spec_helper' -require_relative '../fixtures/classes' - -describe "Delegator#untaint" do - before :each do - @delegate = -> { DelegateSpecs::Delegator.new("") }.call - end -end diff --git a/spec/ruby/library/delegate/delegator/untrust_spec.rb b/spec/ruby/library/delegate/delegator/untrust_spec.rb deleted file mode 100644 index 4f7fa1e582c555..00000000000000 --- a/spec/ruby/library/delegate/delegator/untrust_spec.rb +++ /dev/null @@ -1,8 +0,0 @@ -require_relative '../../../spec_helper' -require_relative '../fixtures/classes' - -describe "Delegator#untrust" do - before :each do - @delegate = DelegateSpecs::Delegator.new("") - end -end diff --git a/spec/ruby/library/matrix/divide_spec.rb b/spec/ruby/library/matrix/divide_spec.rb index 711a5189e44a2b..d4ffee3c86111d 100644 --- a/spec/ruby/library/matrix/divide_spec.rb +++ b/spec/ruby/library/matrix/divide_spec.rb @@ -14,15 +14,12 @@ (@a / @b).should be_close_to_matrix([[2.5, -1.5], [1.5, -0.5]]) end - # Guard against the Mathn library - guard -> { !defined?(Math.rsqrt) } do - it "returns the result of dividing self by a Fixnum" do - (@a / 2).should == Matrix[ [0, 1], [1, 2] ] - end + it "returns the result of dividing self by a Fixnum" do + (@a / 2).should == Matrix[ [0, 1], [1, 2] ] + end - it "returns the result of dividing self by a Bignum" do - (@a / bignum_value).should == Matrix[ [0, 0], [0, 0] ] - end + it "returns the result of dividing self by a Bignum" do + (@a / bignum_value).should == Matrix[ [0, 0], [0, 0] ] end it "returns the result of dividing self by a Float" do diff --git a/spec/ruby/library/matrix/real_spec.rb b/spec/ruby/library/matrix/real_spec.rb index 4589dc22a5ffb9..60872ee64c981a 100644 --- a/spec/ruby/library/matrix/real_spec.rb +++ b/spec/ruby/library/matrix/real_spec.rb @@ -16,11 +16,8 @@ Matrix[ [Complex(1,1), 2], [3, 4] ].real?.should == false end - # Guard against the Mathn library - guard -> { !defined?(Math.rsqrt) } do - it "returns false if one element is a Complex whose imaginary part is 0" do - Matrix[ [Complex(1,0), 2], [3, 4] ].real?.should == false - end + it "returns false if one element is a Complex whose imaginary part is 0" do + Matrix[ [Complex(1,0), 2], [3, 4] ].real?.should == false end end diff --git a/spec/ruby/library/pathname/pathname_spec.rb b/spec/ruby/library/pathname/pathname_spec.rb index 6fa6fd2bcba58d..31228de65ce022 100644 --- a/spec/ruby/library/pathname/pathname_spec.rb +++ b/spec/ruby/library/pathname/pathname_spec.rb @@ -2,14 +2,10 @@ require 'pathname' describe "Kernel#Pathname" do - it "is a private instance method" do + it "is a private method" do Kernel.private_instance_methods(false).should.include?(:Pathname) end - it "is also a public method" do - Kernel.should.respond_to?(:Pathname) - end - it "returns same argument when called with a pathname argument" do path = Pathname('foo') new_path = Pathname(path) @@ -17,3 +13,9 @@ path.should.equal?(new_path) end end + +describe "Kernel.Pathname" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:Pathname) + end +end diff --git a/spec/ruby/library/socket/ipsocket/inspect_spec.rb b/spec/ruby/library/socket/ipsocket/inspect_spec.rb index 85780a16f6f35d..92f60e347266b3 100644 --- a/spec/ruby/library/socket/ipsocket/inspect_spec.rb +++ b/spec/ruby/library/socket/ipsocket/inspect_spec.rb @@ -21,4 +21,22 @@ ensure @socket&.close end + + it 'returns a String marking the socket as closed for a closed TCPSocket' do + @server = TCPServer.new("127.0.0.1", 0) + @socket = TCPSocket.new("127.0.0.1", @server.addr[1]) + @socket.close + + @socket.inspect.should == "#" + ensure + @server&.close + end + + it 'returns a String marking the socket as closed for a closed UDPSocket' do + @socket = UDPSocket.new + @socket.bind('127.0.0.1', 0) + @socket.close + + @socket.inspect.should == "#" + end end diff --git a/spec/ruby/library/stringio/ungetc_spec.rb b/spec/ruby/library/stringio/ungetc_spec.rb index 8753ac9666e55b..dbe1ce5ddd3931 100644 --- a/spec/ruby/library/stringio/ungetc_spec.rb +++ b/spec/ruby/library/stringio/ungetc_spec.rb @@ -12,6 +12,18 @@ @io.string.should == 'A234' end + it "writes the passed string before the current position" do + @io.pos = 3 + @io.ungetc("foo") + @io.string.should == 'foo4' + end + + it "writes the passed string at the start if the current position is 0" do + @io.pos = 0 + @io.ungetc("A") + @io.string.should == 'A1234' + end + it "returns nil" do @io.pos = 1 @io.ungetc(?A).should == nil @@ -23,10 +35,32 @@ @io.pos.should.eql?(1) end + it "decreases the current position by the size of a multibyte character" do + @io.pos = 2 + @io.ungetc("φ") + @io.pos.should == 0 + @io.string.should == "φ34" + end + + it "writes the given string completely when the current position does not have enough space" do + @io.pos = 2 + @io.ungetc("foo") + @io.pos.should == 0 + @io.string.should == "foo34" + end + it "pads with \\000 when the current position is after the end" do @io.pos = 15 @io.ungetc(?A) @io.string.should == "1234\000\000\000\000\000\000\000\000\000\000A" + @io.pos.should == 14 + end + + it "pads with \\000 when the current position is after the end for a multibyte character" do + @io.pos = 15 + @io.ungetc("φ") + @io.string.should == "1234\000\000\000\000\000\000\000\000\000φ" + @io.pos.should == 13 end it "tries to convert the passed argument to an String using #to_str" do diff --git a/spec/ruby/optional/capi/array_spec.rb b/spec/ruby/optional/capi/array_spec.rb index 0b997f774ca470..ad0eb56741efdb 100644 --- a/spec/ruby/optional/capi/array_spec.rb +++ b/spec/ruby/optional/capi/array_spec.rb @@ -343,7 +343,7 @@ end end - ruby_version_is ""..."4.0" do + ruby_version_is ""..."4.1" do describe "rb_iterate" do it "calls an callback function as a block passed to an method" do s = [1,2,3,4] @@ -376,6 +376,10 @@ def o.each s2.should == [1,2,3,4] end + + it "passes non-Ruby pointers to the iteration and block callbacks" do + @s.rb_iterate_with_pointer.should == [14] + end end end diff --git a/spec/ruby/optional/capi/ext/array_spec.c b/spec/ruby/optional/capi/ext/array_spec.c index 628c4df9d74a60..164c9063eed27f 100644 --- a/spec/ruby/optional/capi/ext/array_spec.c +++ b/spec/ruby/optional/capi/ext/array_spec.c @@ -196,7 +196,7 @@ static VALUE copy_ary(RB_BLOCK_CALL_FUNC_ARGLIST(el, new_ary)) { return rb_ary_push(new_ary, el); } -#ifndef RUBY_VERSION_IS_4_0 +#ifndef RUBY_VERSION_IS_4_1 static VALUE array_spec_rb_iterate(VALUE self, VALUE ary) { VALUE new_ary = rb_ary_new(); @@ -218,7 +218,7 @@ static VALUE sub_pair(RB_BLOCK_CALL_FUNC_ARGLIST(el, holder)) { return rb_ary_push(holder, rb_ary_entry(el, 1)); } -#ifndef RUBY_VERSION_IS_4_0 +#ifndef RUBY_VERSION_IS_4_1 static VALUE each_pair(VALUE obj) { return rb_funcall(obj, rb_intern("each_pair"), 0); } @@ -240,12 +240,52 @@ static VALUE array_spec_rb_block_call_each_pair(VALUE self, VALUE obj) { return new_ary; } +#ifndef RUBY_VERSION_IS_4_1 +struct rb_iterate_pointer_data { + int magic; + VALUE array; +}; + +static VALUE pointer_iter(VALUE value) { + struct rb_iterate_pointer_data *data = (struct rb_iterate_pointer_data *)value; + VALUE array; + + if (data->magic != 0x1234) { + rb_raise(rb_eRuntimeError, "invalid iteration pointer"); + } + + if (rb_block_given_p()) { + rb_yield(INT2FIX(14)); + return Qnil; + } else { + array = rb_ary_new(); + rb_ary_push(array, INT2FIX(14)); + return rb_funcall(array, rb_intern("each"), 0); + } +} + +static VALUE pointer_block(RB_BLOCK_CALL_FUNC_ARGLIST(el, value)) { + struct rb_iterate_pointer_data *data = (struct rb_iterate_pointer_data *)value; + if (data->magic != 0x1234) { + rb_raise(rb_eRuntimeError, "invalid block pointer"); + } + + return rb_ary_push(data->array, el); +} + +static VALUE array_spec_rb_iterate_with_pointer(VALUE self) { + struct rb_iterate_pointer_data data = { 0x1234, rb_ary_new() }; + rb_iterate(pointer_iter, (VALUE)&data, pointer_block, (VALUE)&data); + return data.array; +} +#endif + static VALUE iter_yield(RB_BLOCK_CALL_FUNC_ARGLIST(el, ary)) { rb_yield(el); return Qnil; } -#ifndef RUBY_VERSION_IS_4_0 +#ifndef RUBY_VERSION_IS_4_1 static VALUE array_spec_rb_iterate_then_yield(VALUE self, VALUE obj) { rb_iterate(rb_each, obj, iter_yield, obj); return Qnil; @@ -314,10 +354,11 @@ void Init_array_spec(void) { rb_define_method(cls, "rb_ary_plus", array_spec_rb_ary_plus, 2); rb_define_method(cls, "rb_ary_unshift", array_spec_rb_ary_unshift, 2); rb_define_method(cls, "rb_assoc_new", array_spec_rb_assoc_new, 2); -#ifndef RUBY_VERSION_IS_4_0 +#ifndef RUBY_VERSION_IS_4_1 rb_define_method(cls, "rb_iterate", array_spec_rb_iterate, 1); rb_define_method(cls, "rb_iterate_each_pair", array_spec_rb_iterate_each_pair, 1); rb_define_method(cls, "rb_iterate_then_yield", array_spec_rb_iterate_then_yield, 1); + rb_define_method(cls, "rb_iterate_with_pointer", array_spec_rb_iterate_with_pointer, 0); #endif rb_define_method(cls, "rb_block_call", array_spec_rb_block_call, 1); rb_define_method(cls, "rb_block_call_each_pair", array_spec_rb_block_call_each_pair, 1); diff --git a/spec/ruby/optional/capi/ext/fiber_spec.c b/spec/ruby/optional/capi/ext/fiber_spec.c index db54f7ad8c6c92..ef2dcbc35a6f15 100644 --- a/spec/ruby/optional/capi/ext/fiber_spec.c +++ b/spec/ruby/optional/capi/ext/fiber_spec.c @@ -44,6 +44,25 @@ VALUE fiber_spec_rb_fiber_new(VALUE self) { return rb_fiber_new(fiber_spec_rb_fiber_new_function, Qnil); } +struct rb_fiber_new_pointer_data { + int magic; +}; + +VALUE fiber_spec_rb_fiber_new_pointer_function(RB_BLOCK_CALL_FUNC_ARGLIST(args, value)) { + struct rb_fiber_new_pointer_data *data = (struct rb_fiber_new_pointer_data *)value; + if (data->magic != 0x1234) { + rb_raise(rb_eRuntimeError, "invalid fiber pointer"); + } + + return Qtrue; +} + +VALUE fiber_spec_rb_fiber_new_with_pointer(VALUE self) { + struct rb_fiber_new_pointer_data data = { 0x1234 }; + VALUE fiber = rb_fiber_new(fiber_spec_rb_fiber_new_pointer_function, (VALUE)&data); + return rb_fiber_resume(fiber, 0, NULL); +} + VALUE fiber_spec_rb_fiber_raise(int argc, VALUE *argv, VALUE self) { VALUE fiber = argv[0]; return rb_fiber_raise(fiber, argc-1, argv+1); @@ -56,6 +75,7 @@ void Init_fiber_spec(void) { rb_define_method(cls, "rb_fiber_resume", fiber_spec_rb_fiber_resume, 2); rb_define_method(cls, "rb_fiber_yield", fiber_spec_rb_fiber_yield, 1); rb_define_method(cls, "rb_fiber_new", fiber_spec_rb_fiber_new, 0); + rb_define_method(cls, "rb_fiber_new_with_pointer", fiber_spec_rb_fiber_new_with_pointer, 0); rb_define_method(cls, "rb_fiber_raise", fiber_spec_rb_fiber_raise, -1); } diff --git a/spec/ruby/optional/capi/ext/io_spec.c b/spec/ruby/optional/capi/ext/io_spec.c index fe31cffb495241..5cf96ecc0eb2d1 100644 --- a/spec/ruby/optional/capi/ext/io_spec.c +++ b/spec/ruby/optional/capi/ext/io_spec.c @@ -35,6 +35,10 @@ VALUE io_spec_GetOpenFile_fd(VALUE self, VALUE io) { return INT2NUM(io_spec_get_fd(io)); } +VALUE io_spec_rb_io_get_io(VALUE self, VALUE io) { + return rb_io_get_io(io); +} + VALUE io_spec_rb_io_addstr(VALUE self, VALUE io, VALUE str) { return rb_io_addstr(io, str); } @@ -366,6 +370,10 @@ static VALUE io_spec_rb_io_open_descriptor_without_encoding(VALUE self, VALUE kl return rb_io_open_descriptor(klass, FIX2INT(descriptor), FIX2INT(mode), path, timeout, NULL); } +static VALUE io_spec_rb_eIOTimeoutError(VALUE self) { + return rb_eIOTimeoutError; +} + void Init_io_spec(void) { VALUE cls = rb_define_class("CApiIOSpecs", rb_cObject); rb_define_method(cls, "GetOpenFile_fd", io_spec_GetOpenFile_fd, 1); @@ -379,6 +387,7 @@ void Init_io_spec(void) { rb_define_method(cls, "rb_io_check_readable", io_spec_rb_io_check_readable, 1); rb_define_method(cls, "rb_io_check_writable", io_spec_rb_io_check_writable, 1); rb_define_method(cls, "rb_io_check_closed", io_spec_rb_io_check_closed, 1); + rb_define_method(cls, "rb_io_get_io", io_spec_rb_io_get_io, 1); rb_define_method(cls, "rb_io_set_nonblock", io_spec_rb_io_set_nonblock, 1); rb_define_method(cls, "rb_io_taint_check", io_spec_rb_io_taint_check, 1); rb_define_method(cls, "rb_io_wait_readable", io_spec_rb_io_wait_readable, 2); @@ -404,6 +413,7 @@ void Init_io_spec(void) { rb_define_method(cls, "rb_io_closed_p", io_spec_rb_io_closed_p, 1); rb_define_method(cls, "rb_io_open_descriptor", io_spec_rb_io_open_descriptor, 9); rb_define_method(cls, "rb_io_open_descriptor_without_encoding", io_spec_rb_io_open_descriptor_without_encoding, 5); + rb_define_method(cls, "rb_eIOTimeoutError", io_spec_rb_eIOTimeoutError, 0); rb_define_const(cls, "FMODE_READABLE", INT2FIX(FMODE_READABLE)); rb_define_const(cls, "FMODE_WRITABLE", INT2FIX(FMODE_WRITABLE)); rb_define_const(cls, "FMODE_BINMODE", INT2FIX(FMODE_BINMODE)); diff --git a/spec/ruby/optional/capi/ext/kernel_spec.c b/spec/ruby/optional/capi/ext/kernel_spec.c index eee324052d0936..6384aee36e221c 100644 --- a/spec/ruby/optional/capi/ext/kernel_spec.c +++ b/spec/ruby/optional/capi/ext/kernel_spec.c @@ -114,6 +114,24 @@ VALUE kernel_spec_rb_catch_obj(VALUE self, VALUE obj, VALUE main_proc) { return rb_catch_obj(obj, kernel_spec_call_proc_with_catch_obj, main_proc); } +struct rb_catch_obj_pointer_data { + int magic; +}; + +VALUE kernel_spec_catch_obj_pointer(RB_BLOCK_CALL_FUNC_ARGLIST(arg, data)) { + struct rb_catch_obj_pointer_data *pointer_data = (struct rb_catch_obj_pointer_data *)data; + if (pointer_data->magic != 0x1234) { + rb_raise(rb_eRuntimeError, "invalid catch pointer"); + } + + return Qtrue; +} + +VALUE kernel_spec_rb_catch_obj_with_pointer(VALUE self, VALUE obj) { + struct rb_catch_obj_pointer_data data = { 0x1234 }; + return rb_catch_obj(obj, kernel_spec_catch_obj_pointer, (VALUE)&data); +} + VALUE kernel_spec_rb_eval_string(VALUE self, VALUE str) { return rb_eval_string(RSTRING_PTR(str)); } @@ -329,6 +347,29 @@ static VALUE kernel_spec_rb_exec_recursive(VALUE self, VALUE obj) { return rb_exec_recursive(do_rec, obj, Qtrue); } +struct rb_exec_recursive_pointer_data { + int magic; +}; + +static VALUE do_rec_pointer(VALUE obj, VALUE arg, int is_rec) { + struct rb_exec_recursive_pointer_data *obj_data = (struct rb_exec_recursive_pointer_data *)obj; + struct rb_exec_recursive_pointer_data *arg_data = (struct rb_exec_recursive_pointer_data *)arg; + if (obj_data->magic != 0x1234 || arg_data->magic != 0x1234) { + rb_raise(rb_eRuntimeError, "invalid recursive pointer"); + } + + if (is_rec) { + return Qtrue; + } else { + return rb_exec_recursive(do_rec_pointer, obj, arg); + } +} + +static VALUE kernel_spec_rb_exec_recursive_with_pointer(VALUE self) { + struct rb_exec_recursive_pointer_data data = { 0x1234 }; + return rb_exec_recursive(do_rec_pointer, (VALUE)&data, (VALUE)&data); +} + static void write_io(VALUE io) { rb_funcall(io, rb_intern("write"), 1, rb_str_new2("in write_io")); } @@ -432,6 +473,7 @@ void Init_kernel_spec(void) { rb_define_method(cls, "rb_eval_string_protect", kernel_spec_rb_eval_string_protect, 2); rb_define_method(cls, "rb_catch", kernel_spec_rb_catch, 2); rb_define_method(cls, "rb_catch_obj", kernel_spec_rb_catch_obj, 2); + rb_define_method(cls, "rb_catch_obj_with_pointer", kernel_spec_rb_catch_obj_with_pointer, 1); rb_define_method(cls, "rb_sys_fail", kernel_spec_rb_sys_fail, 1); rb_define_method(cls, "rb_syserr_fail", kernel_spec_rb_syserr_fail, 2); rb_define_method(cls, "rb_syserr_fail_str", kernel_spec_rb_syserr_fail_str, 2); @@ -443,6 +485,7 @@ void Init_kernel_spec(void) { rb_define_method(cls, "rb_yield_values2", kernel_spec_rb_yield_values2, 1); rb_define_method(cls, "rb_yield_splat", kernel_spec_rb_yield_splat, 1); rb_define_method(cls, "rb_exec_recursive", kernel_spec_rb_exec_recursive, 1); + rb_define_method(cls, "rb_exec_recursive_with_pointer", kernel_spec_rb_exec_recursive_with_pointer, 0); rb_define_method(cls, "rb_set_end_proc", kernel_spec_rb_set_end_proc, 1); rb_define_method(cls, "ruby_vm_at_exit", kernel_spec_ruby_vm_at_exit, 0); rb_define_method(cls, "rb_f_sprintf", kernel_spec_rb_f_sprintf, 1); diff --git a/spec/ruby/optional/capi/ext/proc_spec.c b/spec/ruby/optional/capi/ext/proc_spec.c index b7cd5d62620034..f836be59d94607 100644 --- a/spec/ruby/optional/capi/ext/proc_spec.c +++ b/spec/ruby/optional/capi/ext/proc_spec.c @@ -60,6 +60,25 @@ VALUE proc_spec_rb_proc_new_callback_arg(VALUE self, VALUE arg) { return rb_proc_new(proc_spec_rb_proc_new_function_callback_arg, arg); } +struct rb_proc_new_pointer_data { + int magic; +}; + +VALUE proc_spec_rb_proc_new_function_pointer(VALUE arg, VALUE callback_arg, int argc, const VALUE *argv, VALUE blockarg) { + struct rb_proc_new_pointer_data *data = (struct rb_proc_new_pointer_data *)callback_arg; + if (data->magic != 0x1234) { + rb_raise(rb_eRuntimeError, "invalid proc pointer"); + } + + return Qtrue; +} + +VALUE proc_spec_rb_proc_new_with_pointer(VALUE self) { + struct rb_proc_new_pointer_data data = { 0x1234 }; + VALUE proc = rb_proc_new(proc_spec_rb_proc_new_function_pointer, (VALUE)&data); + return rb_funcall(proc, rb_intern("call"), 0); +} + VALUE proc_spec_rb_proc_new_blockarg(VALUE self) { return rb_proc_new(proc_spec_rb_proc_new_function_blockarg, Qnil); } @@ -131,6 +150,7 @@ void Init_proc_spec(void) { rb_define_method(cls, "rb_proc_new_argc", proc_spec_rb_proc_new_argc, 0); rb_define_method(cls, "rb_proc_new_argv_n", proc_spec_rb_proc_new_argv_n, 0); rb_define_method(cls, "rb_proc_new_callback_arg", proc_spec_rb_proc_new_callback_arg, 1); + rb_define_method(cls, "rb_proc_new_with_pointer", proc_spec_rb_proc_new_with_pointer, 0); rb_define_method(cls, "rb_proc_new_blockarg", proc_spec_rb_proc_new_blockarg, 0); rb_define_method(cls, "rb_proc_new_block_given_p", proc_spec_rb_proc_new_block_given_p, 0); rb_define_method(cls, "rb_proc_arity", proc_spec_rb_proc_arity, 1); diff --git a/spec/ruby/optional/capi/ext/rbasic_spec.c b/spec/ruby/optional/capi/ext/rbasic_spec.c index 5a95b92804dc2d..892d3258c0b539 100644 --- a/spec/ruby/optional/capi/ext/rbasic_spec.c +++ b/spec/ruby/optional/capi/ext/rbasic_spec.c @@ -13,13 +13,8 @@ extern "C" { #define RBASIC_SET_FLAGS(obj, flags_to_set) (RBASIC(obj)->flags = flags_to_set) #endif -#ifndef FL_SHAREABLE -static const VALUE VISIBLE_BITS = FL_TAINT | FL_FREEZE; -static const VALUE DATA_VISIBLE_BITS = FL_TAINT | FL_FREEZE | ~(FL_USER0 - 1); -#else static const VALUE VISIBLE_BITS = FL_FREEZE; static const VALUE DATA_VISIBLE_BITS = FL_FREEZE | ~(FL_USER0 - 1); -#endif #if SIZEOF_VALUE == SIZEOF_LONG #define VALUE2NUM(v) ULONG2NUM(v) diff --git a/spec/ruby/optional/capi/fiber_spec.rb b/spec/ruby/optional/capi/fiber_spec.rb index c820ba17c2f6ae..d19e85edd70d30 100644 --- a/spec/ruby/optional/capi/fiber_spec.rb +++ b/spec/ruby/optional/capi/fiber_spec.rb @@ -46,6 +46,10 @@ fiber.should.instance_of?(Fiber) fiber.resume(42).should == "42" end + + it "passes non-Ruby pointers to the fiber callback" do + @s.rb_fiber_new_with_pointer.should == true + end end describe "rb_fiber_raise" do diff --git a/spec/ruby/optional/capi/io_spec.rb b/spec/ruby/optional/capi/io_spec.rb index 459a32d954b3f2..b7517f7f322ff6 100644 --- a/spec/ruby/optional/capi/io_spec.rb +++ b/spec/ruby/optional/capi/io_spec.rb @@ -190,6 +190,45 @@ end end + describe "rb_io_get_io" do + it "returns the passed object and does not call #to_io if the object is already an IO" do + @io.should_not_receive(:to_io) + + @o.rb_io_get_io(@io).should.equal?(@io) + end + + it "returns the passed object and does not call #to_io if the object is a subclass of IO" do + file = File.open(@name) + + begin + file.should_not_receive(:to_io) + + @o.rb_io_get_io(file).should.equal?(file) + ensure + file.close + end + end + + it "calls #to_io to convert the object to an IO" do + wrapper = Object.new + io = @io + wrapper.define_singleton_method(:to_io) { io } + + @o.rb_io_get_io(wrapper).should.equal?(@io) + end + + it "raises a TypeError if #to_io does not return an IO" do + wrapper = Object.new + wrapper.define_singleton_method(:to_io) { Object.new } + + -> { @o.rb_io_get_io(wrapper) }.should.raise(TypeError) + end + + it "raises a TypeError if the object does not respond to #to_io" do + -> { @o.rb_io_get_io(Object.new) }.should.raise(TypeError) + end + end + describe "rb_io_binmode" do it "returns self" do @o.rb_io_binmode(@io).should == @io @@ -280,6 +319,36 @@ -> { @o.rb_io_maybe_wait_writable(0, IO.allocate, nil) }.should.raise(IOError, "uninitialized stream") end + ruby_version_is "3.4" do + platform_is_not :windows do + it "raises a IO::TimeoutError if the timeout elapses" do + IOSpec.exhaust_write_buffer(@w_io) + -> { @o.rb_io_maybe_wait_writable(Errno::EAGAIN::Errno, @w_io, 0) }. + should.raise(IO::TimeoutError, "Timed out waiting for IO to become writable!") + end + end + + platform_is :windows do + # Windows select/poll wrapper (rb_w32_select) treats write descriptors of non-sockets + # (such as pipe writers) as always writable. Thus it immediately returns IO::WRITABLE + # instead of timing out. So use sockets instead. + it "raises a IO::TimeoutError if the timeout elapses" do + require 'socket' + r_sock, w_sock = Socket.pair(Socket::AF_INET, Socket::SOCK_STREAM, 0) + begin + r_sock.close_write + w_sock.close_read + IOSpec.exhaust_write_buffer(w_sock) + -> { @o.rb_io_maybe_wait_writable(Errno::EAGAIN::Errno, w_sock, 0) }. + should.raise(IO::TimeoutError, "Timed out waiting for IO to become writable!") + ensure + r_sock.close unless r_sock.closed? + w_sock.close unless w_sock.closed? + end + end + end + end + it "can be interrupted" do IOSpec.exhaust_write_buffer(@w_io) start = Process.clock_gettime(Process::CLOCK_MONOTONIC) @@ -392,6 +461,13 @@ it "raises an IOError if the IO is not initialized" do -> { @o.rb_io_maybe_wait_readable(0, IO.allocate, nil, false) }.should.raise(IOError, "uninitialized stream") end + + ruby_version_is "3.4" do + it "raises a IO::TimeoutError if the timeout elapses" do + -> { @o.rb_io_maybe_wait_readable(Errno::EAGAIN::Errno, @r_io, 0, false) }. + should.raise(IO::TimeoutError, "Timed out waiting for IO to become readable!") + end + end end end @@ -785,4 +861,8 @@ def path.to_str; "a.txt"; end @o.rb_io_mode_sync_flag(io).should == false } end + + specify "rb_eIOTimeoutError references the IO::TimeoutError class" do + @o.rb_eIOTimeoutError.should == IO::TimeoutError + end end diff --git a/spec/ruby/optional/capi/kernel_spec.rb b/spec/ruby/optional/capi/kernel_spec.rb index 62eb21448b24ca..2fee0c292098c8 100644 --- a/spec/ruby/optional/capi/kernel_spec.rb +++ b/spec/ruby/optional/capi/kernel_spec.rb @@ -525,6 +525,10 @@ def proc_caller @s.rb_catch_obj(@tag, -> { 1 }).should == 1 end + it "passes non-Ruby pointers to the catch callback" do + @s.rb_catch_obj_with_pointer(@tag).should == true + end + it "terminates the function at the point it was called" do proc = -> do ScratchPad << :before_throw @@ -695,6 +699,10 @@ def proc_caller s = "hello" @s.rb_exec_recursive(s).should == s end + + it "passes non-Ruby pointers to the recursive callback" do + @s.rb_exec_recursive_with_pointer.should == true + end end describe "rb_set_end_proc" do diff --git a/spec/ruby/optional/capi/object_spec.rb b/spec/ruby/optional/capi/object_spec.rb index 8e907a5a8ca50b..298aad389138a4 100644 --- a/spec/ruby/optional/capi/object_spec.rb +++ b/spec/ruby/optional/capi/object_spec.rb @@ -652,15 +652,6 @@ def reach end end - describe "OBJ_TAINT" do - end - - describe "OBJ_TAINTED" do - end - - describe "OBJ_INFECT" do - end - describe "rb_obj_freeze" do it "freezes the object passed to it" do obj = "" @@ -701,9 +692,6 @@ def reach end end - describe "rb_obj_taint" do - end - describe "rb_check_frozen" do it "raises a FrozenError if the obj is frozen" do -> { @o.rb_check_frozen("".freeze) }.should.raise(FrozenError) diff --git a/spec/ruby/optional/capi/proc_spec.rb b/spec/ruby/optional/capi/proc_spec.rb index 1c250fee761167..c66af484ae5a33 100644 --- a/spec/ruby/optional/capi/proc_spec.rb +++ b/spec/ruby/optional/capi/proc_spec.rb @@ -39,6 +39,10 @@ -> { @prc2.call(3, :foo, :bar) }.should.raise(ArgumentError) end + it "passes non-Ruby pointers to the proc callback" do + @p.rb_proc_new_with_pointer.should == true + end + it "calls the C function with the block passed in blockarg" do a_block = :foo.to_proc @p.rb_proc_new_blockarg.call(&a_block).should == a_block diff --git a/spec/ruby/security/cve_2018_16396_spec.rb b/spec/ruby/security/cve_2018_16396_spec.rb deleted file mode 100644 index 6f8a5d2f5d6030..00000000000000 --- a/spec/ruby/security/cve_2018_16396_spec.rb +++ /dev/null @@ -1,7 +0,0 @@ -require_relative '../spec_helper' - -describe "Array#pack" do -end - -describe "String#unpack" do -end diff --git a/spec/ruby/shared/basicobject/send.rb b/spec/ruby/shared/basicobject/send.rb index d38aea975e3be2..711848e68ae5b6 100644 --- a/spec/ruby/shared/basicobject/send.rb +++ b/spec/ruby/shared/basicobject/send.rb @@ -125,4 +125,10 @@ def foo(first = true) first end c2.new.send(@method, :foo, *[[]]).should == %i[m1 m2] end + + it "can be called reflectively" do + obj = Object.new + obj.method(@method).call(:itself).should.equal?(obj) + obj.method(@method).to_proc.call(:itself).should.equal?(obj) + end end diff --git a/spec/ruby/shared/kernel/fixtures/classes.rb b/spec/ruby/shared/kernel/fixtures/classes.rb new file mode 100644 index 00000000000000..81d6811ed656f8 --- /dev/null +++ b/spec/ruby/shared/kernel/fixtures/classes.rb @@ -0,0 +1,9 @@ +module KernelSpecs + module RaiseSpecs + class UniqueClass + def self.with_raise + yield + end + end + end +end diff --git a/spec/ruby/shared/kernel/raise.rb b/spec/ruby/shared/kernel/raise.rb index 04eaa94e6aa476..cdbbf931a977e1 100644 --- a/spec/ruby/shared/kernel/raise.rb +++ b/spec/ruby/shared/kernel/raise.rb @@ -1,3 +1,5 @@ +require_relative 'fixtures/classes' + describe :kernel_raise, shared: true do before :each do ScratchPad.clear @@ -139,6 +141,42 @@ def e.exception raised_exception.should == exception end + it "re-raises a previously rescued exception with cleaned backtrace and sets a new backtrace" do + begin + raise "raised" + rescue => exception + # ignore + end + + exception.set_backtrace(nil) + + # To get a uniform backtrace check across Kernel, Thread, and Fiber: + # - Kernel#raise raises directly in the outer block. + # - Fiber/Thread wrappers (NewFiberToRaise, NewThreadToRaise) yield/sleep + # inside the inner block in their own context. + # In both cases, the backtrace contains the UniqueClass.with_raise frame. + begin + KernelSpecs::RaiseSpecs::UniqueClass.with_raise do + @object.raise(exception) do |&pause| + KernelSpecs::RaiseSpecs::UniqueClass.with_raise do + pause.call + end + end + end + rescue => reraised_exception + # ignore + end + + reraised_exception.should == exception + reraised_exception.backtrace.should.is_a?(Array) + + ruby_version_is "4.0" do + reraised_exception.backtrace.should.any? { |line| + line.include?("KernelSpecs::RaiseSpecs::UniqueClass.with_raise") + } + end + end + it "allows Exception, message, and backtrace parameters" do -> do @object.raise(ArgumentError, "message", caller) From 09a9bdeb3241d6ac395610329f17561246b0d6b8 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Tue, 7 Jul 2026 22:02:17 +0200 Subject: [PATCH 4/8] Temporarily disable incompatible override of Array#find in YJIT & ZJIT --- array.rb | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/array.rb b/array.rb index 7ee4e09a4c00eb..b2965589325635 100644 --- a/array.rb +++ b/array.rb @@ -285,23 +285,24 @@ def select # :nodoc: end end - if Primitive.rb_builtin_basic_definition_p(:find) - undef :find + # TODO: Temporarily disabled because it is not compatible and fails multiple spec/ruby/core/array specs + # if Primitive.rb_builtin_basic_definition_p(:find) + # undef :find - def find(if_none_proc = nil) # :nodoc: - Primitive.attr! :inline_block, :c_trace, :without_interrupts + # def find(if_none_proc = nil) # :nodoc: + # Primitive.attr! :inline_block, :c_trace, :without_interrupts - unless defined?(yield) - return Primitive.cexpr! 'SIZED_ENUMERATOR(self, 0, 0, ary_enum_length)' - end - i = 0 - until Primitive.rb_jit_ary_at_end(i) - value = Primitive.rb_jit_ary_at(i) - return value if yield(value) - i = Primitive.rb_jit_fixnum_inc(i) - end - if_none_proc&.call - end - end + # unless defined?(yield) + # return Primitive.cexpr! 'SIZED_ENUMERATOR(self, 0, 0, ary_enum_length)' + # end + # i = 0 + # until Primitive.rb_jit_ary_at_end(i) + # value = Primitive.rb_jit_ary_at(i) + # return value if yield(value) + # i = Primitive.rb_jit_fixnum_inc(i) + # end + # if_none_proc&.call + # end + # end end end From c051e88b52209676d265d9a6fc269a134452351d Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Mon, 29 Jun 2026 18:08:40 -0400 Subject: [PATCH 5/8] ZJIT: Box SendDirect data --- zjit/src/codegen.rs | 13 +++++--- zjit/src/hir.rs | 74 ++++++++++++++++++++++++++++----------------- 2 files changed, 55 insertions(+), 32 deletions(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 4df06236bbc0b3..0c8c1ebea30131 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -23,7 +23,7 @@ use crate::stats::{counter_ptr, with_time_stat, trace_compile_phase, Counter, Co use crate::{asm::CodeBlock, cruby::*, options::debug, virtualmem::CodePtr}; use crate::backend::lir::{self, Assembler, C_ARG_OPNDS, C_RET_OPND, CFP, EC, NATIVE_BASE_PTR, Opnd, SP, SideExit, SideExitRecompile, SideExitTarget, Target, asm_ccall, asm_comment}; use crate::hir::{iseq_to_hir, BlockId, Invariant, RangeType, SideExitReason::{self, *}, SpecialBackrefSymbol, SpecialObjectType}; -use crate::hir::{BlockHandler, CCallVariadicData, CCallWithFrameData, Const, FieldName, FrameState, Function, Insn, InsnId, Recompile, SendFallbackReason}; +use crate::hir::{BlockHandler, CCallVariadicData, CCallWithFrameData, Const, FieldName, FrameState, Function, Insn, InsnId, Recompile, SendDirectData, SendFallbackReason}; use crate::hir_type::{types, Type}; use crate::options::{get_option, InlineDepth, PerfMap, DEFAULT_MAX_VERSIONS}; use crate::cast::IntoUsize; @@ -644,10 +644,13 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio &Insn::Send { cd, block: Some(BlockHandler::BlockIseq(blockiseq)), state, reason, .. } => gen_send(jit, asm, cd, blockiseq, &function.frame_state(state), reason), &Insn::Send { cd, block: Some(BlockHandler::BlockArg), state, reason, .. } => gen_send(jit, asm, cd, std::ptr::null(), &function.frame_state(state), reason), &Insn::SendForward { cd, blockiseq, state, reason, .. } => gen_send_forward(jit, asm, cd, blockiseq, &function.frame_state(state), reason), - &Insn::SendDirect { cme, iseq, recv, ref args, kw_bits, block, state, .. } => gen_send_iseq_direct( - cb, jit, asm, cme, iseq, opnd!(recv), opnds!(args), - kw_bits, &function.frame_state(state), block, - ), + Insn::SendDirect(insn) => { + let SendDirectData { cme, iseq, recv, args, kw_bits, block, state, .. } = &**insn; + gen_send_iseq_direct( + cb, jit, asm, *cme, *iseq, opnd!(recv), opnds!(args), + *kw_bits, &function.frame_state(*state), *block, + ) + } Insn::PushInlineFrame { cme, iseq, recv, args, blockiseq, state, .. } => { no_output!(gen_push_inline_frame(jit, asm, *cme, *iseq, opnd!(recv), opnds!(args), &function.frame_state(*state), *blockiseq)) }, diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 4d9fcc7157cb46..a0a2ee4b7f403b 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -856,6 +856,19 @@ pub struct CCallWithFrameData { pub block: Option, } +/// Payload of [`Insn::SendDirect`]. Boxed in the enum to keep `Insn` small. +#[derive(Debug, Clone)] +pub struct SendDirectData { + pub recv: InsnId, + pub cd: *const rb_call_data, + pub cme: *const rb_callable_method_entry_t, + pub iseq: IseqPtr, + pub args: Vec, + pub kw_bits: u32, + pub block: Option, + pub state: InsnId, +} + /// Payload of [`Insn::CCallVariadic`]. Boxed in the enum to keep `Insn` small. #[derive(Debug, Clone)] pub struct CCallVariadicData { @@ -1106,16 +1119,7 @@ pub enum Insn { }, /// Optimized ISEQ call - SendDirect { - recv: InsnId, - cd: *const rb_call_data, - cme: *const rb_callable_method_entry_t, - iseq: IseqPtr, - args: Vec, - kw_bits: u32, - block: Option, - state: InsnId, - }, + SendDirect(Box), /// Push a lighter weight frame used for inlined methods. PushInlineFrame { @@ -1448,7 +1452,6 @@ macro_rules! for_each_operand_impl { } Insn::Send { recv, args, state, .. } | Insn::SendForward { recv, args, state, .. } - | Insn::SendDirect { recv, args, state, .. } | Insn::PushInlineFrame { recv, args, state, .. } | Insn::InvokeBuiltin { recv, args, state, .. } | Insn::InvokeSuper { recv, args, state, .. } @@ -1458,9 +1461,14 @@ macro_rules! for_each_operand_impl { $visit_many!(args); $visit_one!(*state); } - // CCallWithFrame/CCallVariadic carry their operands behind a Box, which - // stable Rust can't destructure in a pattern. visit_one takes a place, so + // SendDirect/CCallWithFrame/CCallVariadic carry their operands behind a Box, + // which stable Rust can't destructure in a pattern. visit_one takes a place, so // a box field works the same as the deref'd bindings used by other arms. + Insn::SendDirect(insn) => { + $visit_one!(insn.recv); + $visit_many!(insn.args); + $visit_one!(insn.state); + } Insn::CCallWithFrame(insn) => { $visit_one!(insn.recv); $visit_many!(insn.args); @@ -1727,7 +1735,7 @@ impl Insn { Insn::InvokeSuperForward { .. } => effects::Any, Insn::InvokeBlock { .. } => effects::Any, Insn::InvokeBlockIfunc { .. } => effects::Any, - Insn::SendDirect { .. } => effects::Any, + Insn::SendDirect(_) => effects::Any, // TODO (nirvdrum 2026-05-28): Revisit when PushInlineFrame is // actually lightweight. The frame writes here pay for the spill // ceremony in the current full frame-push codegen. A lightweight @@ -2042,7 +2050,8 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::FixnumAref { recv, index } => write!(f, "FixnumAref {recv}, {index}"), Insn::Jump(target) => { write!(f, "Jump {target}") } Insn::CondBranch { val, if_true, if_false } => { write!(f, "CondBranch {val}, {if_true}, {if_false}") }, - Insn::SendDirect { recv, cme, iseq, args, block, .. } => { + Insn::SendDirect(insn) => { + let SendDirectData { recv, cme, iseq, args, block, .. } = &**insn; let blockiseq = block.map(|bh| match bh { BlockHandler::BlockIseq(iseq) => iseq, BlockHandler::BlockArg => unreachable!() }); let method_name = unsafe { (**cme).called_id }; write!(f, "SendDirect {recv}, {:p}, :{} ({:?})", self.ptr_map.map_ptr(&blockiseq), method_name, self.ptr_map.map_ptr(iseq))?; @@ -3113,7 +3122,7 @@ impl Function { Insn::FixnumLShift { .. } => types::Fixnum, Insn::FixnumRShift { .. } => types::Fixnum, Insn::PutSpecialObject { .. } => types::BasicObject, - Insn::SendDirect { .. } => types::BasicObject, + Insn::SendDirect(_) => types::BasicObject, Insn::Send { .. } => types::BasicObject, Insn::SendForward { .. } => types::BasicObject, Insn::InvokeSuper { .. } => types::BasicObject, @@ -3746,9 +3755,14 @@ impl Function { /// Try trivially inlining the method. If we can't, emit a SendDirect instruction instead and /// leave it to the general-purpose inliner to handle. fn try_inline_send_direct(&mut self, block: BlockId, insn: Insn) -> InsnId { - let Insn::SendDirect { recv, iseq, cd, ref args, state, .. } = insn else { + let Insn::SendDirect(data) = &insn else { panic!("try_inline_send_direct called with non-SendDirect instruction"); }; + let recv = data.recv; + let iseq = data.iseq; + let cd = data.cd; + let state = data.state; + let args = &data.args; // The trivial inliner runs first to handle simple cases (constant returns, // parameter returns, etc.) without frame push/pop overhead. The general // inliner then handles more complex methods that require full inlining. @@ -3902,7 +3916,7 @@ impl Function { self.insn_types[recv.0] = self.infer_type(recv); } - let replacement = self.try_inline_send_direct(block, Insn::SendDirect { recv, cd, cme, iseq, args: processed_args, kw_bits, state: send_state, block: send_block }); + let replacement = self.try_inline_send_direct(block, Insn::SendDirect(Box::new(SendDirectData { recv, cd, cme, iseq, args: processed_args, kw_bits, state: send_state, block: send_block }))); self.make_equal_to(insn_id, replacement); } else if !has_block && def_type == VM_METHOD_TYPE_BMETHOD { let procv = unsafe { rb_get_def_bmethod_proc((*cme).def) }; @@ -3945,7 +3959,7 @@ impl Function { recv = self.guard_type_recompile(block, recv, Type::from_profiled_type(profiled_type), state, Recompile); } - let replacement = self.try_inline_send_direct(block, Insn::SendDirect { recv, cd, cme, iseq, args: processed_args, kw_bits, state: send_state, block: None }); + let replacement = self.try_inline_send_direct(block, Insn::SendDirect(Box::new(SendDirectData { recv, cd, cme, iseq, args: processed_args, kw_bits, state: send_state, block: None }))); self.make_equal_to(insn_id, replacement); } else if !has_block && def_type == VM_METHOD_TYPE_IVAR && args.is_empty() { // Check if we're accessing ivars of a Class or Module object as they require single-ractor mode. @@ -4490,7 +4504,7 @@ impl Function { emit_super_call_guards(self, block, super_cme, current_cme, mid, state, frame_state.iseq); // Use SendDirect with the super method's CME and ISEQ. - let replacement = self.try_inline_send_direct(block, Insn::SendDirect { + let replacement = self.try_inline_send_direct(block, Insn::SendDirect(Box::new(SendDirectData { recv, cd, cme: super_cme, @@ -4499,7 +4513,7 @@ impl Function { kw_bits, state: send_state, block: None, - }); + }))); self.make_equal_to(insn_id, replacement); } else if def_type == VM_METHOD_TYPE_CFUNC { @@ -4769,18 +4783,18 @@ impl Function { let mut search_start = 0; loop { let Some(offset) = self.blocks[block.0].insns[search_start..].iter() - .position(|&id| matches!(self.find(id), Insn::SendDirect { .. })) + .position(|&id| matches!(self.find(id), Insn::SendDirect(..))) else { break; }; let send_pos = search_start + offset; let send_insn_id = self.blocks[block.0].insns[send_pos]; - let Insn::SendDirect { recv, cd: _, cme, iseq, args, kw_bits, block: call_block, state } - = self.find(send_insn_id) + let Insn::SendDirect(data) = self.find(send_insn_id) else { unreachable!("position {send_insn_id} is not a SendDirect"); }; + let SendDirectData { recv, cme, iseq, args, kw_bits, block: call_block, state, .. } = *data; // SendDirect invariant: block is either None or BlockIseq. // BlockArg is rejected upstream during type specialization. let blockiseq: Option = call_block.map(|bh| match bh { @@ -4878,7 +4892,7 @@ impl Function { // pre-Send body, before the PushLightweightFrame and Jump we add // last). let tail = self.blocks[block.0].insns.split_off(send_pos); - debug_assert!(matches!(self.find(tail[0]), Insn::SendDirect { .. })); + debug_assert!(matches!(self.find(tail[0]), Insn::SendDirect(..))); let omitted_opt_num = opt_num - passed_opt_num; let positional_kw_end = lead_num + opt_num + post_num + kw_num; @@ -6477,8 +6491,7 @@ impl Function { self.assert_subtype(insn_id, allow_nil, types::BoolExact) } // Instructions with recv and a Vec of Ruby objects - Insn::SendDirect { recv, ref args, .. } - | Insn::PushInlineFrame { recv, ref args, .. } + Insn::PushInlineFrame { recv, ref args, .. } | Insn::Send { recv, ref args, .. } | Insn::SendForward { recv, ref args, .. } | Insn::InvokeSuper { recv, ref args, .. } @@ -6492,6 +6505,13 @@ impl Function { } Ok(()) } + Insn::SendDirect(insn) => { + self.assert_subtype(insn_id, insn.recv, types::BasicObject)?; + for &arg in &insn.args { + self.assert_subtype(insn_id, arg, types::BasicObject)?; + } + Ok(()) + } Insn::CCallWithFrame(insn) => { self.assert_subtype(insn_id, insn.recv, types::BasicObject)?; for &arg in &insn.args { From c568aae043f0f0c5f12dfebfa15ac3aefeaab897 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Mon, 29 Jun 2026 18:33:25 -0400 Subject: [PATCH 6/8] ZJIT: Remove Option from InvokeBuiltin We have a reasonable default of `BasicObject` so don't bloat by another 8 bytes with `Option`. --- zjit/src/cruby_methods.rs | 4 ++++ zjit/src/hir.rs | 17 +++++++---------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index fec3b3db5ec284..685208dd7d444c 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -71,6 +71,10 @@ impl Annotations { let func_ptr = unsafe { (*bf).func_ptr as *mut c_void }; self.builtin_funcs.get(&func_ptr).copied() } + + pub fn get_builtin_return_type(&self, bf: *const rb_builtin_function) -> Type { + self.get_builtin_properties(bf).map(|p| p.return_type).unwrap_or(types::BasicObject) + } } fn annotate_c_method(props_map: &mut HashMap<*mut c_void, FnProperties>, class: VALUE, method_name: &'static str, props: FnProperties) { diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index a0a2ee4b7f403b..3bd9aff0cb7830 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -1145,7 +1145,7 @@ pub enum Insn { args: Vec, state: InsnId, leaf: bool, - return_type: Option, // None for unannotated builtins + return_type: Type, // BasicObject for unannotated builtins }, /// Set up frame. Remember the address as the JIT entry for the insn_idx in `jit_entry_insns()[jit_entry_idx]`. @@ -2637,8 +2637,8 @@ enum IseqReturn { Value(VALUE), LocalVariable(u32), Receiver, - // Builtin descriptor and return type (if known) - InvokeLeafBuiltin(*const rb_builtin_function, Option), + // Builtin descriptor and return type + InvokeLeafBuiltin(*const rb_builtin_function, Type), } unsafe extern "C" { @@ -2708,8 +2708,7 @@ fn iseq_get_return_value(iseq: IseqPtr, captured_opnd: Option, ci_flags: if !leaf { return None; } // Check if this builtin is annotated let return_type = ZJITState::get_method_annotations() - .get_builtin_properties(bf) - .map(|props| props.return_type); + .get_builtin_return_type(bf); Some(IseqReturn::InvokeLeafBuiltin(bf, return_type)) } _ => None, @@ -3130,7 +3129,7 @@ impl Function { Insn::InvokeBlock { .. } => types::BasicObject, Insn::InvokeBlockIfunc { .. } => types::BasicObject, Insn::InvokeProc { .. } => types::BasicObject, - Insn::InvokeBuiltin { return_type, .. } => return_type.unwrap_or(types::BasicObject), + Insn::InvokeBuiltin { return_type, .. } => *return_type, Insn::Defined { pushval, .. } => Type::from_value(*pushval).union(types::NilClass), Insn::DefinedIvar { pushval, .. } => Type::from_value(*pushval).union(types::NilClass), Insn::GetConstant { .. } => types::BasicObject, @@ -9040,8 +9039,7 @@ fn add_iseq_to_hir( // Check if this builtin is annotated let return_type = ZJITState::get_method_annotations() - .get_builtin_properties(bf) - .map(|props| props.return_type); + .get_builtin_return_type(bf); let builtin_attrs = unsafe { rb_jit_iseq_builtin_attrs(iseq) }; let leaf = builtin_attrs & BUILTIN_ATTR_LEAF != 0; @@ -9076,8 +9074,7 @@ fn add_iseq_to_hir( // Check if this builtin is annotated let return_type = ZJITState::get_method_annotations() - .get_builtin_properties(bf) - .map(|props| props.return_type); + .get_builtin_return_type(bf); let builtin_attrs = unsafe { rb_jit_iseq_builtin_attrs(iseq) }; let leaf = builtin_attrs & BUILTIN_ATTR_LEAF != 0; From 59e4245a8cbdb98b7d73487d1a2b5626a168b06e Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Mon, 29 Jun 2026 18:54:13 -0400 Subject: [PATCH 7/8] ZJIT: Box SideExitReason Shrink `Insn` from 88 to 80 bytes.
``` print-type-size type: `hir::Insn`: 80 bytes, alignment: 8 bytes print-type-size variant `CondBranch`: 80 bytes print-type-size padding: 8 bytes print-type-size field `.if_true`: 32 bytes, alignment: 8 bytes print-type-size field `.if_false`: 32 bytes print-type-size field `.val`: 8 bytes print-type-size variant `Send`: 80 bytes print-type-size padding: 8 bytes print-type-size field `.block`: 16 bytes, alignment: 8 bytes print-type-size field `.args`: 24 bytes print-type-size field `.reason`: 8 bytes print-type-size field `.recv`: 8 bytes print-type-size field `.cd`: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `PushInlineFrame`: 80 bytes print-type-size padding: 8 bytes print-type-size field `.blockiseq`: 16 bytes, alignment: 8 bytes print-type-size field `.args`: 24 bytes print-type-size field `.iseq`: 8 bytes print-type-size field `.cme`: 8 bytes print-type-size field `.recv`: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `CCall`: 73 bytes print-type-size field `.args`: 24 bytes print-type-size field `.cfunc`: 8 bytes print-type-size field `.recv`: 8 bytes print-type-size field `.name`: 8 bytes print-type-size field `.owner`: 8 bytes print-type-size field `.return_type`: 16 bytes print-type-size field `.elidable`: 1 bytes print-type-size variant `InvokeBuiltin`: 73 bytes print-type-size padding: 8 bytes print-type-size field `.args`: 24 bytes, alignment: 8 bytes print-type-size field `.bf`: 8 bytes print-type-size field `.recv`: 8 bytes print-type-size field `.state`: 8 bytes print-type-size field `.return_type`: 16 bytes print-type-size field `.leaf`: 1 bytes print-type-size variant `SendForward`: 72 bytes print-type-size padding: 8 bytes print-type-size field `.args`: 24 bytes, alignment: 8 bytes print-type-size field `.reason`: 8 bytes print-type-size field `.recv`: 8 bytes print-type-size field `.cd`: 8 bytes print-type-size field `.blockiseq`: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `InvokeSuper`: 72 bytes print-type-size padding: 8 bytes print-type-size field `.args`: 24 bytes, alignment: 8 bytes print-type-size field `.reason`: 8 bytes print-type-size field `.recv`: 8 bytes print-type-size field `.cd`: 8 bytes print-type-size field `.blockiseq`: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `InvokeSuperForward`: 72 bytes print-type-size padding: 8 bytes print-type-size field `.args`: 24 bytes, alignment: 8 bytes print-type-size field `.reason`: 8 bytes print-type-size field `.recv`: 8 bytes print-type-size field `.cd`: 8 bytes print-type-size field `.blockiseq`: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `GuardAnyBitSet`: 65 bytes print-type-size padding: 8 bytes print-type-size field `.mask_name`: 16 bytes, alignment: 8 bytes print-type-size field `.mask`: 16 bytes print-type-size field `.reason`: 8 bytes print-type-size field `.val`: 8 bytes print-type-size field `.state`: 8 bytes print-type-size field `.recompile`: 1 bytes print-type-size variant `ArrayPackBuffer`: 64 bytes print-type-size padding: 8 bytes print-type-size field `.buffer`: 16 bytes, alignment: 8 bytes print-type-size field `.elements`: 24 bytes print-type-size field `.fmt`: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `GuardNoBitsSet`: 64 bytes print-type-size padding: 8 bytes print-type-size field `.mask_name`: 16 bytes, alignment: 8 bytes print-type-size field `.mask`: 16 bytes print-type-size field `.reason`: 8 bytes print-type-size field `.val`: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `InvokeBlock`: 56 bytes print-type-size padding: 8 bytes print-type-size field `.args`: 24 bytes, alignment: 8 bytes print-type-size field `.reason`: 8 bytes print-type-size field `.cd`: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `InvokeBlockIfunc`: 56 bytes print-type-size padding: 8 bytes print-type-size field `.args`: 24 bytes, alignment: 8 bytes print-type-size field `.cd`: 8 bytes print-type-size field `.block_handler`: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `Defined`: 52 bytes print-type-size padding: 8 bytes print-type-size field `.op_type`: 8 bytes, alignment: 8 bytes print-type-size field `.obj`: 8 bytes print-type-size field `.pushval`: 8 bytes print-type-size field `.v`: 8 bytes print-type-size field `.state`: 8 bytes print-type-size field `.lep_level`: 4 bytes print-type-size variant `LoadField`: 52 bytes print-type-size padding: 8 bytes print-type-size field `.id`: 16 bytes, alignment: 8 bytes print-type-size field `.recv`: 8 bytes print-type-size field `.return_type`: 16 bytes print-type-size field `.offset`: 4 bytes print-type-size variant `InvokeProc`: 49 bytes print-type-size padding: 8 bytes print-type-size field `.args`: 24 bytes, alignment: 8 bytes print-type-size field `.recv`: 8 bytes print-type-size field `.state`: 8 bytes print-type-size field `.kw_splat`: 1 bytes print-type-size variant `GuardBitEquals`: 49 bytes print-type-size padding: 8 bytes print-type-size field `.expected`: 16 bytes, alignment: 8 bytes print-type-size field `.reason`: 8 bytes print-type-size field `.val`: 8 bytes print-type-size field `.state`: 8 bytes print-type-size field `.recompile`: 1 bytes print-type-size variant `ToRegexp`: 48 bytes print-type-size padding: 8 bytes print-type-size field `.values`: 24 bytes, alignment: 8 bytes print-type-size field `.opt`: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `ArrayInclude`: 48 bytes print-type-size padding: 8 bytes print-type-size field `.elements`: 24 bytes, alignment: 8 bytes print-type-size field `.target`: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `SetIvar`: 48 bytes print-type-size padding: 8 bytes print-type-size field `.self_val`: 8 bytes, alignment: 8 bytes print-type-size field `.id`: 8 bytes print-type-size field `.val`: 8 bytes print-type-size field `.ic`: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `PatchPoint`: 48 bytes print-type-size padding: 8 bytes print-type-size field `.invariant`: 32 bytes, alignment: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `LoadArg`: 44 bytes print-type-size padding: 8 bytes print-type-size field `.id`: 16 bytes, alignment: 8 bytes print-type-size field `.val_type`: 16 bytes print-type-size field `.idx`: 4 bytes print-type-size variant `StoreField`: 44 bytes print-type-size padding: 8 bytes print-type-size field `.id`: 16 bytes, alignment: 8 bytes print-type-size field `.recv`: 8 bytes print-type-size field `.val`: 8 bytes print-type-size field `.offset`: 4 bytes print-type-size variant `GuardType`: 41 bytes print-type-size padding: 8 bytes print-type-size field `.val`: 8 bytes, alignment: 8 bytes print-type-size field `.guard_type`: 16 bytes print-type-size field `.state`: 8 bytes print-type-size field `.recompile`: 1 bytes print-type-size variant `StringConcat`: 40 bytes print-type-size padding: 8 bytes print-type-size field `.strings`: 24 bytes, alignment: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `NewArray`: 40 bytes print-type-size padding: 8 bytes print-type-size field `.elements`: 24 bytes, alignment: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `NewHash`: 40 bytes print-type-size padding: 8 bytes print-type-size field `.elements`: 24 bytes, alignment: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `ArrayHash`: 40 bytes print-type-size padding: 8 bytes print-type-size field `.elements`: 24 bytes, alignment: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `ArrayMax`: 40 bytes print-type-size padding: 8 bytes print-type-size field `.elements`: 24 bytes, alignment: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `ArrayMin`: 40 bytes print-type-size padding: 8 bytes print-type-size field `.elements`: 24 bytes, alignment: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `HashAset`: 40 bytes print-type-size padding: 8 bytes print-type-size field `.hash`: 8 bytes, alignment: 8 bytes print-type-size field `.key`: 8 bytes print-type-size field `.val`: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `IsMethodCfunc`: 40 bytes print-type-size padding: 8 bytes print-type-size field `.val`: 8 bytes, alignment: 8 bytes print-type-size field `.cd`: 8 bytes print-type-size field `.cfunc`: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `GetConstant`: 40 bytes print-type-size padding: 8 bytes print-type-size field `.klass`: 8 bytes, alignment: 8 bytes print-type-size field `.id`: 8 bytes print-type-size field `.allow_nil`: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `GetIvar`: 40 bytes print-type-size padding: 8 bytes print-type-size field `.self_val`: 8 bytes, alignment: 8 bytes print-type-size field `.id`: 8 bytes print-type-size field `.ic`: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `DefinedIvar`: 40 bytes print-type-size padding: 8 bytes print-type-size field `.self_val`: 8 bytes, alignment: 8 bytes print-type-size field `.id`: 8 bytes print-type-size field `.pushval`: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `SetClassVar`: 40 bytes print-type-size padding: 8 bytes print-type-size field `.id`: 8 bytes, alignment: 8 bytes print-type-size field `.val`: 8 bytes print-type-size field `.ic`: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `Jump`: 40 bytes print-type-size padding: 8 bytes print-type-size field `.0`: 32 bytes, alignment: 8 bytes print-type-size variant `GuardGreaterEq`: 40 bytes print-type-size padding: 8 bytes print-type-size field `.reason`: 8 bytes, alignment: 8 bytes print-type-size field `.left`: 8 bytes print-type-size field `.right`: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `GuardLess`: 40 bytes print-type-size padding: 8 bytes print-type-size field `.reason`: 8 bytes, alignment: 8 bytes print-type-size field `.left`: 8 bytes print-type-size field `.right`: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `CheckMatch`: 36 bytes print-type-size padding: 8 bytes print-type-size field `.target`: 8 bytes, alignment: 8 bytes print-type-size field `.pattern`: 8 bytes print-type-size field `.state`: 8 bytes print-type-size field `.flag`: 4 bytes print-type-size variant `NewRange`: 33 bytes print-type-size padding: 8 bytes print-type-size field `.low`: 8 bytes, alignment: 8 bytes print-type-size field `.high`: 8 bytes print-type-size field `.state`: 8 bytes print-type-size field `.flag`: 1 bytes print-type-size variant `NewRangeFixnum`: 33 bytes print-type-size padding: 8 bytes print-type-size field `.low`: 8 bytes, alignment: 8 bytes print-type-size field `.high`: 8 bytes print-type-size field `.state`: 8 bytes print-type-size field `.flag`: 1 bytes print-type-size variant `Comment`: 32 bytes print-type-size padding: 8 bytes print-type-size field `.message`: 24 bytes, alignment: 8 bytes print-type-size variant `Entries`: 32 bytes print-type-size padding: 8 bytes print-type-size field `.targets`: 24 bytes, alignment: 8 bytes print-type-size variant `StringSetbyteFixnum`: 32 bytes print-type-size padding: 8 bytes print-type-size field `.string`: 8 bytes, alignment: 8 bytes print-type-size field `.index`: 8 bytes print-type-size field `.value`: 8 bytes print-type-size variant `StringAppend`: 32 bytes print-type-size padding: 8 bytes print-type-size field `.recv`: 8 bytes, alignment: 8 bytes print-type-size field `.other`: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `StringAppendCodepoint`: 32 bytes print-type-size padding: 8 bytes print-type-size field `.recv`: 8 bytes, alignment: 8 bytes print-type-size field `.other`: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `DupArrayInclude`: 32 bytes print-type-size padding: 8 bytes print-type-size field `.ary`: 8 bytes, alignment: 8 bytes print-type-size field `.target`: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `ArrayExtend`: 32 bytes print-type-size padding: 8 bytes print-type-size field `.left`: 8 bytes, alignment: 8 bytes print-type-size field `.right`: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `ArrayPush`: 32 bytes print-type-size padding: 8 bytes print-type-size field `.array`: 8 bytes, alignment: 8 bytes print-type-size field `.val`: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `ArrayAset`: 32 bytes print-type-size padding: 8 bytes print-type-size field `.array`: 8 bytes, alignment: 8 bytes print-type-size field `.index`: 8 bytes print-type-size field `.val`: 8 bytes print-type-size variant `HashAref`: 32 bytes print-type-size padding: 8 bytes print-type-size field `.hash`: 8 bytes, alignment: 8 bytes print-type-size field `.key`: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `SetGlobal`: 32 bytes print-type-size padding: 8 bytes print-type-size field `.id`: 8 bytes, alignment: 8 bytes print-type-size field `.val`: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `GetClassVar`: 32 bytes print-type-size padding: 8 bytes print-type-size field `.id`: 8 bytes, alignment: 8 bytes print-type-size field `.ic`: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `PopInlineFrame`: 32 bytes print-type-size padding: 8 bytes print-type-size field `.iseq`: 8 bytes, alignment: 8 bytes print-type-size field `.argc`: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `FixnumAdd`: 32 bytes print-type-size padding: 8 bytes print-type-size field `.left`: 8 bytes, alignment: 8 bytes print-type-size field `.right`: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `FixnumSub`: 32 bytes print-type-size padding: 8 bytes print-type-size field `.left`: 8 bytes, alignment: 8 bytes print-type-size field `.right`: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `FixnumMult`: 32 bytes print-type-size padding: 8 bytes print-type-size field `.left`: 8 bytes, alignment: 8 bytes print-type-size field `.right`: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `FixnumDiv`: 32 bytes print-type-size padding: 8 bytes print-type-size field `.left`: 8 bytes, alignment: 8 bytes print-type-size field `.right`: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `FixnumMod`: 32 bytes print-type-size padding: 8 bytes print-type-size field `.left`: 8 bytes, alignment: 8 bytes print-type-size field `.right`: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `FixnumLShift`: 32 bytes print-type-size padding: 8 bytes print-type-size field `.left`: 8 bytes, alignment: 8 bytes print-type-size field `.right`: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `FloatAdd`: 32 bytes print-type-size padding: 8 bytes print-type-size field `.recv`: 8 bytes, alignment: 8 bytes print-type-size field `.other`: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `FloatSub`: 32 bytes print-type-size padding: 8 bytes print-type-size field `.recv`: 8 bytes, alignment: 8 bytes print-type-size field `.other`: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `FloatMul`: 32 bytes print-type-size padding: 8 bytes print-type-size field `.recv`: 8 bytes, alignment: 8 bytes print-type-size field `.other`: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `FloatDiv`: 32 bytes print-type-size padding: 8 bytes print-type-size field `.recv`: 8 bytes, alignment: 8 bytes print-type-size field `.other`: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `AnyToString`: 32 bytes print-type-size padding: 8 bytes print-type-size field `.val`: 8 bytes, alignment: 8 bytes print-type-size field `.str`: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `RefineType`: 32 bytes print-type-size padding: 8 bytes print-type-size field `.new_type`: 16 bytes, alignment: 8 bytes print-type-size field `.val`: 8 bytes print-type-size variant `HasType`: 32 bytes print-type-size padding: 8 bytes print-type-size field `.expected`: 16 bytes, alignment: 8 bytes print-type-size field `.val`: 8 bytes print-type-size variant `Throw`: 28 bytes print-type-size padding: 8 bytes print-type-size field `.val`: 8 bytes, alignment: 8 bytes print-type-size field `.state`: 8 bytes print-type-size field `.throw_state`: 4 bytes print-type-size variant `StringCopy`: 25 bytes print-type-size padding: 8 bytes print-type-size field `.val`: 8 bytes, alignment: 8 bytes print-type-size field `.state`: 8 bytes print-type-size field `.chilled`: 1 bytes print-type-size variant `SideExit`: 25 bytes print-type-size padding: 8 bytes print-type-size field `.reason`: 8 bytes, alignment: 8 bytes print-type-size field `.state`: 8 bytes print-type-size field `.recompile`: 1 bytes print-type-size variant `Const`: 24 bytes print-type-size padding: 8 bytes print-type-size field `.val`: 16 bytes, alignment: 8 bytes print-type-size variant `StringIntern`: 24 bytes print-type-size padding: 8 bytes print-type-size field `.val`: 8 bytes, alignment: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `StringGetbyte`: 24 bytes print-type-size padding: 8 bytes print-type-size field `.string`: 8 bytes, alignment: 8 bytes print-type-size field `.index`: 8 bytes print-type-size variant `StringEqual`: 24 bytes print-type-size padding: 8 bytes print-type-size field `.left`: 8 bytes, alignment: 8 bytes print-type-size field `.right`: 8 bytes print-type-size variant `ToArray`: 24 bytes print-type-size padding: 8 bytes print-type-size field `.val`: 8 bytes, alignment: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `ToNewArray`: 24 bytes print-type-size padding: 8 bytes print-type-size field `.val`: 8 bytes, alignment: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `ArrayDup`: 24 bytes print-type-size padding: 8 bytes print-type-size field `.val`: 8 bytes, alignment: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `ArrayAref`: 24 bytes print-type-size padding: 8 bytes print-type-size field `.array`: 8 bytes, alignment: 8 bytes print-type-size field `.index`: 8 bytes print-type-size variant `ArrayPop`: 24 bytes print-type-size padding: 8 bytes print-type-size field `.array`: 8 bytes, alignment: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `AdjustBounds`: 24 bytes print-type-size padding: 8 bytes print-type-size field `.index`: 8 bytes, alignment: 8 bytes print-type-size field `.length`: 8 bytes print-type-size variant `HashDup`: 24 bytes print-type-size padding: 8 bytes print-type-size field `.val`: 8 bytes, alignment: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `ObjectAlloc`: 24 bytes print-type-size padding: 8 bytes print-type-size field `.val`: 8 bytes, alignment: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `ObjectAllocClass`: 24 bytes print-type-size padding: 8 bytes print-type-size field `.class`: 8 bytes, alignment: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `IsBitEqual`: 24 bytes print-type-size padding: 8 bytes print-type-size field `.left`: 8 bytes, alignment: 8 bytes print-type-size field `.right`: 8 bytes print-type-size variant `IsBitNotEqual`: 24 bytes print-type-size padding: 8 bytes print-type-size field `.left`: 8 bytes, alignment: 8 bytes print-type-size field `.right`: 8 bytes print-type-size variant `BoxFixnum`: 24 bytes print-type-size padding: 8 bytes print-type-size field `.val`: 8 bytes, alignment: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `FixnumAref`: 24 bytes print-type-size padding: 8 bytes print-type-size field `.recv`: 8 bytes, alignment: 8 bytes print-type-size field `.index`: 8 bytes print-type-size variant `GetConstantPath`: 24 bytes print-type-size padding: 8 bytes print-type-size field `.ic`: 8 bytes, alignment: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `IsA`: 24 bytes print-type-size padding: 8 bytes print-type-size field `.val`: 8 bytes, alignment: 8 bytes print-type-size field `.class`: 8 bytes print-type-size variant `GetGlobal`: 24 bytes print-type-size padding: 8 bytes print-type-size field `.id`: 8 bytes, alignment: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `WriteBarrier`: 24 bytes print-type-size padding: 8 bytes print-type-size field `.recv`: 8 bytes, alignment: 8 bytes print-type-size field `.val`: 8 bytes print-type-size variant `GetBlockParam`: 24 bytes print-type-size padding: 8 bytes print-type-size field `.state`: 8 bytes, alignment: 8 bytes print-type-size field `.level`: 4 bytes print-type-size field `.ep_offset`: 4 bytes print-type-size variant `SetLocal`: 24 bytes print-type-size padding: 8 bytes print-type-size field `.val`: 8 bytes, alignment: 8 bytes print-type-size field `.level`: 4 bytes print-type-size field `.ep_offset`: 4 bytes print-type-size variant `GetSpecialNumber`: 24 bytes print-type-size padding: 8 bytes print-type-size field `.nth`: 8 bytes, alignment: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `EntryPoint`: 24 bytes print-type-size padding: 8 bytes print-type-size field `.jit_entry_idx`: 16 bytes, alignment: 8 bytes print-type-size variant `FixnumEq`: 24 bytes print-type-size padding: 8 bytes print-type-size field `.left`: 8 bytes, alignment: 8 bytes print-type-size field `.right`: 8 bytes print-type-size variant `FixnumNeq`: 24 bytes print-type-size padding: 8 bytes print-type-size field `.left`: 8 bytes, alignment: 8 bytes print-type-size field `.right`: 8 bytes print-type-size variant `FixnumLt`: 24 bytes print-type-size padding: 8 bytes print-type-size field `.left`: 8 bytes, alignment: 8 bytes print-type-size field `.right`: 8 bytes print-type-size variant `FixnumLe`: 24 bytes print-type-size padding: 8 bytes print-type-size field `.left`: 8 bytes, alignment: 8 bytes print-type-size field `.right`: 8 bytes print-type-size variant `FixnumGt`: 24 bytes print-type-size padding: 8 bytes print-type-size field `.left`: 8 bytes, alignment: 8 bytes print-type-size field `.right`: 8 bytes print-type-size variant `FixnumGe`: 24 bytes print-type-size padding: 8 bytes print-type-size field `.left`: 8 bytes, alignment: 8 bytes print-type-size field `.right`: 8 bytes print-type-size variant `FixnumAnd`: 24 bytes print-type-size padding: 8 bytes print-type-size field `.left`: 8 bytes, alignment: 8 bytes print-type-size field `.right`: 8 bytes print-type-size variant `FixnumOr`: 24 bytes print-type-size padding: 8 bytes print-type-size field `.left`: 8 bytes, alignment: 8 bytes print-type-size field `.right`: 8 bytes print-type-size variant `FixnumXor`: 24 bytes print-type-size padding: 8 bytes print-type-size field `.left`: 8 bytes, alignment: 8 bytes print-type-size field `.right`: 8 bytes print-type-size variant `IntAnd`: 24 bytes print-type-size padding: 8 bytes print-type-size field `.left`: 8 bytes, alignment: 8 bytes print-type-size field `.right`: 8 bytes print-type-size variant `IntOr`: 24 bytes print-type-size padding: 8 bytes print-type-size field `.left`: 8 bytes, alignment: 8 bytes print-type-size field `.right`: 8 bytes print-type-size variant `FixnumRShift`: 24 bytes print-type-size padding: 8 bytes print-type-size field `.left`: 8 bytes, alignment: 8 bytes print-type-size field `.right`: 8 bytes print-type-size variant `FloatToInt`: 24 bytes print-type-size padding: 8 bytes print-type-size field `.recv`: 8 bytes, alignment: 8 bytes print-type-size field `.state`: 8 bytes print-type-size variant `PutSpecialObject`: 17 bytes print-type-size padding: 8 bytes print-type-size field `.state`: 8 bytes, alignment: 8 bytes print-type-size field `.value_type`: 1 bytes print-type-size variant `FixnumBitCheck`: 17 bytes print-type-size padding: 8 bytes print-type-size field `.val`: 8 bytes, alignment: 8 bytes print-type-size field `.index`: 1 bytes print-type-size variant `GetSpecialSymbol`: 17 bytes print-type-size padding: 8 bytes print-type-size field `.state`: 8 bytes, alignment: 8 bytes print-type-size field `.symbol_type`: 1 bytes print-type-size variant `ArrayLength`: 16 bytes print-type-size padding: 8 bytes print-type-size field `.array`: 8 bytes, alignment: 8 bytes print-type-size variant `Test`: 16 bytes print-type-size padding: 8 bytes print-type-size field `.val`: 8 bytes, alignment: 8 bytes print-type-size variant `BoxBool`: 16 bytes print-type-size padding: 8 bytes print-type-size field `.val`: 8 bytes, alignment: 8 bytes print-type-size variant `UnboxFixnum`: 16 bytes print-type-size padding: 8 bytes print-type-size field `.val`: 8 bytes, alignment: 8 bytes print-type-size variant `IsBlockGiven`: 16 bytes print-type-size padding: 8 bytes print-type-size field `.lep`: 8 bytes, alignment: 8 bytes print-type-size variant `IsBlockParamModified`: 16 bytes print-type-size padding: 8 bytes print-type-size field `.flags`: 8 bytes, alignment: 8 bytes print-type-size variant `Snapshot`: 16 bytes print-type-size padding: 8 bytes print-type-size field `.state`: 8 bytes, alignment: 8 bytes print-type-size variant `CCallWithFrame`: 16 bytes print-type-size padding: 8 bytes print-type-size field `.0`: 8 bytes, alignment: 8 bytes print-type-size variant `CCallVariadic`: 16 bytes print-type-size padding: 8 bytes print-type-size field `.0`: 8 bytes, alignment: 8 bytes print-type-size variant `SendDirect`: 16 bytes print-type-size padding: 8 bytes print-type-size field `.0`: 8 bytes, alignment: 8 bytes print-type-size variant `Return`: 16 bytes print-type-size padding: 8 bytes print-type-size field `.val`: 8 bytes, alignment: 8 bytes print-type-size variant `IncrCounterPtr`: 16 bytes print-type-size padding: 8 bytes print-type-size field `.counter_ptr`: 8 bytes, alignment: 8 bytes print-type-size variant `CheckInterrupts`: 16 bytes print-type-size padding: 8 bytes print-type-size field `.state`: 8 bytes, alignment: 8 bytes print-type-size variant `GetEP`: 12 bytes print-type-size padding: 8 bytes print-type-size field `.level`: 4 bytes, alignment: 4 bytes print-type-size variant `IncrCounter`: 10 bytes print-type-size padding: 8 bytes print-type-size field `.0`: 2 bytes, alignment: 2 bytes print-type-size variant `Param`: 0 bytes print-type-size variant `LoadPC`: 0 bytes print-type-size variant `LoadEC`: 0 bytes print-type-size variant `LoadSP`: 0 bytes print-type-size variant `LoadSelf`: 0 bytes print-type-size variant `BreakPoint`: 0 bytes print-type-size variant `Unreachable`: 0 bytes ```
--- zjit/src/codegen.rs | 8 ++-- zjit/src/cruby_methods.rs | 18 ++++---- zjit/src/hir.rs | 96 +++++++++++++++++++-------------------- zjit/src/hir/tests.rs | 2 +- 4 files changed, 62 insertions(+), 62 deletions(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 0c8c1ebea30131..a6239074a02ee9 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -715,10 +715,10 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio let val_type = function.type_of(val); gen_guard_type(jit, asm, opnd!(val), val_type, guard_type, recompile, &function.frame_state(state)) } - &Insn::GuardBitEquals { val, expected, reason, state, recompile } => gen_guard_bit_equals(jit, asm, opnd!(val), expected, reason, recompile, &function.frame_state(state)), - &Insn::GuardAnyBitSet { val, mask, reason, state, recompile, .. } => gen_guard_any_bit_set(jit, asm, opnd!(val), mask, reason, recompile, &function.frame_state(state)), - &Insn::GuardNoBitsSet { val, mask, reason, state, .. } => gen_guard_no_bits_set(jit, asm, opnd!(val), mask, reason, &function.frame_state(state)), - &Insn::GuardLess { left, right, reason, state } => gen_guard_less(jit, asm, opnd!(left), opnd!(right), reason, &function.frame_state(state)), + &Insn::GuardBitEquals { val, expected, ref reason, state, recompile } => gen_guard_bit_equals(jit, asm, opnd!(val), expected, **reason, recompile, &function.frame_state(state)), + &Insn::GuardAnyBitSet { val, mask, ref reason, state, recompile, .. } => gen_guard_any_bit_set(jit, asm, opnd!(val), mask, **reason, recompile, &function.frame_state(state)), + &Insn::GuardNoBitsSet { val, mask, ref reason, state, .. } => gen_guard_no_bits_set(jit, asm, opnd!(val), mask, **reason, &function.frame_state(state)), + &Insn::GuardLess { left, right, ref reason, state } => gen_guard_less(jit, asm, opnd!(left), opnd!(right), **reason, &function.frame_state(state)), &Insn::GuardGreaterEq { left, right, state, .. } => gen_guard_greater_eq(jit, asm, opnd!(left), opnd!(right), &function.frame_state(state)), Insn::PatchPoint { invariant, state } => no_output!(gen_patch_point(jit, asm, invariant, &function.frame_state(*state))), Insn::CCall { cfunc, recv, args, name, owner: _, return_type: _, elidable: _ } => gen_ccall(asm, *cfunc, *name, opnd!(recv), opnds!(args)), diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 685208dd7d444c..3608bba453486c 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -377,11 +377,11 @@ fn inline_array_aref(fun: &mut hir::Function, block: hir::BlockId, recv: hir::In let index = fun.coerce_to(block, index, types::Fixnum, state); let index = fun.push_insn(block, hir::Insn::UnboxFixnum { val: index }); let length = fun.push_insn(block, hir::Insn::ArrayLength { array: recv }); - let index = fun.push_insn(block, hir::Insn::GuardLess { left: index, right: length, reason: SideExitReason::GuardLess, state }); + let index = fun.push_insn(block, hir::Insn::GuardLess { left: index, right: length, reason: Box::new(SideExitReason::GuardLess), state }); let index = fun.push_insn(block, hir::Insn::AdjustBounds { index, length }); let zero = fun.push_insn(block, hir::Insn::Const { val: hir::Const::CInt64(0) }); use crate::hir::SideExitReason; - let index = fun.push_insn(block, hir::Insn::GuardGreaterEq { left: index, right: zero, reason: SideExitReason::GuardGreaterEq, state }); + let index = fun.push_insn(block, hir::Insn::GuardGreaterEq { left: index, right: zero, reason: Box::new(SideExitReason::GuardGreaterEq), state }); let result = fun.push_insn(block, hir::Insn::ArrayAref { array: recv, index }); return Some(result); } @@ -402,11 +402,11 @@ fn inline_array_aset(fun: &mut hir::Function, block: hir::BlockId, recv: hir::In // Bounds check: unbox Fixnum index and guard 0 <= idx < length. let index = fun.push_insn(block, hir::Insn::UnboxFixnum { val: index }); let length = fun.push_insn(block, hir::Insn::ArrayLength { array: recv }); - let index = fun.push_insn(block, hir::Insn::GuardLess { left: index, right: length, reason: SideExitReason::GuardLess, state }); + let index = fun.push_insn(block, hir::Insn::GuardLess { left: index, right: length, reason: Box::new(SideExitReason::GuardLess), state }); let index = fun.push_insn(block, hir::Insn::AdjustBounds { index, length }); let zero = fun.push_insn(block, hir::Insn::Const { val: hir::Const::CInt64(0) }); use crate::hir::SideExitReason; - let index = fun.push_insn(block, hir::Insn::GuardGreaterEq { left: index, right: zero, reason: SideExitReason::GuardGreaterEq, state }); + let index = fun.push_insn(block, hir::Insn::GuardGreaterEq { left: index, right: zero, reason: Box::new(SideExitReason::GuardGreaterEq), state }); let _ = fun.push_insn(block, hir::Insn::ArrayAset { array: recv, index, val }); fun.push_insn(block, hir::Insn::WriteBarrier { recv, val }); @@ -502,11 +502,11 @@ fn inline_string_getbyte(fun: &mut hir::Function, block: hir::BlockId, recv: hir // the data dependency is gone (say, the StringGetbyte is elided), they can also be elided. // // This is unlike most other guards. - let unboxed_index = fun.push_insn(block, hir::Insn::GuardLess { left: unboxed_index, right: len, reason: SideExitReason::GuardLess, state }); + let unboxed_index = fun.push_insn(block, hir::Insn::GuardLess { left: unboxed_index, right: len, reason: Box::new(SideExitReason::GuardLess), state }); let unboxed_index = fun.push_insn(block, hir::Insn::AdjustBounds { index: unboxed_index, length: len }); let zero = fun.push_insn(block, hir::Insn::Const { val: hir::Const::CInt64(0) }); use crate::hir::SideExitReason; - let _ = fun.push_insn(block, hir::Insn::GuardGreaterEq { left: unboxed_index, right: zero, reason: SideExitReason::GuardGreaterEq, state }); + let _ = fun.push_insn(block, hir::Insn::GuardGreaterEq { left: unboxed_index, right: zero, reason: Box::new(SideExitReason::GuardGreaterEq), state }); let result = fun.push_insn(block, hir::Insn::StringGetbyte { string: recv, index: unboxed_index }); return Some(result); } @@ -526,11 +526,11 @@ fn inline_string_setbyte(fun: &mut hir::Function, block: hir::BlockId, recv: hir offset: RUBY_OFFSET_RSTRING_LEN, return_type: types::CInt64, }); - let unboxed_index = fun.push_insn(block, hir::Insn::GuardLess { left: unboxed_index, right: len, reason: SideExitReason::GuardLess, state }); + let unboxed_index = fun.push_insn(block, hir::Insn::GuardLess { left: unboxed_index, right: len, reason: Box::new(SideExitReason::GuardLess), state }); let unboxed_index = fun.push_insn(block, hir::Insn::AdjustBounds { index: unboxed_index, length: len }); let zero = fun.push_insn(block, hir::Insn::Const { val: hir::Const::CInt64(0) }); use crate::hir::SideExitReason; - let _ = fun.push_insn(block, hir::Insn::GuardGreaterEq { left: unboxed_index, right: zero, reason: SideExitReason::GuardGreaterEq, state }); + let _ = fun.push_insn(block, hir::Insn::GuardGreaterEq { left: unboxed_index, right: zero, reason: Box::new(SideExitReason::GuardGreaterEq), state }); // We know that all String are HeapObject, so no need to insert a GuardType(HeapObject). fun.guard_not_frozen(block, recv, state); let _ = fun.push_insn(block, hir::Insn::StringSetbyteFixnum { string: recv, index, value }); @@ -566,7 +566,7 @@ fn guard_string_coderange(fun: &mut hir::Function, block: hir::BlockId, args: &[ let mask = fun.push_insn(block, hir::Insn::Const { val: hir::Const::CUInt64(RUBY_ENC_CODERANGE_MASK.into()) }); let cr = fun.push_insn(block, hir::Insn::IntAnd { left: flags, right: mask }); let min_known = fun.push_insn(block, hir::Insn::Const { val: hir::Const::CInt64(RUBY_ENC_CODERANGE_7BIT.into()) }); - Some(fun.push_insn(block, hir::Insn::GuardGreaterEq { left: cr, right: min_known, reason: SideExitReason::GuardGreaterEq, state })) + Some(fun.push_insn(block, hir::Insn::GuardGreaterEq { left: cr, right: min_known, reason: Box::new(SideExitReason::GuardGreaterEq), state })) } // Inlines String#ascii_only? (rb_str_is_ascii_only_p): coderange == 7BIT. diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 3bd9aff0cb7830..07bad0df572326 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -1194,15 +1194,15 @@ pub enum Insn { /// Side-exit if val doesn't have the expected type. GuardType { val: InsnId, guard_type: Type, state: InsnId, recompile: Option }, /// Side-exit if val is not the expected Const. - GuardBitEquals { val: InsnId, expected: Const, reason: SideExitReason, state: InsnId, recompile: Option }, + GuardBitEquals { val: InsnId, expected: Const, reason: Box, state: InsnId, recompile: Option }, /// Side-exit if (val & mask) == 0 - GuardAnyBitSet { val: InsnId, mask: Const, mask_name: Option, reason: SideExitReason, state: InsnId, recompile: Option }, + GuardAnyBitSet { val: InsnId, mask: Const, mask_name: Option, reason: Box, state: InsnId, recompile: Option }, /// Side-exit if (val & mask) != 0 - GuardNoBitsSet { val: InsnId, mask: Const, mask_name: Option, reason: SideExitReason, state: InsnId }, + GuardNoBitsSet { val: InsnId, mask: Const, mask_name: Option, reason: Box, state: InsnId }, /// Side-exit if left is not greater than or equal to right (both operands are C long). - GuardGreaterEq { left: InsnId, right: InsnId, reason: SideExitReason, state: InsnId }, + GuardGreaterEq { left: InsnId, right: InsnId, reason: Box, state: InsnId }, /// Side-exit if left is not less than right (both operands are C long). - GuardLess { left: InsnId, right: InsnId, reason: SideExitReason, state: InsnId }, + GuardLess { left: InsnId, right: InsnId, reason: Box, state: InsnId }, /// Generate no code (or padding if necessary) and insert a patch point /// that can be rewritten to a side exit when the Invariant is broken. @@ -1211,7 +1211,7 @@ pub enum Insn { /// Side-exit into the interpreter. /// If recompile is not None, the side exit will profile and invalidate the ISEQ /// so that it gets recompiled with the new profile data. - SideExit { state: InsnId, reason: SideExitReason, recompile: Option }, + SideExit { state: InsnId, reason: Box, recompile: Option }, /// Increment a counter in ZJIT stats IncrCounter(Counter), @@ -2938,7 +2938,7 @@ impl Function { if self.assume_bop_not_redefined(block, klass, bop, state) { return true; } - self.push_insn(block, Insn::SideExit { state, reason: SideExitReason::PatchPoint(Invariant::BOPRedefined { klass, bop }), recompile: None }); + self.push_insn(block, Insn::SideExit { state, reason: Box::new(SideExitReason::PatchPoint(Invariant::BOPRedefined { klass, bop })), recompile: None }); false } @@ -3678,12 +3678,12 @@ impl Function { pub fn guard_not_frozen(&mut self, block: BlockId, recv: InsnId, state: InsnId) { let flags = self.load_rbasic_flags(block, recv); - self.push_insn(block, Insn::GuardNoBitsSet { val: flags, mask: Const::CUInt64(RUBY_FL_FREEZE as u64), mask_name: Some(ID!(RUBY_FL_FREEZE)), reason: SideExitReason::GuardNotFrozen, state }); + self.push_insn(block, Insn::GuardNoBitsSet { val: flags, mask: Const::CUInt64(RUBY_FL_FREEZE as u64), mask_name: Some(ID!(RUBY_FL_FREEZE)), reason: Box::new(SideExitReason::GuardNotFrozen), state }); } pub fn guard_not_shared(&mut self, block: BlockId, recv: InsnId, state: InsnId) { let flags = self.load_rbasic_flags(block, recv); - self.push_insn(block, Insn::GuardNoBitsSet { val: flags, mask: Const::CUInt64(RUBY_ELTS_SHARED as u64), mask_name: Some(ID!(RUBY_ELTS_SHARED)), reason: SideExitReason::GuardNotShared, state }); + self.push_insn(block, Insn::GuardNoBitsSet { val: flags, mask: Const::CUInt64(RUBY_ELTS_SHARED as u64), mask_name: Some(ID!(RUBY_ELTS_SHARED)), reason: Box::new(SideExitReason::GuardNotShared), state }); } /// `iseq` is the ISEQ that `ep_offset` is relative to, which is the ISEQ that @@ -4396,13 +4396,13 @@ impl Function { // Load ep[VM_ENV_DATA_INDEX_ME_CREF] let method_entry = fun.load_field(block, lep, FieldName::VM_ENV_DATA_INDEX_ME_CREF, SIZEOF_VALUE_I32 * VM_ENV_DATA_INDEX_ME_CREF, types::RubyValue); // Guard that it matches the expected CME - fun.push_insn(block, Insn::GuardBitEquals { val: method_entry, expected: Const::Value(current_cme.into()), reason: SideExitReason::GuardSuperMethodEntry, state, recompile: None }); + fun.push_insn(block, Insn::GuardBitEquals { val: method_entry, expected: Const::Value(current_cme.into()), reason: Box::new(SideExitReason::GuardSuperMethodEntry), state, recompile: None }); let block_handler = fun.load_field(block, lep, FieldName::VM_ENV_DATA_INDEX_SPECVAL, SIZEOF_VALUE_I32 * VM_ENV_DATA_INDEX_SPECVAL, types::RubyValue); fun.push_insn(block, Insn::GuardBitEquals { val: block_handler, expected: Const::Value(VALUE(VM_BLOCK_HANDLER_NONE as usize)), - reason: SideExitReason::UnhandledBlockArg, + reason: Box::new(SideExitReason::UnhandledBlockArg), state, recompile: None, }); @@ -5033,7 +5033,7 @@ impl Function { self.push_insn(block, Insn::GuardBitEquals { val, expected: Const::CShape(expected), - reason: SideExitReason::GuardShape(expected), + reason: Box::new(SideExitReason::GuardShape(expected)), state, recompile, }) @@ -5348,7 +5348,7 @@ impl Function { for insn_id in old_insns { match self.find(insn_id) { Insn::Send { state, reason: SendFallbackReason::SendWithoutBlockNoProfiles | SendFallbackReason::SendNoProfiles, .. } => { - self.push_insn(block, Insn::SideExit { state, reason: SideExitReason::NoProfileSend, recompile: Some(Recompile) }); + self.push_insn(block, Insn::SideExit { state, reason: Box::new(SideExitReason::NoProfileSend), recompile: Some(Recompile) }); // SideExit is a terminator; don't add remaining instructions break; } @@ -5526,7 +5526,7 @@ impl Function { // pass. Every execution would side-exit here, so we replace the guard with an // unconditional exit. The terminator handling below then drops the rest of // the block, which is now unreachable. - self.new_insn(Insn::SideExit { state, reason: SideExitReason::GuardType(guard_type), recompile }) + self.new_insn(Insn::SideExit { state, reason: Box::new(SideExitReason::GuardType(guard_type)), recompile }) } Insn::GuardType { val, guard_type, .. } if self.is_a(val, guard_type) => { self.make_equal_to(insn_id, val); @@ -7627,7 +7627,7 @@ fn add_iseq_to_hir( } _ => { // Unknown opcode; side-exit into the interpreter - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledNewarraySend(method), recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::UnhandledNewarraySend(method)), recompile: None }); break; // End the block } }; @@ -7653,7 +7653,7 @@ fn add_iseq_to_hir( let bop = match method_id { x if x == ID!(include_p).0 => BOP_INCLUDE_P, _ => { - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledDuparraySend(method_id), recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::UnhandledDuparraySend(method_id)), recompile: None }); break; }, }; @@ -7700,11 +7700,11 @@ fn add_iseq_to_hir( .and_then(|types| types.first()) .map(|dist| TypeDistributionSummary::new(dist)); let Some(summary) = summary else { - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::SplatKwNotProfiled, recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::SplatKwNotProfiled), recompile: None }); break; // End the block }; if !summary.is_monomorphic() { - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::SplatKwPolymorphic, recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::SplatKwPolymorphic), recompile: None }); break; // End the block } let ty = Type::from_profiled_type(summary.bucket(0)); @@ -7713,7 +7713,7 @@ fn add_iseq_to_hir( } else if ty.is_subtype(types::HashExact) { fun.push_insn(block, Insn::GuardType { val: hash, guard_type: types::HashExact, state: exit_id, recompile: None }) } else { - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::SplatKwNotNilOrHash, recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::SplatKwNotNilOrHash), recompile: None }); break; // End the block }; state.stack_push(obj); @@ -7859,7 +7859,7 @@ fn add_iseq_to_hir( // This can only happen in iseqs taking more than 32 keywords. // In this case, we side exit to the interpreter. if unsafe {(*rb_get_iseq_body_param_keyword(iseq)).num >= VM_KW_SPECIFIED_BITS_MAX.try_into().unwrap()} { - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::TooManyKeywordParameters, recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::TooManyKeywordParameters), recompile: None }); break; } let ep_offset = get_arg(pc, 0).as_u32(); @@ -8251,7 +8251,7 @@ fn add_iseq_to_hir( // So to check for either of those cases we can use: val & 0x1 == 0x1 // Bail out if the block handler is neither ISEQ nor ifunc - fun.push_insn(unmodified_block, Insn::GuardAnyBitSet { val: block_handler, mask: Const::CUInt64(0x1), mask_name: None, reason: SideExitReason::BlockParamProxyFallbackMiss, state: exit_id, recompile: Some(Recompile) }); + fun.push_insn(unmodified_block, Insn::GuardAnyBitSet { val: block_handler, mask: Const::CUInt64(0x1), mask_name: None, reason: Box::new(SideExitReason::BlockParamProxyFallbackMiss), state: exit_id, recompile: Some(Recompile) }); // TODO(Shopify/ruby#753): GC root, so we should be able to avoid unnecessary GC tracing let proxy_val = fun.push_insn(unmodified_block, Insn::Const { val: Const::Value(unsafe { rb_block_param_proxy }) }); let mut args = vec![proxy_val]; @@ -8264,7 +8264,7 @@ fn add_iseq_to_hir( [profiled_handler] => match profiled_handler { ProfiledBlockHandlerFamily::Nil => { let block_handler = fun.load_ep_env_field(unmodified_block, ep, FieldName::VM_ENV_DATA_INDEX_SPECVAL, VM_ENV_DATA_INDEX_SPECVAL, types::CInt64); - fun.push_insn(unmodified_block, Insn::GuardBitEquals { val: block_handler, expected: Const::CInt64(VM_BLOCK_HANDLER_NONE.into()), reason: SideExitReason::BlockParamProxyNotNil, state: exit_id, recompile: Some(Recompile) }); + fun.push_insn(unmodified_block, Insn::GuardBitEquals { val: block_handler, expected: Const::CInt64(VM_BLOCK_HANDLER_NONE.into()), reason: Box::new(SideExitReason::BlockParamProxyNotNil), state: exit_id, recompile: Some(Recompile) }); let nil_val = fun.push_insn(unmodified_block, Insn::Const { val: Const::Value(Qnil) }); let mut args = vec![nil_val]; if let Some(local) = original_local { @@ -8281,7 +8281,7 @@ fn add_iseq_to_hir( // So to check for either of those cases we can use: val & 0x1 == 0x1 // Bail out if the block handler is neither ISEQ nor ifunc - fun.push_insn(unmodified_block, Insn::GuardAnyBitSet { val: block_handler, mask: Const::CUInt64(0x1), mask_name: None, reason: SideExitReason::BlockParamProxyNotIseqOrIfunc, state: exit_id, recompile: Some(Recompile) }); + fun.push_insn(unmodified_block, Insn::GuardAnyBitSet { val: block_handler, mask: Const::CUInt64(0x1), mask_name: None, reason: Box::new(SideExitReason::BlockParamProxyNotIseqOrIfunc), state: exit_id, recompile: Some(Recompile) }); // TODO(Shopify/ruby#753): GC root, so we should be able to avoid unnecessary GC tracing let proxy_val = fun.push_insn(unmodified_block, Insn::Const { val: Const::Value(unsafe { rb_block_param_proxy }) }); let mut args = vec![proxy_val]; @@ -8301,7 +8301,7 @@ fn add_iseq_to_hir( return_type: types::BasicObject, elidable: true, }); - fun.push_insn(unmodified_block, Insn::GuardBitEquals { val: is_proc, expected: Const::Value(Qtrue), reason: SideExitReason::BlockParamProxyNotProc, state: exit_id, recompile: Some(Recompile) }); + fun.push_insn(unmodified_block, Insn::GuardBitEquals { val: is_proc, expected: Const::Value(Qtrue), reason: Box::new(SideExitReason::BlockParamProxyNotProc), state: exit_id, recompile: Some(Recompile) }); let mut args = vec![proc_val]; if let Some(local) = original_local { args.push(local); @@ -8405,7 +8405,7 @@ fn add_iseq_to_hir( } } - fun.push_insn(current_block, Insn::SideExit { state: exit_id, reason: SideExitReason::BlockParamProxyProfileNotCovered, recompile: None }); + fun.push_insn(current_block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::BlockParamProxyProfileNotCovered), recompile: None }); } } @@ -8498,7 +8498,7 @@ fn add_iseq_to_hir( let flags = unsafe { rb_vm_ci_flag(call_info) }; if let Err(call_type) = unhandled_call_type(flags) { // Can't handle the call type; side-exit into the interpreter - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledCallType(call_type), recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::UnhandledCallType(call_type)), recompile: None }); break; // End the block } let argc = crate::profile::num_arguments_on_stack(cd); @@ -8506,7 +8506,7 @@ fn add_iseq_to_hir( // Side-exit send fallbacks while tracing to avoid FLAG_FINISH breaking throw TAG_RETURN semantics if unsafe { rb_zjit_iseq_tracing_currently_enabled() } { - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::SendWhileTracing, recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::SendWhileTracing), recompile: None }); break; } let args = state.stack_pop_n(argc as usize)?; @@ -8596,7 +8596,7 @@ fn add_iseq_to_hir( let flags = unsafe { rb_vm_ci_flag(call_info) }; if let Err(call_type) = unhandled_call_type(flags) { // Can't handle tailcall; side-exit into the interpreter - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledCallType(call_type), recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::UnhandledCallType(call_type)), recompile: None }); break; // End the block } let argc = crate::profile::num_arguments_on_stack(cd); @@ -8613,7 +8613,7 @@ fn add_iseq_to_hir( if mid == ID!(induce_side_exit_bang) && state::zjit_module_method_match_serial(ID!(induce_side_exit_bang), &state::INDUCE_SIDE_EXIT_SERIAL) { - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::DirectiveInduced, recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::DirectiveInduced), recompile: None }); break; // End the block } if mid == ID!(induce_compile_failure_bang) @@ -8632,7 +8632,7 @@ fn add_iseq_to_hir( // Side-exit send fallbacks while tracing to avoid FLAG_FINISH breaking throw TAG_RETURN semantics if unsafe { rb_zjit_iseq_tracing_currently_enabled() } { - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::SendWhileTracing, recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::SendWhileTracing), recompile: None }); break; } @@ -8690,12 +8690,12 @@ fn add_iseq_to_hir( let flags = unsafe { rb_vm_ci_flag(call_info) }; if let Err(call_type) = unhandled_call_type(flags) { // Can't handle tailcall; side-exit into the interpreter - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledCallType(call_type), recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::UnhandledCallType(call_type)), recompile: None }); break; // End the block } // Side-exit send fallbacks while tracing to avoid FLAG_FINISH breaking throw TAG_RETURN semantics if unsafe { rb_zjit_iseq_tracing_currently_enabled() } { - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::SendWhileTracing, recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::SendWhileTracing), recompile: None }); break; } let block_arg = (flags & VM_CALL_ARGS_BLOCKARG) != 0; @@ -8728,12 +8728,12 @@ fn add_iseq_to_hir( let forwarding = (flags & VM_CALL_FORWARDING) != 0; if let Err(call_type) = unhandled_call_type(flags) { // Can't handle the call type; side-exit into the interpreter - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledCallType(call_type), recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::UnhandledCallType(call_type)), recompile: None }); break; // End the block } // Side-exit send fallbacks while tracing to avoid FLAG_FINISH breaking throw TAG_RETURN semantics if unsafe { rb_zjit_iseq_tracing_currently_enabled() } { - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::SendWhileTracing, recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::SendWhileTracing), recompile: None }); break; } let argc = unsafe { vm_ci_argc((*cd).ci) }; @@ -8757,12 +8757,12 @@ fn add_iseq_to_hir( let flags = unsafe { rb_vm_ci_flag(call_info) }; if let Err(call_type) = unhandled_call_type(flags) { // Can't handle tailcall; side-exit into the interpreter - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledCallType(call_type), recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::UnhandledCallType(call_type)), recompile: None }); break; // End the block } // Side-exit send fallbacks while tracing to avoid FLAG_FINISH breaking throw TAG_RETURN semantics if unsafe { rb_zjit_iseq_tracing_currently_enabled() } { - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::SendWhileTracing, recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::SendWhileTracing), recompile: None }); break; } let args = state.stack_pop_n(crate::profile::num_arguments_on_stack(cd))?; @@ -8787,12 +8787,12 @@ fn add_iseq_to_hir( let forwarding = (flags & VM_CALL_FORWARDING) != 0; if let Err(call_type) = unhandled_call_type(flags) { // Can't handle tailcall; side-exit into the interpreter - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledCallType(call_type), recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::UnhandledCallType(call_type)), recompile: None }); break; // End the block } // Side-exit send fallbacks while tracing to avoid FLAG_FINISH breaking throw TAG_RETURN semantics if unsafe { rb_zjit_iseq_tracing_currently_enabled() } { - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::SendWhileTracing, recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::SendWhileTracing), recompile: None }); break; } let argc = unsafe { vm_ci_argc((*cd).ci) }; @@ -8815,12 +8815,12 @@ fn add_iseq_to_hir( let flags = unsafe { rb_vm_ci_flag(call_info) }; if let Err(call_type) = unhandled_call_type(flags) { // Can't handle tailcall; side-exit into the interpreter - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledCallType(call_type), recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::UnhandledCallType(call_type)), recompile: None }); break; // End the block } // Side-exit send fallbacks while tracing to avoid FLAG_FINISH breaking throw TAG_RETURN semantics if unsafe { rb_zjit_iseq_tracing_currently_enabled() } { - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::SendWhileTracing, recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::SendWhileTracing), recompile: None }); break; } let args = state.stack_pop_n(crate::profile::num_arguments_on_stack(cd))?; @@ -8901,7 +8901,7 @@ fn add_iseq_to_hir( // TODO: We only really need this if self_val is a class/module if !fun.assume_single_ractor_mode(block, exit_id) { // gen_getivar assumes single Ractor; side-exit into the interpreter - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledYARVInsn(opcode), recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::UnhandledYARVInsn(opcode)), recompile: None }); break; // End the block } if let Some(summary) = fun.polymorphic_summary(&profiles, self_param, exit_id) { @@ -8974,7 +8974,7 @@ fn add_iseq_to_hir( // TODO: We only really need this if self_val is a class/module if !fun.assume_single_ractor_mode(block, exit_id) { // gen_setivar assumes single Ractor; side-exit into the interpreter - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledYARVInsn(opcode), recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::UnhandledYARVInsn(opcode)), recompile: None }); break; // End the block } let val = state.stack_pop()?; @@ -9026,7 +9026,7 @@ fn add_iseq_to_hir( // TODO: Support passing arguments on the stack in C calls // +2 for ec, self if (unsafe { (*bf).argc } + 2) as usize > C_ARG_OPNDS.len() { - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::TooManyArgsForLir, recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::TooManyArgsForLir), recompile: None }); break; // End the block } @@ -9061,7 +9061,7 @@ fn add_iseq_to_hir( // +2 for ec, self let argc = unsafe { (*bf).argc } as usize; if argc + 2 > C_ARG_OPNDS.len() { - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::TooManyArgsForLir, recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::TooManyArgsForLir), recompile: None }); break; // End the block } @@ -9140,7 +9140,7 @@ fn add_iseq_to_hir( if svar == 0 { // TODO: Handle non-backref - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnknownSpecialVariable(key), recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::UnknownSpecialVariable(key)), recompile: None }); // End the block break; } else if svar & 0x01 != 0 { @@ -9163,14 +9163,14 @@ fn add_iseq_to_hir( // (reverse?) // // Unhandled opcode; side-exit into the interpreter - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledYARVInsn(opcode), recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::UnhandledYARVInsn(opcode)), recompile: None }); break; // End the block } let val = state.stack_pop()?; let array = fun.push_insn(block, Insn::GuardType { val, guard_type: types::ArrayExact, state: exit_id, recompile: None }); let length = fun.push_insn(block, Insn::ArrayLength { array }); let expected = fun.push_insn(block, Insn::Const { val: Const::CInt64(num as i64) }); - fun.push_insn(block, Insn::GuardGreaterEq { left: length, right: expected, reason: SideExitReason::ExpandArray, state: exit_id }); + fun.push_insn(block, Insn::GuardGreaterEq { left: length, right: expected, reason: Box::new(SideExitReason::ExpandArray), state: exit_id }); for i in (0..num).rev() { // We do not emit a length guard here because in-bounds is already // ensured by the expandarray length check above. @@ -9181,7 +9181,7 @@ fn add_iseq_to_hir( } _ => { // Unhandled opcode; side-exit into the interpreter - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledYARVInsn(opcode), recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::UnhandledYARVInsn(opcode)), recompile: None }); break; // End the block } } diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs index cdb19ddb3b51e1..41506143853119 100644 --- a/zjit/src/hir/tests.rs +++ b/zjit/src/hir/tests.rs @@ -7,7 +7,7 @@ mod size_tests { #[test] fn test_size_of_insn() { - assert_eq!(std::mem::size_of::(), 88); + assert_eq!(std::mem::size_of::(), 80); } #[test] From e6bf65273b5cf54c5a132c4903e049a50c6ab5ce Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Tue, 7 Jul 2026 19:18:55 +0900 Subject: [PATCH 8/8] Decouple string test from slot size --- test/ruby/test_string.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/ruby/test_string.rb b/test/ruby/test_string.rb index 59dce175f13341..a7affd46cdf27b 100644 --- a/test/ruby/test_string.rb +++ b/test/ruby/test_string.rb @@ -678,7 +678,7 @@ def test_string_interpolations_across_heaps_get_embedded omit if GC::INTERNAL_CONSTANTS[:HEAP_COUNT] == 1 require 'objspace' - base_slot_size = GC.stat_heap(0, :slot_size) - GC::INTERNAL_CONSTANTS[:RVALUE_OVERHEAD] + base_slot_size = 64 small_obj_size = (base_slot_size / 2) large_obj_size = base_slot_size * 2