/
nv-lang
/
nova
Обзор
Документация
Войти
/
nv-lang
/
nova
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
main
std/src/prelude/errors.nv
380 строк
17 KB
Evgeniy Golovin
merge: comment-hygiene-2 — чистка комментариев std (batch 1-20) + линт-свип 330→13
01 авг 2026, 18:13
01 авг 2026, 18:13
bb8b33d
Код
Авторство
О чём код?
// std/prelude/errors.nv — file-based source of truth для prelude error // types. // // Архитектурные правила: // - **ZERO imports.** Все declarations self-contained на primitives // (`int`, `str`). Никаких `use std.runtime.*` — иначе circular // import через auto-imported prelude. // // Bootstrap status: // // - `RuntimeError` declared here как plain `type` (не `external`) — // variants visible для type-checker'а через cross-file resolve. // Codegen НЕ doubly-emit'ит C struct: `emit_type_decl` // skip'ает RuntimeError через RUNTIME_DEFINED_TYPES список — // implementation остаётся в `nova_rt/array.h` // (`Nova_RuntimeError` + `nova_make_RuntimeError_<Variant>` // constructors). // - `init_prelude_decls_from_items` распознаёт RuntimeError type decl, // регистрирует Prelude entry с variants + abi + method_routing // **inherited от HardcodedBaseline** (behavior-preserving migration). // - Pre-populated `sum_schemas["RuntimeError"]` оставлен как ABI-compat // fallback baseline («HardcodedBaseline остаётся fallback; lookup // precedence DeclaredFromPrelude > HardcodedBaseline»). // - `ReadBufferError` — нет HardcodedBaseline entry; codegen // distinguish'ет sum-type через type-name только (runtime-level // payload — string-based throw, см. nova_rt/read_buffer.h). // Variants регистрируются через registry для cross-file resolve. // // `RuntimeNoneError` (unit-type) задекларирован ниже. Декларация — // canonical type-name для type-checker'а; runtime throw остаётся // string-payload (`nova_rt/effects.h`), structured payload отложен // (тот же mechanism что у `ReadBufferError`). // Bottom-тип НЕ мигрируется сюда — это строчный встроенный примитив // `never`, не капитализированный prelude-тип. // // DEFER: throw-point conversion to structured `RuntimeError`-payload // (compiler: a/b на 0, arr[i] out-of-bounds, etc.) остаётся отдельной // задачей — требует расширения fail-frame mechanism с `nova_str` на // `void*` payload. module prelude.errors // ────────────────────────────────────────────────────────────────────────── // Runtime errors (bottom-level runtime errors) // ────────────────────────────────────────────────────────────────────────── /// Bottom-level runtime errors. Thrown automatically by the compiler /// (`a/b` on 0, `arr[i]` out-of-bounds, type mismatch, etc.) or /// manually via `throw`. See D26 spec / D65. /// /// `StackOverflow` and `OutOfMemory` are NOT included — they panic (D13), /// not Fail. /// /// **Bootstrap**: the `Nova_RuntimeError*` typedef + `nova_make_RuntimeError_*` /// constructors live in `nova_rt/array.h` (~lines 557-610). The pre-populated /// `sum_schemas["RuntimeError"]` (emit_c.rs:1029-1048) registers 6 variants; /// `init_prelude_decls_from_items` (Plan 62.C) registers a Prelude entry in /// `sum_schema_registry` — inheriting variant info and the (currently empty) /// `method_routing` — from HardcodedBaseline. /// /// **Variant set strict-equality (Plan 62.A.bis acceptance):** this /// declaration MUST match exactly the 6 variants of the HardcodedBaseline /// entry (sum_schema_registry.rs:458-483). Extending the variant set /// requires an edition bump (Plan 62.F). #stable(since = "0.1") export type RuntimeError enum | DivByZero | Overflow | IndexOutOfBounds { index int, length int } | TypeMismatch(str) | AssertFailed(str) | NoHandler(str) // ────────────────────────────────────────────────────────────────────────── // ReadBuffer errors // ────────────────────────────────────────────────────────────────────────── /// Buffer read error: fewer bytes than expected. Thrown by the /// `@read_*` methods of `ReadBuffer` (see nova_rt/read_buffer.h) when the /// cursor goes past the underlying byte-slice boundary. /// /// **Bootstrap**: the actual error payload in `Nova_Fail_fail` is a /// formatted `nova_str` ("ReadBuffer.UnexpectedEnd: wanted N, available M", /// see nova_rt/read_buffer.h:62-77 and :254-272), NOT a structured /// `Nova_ReadBufferError*`. This Nova-level declaration gives the /// type-checker a canonical type name for `Fail[ReadBufferError]` / /// `Result[T, ReadBufferError]` signatures (see runtime_registry.rs:893-954) /// and pattern-matching constructs. Conversion to a structured payload is /// deferred (see spec/decisions/08-runtime.md §«Bootstrap-ограничения»). /// /// Variants: /// - `UnexpectedEnd { wanted int, available int }` — an attempt to read /// `wanted` bytes when `available < wanted` remain. #stable(since = "0.1") export type ReadBufferError enum | UnexpectedEnd { wanted int, available int } // ────────────────────────────────────────────────────────────────────────── // Option-unwrap error // ────────────────────────────────────────────────────────────────────────── /// Unwrap error of an empty `Option`. Thrown by the `expr!!` operator /// when `expr` is `None` (D85: `Option!!` → `throw RuntimeNoneError`). /// A function using `opt!!` must have `Fail[RuntimeNoneError]` /// in its signature (see spec/decisions/04-effects.md §D85). /// /// Unit type (no variants / fields) — it carries the bare fact "an None /// was unwrapped", no payload. /// /// **Bootstrap:** the actual throw in `nova_rt/effects.h` is a string /// payload (`nova_throw(nova_str_from_cstr("RuntimeNoneError"))`); no /// structured `Nova_RuntimeNoneError*` payload is built. This Nova-level /// declaration gives the type-checker a canonical type name for /// `Fail[RuntimeNoneError]` signatures / pattern-matching. Conversion to a /// structured payload is deferred (same mechanism as `ReadBufferError` — /// see spec/decisions/08-runtime.md §«Bootstrap-ограничения»). /// /// # Examples /// /// ```nova /// fn first_word(s str) Fail[RuntimeNoneError] -> str => /// s.split(" ").next()!! /// ``` #stable(since = "0.1") export type RuntimeNoneError // ────────────────────────────────────────────────────────────────────────── // MultiError — composite error для failable cleanup // ────────────────────────────────────────────────────────────────────────── /// Composite error from a **failable defer body** (D158). /// /// When a cleanup failure occurs **while another error is propagating** — /// the primary error is NOT replaced; the cleanup error is appended to the /// `suppressed` chain. The caller inspects the composite via `.primary()` / `.suppressed()`. /// /// # Scenarios /// /// **A. Cleanup failure on normal exit** — `MultiError { primary: cleanup_err, /// suppressed: [] }`. Single error; surfaces as a plain `Fail`. /// /// **B. Cleanup failure during primary propagation** — `MultiError { /// primary: original_err, suppressed: [cleanup_err1, cleanup_err2, ...] }`. /// The caller sees the root cause + cascaded cleanup failures. /// /// **C. Multi-defer LIFO accumulation** — handled by Plan 100.4.4 (D161). /// /// # Bootstrap /// /// String-based: `primary: str` + `suppressed: []str`. The runtime /// materializes MultiError from the `NovaFailFrame.error_suppressed` chain /// (via `nova_failframe_suppressed_count` / `nova_failframe_suppressed_at`). /// /// Production-grade typed payload (`primary: E1`, `suppressed: []E2`) — /// deferred. /// /// # Examples /// /// ```nova /// fn process() Fail[Err] -> () { /// consume tx = begin() /// defer { tx.commit() } // failable cleanup /// do_work_that_fails()? /// } /// /// match process() { /// Ok(_) => Log.info("done"), /// Err(e) => { /// Log.error("primary: ${e.primary()}") /// for s in e.suppressed() { /// Log.error(" suppressed: ${s}") /// } /// } /// } /// ``` #stable(since = "0.1") export type MultiError { ro primary str ro suppressed []str } /// Returns primary (root cause) error — what was originally thrown before /// cleanup chain fired. #stable(since = "0.1") fn MultiError @primary() -> str => @primary /// Returns chain of suppressed errors (cleanup-fail during propagation), /// in LIFO order of firing (most-recently-fired first). #stable(since = "0.1") fn MultiError @suppressed() -> []str => @suppressed /// Human-readable summary of composite — primary + numbered list of /// suppressed. #stable(since = "0.1") fn MultiError @fmt_chain() -> str => @primary // ────────────────────────────────────────────────────────────────────────── // MultiError iteration + walk + panic detection // ────────────────────────────────────────────────────────────────────────── /// Plan 110 D193: walk over all errors in the chain in LIFO order /// (primary first, then suppressed[0], suppressed[1], ...). /// Returns []str — an array of all messages. #stable(since = "0.1") fn MultiError @walk() -> []str { mut result []str = []str.new(cap: @suppressed.len() + 1) result.push(@primary) for s in @suppressed { result.push(s) } result } /// Plan 110 D193: find the first panic message in the chain (if any). /// Bootstrap: searches the "panic:" prefix in messages — full typed panic /// discrimination after the `any` payload migration ([M-110-multierror-any]). #stable(since = "0.1") fn MultiError @find_first_panic() -> Option[str] { if @primary.starts_with("panic:") { return Some(@primary) } for s in @suppressed { if s.starts_with("panic:") { return Some(s) } } None } // ────────────────────────────────────────────────────────────────────────── // Typed cancel/timeout/truncation errors // ────────────────────────────────────────────────────────────────────────── /// Cancel-as-error payload (D90 §7 amend, Plan 110). On cancel/interrupt /// in a `consume {}` body — the outcome arrives as /// `Failure(CancelError { ... })`. The resource sees a typed cancel reason /// instead of an opaque "cancellation". /// /// **Bootstrap status (Plan 110 Ф.6.6):** declaration here; the codegen /// path `nv_consume_emit_cancel_as_failure` materializes the payload on /// cancel delivery into the body (Plan 110 Ф.9.4). /// /// # Examples /// /// ```nova /// fn Transaction consume @cleanup(outcome ScopeOutcome) Fail[DbError] -> () { /// match outcome { /// Success => @commit()!! /// Failure(err) => { /// if err is CancelError { /// // explicit cancel — rollback w/o full retry logic /// @rollback()!! /// } else { /// @rollback_with_log(err.msg)!! /// } /// } /// Panic(_) => @rollback_emergency() /// } /// } /// ``` #stable(since = "0.1") export type CancelError { ro reason str } /// Scope-deadline exceeded (Plan 174 / D349, plan 173 §3a). Thrown by the /// runtime outward from `supervised(deadline:/timeout:) { ... }` when the /// scope's deadline is reached: all the scope's fibers are cooperatively /// cancelled (same path as `cancel:`), their cleanups run to completion, /// and a typed `TimeoutError` flies outward — caught with `is TimeoutError` /// (D54/174.3) or `with Fail[TimeoutError]`. /// /// `deadline_ns` — the exceeded point on the monotonic clock (absolute /// nanoseconds, implementation epoch; only deltas matter). NB: the /// retracted `CleanupTimeoutError` (D192 retract, Plan 173 Ф.5 item 2) was /// about a resource's cleanup budget and was REMOVED (exceeding = watchdog /// warning + overrun in the ResourceTrace exit event); this type is about /// the deadline of the WHOLE scope (bounded-shutdown) and stays. A real /// USER error inside the scope beats the deadline (USER-precedence): on /// deadline AND user-throw, the user error flies outward, not `TimeoutError`. /// /// # Examples /// /// ```nova /// fn fetch_bounded() Fail[TimeoutError] -> () { /// supervised(timeout: 5.to_seconds()) { /// spawn { slow_download() } // if > 5s — TimeoutError /// } /// } /// /// with Fail[TimeoutError] = |_| interrupt handle_timeout() { /// supervised(deadline: Monotonic.now() + 200.to_millis()) { /// spawn { poll_until_ready() } /// } /// } /// ``` #stable(since = "0.1") export type TimeoutError { ro deadline_ns i64 } /// Sentinel composed into `MultiError.suppressed` when the cleanup-cascade /// depth exceeds the `D193` depth-limit (256). Further composes are silently /// ignored; a pointer to this sentinel signals "cascade was truncated at /// depth N". /// /// **Bootstrap status (Plan 110 Ф.6.6):** declaration here; emission /// in `nv_compose_error` (Plan 110 Ф.6.5). #stable(since = "0.1") export type MultiErrorTruncated { ro depth int } // ────────────────────────────────────────────────────────────────────────── // Char conversion errors // ────────────────────────────────────────────────────────────────────────── /// Error of converting `int` → `char`. Thrown by `(cp int).to_char()` /// when the codepoint is outside [0, 0x10FFFF] or is a surrogate /// [0xD800, 0xDFFF]. /// /// Unit type — the bare fact "invalid codepoint", no payload needed. /// /// Renamed from `CharTryFromError` — `char.try_from` renamed to /// `char.from` (was the only static char conversion without an infallible /// sibling). /// /// # Examples /// /// ```nova /// match (-1).to_char() { /// Ok(c) => ... /// Err(CharFromError) => // codepoint out of range /// } /// ``` #stable(since = "0.1") export type CharFromError /// Error of converting `char` → `u8`. Thrown by `u8.try_from(c char)` /// when the codepoint > 0xFF (the character is not Latin-1). /// /// Unit type — the bare fact "the character does not fit in u8". /// /// # Examples /// /// ```nova /// match u8.try_from('é') { /// Ok(b) => ... /// Err(TryFromCharError) => // codepoint > 0xFF /// } /// ``` #stable(since = "0.1") export type TryFromCharError // ────────────────────────────────────────────────────────────────────────── // Numeric narrowing error // ────────────────────────────────────────────────────────────────────────── /// Error of a checked narrowing numeric conversion (number→number). /// Returned by the `try_to_<T>` family (`std/prelude/protocols.nv`, /// `fn[S Ints] S @try_to_i8()`/`@try_to_u8()`/... — one blanket per target /// type `<T>`, covering the whole `Ints` source set) when the source value /// does not fit the target type `<T>` range (`as` stays the fast truncating /// cast — unchanged). /// /// Unit type — the bare fact "did not fit", no payload needed (same /// precedent as `CharFromError`/`TryFromCharError` above — "a fact without data"). /// /// # Examples /// /// ```nova /// match (300u32).try_to_u8() { /// Ok(v) => ... // fits — value as u8 /// Err(RangeError) => ... // 300 > u8.MAX (255) /// } /// ro n = (-1i32).try_to_u8() /// assert(n == Err(RangeError)) // negative → unsigned /// ``` #stable(since = "0.1") export type RangeError