/
nv-lang
/
nova
Обзор
Документация
Войти
/
nv-lang
/
nova
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
main
compiler-codegen/src/ast/mod.rs
3 606 строк
176 KB
Evgeniy Golovin
feat(D449): add supervised(on_timeout:) handler
10 авг 2026, 08:10
10 авг 2026, 08:10
70451e6
Код
Авторство
О чём код?
//! Типы AST. //! //! Минималистичный набор: всё что нужно для bootstrap'а Nova-on-Nova. //! Plan 33.1: добавлены контракты (`Contract`, `VerifyMode`, `Purity`) — //! AST-узлы готовы; парсер/typecheck/SMT расширения — в последующих //! фазах [Plan 33.1](../../../docs/plans/33.1-contracts-core.md). pub mod format_spec; pub mod pretty; pub use format_spec::FormatSpec; use crate::diag::Span; /// Корневой узел — модуль (файл). #[derive(Debug, Clone)] pub struct Module { pub name: Vec<String>, // module a.b.c → ["a", "b", "c"] pub imports: Vec<Import>, pub items: Vec<Item>, /// Plan 42 Sub-plan 42.A: module-level attributes (`#forbid X, Y`). /// `#requires` отвергнут — нарушает AI-first explicit principle. pub attrs: Vec<ModuleAttr>, /// Plan 45 Ф.22.1 / D105: module-level doc-attrs /// (`#stable`/`#unstable`/`#experimental`/`#deprecated`/`#hide_doc`/etc.). /// Семантически: tier propagates на items module'а без явного override. pub doc_attrs: Vec<DocAttr>, pub span: Span, /// Plan 42 Sub-plan 42.4 (шаг 1, 2026-05-14): per-peer attribution. /// /// Для **single-file** module — `peer_files = [PeerFile { ... }]` /// (1 elem) со всеми imports/items entry-файла. /// /// Для **folder-module** — N elem'ов, по одному на peer-файл /// (alphabetical order, как `module.items`). Каждый peer хранит свои /// `imports` (per-peer scope по правилу C) и `items_here` (items, /// объявленные именно в этом peer-файле). /// /// `Module.imports` и `Module.items` остаются flat для backward compat /// (codegen/interp/verify их читают как раньше). 42.4 шаги 2-3 учат /// type-checker использовать `peer_files` для per-peer name resolution. /// /// Имя `peer_files` (а не `files`) — чтобы не конфликтовать со /// смежным `diag::SourceFile` и явно отражать терминологию Plan 42 spec. pub peer_files: Vec<PeerFile>, /// Plan 45 / D104: inner doc-comment модуля (`//!`), если присутствует. /// Допустим только в начале файла (после `module X` и `import`-ов, /// до первого item'а). Объединяется с `#doc "..."` module-attr /// (D101) на этапе collector'а (Plan 45 Ф.4). pub doc: Option<DocBlock>, /// Plan 181 (D347): same-scope re-binding side-table produced by /// `alpha_rename`. Maps a uniquified rebind name (`x__s1`) → the unique /// name it shadows in the same scope. The consume-checker reads this to /// fire `E_REBIND_LIVE_CONSUME` (R2) when a rebind hides an unmet consume /// obligation. Empty (`Default`) until the pass runs; harmless for any /// module with no same-scope rebind. pub rebind_shadows: std::collections::HashMap<String, String>, /// [M-consume-rebind-nested-block-shadow] (Plan 172.13): spans of /// `consume x = expr` `Let`-statements whose prior binding for `x` lives /// in an ENCLOSING (not the current) lexical scope — a nested-block /// consume-rebind. Nova's own semantics (D347/D9) treat this as updating /// the SAME logical variable (not a fresh block-scoped shadow), so /// codegen must reuse the existing C variable (plain reassignment) /// instead of emitting a new block-scoped C declaration that would go /// out of scope at the end of the block, silently reverting to the /// stale (already-consumed) outer value. Populated by `alpha_rename`; /// empty (`Default`) for any module without this pattern. pub consume_reuse_spans: std::collections::HashSet<crate::diag::Span>, } /// Plan 42 Sub-plan 42.4 (шаг 1, 2026-05-14): per-peer source attribution. /// /// Сохраняет imports каждого peer-файла отдельно, не merge'нутые в /// flat `Module.imports`/`Module.items`. Используется type-checker'ом /// для enforce'а правила C (peers share declarations namespace, но /// **не imports**). /// /// Для single-file module — один PeerFile со всеми entry-данными. /// Для folder-module — N PeerFile, по одному на peer-файл. /// /// `file_id` — будет заполнен в шаге 2 (FileRegistry activation + /// span walker). Сейчас (шаг 1) остаётся `MAIN_FILE_ID` placeholder. #[derive(Debug, Clone)] pub struct PeerFile { /// Канонический путь к файлу (для diagnostics + identity). pub path: std::path::PathBuf, /// FileId — назначается в шаге 2. В шаге 1 = `MAIN_FILE_ID`. pub file_id: crate::diag::FileId, /// `import` statements именно этого peer-файла (per-peer scope). pub imports: Vec<Import>, /// Items, объявленные **здесь** (не pulled via imports' transitive /// resolve). Type-checker использует для построения per-peer /// declarations namespace (shared между peers одного module). pub items_here: Vec<Item>, /// Plan 42.15: имена items, ставших видимыми в этом peer'е через /// его **прямые** `import` statements (после rename + selective /// filter). Транзитивные imports (import чужого import'а) сюда НЕ /// попадают — это и есть Rule C strict isolation. /// /// NameResCtx использует это + items_here peers ЭТОГО module /// (shared decls) для построения per-peer visible scope. pub imported_item_names: std::collections::HashSet<String>, /// Plan 42.15: true — peer принадлежит **компилируемому** module /// (entry + его folder-module peers). false — peer импортированного /// модуля. NameResCtx собирает `shared_decls` ТОЛЬКО из /// `is_entry_module = true` peers — иначе items импортированных /// folder-modules «протекли» бы в shared namespace (нарушение Rule C). pub is_entry_module: bool, /// Plan 81 Ф.1: объявленное имя модуля (из `module X.Y.Z`). /// Используется NameResCtx для группировки peers одного module: /// только files с одинаковым module_name делят declarations namespace. /// Защищает от ложного group-sharing когда два разных модуля живут /// в одной директории (e.g. nova_tests/plan81/lib.nv vs /// nova_tests/plan81/visibility_fn_rejected.nv — разные модули, /// разные module_name, но одна папка). pub module_name: Vec<String>, } /// Plan 45 / D104: блок doc-comment'ов, привязанный к item'у или /// модулю. Это последовательность одного или нескольких подряд /// идущих `///` (outer) либо `//!` (inner) комментариев, склеенных /// лексером (см. `lexer::TokenKind::DocComment`). /// /// `kind` — указывает, outer это (привязан к следующей декларации) или /// inner (привязан к окружающему модулю). `content` — сырой текст, /// markdown НЕ парсится на уровне AST; парсинг markdown + /// section-extraction — отдельный pass (`doc::passes::derive_sections`). #[derive(Debug, Clone, PartialEq)] pub struct DocBlock { pub kind: crate::lexer::DocCommentKind, pub content: String, pub span: Span, } /// Plan 42 Sub-plan 42.A: module-level attribute. #[derive(Debug, Clone)] pub struct ModuleAttr { pub kind: ModuleAttrKind, /// Для `#forbid` — список эффектов. Для `#cfg` — пусто (predicate в kind). pub effects: Vec<String>, pub span: Span, } #[derive(Debug, Clone, PartialEq, Eq)] pub enum ModuleAttrKind { /// `#forbid X, Y` — все functions module не могут использовать /// эти effects (compile error через capability check). Forbid, /// Plan 42.12 Ф.2: `#cfg(feature = "X")` или `#cfg(target_os = "Y")` — /// module/peer активен только при matching condition. /// Поскольку filename suffix (Ф.1) покрывает 90% target_os кейсов, /// `#cfg(target_os)` рекомендуется только для item-level (Ф.3). Cfg(CfgPredicate), /// Plan 42.11: `#doc "..."` — module-level documentation line. /// Multi-peer merging: все `#doc` строки из всех peers concat'аются /// в alphabetical filename order. AI-first signal: LLM видит /// module purpose прямо в peer-файле без CLI invoke. /// Consumer: Plan 45 (nova doc). Doc(String), /// Plan 33.3 Ф.13: `#must_verify_module` — все функции модуля /// автоматически получают MustVerify. Любой unproven контракт → compile error. MustVerifyModule, /// Ф.3.4 (Plan 33.6): `#proof_budget(timeout_ms=N, vc_count_max=M)` — /// module-level бюджет верификации. Переопределяется per-fn `#verify_timeout`. ProofBudget { timeout_ms: Option<u32>, vc_count_max: Option<u32> }, /// **Plan 107 D174:** `#no_prelude` before `module` declaration. Suppress'ит /// auto-import `std.prelude` (D26). Применение: /// - real-time / embedded (prelude содержит GC-using код), /// - bootstrap уровни (сам prelude и его sub-modules — auto-detected /// через `is_prelude_self_module`, не требует opt-out), /// - обучающие примеры где надо явно показать всё. /// Прежняя inline-форма `module X no_prelude` удалена (D174, Plan 107). /// Без `#no_prelude` — стандартный prelude auto-import (D26 default). /// Совместим с explicit `import std.prelude.core.{Option}` etc. NoPrelude, /// **Plan 107 D174:** `#prelude(core, runtime)` before `module` declaration. /// Auto-import только перечисленных sub-modules `std.prelude.<name>` /// вместо full facade. Валидные имена: `core`, `runtime`, `errors`, /// `collections`, `protocols`, `effects`. Имена валидируются на /// resolver-этапе (compiler error при опечатке). /// Пустой list `#prelude()` — compile error; используй `#no_prelude`. /// Прежняя inline-форма `module X partial_prelude(...)` удалена (D174). PartialPrelude(Vec<String>), /// **Plan 107 D174:** `#allow(shadow)` before `module` declaration. /// Suppresses `W_PRELUDE_SHADOW` warnings emitted by /// `lints::lint_prelude_shadow` for user-declarations that shadow /// prelude-imported names. /// Прежняя inline-форма `module X allow_prelude_shadow` удалена (D174). /// /// **Когда применять:** /// - Local DSL слой, переопределяющий `Option`/`Result`/etc. с /// осознанным intent'ом (e.g. embedded targets с non-GC types). /// - Test fixtures, где user-decl эксплицитно тестирует shadowing. /// - Bootstrap слои, не имеющие #no_prelude но желающие тихо /// shadow'ить отдельные имена. /// /// Без `#allow(shadow)` (default) — shadowing → W_PRELUDE_SHADOW /// warning + user-declaration wins (compilation продолжается). /// С `#allow(shadow)` — то же поведение, но без warning'а. /// /// Item-level suppress (`#[allow(prelude_shadow)] type Foo`) — DEFERRED /// (требует generic attribute parser, который пока hardcoded на /// `TypeAttr` enum'ы; см. ast::TypeAttr). AllowPreludeShadow, /// **Plan 90.1 D141 amend:** `#allow(view_extend_detach)` before `module`. /// Suppresses `W_VIEW_EXTEND_DETACH` warnings emitted by /// `lints::lint_view_extend_detach` when a grow-method (append / insert / /// reserve) is called on a parent array after a slice-view /// of that parent was created in the same function scope. /// /// **When to apply:** /// - Code where the view is intentionally discarded before the grow call. /// - Test fixtures verifying extend/insert/reserve behaviour on arrays /// that happen to have a prior view binding in scope. /// /// Default: grow-call after view-binding → W_VIEW_EXTEND_DETACH warning. /// With `#allow(view_extend_detach)`: same behaviour, no warning. AllowViewExtendDetach, } /// Plan 42.12 Ф.2 + Plan 42.14 Ф.1: cfg predicate. /// Plan 42.14: добавлены `any/all/not` композиции (Rust-style). #[derive(Debug, Clone, PartialEq, Eq)] pub enum CfgPredicate { /// `#cfg(feature = "X")` — active если feature `X` в `nova.toml [features]` /// AND enabled через `--features` CLI flag. Feature(String), /// `#cfg(target_os = "Y")` — active если current target matches. TargetOs(String), /// Plan 42.14 Ф.1: `#cfg(any(P1, P2, ...))` — active если хоть один. Any(Vec<CfgPredicate>), /// Plan 42.14 Ф.1: `#cfg(all(P1, P2, ...))` — active если все. All(Vec<CfgPredicate>), /// Plan 42.14 Ф.1: `#cfg(not(P))` — active если P inactive. Not(Box<CfgPredicate>), } /// Plan 35 sub-plan 35.A (R26): селективный import — `import X.Y.{A, B as C}`. #[derive(Debug, Clone)] pub struct ImportItem { pub name: String, pub alias: Option<String>, pub span: Span, } /// Plan 84: якорь импорта — абсолютный (от корня пакета) или относительный. #[derive(Debug, Clone, PartialEq, Eq)] pub enum ImportAnchor { /// Абсолютный — путь от корня пакета (bare `import a.b.c`). Default. Package, /// Относительный (Plan 84) — `./` (`up == 0`, директория импортирующего /// файла) либо `../`×n (`up == n`, n уровней вверх). Резолвится /// относительно директории файла, строго в пределах своего пакета. Relative { up: u32 }, } #[derive(Debug, Clone)] pub struct Import { pub path: Vec<String>, /// Plan 35 sub-plan 35.A (R26): селективный список `{A, B as C}`. /// `None` = весь модуль (legacy). `Some([])` — невалидно, парсер не /// эмитит. `Some([item, ...])` — селективный набор: видимы только /// перечисленные имена. pub items: Option<Vec<ImportItem>>, pub alias: Option<String>, /// Plan 35 sub-plan 35.A (R26): `export import X.{A}` — re-export. /// При false — обычный import. pub is_export: bool, pub span: Span, /// Plan 45 Ф.24.11: doc-attrs on import/re-export. /// Currently: DocInline, DocNoInline (controls inline rendering in nova doc). pub doc_attrs: Vec<DocAttr>, /// Plan 84: относительный/абсолютный якорь резолва пути импорта. pub anchor: ImportAnchor, } /// Top-level декларация в модуле. #[derive(Debug, Clone)] pub enum Item { Fn(FnDecl), Type(TypeDecl), Let(LetDecl), Const(ConstDecl), Test(TestDecl), /// Plan 57: `bench "name" { ... measure { ... } ... }` — /// benchmark declaration. Только discoverable под `nova bench`, /// игнорируется в `nova test`/`nova build`. Body содержит setup /// + один `measure { ... }` блок. Bench(BenchDecl), /// Plan 33.5 Ф.4.1: `lemma` — proven proof term. /// Тело SMT-верифицируется (unknown == fail), не emit'ится в runtime. /// Может применяться через `apply lemma_name(args)` в телах функций. Lemma(LemmaDecl), } /// Plan 33.5 Ф.4.1: декларация lemma. /// /// `lemma name(params) requires P ensures Q { proof_body }` /// /// - Тело верифицируется SMT (`verify_mode == MustVerify` по умолчанию). /// - Не emit'ится в C (ghost, только для proof). /// - `apply name(args)` в теле fn добавляет `ensures[args/params]` как /// assertion в SMT-scope вызывающей fn (подстановкой аргументов). #[derive(Debug, Clone)] pub struct LemmaDecl { pub name: String, pub generics: Vec<GenericParam>, pub params: Vec<Param>, pub contracts: Vec<Contract>, pub body: FnBody, pub span: Span, } /// Plan 45 Ф.3 / D105: doc-атрибуты (`#deprecated(...)`, `#since(...)`, /// `#stable`, `#unstable(feature=...)`, `#experimental(note=...)`, /// `#hide_doc`, `#doc_alias("a","b")`, `#doc(summary=...)`, /// `#doc(inline)` / `#doc(no_inline)`, `#doc(section="Name")`). #[derive(Debug, Clone)] pub enum DocAttr { /// `#deprecated(since = "X", note = "...", until = "Y"?)`. Deprecated { since: Option<String>, note: Option<String>, until: Option<String>, }, /// `#since("X.Y")` или `#since(version = "X.Y")`. Since(String), /// `#stable` или `#stable(since = "X.Y")`. Stable { since: Option<String> }, /// `#unstable(feature = "name")`. Unstable { feature: Option<String> }, /// `#experimental(note = "...")`. Experimental { note: Option<String> }, /// `#hide_doc` — exported, но скрыт из nova doc output. HideDoc, /// `#doc_alias("name", "name", ...)` — alternative names для search. DocAlias(Vec<String>), /// `#doc(inline)` — re-export рендерится inline (default same-package). DocInline, /// `#doc(no_inline)` — re-export рендерится только ссылкой. DocNoInline, /// `#doc(summary = "...")` — override первого-предложения summary. DocSummary(String), /// `#doc(section = "Name")` — custom section grouping. DocSection(String), /// `#doc(test_handlers = "path.to.handlers")`. DocTestHandlers(String), /// Plan 175 Ф.2-v2: `#default_handler(EffectName)` — declares this /// zero-arg free fn (`-> Effect[EffectName]`) as the AMBIENT default /// handler-factory for `EffectName`. Compiler synthesizes a lazy, /// once-per-thread construction of the returned handler literal on /// first transitive use of an op of `EffectName` with no enclosing /// `with EffectName = …` in scope (an explicit `with` still overrides, /// same as today); an effect with no `#default_handler` keeps its /// prior default behaviour unchanged (opt-in per effect — see /// spec/decisions/04-effects.md D-block `#default_handler`). /// /// Plan 175.2 Ф.2-v4 (П7, D431): the `(EffectName)` argument is now /// OPTIONAL — `None` when written bare (`#default_handler`, no parens); /// the checker (`check_default_handlers`) infers the effect name from /// the fn's own `-> Effect[X]` return type instead (DRY — the name /// was always redundant with the return type). `Some(name)` (explicit /// form, unchanged) still overrides/cross-checks against the return /// type as before. DefaultHandler(Option<String>), } /// Функция: и свободная, и метод (через `receiver`). /// /// Plan 123 baseline-fix (2026-06-02): `Default` derive — enables test /// fixtures to use `..Default::default()` spread when constructing fake /// FnDecls (sum_schema_registry tests). Robust to future field additions: /// callers may opt to set only the fields they actually need. #[derive(Debug, Clone, Default)] pub struct FnDecl { /// Plan 45 / D104: doc-comment, прикреплённый парсером (один или /// несколько подряд идущих `///` непосредственно перед `fn`). /// `None` — у функции нет doc-comment'а. pub doc: Option<DocBlock>, /// Plan 45 Ф.3 / D105: doc-атрибуты, собранные парсером. pub doc_attrs: Vec<DocAttr>, pub is_export: bool, /// D82: external fn — реализована в nova_rt/*.h. Body отсутствует /// (FnBody::External). Только в std.runtime.* whitelisted. pub is_external: bool, /// Plan 91.12 Ф.-1 (D282): ABI specifier from `extern "ABI" fn`. /// `Some("nova")` → `nova_fn_` prefix (runtime function). /// `Some("C")` → literal C name (external C library function). /// `None` → legacy `external fn` syntax (treated as `"nova"`). pub extern_abi: Option<String>, pub name: String, /// Receiver — для методов через `@`. None у свободных функций. pub receiver: Option<Receiver>, /// Plan 15 (D72): `[T]` или `[T Hashable]` — имя + optional bound. pub generics: Vec<GenericParam>, pub params: Vec<Param>, pub effects: Vec<TypeRef>, // эффекты между `)` и `->` pub return_type: Option<TypeRef>, /// Plan 114.4.2 (D199): `-> const T` — return type marked `const`, /// fn evaluates at compile time. Together with all-`const` params /// makes the fn a `const fn`. All-or-nothing rule (см. is_const на Param). pub return_is_const: bool, /// Plan 77 (D132): `-> @` — метод возвращает сам receiver (fluent). /// `return_type` при этом = `Self` (тип результата — receiver-тип); /// флаг добавляет гарантию «возвращается именно receiver». Валидно /// только для instance-метода (parser enforce'ит). pub returns_receiver: bool, pub body: FnBody, pub span: Span, /// Plan 16 (D64 sugar §3697) / Plan 113: `#realtime` / `#realtime nogc` атрибут перед `fn`. /// Fn-level callee guarantee — body can only call other `#realtime` fns/primitives. pub realtime_attr: RealtimeAttr, /// Plan 113 (D172): `#blocking` attribute перед `fn`. /// Runtime threadpool offload — callers wrap fn in uv_queue_work, fiber parks. pub blocking_attr: bool, /// A-V10 (D441 §5 №167 closure, Plan 238): `#thread_affine` attribute /// перед `extern fn` — маркирует M:N-небезопасный лист (thread-affine/ /// нереэнтерабельный C-side вызов: TLS-состояние, потокопривязанный /// handle и т.п.). Парсер (тот же путь, что `blocking_attr`) требует /// `is_external` (checker enforces — E_THREAD_AFFINE_NOT_EXTERN). /// Транзитивно поднимается по именованному графу вызовов /// (`thread_affine_closure` в `types/mod.rs`) и гейтится на границе /// `spawn`/`detach`/`parallel for` (`E_THREAD_AFFINE_IN_FIBER`). pub thread_affine_attr: bool, /// Plan 154.1 (D268): `#impl(P1 + P2 + ...)` ведущий атрибут на МЕТОД-декларации /// (`fn T @m`) — opt-in объявление, что метод реализует метод протокола(ов) P. /// Checker валидирует подпись против P + привязывает P к receiver-типу /// (`type_impl_protocols`). Пустой вектор = без `#impl` (структурная конформность /// по-прежнему работает). На тип-декларациях `#impl` хранится отдельно /// (`TypeDecl.impl_protocols`, D186/Plan 91.9). pub impl_protocols: Vec<String>, /// Plan 110.7.3.a (D188 §FFI): `#cancel_safe` attribute на `external fn`. /// Attests что C-side function is cancel-safe — can be invoked from inside /// ConsumeScope cleanup body (under cancel-shield). Without this attribute, /// the cancel-unsafe lint (W_FFI_CANCEL_UNSAFE) fires at the call site /// when the call appears inside an cleanup body. Default `false`. pub cancel_safe_attr: bool, /// Plan 214 (D429): `#coerce` attribute перед `fn` — declares an implicit /// zero-cost conversion `I → O` (`I` = receiver type, `O` = return type). /// V1 legal shapes: instance method with zero params (view — non-`consume` /// + `ro`-return, or finalize — `consume` + owning return); a static /// one-param form parses too but is rejected at check-time /// (`E_COERCE_RECEIVER_FORM_DEFERRED`, R1). Checker (`types/mod.rs /// collect_coerce_pairs`) validates shape/zero-cost/effect-freedom/ /// generics/duplicates and builds the accept+rewrite+lint-shared pair /// registry. `#coerce` is rejected on protocol/effect method REQUIREMENTS /// at parse-time (R15) — never reaches an `EffectMethod`/`FnDecl` here. pub coerce_attr: bool, /// Plan 33.1 (D24): контракты после сигнатуры, до тела. /// Пустой вектор у функций без контрактов (backward-compat). pub contracts: Vec<Contract>, /// Plan 33.2 (D24): `reads <expr>{, <expr>}*` — frame-read targets. /// Перечисляет l-values которые функция читает (handler-state + record /// fields). Используется SMT для frame-axiom («всё что вне reads /// не повлияет на ensures»). pub reads: Vec<FrameTarget>, /// Plan 33.2 (D24): `modifies <expr>{, <expr>}*` — frame-write targets. /// Перечисляет l-values которые функция МОЖЕТ изменить. /// Type-check проверяет body: assignments вне frame → error. /// SMT использует frame-axiom: всё вне modifies = old. pub modifies: Vec<FrameTarget>, /// Plan 33.2 Ф.7 (D24): `decreases <expr>` — termination measure /// для recursive функций. Well-founded measure; SMT-verify check'ает /// что для каждого рекурсивного вызова `f(args')` в теле `f(args)`: /// `decreases(args') < decreases(args)`. /// Без decreases для recursive fn — warning (ensures could be vacuously /// satisfied if fn diverges). В trivial-backend 33.2 — parsed, not enforced. pub decreases: Option<Expr>, /// Plan 33.1 (D24): режим верификации контрактов. /// `@must_verify` / `@unverified` / default. pub verify_mode: VerifyMode, /// Plan 33.1 (D24 §35): `@verify_timeout(ms)` — локальный override /// для SMT-таймаута. None = глобальный default (2000ms). pub verify_timeout_ms: Option<u32>, /// Plan 33.1 (D24): `@pure` — assertion что функция чистая. /// Реальная purity выводится в Ф.2 через SCC по call-graph; /// если выведенная не соответствует объявленной — compile error. pub purity: Purity, /// Plan 33.3 Ф.13: `#trusted` external fn — контракты становятся axioms /// (без SMT-доказательства). Допустим только для `external fn`. pub is_trusted: bool, /// Plan 33.9 Ф.1 (D24 §35): `#opaque` — body не раскрывается в SMT /// scope (treated как UF). Требует `#pure`, conflict с `#verify`. /// Реализовано: parser/AST/lints (V1); Z3 axiomatic encoding — V2. pub is_opaque: bool, /// Plan 33.9 Ф.3: `#fuel(n)` — controlled unfolding depth для opaque /// recursive fns. None = default 0 (без unfold). Z3 emits chain of /// N axioms (V2 — TrivialBackend ignores). pub fuel: Option<u32>, /// Plan 33.7: `#nooverflow` — для каждой BitVec-арифметической операции /// в теле fn генерируется overflow VC. Если доказательство неудачно → /// compile error. Без атрибута: wrap-around семантика (2's complement). pub no_overflow: bool, /// Plan 103.6 / Plan 113: sync interaction class — parsed from #realtime/#parks/#wakes. /// None = no annotation (conservative: treated as Parks in realtime context). /// Stored in ExternalDecl for O(1) lookup during emit_call. pub sync_class: Option<SyncClass>, /// Plan 118 (D216 §9, D2 amend): `#unsafe` attribute on fn declaration. /// Body implicitly в unsafe context (pointer ops без unsafe{} wrap). /// Callers must `unsafe { ... }` wrap the call (E_UNSAFE_CALL_REQUIRES_WRAP /// иначе). No effect propagation up — каждая fn decides encapsulate or /// propagate (canonical Rust pattern). V1 Ф.3.2: parse + AST storage; /// type-checker enforcement E_UNSAFE_CALL_REQUIRES_WRAP — Ф.3.3-3.5 /// followup. Default `false`. pub unsafe_attr: bool, /// Plan 114.4.4 Ф.1 (D199 V3): `#fn_eval_max_depth(N)` attribute — /// per-fn override const fn evaluator recursion depth (default 256). /// `None` = use evaluator default. Range 1..=65535 (parser-enforced). pub fn_eval_max_depth: Option<u32>, /// Plan 124.6 (D225): `#test_access(TypeX, TypeY...)` attribute — /// fn body получает priv-field access к указанным types (escape /// hatch для unit tests + sibling helper fns). Empty = no extra /// access. Backward-compat: default empty Vec. pub test_access_for: Vec<String>, /// Plan 126.2 Ф.1: `true` если этот FnDecl был synthesized компилятором /// (auto-derive `#impl(P)` default-protocol-method synthesis), а не написан /// пользователем в исходнике. Используется (a) для регистрации synthesized /// методов в method_table наравне с user-методами, (b) для downstream-проходов /// (Plan 127 lint-suppression W_VALUE_RECORD_UNNECESSARY_PROMOTE), /// которым нужно отличать compiler-generated методы от user-кода. /// Default `false` (backward-compat — все распарсенные fns не synthesized). pub compiler_generated: bool, /// Plan 170 (D307): `priv(file) fn` — file-private visibility. Символ /// помечен «не виден peer-файлам модуля» (visibility-hint, НЕ смена /// module-резолва — модуль остаётся один, D29). Аналог Rust `pub(self)`. /// Лесенка: `priv(file)` ⊂ module-default ⊂ `export`. Взаимоисключаем с /// `is_export`. Default `false`. pub file_private: bool, } /// Plan 194 A2.1: build-policy режим флага `--contracts` (замена legacy /// `enforce|off`, D24 amend — `off` убран, глобальный legacy zero-cost /// bypass больше не существует ни под одним значением флага). /// /// В ЭТОМ атоме все три значения производят БАЙТ-ИДЕНТИЧНЫЙ codegen — /// поведение старого default `enforce`: недоказанные `requires`/`ensures`/ /// `invariant` проверяются в runtime (debug И release), Z3/Trivial-proven /// элидируются. Plan 194 A4: per-fn/module `#unchecked` opt-out (единственный /// прежде существовавший источник элизии сверх пруфа) РЕТРАКТИРОВАН. /// Различия между режимами (Z3-driven элизия под `optimized`/`verified`, /// `#debug`-эрозия) — последующие атомы волны 2 (A2.2+/A3). #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub enum ContractsMode { /// Ничего не элидируется кроме уже доказанного (loop/code-proven ИЛИ /// contract-proven-when-enforced) — как нынешний `enforce`. Default для /// dev-профиля. #[default] Checked, /// Z3-driven sound-элизия доказанных + `#debug`-эрозия. Default для /// release-профиля. (Режим `verified` УДАЛЁН 2026-07-15, владелец: whole-build /// «докажи всё или compile-error» — footgun; статическая верификация — через /// per-fn `#verify` + `nova verify-contracts`, не build-режим.) Optimized, } impl ContractsMode { /// Парсит значение CLI-флага `--contracts`. Clap `value_parser` /// ограничивает вход этими двумя строками — паника здесь означала бы /// баг в самом парсере, а не пользовательский ввод. pub fn parse(s: &str) -> Self { match s { "checked" => ContractsMode::Checked, "optimized" => ContractsMode::Optimized, other => panic!( "invalid --contracts value (CLI value_parser should have rejected this): {other}" ), } } } /// Plan 33.1 (D24): один контракт-clause функции. #[derive(Debug, Clone)] pub struct Contract { pub kind: ContractKind, pub expr: Expr, pub span: Span, /// Plan 140.1 Ф.1 (D24 amend): опциональное пользовательское сообщение /// `requires <expr>, "<msg>"`. `None` — без сообщения (формат A: /// `<file>:<line>: <kind> failed: <expr>`); `Some(msg)` — формат B: /// `<file>:<line>: <kind> failed: <msg> (<expr>)`. Пользователь НЕ /// указывает локацию — она проставляется на codegen-сайте из /// `__FILE__`/`__LINE__`. Только string-literal допустим в этой позиции /// (не-литерал → `E_CONTRACT_MESSAGE_NOT_STRING`). pub message: Option<String>, /// Plan 140.3 ([M-140.1-message-interpolation]): интерполированное сообщение /// `requires <expr>, "... ${e} ..."` — десугарится в `ExprKind::InterpolatedStr`. /// requires/ensures codegen ПРЕДПОЧИТАЕТ это поле: вычисляет строку НА САЙТЕ /// нарушения (lazy, только при провале), захватывая runtime-значения. Когда /// оно `Some`, `message` параллельно хранит СЫРОЙ литерал (`"got ${x}"`) как /// fallback для сайтов, пока не поддерживающих interp (type invariants — /// показывают литерал, как до Plan 140.3; без silent-drop). Плоский литерал /// (без `${}`) → `message_expr = None`, только `message`. Оба `None` — без сообщения. pub message_expr: Option<Expr>, /// Plan 194 Ф.1 (D-блок TBD, 09-tooling.md / D24-амендмент): `#debug`- /// префикс перед клаузой (`#debug requires`/`#debug ensures`/ /// `#debug invariant`) — dev-only контракт (дорогая проверка, стирается /// в release). V1 (эта фаза): парсится и хранится, но ИНЕРТНО — codegen /// эмитирует контракт как обычно независимо от значения этого поля; /// реальная erasure-семантика по `--contracts`-режиму — Ф.2 (следующая /// волна). `false` — обычный always-on контракт (текущее поведение). pub debug_only: bool, } /// Plan 33.2 (D24): frame-target — l-value, который функция читает /// (`reads`) или пишет (`modifies`). /// /// Поддерживаемые формы: /// - `name` — целая variable / receiver (`self` / `acc`). /// - `name.field` — отдельное поле record'а. /// - `arr[i]` — конкретный array-элемент (33.2 partial). /// - `arr[*]` — все элементы array. #[derive(Debug, Clone)] pub enum FrameTarget { /// Whole l-value: `acc`, `self`. Whole(Expr), /// Field of receiver: `acc.balance`, `self.field`. Field { receiver: Expr, field: String, span: Span }, /// Specific array element: `xs[i]`. ArrayElem { array: Expr, index: Expr, span: Span }, /// All elements: `xs[*]`. ArrayAll { array: Expr, span: Span }, } impl FrameTarget { pub fn span(&self) -> Span { match self { FrameTarget::Whole(e) => e.span, FrameTarget::Field { span, .. } | FrameTarget::ArrayElem { span, .. } | FrameTarget::ArrayAll { span, .. } => *span, } } } /// Plan 33.1: вид контракта (`requires` vs `ensures`). #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ContractKind { /// `requires <bool-expr>` — предусловие. Проверяется на входе. /// `result`/`old(...)` запрещены. Requires, /// `ensures <bool-expr>` — постусловие. Проверяется на выходе. /// Доступны `result`, `result.is_ok`/`.is_err`/`.value`/`.error`, /// `old(expr)` для значений до вызова. Ensures, /// D.1.5: `ensures_fail <bool-expr>` — постусловие для Fail-пути. /// `result` недоступен (fn не вернула нормально); `old(x)` доступен. /// SMT-верифицируется независимо; runtime-check не эмитируется (V1). EnsuresFail, } /// Plan 33.1 (D24 §49): режим верификации контрактов функции. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum VerifyMode { /// Без атрибута. SMT пытается доказать; недоказанное в release — /// compile error (R20), пока программист не пометит явно /// `@unverified` или не добавит hint'ов. #[default] Default, /// `@must_verify` — SMT обязан доказать. Unknown → compile error /// даже в debug. MustVerify, /// `@unverified` — отказ от SMT-доказательства заранее. В debug — /// runtime check; в release — контракт стирается (no-op). Unverified, } /// Plan 33.1: чистота функции (для использования в контрактах /// через composition в 33.2 + ghost в 33.3). /// /// Выводится в Ф.2 через SCC по call-graph (как `const fn` в Rust). /// Атрибут `@pure` — assertion программиста; расхождение с выведенным — /// compile error. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum Purity { /// Чистая функция: нет effects в сигнатуре, все вызываемые — /// тоже pure. Можно использовать в контрактах других функций /// (composition — Plan 33.2). Pure, /// Effectful. Использование в контрактах запрещено. Effectful, /// Не определено (до запуска Ф.2 inference). Парсер выставляет /// это значение если `@pure` не указан явно. #[default] Unknown, } /// Plan 16: вид `@realtime` атрибута на функции (D64 §3697). #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum RealtimeAttr { /// Без атрибута. #[default] None, /// `@realtime` — body обёрнут в `realtime { ... }` контекст. Realtime, /// `@realtime nogc` — body обёрнут в `realtime nogc { ... }`. RealtimeNogc, } /// Plan 103.6 / Plan 113: sync interaction class for external fn declarations. /// /// Parsed from `#realtime` / `#parks` / `#wakes` attributes in sync.nv. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum SyncClass { /// Leaf method — no fiber park/wake. Callee guarantee: body is realtime-safe. /// Annotation: `#realtime` (Plan 113 rename from `#realtime_safe`). Realtime, /// May park the calling fiber. Forbidden in #realtime fns and blocking{}. /// Annotation: `#parks`. Parks, /// Wakes other fibers (no self-park). Forbidden in #realtime context /// (scheduler interaction); allowed in blocking{} as leaf operation. /// Annotation: `#wakes`. Wakes, } /// Receiver метода. /// /// `fn TypeName @method() ...` — instance-метод (`@` доступ к receiver'у). /// `fn TypeName.static_method() ...` — static-метод (точка). #[derive(Debug, Clone)] pub struct Receiver { pub type_name: String, pub generics: Vec<TypeRef>, // Repo[T] — generics типа /// Bounds declared in carrier brackets: `fn Vec[T Printable] @m()`. /// Stored for future enforcement; currently informational only. pub carrier_bounds: Vec<GenericParam>, /// Plan 153.5 (D263) / [M-153.5-flatten-nested-receiver]: the FULL /// structured receiver type, preserved so the monomorphizer can bind a /// receiver typevar at ANY nesting depth via structural unification. /// /// For a flat receiver (`[]T`, `Vec[T]`, bare `T`) this carries the same /// info `type_name` + `generics` already encode and the legacy flat path /// is used; for a NESTED receiver (`[][]T` → `Array(Array(Named T))`, /// `Vec[Vec[T]]` → `Named{Vec,[Named{Vec,[Named T]}]}`) it is the only /// place the inner nesting survives — `type_name` flattens to `"[][]T"` /// (the slice spelling) and `generics` only records the carrier free /// typevars, neither of which preserves the structural depth. /// `None` for non-generic receivers / when no structured form was built. pub receiver_ty: Option<TypeRef>, pub kind: ReceiverKind, pub mutable: bool, // `fn Type mut @method` /// Plan 73 (D131): `fn Type consume @method` — consuming receiver. /// После вызова такого метода переменная-источник логически /// инвалидируется; use-after-consume → compile error. Взаимно- /// исключающий с `mutable` (parser enforce'ит). pub consume: bool, pub span: Span, } #[derive(Debug, Clone, PartialEq)] pub enum ReceiverKind { Instance, // @ Static, // . } #[derive(Debug, Clone)] pub struct Param { pub name: String, pub ty: TypeRef, pub span: Span, /// Plan 14 Ф.6 (D69): `...name Type` — variadic-параметр. Только /// последний param может быть variadic; тип обязан быть `[]T`. /// Caller'ы могут передать N args (которые collected'ятся в []T) /// или `...arr` (spread в variadic position). pub is_variadic: bool, /// Plan 46 (D102): значение по умолчанию `fn f(x int = expr)`. /// Default-выражение вычисляется **на месте вызова** (не def-time), /// может ссылаться на предшествующие параметры и module-level const. /// Параметры с дефолтом идут строго после параметров без дефолта; /// variadic-параметр НЕ может иметь дефолт. `None` — обязательный. pub default: Option<Expr>, /// Plan 73 (D131): `consume name Type` — consuming параметр. /// После передачи аргумента в такой параметр переменная-источник /// логически инвалидируется; use-after-consume → compile error. pub consume: bool, /// Plan 108.1 (D176 amend): `mut name Type` — параметр позволяет /// вызов mut-методов / index-assignment в callee. Default = false /// (read-only param). Конфликтует с `consume` и `readonly` — /// parser-level error. pub is_mut: bool, /// Plan 114.4.2 (D199): `const name Type` — comptime-only параметр. /// All-or-nothing: если хоть один param `const`, ВСЕ должны быть /// `const` И return должен быть `const`. Конфликтует с `mut`/ /// `consume` (E_CONST_PARAM_MOD_CONFLICT). pub is_const: bool, /// [M-canon-mut-param-position] (2026-07-17): `true` ТОЛЬКО для голой /// legacy-формы `name mut Type` (D6 "mut-маркер после имени") БЕЗ /// предшествующего явного `ro` — т.е. полный синоним канонической /// префиксной `mut name Type` (parser/mod.rs `parse_param`, ветка после /// `parse_ident`). Санкционированный D246 R2-split `ro name mut Type` /// (explicit `ro` L1 + постфиксный `mut` L2, orthogonal, Plan 118.5 V3 /// amend) НЕ отмечается этим флагом — там `has_readonly_prefix` был true /// в момент разбора. Поле существует ИСКЛЮЧИТЕЛЬНО для lint'а /// `W_PARAM_TYPE_POS_MUT` (lints.rs): `is_mut` сам по себе не различает /// префиксную и постфиксную форму (обе схлопываются в `is_mut=true`). /// Default = false (все синтетические/non-parsed конструкторы `Param`). pub mut_type_pos_legacy: bool, // Plan 184 (заход-5, п.7): поле `ref_mode: ParamRefMode` и сам enum удалены. // D326-ревизия (Plan 184) отменила «ref — режим передачи»: `ref` теперь // ОГРАНИЧЕННЫЙ ТИП (`TypeRef::Ref`), а in-out `mut x T` ведётся value-путём // Р10 (`param_is_inout_ptr`/`param_ty_is_inout_value`), не отдельным полем // режима. Формы `ro ref`/`mut ref` в параметре сняты заходом-1. /// Plan 238 Ф.3 (D446 §4/§5 амендмент): `#fiber_safe name Type` — /// explicit annotation on a function-typed parameter, ТОЛЬКО для границ /// без вывода (extern/no-body fn — тело невидимо, автовывод по телу /// невозможен). Значит то же самое, что вывод из тела вычислил бы, если /// бы мог видеть тело: "этот параметр требует Safe-замыкания". Parsed /// контекстно после `#`, тем же путём, что `#cancel_safe`/`#coerce` /// (`parser/mod.rs`'s `parse_cancel_safe_attr` — образец). НЕ читается /// для fn с телом — там требование ВЫВОДИТСЯ (`fiber_safety.rs`'s /// `compute_required_params`), явная пометка была бы избыточна и могла /// разойтись с реальным телом; парсится безусловно (не ошибка на fn с /// телом), проверяется только там, где вывод НЕ может сработать. /// Default = false (все синтетические/non-parsed конструкторы `Param`). pub fiber_safe_attr: bool, } /// Plan 15 (D72): generic-параметр с optional bound. /// /// `[T]` — `GenericParam { name: "T", bounds: [] }`. /// `[T Hashable]` — `GenericParam { name: "T", bounds: [Hashable_TypeRef] }`. /// /// Plan 101.3 (D145 Ред. 5): multi-bound `[T A + B + C]` — /// `bounds: [A, B, C]`. Type T должен удовлетворить ВСЕМ bound'ам /// (conjunction / intersection). Семантически equivalent протоколу /// `protocol { use A use B use C }` (см. Plan 101.4), но без /// дополнительной декларации. /// /// Bound — это protocol-тип ([D72](spec/decisions/02-types.md#d72)). /// Запрещены forward-references: имя в bound должно быть объявлено /// раньше в том же `[...]` или в окружающем type-context. #[derive(Debug, Clone)] pub struct GenericParam { pub name: String, /// Plan 101.3: список bound'ов (conjunction). Пустой = unbounded. /// Один = `[T Bound]` (D72 legacy). Несколько = `[T A + B]`. pub bounds: Vec<TypeRef>, /// Plan 19 C10 (D88): default-значение generic-параметра. /// Используется когда вызывающий код не указал T явно и /// inference из аргументов не дал результат. Для `[T = f64]` /// или `[T Bound = Default]`. pub default: Option<TypeRef>, pub span: Span, /// Plan 100.2 (D156): `[T consume]` — strict-mode consume bound. /// Внутри тела функции с этим bound'ом, T-typed значения трактуются /// как consume-obligations (must be consumed before scope-exit). /// Backward-compat: без bound — silent-ignore (D133 default). pub consume_bound: bool, } impl GenericParam { /// Helper для legacy кода: если bound не нужен. pub fn unbounded(name: String, span: Span) -> Self { Self { name, bounds: Vec::new(), default: None, span, consume_bound: false } } /// Plan 101.3 helper: первый bound (legacy single-bound API). /// Используется для backward-compat с кодом, который пока работает /// только с первым bound (codegen mono-dispatch, doc render и пр.). /// Когда multi-bound enforcement распространится, callers переедут /// на `bounds()` итерацию. pub fn first_bound(&self) -> Option<&TypeRef> { self.bounds.first() } } #[derive(Debug, Clone)] pub enum FnBody { /// `=> expr` Expr(Expr), /// `{ stmts; ...; expr? }` Block(Block), /// D82: `external fn` — body отсутствует, реализация в nova_rt. External, } // Plan 123 baseline-fix (2026-06-02): minimal Default impl for AST nodes // used by test fixtures. Default body = External (no Block/Expr to construct). // Production code never relies on Default for FnBody. impl Default for FnBody { fn default() -> Self { FnBody::External } } /// Plan 52 Ф.1: атрибут-маркер на декларации типа (`#from_fields`). /// /// Маркер `#from_fields` помечает str-keyed map-тип, в который анонимный /// record-литерал `{field: v}` коэрсится через D55 map-coercion (имена /// полей становятся строковыми ключами). Bootstrap honored только для /// `collections.hashmap.HashMap` — type-checker дополнительно сверяет /// canonical identity, чтобы shadowing локальным `HashMap` не ломал /// правило. Точка расширения (`OrderedMap`, `BTreeMap`) — позже. #[derive(Debug, Clone, PartialEq, Eq)] pub enum TypeAttr { /// `#from_fields` — str-keyed map-тип для D55 map-coercion (D108 / Plan 52). FromFields, /// Plan 52 Ф.23: `#from_pairs` — тип desugar'а map-литерала `[k: v]`. /// Если expected type помечен `#from_pairs`, десугаринг вызывает /// `ExpectedType.new()` + `.cap(n)` + `insert_new(k, v)` per pair /// вместо хардкода `HashMap` (D372 amend 2026-07-06: `with_capacity` /// removed, заменён на `new()` + D117 setter `cap(n)`). Тип должен /// предоставить методы `static new() -> Self`, `mut cap(n int) -> @` /// и `mut insert_new(K, V)`. /// Stdlib HashMap имеет оба attribute. User-типы получают /// расширяемость без модификации компилятора. FromPairs, /// Plan 173.3 (D415): `#share` — audited vouch that a type is safe to /// alias from another fiber (data-race-freedom, one-axis model: share; /// move is covered by `consume`). Bare marker (no args), like /// `#zero_on_move`. Ordinary types need NEVER write this — share-ness /// is auto-derived memberwise (all fields share ⇒ type share, mirroring /// Plan 126 auto-derive). `#share` is needed ONLY to override the /// auto-derived `false` for a type that transitively embeds the poison /// base (a raw `*T` pointer field) but is internally synchronized /// (`Mutex`/`RwLock`/`Atomic*`/channels, or a user lock-free type) — /// the author vouches the auto-inference can't see the real /// synchronization (analogous to Rust `unsafe impl Sync`). NOT a /// protocol (D415 §0 — an empty-marker protocol would be trivially /// true for every type); a plain type-attribute like `#impl`/`#forbid`. Share, } /// Plan 180 Ф.6 (D382) + 180.1 Ф.1/Ф.7 (D-амендмент field-attrs): один /// аргумент `#serde(...)`-аннотации. Общая key/value-грамматика (`key`, /// `key="s"`) разбирается парсером в этот структурированный список /// (`serde_attrs` на TypeDecl / SumVariant / RecordField). Позиционная /// валидность (какой ключ на типе vs на поле) проверяется НЕ парсером, а /// consume-стороной (`compiler-codegen/src/protocols/auto_derive.rs`) — /// тот же принцип, что `E_SERDE_TAGGING_ON_NON_SUM` для `tag`/`content`. /// /// **Потреблено (180.1 Ф.1):** `rename`/`rename_all`/`skip`/ /// `skip_serializing_if`/`default`/`alias`/`deny_unknown_fields`(no-op- /// синоним дефолта, см. ниже)/`allow_unknown`. **Гейт:** `flatten` /// парсится, но synth honest-gate'ит (`E_SERDE_FLATTEN_UNSUPPORTED`, /// `[M-180-serde-flatten]`) — самый сложный item, см. 180.1-план. #[derive(Debug, Clone, PartialEq, Eq)] pub enum SerdeArg { /// `tag = "type"` — имя дискриминатор-поля (internally-tagged; с `content` /// — adjacently-tagged). Только на sum-типе. Tag(String), /// `content = "c"` — имя поля с payload'ом (adjacently-tagged; требует /// `tag`). Только на sum-типе. Content(String), /// `untagged` — untagged (try-each-variant). Только на sum-типе. Untagged, /// `rename = "wireName"` — field-level: явное wire-имя, ПЕРЕОПРЕДЕЛЯЕТ /// type-level `rename_all` (180.1 Ф.1.1/Ф.1.2). Rename(String), /// `rename_all = "convention"` — type-level: массовое переименование /// полей по конвенции (180.1 Ф.1.2). Конвенция валидируется парсером /// (E_SERDE_BAD_ATTRIBUTE при неизвестном имени). RenameAll(RenameConvention), /// `skip` — field-level: поле не сериализуется и не читается; при /// десериализации берётся default (zero-value типа или /// `#serde(default = "fn")`, если указан) — 180.1 Ф.1.3. Skip, /// `skip_serializing_if = "predicate"` — field-level: условный пропуск на /// записи, если `@field.<predicate>()` вернул true (180.1 Ф.1.4). SkipSerializingIf(String), /// `default` (bare) / `default = "fn_name"` — field-level: при отсутствии /// поля в wire — zero-value типа (bare) или `fn_name()` (180.1 Ф.1.5). /// `None` = bare `default`; `Some(fn)` = `default = "fn"`. Default(Option<String>), /// `alias = "old_name"` — field-level: доп. wire-имя ТОЛЬКО на чтение; /// повторяем (несколько `alias` на одном поле) — 180.1 Ф.1.6. Alias(String), /// `flatten` — field-level: вложенный record «размазывается» в плоский /// wire. Гейт `[M-180-serde-flatten]` (180.1 Ф.1.8) — синтаксис принят, /// synth honest-errors. Flatten, /// `deny_unknown_fields` — type-level. **180.1 Ф.7-разворот:** unknown- /// field policy стала STRICT BY DEFAULT (было ignore) → этот ключ теперь /// no-op-синоним уже действующего дефолта (принято, не отклоняется, ничего /// не меняет). Оставлен для serde-мышечной памяти. DenyUnknownFields, /// `allow_unknown` — type-level: opt-out строгого-по-умолчанию unknown- /// field policy (180.1 Ф.7) — неизвестные wire-поля молча игнорируются /// (forward-compat API). AllowUnknown, } /// 180.1 Ф.1.2 (Q8 180-plan): `rename_all`-конвенции. Конвертирует /// canonical (Nova snake_case) имя поля в wire-имя. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum RenameConvention { CamelCase, SnakeCase, KebabCase, ScreamingSnakeCase, PascalCase, } impl RenameConvention { /// Парсит `#serde(rename_all = "...")`-строку в конвенцию. `None` — /// неизвестное имя (парсер эмитит E_SERDE_BAD_ATTRIBUTE со списком). pub fn parse(s: &str) -> Option<RenameConvention> { match s { "camelCase" => Some(RenameConvention::CamelCase), "snake_case" => Some(RenameConvention::SnakeCase), "kebab-case" => Some(RenameConvention::KebabCase), "SCREAMING_SNAKE_CASE" => Some(RenameConvention::ScreamingSnakeCase), "PascalCase" => Some(RenameConvention::PascalCase), _ => None, } } /// Применить конвенцию к canonical snake_case-имени поля Nova. pub fn apply(self, field_name: &str) -> String { let words: Vec<&str> = field_name.split('_').filter(|s| !s.is_empty()).collect(); if words.is_empty() { return field_name.to_string(); } let cap = |w: &str| -> String { let mut c = w.chars(); match c.next() { Some(f) => f.to_uppercase().collect::<String>() + c.as_str(), None => String::new(), } }; match self { RenameConvention::SnakeCase => field_name.to_string(), RenameConvention::CamelCase => { let mut out = String::new(); for (i, w) in words.iter().enumerate() { if i == 0 { out.push_str(w); } else { out.push_str(&cap(w)); } } out } RenameConvention::PascalCase => words.iter().map(|w| cap(w)).collect::<Vec<_>>().join(""), RenameConvention::KebabCase => words.join("-"), RenameConvention::ScreamingSnakeCase => words.iter().map(|w| w.to_uppercase()).collect::<Vec<_>>().join("_"), } } } /// Plan 180 Ф.6 (D382 / D345): режим тегирования sum-типа для serde. Выводится /// из type-level `serde_attrs` через [`sum_tagging_mode`]. `External` — default /// (без атрибута), поведение Ф.2-sum не меняется. #[derive(Debug, Clone, PartialEq, Eq, Default)] pub enum SerdeTagging { /// `{"Variant": payload}` (default, Q4). #[default] External, /// `#serde(tag="k")` — `{"k":"Variant", ...payload-fields}`. Internal { tag: String }, /// `#serde(tag="t", content="c")` — `{"t":"Variant","c":payload}`. Adjacent { tag: String, content: String }, /// `#serde(untagged)` — payload напрямую, вариант выбирается try-each. Untagged, } /// Plan 124.8 (D226 NEW) + Plan 127 V1 (D228 amend): allocation contract. /// /// **Type-level** (TypeDecl.allocation, parser-set): /// - `Heap` — `type X { ... }` обычный GC-managed reference type (default, /// backward compat). C ABI: `Nova_X*`. /// - `Value` — `type X value { ... }` stack-allocated value type с copy /// semantics на pass / inline storage в коллекциях. C ABI: `NovaValue_X` /// (inline struct). /// /// **Per-binding-level** (escape-analysis-set, Plan 127): /// - `ValueHeapPromoted` — местный binding объявлен на value-record, но /// escape analysis обнаружил escape (`&v` returned, captured, stored в /// heap field, etc.) → binding promoted на heap. C ABI: `Nova_X*` (как /// heap-record). The underlying type is STILL `AllocKind::Value` в его /// TypeDecl — promotion живёт только на binding site (Local / Field /// alloc_kind), не на type decl. /// /// **Invariant:** TypeDecl.allocation ∈ {Heap, Value}. ValueHeapPromoted /// валиден ТОЛЬКО на per-binding alloc_kind slots. Asserted via /// `as_type_decl_kind()` helper. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)] pub enum AllocKind { #[default] Heap, Value, /// Plan 127 V1: value-record binding promoted to heap by escape analysis. /// Valid only on per-binding alloc_kind slots, NEVER on TypeDecl.allocation. ValueHeapPromoted, } impl AllocKind { /// True если binding/slot аллоцируется на heap (Heap или ValueHeapPromoted). /// `Value` — единственный stack-allocated case. pub fn is_heap(self) -> bool { matches!(self, AllocKind::Heap | AllocKind::ValueHeapPromoted) } /// True если binding/slot — stack-allocated inline value. pub fn is_stack_value(self) -> bool { matches!(self, AllocKind::Value) } /// True если promotion-mode (escape analysis detected escape на /// value-record locale → heap). False для plain Heap / Value. pub fn is_promoted(self) -> bool { matches!(self, AllocKind::ValueHeapPromoted) } /// C type prefix для record-type'а given allocation mode и type name. /// - `Heap` → `Nova_<name>` (heap-record struct, used as `Nova_<name>*`) /// - `Value` → `NovaValue_<name>` (stack inline struct) /// - `ValueHeapPromoted` → `Nova_<name>` (promoted к heap layout — same /// bit-pattern as Heap; underlying struct name = `Nova_<name>` per /// Plan 127 Ф.3 codegen contract). /// /// NOTE Plan 127 Ф.3: emit_value_record_type generates только /// `NovaValue_<name>` struct. For promotion to work, codegen must /// also emit `Nova_<name>` heap mirror — OR reuse NovaValue layout /// и cast (V1 simpler: heap-promoted уses `NovaValue_<name>*` /// dynamically-allocated). Final decision lives в emit_value_record_lit. pub fn c_type_name(self, type_name: &str) -> String { match self { AllocKind::Heap => format!("Nova_{}", type_name), AllocKind::Value => format!("NovaValue_{}", type_name), // Plan 127 V1: promoted value-record uses NovaValue_X* (pointer to // heap-allocated NovaValue_X struct) — reuses existing layout, // no separate Nova_<X> heap mirror struct needed. AllocKind::ValueHeapPromoted => format!("NovaValue_{}", type_name), } } /// True если binding requires pointer indirection в C-uses /// (field access `->`, ABI as pointer, etc.). /// - `Heap` → true (`Nova_X*` ABI) /// - `Value` → false (`NovaValue_X` ABI — inline value) /// - `ValueHeapPromoted` → true (`NovaValue_X*` ABI — heap pointer) pub fn uses_pointer_abi(self) -> bool { matches!(self, AllocKind::Heap | AllocKind::ValueHeapPromoted) } } impl std::fmt::Display for AllocKind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let s = match self { AllocKind::Heap => "heap", AllocKind::Value => "value", AllocKind::ValueHeapPromoted => "value-heap-promoted", }; f.write_str(s) } } /// Plan 160 (D281): field-level default visibility for a type declaration. /// `Public` — fields default to public (D47 behaviour; no modifier keyword). /// `Module` — `priv` modifier (no qualifier): fields are visible within the /// same module (folder) but private outside (D281 §1 new design). /// `Private` — `priv(type)` modifier: fields are type-private only (accessible /// only from methods of the declaring type itself); explicit `pub` /// field modifier overrides to public (D220). /// Note: `priv(module)` is no longer valid — parser emits E_PRIV_QUALIFIER. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum FieldDefaultVisibility { Public, Module, Private, } impl Default for FieldDefaultVisibility { fn default() -> Self { FieldDefaultVisibility::Public } } /// Plan 123 baseline-fix (2026-06-02): `Default` derive — see FnDecl. #[derive(Debug, Clone, Default)] pub struct TypeDecl { /// Plan 45 / D104: doc-comment перед `type`. pub doc: Option<DocBlock>, /// Plan 45 Ф.3 / D105: doc-атрибуты. pub doc_attrs: Vec<DocAttr>, pub is_export: bool, pub name: String, /// Plan 15 (D72): `[K Hashable, V]` — имена + optional bounds. pub generics: Vec<GenericParam>, pub kind: TypeDeclKind, pub span: Span, /// Plan 114.4.1 (D200): associated constants — `const NAME T = expr` /// внутри `type X { ... }`. НЕ в instance layout; accessible через /// namespace `Type.NAME`. Пустой вектор у типов без assoc consts. pub assoc_consts: Vec<AssocConst>, /// Plan 52 Ф.1: атрибуты-маркеры перед `type` (`#from_fields`). /// Пустой вектор у типов без атрибутов (backward-compat). pub attrs: Vec<TypeAttr>, /// Plan 33.2 Ф.7 (D24): `invariant <expr>` clauses на record-типах. /// Проверяются runtime (debug): после record-литерала, после mut field /// assignments, на выходе mut-методов. SMT-verify в 33.3. /// Пустой вектор у типов без invariants (backward-compat). pub invariants: Vec<Contract>, /// Plan 33.3 Ф.9 (D24): `axiom <name>(binders) => <formula>` — /// global formulas про `pure_view` ops эффекта. Применимо только к /// `TypeDeclKind::Effect`; на других типах пустой Vec. /// При SMT verify добавляются как глобальные `assert` в любом /// scope где эффект импортирован. pub axioms: Vec<EffectAxiom>, /// Plan 100.1 (D133 / D1): `type X consume { ... }` — type-level /// must-be-consumed marker. Каждый instance такого типа обязан /// быть consumed до scope-exit (через consume-method, return, /// consume-param, record-field-move, или defer). /// Backward-compat: default false. pub consume: bool, /// Plan 124 (D220) / Plan 160 (D281): type-level default field visibility. /// `Public` — no modifier, fields default = pub (D47 unchanged). /// `Private` — `priv` modifier, fields default = type-private; `pub` field /// overrides (D220). /// `Module` — `priv(module)` modifier, fields visible within same module /// (folder) but private outside (D281 §1). /// Backward-compat: default Public. pub field_default_visibility: FieldDefaultVisibility, /// Plan 124.8 (D226 NEW): allocation contract for record types. /// `Heap` (default) — `type X { ... }` GC-managed reference type. /// `Value` — `type X value { ... }` stack-allocated value type /// (copy semantics при передаче, inline в массивах и parent structs). /// Применимо только к `TypeDeclKind::Record` — для других форм /// (NamedTuple, Newtype, Sum, etc.) allocation захардкожена их kind'ом. /// Backward-compat: default Heap (existing records unchanged). pub allocation: AllocKind, /// Plan 91.9 (D186): `#impl(P1 + P2 + ...)` annotation list. /// Names of protocols the type explicitly opts into. Verification: /// compiler checks T provides every method of each P (via explicit /// methods OR synthesizable default body, per Plan 91.8a.2). /// Gating: bare-call protocol-method synthesis (`u.greet()` without /// `[T Greetable]` bound or `let g Greetable = u` coercion) ONLY /// fires когда the type is opted-in via `#impl`. Without `#impl`, /// bare call к default-body-synthesized method gives E7320 (no method). /// Empty Vec — default (no opt-in). pub impl_protocols: Vec<String>, /// Plan 124.8 [M-124.8-zero-on-move] (2026-06-03): opt-in security /// attribute — `#zero_on_move type Secret { ... }`. На consume такого /// type'а codegen emits `memset(source, 0, sizeof)` для затирания /// исходной ячейки. Защищает секреты (ключи, токены, пароли) от /// утечки через core-dump / stack-scan / memory disclosure. /// Применимо к Record (heap и value), NamedTuple, Newtype. Для типов /// не участвующих в consume-flow — no-op (flag сохраняется). /// Backward-compat: default false. pub zero_on_move: bool, /// Plan 248 (mech, wave 1, p248-mech): `#no_copy type X { ... }` — /// type-level affine marker (D133 таблица «искомое»: значение нельзя /// связать вторым именем, но забыть — можно, в отличие от `consume` /// не требует расхода на каждом exit-пути). Checker-only признак — /// codegen (`emit_c.rs`) его не читает. Wave 1 строит только /// парсер/AST/реестр-с-уровнем/транзитивность; enforcement /// (запрет второго имени) — отдельная волна (p248-*), намеренно /// не подключена здесь (поведенческая нейтральность волны 1). /// Backward-compat: default false. pub no_copy: bool, /// Plan 124.6 (D225): `#pub_to(TypeA, TypeB, ...)` — selective friend visibility. /// The type's private fields are readable from the bodies of the listed types /// (as if those types were in the same module). Empty Vec = no extra grants /// (backward-compat default). pub pub_to: Vec<String>, /// Plan 170 (D307): `priv(file) type` — file-private visibility. Тип /// помечен «не виден peer-файлам модуля» (visibility-hint; модуль один, /// D29). Лесенка: `priv(file)` ⊂ module-default ⊂ `export`. Взаимоисключаем /// с `is_export`. Ортогонален `field_default_visibility` (видимость самого /// типа vs дефолтная видимость его полей). Default `false`. pub file_private: bool, /// Plan 180 Ф.6 (D382): `#serde(...)` declaration-level attributes. Used /// for serde sum-tagging mode (`tag`/`content`/`untagged`, D345). Empty Vec /// = default (external tagging / no serde customization). See [`SerdeArg`]. pub serde_attrs: Vec<SerdeArg>, } #[derive(Debug, Clone)] pub enum TypeDeclKind { /// `type Name { fields }` Record(Vec<RecordField>), /// `type Name | A | B(int) | C { x int }` (D52) Sum(Vec<SumVariant>), /// `type Name effect { signatures }` — capability с runtime /// vtable + handler-dispatch. Использование в позиции эффекта /// `(...) Eff -> ...` или через handler stack. Effect(Vec<EffectMethod>), /// `type Name protocol { signatures }` — структурный контракт /// (D53). Compile-time проверка satisfaction; нет runtime vtable. /// Используется как: /// - bound в generic params `[T Protocol]` (D72); /// - тип значения (existential), `fn f(x Hashable) -> ...`. /// /// Plan 101.4 (D145 Ред. 5): protocol composition через `use P` items /// в теле — `type ReadWriter protocol { use Reader use Writer }`. /// `embeds` — список TypeRef'ов (всегда Named-форм с указателем на /// другой `TypeDeclKind::Protocol`). Type-checker flatten'ит embeds: /// все методы embedded protocol'а считаются методами outer'а /// (через `flatten_protocol_methods`). При duplicate signatures — /// ошибка `[E_PROTOCOL_EMBED_DUPLICATE]`; при non-protocol target — /// `[E_PROTOCOL_EMBED_NOT_PROTOCOL]`; при cycle — `[E_PROTOCOL_EMBED_CYCLE]`. Protocol { methods: Vec<EffectMethod>, /// Embedded protocols (`use Reader`, `use Writer`). Empty для /// плоских (не-composed) protocol'ов — backward-compat. embeds: Vec<TypeRef>, }, /// `type NewType u64` — newtype (D52) Newtype(TypeRef), /// `type Name alias OtherType` (D52) Alias(TypeRef), /// Plan 172.3 (D310): `type Name set A | B | C` — type-set bound. Именованное /// множество КОНКРЕТНЫХ типов, используемое как generic-bound (`fn[T Name] …`). /// Члены — TypeRef по идентичности (примитивы / объявленные типы; без `~underlying`). /// Диспетчеризация по контекстному kind-токену `set` после имени (D52 first-token rule, /// как `alias`/`protocol`) — ноль конфликта с sum-`|` (у sum нет kind-токена). /// Используется как bound (D72 amended); композиция с протоколами через `+` (D145). TypeSet(Vec<TypeRef>), /// Plan 120 (D215): `type Name(field1 T1, field2 T2)` — named tuple. /// Stack-allocated value type identical to positional tuple (D123) /// but fields accessed by name (`.x`, `.y`) instead of position (`.0`). NamedTuple(Vec<NamedTupleField>), /// Plan 62.D.bis (D126): `external type X [Generics]` — opaque /// type known to compiler by name, реализация в runtime /// (`nova_rt/<x>.h`/.c). Без body, без variants/fields. Restricted /// to `std.runtime.*` / `std.prelude.*` модулям (whitelist в /// types/mod.rs::check_module параллельно `external fn` per D82). /// Codegen эмитит ссылку как `Nova_<Name>*` (pointer); struct /// определение живёт в runtime header. Opaque, } // Plan 123 baseline-fix (2026-06-02): Default for TypeDeclKind used by // test fixtures (`..Default::default()` spread in TypeDecl literals). // Empty record is the most universally inert default. impl Default for TypeDeclKind { fn default() -> Self { TypeDeclKind::Record(Vec::new()) } } /// Plan 114.4.1 (D200): associated constant — `const NAME T = expr` inside /// `type X { ... }` body. НЕ в instance layout (zero storage); accessible /// через namespace `Type.NAME`. Codegen emit'ит как top-level /// `static const T Type_NAME = literal;` в .rodata. #[derive(Debug, Clone)] pub struct AssocConst { pub name: String, pub ty: Option<TypeRef>, pub value: Expr, pub span: Span, /// `export const FOO …` — public cross-module access. pub is_export: bool, /// Plan 157 (D200 amend): `true` when this entry originated from /// `ro Type.NAME [Type] = expr` (associated **ro**-value) rather than /// `const Type.NAME`. A `ro` associated value is NOT required to be /// constexpr (constructor calls / heap allocation are legal) and is /// codegen'd via the module-level `ro` lazy-static-global machine /// (`emit_lazy_const`, Plan 152.4) instead of the strict-constexpr /// `.rodata` literal path. Namespace-only access / instance-access /// rejection / no-record-literal-field rules are shared unchanged with /// `const` (both live in the same `assoc_consts` list). Default `false`. pub is_lazy_ro: bool, } /// Plan 120 (D215): field in a named tuple type declaration. #[derive(Debug, Clone)] pub struct NamedTupleField { pub name: String, pub ty: TypeRef, pub span: Span, /// Plan 124.4 (D222): per-field private visibility marker для /// named tuple. Same semantics as RecordField.priv_field: /// accessible only из methods own type'а. /// Backward-compat: default false (= public). pub priv_field: bool, /// Plan 160 (D281): true when `priv_field` was inherited from the /// `priv` (no qualifier) type-level default (module-private, NOT an /// explicit `priv` field modifier or `priv(type)` type-level default). /// Checker uses this to emit E_FIELD_MODULE_PRIVATE (module-boundary) /// instead of E_PRIV_FIELD_READ (type-boundary) и allow same-module access. /// Backward-compat: default false. pub priv_module_field: bool, /// Plan 124.6 (D225): `#visible_to(OtherType[, ...])` field-level /// friend declaration. Same semantics как RecordField.visible_to. /// Backward-compat: default empty Vec. pub visible_to: Vec<String>, /// D215 amend (Plan 91.8b follow-up): optional default value `= expr`. /// When `Some`, the field may be omitted at the constructor call site; /// the default expression is emitted in place of the missing argument. /// Fields with defaults must come after fields without defaults. /// Backward-compat: default None (= required field). pub default: Option<Expr>, } /// Plan 123 baseline-fix (2026-06-02): `Default` derive — see FnDecl. #[derive(Debug, Clone, Default)] pub struct RecordField { pub name: String, pub ty: TypeRef, pub readonly: bool, pub mutable: bool, /// D39 / Plan 11 Ф.9: `use name Type` (named embed) или /// `use _ Type` (anonymous embed). Auto-proxy методы генерируются /// codegen'ом; override-precedence: own > delegated. /// Для anonymous embed `name` устанавливается в синтетический /// `__embed_<TypeName>` (доступ только через auto-proxy, не через /// `@<name>.method`). pub is_embed: bool, /// Если true — embed был объявлен как `use _ Type` (без alias); /// `name` — синтетический. Используется для multi-anonymous detection. pub embed_anonymous: bool, pub span: Span, /// Plan 100.1 (D133 / D4): `consume field T` — field хранит /// consume-typed значение. Type-decl с таким полем ДОЛЖЕН быть /// сам объявлен `consume` (compile error D133-type-marker-missing /// иначе). Field-type должен сам быть consume-типом (W /// D133-marker-on-non-consume иначе). /// Backward-compat: default false. pub consume: bool, /// Plan 124 (D220): per-field private visibility marker. /// Если `true` — field accessible только из методов own type'а /// (instance + static). Cross-type access outside scope даёт: /// - Read: E_PRIV_FIELD_READ /// - Write: E_PRIV_FIELD_WRITE /// - Record literal init: E_PRIV_FIELD_INIT /// - Pattern destructure: E_PRIV_FIELD_PATTERN /// /// Effective visibility = explicit `priv` modifier /// OR inherited from `TypeDecl::field_default_visibility` /// OR (default) public. /// Explicit `pub` modifier overrides type-level priv default /// (priv_field stays false). /// Backward-compat: default false (= public; D47 MVP unchanged). pub priv_field: bool, /// Plan 160 (D281): true when `priv_field` was inherited from the /// `priv` (no qualifier) type-level default (module-private, NOT an /// explicit `priv` field modifier or `priv(type)` type-level default). /// Checker uses this to emit E_FIELD_MODULE_PRIVATE (module-boundary) /// instead of E_PRIV_FIELD_READ (type-boundary) и allow same-module access. /// Backward-compat: default false. pub priv_module_field: bool, /// Plan 124.6 (D225): `#visible_to(OtherType[, ...])` field-level /// attribute — explicit friend declaration. Methods of listed /// types ALSO get priv access (besides own type). Empty = strict /// type-only access (default). /// Backward-compat: default empty Vec. pub visible_to: Vec<String>, /// Plan 180 Ф.6 (D382) + 180.1 Ф.1: `#serde(...)` field-level attributes. /// Empty Vec = default (no serde customization). See [`SerdeArg`]. /// Consumed by the record-field auto-derive synth (`auto_derive.rs`) — /// `rename`/`skip`/`skip_serializing_if`/`default`/`alias` landed; NOT /// consumed for `SumVariantKind::Record` payload fields yet (sum-serde /// rich synth is a separate gate, `[M-126-sum-*-rich]`). pub serde_attrs: Vec<SerdeArg>, } #[derive(Debug, Clone)] pub struct SumVariant { pub name: String, pub kind: SumVariantKind, pub discriminant: Option<i64>, pub span: Span, /// Plan 180 Ф.6 (D382): `#serde(...)` variant-level attributes. Empty Vec = /// default. See [`SerdeArg`]. Variant-level serde keys (distinct from a /// Record-variant's PAYLOAD field attrs above) remain unconsumed — /// out of 180.1 Ф.1 scope (record types only). pub serde_attrs: Vec<SerdeArg>, } #[derive(Debug, Clone)] pub enum SumVariantKind { Unit, /// `Some(T)` — позиционный variant с одним полем Tuple(Vec<TypeRef>), /// `Cons { head T, tail List[T] }` — record-variant Record(Vec<RecordField>), } #[derive(Debug, Clone)] pub struct EffectMethod { pub name: String, /// Plan 15 (D72): generic-параметры на effect/protocol method. pub generics: Vec<GenericParam>, pub params: Vec<Param>, pub effects: Vec<TypeRef>, pub return_type: Option<TypeRef>, pub span: Span, /// Plan 33.3 Ф.9 (D24): kind операции — Operation (default, /// действие через handler) vs PureView (наблюдение состояния). /// PureView-методы: /// - ВНУТРИ effect-блока объявляются как `pure_view name(args) -> R`. /// - Не имеют side-effects, не вызываются как handler-actions. /// - Используются в контрактах функций (если эффект в сигнатуре). /// - SMT кодируются как uninterpreted functions (UF). /// - Backward-compat: для protocol-методов всегда Operation. pub kind: EffectOpKind, /// Plan 33.5 Ф.5.1: контракты метода эффекта (requires/ensures). /// Используются в Ф.5.2 для верификации Liskov-подобия handler'ов. /// Пустые = нет spec-контрактов (handler принимается без проверки). pub contracts: Vec<Contract>, /// Plan 97 (D58 amend, `Q-static-method-protocol` resolved): метод /// объявлен как **статический** в protocol-теле через leading-точку /// (`.method(...)`); реализация ожидается через D35 `fn Type.method`. /// Для bare-имён (`method(...)`) — `false` (instance, backwards-compat). /// Для effect-методов всегда `false` (у эффектов нет static — это /// handler-actions через runtime stack). Hard-enforcement static↔ /// instance mismatch — followup (см. plan97/Ф.0.4). pub is_static: bool, /// Plan 108.4 Ф.1 (D175 amend): receiver mutability qualifier в /// protocol-method-declaration. Параллельно с `Receiver.mutable` в FnDecl. /// /// Синтаксис в protocol-теле: /// `@method(...)` — read-only receiver (default, `receiver_mut = false`) /// `mut @method(...)` — mutable receiver /// /// Для effect-методов всегда `false` (у эффектов нет receiver). pub receiver_mut: bool, /// Plan 108.4 Ф.1 (D175 amend): consuming receiver qualifier в /// protocol-method-declaration. Параллельно с `Receiver.consume` в FnDecl. /// /// Синтаксис в protocol-теле: /// `consume @method(...)` — consuming receiver (ownership transfer) /// /// Взаимоисключающий с `receiver_mut`. Для effect-методов всегда `false`. pub receiver_consume: bool, /// Plan 91.8a (D183): default body для protocol method. `None` = /// abstract method (тип-implementer ОБЯЗАН реализовать). `Some(body)` = /// default — implementer может override; если не задал явно, codegen /// синтезирует function из default body (substituting Self → impl-type). /// Для effect-методов всегда None (default bodies применимы только к /// protocols). pub default_body: Option<Block>, } /// Plan 33.3 Ф.9 (D24): Operation vs PureView для effect-метода. #[derive(Debug, Clone, PartialEq, Eq)] pub enum EffectOpKind { /// Обычная operation — side-effecting, dispatch через handler. Operation, /// `pure_view` — read-only observation эффект-state'а. Не вызывается /// напрямую; используется в контрактах. SMT-side — uninterpreted /// function; semantics задаётся через `axiom <expr>`. PureView, } /// Plan 33.3 Ф.9 (D24): глобальная формула про pure_view-ops эффекта. /// /// Пример: /// ```nova /// type Db effect { /// SetBalance(id AccountId, x money) /// pure_view balance(id AccountId) -> money /// axiom non_negative(id) => balance(id) >= 0 /// axiom after_set(id, x) => /// post(SetBalance(id, x))(balance(id)) == x /// } /// ``` /// /// Plan 33.4 P1-5: вид binder'а в EffectAxiom. /// Различает три семантически разных состояния вместо Option<TypeRef>: /// - Untyped: `axiom foo(id)` — тип не аннотирован, выводится из usage /// - Typed: `axiom foo(id int)` — конкретный тип /// - Generic: `axiom foo[T](id T)` — ссылка на generic param по имени #[derive(Debug, Clone)] pub enum BinderType { /// Тип не аннотирован — inference из usage. Untyped, /// Конкретный аннотированный тип: `int`, `str`, named type и т.п. Typed(TypeRef), /// Ссылка на generic-параметр аксиомы: `axiom foo[T](id T)` → Generic("T"). Generic(String), } /// Plan 33.4 P1-5: параметр (binder) аксиомы с типом. #[derive(Debug, Clone)] pub struct BinderDef { pub name: String, pub kind: BinderType, pub span: Span, } impl BinderDef { /// Если binder типизирован (не generic и не untyped), вернуть TypeRef. pub fn typed_ref(&self) -> Option<&TypeRef> { match &self.kind { BinderType::Typed(t) => Some(t), _ => None, } } /// True если binder ссылается на generic-параметр аксиомы. pub fn is_generic(&self) -> bool { matches!(&self.kind, BinderType::Generic(_)) } } /// `binders` — параметры формулы (свободные переменные). /// `formula` обязана быть `bool`-выражением; видны binders + все /// pure_view ops эффекта. #[derive(Debug, Clone)] pub struct EffectAxiom { pub name: String, /// Generic-параметры: `axiom foo[T](id T) => ...` → `generics = [T]`. /// V1: парсинг + AST; SMT encoding generic axioms — V2. pub generics: Vec<GenericParam>, /// Plan 33.4 P1-5: параметры формулы с типами через BinderDef. /// Заменяет Vec<(String, Option<TypeRef>)>. pub binders: Vec<BinderDef>, pub formula: Expr, pub span: Span, } #[derive(Debug, Clone)] pub struct LetDecl { pub mutable: bool, pub pattern: Pattern, pub ty: Option<TypeRef>, pub value: Expr, pub span: Span, /// Plan 33.3 (D24): `ghost let` / `ghost var` — spec-only binding. /// Видим в `requires`/`ensures`/`invariant` и других `ghost`-stmts, /// но НЕ emit'ится в codegen (паритет с Dafny). /// Backward-compat: default false. pub is_ghost: bool, /// Plan 100.1 (D133 / D9): `consume tx = expr` — explicit binding /// для Live-linear ownership. Обязателен для consume-типов /// (compile error D133-consume-needs-keyword иначе). Без него /// `let tx = …` для consume-rvalue = parse-accepted, type-check /// rejects. /// Backward-compat: default false. pub consume: bool, } #[derive(Debug, Clone)] pub struct ConstDecl { /// Plan 45 / D104: doc-comment перед `const`. pub doc: Option<DocBlock>, /// Plan 45 Ф.3 / D105: doc-атрибуты. pub doc_attrs: Vec<DocAttr>, pub is_export: bool, pub name: String, pub ty: Option<TypeRef>, pub value: Expr, pub span: Span, /// Plan 170 (D307): `priv(file) const` — file-private visibility. /// Константа не видна peer-файлам модуля (visibility-hint; модуль один, /// D29). Лесенка: `priv(file)` ⊂ module-default ⊂ `export`. Взаимоисключаема /// с `is_export`. Default `false`. pub file_private: bool, /// Plan 157 (D200 amend): `true` when this decl was parsed from the /// qualified `ro Type.NAME [Type] = expr` form (associated **ro**-value, /// not `const`). Set by `parser::parse_assoc_ro_decl`; carried into the /// matching `AssocConst.is_lazy_ro` by `imports::attach_out_of_body_ /// assoc_consts`. `false` for every genuine `const` (including the /// out-of-body `const Type.NAME` form parsed by `parse_const_decl`) and /// for the synthetic `ConstDecl` `types/mod.rs` builds to register a /// bare module-level `ro NAME = expr` binder for name-resolution. pub is_lazy_ro: bool, } #[derive(Debug, Clone)] pub struct TestDecl { pub name: String, pub body: Block, pub span: Span, /// Plan 124.6 (D225): `#test_access(TypeA, TypeB, ...)` before a `test` block. /// The test body gets private-field read access to the listed types, /// mirroring the same attribute on `fn` (but applied at the item level /// so it doesn't require wrapping the test body in a helper fn). pub test_access: Vec<String>, /// Plan 173 Ф.6 (D348): `test "имя" panics "паттерн" { … }` — /// контекстное KW `panics` (как `raw`/`bench`) инвертирует PASS/FAIL: /// PASS ⇔ тело запаниковало (PANIC-класс D13) сообщением ⊇ паттерн /// (substring, как D89). `None` = обычный тест. Пустая строка = /// «любая паника». pub panics: Option<String>, } /// Plan 57: benchmark declaration. /// /// Three forms: /// 1. Plain: `bench "name" { setup; measure { body } teardown }` /// 2. Parameterized: `bench "name" (n in [v1, v2, ...]) { setup; measure { body } teardown }` /// 3. Grouped (57.B.5): `bench "name" { group "g1" { case "c1" { ... } case "c2" { ... } } ... }` /// /// Fields: /// - `setup`, `measure_body`, `teardown` — для плоских benches (формы 1, 2). /// - `params` — sweep param для form 2 (Plan 57.B.3). /// - `groups` — для form 3 (Plan 57.B.5). Если непустой, поля setup/ /// measure_body/teardown игнорируются; каждый case даёт отдельную /// entry с name `<bench>/<group>/<case>`. #[derive(Debug, Clone)] pub struct BenchDecl { pub name: String, pub setup: Vec<Stmt>, pub measure_body: Block, pub teardown: Vec<Stmt>, /// Plan 57.B.3: parameter sweep — `(name in [v1, v2, ...])`. pub params: Option<BenchParams>, /// Plan 57.B.5: sub-benchmarks via `group "..." { case "..." { ... } }`. pub groups: Vec<BenchGroup>, pub span: Span, } /// Plan 57.B.3: parameterized sweep — `bench "name" (n in [10, 100, 1000]) { ... }`. #[derive(Debug, Clone)] pub struct BenchParams { pub var_name: String, pub values: Vec<i64>, pub span: Span, } /// Plan 57.B.5: bench group — collection of cases sharing a logical context. #[derive(Debug, Clone)] pub struct BenchGroup { pub name: String, pub cases: Vec<BenchCase>, pub span: Span, } /// Plan 57.B.5: single case within a bench group. #[derive(Debug, Clone)] pub struct BenchCase { pub name: String, pub setup: Vec<Stmt>, pub measure_body: Block, pub teardown: Vec<Stmt>, pub span: Span, } // **Plan 138.5 / D216 V2/V3 simplification (2026-06-11):** the // `ModifierClass` enum (§V3.4 redundancy classes) was removed together with // the `contains_same_class_in_chain` helper — same-class redundancy is no // longer enforced (prefix pointer modifiers are hard errors; `safe` retired). /// Plan 118 (D216 §1-3, rev-1): pointer modifier in `*T` / `*ro T` / `*mut T` / /// `*unsafe T` family. Default = `Ro` (omitted modifier ≡ `*ro T`). /// /// **Plan 118.5 / D216 V2 amend (2026-06-04):** Decoupled from `TypeRef`. /// Pointer modifiers at AST level migrated to separate wrapper variants /// (`TypeRef::Mut` / `TypeRef::Uninit`) per universal right-binding rule. /// `TypeRef::Pointer` is now pure constructor without modifier. /// /// This enum **remains** as a runtime tag used by `Ty::TypedPtr` (see /// `types/mod.rs`) — bootstrap-minimal Ty collapses Readonly/Mut/Unsafe /// wrappers around a Pointer into a single TypedPtr with this modifier tag. /// The asymmetry is documented: AST = structural (separate wrappers); Ty = /// collapsed semantic (embedded modifier). #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum PointerModifier { /// `*T` ≡ `*ro T` (default) — read-only target typed pointer Ro, /// `*mut T` — writable-target typed pointer Mut, /// `*uninit T` — pointer to a possibly-uninit pointee; deref requires /// an unsafe block. (Plan 138.5: the prefix `uninit * T` outer form is /// retired — only the postfix pointee form `*uninit T` remains. /// §10a rename, Plan 174.5, 2026-07-11: was `*unsafe T` — the surface /// keyword was renamed `unsafe` → `uninit`; this variant also backs the /// legacy fn-pointer shape `*unsafe fn(...)`, which KEEPS the `unsafe` /// spelling at the parser level, D216 §10.) Uninit, } /// Ссылка на тип. Для bootstrap'а — упрощённая структура. #[derive(Debug, Clone)] pub enum TypeRef { /// Простое имя или путь: `int`, `User`, `module.User` Named { path: Vec<String>, generics: Vec<TypeRef>, span: Span, }, /// `[]T` Array(Box<TypeRef>, Span), /// `[N]T` фиксированный массив FixedArray(usize, Box<TypeRef>, Span), /// `(A, B, C)` кортеж Tuple(Vec<TypeRef>, Span), /// `fn(A, B) E1 E2 -> R` — функциональный тип. Эффекты опциональны. /// /// **Plan 174.6 M1 / D353 (2026-07-04):** `extern_abi` — ABI-тег /// fn-указательного ТИПА, параллельный `FnDecl.extern_abi`: /// - `None` → `*fn(...)` / `*unsafe fn(...)` — Nova-ABI captureless /// fn-ptr (Nova-типы в сигнатуре допустимы, D216 §10). /// - `Some("C")` → `*extern "C" fn(...)` — C-ABI fn-ptr (для настоящего /// C-callback: `qsort`-компаратор, libuv-хендлер). Типы /// сигнатуры обязаны быть C-ABI (D282 rule 2), проверка — /// `check_ffi_c_abi_signatures`. Коэрция /// `fn → *extern "C" fn` iff captureless ∧ C-ABI ∧ /// effect-free (D353). /// C-representation обоих тегов идентична (function pointer) → codegen /// не различает; тег живёт только на уровне типа для checker-валидации. Func { params: Vec<TypeRef>, effects: Vec<TypeRef>, return_type: Option<Box<TypeRef>>, /// D353: `Some("C")` для `*extern "C" fn`, `None` для Nova-ABI `*fn`. extern_abi: Option<String>, span: Span, }, /// Plan 97 Ф.2 (D53 §628, D142): анонимный protocol-тип в позиции /// типа — `protocol { method-sig* }`. Используется в: /// - параметре (`fn f(x protocol { close() -> () })`), /// - возвращаемом типе, /// - generic-bound (`fn min[T protocol { @lt(...) -> bool }]`). /// Body — переиспользует парсер named-protocol-тела /// (`parse_effect_methods`); `EffectMethod.is_static` поддерживается. Protocol { methods: Vec<EffectMethod>, span: Span, }, /// `()` unit Unit(Span), /// `ro T` — compile-time immutability modifier (D176, Plan 108). /// Zero runtime overhead: only compile-time check. Forbids mut-methods /// and index writes. `T → ro T` coerce allowed; reverse forbidden. Readonly(Box<TypeRef>, Span), /// **Plan 118.5 / D216 V2 §V2.1 (2026-06-04):** `mut T` — compile-time /// mutability marker as right-binding type wrapper. Semantic-only at /// C-codegen level (no ABI change). Distinct from `ro` (forbids /// mutation) and from binding-level `mut x T` (Plan 108 binding rule); /// here it's the **type** that carries the modifier. /// /// Example (POSTFIX pointee, canonical): `*mut T` → /// `Pointer(Mut(T, span_inner), span_outer)`. (Plan 138.5: the PREFIX /// outer form `mut * T` = `Mut(Pointer(T))` is retired — it is now a /// parse error `E_POINTER_PREFIX_MODIFIER`; the `Mut` wrapper around a /// `Pointer` can no longer be constructed from surface syntax. `Mut` /// still wraps value-T, e.g. `mut x mut T` binding sugar.) Mut(Box<TypeRef>, Span), /// **Plan 118.5 / D216 V2 §V2.3 (2026-06-04); §10a rename (Plan 174.5, /// 2026-07-11):** `uninit T` (was `unsafe T`) — first-class MaybeUninit- /// like type wrapper. Inner T's safety contracts off: /// - init: value may be uninitialized (read without prior write is UB) /// - layout: alignment / size = T's, but caller asserts validity /// - identity: bit-pattern validity caller-asserted /// - aliasing: exclusivity rules off /// Read requires `unsafe { }` wrap (`E_UNSAFE_T_READ_REQUIRES_WRAP`). /// Write safe (transitions к valid). `T → uninit T` implicit widen /// allowed; `uninit T → T` narrow requires `unsafe { }` + explicit cast. /// /// Plan 138.5 (2026-06-11): the PREFIX outer form `uninit * T` = /// `Uninit(Pointer(T))` is RETIRED (parse error `E_POINTER_PREFIX_MODIFIER`). /// Nullable pointers are `Option[*T]` (NPO) only; FFI nullable-uninit is /// `Option[*uninit T]`. The surviving uses of this wrapper: /// - `*uninit T` → `Pointer(Uninit(T))` — valid ptr to possibly-uninit T /// - `uninit T` (value-wrapper, §V2.3) — maybe-uninit value /// /// **§10a rename (Plan 174.5, 2026-07-11):** the surface keyword for /// this variant is `uninit` for DATA T. The variant is ALSO still used, /// unrenamed at the surface, for the legacy fn-pointer composition /// `*unsafe fn(...)` / `*extern "C" unsafe fn(...)` (D216 §10 «unsafe fn /// pointer» — encodes call-requires-unsafe, not possibly-uninit data; /// deliberately NOT renamed, Plan 174.5 §Ф.0). The parser disambiguates /// by keyword + whether the wrapped inner is `TypeRef::Func`: `unsafe` /// is legal here ONLY when inner is `Func`; `uninit` is legal for any /// inner. Both spellings construct this SAME `Uninit` node — there is no /// separate AST variant for the fn-pointer shape. Uninit(Box<TypeRef>, Span), /// Plan 118 (D216 §1-3): typed pointer family `*T`. /// /// **Plan 118.5 / D216 V2 (2026-06-04), Plan 138.5 (2026-06-11):** Pure /// constructor — NO inline modifier field. The pointee modifier is the /// (postfix) inner wrapper; pointer reassignability is a binding concern /// (`let`/`mut` before the name, D36), NOT a type wrapper: /// - `*T` ≡ `*ro T` (default) → `Pointer(T)` / `Pointer(Readonly(T))` /// - `*mut T` → `Pointer(Mut(T))` (writable target) /// - `*unsafe T` → `Pointer(Unsafe(T))` (ptr to possibly-uninit T) /// - `*mut *ro T` → `Pointer(Mut(Pointer(Readonly(T))))` (ptr chain) /// The PREFIX outer forms (`mut * T` / `ro * T` / `unsafe * T`) are /// retired parse errors (`E_POINTER_PREFIX_MODIFIER`). Pointer(Box<TypeRef>, Span), /// **Plan 184 (D326-ревизия, 2026-07-07):** `ref T` — ограниченный /// ссылочный тип (аналог C++ `T&`): непереселяемый алиас на хранилище `T`. /// Легальные позиции (Р1): возврат (`-> ref Self` — де-сахар `-> @` для /// value-типов), локальный алиас (`ro y ref T = x.a.b` / `mut y ref T`), /// тип приёмника (`@` = `ref Self`). ЗАПРЕЩЁН (Р1, → `E_REF_TYPE_POSITION`): /// поля, элементы коллекций, payload сумм, аргумент `Option`, тип-аргументы /// дженериков (`f[ref T]` turbofish/`size_of[ref T]`). Нормализация Р6: для /// кучевого `H` — `ref H ≡ H` (значение уже handle), проверяется ДО /// позиционного запрета, так что `f[ref H]` легален и ≡ `f[H]`. /// C-lowering: `ref T` (value `T`) → `T*` (указатель-алиас); `ref H` (heap) /// → `H` по Р6. Auto-конверсия Р5: `ref T → T` (чтение = разыменование). Ref(Box<TypeRef>, Span), } // Plan 123 baseline-fix (2026-06-02): Default for TypeRef used by test // fixtures. `Unit(dummy span)` is the most inert choice — no fields, no // nested references. Production code uses TypeRef::Named / etc explicitly. impl Default for TypeRef { fn default() -> Self { TypeRef::Unit(Span::default()) } } impl TypeRef { pub fn span(&self) -> Span { match self { TypeRef::Named { span, .. } | TypeRef::Array(_, span) | TypeRef::FixedArray(_, _, span) | TypeRef::Tuple(_, span) | TypeRef::Func { span, .. } | TypeRef::Protocol { span, .. } | TypeRef::Unit(span) | TypeRef::Readonly(_, span) | TypeRef::Mut(_, span) | TypeRef::Uninit(_, span) | TypeRef::Pointer(_, span) | TypeRef::Ref(_, span) => *span, } } /// Returns the inner type if this is `ro T`, otherwise returns `self`. pub fn strip_readonly(&self) -> &TypeRef { match self { TypeRef::Readonly(inner, _) => inner.strip_readonly(), other => other, } } /// **Plan 118.5 / D216 V2 §V2.5 (2026-06-04):** Strip outermost /// type-level modifier wrappers (`Readonly` / `Mut` / `Uninit`) and /// return the inner non-wrapper type. Used for method dispatch lookup /// (e.g. `mut * T` method-resolves on `Pointer(T)`, not on the Mut /// wrapper). pub fn strip_modifiers(&self) -> &TypeRef { match self { TypeRef::Readonly(inner, _) | TypeRef::Mut(inner, _) | TypeRef::Uninit(inner, _) => inner.strip_modifiers(), other => other, } } pub fn is_readonly(&self) -> bool { matches!(self, TypeRef::Readonly(..)) } /// **Plan 118.5 / D216 V2 (2026-06-04):** is this `mut T` wrapper? pub fn is_mut(&self) -> bool { matches!(self, TypeRef::Mut(..)) } /// **Plan 118.5 / D216 V2 §V2.3 (2026-06-04); §10a rename (Plan 174.5):** /// is this `uninit T` wrapper (outermost)? pub fn is_uninit(&self) -> bool { matches!(self, TypeRef::Uninit(..)) } // **Plan 138.5 / D216 V2/V3 simplification (2026-06-11):** the helpers // `contains_unsafe_in_chain` (§V3.2 modifier-order) and // `contains_same_class_in_chain` (§V3.4 redundancy) + the `ModifierClass` // enum they consumed were removed: prefix pointer modifiers are now a // hard error (`E_POINTER_PREFIX_MODIFIER`), the `safe` stopper is retired, // and value-T modifier order/redundancy are no longer enforced (axes // commute on a value-T). The only surviving modifier-conflict rule is // §V3.1 ro+mut on value-T, enforced in `types::check_v3_ro_mut_conflict`. /// **Plan 118.5 / D216 V2 §V2.4 (2026-06-04); §10a rename (Plan 174.5):** /// does this TypeRef contain a Uninit wrapper at the OUTERMOST position /// (before any Pointer)? Used by NPO calculation: if outermost wrapper /// is Uninit around a Pointer, NPO is disabled (null is a valid Some /// value). /// /// Returns true for: `Uninit(*T)`, `Mut(Uninit(*T))`, `Readonly(Uninit(*T))` /// (any outer chain of non-Pointer wrappers ending in Uninit before /// the first Pointer). /// Returns false for: `*T`, `*uninit T` (= `Pointer(Uninit(T))`), /// `mut * T`, `ro * T` (any chain where Pointer is the first non- /// Mut/Ro wrapper encountered). pub fn outer_unsafe_before_pointer(&self) -> bool { match self { TypeRef::Uninit(_, _) => true, TypeRef::Readonly(inner, _) | TypeRef::Mut(inner, _) => inner.outer_unsafe_before_pointer(), _ => false, } } /// Plan 118: returns true if this is a typed pointer `*T` (any modifier /// wrapping). Strips outer Readonly/Mut/Uninit wrappers first. pub fn is_pointer(&self) -> bool { matches!(self.strip_modifiers(), TypeRef::Pointer(..)) } /// **Plan 138.5 / D216 V2/V3 simplification (2026-06-11); §10a rename /// (Plan 174.5):** does the OUTER modifier-wrapper chain of this /// TypeRef reach a `Pointer` before hitting any base (non-modifier) /// type? Walks only the outer `Readonly`/`Mut`/`Uninit` wrappers; stops /// at the first node that is neither a modifier wrapper nor a /// `Pointer`. /// /// Used by the parser to FORBID prefix pointer modifiers /// (`ro */mut */uninit *`) — a `KwRo`/`KwMut`/`KwUninit`/(legacy /// `KwUnsafe`) arm whose recursively-parsed `inner` satisfies this /// predicate is a prefix modifier on a pointer /// (`E_POINTER_PREFIX_MODIFIER`). /// /// Returns true for: `Pointer(..)`, `Mut(Pointer(..))` (= `mut * T`), /// `Readonly(Pointer(..))` (= `ro * T`), `Uninit(Pointer(..))` /// (= `uninit * T`), `Mut(Readonly(Pointer(..)))` (= `mut * ro * T`). /// Returns false for: `Named(..)`, `Uninit(Named(..))` (= `uninit T` /// value-wrapper), `Mut(Named(..))` — anything whose modifier chain /// bottoms out at a non-pointer base. NOTE: the POSTFIX form `*mut T` /// = `Pointer(Mut(Named))` is inspected by the Star arm AFTER the Mut /// arm already returned `Mut(Named)` (false) — so postfix never trips /// this predicate. pub fn is_pointer_or_wraps_pointer(&self) -> bool { match self { TypeRef::Pointer(..) => true, TypeRef::Readonly(inner, _) | TypeRef::Mut(inner, _) | TypeRef::Uninit(inner, _) => inner.is_pointer_or_wraps_pointer(), _ => false, } } /// **Plan 118.1.6 (2026-06-08):** detect an "unsafe fn-pointer" type /// shape — semantic identity «pointer к #unsafe fn signature». /// /// Per spec D216 V2/V3 + Plan 118.5 universal modifier model, two AST /// shapes are accepted as semantically-equivalent encodings of /// `* unsafe fn(...)`: /// - `Pointer(Uninit(Func{...}))` — `*unsafe fn(...) -> T` form /// (D216 V2 §V2.3 «valid ptr to possibly-uninit T»; here T = Func). /// - `Uninit(Pointer(Func{...}))` — `unsafe * fn(...) -> T` form /// (D216 V2 §V2.4 outer-unsafe wrapper around safe pointer). /// Plus chains через outer Readonly/Mut wrappers that don't change /// safety semantics (V3 §V3.2 restricts these, but they still pass). /// /// **§10a rename (Plan 174.5, 2026-07-11):** the `Uninit` AST variant is /// shared between the renamed possibly-uninit data modifier (surface /// `uninit`) and this UNRENAMED fn-pointer shape (surface stays /// `unsafe`, D216 §10) — the parser only accepts `unsafe` here when the /// wrapped payload is `Func`. Structurally indistinguishable at the AST /// level; this helper's semantics (and name) are unchanged by the rename. /// /// Used by Plan 118.1.6 enforcement: /// (1) addr_of(unsafe_fn) result type carries Uninit wrapper; /// (2) E_UNSAFE_FN_PTR_COERCION fires when assigning unsafe-fn-ptr /// к non-unsafe-fn-ptr binding; /// (3) E_UNSAFE_CALL_REQUIRES_WRAP fires when calling через unsafe-fn-ptr /// binding without `unsafe { }` wrap (depth == 0). /// /// Returns true ONLY when both Pointer AND Uninit wrappers appear /// somewhere in the chain wrapping a Func payload. Bare `* fn(...)` — /// false (no Uninit). Bare `unsafe fn(...)` (no Pointer) — false /// (not yet a pointer; will surface for builders/value-level uses). pub fn is_unsafe_fn_pointer(&self) -> bool { // Walk chain, tracking whether we've seen Pointer and Uninit. fn walk(t: &TypeRef, saw_ptr: bool, saw_unsafe: bool) -> bool { match t { TypeRef::Pointer(inner, _) => walk(inner, true, saw_unsafe), TypeRef::Uninit(inner, _) => walk(inner, saw_ptr, true), TypeRef::Readonly(inner, _) | TypeRef::Mut(inner, _) => { walk(inner, saw_ptr, saw_unsafe) } TypeRef::Func { .. } => saw_ptr && saw_unsafe, _ => false, } } walk(self, false, false) } /// **Plan 118.1.6 (2026-06-08):** detect a "safe fn-pointer" type shape — /// `Pointer(Func{...})` (with optional outer Readonly/Mut wrappers, but /// WITHOUT any Uninit wrapper). Used by E_UNSAFE_FN_PTR_COERCION to /// distinguish: assignment of `*unsafe fn(...)` value к binding annotated /// `*fn(...)` (banned) vs assignment к binding annotated `*unsafe fn(...)` /// (allowed). Returns true for `*fn(...)`, `ro * fn(...)`, `mut * fn(...)`; /// false for `*unsafe fn(...)`, `unsafe * fn(...)`, non-fn-pointer types. pub fn is_safe_fn_pointer(&self) -> bool { fn walk(t: &TypeRef, saw_ptr: bool, saw_unsafe: bool) -> bool { match t { TypeRef::Pointer(inner, _) => walk(inner, true, saw_unsafe), TypeRef::Uninit(inner, _) => walk(inner, saw_ptr, true), TypeRef::Readonly(inner, _) | TypeRef::Mut(inner, _) => { walk(inner, saw_ptr, saw_unsafe) } TypeRef::Func { .. } => saw_ptr && !saw_unsafe, _ => false, } } walk(self, false, false) } /// Plan 91.12 V2 followup #3 (2026-06-02): рекурсивная проверка /// использует ли TypeRef один из generic type-параметров из set'а. /// /// Universal helper для type-checker'а и codegen'а — оба нуждаются в /// detection «type-param-in-position» для validation (e.g. reject /// `type X[T](T)` newtype в `walk_typedecl`) и erasure decisions /// (e.g. emit `void*` placeholder для generic record field в /// `emit_type_decl`). /// /// До этого refactor каждый caller имел свою копию (emit_c.rs /// `type_ref_uses_any_type_param`, types/mod.rs `typeref_uses_param`). /// Single source of truth теперь живёт здесь. /// /// Walks все TypeRef variants: /// - Named { path, generics }: hit на single-segment path matching /// param name; recurse на generics. /// - Array / FixedArray / Readonly: recurse на inner. /// - Tuple: recurse на all elements. /// - Func: recurse на params + return type. /// - Protocol: recurse на methods' params + return types. /// - Unit: false. /// - Pointer (Plan 118): recurse на inner. pub fn uses_any_type_param(&self, params: &std::collections::HashSet<String>) -> bool { match self { TypeRef::Named { path, generics, .. } => { if path.len() == 1 && params.contains(&path[0]) { return true; } generics.iter().any(|g| g.uses_any_type_param(params)) } TypeRef::Array(inner, _) | TypeRef::FixedArray(_, inner, _) | TypeRef::Readonly(inner, _) | TypeRef::Mut(inner, _) | TypeRef::Uninit(inner, _) | TypeRef::Pointer(inner, _) | TypeRef::Ref(inner, _) => inner.uses_any_type_param(params), TypeRef::Tuple(ts, _) => ts.iter().any(|t| t.uses_any_type_param(params)), TypeRef::Func { params: p, return_type, .. } => { p.iter().any(|t| t.uses_any_type_param(params)) || return_type.as_ref() .map_or(false, |t| t.uses_any_type_param(params)) } TypeRef::Protocol { methods, .. } => methods.iter().any(|m| { m.params.iter().any(|p| p.ty.uses_any_type_param(params)) || m.return_type.as_ref() .map_or(false, |t| t.uses_any_type_param(params)) }), TypeRef::Unit(_) => false, } } } /// Блок: список statement'ов + опциональное финальное выражение. #[derive(Debug, Clone)] pub struct Block { pub stmts: Vec<Stmt>, pub trailing: Option<Box<Expr>>, pub span: Span, /// Plan 118 (D216 §8, D2 amend): `unsafe { ... }` block marker. /// Type-checker uses for unsafe-context tracking — pointer ops /// (&, *, *T deref, p.field на pointer) require unsafe context /// (E_UNSAFE_REQUIRED иначе — Ф.3.5 follow-on). Default false; /// set true by parser для KwUnsafe-prefixed blocks (parse_primary /// KwUnsafe arm). pub is_unsafe: bool, } /// Plan 201 (D188-амендмент 2026-07-13): приёмник значения /// consume-блока-выражения — `ro s = consume X { …; X }` / /// `mut s = …` / `consume s = …`. Хранится ВНУТРИ `Stmt::ConsumeScope` /// (Let растворяется в стейтмент блока при парсинге) — result-имя /// объявляется в ОБЪЕМЛЮЩЕМ scope ПОСЛЕ блока. #[derive(Debug, Clone)] pub struct ConsumeScopeResult { pub name: String, /// `mut s = …` — приёмник мутабелен. pub mutable: bool, /// `consume s = …` — приёмник объявлен с явным consume-keyword /// (ownership-binding D180). При tail-выносе обязательство /// назначается result'у независимо от keyword'а. pub declared_consume: bool, pub span: Span, } #[derive(Debug, Clone)] pub enum Stmt { Let(LetDecl), /// Plan 114.4 Ф.2: scope-local `const N = expr` — strict constexpr /// binding inside function body / block. Codegen inline'ит literal /// value на use-sites (zero allocation, zero binding overhead). /// Visible до end-of-enclosing-block. Constexpr-eligibility — same как /// module-level const (check_const_constexpr). Const(ConstDecl), Expr(Expr), Assign { target: Expr, op: AssignOp, value: Expr, span: Span, }, /// Plan 136 (D236): `(a, b) = (expr1, expr2)` — tuple destructuring assignment. /// Assigns multiple values simultaneously from a RHS tuple literal. TupleAssign { lhs: Vec<Expr>, rhs: Vec<Expr>, span: Span, }, Return { value: Option<Expr>, span: Span, }, Break(Span), Continue(Span), Throw { value: Expr, span: Span, }, /// D90: `defer body` — выполнить body при любом exit из enclosing /// scope (normal/return/throw/panic/interrupt). НЕ на exit(N, msg). Defer { body: Expr, /// Plan 173 Ф.2 (D314): `defer(o ScopeOutcome) { … }` — опциональный /// outcome-биндинг. `None` = плейн `defer` (byte-identical). `Some(name)` /// = тело получает исход через `name: ScopeOutcome` (Success/Failure/ /// Panic). Материализация `ScopeOutcome*` на каждом exit-path — B2 codegen. outcome_binding: Option<String>, span: Span, }, // Plan 173 Ф.1 (#4, D189 hard cutover, [M-172-errdefer-okdefer-dead-surface]): // `errdefer`/`okdefer`/`defer |result|` РЕТРАКТНУТЫ (D189). AST-варианты // `ErrDefer`/`OkDefer`/`DeferWithResult` УДАЛЕНЫ — парсер отвергает эти // формы на месте с `[D189-removed-*]` (tombstone, parser/mod.rs), поэтому // они никогда не конструировались. Замена: `defer(o ScopeOutcome)` (Ф.2) // / `consume X = e { body }` (D188). Плейн `defer` (выше) остаётся. /// Plan 110 (D188): `consume IDENT (':' TYPE)? '=' EXPR '{' BODY '}'` /// — scope-block с автоматическим вызовом `Cleanup.cleanup` при /// выходе из BODY (success/throw/panic/cancel). /// /// Parser detect block-form через lookahead `{` после init EXPR /// (с disabled `no_trailing_block` чтобы не путать с trailing-block /// call syntax). /// /// `binding`: single identifier (D188 disallows destructure для scope- /// block). /// `type_annot`: optional type annotation (parallel с `LetDecl`). /// `init`: expression — должна resolve к типу implementing /// `Cleanup[E]` (D196 init type constraints; D188 R1 partial- /// construction safety). /// `body`: scope body — sequence of statements + optional trailing /// expression. /// /// Codegen pipeline: /// - Plan 110.1.4: basic desugaring (sync, no shield/timeout). /// - Plan 110.2: cancel-shield + 3-level timeout resolution. /// - Plan 110.1.7: D194 hot-path elision для `Cleanup[never]`. /// /// См. spec/decisions/03-syntax.md D188. ConsumeScope { binding: String, type_annot: Option<TypeRef>, init: Expr, body: Block, /// D188-амендмент 2026-07-13 (Plan 201): `consume X { body }` /// (без `= expr`) — re-consume СУЩЕСТВУЮЩЕГО owned-биндинга /// (consume-параметр / `consume X = …` локал). `init` при этом — /// `Expr::Ident(X)`. Checker (D131-проход): owned-требование /// (E_CONSUME_BLOCK_NOT_OWNED), Cleanup[E]-требование /// (E_D188_NOT_CLEANUP), внутри тела `X` — только ro-view /// (consume-операция → E_CONSUME_BLOCK_MOVE_OUT); санкционированный /// вынос владения — ТОЛЬКО tail-значение `X` или `return X` /// (голый `X`) — на этом пути cleanup дизармится. После блока `X` /// consumed. Codegen — тем же механизмом, что и binding-форма /// (init читается ДО `#define`). /// /// `false` для binding-формы `consume X = expr { body }` И для /// spawn-формы `spawn consume c [= e] { body }` (D415 §4 — /// у неё свои правила, move-out-запрет там не вводился). re_consume: bool, /// Plan 201: блок = ВЫРАЖЕНИЕ. `ro s = consume X { …; X }` — /// биндинг-приёмник значения блока. `None` — statement-позиция /// (значение отброшено, tail-вынос невозможен). result: Option<ConsumeScopeResult>, span: Span, }, /// Plan 33.2 Ф.8 (D24): `assert_static <bool>` — intermediate proof /// obligation. В debug — runtime check; в release — стирается. /// /// Plan 33.8 Ф.6.3: V1 — `assert_static` НЕ верифицируется SMT. /// Корректная compile-time проверка требует flow-sensitive /// верификации (нужно знать состояние именно в точке assert'а, а /// модель `verify_fn` flow-insensitive — та же причина, что у /// `assume`). Полная интеграция отложена (V2). В V1 `assert_static` /// действует как обычный runtime-assert; lint `assert-static-unverified` /// (lints.rs) предупреждает, чтобы не было ложной уверенности. AssertStatic { expr: Expr, span: Span, }, /// Plan 33.3 (D24): `assume <bool>` — escape hatch для FFI / external /// knowledge. В debug — runtime check; в release — стирается. /// /// Plan 33.8 Ф.3.1: вне `#trusted` функции эмитится lint-warning /// `trust-introduced` (lints.rs `check_assume_trust`). /// /// V1: `assume` НЕ интегрирован в SMT-scope верификатора (формула не /// ассертится как аксиома). Корректная интеграция требует /// flow-sensitive верификации (assume на позиции N ограничивает только /// VC после N) — текущая модель verify_fn не flow-sensitive. Полная /// интеграция отложена (Plan 33.8 Ф.3.2 → V2). Текущее поведение /// soundness-safe: assume просто не влияет на доказательства. Assume { expr: Expr, span: Span, }, /// Plan 33.5 Ф.4.1: `apply lemma_name(args)` — активировать lemma. /// В SMT-scope: добавляет `ensures[args/params]` как assertion. /// Не emit'ится в runtime (ghost statement). Apply { lemma: String, args: Vec<Expr>, span: Span, }, /// Plan 33.5 Ф.4.2: `calc { expr; == expr; == expr; }` — structured /// equational reasoning. Ghost statement: each step asserts adjacency /// relation in SMT; erased in codegen. Calc { steps: Vec<CalcStep>, span: Span, }, /// Plan 33.9 Ф.2: `reveal name` — раскрывает opaque fn body в SMT /// scope текущей fn body. Ghost statement: emit'ит axiom /// `forall args. name(args) == body` в SMT scope; erased в codegen. /// V1: parser/AST/lints (без real Z3 axiom emission — V2 task). Reveal { name: String, span: Span, }, } /// Один шаг calc-доказательства: отношение + выражение. /// Первый шаг (expr1) не имеет отношения; остальные — `== expr`, `<= expr`, etc. #[derive(Debug, Clone)] pub struct CalcStep { /// None для первого шага, Some(rel) для последующих. pub rel: Option<CalcRel>, pub expr: Expr, pub span: Span, } /// Отношение между шагами calc. #[derive(Debug, Clone, Copy, PartialEq)] pub enum CalcRel { Eq, // == Le, // <= Lt, // < Ge, // >= Gt, // > } impl CalcRel { pub fn to_smt_op(self) -> &'static str { match self { CalcRel::Eq => "=", CalcRel::Le => "<=", CalcRel::Lt => "<", CalcRel::Ge => ">=", CalcRel::Gt => ">", } } } #[derive(Debug, Clone, Copy, PartialEq)] pub enum AssignOp { Assign, Add, Sub, Mul, Div, /// `&=` — Plan 234 Ф.2а (D46-амендмент §C). BitAnd, /// `|=` — Plan 234 Ф.2а. BitOr, /// `^=` — Plan 234 Ф.2а. BitXor, /// `<<=` — Plan 234 Ф.2а (по симметрии со сдвигами). Shl, /// `>>=` — Plan 234 Ф.2а. Shr, } /// Stable per-`Expr` identity (Plan 172.1 U.4.1 — TypedIR substrate). /// /// Assigned by `number_exprs` AFTER parse, BEFORE type-checking; carries the /// checker's resolved-type annotation (`ModuleEnv.resolved_types: ExprId → /// ResolvedType`) across `desugar` into codegen so codegen READS the type /// instead of re-deriving it (`infer_expr_c_type`, §0/§1). /// /// `UNSET` (0) marks exprs synthesized AFTER numbering — `desugar` lowering /// (map-literal, `+=`, for-invariant) and codegen scaffolding — which carry no /// annotation; codegen falls back to local inference for them. /// /// Why not key by `Span`: parser/desugar synthesize MULTIPLE distinct `Expr`s /// at ONE span (for-invariant wrapper shares the loop span: parser.rs /// `wrap_loop_with_preentry_check`; map-literal lowering stamps ~15-20 nodes /// with the `[k:v]` span: desugar.rs `build_map_block`), so spans collide and /// cannot be the annotation key. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub struct ExprId(pub u32); impl ExprId { /// Sentinel for exprs created after the numbering pass (no annotation). pub const UNSET: ExprId = ExprId(0); /// True once `number_exprs` has stamped a real id. pub fn is_set(self) -> bool { self.0 != 0 } } #[derive(Debug, Clone)] pub struct Expr { pub kind: ExprKind, pub span: Span, /// Plan 172.1 U.4.1: stable identity for type-annotation carry. /// `ExprId::UNSET` until `number_exprs` assigns it (post-parse, pre-check). pub id: ExprId, /// Plan 194 Ф.1 (D-блок TBD, 09-tooling.md / D81-амендмент): `#debug`- /// префикс — dev-only marker. V1 (эта фаза): ставится ТОЛЬКО парсером на /// `assert(...)` Call-выражении, разобранном после statement-префикса /// `#debug` (parser/mod.rs `parse_stmt_or_expr`). Поле ИНЕРТНО в этой /// фазе — ни один consumer его не читает, поведение не меняется; /// codegen-эрозия по `--contracts`-режиму (checked/optimized/verified) — /// Ф.2 (следующая волна). Default `false` для всех остальных Expr. pub debug_only: bool, } impl Expr { pub fn new(kind: ExprKind, span: Span) -> Self { Self { kind, span, id: ExprId::UNSET, debug_only: false, } } /// D38 turbofish: type_args — explicit hint для monomorphization, в bootstrap /// прозрачно. Возвращает inner base, разворачивая вложенные TurboFish; если /// expr — не TurboFish, возвращает себя. pub fn unwrap_turbofish(&self) -> &Expr { let mut cur = self; while let ExprKind::TurboFish { base, .. } = &cur.kind { cur = base.as_ref(); } cur } } /// Часть `"... ${expr} ..."` interpolated-строки (D44, Plan 17 Ф.4). /// /// Plan 91.14 (D229): `Expr` variant gained `spec: FormatSpec` field. /// `spec: FormatSpec::None` — bare `${expr}` → Printable.@fmt (V1 default). /// `spec: FormatSpec::Debug` — `${expr:?}` → DebugPrintable.@debug_fmt (NEW). #[derive(Debug, Clone)] pub enum InterpStrPart { /// Буквальная часть строки (literal-сегмент). Lit(String), /// Подвыражение `${expr}` или `${expr:?}` — будет вычислено /// и приведено к str via Printable/DebugPrintable per `spec`. Expr { expr: Box<Expr>, spec: FormatSpec }, } #[derive(Debug, Clone)] pub enum ExprKind { // Литералы IntLit(i64), FloatLit(f64), StrLit(String), /// D412 (Plan 186): compile-time byte blob, type `[]u8`. Two surface /// sources produce this node: /// - hex-blob literal `x"48 69"` (parser, from `TokenKind::HexBlob`); /// - `embed("path")` intrinsic (embed_resolve pass replaces the Call /// with the file's bytes; path relative to the calling .nv file). /// NOT a numeric literal: leading zero bytes significant, byte order = /// written order. Codegen emits one interned `static const uint8_t /// nova_blob_<hash>[]` per distinct content; ro-binding = zero-copy view /// (data → static, len == cap == N), mut/consume binding = GC-heap copy /// at the binding point. HexBlobLit(Vec<u8>), /// `"hello ${name}, age=${n}"` — D44 string interpolation. /// Codegen эмитит StringBuilder-цепочку: одна аллокация /// + per-fragment `@append` (без O(N²) от `+`). InterpolatedStr { parts: Vec<InterpStrPart> }, BoolLit(bool), UnitLit, /// Q-char-literals: 'a' / '\n' / '\u{...}' — Unicode codepoint as u32. /// Тип char в bootstrap эмитируется как nova_int. CharLit(u32), /// **Plan 115 D214:** `null ptr` two-token literal. Bitwise zero opaque /// pointer. V1 ограничение: только `null ptr` (другие типы → parser /// emit'ит `E_NULL_LITERAL_REQUIRES_PTR`). Plan 118 future расширит до /// `null *T` family. NullPtrLit, /// `arr` или `[1, 2, ...rest, 4]` — D60 ArrayLit(Vec<ArrayElem>), /// `[k1: v1, k2: v2]` — map-литерал (D108, Plan 52). Конструирует /// `HashMap[K, V]`. Ключи и значения — выражения; порядок вычисления /// нормативный: `k1, v1, k2, v2, ...` (слева направо, ключ перед /// значением). Десугарится в codegen/interp в `new()`+`.cap(n)`+`@insert` /// block-expression (D372 amend 2026-07-06: `with_capacity` removed). /// Пустой `[]` остаётся `ArrayLit(vec![])` — /// разрешается по ожидаемому типу на type-check. /// /// Plan 52 Ф.7 production-fix: `inferred_key`/`inferred_value` — /// типы K/V, выведенные type-checker'ом (MapLitCtx::annotate_module) /// после inference. Десугаринг использует их для генерации turbofish /// `HashMap[K, V].new()` — без turbofish мономорфизация /// инстанциирует `HashMap[void*, void*]` → runtime segfault на /// generic-метод-резолюции. Парсер заполняет `None`, type-checker — /// `Some(_)` если K/V определены однозначно. MapLit { /// Plan 55 Ф.* (followup): mixed pairs + spreads. Раньше было /// `Vec<(Expr, Expr)>` (только пары); теперь spreads `...m` /// перемежаются с парами `k: v` в любом порядке. elems: Vec<MapElem>, inferred_key: Option<TypeRef>, inferred_value: Option<TypeRef>, /// Plan 52 Ф.23: имя target-типа для desugar. Если type-checker /// определил что expected type помечен `#from_pairs` — /// записывает сюда полный path (e.g. `["HashMap"]` для stdlib, /// `["MyMap"]` для user-типа). Десугаринг использует это вместо /// хардкода `HashMap`. `None` → fallback на `HashMap` (legacy). inferred_target_type: Option<Vec<String>>, }, /// `{ field: value, ...spread, name }` — D17/D52/D60 RecordLit { type_name: Option<Vec<String>>, // Some(["User"]) для `User { ... }` fields: Vec<RecordLitField>, /// Plan 52 Ф.10 production-fix: D55 map-coercion маркер. Когда /// type-checker (`MapLitAnnotator`) обнаруживает, что /// анонимный `{field: v}` стоит в позиции, ожидающей тип с /// `#from_fields` (= `HashMap[str, V]`) — записывает сюда /// `Some(V)`. Десугаринг тогда превращает узел в /// `HashMap[str, V].new().cap(n) + insert("field", v)` /// block-expression (mirror MapLit-desugar; D372 amend /// 2026-07-06: `with_capacity` removed). Без этого flag'а /// codegen пытался бы построить record-struct из полей → /// «no member named 'debug' in 'struct Nova_HashMap'». /// `None` для обычного record-литерала или type_name = Some. inferred_map_v: Option<TypeRef>, /// D450 (реестр 221.1 №503): target-type name for the `#from_fields` /// coercion, mirroring `MapLit.inferred_target_type` (Plan 52 Ф.23). /// Populated ALONGSIDE `inferred_map_v` at the same annotate site /// (`types/mod.rs`, `MapLitAnnotator`), from the expected type's own /// path — so a `#from_fields` type OTHER than `HashMap` (e.g. /// `IndexMap`) desugars into ITSELF, not a hardcoded `HashMap`. /// `None` → fallback `HashMap` (legacy / `inferred_map_v: None`). inferred_target_type: Option<Vec<String>>, }, /// `(a, b, c)` кортеж TupleLit(Vec<Expr>), // Имена и пути /// `name` Ident(String), /// `Module.name` или `Type.method` Path(Vec<String>), /// `@field` — поле или метод receiver'а SelfAccess, // Доступ /// `obj.field` или `obj.0` (positional) Member { obj: Box<Expr>, name: String, }, /// `arr[index]` Index { obj: Box<Expr>, index: Box<Expr>, }, /// `Type[T1, T2]` или `func[T]` — generic-application (turbofish, D38). /// Семантически `base` сохраняется как есть; `type_args` — explicit hints /// для monomorphization (в bootstrap-codegen monomorphization идёт по /// receiver/call-site, поэтому TurboFish прозрачно делегирует в `base`). /// Появляется только если за `]` идёт `.` / `(` / `?` (postfix-continuation), /// иначе `[` парсится как Index. TurboFish { base: Box<Expr>, type_args: Vec<TypeRef>, }, // Вызовы Call { func: Box<Expr>, /// Plan 14 Ф.6 (D69): `Vec<CallArg>` где `CallArg::Item(Expr)` /// для обычного аргумента и `CallArg::Spread(Expr)` для `...e`. /// Spread разрешён только в variadic-position (codegen check). args: Vec<CallArg>, /// trailing-конструкция (D43-rev, Plan 19): либо `{ block }` /// (без params, DSL), либо `fn(p) body` (с params), либо /// legacy `{ x => body }` до миграции (C13 удалит legacy). trailing: Option<Trailing>, }, /// Plan 172.5 (D326 R4): call-site `ref <place>` argument marker. Produced /// by the parser ONLY in call-argument position; wraps the addressable /// place expression passed to a `mut ref` parameter. Transparent for type /// inference (type = inner place type); codegen passes the operand's /// address; the checker enforces marker ⟺ `mut ref` param, addressability, /// non-overlap (E_REF_ALIAS_OVERLAP) and the escape ban. `ref` in any /// non-argument position never reaches here (parser rejects it). RefArg(Box<Expr>), /// `expr?` — пробрасывание Fail (D25/D65) Try(Box<Expr>), /// `expr!!` — throw-стиль для Result/Option (D85, Plan 19 C7). /// /// На `Some(v)` / `Ok(v)`: разворачивает в `v`. /// На `None` / `Err(e)`: бросает через `Fail[E]` (для Option — /// `RuntimeNoneError`). Внешняя fn должна иметь `Fail[E]` в /// effect-row, иначе compile error. Bang(Box<Expr>), /// `expr ?? default` — coalesce Coalesce(Box<Expr>, Box<Expr>), /// `expr as Type` As(Box<Expr>, TypeRef), /// `expr is Type` — runtime type check (D54) Is(Box<Expr>, TypeRef), // Бинарные / унарные Binary { op: BinOp, left: Box<Expr>, right: Box<Expr>, }, Unary { op: UnOp, operand: Box<Expr>, }, // Control flow If { cond: Box<Expr>, then: Block, else_: Option<ElseBranch>, }, /// `if let pattern = expr { ... }` — D34 /// Optional guard: `if Some(x) = expr && guard_expr { ... }` IfLet { pattern: Pattern, scrutinee: Box<Expr>, /// Guard expression after `&&`; bindings from `pattern` are in scope. guard: Option<Box<Expr>>, then: Block, else_: Option<ElseBranch>, }, Match { scrutinee: Box<Expr>, arms: Vec<MatchArm>, }, For { pattern: Pattern, iter: Box<Expr>, body: Block, /// Plan 87: явная аннотация типа элемента — `for x TYPE in iter`. /// `None` — тип элемента выводится (поведение до Plan 87). elem_type: Option<TypeRef>, /// Plan 33.4 D.0.3: loop invariants (SMT + runtime). invariants: Vec<Expr>, /// Plan 33.4 D.0.3: well-founded termination measure. decreases: Option<Box<Expr>>, /// Plan 100.2 (D156): `for consume x in iter` — consume-iteration mode. /// Each loop variable is a consume-obligation (must be consumed in body). /// The iter expression itself is marked Consumed after the loop. /// Default false = view-mode (iter stays Live, loop var = view borrow). iter_consume: bool, }, /// `parallel for x in iter { body }` — D14, fan-out body for each element. /// Desugars to `supervised { for x in iter { spawn { body } } }`. ParallelFor { pattern: Pattern, iter: Box<Expr>, body: Block, /// Plan 87: явная аннотация типа элемента — `parallel for x TYPE in iter`. elem_type: Option<TypeRef>, }, While { cond: Box<Expr>, body: Block, /// Plan 33.4 D.0.3: loop invariants (SMT + runtime). invariants: Vec<Expr>, /// Plan 33.4 D.0.3: well-founded termination measure. decreases: Option<Box<Expr>>, }, /// `while let pattern = expr { ... }` — D34 /// Optional guard: `while Some(x) = expr && guard_expr { ... }` WhileLet { pattern: Pattern, scrutinee: Box<Expr>, /// Guard expression after `&&`; bindings from `pattern` are in scope. guard: Option<Box<Expr>>, body: Block, /// Plan 33.4 D.0.3: loop invariants. invariants: Vec<Expr>, /// Plan 33.4 D.0.3: well-founded termination measure. decreases: Option<Box<Expr>>, }, Loop { body: Block, /// Plan 33.4 D.0.3: loop invariants (SMT + runtime). invariants: Vec<Expr>, /// Plan 33.4 D.0.3: well-founded termination measure. decreases: Option<Box<Expr>>, }, /// `select { Some(v) = rx.recv() => body, _ => default }` --- D94 Select { arms: Vec<SelectArm>, }, // Функции и handlers /// `(a, b) => expr` — лямбда (D22, строго `=> expr`). /// /// **DEPRECATED — Plan 19** заменяет на [`ExprKind::ClosureLight`] /// (untyped `|x| body`) и [`ExprKind::ClosureFull`] (typed /// `fn(...) ...`). Старый узел остаётся для backward-compat /// до завершения миграции (C11–C13 в Plan 19 retro). Lambda { params: Vec<LambdaParam>, effects: Vec<TypeRef>, return_type: Option<TypeRef>, body: Box<Expr>, }, /// closure-light — `|x| body` (D22-rev, Plan 19). /// /// Untyped lightweight closure. Параметры — только имена (типы /// выводятся из контекста использования: HOF-arg, annotated let, /// return-position, first-use inference). Тело — bare expression /// или block. /// /// Эффекты в сигнатуре **не пишутся** — наследуются из ambient /// effect-set parent fn'а + активных with-блоков. /// /// `||` для no-arg, `|_|` для wildcard (D59 расширение). ClosureLight { params: Vec<ClosureLightParam>, body: ClosureBody, }, /// closure-full — `fn(x int) Effects -> R body` (D22-rev, Plan 19). /// /// Анонимная типизированная fn-форма. Идентична named fn без имени /// (по спецификации D22-rev). Используется когда нужны типы /// параметров, return-type или эффекты — то, чего не может /// closure-light. /// /// Тело — `=> expr` (FnBody::Expr) или `{ block }` (FnBody::Block). /// `external` запрещён (только для named fn). /// /// Содержимое в `Box<FnSigBody>` чтобы избежать infinite-size в /// recursive типе `ExprKind` (FnSigBody содержит FnBody который /// содержит Expr). ClosureFull(Box<FnSigBody>), /// `with X = handler { ... }` — D11 With { bindings: Vec<WithBinding>, body: Block, }, /// Handler-литерал: `effect EffectName { op(p) => ... ; ... }` /// (Plan 97 Ф.3: keyword `handler` → `effect`). HandlerLit { effect_name: Vec<String>, methods: Vec<HandlerMethod>, }, /// Plan 97 Ф.4 (D142): protocol-литерал в expression-position — /// value, реализующий контракт named-protocol'а. Записывается как /// `protocol ProtoName { method-impl* }`. Семантика — closure-bundle /// (D22 capture-rules, managed heap D6) для one-off реализаций /// (capability-split factory pattern). **Instance-only**: static /// методы не могут быть в литерале (static — `Type.method` D35, /// у литерала нет «своего типа»). ProtocolLit { proto_name: Vec<String>, methods: Vec<HandlerMethod>, }, /// `interrupt v` — досрочное завершение всего with-блока (D61). /// Значение становится результатом всего with-блока. Interrupt(Option<Box<Expr>>), /// `forbid X1, X2 { body }` — capability sandbox (D63). /// В bootstrap-интерпретаторе runtime барьер не реализован, /// блок исполняется как обычный block-expression. Compile-time /// проверка type checker'а — задача production-компилятора. Forbid { effects: Vec<TypeRef>, body: Block, }, /// `realtime { body }` или `realtime nogc { body }` — гарантия /// не-приостановки (D64). В bootstrap нет fiber-runtime'а с /// safepoint'ами, блок исполняется как обычный block-expression. Realtime { nogc: bool, body: Block, }, /// Range-expression — D58 (closed-form `a..b`/`a..=b`) + D144 (open-ended /// `a..`/`..b`/`..`, Plan 96 Ф.2). /// /// `start` / `end` — `Option`: `None` означает «без явной границы» /// (требуется bounded context — slice-index, D144). Closed-form (оба /// `Some`) допустимы везде: for-loop, materialize, slice. Open-ended /// (любой `None`) — **только** в slice-position `arr[range]`; вне /// slice-context type-checker эмитит ошибку (D144). Range { start: Option<Box<Expr>>, end: Option<Box<Expr>>, inclusive: bool, }, /// D.1.3: универсальный квантор в контрактах. /// `forall x in lo..hi : P(x)` Forall { var: String, range: Box<Expr>, body: Box<Expr>, }, /// D.1.3: экзистенциальный квантор в контрактах. /// `exists x in lo..hi : P(x)` Exists { var: String, range: Box<Expr>, body: Box<Expr>, }, /// Блок-выражение `{ stmts; expr }` Block(Block), /// `spawn body` — D50 Spawn(Box<Expr>), /// `supervised { body }` / `supervised(cancel: tok) { body }` / /// `supervised(deadline: mono) { body }` / `supervised(timeout: dur) { body }` — /// structured-concurrency scope (D50). `cancel` — опциональный /// именованный аргумент `cancel:` с выражением типа `CancelToken` /// (D75 revised, Plan 47): внешний код может вызвать `tok.cancel()` /// чтобы fail-fast все fiber'ы scope'а. /// /// `deadline` — опциональный областной срок (Plan 174 / D349, §3a плана 173). /// `deadline: <Monotonic>` — абсолютная точка (канон); `timeout: <Duration>` /// — относительный сахар (`Monotonic.now() + d`). По истечении — областная /// отмена (тот же путь, что `cancel:`) + наружу типизированный `TimeoutError` /// (ловится `is TimeoutError`). Пропагируется во вложенные области: срок /// наследуется, внутренний срок не может продлить внешний (min-комбинация). Supervised { body: Block, cancel: Option<Box<Expr>>, deadline: Option<SupervisedDeadline>, /// `on_timeout: |e TimeoutError| ...` — D449. Optional handler CALLED /// instead of raising `Fail[TimeoutError]` when this scope's own /// `deadline:`/`timeout:` expires. Requires `deadline`/`timeout` to /// be present (checked at parse time — an `on_timeout:` with no /// deadline can never fire, D317 never-silent-garbage). The /// construct's type always equals the body's type; the handler must /// return the same type (checker-enforced). on_timeout: Option<Box<Expr>>, }, /// `detach { body }` — fire-and-forget, global supervisor (D50). /// Requires `Detach` effect in the enclosing function's signature. Detach(Block), /// `blocking { body }` — Plan 83.3 (D50): leaf-блокирующая работа /// (FFI/syscall) уводится в libuv threadpool, fiber паркуется, /// M:N-worker свободен. Requires `Blocking` effect in the enclosing /// function's signature; запрещён внутри `realtime { }` (D64). Blocking(Block), /// `throw expr` в позиции expression (D25/D65). Обрабатывается как /// эффект `Fail.fail(msg)`, тип `never`. В codegen эмитируется как /// `(Nova_Fail_fail(msg), zero<T>)` — comma-expression, dummy после /// fail() недостижим. Throw(Box<Expr>), /// `return ...` использованный как fallback `??` (`X ?? return R`). /// /// **[E_COALESCE_RETURN_FALLBACK]** (D86 AMEND 2026-07-23, ретракция /// формы `return` как fallback'а `??`): парсер ПРИНИМАЕТ форму в AST /// (rustc-style parse-then-diagnose — диагностика контекстна: нужен тип /// операнда `X` и return-тип объемлющей функции, парсер типов не знает), /// но чекер ВСЕГДА её отвергает контекстной `Suggestion` (см. /// `types/mod.rs::check_coalesce_return_fallback`). Парсер конструирует /// этот вариант ТОЛЬКО как непосредственный правый операнд /// `ExprKind::Coalesce` (см. `Question2`-ветку `parse_postfix`) — в /// валидном месте больше нигде появиться не может. CoalesceReturnFallback(Option<Box<Expr>>), // Внутреннее: backtick-tagged template — для bootstrap'а: tag-функция // вызывается с (parts: []str, args: []SqlValue/...) — но в bootstrap // мы не делаем split на parts/args. Просто обозначаем как литерал. /// `tag\`literal\`` TaggedTemplate { tag: Box<Expr>, parts: Vec<String>, args: Vec<Expr>, }, } #[derive(Debug, Clone)] pub enum ArrayElem { /// Обычный элемент. Item(Expr), /// `...expr` spread (D60) Spread(Expr), } /// Plan 55 followup (D108-spread): элемент map-литерала. /// `[k: v]` — пара ключ-значение. `[...m]` — spread другой map. /// Mixed: `[...defaults, "x": 1, ...overrides]` — последовательно /// applies в порядке (later overrides earlier для duplicate keys). #[derive(Debug, Clone)] pub enum MapElem { /// Обычная пара `k: v`. Pair(Expr, Expr), /// `...expr` — spread другой map того же типа. expr должен /// иметь тип совместимый с inferred K, V литерала. Spread(Expr), } impl MapElem { /// Возвращает clone'd Vec<(Expr, Expr)> только pairs — для type-check / /// lint callers которые работают только с известными парами k:v. /// Spreads пропускаются (их type-check / lints — отдельной passes). pub fn cloned_pairs(elems: &[MapElem]) -> Vec<(Expr, Expr)> { elems.iter().filter_map(|e| match e { MapElem::Pair(k, v) => Some((k.clone(), v.clone())), MapElem::Spread(_) => None, }).collect() } /// True если есть хотя бы один Spread элемент. pub fn has_spread(elems: &[MapElem]) -> bool { elems.iter().any(|e| matches!(e, MapElem::Spread(_))) } } /// Plan 14 Ф.6 (D69): аргумент вызова. Зеркально к `ArrayElem`. /// `Spread` разрешён только в variadic-position на call-site /// (codegen в emit_call валидирует). /// Plan 46 (D102): `Named` — именованный аргумент `name: expr`. #[derive(Debug, Clone)] pub enum CallArg { /// Обычный позиционный аргумент. Item(Expr), /// `...expr` — spread в variadic-position. Spread(Expr), /// Plan 46 (D102): `name: expr` — именованный аргумент. /// `name` — имя параметра callee (не выражение). Именованные /// аргументы переставимы; позиционный после именованного — error. Named { name: String, value: Expr }, } impl CallArg { /// Достать выражение независимо от kind'а. pub fn expr(&self) -> &Expr { match self { CallArg::Item(e) | CallArg::Spread(e) => e, CallArg::Named { value, .. } => value, } } pub fn is_spread(&self) -> bool { matches!(self, CallArg::Spread(_)) } /// Plan 46: имя именованного аргумента, если это `Named`. pub fn arg_name(&self) -> Option<&str> { match self { CallArg::Named { name, .. } => Some(name.as_str()), _ => None, } } } #[derive(Debug, Clone)] pub struct RecordLitField { pub name: String, /// None — shorthand `{ name }` (D52 field punning) pub value: Option<Expr>, /// Spread `...expr` — D60. Если `is_spread = true`, то `name` = "" /// и `value = Some(expr)`. pub is_spread: bool, /// D52 §2 `@field`-shorthand marker: parser desugar'ит `{ @name }` /// в AST `RecordLitField { name, value: Some(Member { obj: SelfAccess, /// name }) }` — identical к explicit `{ name: @name }`. Flag различает /// shorthand vs explicit, чтобы D52 §2 enforcement не false-positive'ил /// на parser-generated `@field` shorthand. pub at_shorthand: bool, pub span: Span, } #[derive(Debug, Clone)] pub struct LambdaParam { pub name: String, pub ty: Option<TypeRef>, pub span: Span, } /// Параметр closure-light (Plan 19, D22-rev). /// /// В отличие от [`LambdaParam`] и [`Param`] — **тип не пишется**. /// Closure-light всегда untyped; типы выводятся из контекста. /// Wildcard `_` разрешён как имя (D59 расширение): означает «параметр /// требуется по арности, но не используется в теле». #[derive(Debug, Clone)] pub struct ClosureLightParam { /// Имя параметра. `"_"` — wildcard. pub name: String, pub span: Span, } /// Тело closure-light (Plan 19, D22-rev). /// /// Closure-light не использует `=>` в синтаксисе — после `|...|` сразу /// идёт либо expression, либо block. AST разделяет два случая для /// прозрачной поддержки `return`/`break`/`continue` в block-форме. #[derive(Debug, Clone)] pub enum ClosureBody { /// `|x| x + 1` — bare expression Expr(Box<Expr>), /// `|x| { stmts; expr }` — block-форма Block(Block), } impl ClosureBody { pub fn span(&self) -> Span { match self { ClosureBody::Expr(e) => e.span, ClosureBody::Block(b) => b.span, } } } #[derive(Debug, Clone)] pub struct WithBinding { pub effect: TypeRef, pub handler: Expr, pub span: Span, /// Plan 33.3 Ф.9.6: верификационный статус handler'а для этого /// эффекта. Required когда body использует функции с контрактами, /// ссылающимися на pure_view данного эффекта (gate в types/mod.rs). /// По умолчанию — `Unverified`. pub verification: HandlerVerification, } /// Plan 33.3 Ф.9.6 (D24): верификация handler'а для конкретного эффекта. /// /// - `Unverified` — default. Handler не проверен и не trusted; использовать /// нельзя если body вызывает fn с pure_view-contract'ами этого эффекта. /// - `Verify` — `#verify_handler`. Symbolic verification handler.action /// body против axiom'ов эффекта (Ф.9.7). До Ф.9.7 — treated как /// placeholder (Ф.9.6 принимает синтаксис, но не верифицирует). /// - `Trusted` — `#trusted_handler`. Программист берёт ответственность, /// что handler корректно реализует axioms эффекта. Без verification. #[derive(Debug, Clone, PartialEq, Eq)] pub enum HandlerVerification { Unverified, Verify, Trusted, } #[derive(Debug, Clone)] pub struct HandlerMethod { pub name: String, pub params: Vec<HandlerMethodParam>, /// Plan 175.2 Ф.2-v4 (П4, D-амендмент): explicit `-> Type` after the /// param list. `None` for a `protocol P {...}` method-impl (unchanged, /// return type inferred from the protocol contract) — mandatory ONLY /// for `effect X {...}` handler-literal ops (checker enforces via /// `check_handler_op_declarations`, E_INCOMPLETE_HANDLER_OP_DECL). pub ret_ty: Option<TypeRef>, pub body: HandlerMethodBody, pub span: Span, } #[derive(Debug, Clone)] pub struct HandlerMethodParam { pub name: String, pub ty: Option<TypeRef>, // обычно None — выводится из effect-сигнатуры (Q-handler-method-param-inference) pub span: Span, } #[derive(Debug, Clone)] pub enum HandlerMethodBody { /// `op(p) => expr` Expr(Expr), /// `op(p) { stmts }` (без `=>`) Block(Block), } #[derive(Debug, Clone)] pub struct TrailingBlock { pub params: Vec<LambdaParam>, // [] если без params pub body: Block, pub span: Span, } /// Trailing-конструкция при вызове функции (Plan 19, D43-rev). /// /// После `f(args)` может идти: /// - `{ block }` — без параметров (DSL-форма: `with_timeout`, /// `retry`, `transaction`). См. [`Trailing::Block`]. /// - `fn(p) body` — с параметрами, идентично closure-full без /// имени. См. [`Trailing::Fn`]. /// /// Старая форма `f(args) { x => body }` (с параметрами через `=>` /// внутри `{...}`) после Plan 19 **отменена**. Во время dual-mode /// (C2–C12) parser продолжает поддерживать через /// [`Trailing::LegacyBlockWithParams`]; после миграции (C11/C12) /// и удаления (C13) этот вариант исчезает. #[derive(Debug, Clone)] pub enum Trailing { /// `f(args) { block }` — DSL без params (D43-rev). /// Boxed чтобы избежать infinite-size в recursive типе ExprKind. Block(Box<Block>), /// `f(args) fn(p) Effects? -> R? body` — trailing closure-full /// (D43-rev). Boxed по той же причине. Fn(Box<FnSigBody>), /// **DEPRECATED, dual-mode only.** Старая форма /// `f(args) { x => body }` или `{ (a, b) => body }`. Парсер /// сохраняет её для backward-compat до C12 (миграция) и C13 /// (удаление варианта). LegacyBlockWithParams(Box<TrailingBlock>), } impl Trailing { pub fn span(&self) -> Span { match self { Trailing::Block(b) => b.span, Trailing::Fn(f) => f.span, Trailing::LegacyBlockWithParams(tb) => tb.span, } } } /// Сигнатура+тело анонимной fn — общий стержень для /// [`ExprKind::ClosureFull`] и [`Trailing::Fn`] (Plan 19). /// /// Поля повторяют [`FnDecl`] минус `is_export`/`is_external`/`name`/ /// `receiver`/`generics`/`realtime_attr`. Generics на closure-full /// в bootstrap не поддерживаются (rank-2 polymorphism — Q-открытый); /// если потребуется — расширить структуру отдельно. #[derive(Debug, Clone)] pub struct FnSigBody { pub params: Vec<Param>, pub effects: Vec<TypeRef>, pub return_type: Option<TypeRef>, pub body: FnBody, pub span: Span, } #[derive(Debug, Clone)] pub enum ElseBranch { /// `else { ... }` Block(Block), /// `else if ...` — рекурсивно следующий `if` If(Box<Expr>), } #[derive(Debug, Clone)] pub struct MatchArm { pub pattern: Pattern, pub guard: Option<Expr>, pub body: MatchArmBody, pub span: Span, } #[derive(Debug, Clone)] pub enum MatchArmBody { /// `pattern => expr` Expr(Expr), /// `pattern => { block }` — единственное исключение из D40 (D19) Block(Block), } /// Областной срок для `supervised` (Plan 174 / D349, план 173 §3a). /// /// `deadline: <expr>` (`relative == false`) — `expr` типа `Monotonic`, абсолютная /// точка на монотонных часах. `timeout: <expr>` (`relative == true`) — `expr` /// типа `Duration`, относительный сахар = `Monotonic.now() + expr`. #[derive(Debug, Clone)] pub struct SupervisedDeadline { pub expr: Box<Expr>, /// `true` для `timeout:` (Duration, relative), `false` для `deadline:` /// (Monotonic, absolute point). pub relative: bool, pub span: Span, } /// One arm of a `select` expression --- D94. #[derive(Debug, Clone)] pub struct SelectArm { pub op: SelectOp, pub guard: Option<Expr>, pub body: Block, pub span: Span, } /// Channel operation in a select arm --- D94. #[derive(Debug, Clone)] pub enum SelectOp { Recv { /// `Some(v) = rx` → `Some("v")`; `_ = rx` / `None = rx` → `None`. binding: Option<String>, /// Plan 173 Ф.3 п.3 (D94/D414 §3): `None = rx.recv()` арм — fires /// ТОЛЬКО когда канал closed И буфер пуст (recv() вернул бы `None`). /// `Some(v) = rx` (needs value) и `_ = rx` (fires on value ИЛИ closed) /// — `none_arm=false`. Различает closed-vs-value в select-dispatch. none_arm: bool, chan: Box<Expr>, }, Send { chan: Box<Expr>, value: Box<Expr>, }, Default, } #[derive(Debug, Clone, Copy, PartialEq)] pub enum BinOp { Add, Sub, Mul, Div, Mod, Eq, Neq, Lt, Le, Gt, Ge, And, Or, /// Plan 33.1 (D24): `==>` импликация. `A ==> B` ≡ `!A || B`. /// Используется только в контрактах (Ф.2 проверит, что Implies /// не появляется в обычном коде — это compile error). Implies, /// Plan 33.1 (D24): `<==>` эквивалентность. `A <==> B` ≡ `A == B` для bool. /// Используется только в контрактах. Iff, /// Bitwise (D-operators в spec/03-syntax.md). Применимы только к int. BitAnd, BitOr, BitXor, Shl, Shr, } #[derive(Debug, Clone, Copy, PartialEq)] pub enum UnOp { Neg, Not, /// Plan 234 Ф.2 (D46-амендмент 2026-07-27): `~a` — побитовое дополнение. /// НЕ связан с `Not` (`!` остаётся ЛОГИЧЕСКИМ отрицанием) — отдельный /// оператор, отдельный токен `~`, диспетч на примитивах в C `~`/XOR-маску /// (integer promotion, план 234 таблица эмиссии), на пользовательских /// типах — на `@bitnot()`. BitNot, /// Plan 118 D216 §4: `&value` pointer creation — safe, без unsafe {}. /// Escape analysis + auto-promote: если указатель уходит за scope → /// x аллоцируется в heap. Результат всегда валиден. AddrOf, /// Plan 118.7 D216 §4 amend: `raw &value` — сырой стек-адрес без /// escape analysis / auto-promote. Требует `unsafe {}` контекст /// (E_UNSAFE_REQUIRED иначе). Программист берёт ответственность: /// указатель может быть dangling после выхода из scope. RawAddrOf, /// Plan 118 D216 §5: `*p` explicit deref (one level). Result type = /// inner pointee T. Inside unsafe context only. Deref, } /// Plan 118.1 [M-118.1-addr-of-chains]: classification of an `addr_of(...)` / /// `&value` operand's field-access chain by its ROOT. The lvalue check must /// walk the `Member.obj` / `(*p)` chain to its base — a field path rooted in a /// temporary (call result, arithmetic, …) would yield a dangling pointer. #[derive(Debug, Clone, PartialEq)] pub enum AddrChainRoot { /// Chain roots in a named binding (`Some(name)`) or `self` (`None`). /// Addressable — stable storage. Lvalue(Option<String>), /// Some link in the chain is an array index (`arr[i].field`) — unstable /// base (buffer resize / GC compaction). Maps to E_ARRAY_INDEX_PTR_BANNED. IndexInChain, /// Chain roots in an rvalue (call result, arithmetic, record literal, …). /// Maps to E_ADDR_OF_NON_LVALUE. Rvalue, } /// Walk an `addr_of` / `&` operand to its field-access-chain root and classify /// it. `Member` / `TurboFish` links are descended; an explicit `(*p)` deref is /// descended too — so `(*p).field` is treated identically to the auto-deref /// sugar `p.field` (both root in `p`; the pointee is stable storage, the C /// `&(p->field)` pattern). An `Index` link short-circuits to `IndexInChain`; /// anything else is an `Rvalue` base. pub fn addr_of_chain_root(mut k: &ExprKind) -> AddrChainRoot { loop { match k { ExprKind::Ident(n) => return AddrChainRoot::Lvalue(Some(n.clone())), ExprKind::SelfAccess => return AddrChainRoot::Lvalue(None), // A `Member` whose name starts with '@' is a bound method VALUE // (`obj.@method`), an rvalue — not an addressable field. (The // `@field` self-access form parses to `Member{obj:SelfAccess, // name:"field"}` — no '@' prefix — so it is unaffected.) ExprKind::Member { obj, name } => { if name.starts_with('@') { return AddrChainRoot::Rvalue; } k = &obj.kind; } ExprKind::TurboFish { base, .. } => k = &base.kind, ExprKind::Unary { op: UnOp::Deref, operand } => k = &operand.kind, ExprKind::Index { .. } => return AddrChainRoot::IndexInChain, _ => return AddrChainRoot::Rvalue, } } } /// Plan 118.1 [M-118.1-addr-of-chains]: root binding name of a PURE value /// field-access chain (`x`, `s.field`, `a.b.c`) — `Some(name)`. Returns `None` /// if the operand passes through a deref / index / `self` / any non-(Member| /// Ident) node, so the `addr_of_mut` mut-binding requirement applies only to /// value-field chains (pointer-deref mutability is governed by `*mut T` typing). pub fn addr_of_mut_root_ident(mut k: &ExprKind) -> Option<&str> { loop { match k { ExprKind::Ident(n) => return Some(n.as_str()), // Bound method value (`obj.@method`) is not a field — bail. ExprKind::Member { obj, name } => { if name.starts_with('@') { return None; } k = &obj.kind; } _ => return None, } } } /// Pattern для match / let / if-let. #[derive(Debug, Clone)] pub enum Pattern { /// `_` Wildcard(Span), /// `42`, `"hello"`, `true` Literal(Literal, Span), /// `name` — связывает (или enum unit-variant без скобок). /// Plan 108.3 (D36 amend): `is_mut` — `mut name` форма (per-name /// в pattern: `let (mut a, b) = ...`; для loop-var `for mut x in ...`). /// Plan 73.2 (D157/D180-амендмент, consume-волна А, 2026-07-19): /// `is_consume` — `consume name` форма, ТОЛЬКО как sub-pattern внутри /// single-arg tuple-variant (`Ok(consume tcp)`, `Some(consume f)`) — /// explicit ownership-transfer биндинг для must-consume пейлоада /// (D156 propagation). Взаимоисключающе с `is_mut` (parse error, /// симметрия D131 «consume и mut на одном биндинге»). Ident { name: String, span: Span, is_mut: bool, is_consume: bool, }, /// `Variant`, `Variant(p1, p2)`, `Cons(h, ..)` — D59 Variant { path: Vec<String>, kind: VariantPatternKind, span: Span, }, /// `{ field: pat, name, .. }` — D17/D52 Record { type_path: Option<Vec<String>>, fields: Vec<RecordPatternField>, rest: bool, // присутствует ли .. span: Span, }, /// `[]`, `[a]`, `[head, ..rest]` — D59 Array { elems: Vec<ArrayPatternElem>, span: Span, }, /// `(a, b, c)` Tuple(Vec<Pattern>, Span), /// `pattern as binding` — TODO (не нужно в bootstrap) Binding { name: String, inner: Box<Pattern>, span: Span, }, /// `p1 | p2 | p3` — alternation в match arm. /// Все варианты должны иметь одинаковый набор bindings (по spec /// pattern-match семантике); в bootstrap'е bindings из первого /// варианта используются в теле arm. Не вкладывается внутрь других /// patterns — alternation только на верхнем уровне match-arm. Or { alternatives: Vec<Pattern>, span: Span, }, } #[derive(Debug, Clone)] pub enum VariantPatternKind { /// `Variant` Unit, /// `Variant(pat1, pat2)` или `Variant(..)` или `Variant(pat, ..)` Tuple { patterns: Vec<Pattern>, rest: bool, }, } #[derive(Debug, Clone)] pub struct RecordPatternField { pub name: String, /// `field: pat` — Some(pat); `field` — None (shorthand) pub pattern: Option<Pattern>, pub span: Span, } #[derive(Debug, Clone)] pub enum ArrayPatternElem { /// `[a, b]` — обычный pattern Item(Pattern), /// `..` — без bind Rest, /// `..rest` — slice-bind (D59) RestBind(String), } #[derive(Debug, Clone)] pub enum Literal { Int(i64), Float(f64), Str(String), Bool(bool), Unit, /// Q-char-literals: 'a' codepoint as u32 (используется в pattern'ах, как `match c { 'a' => ... }`). Char(u32), } impl Pattern { pub fn span(&self) -> Span { match self { Pattern::Wildcard(s) | Pattern::Literal(_, s) | Pattern::Ident { span: s, .. } | Pattern::Variant { span: s, .. } | Pattern::Record { span: s, .. } | Pattern::Array { span: s, .. } | Pattern::Tuple(_, s) | Pattern::Binding { span: s, .. } | Pattern::Or { span: s, .. } => *s, } } } #[cfg(test)] mod typeref_uses_any_type_param_tests { use super::*; use crate::diag::Span; use std::collections::HashSet; fn sp() -> Span { Span::dummy() } fn type_params(names: &[&str]) -> HashSet<String> { names.iter().map(|s| (*s).to_string()).collect() } fn named(path: &[&str]) -> TypeRef { TypeRef::Named { path: path.iter().map(|s| (*s).to_string()).collect(), generics: vec![], span: sp(), } } fn named_with_generics(path: &[&str], generics: Vec<TypeRef>) -> TypeRef { TypeRef::Named { path: path.iter().map(|s| (*s).to_string()).collect(), generics, span: sp(), } } // ─── Positive cases — should return true ───────────────────────── #[test] fn named_is_type_param() { let params = type_params(&["T"]); assert!(named(&["T"]).uses_any_type_param(¶ms)); } #[test] fn named_generic_arg_is_type_param() { // Vec[T] — generic arg is T let params = type_params(&["T"]); let ty = named_with_generics(&["Vec"], vec![named(&["T"])]); assert!(ty.uses_any_type_param(¶ms)); } #[test] fn nested_named_uses_param() { // HashMap[K, Vec[V]] — nested Vec[V] uses V let params = type_params(&["V"]); let inner_vec = named_with_generics(&["Vec"], vec![named(&["V"])]); let ty = named_with_generics(&["HashMap"], vec![named(&["K"]), inner_vec]); assert!(ty.uses_any_type_param(¶ms)); } #[test] fn array_inner_is_param() { let params = type_params(&["T"]); let ty = TypeRef::Array(Box::new(named(&["T"])), sp()); assert!(ty.uses_any_type_param(¶ms)); } #[test] fn fixed_array_inner_is_param() { let params = type_params(&["T"]); let ty = TypeRef::FixedArray(8, Box::new(named(&["T"])), sp()); assert!(ty.uses_any_type_param(¶ms)); } #[test] fn tuple_element_is_param() { // (int, T, str) — second element is T let params = type_params(&["T"]); let ty = TypeRef::Tuple( vec![named(&["int"]), named(&["T"]), named(&["str"])], sp(), ); assert!(ty.uses_any_type_param(¶ms)); } #[test] fn func_param_uses_type_param() { let params = type_params(&["T"]); let ty = TypeRef::Func { params: vec![named(&["int"]), named(&["T"])], return_type: Some(Box::new(named(&["bool"]))), effects: vec![], extern_abi: None, span: sp(), }; assert!(ty.uses_any_type_param(¶ms)); } #[test] fn func_return_uses_type_param() { let params = type_params(&["U"]); let ty = TypeRef::Func { params: vec![named(&["int"])], return_type: Some(Box::new(named(&["U"]))), effects: vec![], extern_abi: None, span: sp(), }; assert!(ty.uses_any_type_param(¶ms)); } #[test] fn readonly_inner_is_param() { let params = type_params(&["T"]); let ty = TypeRef::Readonly(Box::new(named(&["T"])), sp()); assert!(ty.uses_any_type_param(¶ms)); } #[test] fn multi_param_match_any() { // Set {T, U}; TypeRef references U. let params = type_params(&["T", "U", "V"]); let ty = named(&["U"]); assert!(ty.uses_any_type_param(¶ms)); } // ─── Negative cases — should return false ──────────────────────── #[test] fn concrete_named_no_param() { let params = type_params(&["T"]); assert!(!named(&["int"]).uses_any_type_param(¶ms)); assert!(!named(&["str"]).uses_any_type_param(¶ms)); assert!(!named(&["ptr"]).uses_any_type_param(¶ms)); assert!(!named(&["MyType"]).uses_any_type_param(¶ms)); } #[test] fn multi_segment_path_not_param() { // `std.Foo` — multi-segment path; only single-segment matches param let params = type_params(&["T"]); let ty = named(&["std", "T"]); // path has 2 segments; T is final but path.len() > 1 assert!(!ty.uses_any_type_param(¶ms)); } #[test] fn empty_params_set_never_matches() { let params = type_params(&[]); assert!(!named(&["T"]).uses_any_type_param(¶ms)); } #[test] fn unit_never_matches() { let params = type_params(&["T"]); assert!(!TypeRef::Unit(sp()).uses_any_type_param(¶ms)); } #[test] fn concrete_generic_no_param() { // Vec[int] — generic arg is concrete let params = type_params(&["T"]); let ty = named_with_generics(&["Vec"], vec![named(&["int"])]); assert!(!ty.uses_any_type_param(¶ms)); } #[test] fn tuple_all_concrete_no_param() { let params = type_params(&["T"]); let ty = TypeRef::Tuple( vec![named(&["int"]), named(&["str"]), named(&["bool"])], sp(), ); assert!(!ty.uses_any_type_param(¶ms)); } #[test] fn func_all_concrete_no_param() { let params = type_params(&["T"]); let ty = TypeRef::Func { params: vec![named(&["int"]), named(&["str"])], return_type: Some(Box::new(named(&["bool"]))), effects: vec![], extern_abi: None, span: sp(), }; assert!(!ty.uses_any_type_param(¶ms)); } #[test] fn different_param_name_no_match() { let params = type_params(&["T"]); let ty = named(&["U"]); // U not in params set assert!(!ty.uses_any_type_param(¶ms)); } }