From f55421e81c20dea72eb81b38cd1093e5d9936597 Mon Sep 17 00:00:00 2001 From: Jacob Date: Fri, 7 Nov 2025 13:50:17 -0500 Subject: [PATCH 1/7] ZJIT: Add compilation for checkkeyword (#14764)
Before
``` **ZJIT: Printing ZJIT statistics on exit*** Top-20 not inlined C methods (64.0% of total 3,683,424): Kernel#is_a?: 427,127 (11.6%) Hash#[]=: 426,276 (11.6%) String#start_with?: 336,245 ( 9.1%) ObjectSpace::WeakKeyMap#[]: 139,406 ( 3.8%) Hash#fetch: 127,291 ( 3.5%) String#hash: 79,259 ( 2.2%) Process.clock_gettime: 74,658 ( 2.0%) Array#any?: 74,441 ( 2.0%) Integer#==: 71,067 ( 1.9%) Kernel#dup: 68,058 ( 1.8%) Hash#key?: 62,306 ( 1.7%) Regexp#match?: 62,247 ( 1.7%) SQLite3::Statement#step: 61,172 ( 1.7%) SQLite3::Statement#done?: 61,172 ( 1.7%) Kernel#Array: 55,015 ( 1.5%) Integer#<=>: 49,127 ( 1.3%) String.new: 48,363 ( 1.3%) IO#read: 47,753 ( 1.3%) Array#include?: 43,307 ( 1.2%) Struct#initialize: 42,650 ( 1.2%) Top-3 not optimized method types for send (100.0% of total 1,022,743): iseq: 736,483 (72.0%) cfunc: 286,174 (28.0%) null: 86 ( 0.0%) Top-6 not optimized method types for send_without_block (100.0% of total 189,556): optimized_call: 115,966 (61.2%) optimized_send: 36,767 (19.4%) optimized_struct_aset: 33,788 (17.8%) null: 2,521 ( 1.3%) optimized_block_call: 510 ( 0.3%) cfunc: 4 ( 0.0%) Top-13 not optimized instructions (100.0% of total 1,648,882): invokesuper: 697,471 (42.3%) invokeblock: 496,687 (30.1%) sendforward: 221,094 (13.4%) opt_eq: 147,620 ( 9.0%) opt_minus: 40,865 ( 2.5%) opt_plus: 22,912 ( 1.4%) opt_send_without_block: 18,932 ( 1.1%) opt_gt: 867 ( 0.1%) opt_mult: 768 ( 0.0%) opt_neq: 654 ( 0.0%) opt_or: 508 ( 0.0%) opt_lt: 359 ( 0.0%) opt_ge: 145 ( 0.0%) Top-13 send fallback reasons (100.0% of total 8,308,826): send_without_block_polymorphic: 3,174,975 (38.2%) not_optimized_instruction: 1,648,882 (19.8%) fancy_call_feature: 1,072,807 (12.9%) send_not_optimized_method_type: 1,022,743 (12.3%) send_no_profiles: 599,715 ( 7.2%) send_without_block_no_profiles: 486,108 ( 5.9%) send_without_block_not_optimized_optimized_method_type: 187,031 ( 2.3%) send_polymorphic: 101,834 ( 1.2%) obj_to_string_not_string: 7,610 ( 0.1%) send_without_block_not_optimized_method_type: 2,525 ( 0.0%) send_without_block_direct_too_many_args: 2,369 ( 0.0%) send_without_block_cfunc_array_variadic: 2,190 ( 0.0%) ccall_with_frame_too_many_args: 37 ( 0.0%) Top-8 popular unsupported argument-parameter features (100.0% of total 1,209,121): param_opt: 583,595 (48.3%) param_forwardable: 178,162 (14.7%) param_block: 162,689 (13.5%) param_kw: 150,575 (12.5%) param_rest: 90,091 ( 7.5%) param_kwrest: 33,791 ( 2.8%) caller_splat: 10,214 ( 0.8%) caller_kw_splat: 4 ( 0.0%) Top-7 unhandled YARV insns (100.0% of total 128,032): checkkeyword: 88,698 (69.3%) invokesuperforward: 22,296 (17.4%) getblockparam: 16,292 (12.7%) getconstant: 336 ( 0.3%) checkmatch: 290 ( 0.2%) setblockparam: 101 ( 0.1%) once: 19 ( 0.0%) Top-1 compile error reasons (100.0% of total 21,283): exception_handler: 21,283 (100.0%) Top-18 side exit reasons (100.0% of total 2,335,562): guard_type_failure: 677,930 (29.0%) guard_shape_failure: 410,183 (17.6%) unhandled_kwarg: 235,100 (10.1%) patchpoint_stable_constant_names: 206,172 ( 8.8%) block_param_proxy_not_iseq_or_ifunc: 199,931 ( 8.6%) patchpoint_no_singleton_class: 188,359 ( 8.1%) unhandled_yarv_insn: 128,032 ( 5.5%) unknown_newarray_send: 124,805 ( 5.3%) patchpoint_method_redefined: 73,062 ( 3.1%) unhandled_hir_insn: 56,688 ( 2.4%) compile_error: 21,283 ( 0.9%) block_param_proxy_modified: 11,647 ( 0.5%) fixnum_mult_overflow: 954 ( 0.0%) patchpoint_no_ep_escape: 813 ( 0.0%) guard_bit_equals_failure: 316 ( 0.0%) obj_to_string_fallback: 230 ( 0.0%) interrupt: 35 ( 0.0%) guard_type_not_failure: 22 ( 0.0%) send_count: 26,775,579 dynamic_send_count: 8,308,826 (31.0%) optimized_send_count: 18,466,753 (69.0%) iseq_optimized_send_count: 7,611,729 (28.4%) inline_cfunc_optimized_send_count: 5,935,290 (22.2%) inline_iseq_optimized_send_count: 657,555 ( 2.5%) non_variadic_cfunc_optimized_send_count: 3,169,054 (11.8%) variadic_cfunc_optimized_send_count: 1,093,125 ( 4.1%) dynamic_getivar_count: 2,793,635 dynamic_setivar_count: 3,040,844 compiled_iseq_count: 4,496 failed_iseq_count: 0 compile_time: 915ms profile_time: 6ms gc_time: 6ms invalidation_time: 20ms vm_write_pc_count: 26,857,114 vm_write_sp_count: 25,770,558 vm_write_locals_count: 25,770,558 vm_write_stack_count: 25,770,558 vm_write_to_parent_iseq_local_count: 106,036 vm_read_from_parent_iseq_local_count: 3,213,992 guard_type_count: 27,683,170 guard_type_exit_ratio: 2.4% code_region_bytes: 32,178,176 side_exit_count: 2,335,562 total_insn_count: 170,714,077 vm_insn_count: 28,999,194 zjit_insn_count: 141,714,883 ratio_in_zjit: 83.0% ```
After
``` **ZJIT: Printing ZJIT statistics on exit*** Top-20 not inlined C methods (63.9% of total 3,686,703): Kernel#is_a?: 427,123 (11.6%) Hash#[]=: 426,276 (11.6%) String#start_with?: 336,245 ( 9.1%) ObjectSpace::WeakKeyMap#[]: 139,406 ( 3.8%) Hash#fetch: 127,291 ( 3.5%) String#hash: 79,259 ( 2.1%) Process.clock_gettime: 74,658 ( 2.0%) Array#any?: 74,441 ( 2.0%) Integer#==: 71,067 ( 1.9%) Kernel#dup: 68,058 ( 1.8%) Regexp#match?: 62,336 ( 1.7%) Hash#key?: 62,306 ( 1.7%) SQLite3::Statement#step: 61,172 ( 1.7%) SQLite3::Statement#done?: 61,172 ( 1.7%) Kernel#Array: 55,048 ( 1.5%) Integer#<=>: 49,127 ( 1.3%) String.new: 48,363 ( 1.3%) IO#read: 47,753 ( 1.3%) Array#include?: 43,309 ( 1.2%) Struct#initialize: 42,650 ( 1.2%) Top-3 not optimized method types for send (100.0% of total 1,026,413): iseq: 737,496 (71.9%) cfunc: 288,831 (28.1%) null: 86 ( 0.0%) Top-6 not optimized method types for send_without_block (100.0% of total 189,556): optimized_call: 115,966 (61.2%) optimized_send: 36,767 (19.4%) optimized_struct_aset: 33,788 (17.8%) null: 2,521 ( 1.3%) optimized_block_call: 510 ( 0.3%) cfunc: 4 ( 0.0%) Top-13 not optimized instructions (100.0% of total 1,648,949): invokesuper: 697,452 (42.3%) invokeblock: 496,687 (30.1%) sendforward: 221,094 (13.4%) opt_eq: 147,620 ( 9.0%) opt_minus: 40,863 ( 2.5%) opt_plus: 22,912 ( 1.4%) opt_send_without_block: 19,020 ( 1.2%) opt_gt: 867 ( 0.1%) opt_mult: 768 ( 0.0%) opt_neq: 654 ( 0.0%) opt_or: 508 ( 0.0%) opt_lt: 359 ( 0.0%) opt_ge: 145 ( 0.0%) Top-13 send fallback reasons (100.0% of total 8,318,975): send_without_block_polymorphic: 3,177,471 (38.2%) not_optimized_instruction: 1,648,949 (19.8%) fancy_call_feature: 1,075,143 (12.9%) send_not_optimized_method_type: 1,026,413 (12.3%) send_no_profiles: 599,748 ( 7.2%) send_without_block_no_profiles: 486,190 ( 5.8%) send_without_block_not_optimized_optimized_method_type: 187,031 ( 2.2%) send_polymorphic: 102,497 ( 1.2%) obj_to_string_not_string: 8,412 ( 0.1%) send_without_block_not_optimized_method_type: 2,525 ( 0.0%) send_without_block_direct_too_many_args: 2,369 ( 0.0%) send_without_block_cfunc_array_variadic: 2,190 ( 0.0%) ccall_with_frame_too_many_args: 37 ( 0.0%) Top-8 popular unsupported argument-parameter features (100.0% of total 1,211,457): param_opt: 584,073 (48.2%) param_forwardable: 178,907 (14.8%) param_block: 162,689 (13.4%) param_kw: 151,688 (12.5%) param_rest: 90,091 ( 7.4%) param_kwrest: 33,791 ( 2.8%) caller_splat: 10,214 ( 0.8%) caller_kw_splat: 4 ( 0.0%) Top-6 unhandled YARV insns (100.0% of total 39,334): invokesuperforward: 22,296 (56.7%) getblockparam: 16,292 (41.4%) getconstant: 336 ( 0.9%) checkmatch: 290 ( 0.7%) setblockparam: 101 ( 0.3%) once: 19 ( 0.0%) Top-1 compile error reasons (100.0% of total 21,283): exception_handler: 21,283 (100.0%) Top-18 side exit reasons (100.0% of total 2,253,541): guard_type_failure: 682,695 (30.3%) guard_shape_failure: 410,183 (18.2%) unhandled_kwarg: 236,780 (10.5%) patchpoint_stable_constant_names: 206,310 ( 9.2%) block_param_proxy_not_iseq_or_ifunc: 199,931 ( 8.9%) patchpoint_no_singleton_class: 188,438 ( 8.4%) unknown_newarray_send: 124,805 ( 5.5%) patchpoint_method_redefined: 73,056 ( 3.2%) unhandled_hir_insn: 56,686 ( 2.5%) unhandled_yarv_insn: 39,334 ( 1.7%) compile_error: 21,283 ( 0.9%) block_param_proxy_modified: 11,647 ( 0.5%) fixnum_mult_overflow: 954 ( 0.0%) patchpoint_no_ep_escape: 813 ( 0.0%) guard_bit_equals_failure: 316 ( 0.0%) obj_to_string_fallback: 230 ( 0.0%) interrupt: 58 ( 0.0%) guard_type_not_failure: 22 ( 0.0%) send_count: 27,032,751 dynamic_send_count: 8,318,975 (30.8%) optimized_send_count: 18,713,776 (69.2%) iseq_optimized_send_count: 7,809,698 (28.9%) inline_cfunc_optimized_send_count: 5,980,083 (22.1%) inline_iseq_optimized_send_count: 657,677 ( 2.4%) non_variadic_cfunc_optimized_send_count: 3,170,381 (11.7%) variadic_cfunc_optimized_send_count: 1,095,937 ( 4.1%) dynamic_getivar_count: 2,793,987 dynamic_setivar_count: 3,350,905 compiled_iseq_count: 4,498 failed_iseq_count: 0 compile_time: 884ms profile_time: 6ms gc_time: 6ms invalidation_time: 19ms vm_write_pc_count: 27,417,915 vm_write_sp_count: 26,327,928 vm_write_locals_count: 26,327,928 vm_write_stack_count: 26,327,928 vm_write_to_parent_iseq_local_count: 106,036 vm_read_from_parent_iseq_local_count: 3,213,992 guard_type_count: 27,937,831 guard_type_exit_ratio: 2.4% code_region_bytes: 32,571,392 side_exit_count: 2,253,541 total_insn_count: 170,630,429 vm_insn_count: 26,617,244 zjit_insn_count: 144,013,185 ratio_in_zjit: 84.4% ```
--- zjit/src/codegen.rs | 7 ++++ zjit/src/hir.rs | 26 ++++++++++++ zjit/src/hir/tests.rs | 97 ++++++++++++++++++++++++++++++++++++++++++- zjit/src/stats.rs | 2 + 4 files changed, 131 insertions(+), 1 deletion(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index fd34b6e0dfba17..3ef93ee3d971ec 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -433,6 +433,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::SetClassVar { id, val, ic, state } => no_output!(gen_setclassvar(jit, asm, *id, opnd!(val), *ic, &function.frame_state(*state))), Insn::SetIvar { self_val, id, val, state } => no_output!(gen_setivar(jit, asm, opnd!(self_val), *id, opnd!(val), &function.frame_state(*state))), Insn::SetInstanceVariable { self_val, id, ic, val, state } => no_output!(gen_set_instance_variable(jit, asm, opnd!(self_val), *id, *ic, opnd!(val), &function.frame_state(*state))), + Insn::FixnumBitCheck { val, index } => gen_fixnum_bit_check(asm, opnd!(val), *index), Insn::SideExit { state, reason } => no_output!(gen_side_exit(jit, asm, reason, &function.frame_state(*state))), Insn::PutSpecialObject { value_type } => gen_putspecialobject(asm, *value_type), Insn::AnyToString { val, str, state } => gen_anytostring(asm, opnd!(val), opnd!(str), &function.frame_state(*state)), @@ -652,6 +653,12 @@ fn gen_get_constant_path(jit: &JITState, asm: &mut Assembler, ic: *const iseq_in asm_ccall!(asm, rb_vm_opt_getconstant_path, EC, CFP, Opnd::const_ptr(ic)) } +fn gen_fixnum_bit_check(asm: &mut Assembler, val: Opnd, index: u8) -> Opnd { + let bit_test: u64 = 0x01 << (index + 1); + asm.test(val, bit_test.into()); + asm.csel_z(Qtrue.into(), Qfalse.into()) +} + fn gen_invokebuiltin(jit: &JITState, asm: &mut Assembler, state: &FrameState, bf: &rb_builtin_function, leaf: bool, args: Vec) -> lir::Opnd { assert!(bf.argc + 2 <= C_ARG_OPNDS.len() as i32, "gen_invokebuiltin should not be called for builtin function {} with too many arguments: {}", diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 5a15aac06c43cb..7c180929abef9f 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -462,6 +462,7 @@ pub enum SideExitReason { UnhandledHIRInsn(InsnId), UnhandledYARVInsn(u32), UnhandledCallType(CallType), + TooManyKeywordParameters, FixnumAddOverflow, FixnumSubOverflow, FixnumMultOverflow, @@ -664,6 +665,9 @@ pub enum Insn { /// Kernel#block_given? but without pushing a frame. Similar to [`Insn::Defined`] with /// `DEFINED_YIELD` IsBlockGiven, + /// Test the bit at index of val, a Fixnum. + /// Return Qtrue if the bit is set, else Qfalse. + FixnumBitCheck { val: InsnId, index: u8 }, /// Get a global variable named `id` GetGlobal { id: ID, state: InsnId }, @@ -1147,6 +1151,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::PatchPoint { invariant, .. } => { write!(f, "PatchPoint {}", invariant.print(self.ptr_map)) }, Insn::GetConstantPath { ic, .. } => { write!(f, "GetConstantPath {:p}", self.ptr_map.map_ptr(ic)) }, Insn::IsBlockGiven => { write!(f, "IsBlockGiven") }, + Insn::FixnumBitCheck {val, index} => { write!(f, "FixnumBitCheck {val}, {index}") }, Insn::CCall { cfunc, args, name, return_type: _, elidable: _ } => { write!(f, "CCall {}@{:p}", name.contents_lossy(), self.ptr_map.map_ptr(cfunc))?; for arg in args { @@ -1691,6 +1696,7 @@ impl Function { } }, &Return { val } => Return { val: find!(val) }, + &FixnumBitCheck { val, index } => FixnumBitCheck { val: find!(val), index }, &Throw { throw_state, val, state } => Throw { throw_state, val: find!(val), state }, &StringCopy { val, chilled, state } => StringCopy { val: find!(val), chilled, state }, &StringIntern { val, state } => StringIntern { val: find!(val), state: find!(state) }, @@ -1952,6 +1958,7 @@ impl Function { Insn::DefinedIvar { pushval, .. } => Type::from_value(*pushval).union(types::NilClass), Insn::GetConstantPath { .. } => types::BasicObject, Insn::IsBlockGiven => types::BoolExact, + Insn::FixnumBitCheck { .. } => types::BoolExact, Insn::ArrayMax { .. } => types::BasicObject, Insn::ArrayInclude { .. } => types::BoolExact, Insn::DupArrayInclude { .. } => types::BoolExact, @@ -3245,6 +3252,9 @@ impl Function { | &Insn::GetConstantPath { ic: _, state } => { worklist.push_back(state); } + &Insn::FixnumBitCheck { val, index: _ } => { + worklist.push_back(val) + } &Insn::ArrayMax { ref elements, state } | &Insn::NewHash { ref elements, state } | &Insn::NewArray { ref elements, state } => { @@ -4648,6 +4658,22 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); state.stack_push(fun.push_insn(block, Insn::DefinedIvar { self_val: self_param, id, pushval, state: exit_id })); } + YARVINSN_checkkeyword => { + // When a keyword is unspecified past index 32, a hash will be used instead. + // This can only happen in iseqs taking more than 32 keywords. + // In this case, we side exit to the interpreter. + // TODO(Jacob): Replace the magic number 32 with a named constant. (Can be completed after PR 15039) + if unsafe {(*rb_get_iseq_body_param_keyword(iseq)).num >= 32} { + let exit_id = fun.push_insn(block, Insn::Snapshot {state: exit_state}); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::TooManyKeywordParameters }); + break; + } + let ep_offset = get_arg(pc, 0).as_u32(); + let index = get_arg(pc, 1).as_u64(); + let index: u8 = index.try_into().map_err(|_| ParseError::MalformedIseq(insn_idx))?; + let val = fun.push_insn(block, Insn::GetLocal { ep_offset, level: 0, use_sp: false, rest_param: false }); + state.stack_push(fun.push_insn(block, Insn::FixnumBitCheck { val, index })); + } YARVINSN_opt_getconstant_path => { let ic = get_arg(pc, 0).as_ptr(); let snapshot = fun.push_insn(block, Insn::Snapshot { state: exit_state }); diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs index af3fd3de9153d0..accc10471911ad 100644 --- a/zjit/src/hir/tests.rs +++ b/zjit/src/hir/tests.rs @@ -3273,4 +3273,99 @@ pub mod hir_build_tests { SideExit UnhandledYARVInsn(expandarray) "); } -} + + #[test] + fn test_checkkeyword_tests_fixnum_bit() { + eval(r#" + def test(kw: 1 + 1) = kw + "#); + assert_contains_opcode("test", YARVINSN_checkkeyword); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v14:BasicObject = GetLocal l0, EP@3 + v15:BoolExact = FixnumBitCheck v14, 0 + CheckInterrupts + v18:CBool = Test v15 + IfTrue v18, bb3(v10, v11, v12) + v20:Fixnum[1] = Const Value(1) + v21:Fixnum[1] = Const Value(1) + v25:BasicObject = SendWithoutBlock v20, :+, v21 + PatchPoint NoEPEscape(test) + Jump bb3(v10, v25, v12) + bb3(v29:BasicObject, v30:BasicObject, v31:BasicObject): + PatchPoint NoEPEscape(test) + CheckInterrupts + Return v30 + "); + } + + #[test] + fn test_checkkeyword_too_many_keywords_causes_side_exit() { + eval(r#" + def test(k1: k1, k2: k2, k3: k3, k4: k4, k5: k5, + k6: k6, k7: k7, k8: k8, k9: k9, k10: k10, k11: k11, + k12: k12, k13: k13, k14: k14, k15: k15, k16: k16, + k17: k17, k18: k18, k19: k19, k20: k20, k21: k21, + k22: k22, k23: k23, k24: k24, k25: k25, k26: k26, + k27: k27, k28: k28, k29: k29, k30: k30, k31: k31, + k32: k32, k33: k33) = k1 + "#); + assert_contains_opcode("test", YARVINSN_checkkeyword); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@37 + v3:BasicObject = GetLocal l0, SP@36 + v4:BasicObject = GetLocal l0, SP@35 + v5:BasicObject = GetLocal l0, SP@34 + v6:BasicObject = GetLocal l0, SP@33 + v7:BasicObject = GetLocal l0, SP@32 + v8:BasicObject = GetLocal l0, SP@31 + v9:BasicObject = GetLocal l0, SP@30 + v10:BasicObject = GetLocal l0, SP@29 + v11:BasicObject = GetLocal l0, SP@28 + v12:BasicObject = GetLocal l0, SP@27 + v13:BasicObject = GetLocal l0, SP@26 + v14:BasicObject = GetLocal l0, SP@25 + v15:BasicObject = GetLocal l0, SP@24 + v16:BasicObject = GetLocal l0, SP@23 + v17:BasicObject = GetLocal l0, SP@22 + v18:BasicObject = GetLocal l0, SP@21 + v19:BasicObject = GetLocal l0, SP@20 + v20:BasicObject = GetLocal l0, SP@19 + v21:BasicObject = GetLocal l0, SP@18 + v22:BasicObject = GetLocal l0, SP@17 + v23:BasicObject = GetLocal l0, SP@16 + v24:BasicObject = GetLocal l0, SP@15 + v25:BasicObject = GetLocal l0, SP@14 + v26:BasicObject = GetLocal l0, SP@13 + v27:BasicObject = GetLocal l0, SP@12 + v28:BasicObject = GetLocal l0, SP@11 + v29:BasicObject = GetLocal l0, SP@10 + v30:BasicObject = GetLocal l0, SP@9 + v31:BasicObject = GetLocal l0, SP@8 + v32:BasicObject = GetLocal l0, SP@7 + v33:BasicObject = GetLocal l0, SP@6 + v34:BasicObject = GetLocal l0, SP@5 + v35:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) + bb1(v38:BasicObject, v39:BasicObject, v40:BasicObject, v41:BasicObject, v42:BasicObject, v43:BasicObject, v44:BasicObject, v45:BasicObject, v46:BasicObject, v47:BasicObject, v48:BasicObject, v49:BasicObject, v50:BasicObject, v51:BasicObject, v52:BasicObject, v53:BasicObject, v54:BasicObject, v55:BasicObject, v56:BasicObject, v57:BasicObject, v58:BasicObject, v59:BasicObject, v60:BasicObject, v61:BasicObject, v62:BasicObject, v63:BasicObject, v64:BasicObject, v65:BasicObject, v66:BasicObject, v67:BasicObject, v68:BasicObject, v69:BasicObject, v70:BasicObject, v71:BasicObject, v72:BasicObject): + EntryPoint JIT(0) + Jump bb2(v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63, v64, v65, v66, v67, v68, v69, v70, v71, v72) + bb2(v74:BasicObject, v75:BasicObject, v76:BasicObject, v77:BasicObject, v78:BasicObject, v79:BasicObject, v80:BasicObject, v81:BasicObject, v82:BasicObject, v83:BasicObject, v84:BasicObject, v85:BasicObject, v86:BasicObject, v87:BasicObject, v88:BasicObject, v89:BasicObject, v90:BasicObject, v91:BasicObject, v92:BasicObject, v93:BasicObject, v94:BasicObject, v95:BasicObject, v96:BasicObject, v97:BasicObject, v98:BasicObject, v99:BasicObject, v100:BasicObject, v101:BasicObject, v102:BasicObject, v103:BasicObject, v104:BasicObject, v105:BasicObject, v106:BasicObject, v107:BasicObject, v108:BasicObject): + SideExit TooManyKeywordParameters + "); + } + } diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index e1d7c692ed52e0..6b3f9b5ce8179b 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -159,6 +159,7 @@ make_counters! { exit_stackoverflow, exit_block_param_proxy_modified, exit_block_param_proxy_not_iseq_or_ifunc, + exit_too_many_keyword_parameters, } // Send fallback counters that are summed as dynamic_send_count @@ -395,6 +396,7 @@ pub fn side_exit_counter(reason: crate::hir::SideExitReason) -> Counter { StackOverflow => exit_stackoverflow, BlockParamProxyModified => exit_block_param_proxy_modified, BlockParamProxyNotIseqOrIfunc => exit_block_param_proxy_not_iseq_or_ifunc, + TooManyKeywordParameters => exit_too_many_keyword_parameters, PatchPoint(Invariant::BOPRedefined { .. }) => exit_patchpoint_bop_redefined, PatchPoint(Invariant::MethodRedefined { .. }) From 4816969c28def82d2fe57045758f5b39b3ac8081 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 7 Nov 2025 07:12:25 +0900 Subject: [PATCH 2/7] [ruby/rubygems] Replaced pathname auto-loading in bootstrap of bundler https://github.com/ruby/rubygems/commit/79ba4a537d --- lib/bundler.rb | 2 +- lib/bundler/shared_helpers.rb | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/bundler.rb b/lib/bundler.rb index e76dfde122d7e8..51ea3beeb0f70a 100644 --- a/lib/bundler.rb +++ b/lib/bundler.rb @@ -2,7 +2,7 @@ require_relative "bundler/rubygems_ext" require_relative "bundler/vendored_fileutils" -require "pathname" +autoload :Pathname, "pathname" unless defined?(Pathname) require "rbconfig" require_relative "bundler/errors" diff --git a/lib/bundler/shared_helpers.rb b/lib/bundler/shared_helpers.rb index 987a68afd75326..a03f1f83f0128a 100644 --- a/lib/bundler/shared_helpers.rb +++ b/lib/bundler/shared_helpers.rb @@ -4,8 +4,6 @@ require_relative "rubygems_integration" require_relative "current_ruby" -autoload :Pathname, "pathname" - module Bundler autoload :WINDOWS, File.expand_path("constants", __dir__) autoload :FREEBSD, File.expand_path("constants", __dir__) From 9767b31090f923200abff68657c6489e8f32cfde Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 7 Nov 2025 07:14:31 +0900 Subject: [PATCH 3/7] [ruby/rubygems] Removed unnecessary loading of pathname https://github.com/ruby/rubygems/commit/6e965b7872 --- lib/bundler/cli/gem.rb | 2 -- lib/bundler/compact_index_client.rb | 1 - lib/bundler/vendor/thor/lib/thor/runner.rb | 2 +- spec/bundler/support/path.rb | 1 - 4 files changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/bundler/cli/gem.rb b/lib/bundler/cli/gem.rb index 23b29bf36bee36..20d678d66d7e98 100644 --- a/lib/bundler/cli/gem.rb +++ b/lib/bundler/cli/gem.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require "pathname" - module Bundler class CLI Bundler.require_thor_actions diff --git a/lib/bundler/compact_index_client.rb b/lib/bundler/compact_index_client.rb index 37e2ccced8e1d1..6865e30dbcaab1 100644 --- a/lib/bundler/compact_index_client.rb +++ b/lib/bundler/compact_index_client.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -require "pathname" require "set" module Bundler diff --git a/lib/bundler/vendor/thor/lib/thor/runner.rb b/lib/bundler/vendor/thor/lib/thor/runner.rb index 95f8b16e31893a..f0ce6df96ce99b 100644 --- a/lib/bundler/vendor/thor/lib/thor/runner.rb +++ b/lib/bundler/vendor/thor/lib/thor/runner.rb @@ -2,7 +2,7 @@ require_relative "group" require "digest/sha2" -require "pathname" +require "pathname" unless defined?(Pathname) class Bundler::Thor::Runner < Bundler::Thor #:nodoc: map "-T" => :list, "-i" => :install, "-u" => :update, "-v" => :version diff --git a/spec/bundler/support/path.rb b/spec/bundler/support/path.rb index cc750f55d81f53..bbbf5cc9086ac0 100644 --- a/spec/bundler/support/path.rb +++ b/spec/bundler/support/path.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -require "pathname" require "rbconfig" require_relative "env" From 2518aa9117907d7ed08f469df596c0be961fc8fe Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 7 Nov 2025 07:23:07 +0900 Subject: [PATCH 4/7] [ruby/rubygems] Replace Pathname#rmtree to FileUtils.rm_rf directly https://github.com/ruby/rubygems/commit/33c7a9a565 --- spec/bundler/cache/gems_spec.rb | 10 +++++----- spec/bundler/install/gemfile/path_spec.rb | 2 +- spec/bundler/install/gems/compact_index_spec.rb | 2 +- spec/bundler/install/gems/dependency_api_spec.rb | 2 +- spec/bundler/install/path_spec.rb | 4 ++-- spec/bundler/runtime/load_spec.rb | 2 +- spec/bundler/support/builders.rb | 2 +- spec/bundler/update/git_spec.rb | 2 +- 8 files changed, 13 insertions(+), 13 deletions(-) diff --git a/spec/bundler/cache/gems_spec.rb b/spec/bundler/cache/gems_spec.rb index b23d475a5f63cd..c9b85556e1ef1d 100644 --- a/spec/bundler/cache/gems_spec.rb +++ b/spec/bundler/cache/gems_spec.rb @@ -226,7 +226,7 @@ it "re-caches during install" do setup_main_repo - cached_gem("myrack-1.0.0").rmtree + FileUtils.rm_rf cached_gem("myrack-1.0.0") bundle :install expect(out).to include("Updating files in vendor/cache") expect(cached_gem("myrack-1.0.0")).to exist @@ -307,7 +307,7 @@ it "doesn't remove gems cached gems that don't match their remote counterparts, but also refuses to install and prints an error" do setup_main_repo cached_myrack = cached_gem("myrack-1.0.0") - cached_myrack.rmtree + FileUtils.rm_rf cached_myrack build_gem "myrack", "1.0.0", path: cached_myrack.parent, rubygems_version: "1.3.2" @@ -338,7 +338,7 @@ it "raises an error when a cached gem is altered and produces a different checksum than the remote gem" do setup_main_repo - cached_gem("myrack-1.0.0").rmtree + FileUtils.rm_rf cached_gem("myrack-1.0.0") build_gem "myrack", "1.0.0", path: bundled_app("vendor/cache") checksums = checksums_section do |c| @@ -362,14 +362,14 @@ expect(err).to include("1. remove the gem at #{cached_gem("myrack-1.0.0")}") expect(cached_gem("myrack-1.0.0")).to exist - cached_gem("myrack-1.0.0").rmtree + FileUtils.rm_rf cached_gem("myrack-1.0.0") bundle :install expect(cached_gem("myrack-1.0.0")).to exist end it "installs a modified gem with a non-matching checksum when the API implementation does not provide checksums" do setup_main_repo - cached_gem("myrack-1.0.0").rmtree + FileUtils.rm_rf cached_gem("myrack-1.0.0") build_gem "myrack", "1.0.0", path: bundled_app("vendor/cache") pristine_system_gems diff --git a/spec/bundler/install/gemfile/path_spec.rb b/spec/bundler/install/gemfile/path_spec.rb index 31d79ed41ca5da..b55fe3dbbf1e85 100644 --- a/spec/bundler/install/gemfile/path_spec.rb +++ b/spec/bundler/install/gemfile/path_spec.rb @@ -454,7 +454,7 @@ it "handles directories in bin/" do build_lib "foo" - lib_path("foo-1.0").join("foo.gemspec").rmtree + FileUtils.rm_rf lib_path("foo-1.0").join("foo.gemspec") lib_path("foo-1.0").join("bin/performance").mkpath install_gemfile <<-G diff --git a/spec/bundler/install/gems/compact_index_spec.rb b/spec/bundler/install/gems/compact_index_spec.rb index 64c59d4826f357..32a42aa93a4809 100644 --- a/spec/bundler/install/gems/compact_index_spec.rb +++ b/spec/bundler/install/gems/compact_index_spec.rb @@ -698,7 +698,7 @@ def start bundle :install, artifice: "compact_index_forbidden" ensure - home(".gemrc").rmtree + FileUtils.rm_rf home(".gemrc") end end end diff --git a/spec/bundler/install/gems/dependency_api_spec.rb b/spec/bundler/install/gems/dependency_api_spec.rb index 1650df3dfb6ae6..ac986a0c67a2f9 100644 --- a/spec/bundler/install/gems/dependency_api_spec.rb +++ b/spec/bundler/install/gems/dependency_api_spec.rb @@ -649,7 +649,7 @@ def start bundle "install", artifice: "endpoint_marshal_fail" ensure - home(".gemrc").rmtree + FileUtils.rm_rf home(".gemrc") end end end diff --git a/spec/bundler/install/path_spec.rb b/spec/bundler/install/path_spec.rb index beea1e36dbc73d..8c0793642baf15 100644 --- a/spec/bundler/install/path_spec.rb +++ b/spec/bundler/install/path_spec.rb @@ -35,7 +35,7 @@ bundle :install, dir: dir expect(out).to include("installed into `./vendor/bundle`") - dir.rmtree + FileUtils.rm_rf dir end it "prints a message to let the user know where gems where installed" do @@ -181,7 +181,7 @@ def set_bundle_path(type, location) expect(vendored_gems("extensions")).to be_directory expect(the_bundle).to include_gems "very_simple_binary 1.0", source: "remote1" - vendored_gems("extensions").rmtree + FileUtils.rm_rf vendored_gems("extensions") run "require 'very_simple_binary_c'", raise_on_error: false expect(err).to include("Bundler::GemNotFound") diff --git a/spec/bundler/runtime/load_spec.rb b/spec/bundler/runtime/load_spec.rb index 15f3d0eb5bcd6c..472cde87c5421f 100644 --- a/spec/bundler/runtime/load_spec.rb +++ b/spec/bundler/runtime/load_spec.rb @@ -68,7 +68,7 @@ begin expect { Bundler.load }.to raise_error(Bundler::GemfileNotFound) ensure - bundler_gemfile.rmtree if @remove_bundler_gemfile + FileUtils.rm_rf bundler_gemfile if @remove_bundler_gemfile end end end diff --git a/spec/bundler/support/builders.rb b/spec/bundler/support/builders.rb index 50fedd38f16a7b..3ebb7e386490bf 100644 --- a/spec/bundler/support/builders.rb +++ b/spec/bundler/support/builders.rb @@ -463,7 +463,7 @@ def _build(options = {}) FileUtils.mv bundler_path, options[:path] end ensure - build_path.rmtree + FileUtils.rm_rf build_path end end diff --git a/spec/bundler/update/git_spec.rb b/spec/bundler/update/git_spec.rb index 2cb0abe02fb57b..aaa039211e2b2d 100644 --- a/spec/bundler/update/git_spec.rb +++ b/spec/bundler/update/git_spec.rb @@ -199,7 +199,7 @@ gem "foo", :git => "#{lib_path("foo-1.0")}" G - lib_path("foo-1.0").join(".git").rmtree + FileUtils.rm_rf lib_path("foo-1.0").join(".git") bundle :update, all: true, raise_on_error: false expect(err).to include(lib_path("foo-1.0").to_s). From 110f24c47e162db7730757cb4a9b13d1148ec85e Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 7 Nov 2025 09:08:50 +0900 Subject: [PATCH 5/7] [ruby/rubygems] Restore pathname for rake dev:deps https://github.com/ruby/rubygems/commit/89e95d0f15 --- spec/bundler/support/path.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/bundler/support/path.rb b/spec/bundler/support/path.rb index bbbf5cc9086ac0..0a534dd40e44eb 100644 --- a/spec/bundler/support/path.rb +++ b/spec/bundler/support/path.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +require "pathname" unless defined?(Pathname) require "rbconfig" require_relative "env" From a7c23b9a9f726bec7cbd6f89d157d7dd140420da Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 7 Nov 2025 13:16:34 +0900 Subject: [PATCH 6/7] [ruby/rubygems] Use Dir.pwd instead of Pathname.pwd https://github.com/ruby/rubygems/commit/6c161b253d --- lib/bundler/cli/gem.rb | 4 ++-- lib/bundler/shared_helpers.rb | 2 +- lib/bundler/source/path.rb | 2 +- spec/bundler/bundler/shared_helpers_spec.rb | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/bundler/cli/gem.rb b/lib/bundler/cli/gem.rb index 20d678d66d7e98..abfb095d469f81 100644 --- a/lib/bundler/cli/gem.rb +++ b/lib/bundler/cli/gem.rb @@ -24,7 +24,7 @@ def initialize(options, gem_name, thor) thor.destination_root = nil @name = @gem_name - @target = SharedHelpers.pwd.join(gem_name) + @target = Pathname.new(SharedHelpers.pwd).join(gem_name) @extension = options[:ext] @@ -276,7 +276,7 @@ def run private def resolve_name(name) - SharedHelpers.pwd.join(name).basename.to_s + Pathname.new(SharedHelpers.pwd).join(name).basename.to_s end def ask_and_set(key, prompt, explanation) diff --git a/lib/bundler/shared_helpers.rb b/lib/bundler/shared_helpers.rb index a03f1f83f0128a..4c914eb1a447a4 100644 --- a/lib/bundler/shared_helpers.rb +++ b/lib/bundler/shared_helpers.rb @@ -55,7 +55,7 @@ def chdir(dir, &blk) def pwd Bundler.rubygems.ext_lock.synchronize do - Pathname.pwd + Dir.pwd end end diff --git a/lib/bundler/source/path.rb b/lib/bundler/source/path.rb index d258270fe09dd9..82e782ba257a6a 100644 --- a/lib/bundler/source/path.rb +++ b/lib/bundler/source/path.rb @@ -24,7 +24,7 @@ def initialize(options) @path = Pathname.new(options["path"]) expanded_path = expand(@path) @path = if @path.relative? - expanded_path.relative_path_from(root_path.expand_path) + expanded_path.relative_path_from(File.expand_path(root_path)) else expanded_path end diff --git a/spec/bundler/bundler/shared_helpers_spec.rb b/spec/bundler/bundler/shared_helpers_spec.rb index 0aacb93c16e2a9..c93c3e98149a0e 100644 --- a/spec/bundler/bundler/shared_helpers_spec.rb +++ b/spec/bundler/bundler/shared_helpers_spec.rb @@ -159,7 +159,7 @@ let(:pwd_stub) { nil } it "returns the current absolute path" do - expect(subject.pwd).to eq(source_root) + expect(subject.pwd).to eq(source_root.to_s) end end From 7037d8f89e71a13547d031d76747e45cfe930c9f Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Fri, 7 Nov 2025 13:14:07 +0100 Subject: [PATCH 7/7] [ruby/prism] Rename Ruby 3.5 to Ruby 4.0 See https://github.com/ruby/ruby/commit/6d81969b475262aba251e99b518181bdf7c5a523 It leaves the old variant around. RuboCop for examples accesses `Prism::Translation::Parser35` to test against ruby-head. For now I left these simply as an alias https://github.com/ruby/prism/commit/d0a823f045 --- lib/prism/ffi.rb | 4 +--- lib/prism/prism.gemspec | 2 ++ lib/prism/translation.rb | 1 + lib/prism/translation/parser.rb | 6 +++--- lib/prism/translation/parser35.rb | 7 +------ lib/prism/translation/parser40.rb | 13 ++++++++++++ lib/prism/translation/parser_current.rb | 4 ++-- prism/options.c | 20 +++++-------------- prism/options.h | 4 ++-- prism/prism.c | 8 ++++---- test/prism/api/parse_test.rb | 3 +++ .../endless_methods_command_call.txt | 0 .../fixtures/{3.5 => 4.0}/leading_logical.txt | 0 test/prism/fixtures_test.rb | 4 ++-- test/prism/lex_test.rb | 4 ++-- test/prism/locals_test.rb | 4 ++-- test/prism/ractor_test.rb | 2 +- test/prism/ruby/parser_test.rb | 7 ++++--- test/prism/ruby/ripper_test.rb | 4 ++-- test/prism/ruby/ruby_parser_test.rb | 4 ++-- test/prism/test_helper.rb | 3 ++- 21 files changed, 54 insertions(+), 50 deletions(-) create mode 100644 lib/prism/translation/parser40.rb rename test/prism/fixtures/{3.5 => 4.0}/endless_methods_command_call.txt (100%) rename test/prism/fixtures/{3.5 => 4.0}/leading_logical.txt (100%) diff --git a/lib/prism/ffi.rb b/lib/prism/ffi.rb index f6ad6f98b1cb92..7e6103fde775bf 100644 --- a/lib/prism/ffi.rb +++ b/lib/prism/ffi.rb @@ -432,10 +432,8 @@ def dump_options_version(version) 1 when /\A3\.4(\.\d+)?\z/ 2 - when /\A3\.5(\.\d+)?\z/ + when /\A3\.5(\.\d+)?\z/, /\A4\.0(\.\d+)?\z/ 3 - when /\A4\.0(\.\d+)?\z/ - 4 else if current raise CurrentVersionError, RUBY_VERSION diff --git a/lib/prism/prism.gemspec b/lib/prism/prism.gemspec index 168f8211ff384a..10c2eaad209ed4 100644 --- a/lib/prism/prism.gemspec +++ b/lib/prism/prism.gemspec @@ -101,6 +101,7 @@ Gem::Specification.new do |spec| "lib/prism/translation/parser33.rb", "lib/prism/translation/parser34.rb", "lib/prism/translation/parser35.rb", + "lib/prism/translation/parser40.rb", "lib/prism/translation/parser/builder.rb", "lib/prism/translation/parser/compiler.rb", "lib/prism/translation/parser/lexer.rb", @@ -123,6 +124,7 @@ Gem::Specification.new do |spec| "rbi/prism/translation/parser33.rbi", "rbi/prism/translation/parser34.rbi", "rbi/prism/translation/parser35.rbi", + "rbi/prism/translation/parser40.rbi", "rbi/prism/translation/ripper.rbi", "rbi/prism/visitor.rbi", "sig/prism.rbs", diff --git a/lib/prism/translation.rb b/lib/prism/translation.rb index d127f2006c59f2..7933b4a7222e93 100644 --- a/lib/prism/translation.rb +++ b/lib/prism/translation.rb @@ -10,6 +10,7 @@ module Translation # steep:ignore autoload :Parser33, "prism/translation/parser33" autoload :Parser34, "prism/translation/parser34" autoload :Parser35, "prism/translation/parser35" + autoload :Parser40, "prism/translation/parser40" autoload :Ripper, "prism/translation/ripper" autoload :RubyParser, "prism/translation/ruby_parser" end diff --git a/lib/prism/translation/parser.rb b/lib/prism/translation/parser.rb index 1ad7a193c4f18b..23245dc383e9e9 100644 --- a/lib/prism/translation/parser.rb +++ b/lib/prism/translation/parser.rb @@ -84,7 +84,7 @@ def initialize(builder = Prism::Translation::Parser::Builder.new, parser: Prism) end def version # :nodoc: - 35 + 40 end # The default encoding for Ruby files is UTF-8. @@ -356,8 +356,8 @@ def convert_for_prism(version) "3.3.1" when 34 "3.4.0" - when 35 - "3.5.0" + when 35, 40 + "4.0.0" else "latest" end diff --git a/lib/prism/translation/parser35.rb b/lib/prism/translation/parser35.rb index 79cd59cbd96a8e..52eeeb6c8c4d75 100644 --- a/lib/prism/translation/parser35.rb +++ b/lib/prism/translation/parser35.rb @@ -3,11 +3,6 @@ module Prism module Translation - # This class is the entry-point for Ruby 3.5 of `Prism::Translation::Parser`. - class Parser35 < Parser - def version # :nodoc: - 35 - end - end + Parser35 = Parser40 # :nodoc: end end diff --git a/lib/prism/translation/parser40.rb b/lib/prism/translation/parser40.rb new file mode 100644 index 00000000000000..2ec7445882ce36 --- /dev/null +++ b/lib/prism/translation/parser40.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true +# :markup: markdown + +module Prism + module Translation + # This class is the entry-point for Ruby 4.0 of `Prism::Translation::Parser`. + class Parser40 < Parser + def version # :nodoc: + 40 + end + end + end +end diff --git a/lib/prism/translation/parser_current.rb b/lib/prism/translation/parser_current.rb index 1b1794abbe047a..76d71e940926cc 100644 --- a/lib/prism/translation/parser_current.rb +++ b/lib/prism/translation/parser_current.rb @@ -10,8 +10,8 @@ module Translation ParserCurrent = Parser33 when /^3\.4\./ ParserCurrent = Parser34 - when /^3\.5\./ - ParserCurrent = Parser35 + when /^3\.5\./, /^4\.0\./ + ParserCurrent = Parser40 else # Keep this in sync with released Ruby. parser = Parser34 diff --git a/prism/options.c b/prism/options.c index 373d76a21fee45..4a8953da7d2aac 100644 --- a/prism/options.c +++ b/prism/options.c @@ -88,12 +88,7 @@ pm_options_version_set(pm_options_t *options, const char *version, size_t length return true; } - if (strncmp(version, "3.5", 3) == 0) { - options->version = PM_OPTIONS_VERSION_CRUBY_3_5; - return true; - } - - if (strncmp(version, "4.0", 3) == 0) { + if (strncmp(version, "3.5", 3) == 0 || strncmp(version, "4.0", 3) == 0) { options->version = PM_OPTIONS_VERSION_CRUBY_4_0; return true; } @@ -101,23 +96,18 @@ pm_options_version_set(pm_options_t *options, const char *version, size_t length return false; } - if (length >= 4) { - if (strncmp(version, "3.3.", 4) == 0 && is_number(version + 4, length - 4)) { + if (length >= 4 && is_number(version + 4, length - 4)) { + if (strncmp(version, "3.3.", 4) == 0) { options->version = PM_OPTIONS_VERSION_CRUBY_3_3; return true; } - if (strncmp(version, "3.4.", 4) == 0 && is_number(version + 4, length - 4)) { + if (strncmp(version, "3.4.", 4) == 0) { options->version = PM_OPTIONS_VERSION_CRUBY_3_4; return true; } - if (strncmp(version, "3.5.", 4) == 0 && is_number(version + 4, length - 4)) { - options->version = PM_OPTIONS_VERSION_CRUBY_3_5; - return true; - } - - if (strncmp(version, "4.0.", 4) == 0 && is_number(version + 4, length - 4)) { + if (strncmp(version, "3.5.", 4) == 0 || strncmp(version, "4.0.", 4) == 0) { options->version = PM_OPTIONS_VERSION_CRUBY_4_0; return true; } diff --git a/prism/options.h b/prism/options.h index 44cd745e15520d..a663c9767e994e 100644 --- a/prism/options.h +++ b/prism/options.h @@ -91,11 +91,11 @@ typedef enum { /** The vendored version of prism in CRuby 3.4.x. */ PM_OPTIONS_VERSION_CRUBY_3_4 = 2, - /** The vendored version of prism in CRuby 3.5.x. */ + /** The vendored version of prism in CRuby 4.0.x. */ PM_OPTIONS_VERSION_CRUBY_3_5 = 3, /** The vendored version of prism in CRuby 4.0.x. */ - PM_OPTIONS_VERSION_CRUBY_4_0 = 4, + PM_OPTIONS_VERSION_CRUBY_4_0 = 3, /** The current version of prism. */ PM_OPTIONS_VERSION_LATEST = PM_OPTIONS_VERSION_CRUBY_4_0 diff --git a/prism/prism.c b/prism/prism.c index 03b12e9db82d34..02dd2f117564ee 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -10864,11 +10864,11 @@ parser_lex(pm_parser_t *parser) { } - // If we are parsing as CRuby 3.5 or later and we + // If we are parsing as CRuby 4.0 or later and we // hit a '&&' or a '||' then we will lex the ignored // newline. if ( - (parser->version >= PM_OPTIONS_VERSION_CRUBY_3_5) && + (parser->version >= PM_OPTIONS_VERSION_CRUBY_4_0) && following && ( (peek_at(parser, following) == '&' && peek_at(parser, following + 1) == '&') || (peek_at(parser, following) == '|' && peek_at(parser, following + 1) == '|') || @@ -10915,7 +10915,7 @@ parser_lex(pm_parser_t *parser) { LEX(PM_TOKEN_AMPERSAND_DOT); } - if (parser->version >= PM_OPTIONS_VERSION_CRUBY_3_5) { + if (parser->version >= PM_OPTIONS_VERSION_CRUBY_4_0) { // If we hit an && then we are in a logical chain // and we need to return the logical operator. if (peek_at(parser, next_content) == '&' && peek_at(parser, next_content + 1) == '&') { @@ -19625,7 +19625,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b statements = (pm_node_t *) pm_statements_node_create(parser); bool allow_command_call; - if (parser->version >= PM_OPTIONS_VERSION_CRUBY_3_5) { + if (parser->version >= PM_OPTIONS_VERSION_CRUBY_4_0) { allow_command_call = accepts_command_call; } else { // Allow `def foo = puts "Hello"` but not `private def foo = puts "Hello"` diff --git a/test/prism/api/parse_test.rb b/test/prism/api/parse_test.rb index 1f885fa4935302..bb1761109fadbb 100644 --- a/test/prism/api/parse_test.rb +++ b/test/prism/api/parse_test.rb @@ -119,6 +119,9 @@ def test_version assert Prism.parse_success?("1 + 1", version: "3.5") assert Prism.parse_success?("1 + 1", version: "3.5.0") + assert Prism.parse_success?("1 + 1", version: "4.0") + assert Prism.parse_success?("1 + 1", version: "4.0.0") + assert Prism.parse_success?("1 + 1", version: "latest") # Test edge case diff --git a/test/prism/fixtures/3.5/endless_methods_command_call.txt b/test/prism/fixtures/4.0/endless_methods_command_call.txt similarity index 100% rename from test/prism/fixtures/3.5/endless_methods_command_call.txt rename to test/prism/fixtures/4.0/endless_methods_command_call.txt diff --git a/test/prism/fixtures/3.5/leading_logical.txt b/test/prism/fixtures/4.0/leading_logical.txt similarity index 100% rename from test/prism/fixtures/3.5/leading_logical.txt rename to test/prism/fixtures/4.0/leading_logical.txt diff --git a/test/prism/fixtures_test.rb b/test/prism/fixtures_test.rb index 0f0577c10d5735..2aebb1847782dc 100644 --- a/test/prism/fixtures_test.rb +++ b/test/prism/fixtures_test.rb @@ -35,8 +35,8 @@ class FixturesTest < TestCase except << "3.3-3.3/return_in_sclass.txt" # Leaving these out until they are supported by parse.y. - except << "3.5/leading_logical.txt" - except << "3.5/endless_methods_command_call.txt" + except << "4.0/leading_logical.txt" + except << "4.0/endless_methods_command_call.txt" # https://bugs.ruby-lang.org/issues/21168#note-5 except << "command_method_call_2.txt" diff --git a/test/prism/lex_test.rb b/test/prism/lex_test.rb index 9682bf8a322c21..19dd845d755621 100644 --- a/test/prism/lex_test.rb +++ b/test/prism/lex_test.rb @@ -43,10 +43,10 @@ class LexTest < TestCase end # https://bugs.ruby-lang.org/issues/20925 - except << "3.5/leading_logical.txt" + except << "4.0/leading_logical.txt" # https://bugs.ruby-lang.org/issues/17398#note-12 - except << "3.5/endless_methods_command_call.txt" + except << "4.0/endless_methods_command_call.txt" # https://bugs.ruby-lang.org/issues/21168#note-5 except << "command_method_call_2.txt" diff --git a/test/prism/locals_test.rb b/test/prism/locals_test.rb index 439625b750a6f1..814c9a9978d84a 100644 --- a/test/prism/locals_test.rb +++ b/test/prism/locals_test.rb @@ -38,8 +38,8 @@ class LocalsTest < TestCase "3.3-3.3/return_in_sclass.txt", # Leaving these out until they are supported by parse.y. - "3.5/leading_logical.txt", - "3.5/endless_methods_command_call.txt", + "4.0/leading_logical.txt", + "4.0/endless_methods_command_call.txt", "command_method_call_2.txt" ] diff --git a/test/prism/ractor_test.rb b/test/prism/ractor_test.rb index 6169940bebc558..0e008ffb088c4f 100644 --- a/test/prism/ractor_test.rb +++ b/test/prism/ractor_test.rb @@ -64,7 +64,7 @@ def with_ractor(*arguments, &block) else ractor = ignore_warnings { Ractor.new(*arguments, &block) } - # Somewhere in the Ruby 3.5.* series, Ractor#take was removed and + # Somewhere in the Ruby 4.0.* series, Ractor#take was removed and # Ractor#value was added. puts(ractor.respond_to?(:value) ? ractor.value : ractor.take) end diff --git a/test/prism/ruby/parser_test.rb b/test/prism/ruby/parser_test.rb index 3104369d3eadd3..1629c36b382b53 100644 --- a/test/prism/ruby/parser_test.rb +++ b/test/prism/ruby/parser_test.rb @@ -69,10 +69,10 @@ class ParserTest < TestCase "3.4/circular_parameters.txt", # Cannot yet handling leading logical operators. - "3.5/leading_logical.txt", + "4.0/leading_logical.txt", - # Ruby >= 3.5 specific syntax - "3.5/endless_methods_command_call.txt", + # Ruby >= 4.0 specific syntax + "4.0/endless_methods_command_call.txt", # https://bugs.ruby-lang.org/issues/21168#note-5 "command_method_call_2.txt", @@ -172,6 +172,7 @@ def test_non_prism_builder_class_deprecated if RUBY_VERSION >= "3.3" def test_current_parser_for_current_ruby major, minor = current_major_minor.split(".") + return if major == "3" && minor == "5" # TODO: Remove once ruby-dev becomes 4.0 # Let's just hope there never is a Ruby 3.10 or similar expected = major.to_i * 10 + minor.to_i assert_equal(expected, Translation::ParserCurrent.new.version) diff --git a/test/prism/ruby/ripper_test.rb b/test/prism/ruby/ripper_test.rb index 12c854aea660be..400139acc03d01 100644 --- a/test/prism/ruby/ripper_test.rb +++ b/test/prism/ruby/ripper_test.rb @@ -9,7 +9,7 @@ class RipperTest < TestCase # Skip these tests that Ripper is reporting the wrong results for. incorrect = [ # Not yet supported. - "3.5/leading_logical.txt", + "4.0/leading_logical.txt", # Ripper incorrectly attributes the block to the keyword. "seattlerb/block_break.txt", @@ -40,7 +40,7 @@ class RipperTest < TestCase "3.4/circular_parameters.txt", # https://bugs.ruby-lang.org/issues/17398#note-12 - "3.5/endless_methods_command_call.txt", + "4.0/endless_methods_command_call.txt", # https://bugs.ruby-lang.org/issues/21168#note-5 "command_method_call_2.txt", diff --git a/test/prism/ruby/ruby_parser_test.rb b/test/prism/ruby/ruby_parser_test.rb index 42a888be820924..fae5077e20f334 100644 --- a/test/prism/ruby/ruby_parser_test.rb +++ b/test/prism/ruby/ruby_parser_test.rb @@ -84,8 +84,8 @@ class RubyParserTest < TestCase "3.4/circular_parameters.txt", - "3.5/endless_methods_command_call.txt", - "3.5/leading_logical.txt", + "4.0/endless_methods_command_call.txt", + "4.0/leading_logical.txt", # https://bugs.ruby-lang.org/issues/21168#note-5 "command_method_call_2.txt", diff --git a/test/prism/test_helper.rb b/test/prism/test_helper.rb index faf6117668a404..c03f70b2cdab18 100644 --- a/test/prism/test_helper.rb +++ b/test/prism/test_helper.rb @@ -230,7 +230,7 @@ def self.windows? end # All versions that prism can parse - SYNTAX_VERSIONS = %w[3.3 3.4 3.5 4.0] + SYNTAX_VERSIONS = %w[3.3 3.4 4.0] # Returns an array of ruby versions that a given filepath should test against: # test.txt # => all available versions @@ -256,6 +256,7 @@ def current_major_minor if RUBY_VERSION >= "3.3.0" def test_all_syntax_versions_present + return if RUBY_VERSION.start_with?("3.5") # TODO: Remove once ruby-dev becomes 4.0 assert_include(SYNTAX_VERSIONS, current_major_minor) end end