ENCA 24日目: IsRegExp の挙動変更(リジェクト)
IsRegExp とは
ECMAScript の仕様に IsRegExp が定義されています。これは文字列のメソッド内で、渡された引数が正規表現オブジェクトかどうかを判定する際に用いられます。使われている箇所について見てみると以下の2つのパターンに分類できます。
- 引数として文字列のみを許容するため、正規表現オブジェクトが渡ってきたら
TypeError
を投げたい場合String.prototype.includes
String.prototype.startsWith
String.prototype.endsWith
- 引数に正規表現オブジェクトが渡ってきたら
g
フラグを持っているかチェックしたい場合String.prototype.matchAll
String.prototype.replaceAll
IsRegExp の挙動変更(リジェクト)
IsRegExp は以下のようになっています。
IsRegExp ( argument )
The abstract operation IsRegExp takes argument argument (an ECMAScript language value) and returns either a normal completion containing a Boolean or a throw completion. It performs the following steps when called:
- If argument is not an Object, return
false
.- Let matcher be ?
Get(argument, %Symbol.match%)
.- If matcher is not
undefined
, returnToBoolean(matcher)
.- If argument has a
[[RegExpMatcher]]
internal slot, returntrue
.- Return
false
.
仕様を読むと [[RegExpMatcher]]
内部スロットを持つネイティブの RegExp
の他にオブジェクトが Symbol.match
を実装している場合にも true
を返すようになっていることがわかります。しかし正規表現の Well-known Symbols メソッドは Symbol.replace
, Symbol.search
, Symbol.split
そして Symbol.matchAll
が存在しており、Symbol.match
だけ特別扱いしているのは不自然です。
というわけで2018年11月に単に [[RegExpMatcher]]
内部スロットを持つかどうかをチェックする挙動に変更しようと議論され、承認されました。しかしその後 Chromium の調査によりネイティブの RegExp
ではないが Symbol.match
を実装しているオブジェクトのケースが 20% ほどあることがわかり、破壊的変更になってしまうということからリジェクトされることとなってしまいました。
Discussion
プルリクの最後から2番目のコメント
が気をそそるんですけど
そもそもその feature detection は
String#{includes,startsWith,endsWith}
でしか使われていませんし、この変更を取り入れても旧バージョンの core-js が使われているサイトはこれらのString
メソッドの古い挙動が core-js の polyfill によって保たれるんですから互換性がそもそもあるのでは?まあ依然として
V8RegExpMatchIsTrueishOnNonJSRegExp
の 0.03% を調査しないといけないんですけどcore-js を使っている場合は問題ないという認識です。ただ npm パッケージで機能追加された正規表現ライブラリが提供されることがあるので、きっとその中にネイティブの
RegExp
を継承していないものがあるんでしょうね……。core-js を抜いたら
V8RegExpMatchIsFalseishOnJSRegExp
が何%になるか気になりますけど、調べる方法ありますかね……正確に調べる方法が思いつかないですね……。通常の提案を入れる時のように Chrome Canary に入れてみて壊れるサイトがないか確かめるしかない気がします。