diff --git a/src/backend/shared/transpilers/st-transpiler/emit/pou-graphical.ts b/src/backend/shared/transpilers/st-transpiler/emit/pou-graphical.ts index 5f68bd18c..0540334e7 100644 --- a/src/backend/shared/transpilers/st-transpiler/emit/pou-graphical.ts +++ b/src/backend/shared/transpilers/st-transpiler/emit/pou-graphical.ts @@ -26,6 +26,38 @@ interface InterfaceEntry { vars: TranspileVariable[] } +/** + * Destination types of the IEC 61131-3 polymorphic conversion family + * (`TO_BOOL`, `TO_INT`, `TO_UINT`, …). Hard-coded here rather than + * derived at runtime from the catalog so any future addition is visible + * in code review. Kept in sync with `data/std_block_catalog.json` — any + * `_TO_` entry in the catalog implies `TO_` is a valid + * polymorphic conversion target. + */ +const TO_CONVERSION_TARGETS: ReadonlySet = new Set([ + 'BCD', + 'BOOL', + 'BYTE', + 'DATE', + 'DINT', + 'DT', + 'DWORD', + 'INT', + 'LINT', + 'LREAL', + 'LWORD', + 'REAL', + 'SINT', + 'STRING', + 'TIME', + 'TOD', + 'UDINT', + 'UINT', + 'ULINT', + 'USINT', + 'WORD', +]) + /* ─────────────────────────── public entry ───────────────────────────────── */ /** @@ -68,6 +100,15 @@ export function generateGraphicalPou(pou: TranspilePou, project: TranspileProjec // …) collapse to `BOOL`, which matches the corpus where these // operators are always Boolean rung logic. A future // computeConnectionTypes port will narrow these properly. + // 3. Polymorphic IEC 61131-3 type-conversion functions of the + // form `TO_` (TO_INT, TO_UINT, TO_REAL, …) — the + // catalog enumerates the source-specific variants + // (`BOOL_TO_UINT`, `INT_TO_UINT`, …) but NOT the generic + // `TO_` family, so resolveBlockType returns null for + // them. Without this case the synthetic var stayed at + // `ANY` and strucpp rejected the program with + // "Undefined type 'ANY' in PROGRAM" — fixed here by reading + // the destination type directly from the function name. const resolvedSyntheticVars = emitted.syntheticVars.map((sv) => { if (sv.type !== 'ANY' || sv.originBlockTypeName === undefined) return sv const referenced = project.pous.find((p) => p.name === sv.originBlockTypeName) @@ -82,6 +123,10 @@ export function generateGraphicalPou(pou: TranspilePou, project: TranspileProjec return { ...sv, type: collapsed } } } + const polymorphicMatch = sv.originBlockTypeName.match(/^TO_([A-Z]+)$/) + if (polymorphicMatch && TO_CONVERSION_TARGETS.has(polymorphicMatch[1])) { + return { ...sv, type: polymorphicMatch[1] } + } return sv })