RFC-029: Семантическая система модулей
Резюме
Интеграция системы модулей в конвейер компиляции, реализация многофайловой компиляции.
Базовое определение: Модуль = все верхнеуровневые связывания одного файла .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)
Цели проектирования
- Проект может компилировать несколько файлов
.yx - Чёткая семантика оператора
use: деструктуризация record, а не специальный механизм - Один файл продолжает работать, не требуя
yaoxiang.toml - Конвейер (
Pipeline) без изменений, поддержка нескольких файлов — задача уровня оркестрации - Никаких новых ключевых слов, новых узлов AST, новых концепций
Предложение
1. Модуль = record of bindings
Модуль — это все верхнеуровневые связывания одного файла .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} | Берёт поле item record'а path, связывает с текущей областью видимости |
use path.{a, b} | Берёт несколько полей |
use path | Берёт сам record path, связывает с именем последнего сегмента |
use path as alias | Берёт сам record path, связывает с 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(установленный в §5 данного RFC протокол), заменяя рекурсию по каталогам из первоначальной реализации — ошибки компиляции в несвязанных файлах больше не блокируют запуск, изоляция тестовых файлов в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)pathпервой записи[[bin]]src/main.yx(соглашение по умолчанию)
Без yaoxiang.toml: компилировать данный файл напрямую. В Registry только std. Это не «однофайловый режим» — это «естественный результат пустого обнаружения».
Registry загружается по требованию
Содержимое Registry = все модули, достижимые из точки входа по use. Недостижимые модули не разбираются, не регистрируются, не существуют. Это не оптимизация, это определение.
6. std и пользовательские модули однородны
Для проверки типов use std.io.{println} и use math.geometry.{Point} — совершенно одинаковые операции:
- Найти record модуля в Registry
- Взять поле
- Связать с текущей областью видимости
Источник (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 029afrontend/module/cache.rs— не существует, относится к дочернему RFC 029bfrontend/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
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 | Единая модель name: type = value из RFC-010 |
Семантика 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
