Skip to content

RFC-029: Семантическая система модулей ​

Резюме ​

Интеграция системы модулей в конвейер компиляции, реализация многофайловой компиляции.

Базовое определение: Модуль = все верхнеуровневые связывания одного файла .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)

Цели проектирования ​

  • Проект может компилировать несколько файлов .yx
  • Чёткая семантика оператора use: деструктуризация record, а не специальный механизм
  • Один файл продолжает работать, не требуя yaoxiang.toml
  • Конвейер (Pipeline) без изменений, поддержка нескольких файлов — задача уровня оркестрации
  • Никаких новых ключевых слов, новых узлов AST, новых концепций

Предложение ​

1. Модуль = record of bindings ​

Модуль — это все верхнеуровневые связывания одного файла .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}Берёт поле item record'а path, связывает с текущей областью видимости
use path.{a, b}Берёт несколько полей
use pathБерёт сам record path, связывает с именем последнего сегмента
use path as aliasБерёт сам record path, связывает с 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 (установленный в §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 файлов внутри пакета эквивалентен взаимным ссылкам внутри одного файла:

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. path первой записи [[bin]]
  3. src/main.yx (соглашение по умолчанию)

Без yaoxiang.toml: компилировать данный файл напрямую. В Registry только std. Это не «однофайловый режим» — это «естественный результат пустого обнаружения».

Registry загружается по требованию ​

Содержимое Registry = все модули, достижимые из точки входа по use. Недостижимые модули не разбираются, не регистрируются, не существуют. Это не оптимизация, это определение.

6. std и пользовательские модули однородны ​

Для проверки типов use std.io.{println} и use math.geometry.{Point} — совершенно одинаковые операции:

  1. Найти record модуля в Registry
  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. 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» нет.

Запись проектных решений ​

РешениеВыводДатаОбоснование
Что такое модульRecord верхнеуровневых связываний файла2026-07-30Единая модель name: type = value из RFC-010
Семантика useДеструктуризация record2026-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 разное, логика поиска та же

Ссылки ​