Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
17b2c37
feat(tags): add implements field and bump schema to v2
TwoSquirrels Jul 7, 2026
e26caf0
feat(identifiers): capture implementation target type names
TwoSquirrels Jul 8, 2026
a10bf8a
feat(bundle): keep implementation files of needed types
TwoSquirrels Jul 8, 2026
176823e
refactor: remove context-dependent comments and update stale docs
TwoSquirrels Jul 8, 2026
e284b8e
test: cover operator implementation files surviving tree-shaking
TwoSquirrels Jul 8, 2026
0268ef4
docs(spec): describe implementation-file retention and tags.json v2
TwoSquirrels Jul 8, 2026
0dbab70
fix(library): let update recover from older tags.json schemas
TwoSquirrels Jul 8, 2026
ac124b4
fix(library): let add-std recover from mismatched tags.json schemas
TwoSquirrels Jul 8, 2026
09fc322
refactor(tags): make schema mismatch a typed error
TwoSquirrels Jul 9, 2026
ae857ef
feat(library): auto-migrate outdated tags.json before bundling
TwoSquirrels Jul 9, 2026
a314710
feat(library): tolerate outdated schema in list and show
TwoSquirrels Jul 9, 2026
eedc0c5
docs(spec): describe automatic tags.json schema migration
TwoSquirrels Jul 9, 2026
a733430
refactor(library): clarify migration paths and cover error propagation
TwoSquirrels Jul 9, 2026
ebf6ba0
refactor(library): promote MigrationSource to Registration
TwoSquirrels Jul 9, 2026
e476c6f
refactor(inventory): return a FileTags record instead of a 3-tuple
TwoSquirrels Jul 9, 2026
db279e1
docs(architecture): record implements retention and schema auto-migra…
TwoSquirrels Jul 9, 2026
bc9d4c9
style: drop redundant test comments
TwoSquirrels Jul 9, 2026
95a451b
docs(spec): use a fictional library for the tags.json example
TwoSquirrels Jul 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,15 @@ CLI ツール (約 2000 行) では、抽象を足すほど薄いディレクト
C++ の厳密な依存解析は難しいので、識別子名の照合で近似する。余分に拾う分には安全 (取りこぼしだけが依存漏れ = コンパイルエラーになる) なので、メンバ名やマクロ名まで拾っても気にしない。

例外は namespace 名だけ。`atcoder` のような名前はほぼ全ファイルに現れ、定義として登録すると逆引きで大半のヘッダーが依存扱いになって tree-shaking が効かなくなる。過剰検出が裏目に出る唯一のケースなので、namespace 名は集めない (中のメンバは個別に拾う)。詳細は identifiers.rs の `NAME_NODES` を参照。

## 実装ファイルは「実装先の型名」で引く

宣言と実装が別ファイルに分かれたライブラリでは、識別子名の照合だけでは依存を辿れない依存が生じる。典型は演算子オーバーロードで、利用側は `f * g` と書くだけで `operator*=` というトークンは現れず、実装ファイル (`operator*=` の本体だけを置いたファイル) が逆引きに引っかからない。過剰検出側に倒す方針でも、これは「取りこぼし」なので放置できない。

