RubyKaigi 2022 で動的に RBS を生成してみた話をする
前日の宣伝になってしまうんですが明日の RubyKaigi 2022 で動的に RBS を生成してみた話をします。
内容としては上に書いてある通りなんですが Ruby の実行中に型情報を収集して RBS を生成してみたって内容の話になります。 やったことに対する先出しをすると以下のように Ruby を実行しつつ RBS を出力するような gem をつくりました(つくっています)。
# sample.rb class FizzBuzz def initialize(value) @value = value end def value; @value end def apply value % 15 == 0 ? "FizzBuzz" : value % 3 == 0 ? "Fizz" : value % 5 == 0 ? "Buzz" : value end end p (1..20).map { FizzBuzz.new(_1).apply }
# sample.rb を実行して、その結果を元にして RBS を出力する $ rbs-dynamic trace sample.rb # RBS dynamic trace 0.1.0 class FizzBuzz private def initialize: (Integer value) -> Integer def apply: () -> (Integer | String) def value: () -> Integer @value: Integer end $
内容的には苦労話メインになりそうなんですが、RBS や Ruby の型について気になる人がいればぜひぜひ聞きに来てくださいー。 参加されない方も後日スライドは公開するのでぜひ読んでもらえるとー。
2022/09/02 今回の気になった bugs.ruby のチケット
今週は Numbered Parameters の _1
を it
に置き換えるチケットがありました。
[Raises Exception for Range#last(n) with Float::INFINITY]
Range
の終端がFloat::INFINITY
の場合に無限ループする
(6..Float::INFINITY).last(1) # => infinite loop!! (6..).last(1) # cannot get the last element of endless range (RangeError) (-Float::INFINITY..4).last(1) # can't iterate from Float (TypeError) (..4).last(1) # can't iterate from Float (TypeError)
- これを
RangeError
で例外にしようというチケット - そういえば昔
Range#first
で似たようなチケット立てた記憶- https://bugs.ruby-lang.org/issues/15813
- これは
(..10).first # => nil
になっていたのを例外にするようにした奴
- このあたり、微妙に挙動が違っていて無限に難しい
- 引数がない場合は以下のような挙動にもなる
p (1..10).last # => 10 p (1..Float::INFINITY).last # => Infinity p (1..).last # Error: in `last': cannot get the last element of endless range (rangeerror)
[Feature #18159] Integrate functionality of syntax_suggest gem into Ruby
dead_end
あらためsyntax_suggest
が標準ライブラリに追加されました- 去年の RubyKaigi で発表されていた gem ですね
- 現状は標準ライブラリに入っただけでまだ
ruby
コマンドからスクリプトを実行したときには特にエラー出力とかはされないぽいですね- エラー制御周りでまだ実装する必要がありそう?
- https://bugs.ruby-lang.org/issues/18159#note-35
- Ruby 3.2 のリリースまでにはエラー出力までされるようになるんですかねー
[Feature #18980] Re-reconsider numbered parameters: it
as a default block parameter
_1
の変わりにit
を Numbered parameters で利用したいというチケット_1
は『未使用のローカル変数』に見えてしまうらしい- わたしは結構カジュアルに
_1
を使っているんですが今の所名前に関して困ったとか混乱しとかはないですねえ(そういう話も聞いたことはない - 慣れの問題なんですかね?
- どっちかって言うとナンパラ自体が特別な構文なので
it
のような一般的に使用されるような名前よりも_1
のように『特別な名前に見える方』が好みではありますね - チケット内でかなり議論が進んでいるので気になる人は読んでみてみるとよいかも
it
じゃなくて@
を使おう、みたいな話もでてますねit
よりも@
の方が特別な意味があるように見えるのでit
よりはマシだと思うんですが@
にするぐらいなら_1
のままの方がいいかなあ…
[Feature #18951] Object#with to set and restore attributes around a block
- 以下のように一時的に設定を変えたりインスタンス変数を変えたいときがある
def test_something_when_enabled # 一時的に SomeLibrary.enabled を有効にしたい enabled_was, SomeLibrary.enabled = SomeLibrary.enabled, true # test things ensure SomeLibrary.enabled = enabled_was end
def with_something_enabled # ブロック引数内では enabled_was を有効な状態で実行したい enabled_was = @enabled @enabled = true yield ensure @enabled = enabled_was end
- この時に以下のような場合だと意図しない挙動になるケースがある
def test_something_when_enabled # some_call_that_may_raise で例外が発生した場合 SomeLibrary.enabled = nil になってしまう # これはまだ enabled_was に対して値が割り当てられていないため some_call_that_may_raise enabled_was, SomeLibrary.enabled = SomeLibrary.enabled, true # test things ensure SomeLibrary.enabled = enabled_was end
- これを解決するために以下のような
Object#with
を導入したいというチケット
class Object def with(**attributes) old_values = {} attributes.each_key do |key| old_values[key] = public_send(key) end begin attributes.each do |key, value| public_send("#{key}=", value) end yield ensure old_values.each do |key, old_value| public_send("#{key}=", old_value) end end end end def test_something_when_enabled SomeLibrary.with(enabled: true) do # test things end end GC.with(measure_total_time: true, auto_compact: false) do # do something end
- こういう制御は結構するので便利そう
2022/08/25 今回の気になった bugs.ruby のチケット
今週は lazy.take(0)
した時に期待した挙動にならないというバグ報告がありました。
[Bug #18972] String#byteslice should return BINARY (aka ASCII-8BIT) Strings
# 今はレシーバのエンコーディングになっている p "fée".byteslice(1).encoding # => #<Encoding:UTF-8> p "fée".byteslice(1).valid_encoding? # => false
- 期待する挙動は以下の通り
p "fée".byteslice(1).encoding # => #<Encoding:ASCII-8BIT> p "fée".byteslice(1).valid_encoding? # => true
[Bug #18971] Enumerator::Lazy.take(0) leaks first element into next operation
- 以下のように
lazy
でtake(0)
を使用した時は期待する挙動になる
(2..10).take(0).to_a # => [] (2..10).take(0).map(:&itself).to_a # => [] (2..10).lazy.take(0).to_a # => []
- しかし、以下のように
take(0)
した後に別の操作をすると期待する挙動にならないというバグ報告
p (2..10).lazy.take(0).map(&:itself).to_a # => [2] p (2..10).lazy.take(0).select(&:even?).to_a # => [2] p (2..10).lazy.take(0).select(&:odd?).to_a # => [] p (2..10).lazy.take(0).reject(&:even?).to_a # => [] p (2..10).lazy.take(0).reject(&:odd?).to_a # => [2] p (2..10).lazy.take(0).take(1).to_a # => [2] p (2..10).lazy.take(0).take(0).take(1).to_a # => [2] p (2..10).lazy.take(0).drop(0).to_a # => [2] p (2..10).lazy.take(0).find_all {|_| true}.to_a # => [2] p (2..10).lazy.take(0).zip((12..20)).to_a # => [[2, 12]] p (2..10).lazy.take(0).uniq.to_a # => [2] p (2..10).lazy.take(0).sort.to_a # => [] p (2..2).lazy.take(0).sort.to_a # => []
[Bug #18974] Wrong line number in the rescue iseq for the exception matching code
iseq
の出力とrescue => e
している箇所の行番号がズレているというバグ報告- コード上は4行目になっているが iseq の出力では5行目になっている
def foo begin raise 'error' rescue => e puts e.message end end puts RubyVM::InstructionSequence.of(method :foo).disasm __END__ == disasm: #<ISeq:foo@/tmp/vL5efqT/19:1 (1,0)-(7,3)> (catch: TRUE) == catch table | catch type: rescue st: 0000 ed: 0005 sp: 0000 cont: 0006 | == disasm: #<ISeq:rescue in foo@/tmp/vL5efqT/19:4 (4,2)-(5,18)> (catch: TRUE) | local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1]) | [ 1] $!@0 | 0000 getlocal_WC_0 $!@0 ( 5)[Li] | 0002 putobject StandardError | 0004 checkmatch 3 | 0006 branchunless 20 | 0008 getlocal_WC_0 $!@0 ( 4) | 0010 setlocal_WC_1 e@0 | 0012 putself ( 5) | 0013 getlocal_WC_1 e@0 | 0015 opt_send_without_block <calldata!mid:message, argc:0, ARGS_SIMPLE> | 0017 opt_send_without_block <calldata!mid:puts, argc:1, FCALL|ARGS_SIMPLE> | 0019 leave | 0020 getlocal_WC_0 $!@0 | 0022 throw 0 | catch type: retry st: 0005 ed: 0006 sp: 0000 cont: 0000 |------------------------------------------------------------------------ local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1]) [ 1] e@0 0000 putself ( 3)[LiCa] 0001 putstring "error" 0003 opt_send_without_block <calldata!mid:raise, argc:1, FCALL|ARGS_SIMPLE> 0005 nop ( 1) 0006 leave ( 7)[Re]
[Feature #18408] Allow pattern match to set instance variables
# これはエラーになる case {name: "John", age: 42} in name: /jo/ => @name, age: @age end puts [@name, @age] #=> ["John", 42]
- 上記のようにインスタンス変数でも束縛したいことがチケットの内容になる
- これに関しては以前から議論されていたんですが現時点では『ローカル変数以外はサポートしない』という意思決定がされたようです
- https://bugs.ruby-lang.org/issues/18408#note-19
42 => @a
を許容すると42 => a[1]
や42 => obj.attr
などより複雑な要求がくる事が理由として上げられていました
2022/08/18 今回の気になった bugs.ruby のチケット
今週はメモ化を行う API を Ruby のコア機能に入れる議論がありました。
[eature #18934] Proposal: Introduce method results memoization API in the core
class Test def foo puts "call!" 5 end # foo メソッドをメモ化する memoized :foo end o = Test.new # 1回目の呼び出しはメソッド本体が呼び出される o.foo # prints "call!", returns 5 # 2回目以降はメソッドの本体は呼び出されず1回目の結果を返す o.foo # returns 5 immediately
- 具体的な例として以下のようなコードが上げられている
SomeTokenizer#call
が非効率なメソッドの場合にボトルネックになる可能性がある
class Sentence attr_reader :text def initialize(text) = @text = text def tokens() = SomeTokenizer.new(text).call def size() = tokens.size def empty?() = tokens.all?(&:whitespace?) def words() = tokens.select(&:word?) def service?() = words.empty? end many_sentences .reject(&:empty?) .select { _1.words.include?('Ruby') } .map { _1.words.count / _1.tokens.count }
- メモ化の手段として
||=
を使うイディオムもある
def words()= @words ||= tokens.select(&:word?)
- しかし、イディオムにはいくつか欠点があり例えば
service?
のようにnil / false
を返すようなメソッドの場合にはその都度処理が呼ばれてしまう - なのでそれを回避する場合は以下のように定義する必要がある
def empty? # @empty が定義されている場合のみ tokens.all?(&:whitespace?) を呼び出す return @empty if defined?(@empty) @empty = tokens.all?(&:whitespace?) end
- またインスタンス変数を使うことで
#inspect
の結果などに保持している値が含まれてしまう
class Sentence def initialize(text) @text = text end def empty? # @empty が定義されている場合のみ tokens.all?(&:whitespace?) を呼び出す return @empty if defined?(@empty) @empty = @text.empty? end end require "yaml" s = Sentence.new('Ruby is cool') puts s.to_yaml # => --- !ruby/object:Sentence # text: Ruby is cool p s.empty? # => false # s の中身が汚染されてしまう puts s.to_yaml # --- !ruby/object:Sentence # text: Ruby is cool # empty: false
- メモ化するライブラリとして
memoist
やmemo_wise
が紹介されているmemo_wise
の方が新しくて効率がいいらしい- これらのライブラリでは
nil / false
の戻り値にも対応している
# 使用例 class Sentence prepend MemoWise # ... memo_wise def empty?() = tokens.all?(:whitespace?) end
- ただし、上記のライブラリを使った場合でもインスタンス変数を使っているのでオブジェクトが汚染されてしまう
p s.empty? # false p s # #<Sentence:0x00007f0f474eb418 @_memo_wise={:empty?=>false}, @text="Ruby is cool"> puts s.to_yaml # --- !ruby/object:Sentence # _memo_wise: # :empty?: false # text: Ruby is cool
2022/08/11 今回の気になった bugs.ruby のチケット
今週は **nil
を許容する提案などがありました。
[Feature #18959] Handle gracefully nil kwargs eg. **nil
- 以下のように『値が存在している時のみ』
Hash
に値を追加したいケースがある
{ some: 'value', **({ id: id } if id.present?), # id の値が存在していれば `{ id: id }` を Has に定義したい }
- しかし以下のように
**nil
は呼び出すことができないので上記のコードは意図する挙動をしない
def qwe(a: 1) end qwe(**nil) #=> fails with `no implicit conversion of nil into Hash (TypeError)` error { a:1, **nil } #=> fails with `no implicit conversion of nil into Hash (TypeError)` error
- 以下のようにして回避する事もできるが冗長である
h = { some: 'value' } h[:id] = id if id.present? h # または以下 { some: 'value', **(id.present? ? { id: id } : {}), }
*nil
は動作しており、以下のように書くことができるので**nil
も許容してほしいという提案- ちなみに
*nil
は[]
になる - Rails でよく使うらしい
- ちなみに
content_tag :div, class: [*('is-hero' if hero), *('is-search-page' if search_page)].presence
- ちなみに
*obj
や**obj
はobj.to_a
やobj.to_hash
が内部で呼び出される
class X def to_a ["default"] end def to_hash { default: 0 } end end x = X.new pp(*x) # => "default" pp(**x) # => {:default=>0}
- なので以下のように
nil.to_hash
を定義して対応する事は可能
# to_hash を定義しておく def nil.to_hash = {} def qwe(a: 1) end qwe(**nil) #=> OK { a:1, **nil } #=> OK
- ちなみに
nil.to_hash
を定義している場合に Ruby 2.6 で以下のケースで segv するらしい
h = {"y"=>"!"} "xyz".sub(/y/, h) #=> "x!z" h = nil "xyz".sub(/y/, h) #=> TypeError (no implicit conversion of nil into String) def nil.to_hash; {}; end "xyz".sub(/y/, h) #=> "xz" (segfault in 2.6!)
[Feature #18961] Introduce support for pattern matching all elements of an array
- 以下のように配列の全ての要素が特定のパータンの場合のパターンマッチを書きたいことがある
class Robot; end class User; end case [User.new, User.new] in [User, *] => u if u.all? { _1 in User } puts 'array of users!' end # => array of users! case [User.new, User.new, Robot.new] in [User, *] => u if u.all? { _1 in User } puts 'array of users!' end # => Error: guard clause does not return true (NoMatchingPatternError)
- これを以下のような構文で対応させいたいという提案
case [User.new, User.new] in [User*] puts 'array of users!' end
- これだけだと
u.all? { _1 in User }
でよさそうに見えるんですがパターンの一部だけに記述したい、みたいな要求はありそう
[Bug #18960] Module#using raises RuntimeError when called at toplevel from wrapped script
load
の第二引数にtrue
(またモジュール) を渡した時にトップレベルでusing
されているとエラーになるというバグ報告true
を渡すと無名モジュール内でload
したスクリプトが定義される- Kernel.#load (Ruby 3.1 リファレンスマニュアル)
# using.rb using Module.new
# OK load "./using.rb" # NG load "./using.rb", true # raises RuntimeError (main.using is permitted only at toplevel)
- こんなバグが
マージされた機能
{Method,UnboundMethod}#{public?,private?,protected?}
を RevertSymbol#to_proc
ではpublic
メソッドのみ呼び出されるように変更obj.map(&:hoge)
のhoge
はpublic
である必要がある- https://github.com/ruby/ruby/pull/6018
[Bug #18946] Time#to_date returns incorrect date
- 以下のように
1499-12-27
のTime
をDate
に変換すると1499-12-18
になるというバグ報告
require "time" time = Time.local(1499, 12, 27) pp time # => 1499-12-27 00:00:00 +091859 # 変換した日付は -9日されている pp time.to_date # => #<Date: 1499-12-18 ((2268919j,0s,0n),+0s,2299161j)>
- これは対象の日付が
ユリウス暦
として扱われているのが原因かも?- https://ja.wikipedia.org/wiki/%E3%82%B0%E3%83%AC%E3%82%B4%E3%83%AA%E3%82%AA%E6%9A%A6
- `ユリウス暦1582年10月4日木曜日の翌日を、曜日を連続させながら、グレゴリオ暦1582年10月15日金曜日とすることを定め、その通りに実施された。
# 1582-10-15 の場合 require "time" time = Time.local(1582, 10, 15) pp time # => 1582-10-15 00:00:00 +091859 # 変換前と変換後は同じ日付 pp time.to_date # => #<Date: 1582-10-15 ((2299161j,0s,0n),+0s,2299161j)>
# 1582-10-15 の場合 require "time" time = Time.local(1582, 10, 14) pp time # => 1582-10-14 00:00:00 +091859 # この日付だと変換後はユリウス暦の日付に置き換わっている pp time.to_date # => #<Date: 1582-10-04 ((2299160j,0s,0n),+0s,2299161j)>
1499-12-27
とはズレてる日数がちょっと違ってるのが気になる…- また
Time#to_datetime
とTime#to_date
で返ってくる日付が異なっている
require "date" p Time.local(1499, 12, 27).to_datetime # => #<DateTime: 1499-12-27T00:00:00-07:52 ((2268928j,28378s,0n),-28378s,2299161j)> p Time.local(1499, 12, 27).to_date # => #<Date: 1499-12-18 ((2268919j,0s,0n),+0s,2299161j)>
- これはバグだったので最新版で修正されている
- どちらもグレゴリオ暦を参照するようになる
- https://github.com/ruby/date/pull/73
require "date" p RUBY_VERSION # => "3.2.0" p Time.local(1499, 12, 27).to_datetime # => #<DateTime: 1499-12-18T00:00:00+09:18 ((2268918j,52861s,0n),+33539s,2299161j)> p Time.local(1499, 12, 27).to_date # => #<Date: 1499-12-18 ((2268919j,0s,0n),+0s,2299161j)>
2022/08/04 今回の気になった bugs.ruby のチケット
今週は Enumerator.product
が新しく追加されました。
[Bug #18953] Array#uniq
doesn't evaluate the given block when the size of the array is one
- 配列の要素が1つの場合に
Array#uniq
のブロックが呼ばれないんだけどこれは期待する挙動?というバグ報告
# これはエラーにならないが [1].uniq { aaa } # これはエラーになる # error: undefined local variable or method `aaa' for main:Object (NameError) [1, 2].uniq { aaa }
- これ自体は期待する挙動ぽいですが、実装依存になるんですかね?
- また
Array#sort_by
#max_by
#min_by
は要素が1つの場合でもブロックが呼び出されます
# error: undefined local variable or method `aaa' for main:Object (NameError) [1].sort_by { aaa }
- あと
Enumerable#uniq
は要素が1つの場合でもエラーになるみたいですね
require "set" # 2つともエラーになる Set[1].uniq { aaa } { a: 1 }.uniq { aaa }
[Feature #18950] Hash#slice fails to copy default block
Hash#slice
がデフォルトの proc をコピーして返さないバグ報告
hash_with_default = Hash.new { |h, k| h[k] = {} } # デフォルト proc が設定されている pp hash_with_default.default_proc # => #<Proc:0x00007f7cd0f3c8a8 /tmp/vdmem8a/29:1> # #slice の戻り値はデフォルト proc が設定されていない pp hash_with_default.slice(:a).default_proc # => nil
- これは期待する挙動で
Hash#slice
は新しいHash
オブジェクトを返してその時にデフォルトの proc はコピーされないみたいです - 以下のように
Hash#slice
以外でも同様の挙動
hash_with_default = Hash.new { |h, k| h[k] = {} } pp hash_with_default.except(:a).default_proc # => nil pp hash_with_default.select {}.default_proc # => nil pp hash_with_default.invert.default_proc # => nil
[Bug #18743] Enumerator#next / peek re-use each others stacktraces
- 次のように
Enumerator#peek
とEnumerator#next
がある
enum = [1, 2, 3].each # Enumerator#peek は状態を変化させないで「次」を返す pp enum.peek # => 1 pp enum.peek # => 1 pp enum.peek # => 1 # Enumerator#next は状態を変化させて「次」を返す pp enum.next # => 1 pp enum.next # => 2 pp enum.next # => 3 # 「次」がない場合はエラーになる # error: `next': iteration reached an end (StopIteration) pp enum.next # => 3
- 次のように
peek
で失敗したした後にnext
でエラーになるとバックトレースの行数が正しくないというバグ報告
# enum.rb # 1 # 2 enum = [].each # 3 enum.peek rescue nil # 4 <- エラー行はここを指している enum.next # 5 <- ここでエラーになるが↑
$ ruby enum.rb enum.rb:4:in `peek': iteration reached an end (stopiteration) from enum.rb:4:in `<main>'
- 以下のように複数呼び出した場合も意図しない行数になっている
# enum.rb # 1 # 2 enum = [].each # 3 enum.peek rescue nil # 4 enum.next rescue nil # 5 enum.peek rescue nil # 6 puts "line #{__line__}" # 7 enum.next # 8
$ ruby enum.rb line 7 enum.rb:4:in `peek': iteration reached an end (stopiteration) from enum.rb:4:in `<main>'
[feature #18685] Enumerator.product: cartesian product of enumerables
- 要素ごとの全ての組み合わせの
Enumerator
を生成するEnumerator.product
を追加する提案
product = Enumerator.product(1..3, ["a", "b"]) p product.class #=> Enumerator product.each do |i, c| puts "#{i}-#{c}" end __end__ output: 1-a 1-b 2-a 2-b 3-a 3-b
- この機能は ruby 3.2 にマージされました
2022/07/29 今回の気になった bugs.ruby のチケット
今週は CRuby のインデントが全てスペースに展開されるチケットがありました。
[Bug #18883] parse.y: trailing comma cannot coexist with star
- 末尾に
,
がある時に*
付きの多重代入がうまく動作しないというバグ報告
x, y = 1, 2 # OK => x = 1, y = 2 x, y, = 1, 2 # OK => x = 1, y = 2 *x, y = 1, 2 # OK => x = [1], y = 2 *x, y, = 1, 2 # syntax error, unexpected '='
x, = [1, 2, 3]
はx, * = [1, 2, 3]
と同等になっている- したがって
*x, y, = 1, 2
は*x, y, * = 1, 2
と同等となる - Ruby では
*
が2つある場合は代入することができないので報告されているエラー自体は期待する挙動っぽい?
*x, y, * = 1, 2 # => syntax error, unexpected *
- また、以下のコードは全て同じ VM 命令になるらしい
- なので末尾の
,
で違いが発生すべきではないとのこと - https://bugs.ruby-lang.org/issues/18883#note-2
- なので末尾の
x, a = [1, 2, 3] x, a, = [1, 2, 3] x, a, * = [1, 2, 3]
bin = RubyVM::InstructionSequence.compile("x, a = [1, 2, 3]") puts bin.disasm # => == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,16)> (catch: FALSE) # local table (size: 2, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1]) # [ 2] x@0 [ 1] a@1 # 0000 duparray [1, 2, 3] ( 1)[Li] # 0002 dup # 0003 expandarray 2, 0 # 0006 setlocal_WC_0 x@0 # 0008 setlocal_WC_0 a@1 # 0010 leave bin = RubyVM::InstructionSequence.compile("x, a, = [1, 2, 3]") puts bin.disasm # => == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,16)> (catch: FALSE) # local table (size: 2, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1]) # [ 2] x@0 [ 1] a@1 # 0000 duparray [1, 2, 3] ( 1)[Li] # 0002 dup # 0003 expandarray 2, 0 # 0006 setlocal_WC_0 x@0 # 0008 setlocal_WC_0 a@1 # 0010 leave bin = RubyVM::InstructionSequence.compile("x, a, * = [1, 2, 3]") puts bin.disasm # => == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,16)> (catch: FALSE) # local table (size: 2, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1]) # [ 2] x@0 [ 1] a@1 # 0000 duparray [1, 2, 3] ( 1)[Li] # 0002 dup # 0003 expandarray 2, 0 # 0006 setlocal_WC_0 x@0 # 0008 setlocal_WC_0 a@1 # 0010 leave
[Misc #18891] Expand tabs in C code
- 既存の Ruby の実装の C のコードのインデントを全てスペースに統一するというチケット
- インデント幅は 4 で幅が8であればタブ文字、それ以外はスペースというスタイルになっていた
- 新しいコードは基本的にスペースを使うようにはなっていたが古いコードではタブ文字が依然として残っている状態になっていた
- VSCode だと古いスタイルでコードを書くことが難しいというのが起因ぽいですね
- また今回の変更を
git blame
で無視されるように.git-blame-ignore-revs
に今回のコミットを追加しているぽいですね- https://github.com/ruby/ruby/blob/708d06f301df9a703fbee6bd1ae369c5e11de7ad/.git-blame-ignore-revs#L6-L7
--ignore-revs-file
オプションでgit blame
で無視できるようなるらしい- 参照: https://tech.hey.jp/entry/2020/10/23/111200
- これ、長年の問題だったので解消されてめでたい
[Bug #18931] Inconsistent handling of invalid codepoints in String#lstrip and String#rstrip
- 無効なコードポイントが文字列に含まれている場合の
String#lstrip
とString#rstrip
で一貫性がないというバグ報告 String#lstrip
だと以下のような挙動String#lstrip
は先頭の空白を取り除くメソッド
# error: `lstrip': invalid byte sequence in UTF-8 (ArgumentError) p " \x80abc".lstrip
# error: `lstrip': invalid byte sequence in UTF-8 (ArgumentError) p " \x80 abc".lstrip
# error: `lstrip': invalid byte sequence in UTF-8 (ArgumentError) p "\x80".lstrip
# ok p " abc \x80".lstrip # => "abc \x80"
String#rstrip
だと以下のような挙動String#rstrip
は末尾の空白を取り除くメソッド
# ok p "abc\x80 ".rstrip # => "abc\x80"
# ok p "abc\x80".rstrip # => "abc\x80"
- また
String#rstrip
の場合は更に意図しない挙動になっているぽい?
# ok: \x80 が消える p "abc \x80".rstrip # => "abc"
# ok: \x80 が消える p "abc \x80 ".rstrip # => "abc"
# ok: \x80 が消える p " \x80 ".rstrip # => ""
\x80
だけの場合だとエラーになる
# error: `rstrip': invalid byte sequence in UTF-8 (ArgumentError) p "\x80 ".rstrip
# error: `rstrip': invalid byte sequence in UTF-8 (ArgumentError) p "\x80".rstrip
- なにもわからない