Skip to content

RFC-029: モジュール意味論システム ​

概要 ​

モジュールシステムをコンパイルパイプラインに接続し、マルチファイルコンパイルを実現する。

核心定義:モジュール = 1つの .yx ファイルのすべてのトップレベルバインディング。モジュールの型 = それらのバインディングの型(推論)。 use = record 分解。pub も private も export も、可視性メカニズムも一切ない。

核心原則:

  • 型検査器は事前構築された ModuleRegistry のみを照会し、ディスクには触れない
  • パッケージ内のファイルは単一のコンパイル単位(AST 連結)に統合され、パッケージ内の循環参照は自然に許可される
  • Registry はオンデマンドで読み込まれる:エントリから use 沿いに到達可能なモジュールだけを装填する

含まないもの:キャッシュ、ファイル監視、ホットリロード、増分再コンパイル、パッケージ間循環依存処理。

動機 ​

現在の問題 ​

  1. コンパイラは単一ファイルのみ対応:Pipeline::run(name, source) は文字列を受け取るだけで、ファイル間依存を処理できない
  2. use は std モジュールのみ解決可能:ローカルファイル間の use はすべて "Unknown variable" エラーになる
  3. モジュールリゾルバの位置が誤っている:唯一のパス解決ロジックは package/source/module_resolver.rs にあり、frontend/module/resolver.rs は実際にはコンパイル時述語正格化(RFC-027)である

設計目標 ​

  • 1つのプロジェクトで複数の .yx ファイルをコンパイルできること
  • use 文の意味が明確であること:record 分解であり、特別なメカニズムではない
  • 単一ファイルは引き続き動作し、yaoxiang.toml を要求しないこと
  • パイプライン(Pipeline)はゼロ変更で、マルチファイル対応はオーケストレーション層の責務であること
  • 新キーワード・新 AST ノード・新概念を導入しないこと

提案 ​

1. モジュール = バインディングの record ​

モジュールとは、1つの .yx ファイルのすべてのトップレベルバインディングのことである。

yaoxiang
// 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 で導入されるバインディングもまたモジュール内容の一部である:

yaoxiang
// 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 フィールドアクセス + バインディングである:

yaoxiang
use math.geometry.{Point, distance}

これは次と等価である:

yaoxiang
Point = math.geometry.Point
distance = math.geometry.distance
構文意味
use path.{item}path record の item フィールドを取得し、現在のスコープにバインド
use path.{a, b}複数フィールドを取得
use pathpath record 自体を取得し、最後のセグメント名にバインド
use path as aliaspath record 自体を取得し、alias にバインド

存在しない構文 ​

  • use path.*:ワイルドカードインポート。バインディングを明示列挙するため不要。
  • from path use item:Python 形式。採用しない。
  • use path.{item as alias}:波括弧内別名。Phase 4 で任意、本線の阻害にはならない。

インポート衝突 ​

同名のバインディングは直接エラー:

名前 `Point` が衝突:
  math.geometry.Point
  graphics.shapes.Point
異なる名前かモジュール別名を使用してください。

3. 可視性:存在しない ​

本 RFC は可視性メカニズムを一切導入しない。 すべてのトップレベルバインディングは、パスを書き出せるすべてのコードから可視である。

これは意図的な設計判断であり、漏れではない。

設計根拠 ​

表現したいこと実現方法メカニズム
「これは API」公開パッケージに置く配布
「これは内部用」公開しないパッケージに置く配布
「これは関数内」関数本体に書くスコープ

三層すべて既存メカニズム:パッケージ、ファイル、スコープ。新しいものは不要。

なぜ pub を採用しないか ​

  • 人に使われたくないものはトップレベルに置くべきではない(ローカルスコープへ)
  • 複数ファイルで共有する補助関数は、公開しない独立パッケージに置く
  • 「防げるか」と「シグナルを持つべきか」は別問題。現段階にはサードパーティエコシステムがなく、シグナルは無意味
  • ドアに鍵はかけない。ドアをくぐるのが礼儀、塀を飛び越えるのが自由。言語は礼儀を管理しない

将来 ​

エコシステムが成熟し強制境界が必要になった場合、独立 RFC で導入可能。制限の追加は後方互換的である(デフォルト公開 → 明示的に内部とマーク)。しかし本 RFC はこの方向を予見せず、到来も約束しない。

4. パス解決 ​

モジュールパス → ファイル ​

use math.geometry.{Point}

検索順序:

  1. Registry 登録済みモジュール:math.geometry が既に Registry に存在 → 直接使用
  2. 標準ライブラリ:std または std.* → 組み込みモジュール
  3. インポーターのディレクトリ:<importer_dir>/math/geometry.yx(ローカルモジュール優先)
  4. プロジェクトルート(直近の yaoxiang.toml 祖先):<project_root>/math/geometry.yx
  5. 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 は同一ファイル内の相互参照と等価:

