Skip to content

RFC 013: Спецификация кодов ошибок ​

Резюме ​

Настоящий RFC предлагает спецификацию классификации кодов ошибок компилятора YaoXiang, использующую одноуровневую систему нумерации по аналогии с Rust, в сочетании с файлами ресурсов JSON для поддержки мультиязычности, с предоставлением объяснений ошибок через команду yaoxiang explain.

Мотивация ​

Зачем нужна стандартизированная система кодов ошибок? ​

  1. Пользовательский опыт: пользователь, видя код ошибки, может быстро определить её тип и серьёзность
  2. Организация документации: группировка по категориям упрощает написание и поддержку справочной документации по ошибкам
  3. Интеграция с инструментами: IDE/LSP могут предлагать быстрые исправления и ссылки на документацию на основе кода ошибки
  4. Поддержка интернационализации: разделение сообщений об ошибках и кодов упрощает мультиязычный перевод

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

  • Простота: одноуровневая нумерация, пользователю не нужно запоминать сложные правила классификации
  • Дружелюбность: формат сообщений об ошибках, аналогичный Rust, с подсказками и примерами
  • Расширяемость: управление через файлы ресурсов, лёгкость добавления новых ошибок и языков
  • Удобство для инструментов: команда explain + вывод в формате JSON, поддержка интеграции с IDE/LSP

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

Основной дизайн: одноуровневая система нумерации ​

Используется четырёхзначная нумерация с группировкой по фазам компиляции:

Exxxx
││││
│││└── Порядковый номер (000-999)
││└─── Фаза компиляции (0-9)
└───── Фиксированный префикс 'E'

Разделение по фазам ​

ФазаДиапазонОписание
0E0xxxЛексический и синтаксический анализ
1E1xxxПроверка типов
2E2xxxСемантический анализ
3E3xxxГенерация кода
4E4xxxДженерики и трейты
5E5xxxМодули и импорт
6E6xxxОшибки времени выполнения
7E7xxxI/O и системные ошибки
8E8xxxВнутренние ошибки компилятора
9E9xxxЗарезервировано/экспериментальное

Перечисление категорий ошибок ​

rust
/// Категория ошибки
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorCategory {
    Lexer,      // E0xxx: Лексический и синтаксический анализ
    Parser,     // E0xxx: Parser errors
    TypeCheck,  // E1xxx: Проверка типов
    Semantic,   // E2xxx: Семантический анализ
    Generic,    // E4xxx: Дженерики и трейты
    Module,     // E5xxx: Модули и импорт
    Runtime,    // E6xxx: Ошибки времени выполнения
    Io,         // E7xxx: I/O и системные ошибки
    Internal,   // E8xxx: Внутренние ошибки компилятора
}

Определение кода ошибки и общий Builder ​

