From a8c5b2bf510161b56a18ee7e9fd96ca63b04f393 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Fri, 12 Sep 2025 08:12:09 -0500 Subject: [PATCH 01/20] [DOC] Tweaks for String#lstrip --- string.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/string.c b/string.c index 7b8a55a5358e7a..dd275ba0754d46 100644 --- a/string.c +++ b/string.c @@ -10404,10 +10404,11 @@ rb_str_lstrip_bang(VALUE str) * * whitespace = "\x00\t\n\v\f\r " * s = whitespace + 'abc' + whitespace - * s # => "\u0000\t\n\v\f\r abc\u0000\t\n\v\f\r " - * s.lstrip # => "abc\u0000\t\n\v\f\r " + * # => "\u0000\t\n\v\f\r abc\u0000\t\n\v\f\r " + * s.lstrip + * # => "abc\u0000\t\n\v\f\r " * - * Related: String#rstrip, String#strip. + * Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. */ static VALUE From fbeeb89b3d442d3be27544b058cc9d5b5dc4accc Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Fri, 12 Sep 2025 08:14:24 -0500 Subject: [PATCH 02/20] [DOC] Tweaks for String#match? (#14477) --- string.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/string.c b/string.c index dd275ba0754d46..ca5d83725285e9 100644 --- a/string.c +++ b/string.c @@ -5119,24 +5119,23 @@ rb_str_match_m(int argc, VALUE *argv, VALUE str) * call-seq: * match?(pattern, offset = 0) -> true or false * - * Returns +true+ or +false+ based on whether a match is found for +self+ and +pattern+. + * Returns whether a match is found for +self+ and the given arguments; + * does not update {Regexp Global Variables}[rdoc-ref:Regexp@Global+Variables]. * - * Note: does not update Regexp@Global+Variables. + * Computes +regexp+ by converting +pattern+ (if not already a Regexp): * - * Computes +regexp+ by converting +pattern+ (if not already a Regexp). * regexp = Regexp.new(pattern) * - * Returns +true+ if self+.match(regexp) returns a MatchData object, + * Returns +true+ if self[offset..].match(regexp) returns a MatchData object, * +false+ otherwise: * * 'foo'.match?(/o/) # => true * 'foo'.match?('o') # => true * 'foo'.match?(/x/) # => false - * - * If Integer argument +offset+ is given, the search begins at index +offset+: * 'foo'.match?('f', 1) # => false * 'foo'.match?('o', 1) # => true * + * Related: see {Querying}[rdoc-ref:String@Querying]. */ static VALUE From 95ae42cea1df8015876d185387091a4fb617b82d Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Fri, 12 Sep 2025 08:30:50 -0500 Subject: [PATCH 03/20] [DOC] Tweaks for String#match (#14476) --- string.c | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/string.c b/string.c index ca5d83725285e9..8d27248020cded 100644 --- a/string.c +++ b/string.c @@ -5070,34 +5070,36 @@ static VALUE get_pat(VALUE); * match(pattern, offset = 0) -> matchdata or nil * match(pattern, offset = 0) {|matchdata| ... } -> object * - * Returns a MatchData object (or +nil+) based on +self+ and the given +pattern+. - * - * Note: also updates Regexp@Global+Variables. + * Creates a MatchData object based on +self+ and the given arguments; + * updates {Regexp Global Variables}[rdoc-ref:Regexp@Global+Variables]. * * - Computes +regexp+ by converting +pattern+ (if not already a Regexp). + * * regexp = Regexp.new(pattern) + * * - Computes +matchdata+, which will be either a MatchData object or +nil+ * (see Regexp#match): - * matchdata = regexp.match(self) - * - * With no block given, returns the computed +matchdata+: * - * 'foo'.match('f') # => # - * 'foo'.match('o') # => # - * 'foo'.match('x') # => nil + * matchdata = regexp.match(self[offset..]) * - * If Integer argument +offset+ is given, the search begins at index +offset+: + * With no block given, returns the computed +matchdata+ or +nil+: * + * 'foo'.match('f') # => # + * 'foo'.match('o') # => # + * 'foo'.match('x') # => nil * 'foo'.match('f', 1) # => nil * 'foo'.match('o', 1) # => # * - * With a block given, calls the block with the computed +matchdata+ - * and returns the block's return value: + * With a block given and computed +matchdata+ non-nil, calls the block with +matchdata+; + * returns the block's return value: * * 'foo'.match(/o/) {|matchdata| matchdata } # => # - * 'foo'.match(/x/) {|matchdata| matchdata } # => nil - * 'foo'.match(/f/, 1) {|matchdata| matchdata } # => nil * + * With a block given and +nil+ +matchdata+, does not call the block: + * + * 'foo'.match(/x/) {|matchdata| fail 'Cannot happen' } # => nil + * + * Related: see {Querying}[rdoc-ref:String@Querying]. */ static VALUE From adcde78dbf0cc6f6a536aa15c0807f15c9daa22f Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Thu, 11 Sep 2025 08:47:08 -0400 Subject: [PATCH 04/20] Use IMEMO_NEW in rb_imemo_tmpbuf_new --- imemo.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/imemo.c b/imemo.c index abf101f362f325..02cba387bd22be 100644 --- a/imemo.c +++ b/imemo.c @@ -51,11 +51,7 @@ rb_imemo_new(enum imemo_type type, VALUE v0, size_t size) static rb_imemo_tmpbuf_t * rb_imemo_tmpbuf_new(void) { - size_t size = sizeof(struct rb_imemo_tmpbuf_struct); - VALUE flags = T_IMEMO | (imemo_tmpbuf << FL_USHIFT); - NEWOBJ_OF(obj, struct rb_imemo_tmpbuf_struct, 0, flags, size, 0); - - return obj; + return IMEMO_NEW(rb_imemo_tmpbuf_t, imemo_tmpbuf, 0); } void * From 5a2cedd051634d6d1c8fbf48b0327f8cd8eec495 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 12 Sep 2025 23:14:59 +0900 Subject: [PATCH 05/20] Just touch the timestamp for prism/srcs.mk when no baseruby --- prism/srcs.mk | 3 +++ 1 file changed, 3 insertions(+) diff --git a/prism/srcs.mk b/prism/srcs.mk index aa5c0fa2b5ee33..565d793cc0a210 100644 --- a/prism/srcs.mk +++ b/prism/srcs.mk @@ -5,6 +5,9 @@ PRISM_CONFIG = $(PRISM_SRCDIR)/config.yml srcs uncommon.mk: prism/.srcs.mk.time prism/.srcs.mk.time: +prism/$(HAVE_BASERUBY:no=.srcs.mk.time): + mkdir -p $(@D) + touch $@ prism/$(HAVE_BASERUBY:yes=.srcs.mk.time): \ $(PRISM_SRCDIR)/templates/template.rb \ $(PRISM_SRCDIR)/srcs.mk.in From 8a30594d2cf22c223d57f0fb3832c67b5bbdc76d Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Fri, 12 Sep 2025 23:02:27 +0900 Subject: [PATCH 06/20] [ruby/openssl] pkey: fix loading public keys with early OpenSSL 3.0.x releases Treat an empty error queue after calling OSSL_DECODER_from_bio() as a retryable error. This is a follow-up to the previous commit https://github.com/ruby/openssl/commit/985ba27d6339 (pkey: stop retrying after non-retryable error from OSSL_DECODER). The commit broke loading public keys on Ubuntu 22.04 LTS, which ships OpenSSL 3.0.2. https://github.com/ruby/openssl/commit/5347880c6e --- ext/openssl/ossl_pkey.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ext/openssl/ossl_pkey.c b/ext/openssl/ossl_pkey.c index 481bd8a8ee09e3..ee6a7a988f59f0 100644 --- a/ext/openssl/ossl_pkey.c +++ b/ext/openssl/ossl_pkey.c @@ -103,7 +103,10 @@ ossl_pkey_read(BIO *bio, const char *input_type, int selection, VALUE pass, while (1) { if (OSSL_DECODER_from_bio(dctx, bio) == 1) break; - if (ERR_GET_REASON(ERR_peek_error()) != ERR_R_UNSUPPORTED) + // Error queue may not be populated in OpenSSL < 3.0.11 and < 3.1.3 + // https://github.com/openssl/openssl/pull/21603 + unsigned long err = ERR_peek_error(); + if (err && ERR_GET_REASON(err) != ERR_R_UNSUPPORTED) break; if (BIO_eof(bio) == 1) { *retryable = 1; From 691a5545e6161cf8c2f574ae9fc69df1232cda97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Barri=C3=A9?= Date: Fri, 12 Sep 2025 16:16:52 +0200 Subject: [PATCH 07/20] Restore test example for argument forwarding Since cb419e3912f0514b8151469b0a4a4b83cbbcce78 we're no longer testing this case because foo is redefined on obj1. --- test/ruby/test_syntax.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/ruby/test_syntax.rb b/test/ruby/test_syntax.rb index b7e021a4ff4976..bbdeb876ec3eb0 100644 --- a/test/ruby/test_syntax.rb +++ b/test/ruby/test_syntax.rb @@ -2027,10 +2027,11 @@ def obj1.bar(*args, **kws, &block) end obj4 = obj1.clone obj5 = obj1.clone + obj6 = obj1.clone obj1.instance_eval('def foo(...) bar(...) end', __FILE__, __LINE__) - obj1.instance_eval('def foo(...) eval("bar(...)") end', __FILE__, __LINE__) obj4.instance_eval("def foo ...\n bar(...)\n""end", __FILE__, __LINE__) obj5.instance_eval("def foo ...; bar(...); end", __FILE__, __LINE__) + obj6.instance_eval('def foo(...) eval("bar(...)") end', __FILE__, __LINE__) klass = Class.new { def foo(*args, **kws, &block) @@ -2059,7 +2060,7 @@ def obj3.bar(*args, &block) end obj3.instance_eval('def foo(...) bar(...) end', __FILE__, __LINE__) - [obj1, obj2, obj3, obj4, obj5].each do |obj| + [obj1, obj2, obj3, obj4, obj5, obj6].each do |obj| assert_warning('') { assert_equal([[1, 2, 3], {k1: 4, k2: 5}], obj.foo(1, 2, 3, k1: 4, k2: 5) {|*x| x}) } From a35ceeedfada7cd556462401bef795470c38d41f Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Fri, 12 Sep 2025 09:19:18 -0700 Subject: [PATCH 08/20] Revert "Just touch the timestamp for prism/srcs.mk when no baseruby" This reverts commit 5a2cedd051634d6d1c8fbf48b0327f8cd8eec495. The CI is telling us to revert the diff: https://github.com/ruby/ruby/actions/runs/17679836157/job/50251138515 --- prism/srcs.mk | 3 --- 1 file changed, 3 deletions(-) diff --git a/prism/srcs.mk b/prism/srcs.mk index 565d793cc0a210..aa5c0fa2b5ee33 100644 --- a/prism/srcs.mk +++ b/prism/srcs.mk @@ -5,9 +5,6 @@ PRISM_CONFIG = $(PRISM_SRCDIR)/config.yml srcs uncommon.mk: prism/.srcs.mk.time prism/.srcs.mk.time: -prism/$(HAVE_BASERUBY:no=.srcs.mk.time): - mkdir -p $(@D) - touch $@ prism/$(HAVE_BASERUBY:yes=.srcs.mk.time): \ $(PRISM_SRCDIR)/templates/template.rb \ $(PRISM_SRCDIR)/srcs.mk.in From d8e9ec6693d5f88457b35a3cbf890f8247cfe20e Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Fri, 12 Sep 2025 18:01:45 +0100 Subject: [PATCH 09/20] ZJIT: Add specific dynamic send type counters (#14528) --- zjit.rb | 1 + zjit/src/codegen.rs | 8 ++++++-- zjit/src/stats.rs | 4 ++++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/zjit.rb b/zjit.rb index 5e647bfbeeeb89..d6f7a4069267b8 100644 --- a/zjit.rb +++ b/zjit.rb @@ -44,6 +44,7 @@ def stats_string print_counters_with_prefix(prefix: 'unhandled_yarv_insn_', prompt: 'unhandled YARV insns', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'compile_error_', prompt: 'compile error reasons', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'exit_', prompt: 'side exit reasons', buf:, stats:, limit: 20) + print_counters_with_prefix(prefix: 'dynamic_send_type_', prompt: 'dynamic send types', buf:, stats:, limit: 20) # Show the most important stats ratio_in_zjit at the end print_counters([ diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 2a581d8baddac8..1f71ad9db3b2b8 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -370,7 +370,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio gen_send_without_block(jit, asm, *cd, &function.frame_state(*state)), Insn::SendWithoutBlockDirect { cme, iseq, recv, args, state, .. } => gen_send_without_block_direct(cb, jit, asm, *cme, *iseq, opnd!(recv), opnds!(args), &function.frame_state(*state)), &Insn::InvokeSuper { cd, blockiseq, state, .. } => gen_invokesuper(jit, asm, cd, blockiseq, &function.frame_state(state)), - Insn::InvokeBlock { cd, state, .. } => gen_invoke_block(jit, asm, *cd, &function.frame_state(*state)), + Insn::InvokeBlock { cd, state, .. } => gen_invokeblock(jit, asm, *cd, &function.frame_state(*state)), // Ensure we have enough room fit ec, self, and arguments // TODO remove this check when we have stack args (we can use Time.new to test it) Insn::InvokeBuiltin { bf, state, .. } if bf.argc + 2 > (C_ARG_OPNDS.len() as i32) => return Err(*state), @@ -981,6 +981,7 @@ fn gen_send( state: &FrameState, ) -> lir::Opnd { gen_incr_counter(asm, Counter::dynamic_send_count); + gen_incr_counter(asm, Counter::dynamic_send_type_send); // Save PC and SP gen_prepare_call_with_gc(asm, state); @@ -1008,6 +1009,7 @@ fn gen_send_without_block( state: &FrameState, ) -> lir::Opnd { gen_incr_counter(asm, Counter::dynamic_send_count); + gen_incr_counter(asm, Counter::dynamic_send_type_send_without_block); // Note that it's incorrect to use this frame state to side exit because // the state might not be on the boundary of an interpreter instruction. @@ -1108,13 +1110,14 @@ fn gen_send_without_block_direct( } /// Compile for invokeblock -fn gen_invoke_block( +fn gen_invokeblock( jit: &mut JITState, asm: &mut Assembler, cd: *const rb_call_data, state: &FrameState, ) -> lir::Opnd { gen_incr_counter(asm, Counter::dynamic_send_count); + gen_incr_counter(asm, Counter::dynamic_send_type_invokeblock); // Save PC and SP, spill locals and stack gen_prepare_call_with_gc(asm, state); @@ -1141,6 +1144,7 @@ fn gen_invokesuper( state: &FrameState, ) -> lir::Opnd { gen_incr_counter(asm, Counter::dynamic_send_count); + gen_incr_counter(asm, Counter::dynamic_send_type_invokesuper); // Save PC and SP gen_prepare_call_with_gc(asm, state); diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 98ddc20226621b..39cfe6a4398fb3 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -142,6 +142,10 @@ make_counters! { // The number of times we do a dynamic dispatch from JIT code dynamic_send_count, + dynamic_send_type_send_without_block, + dynamic_send_type_send, + dynamic_send_type_invokeblock, + dynamic_send_type_invokesuper, } /// Increase a counter by a specified amount From dd3aa0a52c564624a1e15b4447c3dc291304dda5 Mon Sep 17 00:00:00 2001 From: Herwin Date: Fri, 28 Jun 2024 11:56:05 +0200 Subject: [PATCH 10/20] [ruby/prism] Add field documentation for MatchRequiredNode https://github.com/ruby/prism/commit/03ca35b3ab --- prism/config.yml | 54 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/prism/config.yml b/prism/config.yml index b37b98cbdfe252..54538300085b6f 100644 --- a/prism/config.yml +++ b/prism/config.yml @@ -3478,11 +3478,65 @@ nodes: - name: value type: node kind: non-void expression + comment: | + Represents the left-hand side of the operator. + + foo => bar + ^^^ - name: pattern type: node kind: pattern expression + comment: | + Represents the right-hand side of the operator. The type of the node depends on the expression. + + Anything that looks like a local variable name (including `_`) will result in a `LocalVariableTargetNode`. + + foo => a # This is equivalent to writing `a = foo` + ^ + + Using an explicit `Array` or combining expressions with `,` will result in a `ArrayPatternNode`. This can be preceded by a constant. + + foo => [a] + ^^^ + + foo => a, b + ^^^^ + + foo => Bar[a, b] + ^^^^^^^^^ + + If the array pattern contains at least two wildcard matches, a `FindPatternNode` is created instead. + + foo => *, 1, *a + ^^^^^ + + Using an explicit `Hash` or a constant with square brackets and hash keys in the square brackets will result in a `HashPatternNode`. + + foo => { a: 1, b: } + + foo => Bar[a: 1, b:] + + foo => Bar[**] + + To use any variable that needs run time evaluation, pinning is required. This results in a `PinnedVariableNode` + + foo => ^a + ^^ + + Similar, any expression can be used with pinning. This results in a `PinnedExpressionNode`. + + foo => ^(a + 1) + + Anything else will result in the regular node for that expression, for example a `ConstantReadNode`. + + foo => CONST - name: operator_loc type: location + comment: | + The location of the operator. + + foo => bar + ^^ comment: | Represents the use of the `=>` operator. From 9fddf5a032d6c3b7982fc0f7213c008e9bc17abc Mon Sep 17 00:00:00 2001 From: Herwin Date: Fri, 28 Jun 2024 11:58:02 +0200 Subject: [PATCH 11/20] [ruby/prism] Add pattern match documentation example to LocalVariableTargetNode https://github.com/ruby/prism/commit/193984b760 --- prism/config.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/prism/config.yml b/prism/config.yml index 54538300085b6f..923e535ba9328c 100644 --- a/prism/config.yml +++ b/prism/config.yml @@ -3388,6 +3388,9 @@ nodes: foo, bar = baz ^^^ ^^^ + + foo => baz + ^^^ - name: LocalVariableWriteNode fields: - name: name From 8efa67034947352f32e3c56aa6338eea6611c8b7 Mon Sep 17 00:00:00 2001 From: Herwin Date: Fri, 28 Jun 2024 13:37:05 +0200 Subject: [PATCH 12/20] [ruby/prism] Add field documentation for ArrayPatternNode https://github.com/ruby/prism/commit/c80c4d958e --- prism/config.yml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/prism/config.yml b/prism/config.yml index 923e535ba9328c..f2e86a497cfc76 100644 --- a/prism/config.yml +++ b/prism/config.yml @@ -992,8 +992,19 @@ nodes: - name: constant type: node? kind: - - ConstantReadNode - ConstantPathNode + - ConstantReadNode + comment: | + Represents the optional constant preceding the Array + + foo in Bar[] + ^^^ + + foo in Bar[1, 2, 3] + ^^^ + + foo in Bar::Baz[1, 2, 3] + ^^^^^^^^ - name: requireds type: node[] kind: pattern expression From 22702a1e3b5d9789360b3fc72ca5c82ddff38828 Mon Sep 17 00:00:00 2001 From: Herwin Date: Fri, 28 Jun 2024 14:07:55 +0200 Subject: [PATCH 13/20] [ruby/prism] Add field documentation for HashPatternNode https://github.com/ruby/prism/commit/9b7dfcc3e0 --- prism/config.yml | 48 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/prism/config.yml b/prism/config.yml index f2e86a497cfc76..16754318b2bfc6 100644 --- a/prism/config.yml +++ b/prism/config.yml @@ -2725,20 +2725,60 @@ nodes: - name: constant type: node? kind: - - ConstantReadNode - ConstantPathNode + - ConstantReadNode + comment: | + Represents the optional constant preceding the Hash. + + foo => Bar[a: 1, b: 2] + ^^^ + + foo => Bar::Baz[a: 1, b: 2] + ^^^^^^^^ - name: elements type: node[] kind: AssocNode + comment: | + Represents the explicit named hash keys and values. + + foo => { a: 1, b:, ** } + ^^^^^^^^ - name: rest type: node? kind: - AssocSplatNode - NoKeywordsParameterNode + comment: | + Represents the rest of the Hash keys and values. This can be named, unnamed, or explicitly forbidden via `**nil`, this last one results in a `NoKeywordsParameterNode`. + + foo => { a: 1, b:, **c } + ^^^ + + foo => { a: 1, b:, ** } + ^^ + + foo => { a: 1, b:, **nil } + ^^^^^ - name: opening_loc type: location? + comment: | + The location of the opening brace. + + foo => { a: 1 } + ^ + + foo => Bar[a: 1] + ^ - name: closing_loc type: location? + comment: | + The location of the closing brace. + + foo => { a: 1 } + ^ + + foo => Bar[a: 1] + ^ comment: | Represents a hash pattern in pattern matching. @@ -2747,6 +2787,12 @@ nodes: foo => { a: 1, b: 2, **c } ^^^^^^^^^^^^^^^^^^^ + + foo => Bar[a: 1, b: 2] + ^^^^^^^^^^^^^^^ + + foo in { a: 1, b: 2 } + ^^^^^^^^^^^^^^ - name: IfNode fields: - name: if_keyword_loc From bfd6da7448dbb32916d9802308fe2c136ae58e9a Mon Sep 17 00:00:00 2001 From: Herwin Date: Fri, 28 Jun 2024 14:16:35 +0200 Subject: [PATCH 14/20] [ruby/prism] Add field documentation for FindPatternNode https://github.com/ruby/prism/commit/a0cc316e91 --- prism/config.yml | 50 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/prism/config.yml b/prism/config.yml index 16754318b2bfc6..de78d654622ac7 100644 --- a/prism/config.yml +++ b/prism/config.yml @@ -2420,23 +2420,68 @@ nodes: - name: constant type: node? kind: - - ConstantReadNode - ConstantPathNode + - ConstantReadNode + comment: | + Represents the optional constant preceding the pattern + + foo in Foo(*bar, baz, *qux) + ^^^ - name: left type: node kind: SplatNode + comment: | + Represents the first wildcard node in the pattern. + + foo in *bar, baz, *qux + ^^^^ + + foo in Foo(*bar, baz, *qux) + ^^^^ - name: requireds type: node[] kind: pattern expression + comment: | + Represents the nodes in between the wildcards. + + foo in *bar, baz, *qux + ^^^ + + foo in Foo(*bar, baz, 1, *qux) + ^^^^^^ - name: right type: node kind: - SplatNode - on error: MissingNode + comment: | + Represents the second wildcard node in the pattern. + + foo in *bar, baz, *qux + ^^^^ + + foo in Foo(*bar, baz, *qux) + ^^^^ - name: opening_loc type: location? + comment: | + The location of the openingbrace. + + foo in [*bar, baz, *qux] + ^ + + foo in Foo(*bar, baz, *qux) + ^ - name: closing_loc type: location? + comment: | + The location of the closing brace. + + foo in [*bar, baz, *qux] + ^ + + foo in Foo(*bar, baz, *qux) + ^ comment: | Represents a find pattern in pattern matching. @@ -2448,6 +2493,9 @@ nodes: foo in Foo(*bar, baz, *qux) ^^^^^^^^^^^^^^^^^^^^ + + foo => *bar, baz, *qux + ^^^^^^^^^^^^^^^ - name: FlipFlopNode flags: RangeFlags fields: From aaeeac441eb88d59631a48f808ccce59a9c30b86 Mon Sep 17 00:00:00 2001 From: Herwin Date: Sat, 29 Jun 2024 17:17:16 +0200 Subject: [PATCH 15/20] [ruby/prism] Add field documentation for PinnedVariableNode https://github.com/ruby/prism/commit/af9047f378 --- prism/config.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/prism/config.yml b/prism/config.yml index de78d654622ac7..c53265b3507532 100644 --- a/prism/config.yml +++ b/prism/config.yml @@ -4098,8 +4098,18 @@ nodes: - NumberedReferenceReadNode # foo in ^$1 - ItLocalVariableReadNode # proc { 1 in ^it } - on error: MissingNode # foo in ^Bar + comment: | + The variable used in the pinned expression + + foo in ^bar + ^^^ - name: operator_loc type: location + comment: | + The location of the `^` operator + + foo in ^bar + ^ comment: | Represents the use of the `^` operator for pinning a variable in a pattern matching expression. From dcfd98b00537e53505c6be8e53cba01ad22ba416 Mon Sep 17 00:00:00 2001 From: Herwin Date: Tue, 2 Jul 2024 16:56:38 +0200 Subject: [PATCH 16/20] [ruby/prism] Add field documentation for PinnedExpressionNode https://github.com/ruby/prism/commit/0d94291416 --- prism/config.yml | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/prism/config.yml b/prism/config.yml index c53265b3507532..3366b6235d003c 100644 --- a/prism/config.yml +++ b/prism/config.yml @@ -2465,7 +2465,7 @@ nodes: - name: opening_loc type: location? comment: | - The location of the openingbrace. + The location of the opening brace. foo in [*bar, baz, *qux] ^ @@ -4074,12 +4074,32 @@ nodes: - name: expression type: node kind: non-void expression + comment: | + The expression used in the pinned expression + + foo in ^(bar) + ^^^ - name: operator_loc type: location + comment: | + The location of the `^` operator + + foo in ^(bar) + ^ - name: lparen_loc type: location + comment: | + The location of the opening parenthesis. + + foo in ^(bar) + ^ - name: rparen_loc type: location + comment: | + The location of the closing parenthesis. + + foo in ^(bar) + ^ comment: | Represents the use of the `^` operator for pinning an expression in a pattern matching expression. From 0803b9a6acd6634fd24ec9cc53725eafbaccb856 Mon Sep 17 00:00:00 2001 From: Alexander Momchilov Date: Mon, 16 Sep 2024 09:45:23 -0400 Subject: [PATCH 17/20] [ruby/prism] Document lifetime of `pm_options_t` https://github.com/ruby/prism/commit/ed8f6307c1 --- prism/prism.h | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/prism/prism.h b/prism/prism.h index a6f22f1a5ad39f..11e854eb35b502 100644 --- a/prism/prism.h +++ b/prism/prism.h @@ -49,10 +49,13 @@ PRISM_EXPORTED_FUNCTION const char * pm_version(void); /** * Initialize a parser with the given start and end pointers. * + * The resulting parser must eventually be freed with `pm_parser_free()`. + * * @param parser The parser to initialize. * @param source The source to parse. * @param size The size of the source. - * @param options The optional options to use when parsing. + * @param options The optional options to use when parsing. These options must + * live for the whole lifetime of this parser. */ PRISM_EXPORTED_FUNCTION void pm_parser_init(pm_parser_t *parser, const uint8_t *source, size_t size, const pm_options_t *options); @@ -68,6 +71,9 @@ PRISM_EXPORTED_FUNCTION void pm_parser_register_encoding_changed_callback(pm_par /** * Free any memory associated with the given parser. * + * This does not free the `pm_options_t` object that was used to initialize the + * parser. + * * @param parser The parser to free. */ PRISM_EXPORTED_FUNCTION void pm_parser_free(pm_parser_t *parser); From 2c9afcc3896c9c18f6ea8278f479a379401551a1 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Sat, 21 Dec 2024 15:36:03 -0500 Subject: [PATCH 18/20] [ruby/prism] Support leading logical operators https://github.com/ruby/prism/commit/3f58fa7705 --- prism/prism.c | 94 +++++++++++++++++++++++-- test/prism/fixtures/leading_logical.txt | 21 ++++++ test/prism/fixtures_test.rb | 2 + test/prism/lex_test.rb | 3 + test/prism/ruby/parser_test.rb | 3 + test/prism/ruby/ripper_test.rb | 3 + test/prism/ruby/ruby_parser_test.rb | 1 + 7 files changed, 120 insertions(+), 7 deletions(-) create mode 100644 test/prism/fixtures/leading_logical.txt diff --git a/prism/prism.c b/prism/prism.c index 2f7ce0b9866438..daf69d2ef97553 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -10834,14 +10834,37 @@ parser_lex(pm_parser_t *parser) { following = next_newline(following, parser->end - following); } - // If the lex state was ignored, or we hit a '.' or a '&.', - // we will lex the ignored newline + // If the lex state was ignored, we will lex the + // ignored newline. + if (lex_state_ignored_p(parser)) { + if (!lexed_comment) parser_lex_ignored_newline(parser); + lexed_comment = false; + goto lex_next_token; + } + + // If we hit a '.' or a '&.' we will lex the ignored + // newline. + if (following && ( + (peek_at(parser, following) == '.') || + (peek_at(parser, following) == '&' && peek_at(parser, following + 1) == '.') + )) { + if (!lexed_comment) parser_lex_ignored_newline(parser); + lexed_comment = false; + goto lex_next_token; + } + + + // If we are parsing as CRuby 3.5 or later and we + // hit a '&&' or a '||' then we will lex the ignored + // newline. if ( - lex_state_ignored_p(parser) || - (following && ( - (peek_at(parser, following) == '.') || - (peek_at(parser, following) == '&' && peek_at(parser, following + 1) == '.') - )) + (parser->version >= PM_OPTIONS_VERSION_CRUBY_3_5) && + following && ( + (peek_at(parser, following) == '&' && peek_at(parser, following + 1) == '&') || + (peek_at(parser, following) == '|' && peek_at(parser, following + 1) == '|') || + (peek_at(parser, following) == 'a' && peek_at(parser, following + 1) == 'n' && peek_at(parser, following + 2) == 'd' && !char_is_identifier(parser, following + 3, parser->end - (following + 3))) || + (peek_at(parser, following) == 'o' && peek_at(parser, following + 1) == 'r' && !char_is_identifier(parser, following + 2, parser->end - (following + 2))) + ) ) { if (!lexed_comment) parser_lex_ignored_newline(parser); lexed_comment = false; @@ -10881,6 +10904,63 @@ parser_lex(pm_parser_t *parser) { parser->next_start = NULL; LEX(PM_TOKEN_AMPERSAND_DOT); } + + if (parser->version >= PM_OPTIONS_VERSION_CRUBY_3_5) { + // 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) == '&') { + if (!lexed_comment) parser_lex_ignored_newline(parser); + lex_state_set(parser, PM_LEX_STATE_BEG); + parser->current.start = next_content; + parser->current.end = next_content + 2; + parser->next_start = NULL; + LEX(PM_TOKEN_AMPERSAND_AMPERSAND); + } + + // If we hit a || 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) == '|') { + if (!lexed_comment) parser_lex_ignored_newline(parser); + lex_state_set(parser, PM_LEX_STATE_BEG); + parser->current.start = next_content; + parser->current.end = next_content + 2; + parser->next_start = NULL; + LEX(PM_TOKEN_PIPE_PIPE); + } + + // If we hit an 'and' then we are in a logical chain + // and we need to return the logical operator. + if ( + peek_at(parser, next_content) == 'a' && + peek_at(parser, next_content + 1) == 'n' && + peek_at(parser, next_content + 2) == 'd' && + !char_is_identifier(parser, next_content + 3, parser->end - (next_content + 3)) + ) { + if (!lexed_comment) parser_lex_ignored_newline(parser); + lex_state_set(parser, PM_LEX_STATE_BEG); + parser->current.start = next_content; + parser->current.end = next_content + 3; + parser->next_start = NULL; + parser->command_start = true; + LEX(PM_TOKEN_KEYWORD_AND); + } + + // If we hit a 'or' then we are in a logical chain + // and we need to return the logical operator. + if ( + peek_at(parser, next_content) == 'o' && + peek_at(parser, next_content + 1) == 'r' && + !char_is_identifier(parser, next_content + 2, parser->end - (next_content + 2)) + ) { + if (!lexed_comment) parser_lex_ignored_newline(parser); + lex_state_set(parser, PM_LEX_STATE_BEG); + parser->current.start = next_content; + parser->current.end = next_content + 2; + parser->next_start = NULL; + parser->command_start = true; + LEX(PM_TOKEN_KEYWORD_OR); + } + } } // At this point we know this is a regular newline, and we can set the diff --git a/test/prism/fixtures/leading_logical.txt b/test/prism/fixtures/leading_logical.txt new file mode 100644 index 00000000000000..feb5ee245c8b2f --- /dev/null +++ b/test/prism/fixtures/leading_logical.txt @@ -0,0 +1,21 @@ +1 +&& 2 +&& 3 + +1 +|| 2 +|| 3 + +1 +and 2 +and 3 + +1 +or 2 +or 3 + +1 +andfoo + +2 +orfoo diff --git a/test/prism/fixtures_test.rb b/test/prism/fixtures_test.rb index 3b4a502b9010f1..124a834317498b 100644 --- a/test/prism/fixtures_test.rb +++ b/test/prism/fixtures_test.rb @@ -25,6 +25,8 @@ class FixturesTest < TestCase except << "whitequark/ruby_bug_19281.txt" end + except << "leading_logical.txt" if RUBY_VERSION < "3.5.0" + Fixture.each(except: except) do |fixture| define_method(fixture.test_name) { assert_valid_syntax(fixture.read) } end diff --git a/test/prism/lex_test.rb b/test/prism/lex_test.rb index d34c3d9dd3cb2f..abce18a0ad3387 100644 --- a/test/prism/lex_test.rb +++ b/test/prism/lex_test.rb @@ -42,6 +42,9 @@ class LexTest < TestCase except << "whitequark/ruby_bug_19281.txt" end + # https://bugs.ruby-lang.org/issues/20925 + except << "leading_logical.txt" + Fixture.each(except: except) do |fixture| define_method(fixture.test_name) { assert_lex(fixture) } end diff --git a/test/prism/ruby/parser_test.rb b/test/prism/ruby/parser_test.rb index 2a4ea981bdb5c3..129c38a3b5b40d 100644 --- a/test/prism/ruby/parser_test.rb +++ b/test/prism/ruby/parser_test.rb @@ -64,6 +64,9 @@ class ParserTest < TestCase # 1.. && 2 "ranges.txt", + + # Cannot yet handling leading logical operators. + "leading_logical.txt", ] # These files contain code that is being parsed incorrectly by the parser diff --git a/test/prism/ruby/ripper_test.rb b/test/prism/ruby/ripper_test.rb index 5c37178889ecf5..637202487811b5 100644 --- a/test/prism/ruby/ripper_test.rb +++ b/test/prism/ruby/ripper_test.rb @@ -8,6 +8,9 @@ module Prism class RipperTest < TestCase # Skip these tests that Ripper is reporting the wrong results for. incorrect = [ + # Not yet supported. + "leading_logical.txt", + # Ripper incorrectly attributes the block to the keyword. "seattlerb/block_break.txt", "seattlerb/block_next.txt", diff --git a/test/prism/ruby/ruby_parser_test.rb b/test/prism/ruby/ruby_parser_test.rb index 960e7f63e46385..f4f0f331fb89a9 100644 --- a/test/prism/ruby/ruby_parser_test.rb +++ b/test/prism/ruby/ruby_parser_test.rb @@ -38,6 +38,7 @@ class RubyParserTest < TestCase "dos_endings.txt", "heredocs_with_fake_newlines.txt", "heredocs_with_ignored_newlines.txt", + "leading_logical.txt", "method_calls.txt", "methods.txt", "multi_write.txt", From a9b35b3a0777e299b27d039b4b09fe8bdc9984c8 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Fri, 12 Sep 2025 09:32:30 -0400 Subject: [PATCH 19/20] Remove useless field in rb_imemo_tmpbuf_t --- internal/imemo.h | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/imemo.h b/internal/imemo.h index f7bd6202384dca..de39102432ea84 100644 --- a/internal/imemo.h +++ b/internal/imemo.h @@ -93,7 +93,6 @@ struct vm_ifunc { struct rb_imemo_tmpbuf_struct { VALUE flags; - VALUE reserved; VALUE *ptr; /* malloc'ed buffer */ struct rb_imemo_tmpbuf_struct *next; /* next imemo */ size_t cnt; /* buffer size in VALUE */ From 120d3b12a9981f547b07c937dd183c0abe77c6cb Mon Sep 17 00:00:00 2001 From: Alexander Momchilov Date: Fri, 30 Aug 2024 15:02:01 -0400 Subject: [PATCH 20/20] [ruby/prism] Add links to code refs in docs https://github.com/ruby/prism/commit/d2d9a1f1a7 --- prism/prism.h | 20 ++++++++++---------- prism/util/pm_string.h | 4 ++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/prism/prism.h b/prism/prism.h index 11e854eb35b502..555bda08515fa9 100644 --- a/prism/prism.h +++ b/prism/prism.h @@ -322,10 +322,10 @@ PRISM_EXPORTED_FUNCTION pm_string_query_t pm_string_query_method_name(const uint * to want to use and be aware of are: * * * `pm_parser_t` - the main parser structure - * * `pm_parser_init` - initialize a parser - * * `pm_parse` - parse and return the root node - * * `pm_node_destroy` - deallocate the root node returned by `pm_parse` - * * `pm_parser_free` - free the internal memory of the parser + * * `pm_parser_init()` - initialize a parser + * * `pm_parse()` - parse and return the root node + * * `pm_node_destroy()` - deallocate the root node returned by `pm_parse()` + * * `pm_parser_free()` - free the internal memory of the parser * * Putting all of this together would look something like: * @@ -342,8 +342,8 @@ PRISM_EXPORTED_FUNCTION pm_string_query_t pm_string_query_method_name(const uint * } * ``` * - * All of the nodes "inherit" from `pm_node_t` by embedding those structures as - * their first member. This means you can downcast and upcast any node in the + * All of the nodes "inherit" from `pm_node_t` by embedding those structures + * as their first member. This means you can downcast and upcast any node in the * tree to a `pm_node_t`. * * @section serializing Serializing @@ -355,9 +355,9 @@ PRISM_EXPORTED_FUNCTION pm_string_query_t pm_string_query_method_name(const uint * use and be aware of are: * * * `pm_buffer_t` - a small buffer object that will hold the serialized AST - * * `pm_buffer_free` - free the memory associated with the buffer - * * `pm_serialize` - serialize the AST into a buffer - * * `pm_serialize_parse` - parse and serialize the AST into a buffer + * * `pm_buffer_free()` - free the memory associated with the buffer + * * `pm_serialize()` - serialize the AST into a buffer + * * `pm_serialize_parse()` - parse and serialize the AST into a buffer * * Putting all of this together would look something like: * @@ -375,7 +375,7 @@ PRISM_EXPORTED_FUNCTION pm_string_query_t pm_string_query_method_name(const uint * @section inspecting Inspecting * * Prism provides the ability to inspect the AST by pretty-printing nodes. You - * can do this with the `pm_prettyprint` function, which you would use like: + * can do this with the `pm_prettyprint()` function, which you would use like: * * ```c * void prettyprint(const uint8_t *source, size_t length) { diff --git a/prism/util/pm_string.h b/prism/util/pm_string.h index f99f1abdf38c9b..ccf6648d263bc0 100644 --- a/prism/util/pm_string.h +++ b/prism/util/pm_string.h @@ -45,11 +45,11 @@ typedef struct { /** This is a slice of another string, and should not be freed. */ PM_STRING_SHARED, - /** This string owns its memory, and should be freed using `pm_string_free`. */ + /** This string owns its memory, and should be freed using `pm_string_free()`. */ PM_STRING_OWNED, #ifdef PRISM_HAS_MMAP - /** This string is a memory-mapped file, and should be freed using `pm_string_free`. */ + /** This string is a memory-mapped file, and should be freed using `pm_string_free()`. */ PM_STRING_MAPPED #endif } type;