feat(meta-compile,meta-validate): проверки на тип-множество там, где его не принимают

Запрет неочевидный, а отказ платформы приходит поздно и одной строкой из
Конфигуратора. Три уровня строгости, все замерены загрузкой в базу на
8.3.24.1691, а не выведены из документации:

- ОШИБКА — множество в составе определяемого типа. Платформа отвергает файл
  целиком («ОпределяемыйТип.<Имя> - Недопустимый тип»); проверено на
  ОпределяемыйТип, Характеристика, ЛюбаяСсылка и голых ссылках, контрольный
  вариант с обычным CatalogRef.<Имя> принят. Отсюда и ноль таких узлов в
  корпусе — это запрет, а не совпадение.
- ПРЕДУПРЕЖДЕНИЕ — голый метатип в типе значения ПВХ. Загрузка проходит, но
  Конфигуратор такой тип не предлагает: в дереве выбора СправочникСсылка и
  ДокументСсылка — папки без флажка, а ЛюбаяСсылки там нет вовсе. Плюс AnyRef
  раундтрипом возвращается как AnyIBRef, то есть молча меняется.
- ПРЕДУПРЕЖДЕНИЕ — определяемый тип одним из составного. Конфигуратор даёт
  выбрать его только единственным, но отказывать нельзя: в корпусе erp+acc
  6500 единственных против 1 составного, и этот один лежит в типовой ERP
  (Документ.НачислениеИСписаниеБонусныхБаллов.Баллы — два определяемых типа
  подряд). Навык не вправе отказаться собрать то, что поставляет 1С.

В meta-compile состав множеств берётся опросом самого эмиттера, а не второй
копией его регулярок: список видов живёт в Emit-TypeContent, и копия
разъехалась бы с ним молча.

Развёртка по 3428 объектам ERP: ровно 2 срабатывания, оба на том самом
реальном составном реквизите. Ложных нет.

Предупреждения пишутся прямо в stderr, а не через Write-Warning: в PS 5.1 тот
уходит не в тот поток (конвенция из cf-init), и до проверки они не доезжали.