Основной принцип: разделение определения кода ошибки и отображаемого текста

  • ErrorCodeDefinition: метаданные кода ошибки (code, category, template), без отображаемого текста
  • locales/*.json: отображаемый текст на различных языках (title, message, help, коды ошибок в виде вложенных объектов)
  • DiagnosticBuilder: универсальный построитель, заменяющий дизайн с trait-per-error

Определение кода ошибки ​

rust
// diagnostic/codes/mod.rs

use crate::util::span::Span;
use crate::util::diagnostic::{Diagnostic, Severity};

/// Определение кода ошибки (только метаданные, отображаемый текст в i18n-файлах)
#[derive(Debug, Clone, Copy)]
pub struct ErrorCodeDefinition {
    pub code: &'static str,
    pub category: ErrorCategory,
    pub message_template: &'static str,  // Шаблон сообщения, поддерживает плейсхолдеры {param}
}

/// Универсальный построитель диагностик
pub struct DiagnosticBuilder {
    code: &'static str,
    message_template: &'static str,
    params: Vec<(&'static str, String)>,
    span: Option<Span>,
}

impl DiagnosticBuilder {
    pub fn new(code: &'static str, template: &'static str) -> Self {
        Self {
            code,
            message_template: template,
            params: Vec::new(),
            span: None,
        }
    }

    /// Добавить параметр шаблона
    pub fn param(mut self, key: &'static str, value: impl Into<String>) -> Self {
        self.params.push((key, value.into()));
        self
    }

    /// Установить позицию
    pub fn at(mut self, span: Span) -> Self {
        self.span = Some(span);
        self
    }

    /// Собрать Diagnostic (рендеринг шаблона выполняется в compile-time)
    pub fn build(&self, i18n: &I18nRegistry) -> Diagnostic {
        // Проверка, что для всех {key} в шаблоне есть соответствующие параметры
        self.validate_params();

        let message = i18n.render(self.message_template, &self.params);
        let help = self.help(i18n);

        Diagnostic {
            severity: Severity::Error,
            code: self.code.to_string(),
            message,
            help,
            span: self.span,
            related: Vec::new(),
        }
    }
}

Сокращённые методы для каждого кода ошибки ​

rust
// diagnostic/codes/e1xxx.rs

impl ErrorCodeDefinition {
    /// E1001 Неизвестная переменная
    pub fn unknown_variable(name: &str) -> DiagnosticBuilder {
        let def = Self::find("E1001").unwrap();
        DiagnosticBuilder::new(def.code, def.message_template)
            .param("name", name)
    }

    /// E1002 Несоответствие типов
    pub fn type_mismatch(expected: &str, found: &str) -> DiagnosticBuilder {
        let def = Self::find("E1002").unwrap();
        DiagnosticBuilder::new(def.code, def.message_template)
            .param("expected", expected)
            .param("found", found)
    }
}

Пример использования ​

rust
// checking/mod.rs

use crate::util::diagnostic::codes::{ErrorCodeDefinition, E1001};

// Упрощённый способ
return Err(E1001::unknown_variable(&var_name)
    .at(span)
    .build(&i18n_registry));

// Ручной способ
return Err(ErrorCodeDefinition::find("E1001")
    .builder()
    .param("name", var_name)
    .at(span)
    .build(&i18n_registry));

Пример определения кода ошибки ​

rust
// diagnostic/codes/e1xxx.rs

pub static E1XXX: &[ErrorCodeDefinition] = &[
    ErrorCodeDefinition {
        code: "E1001",
        category: ErrorCategory::TypeCheck,
        message_template: "Unknown variable: '{name}'",
    },
    ErrorCodeDefinition {
        code: "E1002",
        category: ErrorCategory::TypeCheck,
        message_template: "Expected type '{expected}', found type '{found}'",
    },
    // ... другие коды ошибок
];

Преимущества дизайна ​

СвойствоОписание
Единый BuilderОдин DiagnosticBuilder универсален для всех кодов ошибок
ТипобезопасностьСокращённые методы гарантируют корректность параметров
СамодокументируемостьE1001::unknown_variable(name) говорит сам за себя
Разделение шаблоновШаблон сообщения отделён от кода, удобно для i18n
Нулевые накладные расходы в runtimeРендеринг в compile-time, AOT-бинарник без таблиц поиска

Упрощение с помощью макросов ошибок ​

Макрос error! (автоматическая инъекция контекста) ​

rust
/// Макрос для автоматического получения span и конфигурации i18n в compile-time
macro_rules! error {
    ($code:ident, $($key:ident = $value:expr),* $(,)?) => {
        $code()
            $(.$key($value))*
            .at(crate::util::span::Span::current())
            .build(crate::util::diagnostic::I18nRegistry::current())
    };
}

/// Использование: нужно передать только параметры, span и i18n инжектируются автоматически
return Err(error!(E1001, name = var_name));
return Err(error!(E1002, expected = "bool", found = cond_ty));

Ручное использование Builder ​

rust
// Когда нужен ручной контроль
E1001::unknown_variable(&var_name)
    .at(my_span)           // Пользовательский span
    .build(&custom_i18n)   // Пользовательский i18n

Детальный дизайн ​

Список кодов ошибок ​

E0xxx: Лексический и синтаксический анализ ​

КодОписание
E0001Недопустимый символ
E0002Недопустимый числовой литерал
E0003Незакрытая строка
E0004Недопустимый символьный литерал
E0010Ожидаемый токен
E0011Неожиданный токен
E0012Недопустимый синтаксис
E0013Несоответствующая скобка
E0014Отсутствует точка с запятой
E0016Ожидаемое выражение
E0018Ключевое слово в качестве имени

E1xxx: Проверка типов ​

КодОписание
E1001Неизвестная переменная
E1002Несоответствие типов
E1003Неизвестный тип
E1010Несоответствие количества аргументов
E1011Несоответствие типов аргументов
E1012Несоответствие возвращаемого типа
E1013Функция не найдена
E1014Неизвестное имя именованного аргумента
E1015Повторное указание аргумента
E1020Невозможно вывести тип
E1021Конфликт вывода типов
E1030Неполный шаблон
E1031Недостижимый шаблон
E1040Операция не поддерживается
E1041Выход за границы индекса
E1042Поле не найдено
E1050Требуется логический операнд
E1051Логическое NOT требует логического операнда
E1052Недопустимое разыменование
E1053Доступ к полю неструктурного типа
E1054Несоответствие условного типа
E1055Ограничение в не-generic контексте
E1060Несоответствие количества типовых параметров
E1061Невозможно инстанцировать дженерик
E1062Ограничение const-дженерика не выполнено
E1064Недопустимый индекс позиции привязки
E1065Вызов не-функционального значения
E1071Определение типа допустимо только на уровне модуля
E1081? допустим только в функциях, возвращающих Result
E1082? может использоваться только для выражений Result
E1083Несоответствие типа ошибки для ?
E1090✨ Несказуемое ✨
E1091Недопустимый мета-тип дженерика
E1092Недопустимая форма аргумента уточнённого типа
E1093Несоответствие количества уточнённых аргументов
E1094Неиспользованный параметр compile-time значения
E1095Неизвестный интерфейс
E1096Несоответствие количества параметров интерфейса
E1097Конфликт имён членов интерфейса
E1098Метод интерфейса не реализован
E1099Несоответствие сигнатуры метода интерфейса
E1100Повторная реализация метода интерфейса
E1101Тип не реализует интерфейс
E1102Управляющий оператор цикла вне цикла
E1103Квадратные скобки недопустимы в позиции типа
E1104Реализация интерфейса вне модуля определения типа
E1105Конструктор варианта не может использоваться как доступ к полю

Связь с RFC-011b (примечание от 2026-09-22): RFC-011b: Перегрузка операторов При реализации будут затронуты три места в данном разделе — ① тексты E1081 / E1082 освободятся от слова "Result" (? будет определяться через интерфейс Try, без привязки к конкретному имени типа), синхронизация по процессу «трёхсторонней согласованности» данного документа (codes/*.rs ↔ locales ↔ таблица кодов) в фазе 2; ② невыполнение предварительного ограничения Equal (линейный токен) будет использовать семейство E1101 (тип не реализует интерфейс) для отказа; ③ после подключения фазы 1 Struct == Struct преобразуется из ошибки runtime E6007 в compile-time проверку, поверхность срабатывания E6007 сократится. Зарегистрированные тексты в таблице остаются в текущем виде до завершения реализации.

E2xxx: Семантический анализ ​

КодОписание
E2001Ошибка области видимости
E2002Повторное определение
E2003Ошибка владения
E2010Присваивание неизменяемому
E2011Использование неинициализированной переменной
E2012Конфликт изменяемости
E2013Затенение переменной
E2014Использование перемещённого значения
E2016Присваивание неизменяемому
E2018Конфликт изменяемого/неизменяемого заимствования
E2019Двойное освобождение
E2020Использование после освобождения
E2027Разыменование unsafe
E2029Циклическая ссылка в spawn
E2030Нарушение ограничения уточнённого типа
E2090Недопустимая сигнатура
E2091Неизвестный тип в сигнатуре
E2092В сигнатуре отсутствует стрелка
E2093Повторяющееся имя параметра
E2094Затенение параметра дженерика
E2095Затенение имени параметром имени дженерика

Пояснение о зарезервированных кодах (инвентаризация от 2026-09-14, релизная позиция #251): коды E2019 (двойное освобождение), E2020 (использование после освобождения), E2027 (разыменование unsafe), E2029 (циклическая ссылка в spawn) зарегистрированы и закреплены модульными тестами, но пока не имеют достижимого поверхностного слоя в исходном коде yx (явный оператор drop, синтаксис разыменования Ptr, пути построения циклов ref в spawn) — утверждение о полной семантической корректности не распространяется на эти четыре кода, до завершения реализации они считаются «зарезервированными».

E3xxx: Генерация кода ​

КодОписание
E3004Неподдерживаемый итератор
E3005Ошибка генерации IR
E3006Неразрешённая переменная
E3007Инициализация привязки верхнего уровня должна быть константой
E3008Неподдерживаемый шаблон match
E3014Переполнение регистров
E3017Недопустимый операнд (генерация кода)
E3018Сбой инстанцирования при мономорфизации
E3019Циклическая зависимость привязок верхнего уровня
E3020Отсутствует точка входа программы
E3021Точка входа не является функцией
E3022Сигнатура точки входа main не соответствует
E3023Исполняемые операторы недопустимы на верхнем уровне

E4xxx: Дженерики и трейты ​

КодОписание
E4001Нарушение ограничения дженерика
E4002Трейт не найден
E4003Реализация трейта отсутствует
E4004Конфликт реализаций трейта
E4005Ассоциированный тип не найден
E4010Деление константы на ноль
E4011Переполнение константы
E4012Слишком глубокая рекурсия констант
E4014Сбой вычисления константы
E4018Нарушение уточняющего предиката
E4019Равенство типов не выполняется
E4020Требуется функция-доказательство

E4006/E8004 в настоящее время не имеют точек срабатывания (зарезервированные коды): ограничение Sized и пути ошибок оптимизации ожидают реализации, при реализации будут подключены к реальным поверхностям срабатывания.

E5xxx: Модули и импорт ​

КодОписание
E5001Модуль не найден
E5002Ошибка импорта
E5003Экспорт не найден
E5004Циклическая зависимость
E5005Недопустимый путь модуля
E5006Повторный импорт
E5007Экспорт модуля

E6xxx: Ошибки времени выполнения ​

КодОписание
E6001Деление на ноль
E6003Выход индекса массива за границы
E6004Переполнение стека
E6005Сбой утверждения
E6006Функция не найдена (runtime)
E6007Ошибка времени выполнения
E6008Ключ не существует
E6009Недопустимый шаг Range
E6010Сбой парсинга целого числа
E6011Сбой парсинга числа с плавающей точкой

Редакция таблицы кодов (2026-08-09): исходная таблица кодов была определена по семантическому проекту Rust (Assertion failed/Arithmetic overflow/Heap allocation failed/Type cast failed), что не соответствует реальным потребностям реализации. В YaoXiang отсутствуют понятия нулевого указателя/сбоя кучи/приведения типов (семантика значений + безопасность памяти Rust), путь runtime-переполнения не реализует обнаружение. После корректировки:

  • E6002 удалён (исходный Assertion failed перемещён в E6005; семантика нулевого указателя не имеет аналога в языке)
  • E6003 изменён с Arithmetic overflow на Runtime index out of bounds (реальная поверхность срабатывания)
  • E6005 изменён с Heap allocation failed на Assertion failed (реальный путь std.assert)
  • E6006 изменён с Runtime index out of bounds на Function not found (так было в реализации изначально)
  • E6007 изменён с Type cast failed на общий Runtime error (унифицированная точка для неотображённых вариантов ExecutorError)

E7xxx: I/O и системные ошибки ​

КодОписание
E7001Файл не найден
E7002Доступ запрещён
E7003Ошибка I/O
E7004Сетевая ошибка

E8xxx: Внутренние ошибки компилятора ​

КодОписание
E8001Внутренняя ошибка компилятора
E8002Неожиданный Panic
E8003Ошибка фазы компилятора

W1xxx: Коды предупреждений ​

КодОписание
W1001Неиспользуемая приватная функция
W1002Неиспользуемый приватный тип
W1003Неиспользуемый импорт
W1004Неиспользуемая приватная переменная
W1005Неиспользуемый приватный метод
W1063Невозможно вычислить ограничение const-дженерика
W1080Деградация compile-time доказательства

Правило распределения W-кодов: изоморфно E-кодам, группировка по фазам (W + фаза в тысячных разрядах), W1xxx = предупреждения фазы проверки типов.

Семантика кода мёртвого кода (решение B по #321): определения с pub являются внешним интерфейсом и никогда не репортируются — вопрос использования внешним потребителем выходит за границы анализа одного файла, лучше молчать, чем ложно срабатывать. W1001/W1002/W1004/W1005 применяются только к приватным (не pub) определениям: если в анализе достижимости, начиная с main и pub определений, определение никогда не используется — репортируется. Для методов (W1005) сопоставление по короткому имени в точке вызова. Семантика bin/lib target (репорт неиспользуемого pub в bin) — будущее расширение (вариант A по #289), требует поддержки со стороны модели проекта.

Неиспользуемый импорт (W1003): обнаруживается на этапе use-elaboration в typecheck (pass2 регистрирует локальные имена импорта; попадание в выражения и позиции аннотаций типов считается использованием), покрывает как глобальный импорт (use std.io → псевдоним модуля), так и именованный (use std.io.{print}).

Канал срабатывания: W-диагностики от builder по умолчанию помечаются префиксом W как Severity::Warning (явное указание имеет приоритет), сбор и отображение идут по тому же пути, что и ошибки (рендеринг с префиксом warning[W####]), но не блокируют компиляцию и не влияют на код успешного завершения. yaoxiang check --deny-warnings повышает предупреждения до ошибки (ненулевой код выхода при наличии предупреждений), для строгого режима CI. Подавление per-code (атрибуты allow и т.п.) — пункт для последующего расширения.

Спецификация качества сообщений ​

Данный раздел введён ревизией унификации и качества сообщений (2026-09-03). Принудительно выполняется в CI скриптом scripts/audit_diagnostics.py.

  1. Единый путь сообщений: все видимые пользователю диагностические сообщения должны проходить через сокращённые методы авторитетного реестра + рендеринг шаблонов locales, код передаёт только структурированные параметры. Запрещено обходить реестр и напрямую конструировать сырые значения вроде Diagnostic::error(...) — этот путь обходит проверку кодов и i18n.
  2. Легитимность кода: запрещено использовать незарегистрированные коды и псевдокоды (например, E_INTERNAL); литералы кода в точке использования должны быть определены в реестре. Внутренние ошибки всегда приводят к E8001 (internal_error).
  3. Отображение типов: Display типов должен различать форму до и после инстанцирования (голое имя Expected 'Container', found 'Container' неразличимо).
  4. Изоляция внутреннего состояния солвера: промежуточные TypeVar солвера (формат Display t<N>) не должны попадать в видимые пользователю сообщения. Тестовый якорь: test_type_error_message_no_solver_typevar_leak.
  5. Граница E8xxx: E8xxx используется только для внутренних проблем согласованности компилятора (ICE). Ошибки, которые пользователь может исправить, запрещено маскировать под E8001; сообщения ICE должны сопровождаться указаниями по минимальному воспроизведению.

Значения ошибок runtime и сквозное использование кодов ​

Данный раздел введён ревизией привязки кода к значениям ошибок runtime (2026-09-03). Семантическое пространство E6xxx/E7xxx одновременно обслуживает два канала, пространство кодов едино, каналы отображения различны.

Два канала ​

КаналНосительСпособ отображения
Канал диагностики компилятора/CLIExecutorError и другие жёсткие ошибки уровня хостаstderr error[E####]: (уже подключены E6003/E6005/E6007)
Канал значений ошибок внутри программыErr-носитель Error типа Result(T, Error) из stdЗначение языка, потребляется программой через match/сравнение

Структура Error (с v0.8, ломающее изменение) ​

Error { code: String, message: String }
  • code повторно использует нумерацию E6xxx/E7xxx данной спецификации, в строковом виде (например, "E6008").
  • Стабильный контракт: выделенные коды сохраняют семантику между версиями; одна и та же семантика не переиспользует удалённый код (прецедент E6002).
  • Поверхность потребления: сравнение e.code == "E6xxx" внутри программы — единственный программируемый контракт определения; документация yaoxiang explain E6xxx сквозная; инструментарий (LSP / DAP, см. RFC-034) использует код как exceptionId.
  • Аксессоры: std.result.code(e) / std.result.message(e).
  • Пользовательские ошибки: E в Result(T, E) — параметр дженерика; для серьёзного моделирования используются пользовательские типы; std Error — лишь удобный резервный носитель, его система кодов не ограничивает пользовательские типы E.

Правила распределения кодов ​

  1. Коды значений ошибок runtime и диагностические коды компилятора разделяют пространство E6xxx/E7xxx; новые коды выделяются по реальной поверхности срабатывания, без резервирования под воображаемые сценарии.
  2. Сначала регистрация, затем использование: новый код должен быть внесён в авторитетный реестр и пройти проверку трёхсторонней согласованности (codes/*.rs ↔ locales ↔ таблица кодов данного документа), прежде чем он может быть отправлен. Источником регистрации кодов значений ошибок runtime служит таблица RUNTIME_ERROR_CODES в src/std/result.rs (наряду с диагностическими кодами проходит порог build-time в build.rs + проверку tools/code-tables).
  3. E7xxx — зарезервированный сегмент для значений ошибок std.io / std.net (в данный момент пуст, будет активирован при превращении io/net в Result).
  4. Точки срабатывания: модули std конструируют значение Error через error_new(code, message); на стороне потребителя std.result.unwrap_err извлекает Err-носитель, std.result.code/message читают поля.

Путь эволюции (линия C, не реализовано) ​

После завершения полноты сопоставления с образцом (RFC-010b) Error может быть обновлён до { kind: ErrorKind, message: String }, где code становится свойством, выводимым из kind (определение варианта — точка регистрации кода). В период эволюции стабильный контракт кода из данного раздела остаётся неизменным; это обновление — отдельное решение, не является обязательством данного раздела.


Файлы ресурсов для мультиязычности ​

Формат файла ресурсов ​

json
// locales/en.json
{
  "E1001": {
    "title": "Unknown variable",
    "message": "Referenced variable is not defined",
    "template": "Unknown variable: '{name}'",
    "help": "Check if the variable name is spelled correctly, or define it first",
    "example": "x = 100;",
    "error_output": "error[E1001]: Unknown variable: 'x'\n  --> example.yx:1:1\n   |\n 1 | print(x)\n   | ^ unknown variable 'x'"
  },
  "E1002": {
    "title": "Type mismatch",
    "message": "Expected type does not match actual type",
    "template": "Expected type '{expected}', found type '{found}'",
    "help": "Use the correct type or add a type conversion",
    "example": "x: Int = \"hello\";",
    "error_output": "error[E1002]: Type mismatch\n  --> example.yx:1:12\n   |\n 1 | x: Int = \"hello\";\n   |            ^ expected 'Int', found 'String'"
  }
}
json
// locales/zh.json
{
  "E1001": {
    "title": "未知变量",
    "message": "引用的变量未定义",
    "template": "未知变量:'{name}'",
    "help": "检查变量名是否拼写正确,或先定义它",
    "example": "x = 100;",
    "error_output": "error[E1001]: 未知变量:'x'\n  --> example.yx:1:1\n   |\n 1 | print(x)\n   | ^ 未知变量 'x'"
  },
  "E1002": {
    "title": "类型不匹配",
    "message": "期望类型与实际类型不匹配",
    "template": "期望类型 '{expected}',实际类型 '{found}'",
    "help": "使用正确的类型或添加类型转换",
    "example": "x: Int = \"hello\";",
    "error_output": "error[E1002]: 类型不匹配\n  --> example.yx:1:12\n   |\n 1 | x: Int = \"hello\";\n   |            ^ 期望 'Int',找到 'String'"
  }
}

Реализация I18nRegistry ​

rust
// locales/*.json (объекты кодов ошибок)

/// Реестр отображаемых текстов i18n (загружается в compile-time из JSON, нулевые таблицы поиска в runtime)
pub struct I18nRegistry {
    /// Заголовки
    titles: HashMap<&'static str, &'static str>,
    /// Описания
    messages: HashMap<&'static str, &'static str>,
    /// Справочная информация
    helps: HashMap<&'static str, &'static str>,
    /// Примеры кода
    examples: HashMap<&'static str, &'static str>,
    /// Примеры вывода ошибок
    error_outputs: HashMap<&'static str, &'static str>,
}

/// Информация об одном коде ошибки
#[derive(Clone, Copy)]
pub struct ErrorInfo<'a> {
    pub title: &'a str,
    pub message: &'a str,
    pub help: &'a str,
    pub example: Option<&'a str>,
    pub error_output: Option<&'a str>,
}

impl I18nRegistry {
    /// Получить реестр по коду языка
    pub fn new(lang: &str) -> Self {
        match lang {
            "zh" => Self::zh(),
            _ => Self::en(),
        }
    }

    /// Получить информацию об ошибке
    pub fn get_info(&self, code: &str) -> Option<ErrorInfo<'_>> {
        Some(ErrorInfo {
            title: self.titles.get(code)?,
            message: self.messages.get(code)?,
            help: self.helps.get(code)?,
            example: self.examples.get(code).copied(),
            error_output: self.error_outputs.get(code).copied(),
        })
    }

    /// Рендеринг шаблона (выполняется в compile-time, нулевые накладные расходы в runtime)
    pub fn render(&self, template: &'static str, params: &[(&str, String)]) -> String {
        let mut result = String::with_capacity(template.len() + 64);
        let mut chars = template.chars().peekable();

        while let Some(c) = chars.next() {
            if c == '{' {
                let mut key = String::new();
                while let Some(&c) = chars.peek() {
                    if c == '}' {
                        chars.next();
                        if let Some((_, value)) = params.iter().find(|(k, _)| k == &key) {
                            result.push_str(value);
                        } else {
                            result.push_str(&format!("{{{}}}", key));
                        }
                        break;
                    }
                    key.push(c);
                    chars.next();
                }
            } else {
                result.push(c);
            }
        }
        result
    }
}

Плейсхолдеры шаблонов ​

Предопределённые плейсхолдеры (часто используемые) ​
ПлейсхолдерНазначениеПример
{name}Имя переменной/типа/трейта и т.п.Unknown variable: '{name}'
{expected}Ожидаемый типExpected type '{expected}'
{found}Фактический/найденный тип, found type '{found}'
{method}Имя методаMethod {method} is not a function
{trait}Имя трейтаCannot find trait: {trait}
{path}Путь модуляInvalid path: {path}
{ty}Выражение типаInvalid type: {ty}
{message}Сообщение внутренней ошибкиInternal error: {message}
Поддержка произвольных ключей ​

params поддерживает произвольные ключи, не ограничиваясь предопределёнными. Вызывающий может передать любой key:

rust
// Использование произвольного ключа
E1001::unknown_variable(&var_name)
    .param("location", "global scope")
    .param("hint", "try declaring it first")
    .at(span)
    .build(&i18n);

// Определение шаблона
"Unknown variable: '{name}' at {location}. {hint}"

Примечание: не все коды ошибок используют плейсхолдеры. Некоторые коды ошибок (например, E0001) имеют статические сообщения и не требуют параметров.

Приоритет языка ​

1. yaoxiang.toml [language.default]
2. ~/.yaoxiang/yaoxiang.toml [language.default]
3. Значение по умолчанию: en

Конфигурация yaoxiang.toml ​

Конфигурация уровня проекта ​

toml
# yaoxiang.toml
[project]
name = "my-project"
version = "0.1.0"

[language]
# Язык сообщений об ошибках, возможные значения: en, zh, ja, ...
default = "zh"

Конфигурация уровня пользователя ​

toml
# ~/.yaoxiang/yaoxiang.toml
[language]
default = "zh"

Выбор языка в compile-time ​

1. Чтение language.default из yaoxiang.toml уровня проекта
2. Если не настроено, чтение ~/.yaoxiang/yaoxiang.toml уровня пользователя
3. Если оба не настроены, по умолчанию используется "en"
4. Компилятор создаёт I18nRegistry согласно выбранному языку (однократно)
5. Все ошибки используют этот I18nRegistry для рендеринга сообщений

Ключ к нулевым накладным расходам поиска ​

Рендеринг происходит при компиляции пользовательского проекта, а не в runtime.

┌─────────────────────────────────────────────────────────────────────────┐
│  Фаза 1: Rust-компиляция компилятора YaoXiang                            │
│                                                                           │
│  JSON упаковывается в бинарник компилятора                               │
│  Цель: команда explain может напрямую читать данные i18n                  │
└─────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────┐
│  Фаза 2: YaoXiang компилирует пользовательский проект (здесь происходит  │
│          рендеринг)                                                       │
│                                                                           │
│  При вызове макроса error!:                                               │
│  1. Чтение yaoxiang.toml для получения языковых предпочтений              │
│  2. Загрузка JSON нужного языка из бинарника компилятора                  │
│  3. Шаблон + параметры → render() → "Unknown variable: 'x'"              │
│  4. Diagnostic.message = отрендеренная строка                             │
│                                                                           │
│  AOT-бинарник напрямую хранит итоговую строку, без шаблонов и поиска    │
└─────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────┐
│  Фаза 3: Выполнение пользовательской программы                           │
│                                                                           │
│  println!("{}", diagnostic.message)                                      │
│  // Прямой вывод итоговой строки, без какого-либо поиска                  │
└─────────────────────────────────────────────────────────────────────────┘
КомпонентОбязанностиМомент рендеринга
I18nRegistryПредоставление шаблонов и отображаемых текстовПри компиляции пользовательского проекта
DiagnosticBuilder.render()Шаблон + параметры → итоговая строкаПри компиляции пользовательского проекта
Diagnostic.messageОтрендеренная строкаХранит итоговый результат
AOT-бинарникСодержит итоговые строкиИспользуется напрямую в runtime

Формат сообщений об ошибках ​

Сообщения об ошибках используют следующий формат:

error[E####]: <краткое описание>
  --> <файл>:<строка>:<столбец>
   <строка> | <фрагмент кода>
          ^^^<подсветка>

Полный пример ​

error[E1001]: Unknown variable: x
  --> src/main.yx:5:12
   5 |   print(x)
          ^
          help: Did you mean to define it?

Уровни серьёзности ​

Уровень серьёзности ошибки управляется перечислением DiagnosticLevel, декаплированным от номера кода ошибки:

rust
pub enum DiagnosticLevel {
    Error,    // Приводит к сбою компиляции
    Warning,  // Не влияет на компиляцию, но рекомендуется исправить
    Note,     // Дополнительная информация
    Help,     // Предложение по исправлению
}
УровеньПрефиксОписание
Errorerror[E####]:Приводит к сбою компиляции
Warningwarning[E####]:Не влияет на компиляцию
Notenote[E####]:Дополнительная информация
Helphelp[E####]:Предложение по исправлению

Команда yaoxiang explain ​

Синтаксис команды ​

bash
yaoxiang explain <ERROR_CODE> [OPTIONS]

Опции ​

ОпцияОписание
--lang <code>Указать язык (en-US, zh-CN, по умолчанию en-US)
--jsonВывод в формате JSON (для IDE/LSP)
--json-prettyФорматированный JSON-вывод
--examplesПоказывать только примеры кода
--helpПоказать справку

Примеры использования ​

bash
# По умолчанию на английском
$ yaoxiang explain E1001
error[E1001]: Unknown variable: {name}
  --> <file>:<line>:<col>

Help: Did you mean to define it?

Example:
  let {name} = value;

# Вывод на китайском
$ yaoxiang explain E1001 --lang zh
error[E1001]: 未知变量: {name}
  --> <file>:<line>:<col>

帮助: 你是否想要定义它?

示例:
  let {name} = value;

# JSON-вывод (для интеграции с LSP)
$ yaoxiang explain E1001 --json
{
  "code": "E1001",
  "message": "Unknown variable: {name}",
  "help": "Did you mean to define it?",
  "examples": ["let {name} = value;"],
  "language": "en-US"
}

Формат JSON-вывода ​

json
{
  "code": "E1001",
  "message": "Unknown variable: {name}",
  "help": "Did you mean to define it?",
  "examples": ["let {name} = value;"],
  "language": "en-US"
}

Обратная совместимость ​

Поскольку данный RFC проектирует систему кодов ошибок с нуля, проблемы обратной совместимости отсутствуют.

Стратегия будущей миграции (для справки в последующих версиях):

  1. Сохранять отображение старых кодов ошибок на новые
  2. В период миграции одновременно отображать старые и новые коды
  3. Предоставить график устаревания

Стратегия реализации ​

Фаза первая: базовая инфраструктура кодов ошибок ​

  1. Создать структуру каталога src/diagnostics/
  2. Реализовать перечисление ErrorCode
  3. Реализовать Diagnostic и DiagnosticLevel
  4. Создать каталог файлов ресурсов и примеры JSON

Фаза вторая: команда explain ​

  1. Реализовать CLI-команду yaoxiang explain
  2. Поддержка опций --lang и --json
  3. Интеграция загрузки файлов ресурсов
  4. Реализация рендеринга шаблонов с параметрами

Фаза третья: интеграция в compile-time ​

  1. Обновить все точки репортинга ошибок для использования новой системы
  2. Реализовать инъекцию параметров шаблона сообщения
  3. Добавить логику приоритета языка
  4. Покрытие модульными тестами

Фаза четвёртая: интеграция с IDE/LSP ​

  1. LSP-сервер интегрирует JSON-вывод команды explain
  2. Отображение ссылок на коды ошибок в IDE
  3. Показ объяснения ошибки при наведении
  4. Предложения быстрых исправлений

Приложение ​

Полная сводная таблица кодов ошибок ​

ДиапазонКатегория
E0xxxЛексический и синтаксический анализ
E1xxxПроверка типов
E2xxxСемантический анализ
E3xxxГенерация кода
E4xxxДженерики и трейты
E5xxxМодули и импорт
E6xxxОшибки времени выполнения
E7xxxI/O и системные ошибки
E8xxxВнутренние ошибки компилятора
E9xxxЗарезервировано

Поддерживаемые языки ​

КодЯзыкСтатус
en-USEnglish (US)По умолчанию
zh-CN简体中文В планах

Сравнение примеров сообщений об ошибках ​

# Английский (en-US)
error[E1001]: Unknown variable: x
  --> src/main.yx:5:12
   5 |   print(x)
          ^
          help: Did you mean to define it?

# Китайский (zh-CN)
error[E1001]: 未知变量: x
  --> src/main.yx:5:12
   5 |   print(x)
          ^
          帮助: 你是否想要定义它?

Ссылки ​