yaoxiang
// tree.yx
use node.{Node}
Tree: Type = { root: Node }

// node.yx
use tree.{Tree}
Node: Type = { value: Int, parent: Tree }

統合後は一つの AST 内で相互参照する二つの型定義となる。コンパイラは元来これをサポートする。

パッケージ間循環:後回し ​

パッケージは配布単位であり、パッケージ間にはトポロジカル順序が必要。現時点ではサードパーティパッケージエコシステムがないため、処理しない。遭遇したら直接エラーを報告すればよい。

エントリファイル選択 ​

優先順位:

  1. [run].main(yaoxiang.toml)
  2. [[bin]] 第一項の path
  3. src/main.yx(規約デフォルト)

yaoxiang.toml なし:与えられたファイルを直接コンパイル。Registry には std のみ。これは「単一ファイルモード」ではなく、「発見結果が空であることの自然な結果」。

Registry オンデマンド読み込み ​

Registry の内容 = エントリから use 沿いに到達可能なすべてのモジュール。到達不能なモジュールは解析されず、登録されず、存在しない。 これは最適化ではなく、定義である。

6. std モジュールとユーザモジュールの同質性 ​

typecheck にとって、use std.io.{println} と use math.geometry.{Point} の操作は完全に同一:

  1. Registry でモジュール record を探す
  2. フィールドを取得
  3. 現在のスコープにバインド

出所(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 — 存在しない、責務はオーケストレータが担う
  • frontend/module/dep_graph.rs — 存在しない、パッケージ内はトポロジカルソート不要(AST 統合)
  • frontend/module/cache.rs — 存在しない、子 RFC 029a に属する
  • frontend/module/hot_reload.rs — 存在しない、子 RFC 029b に属する

実装戦略 ​

Phase 1:パス解決の統一 ​

  1. 述語正格化を frontend/module/resolver.rs から frontend/core/types/eval/ へ移す
  2. package/source/module_resolver.rs のパス解決ロジックを frontend/module/resolver.rs に移行
  3. モジュールパス曖昧検出(name.yx と name/mod.yx が同時存在 → エラー)

Phase 2:マルチファイルオーケストレータ ​

  1. frontend/module/orchestrator.rs を新設
  2. オンデマンド発見を実装(エントリから use 沿いに再帰)
  3. AST 統合を実装(複数ファイル items の連結、Span に元ファイル保持)
  4. compiler.rs に compile_project(project_root) 呼び出しを新設

Phase 3:use 名前解決 ​

  1. typecheck の process_use_stmt を Registry 照会に変更(std のみ照会は廃止)
  2. インポート衝突検出(同名ならエラー)
  3. E2E テスト:マルチファイルプロジェクトのローカルモジュール use

Phase 4(任意、本線を阻害しない) ​

  1. use path.{item as alias} 波括弧内別名
  2. vendor ディレクトリ解決(RFC-014 と連動)

依存関係 ​

  • RFC-014(パッケージマネージャ)— yaoxiang.toml フィールド、vendor ディレクトリ構造(Phase 4 で必要)
  • 他の前置依存なし

子 RFC 計画 ​

子 RFC能力前提
029aモジュールキャッシュと増分再コンパイルオーケストレータ安定
029bファイル監視とホットリロード029a
029dCLI --entry によるエントリ上書きオーケストレータ利用可能
029eマルチファイル診断 --json 出力診断集約
029fコンパイル対象ロールとインポート面意味論(2026-09-13 承認済み、#334)オーケストレータ安定

削除済み:029c(再エクスポート) — 不要。use がそのまま再エクスポートであり、"pub use" 概念はない。

設計判断記録 ​

判断結論日付根拠
モジュールとはファイルトップレベルバインディングの record2026-07-30RFC-010 name: type = value 統一モデル
use の意味record 分解2026-07-30新メカニズム導入せず、既存 record 意味論を再利用
可視性存在しない2026-07-30スコープ + 配布境界が全シナリオをカバー、新キーワード不要
pub キーワード不要2026-07-30「使われたくなければトップレベルに置くな/そのパッケージを公開するな」
mod.yx の意味ディレクトリ入口(規約、非強制)2026-07-30Python __init__.py モデル:ドアに鍵はかけない
モジュール型注釈不要2026-07-30内部バインディングは既に型を持ち、注釈は冗長
パッケージ内循環許可(AST 統合)2026-07-30パス A:パイプラインゼロ変更、Rust crate 内モデル
パッケージ間循環暫定未処理2026-07-30サードパーティエコシステムなし、遭遇時エラーで十分
Registry 読み込みオンデマンド(到達可能モジュールのみ装填)2026-07-30最適化ではなく定義
単一ファイル vs プロジェクト同一メカニズム2026-07-30Registry 内容異なり、探索ロジック同一

参考文献 ​