Оба кейса-предупреждения проверены платформой — она эти конфигурации грузит,
что и подтверждает выбор «предупреждение, а не отказ».

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-09-20 18:45:16 +03:00
co-authored by Claude Opus 5
parent 079a177cf4
commit 1481e58bfc
67 changed files with 4022 additions and 9 deletions
@@ -1,4 +1,4 @@
# meta-compile v1.113 — Compile 1C metadata object from JSON
# meta-compile v1.114 — Compile 1C metadata object from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param(
@@ -982,10 +982,41 @@ function Emit-TypeContent {
function Emit-ValueType {
param([string]$indent, [string]$typeStr)
X "$indent<Type>"
$mark = $script:xml.Length
Emit-TypeContent "$indent`t" $typeStr
Warn-DefinedTypeInComposite $mark
X "$indent</Type>"
}
# Определяемый тип Конфигуратор даёт выбрать только ЕДИНСТВЕННЫМ, не одним из составного.
# Загрузчик такое принимает, и в типовой ERP один такой реквизит есть
# (Документ.НачислениеИСписаниеБонусныхБаллов.Баллы — два определяемых типа подряд), поэтому
# предупреждение, а не отказ: иначе навык не собрал бы того, что поставляет 1С. Соотношение в
# корпусе erp+acc — 6500 единственных против 1 составного.
function Warn-DefinedTypeInComposite([int]$fromLength) {
$frag = $script:xml.ToString().Substring($fromLength)
$members = [regex]::Matches($frag, '<v8:(Type|TypeSet)>').Count
if ($members -lt 2) { return }
$dts = @()
foreach ($m in [regex]::Matches($frag, '<v8:TypeSet>cfg:(DefinedType\.[^<]+)</v8:TypeSet>')) { $dts += $m.Groups[1].Value }
if ($dts.Count -gt 0) {
# Прямо в stderr, а не Write-Warning: в PS 5.1 предупреждение уходит не в тот поток
# (см. cf-init) и до expect.stderrContains не доезжает.
[Console]::Error.WriteLine("WARNING: Составной тип содержит определяемый тип (" + ($dts -join ', ') +
"). Конфигуратор даёт выбрать определяемый тип только единственным — собрать такое руками не получится. Платформа загрузит.")
}
}
# Что из только что записанного фрагмента ушло МНОЖЕСТВОМ. Спрашиваем сам эмиттер, а не повторяем
# его регулярки: список видов, дающих v8:TypeSet, живёт в Emit-TypeContent, и вторая копия
# разъехалась бы с ним молча — ровно тот класс отказа, от которого держим гарды.
function Get-EmittedTypeSets([int]$fromLength) {
$sets = @()
$frag = $script:xml.ToString().Substring($fromLength)
foreach ($m in [regex]::Matches($frag, '<v8:TypeSet>cfg:([^<]+)</v8:TypeSet>')) { $sets += $m.Groups[1].Value }
return $sets
}
# --- FillValue (значение заполнения реквизита) ---
# Пара FillFromFillingValue+FillValue — единый блок «заполнения» (недоступен у реквизитов ТЧ).
# Форма пустого FillValue зависит от типа реквизита (то же значение по умолчанию, что и «пустое»
@@ -3044,7 +3075,20 @@ function Emit-DefinedTypeProperties {
$vt = if ($def.valueType) { "$($def.valueType)" }
elseif ($def.valueTypes) { (@($def.valueTypes) | ForEach-Object { "$_" }) -join ' + ' }
else { '' }
if ($vt) { Emit-ValueType $i $vt } else { X "$i<Type/>" }
if ($vt) {
# Состав определяемого типа — только конкретные типы. Тип-множество внутри платформа не
# принимает: загрузка падает целиком с «ОпределяемыйТип.<Имя> - Недопустимый тип» (замерено
# на 8.3.24.1691 для ОпределяемыйТип, Характеристика, ЛюбаяСсылка и голых ссылок).
# Отказываем здесь, чтобы ошибка называла причину, а не приходила из Конфигуратора.
$mark = $script:xml.Length
Emit-ValueType $i $vt
$sets = @(Get-EmittedTypeSets $mark)
if ($sets.Count -gt 0) {
Write-Error ("Определяемый тип '$objName': в составе тип-множество — " + ($sets -join ', ') +
". Платформа такую конфигурацию не загрузит («Недопустимый тип»). Состав определяемого типа — только конкретные типы (CatalogRef.<Имя>, Number(15,2) и т.п.).")
exit 1
}
} else { X "$i<Type/>" }
}
function Emit-FunctionalOptionProperties {
@@ -3603,7 +3647,18 @@ function Emit-ChartOfCharacteristicTypesProperties {
$vt = $def.valueType; if (-not $vt -and $def.valueTypes) { $vt = ($def.valueTypes -join ' + ') }
if ($vt) {
X "$i<Type>"
$mark = $script:xml.Length
Emit-TypeContent "$i`t" "$vt"
# Определяемый тип и характеристику Конфигуратор в типе значения ПВХ предлагает, а голый
# метатип (СправочникСсылка/ДокументСсылка/…) и ЛюбаяСсылка — нет: в дереве выбора это
# папки без флажка. Загрузку платформа пропускает, но ЛюбаяСсылка молча превращается в
# ЛюбаяСсылкуИБ на выгрузке. Предупреждаем: не ошибка, но руками так не собрать.
Warn-DefinedTypeInComposite $mark
foreach ($ts in @(Get-EmittedTypeSets $mark)) {
if ($ts -notmatch '^(DefinedType|Characteristic)\.') {
[Console]::Error.WriteLine("WARNING: План видов характеристик '$objName': тип значения '$ts' Конфигуратор не предлагает (в дереве выбора это папка без флажка). Платформа загрузит, но AnyRef вернётся как AnyIBRef. Обычно нужен конкретный тип или ОпределяемыйТип.<Имя>.")
}
}
X "$i</Type>"
} else {
X "$i<Type>"
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# meta-compile v1.113 — Compile 1C metadata object from JSON
# meta-compile v1.114 — Compile 1C metadata object from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -1091,9 +1091,35 @@ def emit_type_content(indent, type_str):
def emit_value_type(indent, type_str):
X(f'{indent}<Type>')
mark = len(lines)
emit_type_content(f'{indent}\t', type_str)
warn_defined_type_in_composite(mark)
X(f'{indent}</Type>')
# Определяемый тип Конфигуратор даёт выбрать только ЕДИНСТВЕННЫМ, не одним из составного.
# Загрузчик такое принимает, и в типовой ERP один такой реквизит есть
# (Документ.НачислениеИСписаниеБонусныхБаллов.Баллы — два определяемых типа подряд), поэтому
# предупреждение, а не отказ: иначе навык не собрал бы того, что поставляет 1С. Соотношение в
# корпусе erp+acc — 6500 единственных против 1 составного.
def warn_defined_type_in_composite(from_index):
frag = chr(10).join(lines[from_index:])
if len(re.findall(r'<v8:(?:Type|TypeSet)>', frag)) < 2:
return
dts = re.findall(r'<v8:TypeSet>cfg:(DefinedType[.][^<]+)</v8:TypeSet>', frag)
if dts:
print("WARNING: Составной тип содержит определяемый тип (" + ', '.join(dts) +
"). Конфигуратор даёт выбрать определяемый тип только единственным — собрать такое "
"руками не получится. Платформа загрузит.", file=sys.stderr)
# Что из только что записанного фрагмента ушло МНОЖЕСТВОМ. Спрашиваем сам эмиттер, а не повторяем
# его регулярки: список видов, дающих v8:TypeSet, живёт в emit_type_content, и вторая копия
# разъехалась бы с ним молча — ровно тот класс отказа, от которого держим гарды.
def get_emitted_type_sets(from_index):
out = []
for ln in lines[from_index:]:
out += re.findall(r'<v8:TypeSet>cfg:([^<]+)</v8:TypeSet>', ln)
return out
# --- FillValue (значение заполнения реквизита) ---
# Пара FillFromFillingValue+FillValue — единый блок «заполнения» (недоступен у реквизитов ТЧ).
# Форма пустого FillValue зависит от типа реквизита (то же значение по умолчанию, что и «пустое»
@@ -3132,7 +3158,19 @@ def emit_defined_type_properties(indent):
else:
vt = ''
if vt:
# Состав определяемого типа — только конкретные типы. Тип-множество внутри платформа не
# принимает: загрузка падает целиком с «ОпределяемыйТип.<Имя> - Недопустимый тип» (замерено
# на 8.3.24.1691 для ОпределяемыйТип, Характеристика, ЛюбаяСсылка и голых ссылок).
# Отказываем здесь, чтобы ошибка называла причину, а не приходила из Конфигуратора.
mark = len(lines)
emit_value_type(i, vt)
sets = get_emitted_type_sets(mark)
if sets:
print(f"Определяемый тип '{obj_name}': в составе тип-множество — " + ', '.join(sets) +
". Платформа такую конфигурацию не загрузит («Недопустимый тип»). "
"Состав определяемого типа — только конкретные типы (CatalogRef.<Имя>, Number(15,2) и т.п.).",
file=sys.stderr)
sys.exit(1)
else:
X(f'{i}<Type/>')
@@ -3699,7 +3737,18 @@ def emit_chart_of_characteristic_types_properties(indent):
vt = ' + '.join(defn['valueTypes'])
if vt:
X(f'{i}<Type>')
mark = len(lines)
emit_type_content(f'{i}\t', str(vt))
# Определяемый тип и характеристику Конфигуратор в типе значения ПВХ предлагает, а голый
# метатип (СправочникСсылка/ДокументСсылка/…) и ЛюбаяСсылка — нет: в дереве выбора это
# папки без флажка. Загрузку платформа пропускает, но ЛюбаяСсылка молча превращается в
# ЛюбаяСсылкуИБ на выгрузке. Предупреждаем: не ошибка, но руками так не собрать.
warn_defined_type_in_composite(mark)
for ts in get_emitted_type_sets(mark):
if not re.match(r'^(DefinedType|Characteristic)[.]', ts):
print(f"WARNING: План видов характеристик '{obj_name}': тип значения '{ts}' Конфигуратор не предлагает "
"(в дереве выбора это папка без флажка). Платформа загрузит, но AnyRef вернётся как AnyIBRef. "
"Обычно нужен конкретный тип или ОпределяемыйТип.<Имя>.", file=sys.stderr)
X(f'{i}</Type>')
else:
X(f'{i}<Type>')