/
nv-lang
/
nova
Обзор
Документация
Войти
/
nv-lang
/
nova
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
main
compiler-codegen/src/codegen/mono_method_registry.rs
137 строк
11 KB
Evgeniy Golovin
fix(170, приёмка): дифф ужат +80→+18 (обоснования в mono_method_registry §№170), ratchet путь B, реестры сведены
31 июл 2026, 03:29
31 июл 2026, 03:29
cb63eb6
Код
Авторство
О чём код?
//! №129 (реестр 221.1, №125-корень / №149-блокер): честная диагностика для //! `mono_method_decls` — единственный `HashMap<(String, String), FnDecl>` в //! `emit_c.rs`'s `CEmitter`, ключ = (receiver-spelling, method-name), одно //! `FnDecl` на ключ. //! //! Для РЕАЛЬНОГО generic-типа (`Vec`, пользовательский `Container[T]`, …) этот //! ключ уникален на исходную декларацию — чекер (`types/mod.rs`, обработка //! `Item::Fn`, ~строка 1331) уже отклоняет буквально дублирующую (та же //! arity+типы-параметров+return-type) декларацию как `E_METHOD_REDEFINITION` //! / «duplicate definition ... with same signature». //! //! Для BLANKET-декларации (`fn[I Proto[T]] I @m(...)`, D355) `recv_type_name` //! — это ГОЛОЕ ИМЯ типопараметра-получателя («I»), НЕ реальный тип. //! `check_blanket_conflict` (types/mod.rs, D355 §5) ловит конфликт только //! ДВУХ blanket'ов на ОДИН и тот же протокол (группирует по имени протокола, //! сознательно НЕ по буквенному имени typevar — см. её doc-comment). Два //! blanket'а на РАЗНЫЕ протоколы, использующие ОДНУ и ту же идиоматичную //! букву («I», «T», …) и ОДНО имя метода, протокольно не конфликтуют — и, //! если сигнатуры при этом различаются (арность / мутабельность получателя / //! тип возврата — все три валидные D84-overload оси, types/mod.rs ~1353-1376), //! чекер держит ОБА объявления как отдельные записи в `env.fns`. У //! `mono_method_decls` такой оси нет: она хранит РОВНО ОДНО `FnDecl` на ключ, //! так что вторая регистрация тут молча вытесняла бы первую (last-wins) — и //! диспетч для ЛЮБОГО из двух получателей ошибочно исполнял бы тело второго. //! //! Эмпирически (аудит №129, `scratch_repro/p129_repro*.nv` в //! `nova-p129`-worktree): без вызова конфликтующего метода сегодня программа //! собирается ЧИСТО, без единой диагностики (последняя декларация тихо //! победила); с вызовом — падает во ВНУТРЕННЮЮ ошибку компилятора //! (`P67-LEGACY`, отдельный, вне периметра №129, пробел резолва return-type //! чекером для multi-candidate blanket'ов) ДО того, как этот реестр вообще //! читается для диспетча. Ни то, ни другое — не честная диагностика. //! //! Диагностируем ЗДЕСЬ, в момент регистрации (форвард-декларация), — фиксируя //! коллизию независимо от того, вызывается ли неоднозначный метод вообще, до //! того как любой из двух плохих исходов может произойти. Повторная вставка //! ТОГО ЖЕ физического объявления (тот же `Span`) — idempotent, не ошибка. use crate::ast::FnDecl; use crate::diag::Span; /// Форма сигнатуры для D84-осей overload'а: арность, мутабельность/consume /// получателя, текстовые типы параметров, тип возврата. Две декларации с /// РАЗНОЙ формой — легальный overload (чекер держит обе в `env.fns`, диспетч /// у вызова идёт по точной перегрузке через `[M-138.2]` /// `mono_method_fndecl_for_name` / by-span №130 — значение `mono_method_decls` /// для них лишь fallback, и last-wins для этого случая эмпирически безвреден: /// весь зелёный корпус 592 теста жил на нём). Две декларации с ОДНОЙ формой — /// настоящая коллизия: у чекера они не могли пройти E_METHOD_REDEFINITION для /// реального типа, значит это blanket-пара разных протоколов под одной буквой /// typevar (№129-кейс) — честная ошибка. /// /// Ложное срабатывание, поймано приёмкой №129 на мега-CU (гейт интегратора, /// 2026-07-30): `Router130 @get(path, h)` / `@get(path, h, opts)` — легальный /// арность-overload на КОНКРЕТНОМ типе бил ошибкой. Отсюда это уточнение: /// сравниваем форму, а не только span. fn signature_shape(f: &FnDecl) -> String { let recv = f .receiver .as_ref() .map(|r| format!("mut={} consume={}", r.mutable, r.consume)) .unwrap_or_default(); let params: Vec<String> = f.params.iter().map(|p| format!("{:?}", p.ty)).collect(); format!( "{recv}|{arity}|{params:?}|{ret:?}", arity = f.params.len(), params = params, ret = f.return_type, ) } /// `Ok(())` — можно вставлять (новый ключ, повторная регистрация той же /// декларации, либо легальный D84-overload с РАЗНОЙ формой сигнатуры — для /// него сохраняется прежнее поведение реестра). `Err(..)` — коллизия РАЗНЫХ /// деклов ОДНОЙ формы на одном ключе; текст диагностики цитирует оба span'а. pub(crate) fn check_mono_method_decl_collision( existing: Option<&FnDecl>, new_span: Span, recv_type_name: &str, method_name: &str, new_decl: &FnDecl, ) -> Result<(), String> { let Some(existing) = existing else { return Ok(()); }; if existing.span == new_span { return Ok(()); // та же физическая декларация — idempotent re-insert. } if signature_shape(existing) != signature_shape(new_decl) { return Ok(()); // легальный D84-overload — оси различают, вставка разрешена. } Err(format!( "[E_MONO_METHOD_KEY_COLLISION] два РАЗНЫХ объявления метода `@{method}()` \ делят один ключ (получатель=`{recv_type_name}`, метод=`{method_name}`) в реестре \ generic-методов кодогена (`mono_method_decls`), который хранит РОВНО ОДНО FnDecl на \ ключ — например, два blanket-объявления (`fn[{recv_type_name} Proto[T]] \ {recv_type_name} @{method}(...)`), связанных РАЗНЫМИ протоколами, но использующих ОДНУ \ и ту же букву типопараметра-получателя и одно имя метода (D355 §5 ловит только \ конфликт в пределах ОДНОГО протокола — разные протоколы под тем же \ `{recv_type_name}` сюда не попадают), либо валидный по D84 overload (арность / \ мутабельность получателя / тип возврата — чекер разрешает как отдельные записи в \ `env.fns`, но здесь для них ровно одно место). Второе объявление молча вытеснило бы \ первое (last-wins): диспетч по ЛЮБОМУ из двух получателей ошибочно исполнял бы тело \ ВТОРОГО.\n первое объявление: {existing_span}\n второе объявление: {new_span}\n \ fix: дай получателю-typevar РАЗНУЮ букву в одной из деклараций (например, `fn[J \ ДругойProto[T]] J @{method}(...)`), либо переименуй один из методов — реестр кодогена \ не различает две декларации с одним и тем же (получатель, имя метода), даже если \ чекер считает их валидными отдельными объявлениями.", method = method_name, recv_type_name = recv_type_name, existing_span = existing.span, new_span = new_span, )) } // ─── §№170 [M-generic-body-calls-generic-mono-placeholder] — обоснование ──── // // `resolve_mono_recv_nova_name` (emit_c.rs): worklist-ключ mono-метода — это // НАМЕРЕННО голое имя typevar'а («T» для `fn[T Ints] T @m()`, маршрутизация // per-declaration: `register_mono_method_instance`, recv_type_key = tvname). // Но self-call диспетч (Plan 132.1 Ф.1) читает `current_receiver_type` как // ключ `method_overloads` ДОСЛОВНО — с сырым «T» он совпадал с ключом // немонообразованной базовой декларации callee (плейсхолдер `Nova_T_method_*`) // и срабатывал РАНЬШЕ protocol-aware blanket-диспетча, который подставил бы // T и зарегистрировал реальную инстанцию. Отсюда CC-FAIL класса // «initializing NovaValue_BigInt with int» (bigint/bigdecimal, D310 type-set // блокеты, int128) — и вынужденные 10 перегрузок вместо одной generic. // // СУЖЕНИЕ до примитивных скаляров (substituted C-тип без "Nova" и не `*`): // первая, широкая версия резолвила и protocol-bound blanket-ресиверы // (`fn[I Next[T]] I mut @fold()`, std vec_iter/SplitIter/адаптеры, D355) — // и мега-CU окна поймал ABI-регрессию: резолвленное имя («SplitIter») // совпадало с ключом НЕЗАВИСИМО объявленного конкретного метода того же // имени с ДРУГИМ ABI (byref), а ранний self-call диспетч не делает // prepare_method_recv-конверсию — value уходил туда, где ждали указатель. // Откат на baseline-бинарь подтвердил: регрессия фикса, не корпуса. // Примитивные скаляры этого ABI-раскола не имеют (всегда by-value) — // сужение безопасно для класса бага и byte-identical для всех // struct/value-generic ресиверов.