素朴な対策 (必要ファイルを include するファイルを全部残す / 識別子検出を残存コード全体へ広げる) はいずれも巻き込みが連鎖して tree-shaking をほぼ無効化する (Nyaan's Library で実測)。決め手は、クラス外実装が `void FormalPowerSeries<mint>::set_fft()` のように**実装先の型名を必ず自分で書いている**という観察。そこで登録時にこの型名 (クラス外修飾定義の修飾側・明示的特殊化の主テンプレート名) を `implements` として別に記録し、バンドル時に「必要ファイルが定義する型名を実装先に持つファイルも必要」と引く。識別子の逆引きと同型の引き方をもう 1 種足し、増えなくなるまで反復する。これは「余分に残すのは安全」の一貫であり、実装先を静的に特定できない依存 (自由関数の演算子だけのファイル等) は依然取りこぼすが、その場合も提出時のコンパイル/リンクエラーとして顕在化する。

## tags.json のスキーマ変化は黙って吸収する

tags.json はライブラリ実体から決定的に再生成できるキャッシュであり、可搬性を持たない (`path` は絶対パス)。だから schema_version がバイナリの現行と食い違うのは「ユーザーの落ち度」ではなく「risundle の都合」であり、ユーザー操作を要求すべきでない。バンドル時に食い違う登録をライブラリ実体から黙って作り直す (`std` の初回自動登録と同じ発想)。

この非対称が肝: **ユーザーの内容が変わった (ハッシュ不一致) は確認させ、risundle の形式が変わった (スキーマ不一致) は黙って直す**。前者は意味的な事象なので `update` か `--no-check` の判断をユーザーに委ね、後者はキャッシュの世代交代なので自動で吸収する。`update`・`add-std` は登録を作り直すコマンドなので、スキーマ非依存の中核 (`Registration` = `path` + 任意の `compilers`) だけを検証せず読んで回復する。`list`・`show` は読み取りに徹し、再生成せず読める情報だけ出す。
30 changes: 22 additions & 8 deletions docs/spec.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ README より踏み込んだ、各コマンドの挙動・エラー条件・出
- `add <id> <path>` — `<path>` をインクルードパスとして登録する。
- `<id>` が `std` の場合はエラー (標準ライブラリは `add-std` を使う)。
- 既に同じ `<id>` が登録済みの場合はエラー。
- 登録時に、各ファイルの定義識別子一覧と、`<path>` 以下の内容から計算した集約ハッシュを記録する。
- 登録時に、各ファイルの定義識別子一覧・実装先の型名一覧と、`<path>` 以下の内容から計算した集約ハッシュを記録する。実装先の型名とは、クラス外の修飾付き定義 (`X<...>::method`) の修飾側や明示的特殊化 (`template <> struct T<...>`) の主テンプレート名で、そのファイルが「どの型の実装か」を表す
- `add-std [<compiler>]` — 標準ライブラリ (`std`) を登録する。
- `<compiler>` (省略時 `g++`) のシステム include 探索パスを自動検出する。
- 繰り返し呼ぶと、その都度コンパイラを認識集合に加える (加算式)。集合全コンパイラの探索パスを 1 つに統合するため、複数コンパイラを使い分けられる。
Expand All @@ -28,7 +28,7 @@ README より踏み込んだ、各コマンドの挙動・エラー条件・出
- `list` — 登録済みライブラリの ID とインクルードパスを一覧する。
- `show [-v | --verbose] <id>` — ライブラリの詳細を表示する。未登録の場合はエラー。
- 既定では ID・インクルードパス・種別・定義識別子を持つファイル数を表示する。
- `-v` 指定時は、集約ハッシュと各ファイルの定義識別子一覧も表示する
- `-v` 指定時は、集約ハッシュ、各ファイルの定義識別子一覧、実装先の型名一覧も表示する

## バンドル

Expand All @@ -54,6 +54,16 @@ README より踏み込んだ、各コマンドの挙動・エラー条件・出

tree-shaking がうまくいかないときの一時的なフォールバック用途を想定しており、`.risundlerc.toml` からは設定できない (設定ファイルで常用できるようにすると、CLI から tree-shaking を有効に戻すオプションが対で必要になり、仕様が複雑化するため)。

### 実装ファイルの維持

宣言と実装が別ファイルに分かれたライブラリでは、演算子オーバーロードのように識別子として検出できない依存が生じる (利用側は `f * g` と書くだけで、`operator*=` というトークンは現れない)。このため識別子の逆引きに加えて、次の規則を適用する。

> 必要と判定されたファイルが定義する型名を「実装先の型名」として持つファイルも、必要とみなす。

新たに必要になったファイルがあれば `-M` の必要集合を取り直し、増えなくなるまで繰り返す (対象は出力に現れたファイルに限られ単調増加のため、必ず停止する)。実装先の照合対象はあくまで出力に現れたファイルであり、include されていない実装ファイルを補うことはしない。

実装先を静的に特定できない修飾 (decltype・依存名など) は記録されず、この規則の対象にならない。名前の付いた定義もクラス外実装も持たないファイル (自由関数の演算子だけを定義するファイル等) も対象外で、tree-shaking で消え得る。

### 出力形式

- 先頭行に `// Bundled with risundle v<version>` のクレジットを付ける。
Expand All @@ -72,12 +82,15 @@ tree-shaking がうまくいかないときの一時的なフォールバック

```json
{
"schema_version": 1,
"path": "/usr/local/include",
"schema_version": 2,
"path": "/home/user/cp-library",
"hash": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"files": {
"atcoder/modint.hpp": ["modint", "modint_base", "modint_common"],
"atcoder/segtree.hpp": ["segtree"]
"matrix/matrix.hpp": ["Matrix", "identity"],
"matrix/matrix-mul.hpp": ["pow"]
},
"implements": {
"matrix/matrix-mul.hpp": ["Matrix"]
}
}
```
Expand All @@ -86,14 +99,15 @@ tree-shaking がうまくいかないときの一時的なフォールバック

```json
{
"schema_version": 1,
"schema_version": 2,
"path": "/usr/include/c++/12",
"compilers": ["/usr/bin/x86_64-linux-gnu-g++-14"]
}
```

- `schema_version`: スキーマの互換性チェック用の整数。未知の値の場合は再生成を促すエラーを出す
- `schema_version`: スキーマの互換性チェック用の整数。現行と異なる値の登録は、バンドル時にライブラリ実体から自動で再生成する (キャッシュの形式差は risundle 側の都合であり、ユーザー操作を要求しない)。`update`・`add-std` も同じく登録パス (`std` ならコンパイラ集合) だけを読んで作り直すため、スキーマが合わなくても回復できる。`list`・`show` は読み取りに徹し、再生成せず読める情報だけを出す (`show` は詳細の代わりに再生成の案内を出す)
- `path`: インクルードパス (絶対パス)。`std` では検出したシステム include パスのうち代表 (最初のコンパイラの C++ 標準ライブラリ dir) を表示用に記録する。
- `compilers`: `std` のみ。std を認識させたコンパイラの集合 (実体の絶対パスへ正規化済み)。
- `hash`: `path` 以下の全ファイルの相対パスと内容から計算した集約ハッシュ。`std` では持たない。
- `files`: ライブラリルートからの相対パスをキーとし、そのファイルが定義する識別子名の配列を値とする。`std` では持たない。`std` 以外は識別子が無くても空オブジェクト `{}` を持つため、構造上 `std` と区別できる。
- `implements`: ライブラリルートからの相対パスをキーとし、そのファイルの実装先の型名の配列を値とする。実装先を持たないファイルはキー自体を持たない。`std` では持たない。読み込み時にキーが無い場合は空として扱う。
30 changes: 22 additions & 8 deletions docs/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ This document goes deeper than the README, covering each command's behavior, err
- `add <id> <path>` — Register `<path>` as an include path.
- Errors if `<id>` is `std` (use `add-std` for the standard library).
- Errors if the same `<id>` is already registered.
- On registration, records the list of defined identifiers for each file and an aggregate hash computed from the contents under `<path>`.
- On registration, records the list of defined identifiers for each file, the list of implementation target type names, and an aggregate hash computed from the contents under `<path>`. An implementation target is the qualifier of an out-of-class qualified definition (`X<...>::method`) or the primary template name of an explicit specialization (`template <> struct T<...>`), expressing "which type this file implements".
- `add-std [<compiler>]` — Register the standard library (`std`).
- Auto-detects the system include search paths of `<compiler>` (default `g++`).
- Calling it repeatedly adds that compiler to the recognized set each time (additive). It merges the search paths of all compilers in the set into one, so you can switch between multiple compilers.
Expand All @@ -28,7 +28,7 @@ This document goes deeper than the README, covering each command's behavior, err
- `list` — List the IDs and include paths of registered libraries.
- `show [-v | --verbose] <id>` — Show details of a library. Errors if not registered.
- By default, shows the ID, include path, kind, and the number of files that have defined identifiers.
- With `-v`, also shows the aggregate hash and the list of defined identifiers per file.
- With `-v`, also shows the aggregate hash, the list of defined identifiers per file, and the implementation target type names.

## Bundling

Expand All @@ -54,6 +54,16 @@ With `--no-tree-shaking`, identifier detection, dependency-header reverse lookup

This option is intended as a temporary fallback for when tree-shaking goes wrong, and it cannot be set from `.risundlerc.toml` (allowing it in the configuration file would require a paired CLI option to turn tree-shaking back on, complicating the specification).

### Keeping implementation files

In a library that splits declarations and implementations across files, some dependencies cannot be detected as identifiers — operator overloads are the typical case (the user writes `f * g`; the token `operator*=` never appears). In addition to the identifier reverse lookup, the following rule therefore applies.

> A file whose implementation target names include a type defined by an already-needed file is also considered needed.

Whenever new files become needed, the `-M` required set is recomputed, repeating until nothing is added (the candidates are limited to files present in the output and grow monotonically, so this always terminates). The matching only considers files present in the output; implementation files that are not included are never pulled in.

Qualifiers whose target cannot be determined statically (decltype, dependent names, etc.) are not recorded and are outside this rule. Files with neither a named definition nor an out-of-class implementation (e.g. a file defining only free-function operators) are also outside it and may still be dropped by tree-shaking.

### Output format

- The first line carries the credit `// Bundled with risundle v<version>`.
Expand All @@ -72,12 +82,15 @@ When `<id>` is not `std`:

```json
{
"schema_version": 1,
"path": "/usr/local/include",
"schema_version": 2,
"path": "/home/user/cp-library",
"hash": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"files": {
"atcoder/modint.hpp": ["modint", "modint_base", "modint_common"],
"atcoder/segtree.hpp": ["segtree"]
"matrix/matrix.hpp": ["Matrix", "identity"],
"matrix/matrix-mul.hpp": ["pow"]
},
"implements": {
"matrix/matrix-mul.hpp": ["Matrix"]
}
}
```
Expand All @@ -86,14 +99,15 @@ When `<id>` is `std`:

```json
{
"schema_version": 1,
"schema_version": 2,
"path": "/usr/include/c++/12",
"compilers": ["/usr/bin/x86_64-linux-gnu-g++-14"]
}
```

- `schema_version`: An integer for schema compatibility checks. On an unknown value, it raises an error prompting regeneration.
- `schema_version`: An integer for schema compatibility checks. Registrations whose value differs from the current one are automatically regenerated from the library sources at bundle time (a cache format difference is risundle's own concern and requires no user action). `update` and `add-std` likewise read only the registered path (or the compiler set for `std`) and rebuild, so they recover even from a mismatched schema. `list` and `show` stay read-only and never regenerate, printing what they can read (`show` prints regeneration guidance in place of the details).
- `path`: The include path (absolute). For `std`, it records a representative one among the detected system include paths (the C++ standard library dir of the first compiler) for display.
- `compilers`: `std` only. The set of compilers that `std` was registered with (normalized to the absolute paths of the actual binaries).
- `hash`: An aggregate hash computed from the relative paths and contents of all files under `path`. Not present for `std`.
- `files`: Keys are paths relative to the library root, and values are the arrays of identifier names that the file defines. Not present for `std`. Non-`std` libraries always carry an empty object `{}` even when there are no identifiers, so they are structurally distinguishable from `std`.
- `implements`: Keys are paths relative to the library root, and values are the arrays of implementation target type names of the file. Files without implementation targets have no key. Not present for `std`. Treated as empty when the key is missing on load.
Loading
Loading