From: "jeremyevans0 (Jeremy Evans) via ruby-core" Date: 2025-07-28T18:06:05+00:00 Subject: [ruby-core:122874] [Ruby Bug#21139] Prism and parse.y parses `it = it` differently Issue #21139 has been updated by jeremyevans0 (Jeremy Evans). vinistock (Vinicius Stock) wrote in #note-15: > The way I reason about this code is as follows: > > ```ruby > 42.tap do > it = it > # ^ First step: read the value of `it`, which is the block's argument and therefore is equal to 42 > # ^ Second step: declare a local variable called `it` and store the value of the right hand side (which is 42) > p it > # ^ Third step: at this point `it` no longer refers to the special variable `it`, but to a local variable `it`, for which the value is currently 42 > end > ``` This isn't how Ruby works in general. Ruby is parsed left-to-right. When it comes across a local variable declaration, it registers the local variable. If Ruby comes across that identifier later in the same local variable scope (even the RHS of the same assignment expression), Ruby treats the identifier as a local variable reference instead of a method call. Consider: ```ruby def x = 1 x = x x # => nil ``` `x` is `nil` in the above example because by the time the RHS of the assignment is parsed, `x` is a local variable, so it is not treated as a method call. When parsing `it` instead of a block, `it` is not considered a local variable until it is referenced, and `it` as a user-defined local variable has precedence over `it` as a block parameter. You can tell this from the following code: ```ruby it = 1 proc{it}.call(2) # 1, not 2 ``` Since `it` is declared as a user-defined variable on the LHS of the assignment before `it` is referenced on the RHS of the assignment, `it` is a user-defined local variable and not the implicit block parameter on the RHS on the assignment, and therefore should have value `nil`, just as any user-defined local variable would. > > I think it would be far simpler to forbid it as a local variable name, though the compatibility implications would need to be carefully considered. > > Maybe this is a better path forward as these edge cases can be confusing. That said, I still think that the current behaviour in Prism is more intuitive and consistent. It think it is inconsistent with the rest of Ruby, and a bug in Prism. Consider the VM instructions: For `it = it` outside of a block, both parse.y and Prism, and for `it = it` inside a block in parse.y: ``` local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1]) [ 1] it@0 0000 getlocal_WC_0 it@0 ( 1)[Li] 0002 leave ``` For `it = it` instead of a block in Prism: ``` local table (size: 2, argc: 1 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1]) [ 2] ?@0[ 1] it@1 0000 getlocal_WC_0 ?@0 ( 1)[LiBc] 0002 dup 0003 setlocal_WC_0 it@1 ``` You could argue the Prism behavior is more useful. But if you want to make it consistent, you would have to change Ruby so that local variables are not in scope until after they are assigned, instead of after they are declared. That would break a lot of Ruby code. ---------------------------------------- Bug #21139: Prism and parse.y parses `it = it` differently https://bugs.ruby-lang.org/issues/21139#change-114177 * Author: tompng (tomoya ishida) * Status: Feedback * Assignee: prism * ruby -v: ruby 3.5.0dev (2025-02-14T16:49:52Z master ee181d1bb7) +PRISM [x86_64-linux] * Backport: 3.1: UNKNOWN, 3.2: UNKNOWN, 3.3: UNKNOWN, 3.4: UNKNOWN ---------------------------------------- ~~~ # ruby --parser=parse.y -e "42.tap { it = it; p it }" nil # ruby --parser=prism -e "42.tap { it = it; p it }" 42 ~~~ ---Files-------------------------------- clipboard-202503081702-idzz2.png (22.6 KB) -- https://bugs.ruby-lang.org/ ______________________________________________ ruby-core mailing list -- ruby-core@ml.ruby-lang.org To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/