これは、asumikam #phpcon_odawara Advent Calendarの1日目の記事です🎄 本日は「二郎系ラーメンのコールで学ぶ AST 解析」の感想を書いていきます!
📖 スライド speakerdeck.com
感想
他のカンファレンスでもしばしばみるAST関連の登壇ですが、この発表は今まで触れてきてない人も「わかる...わかるぞ!」となるような発表になっていました。 「ニンニクマシマシ野菜マシマシカラメ」というワードを字句解析→構文解析をするところは面白さもありながらわかりやすさも兼ね揃えていました。 資料の最後に出てくるPHPでの例も、これまでの話があったためかなりスッと入ってきました。
▼ 字句解析・構文解析については、業務で偶然にも似たような概念のことをやっていたりした*1ので、めもり〜☆さんの登壇と合わせて、チョット・・・ほんのチョット・・・前より仲良くなれた気がしています。 asumikam.com
あとは、前職の時PHPStanのカスタムルールを作ろうとテストで書いていたことを思い出しました。 その流れで PHPStan API documentationを睨めっこしていて、ASTというデータ構造を理解していくのが楽しかった思い出があります。
PHPStanのルールをチラ見してみる
と、いうことでASTのことをもうちょっと知りたいということで、PHPStanが実際どんなルールで動いているか、どんなコードの書き方が為されているかをみていこうとおもいます👀
上記がいわゆるcomposer require --dev phpstan/phpstan
をしたら入る方です。composer.jsonをみてもそうなっていますね。
そしてリポジトリをさら〜っと眺めても..........なんだかRuleっぽいものがないぞ?🧐
ムムム...とおもいREADMEを見たら以下が書いてありました。
Any contributions are welcome. PHPStan's source code open to pull requests lives at phpstan/phpstan-src.
こっちにPHPStanのルールが格納されているんですね。では早速中をみていく〜。 srcディレクトリ配下にRulesディレクトリをみつけました。ビンゴ!!!!!!! なんだかわかりやすいルールをめちゃくちゃななめよみしていきましょう。名前的にわかりやすそうなphpstan-src/src/Rules/Variables/DefinedVariableRule.phpを見ていきます。
Ruleクラスの構成は「getNodeType」「processNode」2つのメソッドで構成されています。
<?php public function getNodeType(): string { return Variable::class; }
▲ このメソッドを利用して、このルールは変数に対して適用すんぞ〜ってやっている部分があるんだろうな。(エアプ)
<?php public function processNode(Node $node, Scope $scope): array { if (!is_string($node->name)) { return []; } if ($this->cliArgumentsVariablesRegistered && in_array($node->name, [ 'argc', 'argv', ], true)) { $isInMain = !$scope->isInClass() && !$scope->isInAnonymousFunction() && $scope->getFunction() === null; if ($isInMain) { return []; } } if ($scope->isInExpressionAssign($node) || $scope->isUndefinedExpressionAllowed($node)) { return []; } if ($scope->hasVariableType($node->name)->no()) { return [ RuleErrorBuilder::message(sprintf('Undefined variable: $%s', $node->name)) ->identifier('variable.undefined') ->build(), ]; } elseif ( $this->checkMaybeUndefinedVariables && !$scope->hasVariableType($node->name)->yes() ) { return [ RuleErrorBuilder::message(sprintf('Variable $%s might not be defined.', $node->name)) ->identifier('variable.undefined') ->build(), ]; } return []; }
▲ interfaceがきられているので具体的なクラスは書いてないですが、ここでのNodeクラスはPhpParser\Node\Expr\Variable
じゃないかなあと推測できます。
ドキュメントをのname
プロパティをみるとstring|Expr
が入っていそうで、ナチュラルにstringな変数か.......Expr
、ハッ!!動的変数を取っている場合か、その2択のどちらかが取れそうですね。
中身を実際に見ていきましょう。
単純にreturn [];
をしているのは以下の時っぽいです。
- そもそも変数がstringじゃなかったらreturnしちゃう
- 特別なCLI変数名だったらreturnしちゃう
- 変数のアサインしていそうな時や(未定義だがいま定義しとるんじゃ)
- 未定義変数だが許してもらえる時(
isset($hoge)
とかの行?)
めっちゃおもれ〜〜〜〜〜。なるほど〜〜〜〜、ってなりながら読めますね。
で、大事なのは if-elseifのブロック。ここで配列にRuleErrorBuilder::message
を突っ込んで返しているのでダメな時っぽい。
中に書いてある文章もみて。'Undefined variable: $%s', $node->name)
....イヤ〜ンいつもみるやつじゃん。
・・・てな感じで、ASTに分解した後の処理(PHPStan)はこう書いてあるんですね。 実は前もこれを読んでなるほど、となった体験があるんですが、あの時よりもどうしてその構造になっているかを考えながら読めてたのしい!!!
▼ PHPerKaigi2024でこんなCfPを出していたのを思い出したので、もうちょっと精度上げてPHPerKaigi2025で出し直そ・・・💨 fortee.jp
めもり〜☆さん、登壇ありがとうございました
セッション時間が20分とか30分とかで戸惑わせてしまってすみませんでした😭🙏
タイトルから参加者のハートをキャッチしているのがYouTube越しでめちゃくちゃ伝わりましたw 朝一発目で軽やかな雰囲気でセッションを運んでくださっていてめちゃくちゃ最高〜〜。
素敵な登壇ありがとうございました🙌
*1:発表してnsfisisさんに指摘されて気づいた