続・競技プログラミングにprocedural macroを持ち込む

[競技プログラミングにprocedural macroを持ち込む](https://qiita.com/qryxip/items/1b4716b1357c89adeaae)

前回の記事では競技プログラミングに手続き型マクロを持ち込みたい動機と、それを成すための(あまりスマートとは言えない)方法を紹介しました。そして先日「そういえばrust-analzyer (RA)はどうやって手続き型マクロを扱っているのか」と思いふとGitHub上でRAのソースコードのそれっぽい場所を眺めてみたところ、`rpc`, `msg`, `stdin`, `stdout`といった単語が目に入ってきました。もしやと思い[architecture.md](https://github.com/rust-analyzer/rust-analyzer/blob/master/docs/dev/architecture.md#cratesmbe-cratestt-cratesproc_macro_api-cratesproc_macro_srv)を読むとこんなことが書かれているではありませんか。

> For proc macros, the client-server model are used. We pass an argument `--proc-macro` to `rust-analyzer` binary to start a separate process  (`proc_macro_srv`). And the client (`proc_macro_api`) provides an interface to talk to that server separately.

(今は`--proc-macro`ではなくサブコマンド`proc-macro`のようですが)試しにクエリ`ListMacro`のJSONを放り込んだところ普通に結果を返してくれました。

```console
$ echo '{"ListMacro":{"lib":"./target/debug/deps/libfastout-2dbcc333cc21dae5.so"}}' | rust-analyzer proc-macro
{"ListMacro":{"macros":[["fastout","Attr"]]}}
```

もう一つのクエリ`ExpansionMacro`についても`proc-macro2`から上手くJSONにシリアライズすればいけるはずです。これで`watt`を要求しなくてもよくなります。今回cargo-equipに[RAを使った手続き型マクロの展開機能を実装](https://github.com/qryxip/cargo-equip/pull/83)し、[v0.10.0](https://github.com/qryxip/cargo-equip/releases/tag/v0.10.0)としてリリースしました。本記事ではそれにあたり遭遇した問題とその解決方法を書いていこうと思います。

## `rust-analzyer(.exe)`のダウンロード

JSONの形式についてはドキュメント化されておらず、ユーザが使うことも想定されていません。従ってこれからやることはhackであり、単純に`$PATH`内の`rust-analzyer(.exe)`を使うとRAの更新により動作しなくなる可能性が付き纏います。なので特定のバージョンのバイナリを[GitHub Releases](https://github.com/rust-analyzer/rust-analyzer/releases)からツール側でダウンロードして使うような形にしました。

## メッセージのスキーマ

やりとりするメッセージのスキーマは<code>proc\_macro\_api::msg::\{[Request](https://rust-analyzer.github.io/rust-analyzer/proc_macro_api/msg/enum.Request.html), [Response](https://rust-analyzer.github.io/rust-analyzer/proc_macro_api/msg/enum.Response.html)\}</code>で定義されています。<code>[tt::Subtree](https://rust-analyzer.github.io/rust-analyzer/tt/struct.Subtree.html)</code>を模した型を作り、`Serialize`と`Deserialize`と<code>[proc\_macro2::Group](https://docs.rs/proc-macro2/1/proc_macro2/struct.Group.html)</code>との相互`From`を実装すればあとは簡単に扱うことができます。

## `proc-macro`クレートのDLLの場所

`proc-macro`クレートは動的リンクライブラリとしてコンパイルされます。この場所ですが、`cargo check --message-format json`の出力を[cargo\_metadataクレート](https://docs.rs/cargo_metadata)でパースすれば得ることができます。目当てである<code>[compiler-artifact](https://docs.rs/cargo_metadata/0.12/cargo_metadata/struct.Artifact.html)</code>はコンパイルがスキップされていても含まれるため、再コンパイルを試みる必要はありません。

## `proc-macro`クレートをコンパイルするRustのバージョン

`proc-macro`クレートは1.47.0以上のRustでコンパイルされる必要があります。1.42.0でコンパイルしたものをRAに渡すとこのようなことになります。

![Screenshot](https://user-images.githubusercontent.com/14125495/109411140-380f5c80-79e3-11eb-8f68-e8d9054f1d73.png)

今回はactive toolchainが1.47.0未満ならrustupにあるツールチェインを探してそれを使うという形にしました。

## 動作例

[proconio-derive](https://docs.rs/proconio-derive)と[memoise](https://docs.rs/memoise)がAtCoder以外で使えるようになりました。

```rust
#[macro_use]
extern crate memoise as _;
#[macro_use]
extern crate proconio_derive as _;

#[fastout]
fn main() {
    for i in 0..=100 {
        println!("{}", fib(i));
    }
}

#[memoise(n <= 100)]
fn fib(n: i64) -> i64 {
    if n == 0 || n == 1 {
        return n;
    }
    fib(n - 1) + fib(n - 2)
}
```

↓

<details>
<summary>Output</summary>
<div>

```rust
//! # Procedural macros
//!
//! - `memoise 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)`         licensed under `BSD-3-Clause`
//! - `proconio-derive 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)` licensed under `MIT OR Apache-2.0`

/*#[macro_use]
extern crate memoise as _;*/
/*#[macro_use]
extern crate proconio_derive as _;*/

/*#[fastout]
fn main() {
    for i in 0..=100 {
        println!("{}", fib(i));
    }
}*/
fn main() {
    let __proconio_stdout = ::std::io::stdout();
    let mut __proconio_stdout = ::std::io::BufWriter::new(__proconio_stdout.lock());
    #[allow(unused_macros)]
    macro_rules ! print { ($ ($ tt : tt) *) => { { use std :: io :: Write as _ ; :: std :: write ! (__proconio_stdout , $ ($ tt) *) . unwrap () ; } } ; }
    #[allow(unused_macros)]
    macro_rules ! println { ($ ($ tt : tt) *) => { { use std :: io :: Write as _ ; :: std :: writeln ! (__proconio_stdout , $ ($ tt) *) . unwrap () ; } } ; }
    let __proconio_res = {
        for i in 0..=100 {
            println!("{}", fib(i));
        }
    };
    <::std::io::BufWriter<::std::io::StdoutLock> as ::std::io::Write>::flush(
        &mut __proconio_stdout,
    )
    .unwrap();
    return __proconio_res;
}

/*#[memoise(n <= 100)]
fn fib(n: i64) -> i64 {
    if n == 0 || n == 1 {
        return n;
    }
    fib(n - 1) + fib(n - 2)
}*/
thread_local ! (static FIB : std :: cell :: RefCell < Vec < Option < i64 > > > = std :: cell :: RefCell :: new (vec ! [None ; 101usize]));
fn fib_reset() {
    FIB.with(|cache| {
        let mut r = cache.borrow_mut();
        for r in r.iter_mut() {
            *r = None
        }
    });
}
fn fib(n: i64) -> i64 {
    if let Some(ret) = FIB.with(|cache| {
        let mut bm = cache.borrow_mut();
        bm[(n) as usize].clone()
    }) {
        return ret;
    }
    let ret: i64 = (|| {
        if n == 0 || n == 1 {
            return n;
        }
        fib(n - 1) + fib(n - 2)
    })();
    FIB.with(|cache| {
        let mut bm = cache.borrow_mut();
        bm[(n) as usize] = Some(ret.clone());
    });
    ret
}

// The following code was expanded by `cargo-equip`.

#[allow(clippy::deprecated_cfg_attr)]#[cfg_attr(rustfmt,rustfmt::skip)]#[allow(unused)]pub mod memoise{}
#[allow(clippy::deprecated_cfg_attr)]#[cfg_attr(rustfmt,rustfmt::skip)]#[allow(unused)]pub mod proconio_derive{}
```

</div>
</details>