Skip to content

fix(hir-ty): вызов метода через экспортную переменную модуля приложения - #423

Open
flamber-dev wants to merge 1 commit into
itrous:developfrom
flamber-dev:pr/application-module-exported-variables
Open

flamber-dev wants to merge 1 commit into
itrous:developfrom
flamber-dev:pr/application-module-exported-variables

Conversation

@flamber-dev

Copy link
Copy Markdown
Contributor

Проблема

Переменная, объявленная в модуле управляемого (или обычного) приложения как Перем глМояПеременная Экспорт;,
по документации платформы - глобальная переменная клиентского контекста:

Глобальные переменные, объявленные с ключевым словом Экспорт, доступны для использования в исполняемых
операторах, выражениях, в любой процедуре и функции любого клиентского программного модуля конфигурации.

(Руководство разработчика 8.3.27, глава 4 «Встроенный язык», https://its.1c.eu/db/v8327doc/content/49/hdoc)

Анализатор это уже знает на пути чтения имени: Значение = глМояПеременная; в клиентском модуле молчит, на сервере
даёт UnavailableInEnvironment. Но вызов метода через такую переменную идёт другим путём - через разбор «голого»
получателя вызова, - и там эта поверхность не спрашивается. Получатель проваливается до конца каскада, и develop
сообщает ложную ошибку:

// Модуль управляемого приложения
Перем глМояПеременная Экспорт;
Перем глНастройки Экспорт;

// Клиентский общий модуль МодульКлиент1
Процедура Тест() Экспорт
    глМояПеременная.Метод();               // UnresolvedMethodCall: Не удалось разрешить получателя вызова 'глМояПеременная'
    глНастройки.Вставить("Ключ", 1);       // UnresolvedMethodCall: Не удалось разрешить получателя вызова 'глНастройки'
    Значение = глНастройки.Получить("Ключ"); // UnresolvedMethodCall: Не удалось разрешить получателя вызова 'глНастройки'
КонецПроцедуры

Код компилируется и работает: так устроено хранение клиентских параметров сеанса в библиотеке стандартных подсистем
(экспортная переменная модуля приложения с соответствием внутри) и в подключаемых клиентских компонентах типовых
конфигураций. Находка severity Major, и на рабочих конфигурациях она составляет заметную долю всех
UnresolvedMethodCall (867 из 1260 на одной, 238 из 511 на другой).

Что меняется

crates/hir-ty/src/infer.rs, dispatch_bare_ident_field_call: перед проверкой платформенных глобальных контейнеров
спрашивается та же поверхность экспортов модулей приложения (global_read_export), что и на пути чтения имени. Если
имя - экспортная переменная модуля приложения, разбор отдаётся обычному пути вызова на нетипизированном получателе
(BareReceiverDispatch::Unhandled), как для локальной переменной или переменной модуля.

Порядок тот же, что у resolve_bare_name: общий модуль с тем же именем побеждает (проверка стоит после
user_common_module_exists), реквизит формы тоже (проверка после resolve_form_attribute), платформенные глобальные
имена - нет (переменная пользователя их затеняет). Доступность по окружениям не проверяется второй раз: получатель
уже прочитан infer_expr до этого разбора, и чтение имени проверило, где переменная видна. Поэтому в серверном модуле
вызов по-прежнему не молчит: вместо «не удалось разрешить получателя» остаётся UnavailableInEnvironment на самой
переменной - точная причина, по которой платформа такой модуль не скомпилирует.

Опечатка в имени переменной не экспорт модуля приложения и по-прежнему даёт UnresolvedMethodCall; вызов
несуществующей процедуры без квалификатора по-прежнему даёт UnresolvedBareCall.

Вызов через свойство переменной (глНастройки.Свойство.Метод()) develop и раньше не трогал: получатель там -
выражение, а не имя.

Доказательство

Тесты - crates/ide/tests/infer_application_module_variable_receiver.rs, на фикстуре
bsl-metadata/fixtures/designer: модуль управляемого приложения подменяется текстом с двумя экспортными переменными
и одной экспортной процедурой, вызывающий модуль кладётся на место существующего общего модуля фикстуры с нужными
свойствами (клиентский / серверный).

  • a_call_through_an_application_variable_is_silent_in_a_client_common_module - клиентский общий модуль: ни одного
    UnresolvedMethodCall, ни одного UnavailableInEnvironment;
  • a_call_through_an_application_variable_is_silent_in_a_client_form_procedure - процедура &НаКлиенте модуля
    формы: ни одного UnresolvedMethodCall;
  • an_application_variable_is_unavailable_in_a_server_common_module - серверный общий модуль: UnresolvedMethodCall
    нет, каждое чтение переменной даёт UnavailableInEnvironment;
  • a_misspelled_receiver_and_an_undeclared_bare_call_are_still_reported - контроль: глМояПеремення.Метод()
    остаётся UnresolvedMethodCall, ПроцедураПрилжения() - UnresolvedBareCall, а верный
    ПроцедураПриложения() молчит. Проходит и на develop, и на ветке - правка не глушит находку шире своего случая.

develop (f63a2ebf):

   Compiling ide v0.3.2 (...\wt-dev\crates\ide)
     Running tests\infer_application_module_variable_receiver.rs (...)
running 4 tests
test an_application_variable_is_unavailable_in_a_server_common_module ... FAILED
test a_misspelled_receiver_and_an_undeclared_bare_call_are_still_reported ... ok
test a_call_through_an_application_variable_is_silent_in_a_client_common_module ... FAILED
test a_call_through_an_application_variable_is_silent_in_a_client_form_procedure ... FAILED

thread 'an_application_variable_is_unavailable_in_a_server_common_module' panicked at crates\ide\tests\infer_application_module_variable_receiver.rs:141:5:
["глМояПеременная.Метод", "глНастройки.Вставить", "глНастройки.Получить"]

thread 'a_call_through_an_application_variable_is_silent_in_a_client_common_module' panicked at crates\ide\tests\infer_application_module_variable_receiver.rs:116:5:
an exported application-module variable is a client global: ["глМояПеременная.Метод", "глНастройки.Вставить", "глНастройки.Получить"]

thread 'a_call_through_an_application_variable_is_silent_in_a_client_form_procedure' panicked at crates\ide\tests\infer_application_module_variable_receiver.rs:129:5:
an exported application-module variable is a client global: ["глМояПеременная.Метод", "глНастройки.Вставить"]

test result: FAILED. 1 passed; 3 failed; 0 ignored; 0 measured; 0 filtered out

ветка:

running 4 tests
test a_call_through_an_application_variable_is_silent_in_a_client_form_procedure ... ok
test an_application_variable_is_unavailable_in_a_server_common_module ... ok
test a_call_through_an_application_variable_is_silent_in_a_client_common_module ... ok
test a_misspelled_receiver_and_an_undeclared_bare_call_are_still_reported ... ok
test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

Рабочие конфигурации

Дифференциальный прогон bsl-analyzer-app analyze --format jsonl, release-сборки develop и ветки, на трёх рабочих
конфигурациях (две - управляемое приложение, одна - обычное). Сравнение по ключу «файл, строка, колонка, код,
сообщение»:

Конфигурация Ушло Из них: получатель - экспортная переменная модуля приложения Новых
А (управляемое приложение) 238 UnresolvedMethodCall 238 0
Б (обычное приложение) 6 UnresolvedMethodCall 6 0
В (управляемое приложение) 867 UnresolvedMethodCall 867 0

Три рабочие конфигурации, ложных находок ушло 1111, новых 0; других кодов разница не затронула. Ушедшие находки - в
модулях управляемых форм (в основном формы отчётов), клиентских общих модулях, модулях команд, а в конфигурации на
обычном приложении - в общем модуле (4) и модулях объектов (2), исполняемых толстым клиентом.
Ни на одной из этих строк ветка не выдаёт UnavailableInEnvironment, и общее число UnavailableInEnvironment не
изменилось - ни одна снятая находка не оказалась в коде, где переменная не видна.

Прочее

  • Полный прогон тестов hir-ty и ide на develop и на ветке: списки упавших совпадают (9 тестов, одинаково на обеих сторонах:
    infer_three_level - 6 тестов, graph::tests::method_graph_id_* - 2, name_lookup::tests::the_address_phases_observe_a_cancelled_request), кроме трёх новых тестов, которые падают
    только на develop.
  • cargo fmt --check - чисто; cargo clippy -p hir-ty -p ide --all-targets - вывод совпадает с develop.

Вне рамок

  • Экспортные переменные модуля внешнего соединения и их видимость в коде, исполняемом во внешнем соединении: путь
    тот же (global_read_export учитывает их окружение), отдельного сценария на рабочих конфигурациях не нашлось.
  • Тип значения такой переменной не выводится (из присваивания в модуле приложения): методы через неё не
    проверяются вовсе, как и через любую нетипизированную переменную.

`глПеременная.Метод()`, where `глПеременная` is declared
`Перем глПеременная Экспорт;` in the application module, was reported as
UnresolvedMethodCall «Не удалось разрешить получателя вызова» in client
common modules and client form procedures, although the platform compiles
it: an exported application-module variable is a global of every client
module of the configuration.

The bare-name read path already knew these variables (an untyped user
global, ranked below a common module of the same name and above platform
globals, with its availability checked). The bare-receiver dispatch of a
call did not consult that surface and fell through to the unresolved
receiver. It now does, in the same rank, and leaves the call to the
ordinary path for an untyped receiver. Server code keeps its verdict: the
read of the variable is reported as UnavailableInEnvironment there, and a
misspelled receiver is still an unresolved receiver.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant