小ネタ: Ruby 2.5.0 の細かい構文的変化

サーバーのRubyバージョンを2.4.5から2.5.3に上げようとしたところ、構文エラーが発生しました。その構文エラーは devise に由来しているもので、 #4668 で修正されたものでした。最小化すると、以下のようなコードが構文エラーになるようになっていました。

def foo(options, &block); end
foo a: [ ] {}

devise 自体はアップデートすればいいとして、せっかくなのでこの原因を追跡してみました。構文エラーなので parse.y (2.4.5, 2.5.3) を読んでいきます。字句解析器がステートフルなので完全に理詰めで詰めるのは難しいですが、2行目の導出列はおそらく以下のようになるだろうと見当をつけました。


program
top_compstmt
top_stmts opt_terms
top_stmt opt_terms
stmt opt_terms
expr opt_terms
command_call opt_terms
command opt_terms
fcall command_args cmd_brace_block opt_terms
operation command_args cmd_brace_block opt_terms
tIDENTIFIER command_args cmd_brace_block opt_terms
tIDENTIFIER call_args cmd_brace_block opt_terms
tIDENTIFIER assocs opt_block_arg cmd_brace_block opt_terms
tIDENTIFIER assoc opt_block_arg cmd_brace_block opt_terms
tIDENTIFIER tLABEL arg_value opt_block_arg cmd_brace_block opt_terms
tIDENTIFIER tLABEL arg opt_block_arg cmd_brace_block opt_terms
tIDENTIFIER tLABEL primary opt_block_arg cmd_brace_block opt_terms
tIDENTIFIER tLABEL tLBRACK aref_args ']' opt_block_arg cmd_brace_block opt_terms
tIDENTIFIER tLABEL tLBRACK none ']' opt_block_arg cmd_brace_block opt_terms
tIDENTIFIER tLABEL tLBRACK ']' opt_block_arg cmd_brace_block opt_terms
tIDENTIFIER tLABEL tLBRACK ']' none cmd_brace_block opt_terms
tIDENTIFIER tLABEL tLBRACK ']' tLBRACE_ARG brace_body '}' opt_terms
tIDENTIFIER tLABEL tLBRACK ']' tLBRACE_ARG opt_block_param compstmt '}' opt_terms
tIDENTIFIER tLABEL tLBRACK ']' tLBRACE_ARG none compstmt '}' opt_terms
tIDENTIFIER tLABEL tLBRACK ']' tLBRACE_ARG compstmt '}' opt_terms
tIDENTIFIER tLABEL tLBRACK ']' tLBRACE_ARG stmts opt_terms '}' opt_terms
tIDENTIFIER tLABEL tLBRACK ']' tLBRACE_ARG none opt_terms '}' opt_terms
tIDENTIFIER tLABEL tLBRACK ']' tLBRACE_ARG '}' opt_terms
tIDENTIFIER tLABEL tLBRACK ']' tLBRACE_ARG '}'

このように見当をつけた上で、エラーメッセージを見ると、

test.rb:2: syntax error, unexpected '{', expecting end-of-input
foo a: [ ] {}

なので、 tLBRACE_ARG ではなく '{' という終端記号を期待していることがわかります。この2つはどちらも同じ { という文字から、字句解析器の状態に応じて出ています。ということは、状態の計算を調べると良さそうです。

上記の終端記号列が出るはずという推測から字句解析器 (parse_yylex 関数) をつまみ食いしてみます。

すると、字句解析器の状態のうち lex_state が以下のような遷移をとっていると推測がつきます。


(EXPR_BEG)
foo -> tIDENTIFIER (EXPR_END|EXPR_LABEL)
a: -> tLABEL (EXPR_ARG|EXPR_LABELED)
[ -> tLBRACK (EXPR_BEG|EXPR_LABEL)
] -> ']' (EXPR_ENDARG (2.4.5) EXPR_END (2.5.3))
...

これで原因の見当がつきました。 ] を入力したときの次の状態の計算 (2.4.5, 2.5.3) がこのコミットで変更されています。これはこのチケットを修正するために入ったもののようでした。

試しに、このコミットの前後のソースをビルドして最初の例を実行してみたところ、確かにこのコミットの後で構文解析に失敗するようになったことが確認できました。

仕組みがわかったので、気持ちよく年越しができそうです。皆さま、よいお年を。

Wantedly, Inc.'s job postings
12 Likes
12 Likes

Weekly ranking

Show other rankings
If this story triggered your interest, go ahead and visit them to learn more