RFC-029: モジュール意味論システム
概要
モジュールシステムをコンパイルパイプラインに接続し、マルチファイルコンパイルを実現する。
核心定義:モジュール = 1つの .yx ファイルのすべてのトップレベルバインディング。モジュールの型 = それらのバインディングの型(推論)。 use = record 分解。pub も private も export も、可視性メカニズムも一切ない。
核心原則:
- 型検査器は事前構築された ModuleRegistry のみを照会し、ディスクには触れない
- パッケージ内のファイルは単一のコンパイル単位(AST 連結)に統合され、パッケージ内の循環参照は自然に許可される
- Registry はオンデマンドで読み込まれる:エントリから
use沿いに到達可能なモジュールだけを装填する
含まないもの:キャッシュ、ファイル監視、ホットリロード、増分再コンパイル、パッケージ間循環依存処理。
動機
現在の問題
- コンパイラは単一ファイルのみ対応:
Pipeline::run(name, source)は文字列を受け取るだけで、ファイル間依存を処理できない useは std モジュールのみ解決可能:ローカルファイル間のuseはすべて "Unknown variable" エラーになる- モジュールリゾルバの位置が誤っている:唯一のパス解決ロジックは
package/source/module_resolver.rsにあり、frontend/module/resolver.rsは実際にはコンパイル時述語正格化(RFC-027)である
設計目標
- 1つのプロジェクトで複数の
.yxファイルをコンパイルできること use文の意味が明確であること:record 分解であり、特別なメカニズムではない- 単一ファイルは引き続き動作し、
yaoxiang.tomlを要求しないこと - パイプライン(
Pipeline)はゼロ変更で、マルチファイル対応はオーケストレーション層の責務であること - 新キーワード・新 AST ノード・新概念を導入しないこと
提案
1. モジュール = バインディングの record
モジュールとは、1つの .yx ファイルのすべてのトップレベルバインディングのことである。
// math/geometry.yx
Point: Type = { x: Float, y: Float }
distance: (a: Point, b: Point) -> Float = { ... }このモジュールの中身は { Point: Type, distance: (Point, Point) -> Float } である。
モジュールは特別な存在ではない。name: type = value モデルの一例にすぎない——ファイル境界上で定義された record なのだ。モジュールの型はバインディングから推論され、明示的な注釈は決して必要ない。
use で導入されるバインディングもまたモジュール内容の一部である:
// math/mod.yx
use geometry.{Point, distance}math モジュールの中身 = { Point: Type, distance: (Point, Point) -> Float }。外側で use math.{Point} すれば取得できる。use math.geometry.{Point} でも取得できる。両パスは同じバインディングを指す。
2. use = record 分解
すべての use 形式は record フィールドアクセス + バインディングである:
use math.geometry.{Point, distance}これは次と等価である:
Point = math.geometry.Point
distance = math.geometry.distance| 構文 | 意味 |
|---|---|
use path.{item} | path record の item フィールドを取得し、現在のスコープにバインド |
use path.{a, b} | 複数フィールドを取得 |
use path | path record 自体を取得し、最後のセグメント名にバインド |
use path as alias | path record 自体を取得し、alias にバインド |
存在しない構文
:ワイルドカードインポート。バインディングを明示列挙するため不要。use path.*:Python 形式。採用しない。from path use item:波括弧内別名。Phase 4 で任意、本線の阻害にはならない。use path.{item as alias}
インポート衝突
同名のバインディングは直接エラー:
名前 `Point` が衝突:
math.geometry.Point
graphics.shapes.Point
異なる名前かモジュール別名を使用してください。3. 可視性:存在しない
本 RFC は可視性メカニズムを一切導入しない。 すべてのトップレベルバインディングは、パスを書き出せるすべてのコードから可視である。
これは意図的な設計判断であり、漏れではない。
設計根拠
| 表現したいこと | 実現方法 | メカニズム |
|---|---|---|
| 「これは API」 | 公開パッケージに置く | 配布 |
| 「これは内部用」 | 公開しないパッケージに置く | 配布 |
| 「これは関数内」 | 関数本体に書く | スコープ |
三層すべて既存メカニズム:パッケージ、ファイル、スコープ。新しいものは不要。
なぜ pub を採用しないか
- 人に使われたくないものはトップレベルに置くべきではない(ローカルスコープへ)
- 複数ファイルで共有する補助関数は、公開しない独立パッケージに置く
- 「防げるか」と「シグナルを持つべきか」は別問題。現段階にはサードパーティエコシステムがなく、シグナルは無意味
- ドアに鍵はかけない。ドアをくぐるのが礼儀、塀を飛び越えるのが自由。言語は礼儀を管理しない
将来
エコシステムが成熟し強制境界が必要になった場合、独立 RFC で導入可能。制限の追加は後方互換的である(デフォルト公開 → 明示的に内部とマーク)。しかし本 RFC はこの方向を予見せず、到来も約束しない。
4. パス解決
モジュールパス → ファイル
use math.geometry.{Point}検索順序:
- Registry 登録済みモジュール:
math.geometryが既に Registry に存在 → 直接使用 - 標準ライブラリ:
stdまたはstd.*→ 組み込みモジュール - インポーターのディレクトリ:
<importer_dir>/math/geometry.yx(ローカルモジュール優先) - プロジェクトルート(直近の
yaoxiang.toml祖先):<project_root>/math/geometry.yx - vendor ディレクトリ:
.yaoxiang/vendor/<pkg>-*/src/(将来)
ファイル位置の試行順序:
base/name.yx
base/name/mod.yx最初に見つかった時点で停止。両者同時に存在 → エラー:
モジュールパスが曖昧:`math.geometry` が同時にマッチ:
src/math/geometry.yx
src/math/geometry/mod.yx
一方を削除してください。同一モジュールキーが二つのルートでそれぞれファイルにヒットし、両方が参照される場合(例:tests/lib.yx と <root>/lib.yx が tests/ エントリとルートエントリからそれぞれ参照される)→ 同様に曖昧エラーを報告し、沈黙的遮蔽は行わない。
2026-08-03 改訂(RFC-036 駆動):発見と解決を実装に従って着地。発見は
useを追跡(本 RFC §5 既定プロトコル)、初版実装のディレクトリ再帰に置き換えた——無関係ファイルのコンパイルエラーが実行を阻むことはなくなり、yaoxiang testのテストファイル隔離が成立する。二重ルートルールにおける「インポーターのディレクトリ優先」は同ディレクトリ内のプロジェクト挙動を不変に保つ;「プロジェクトルート兜底」はサブディレクトリエントリ(例:tests/foo_test.yx)がプロジェクトルートモジュールをインポートできるようにする。src/レイアウト(RFC-014 パッケージ)の着地は vendor 層で一括処理し、ローカル二重ルートに影響しない。
mod.yx = ディレクトリ入口(規約)
mod.yx はディレクトリの入口ファイルである。use math 時に src/math/mod.yx を読み込む。
これは規約であり、強制ではない。ユーザは直接 use math.geometry で子ファイルに透過できる。 mod.yx は「推奨入口」(表札)であり、「唯一の入口」(鍵)ではない。
統一リゾルバ
現在唯一のパス解決ロジックは package/source/module_resolver.rs にある。これを frontend/module/resolver.rs に移す(現在の名実不一致の述語正格化ファイルを置換し、述語正格化は frontend/core/types/eval/ へ移す)。
5. プロジェクトコンパイルフロー
パス A:AST 統合
パッケージ内の全ファイルを単一コンパイル単位に統合する。パイプラインはゼロ変更。
オーケストレータ(Pipeline の上層):
1. エントリファイルを決定
2. エントリファイルの use 文を解析(use 行のみ読み取り、関数本体は解析しない)
3. use パス沿いにファイルを発見し、キューに追加
4. キュー内のファイルについて、それらの use 文を解析
5. キューが空になるまで 3-4 を繰り返す(オンデマンド発見)
6. 発見したファイルを順次完全解析 → 複数の AST
7. 一つの Module に統合(すべてのトップレベル items を連結、Span は元ファイルを保持)
8. Pipeline::run() に投入(パイプラインは複数ファイルがあることを知らない)パッケージ内循環参照:許可
全ファイルが単一 AST に統合されるため、パッケージ内の相互 use は同一ファイル内の相互参照と等価:
// tree.yx
use node.{Node}
Tree: Type = { root: Node }
// node.yx
use tree.{Tree}
Node: Type = { value: Int, parent: Tree }統合後は一つの AST 内で相互参照する二つの型定義となる。コンパイラは元来これをサポートする。
パッケージ間循環:後回し
パッケージは配布単位であり、パッケージ間にはトポロジカル順序が必要。現時点ではサードパーティパッケージエコシステムがないため、処理しない。遭遇したら直接エラーを報告すればよい。
エントリファイル選択
優先順位:
[run].main(yaoxiang.toml)[[bin]]第一項のpathsrc/main.yx(規約デフォルト)
yaoxiang.toml なし:与えられたファイルを直接コンパイル。Registry には std のみ。これは「単一ファイルモード」ではなく、「発見結果が空であることの自然な結果」。
Registry オンデマンド読み込み
Registry の内容 = エントリから use 沿いに到達可能なすべてのモジュール。到達不能なモジュールは解析されず、登録されず、存在しない。 これは最適化ではなく、定義である。
6. std モジュールとユーザモジュールの同質性
typecheck にとって、use std.io.{println} と use math.geometry.{Point} の操作は完全に同一:
- Registry でモジュール record を探す
- フィールドを取得
- 現在のスコープにバインド
出所(Std / User / Vendor)はメタデータであり、解決ロジックに影響しない。native 関数の特殊処理は IR gen / codegen 層に先送り。
コンパイラ変更
| コンポーネント | 変更 |
|---|---|
frontend/module/resolver.rs | 書き直し:現在は述語正格化(RFC-027)で、frontend/core/types/eval/ へ移す。本ファイルは真のモジュールパス解決に変更(package/source/module_resolver.rs から移行) |
frontend/module/mod.rs | 拡張:AST 統合に必要な元ファイル追跡を補足(Span にファイル名付与) |
frontend/module/registry.rs | 拡張:ユーザモジュールの登録に対応(現在は std のみ登録) |
frontend/module/orchestrator.rs | 新設:マルチファイルオーケストレータ(発見 → 解析 → 統合 → Pipeline 呼出) |
frontend/pipeline.rs | 変更なし |
frontend/core/parser/statements/imports.rs | 変更なし(use 解析は既に実装済み) |
package/source/module_resolver.rs | 削除、ロジックを frontend/module/resolver.rs に移行 |
frontend/core/typecheck/ | use 処理を Registry 照会に変更(現在は std のみ照会) |
AST is_pub: bool | 触らない。本 RFC は可視性に関与しない |
存在しないファイル(旧 RFC 版で「実装済み」と称されたが実際には存在しない)
— 存在しない、責務はオーケストレータが担うfrontend/module/loader.rs— 存在しない、パッケージ内はトポロジカルソート不要(AST 統合)frontend/module/dep_graph.rs— 存在しない、子 RFC 029a に属するfrontend/module/cache.rs— 存在しない、子 RFC 029b に属するfrontend/module/hot_reload.rs
実装戦略
Phase 1:パス解決の統一
- 述語正格化を
frontend/module/resolver.rsからfrontend/core/types/eval/へ移す package/source/module_resolver.rsのパス解決ロジックをfrontend/module/resolver.rsに移行- モジュールパス曖昧検出(
name.yxとname/mod.yxが同時存在 → エラー)
Phase 2:マルチファイルオーケストレータ
frontend/module/orchestrator.rsを新設- オンデマンド発見を実装(エントリから use 沿いに再帰)
- AST 統合を実装(複数ファイル items の連結、Span に元ファイル保持)
compiler.rsにcompile_project(project_root)呼び出しを新設
Phase 3:use 名前解決
- typecheck の
process_use_stmtを Registry 照会に変更(std のみ照会は廃止) - インポート衝突検出(同名ならエラー)
- E2E テスト:マルチファイルプロジェクトのローカルモジュール
use
Phase 4(任意、本線を阻害しない)
use path.{item as alias}波括弧内別名- vendor ディレクトリ解決(RFC-014 と連動)
依存関係
- RFC-014(パッケージマネージャ)—
yaoxiang.tomlフィールド、vendor ディレクトリ構造(Phase 4 で必要) - 他の前置依存なし
子 RFC 計画
| 子 RFC | 能力 | 前提 |
|---|---|---|
| 029a | モジュールキャッシュと増分再コンパイル | オーケストレータ安定 |
| 029b | ファイル監視とホットリロード | 029a |
| 029d | CLI --entry によるエントリ上書き | オーケストレータ利用可能 |
| 029e | マルチファイル診断 --json 出力 | 診断集約 |
| 029f | コンパイル対象ロールとインポート面意味論(2026-09-13 承認済み、#334) | オーケストレータ安定 |
削除済み:029c(再エクスポート) — 不要。use がそのまま再エクスポートであり、"pub use" 概念はない。
設計判断記録
| 判断 | 結論 | 日付 | 根拠 |
|---|---|---|---|
| モジュールとは | ファイルトップレベルバインディングの record | 2026-07-30 | RFC-010 name: type = value 統一モデル |
use の意味 | record 分解 | 2026-07-30 | 新メカニズム導入せず、既存 record 意味論を再利用 |
| 可視性 | 存在しない | 2026-07-30 | スコープ + 配布境界が全シナリオをカバー、新キーワード不要 |
pub キーワード | 不要 | 2026-07-30 | 「使われたくなければトップレベルに置くな/そのパッケージを公開するな」 |
| mod.yx の意味 | ディレクトリ入口(規約、非強制) | 2026-07-30 | Python __init__.py モデル:ドアに鍵はかけない |
| モジュール型注釈 | 不要 | 2026-07-30 | 内部バインディングは既に型を持ち、注釈は冗長 |
| パッケージ内循環 | 許可(AST 統合) | 2026-07-30 | パス A:パイプラインゼロ変更、Rust crate 内モデル |
| パッケージ間循環 | 暫定未処理 | 2026-07-30 | サードパーティエコシステムなし、遭遇時エラーで十分 |
| Registry 読み込み | オンデマンド(到達可能モジュールのみ装填) | 2026-07-30 | 最適化ではなく定義 |
| 単一ファイル vs プロジェクト | 同一メカニズム | 2026-07-30 | Registry 内容異なり、探索ロジック同一 |
参考文献
- RFC-010: 統一型構文 —
name: type = valueモデル - RFC-009: 所有権モデル — インポートはコンパイル時名前解決
- RFC-011: ジェネリクス型システム — 構造的型
- RFC-014: パッケージ管理システム設計 — パッケージ名、vendor ディレクトリ
- RFC-026: FFI コアメカニズム — StdModule 登録
- RFC-030: assert 断言メカニズム — StdModule 統一登録の先例
