Skip to content

fix(transpiler): resolve polymorphic TO_<TYPE> conversions in JSON→ST emitter - #854

Merged
thiagoralves merged 2 commits into
developmentfrom
fix/transpiler-polymorphic-to-conversions
Jun 8, 2026
Merged

fix(transpiler): resolve polymorphic TO_<TYPE> conversions in JSON→ST emitter#854
thiagoralves merged 2 commits into
developmentfrom
fix/transpiler-polymorphic-to-conversions

Conversation

@thiagoralves

@thiagoralves thiagoralves commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Summary

User-reported regression: a project that compiles fine on the web editor failed on the desktop editor with:

main.st:15:30: error: Undefined type 'ANY' in PROGRAM 'MAIN'
  15 |     _TMP_TO_UINT874561_OUT : ANY;

The bug is in the new JSON→ST transpiler from PR #843 — synthetic output temps for polymorphic IEC type-conversion functions (TO_INT, TO_UINT, TO_REAL, …) were left at ANY because the catalog only enumerates source-specific variants (BOOL_TO_UINT, INT_TO_UINT, …), never the polymorphic shortcuts. STruC++ correctly rejects ANY as a variable declaration type — it's a generic placeholder, not a concrete type.

Web compiled the same project because it's still on xml2st in a worker (different compiler, different code path).

Fix

Single file (src/backend/shared/transpilers/st-transpiler/emit/pou-graphical.ts), 27 lines added. After the catalog lookup fails, the resolver now matches ^TO_([A-Z]+)$ against the block name and, if the suffix is one of the 21 known IEC destination types (BOOL/INT/UINT/REAL/DINT/LREAL/BYTE/WORD/DWORD/LWORD/SINT/USINT/LINT/ULINT/UDINT/BCD/TIME/DATE/TOD/DT/STRING), uses the suffix as the output type. Unknown block names still fall through to ANY so STruC++ produces the same clear error for truly-unknown blocks — no regression on the unhappy path.

Why this is essentially a no-op today

With PR #853 just merged, the editor ships with OPENPLC_USE_NEW_TRANSPILER defaulting to OFF — the new JSON→ST transpiler is only exercised when an operator explicitly opts in. Production projects still flow through legacy xml2st. This fix lands the moment-someone-flips-the-toggle behavior we want, without changing default compilation.

Test plan

  • tsc --noEmit clean
  • Manual: with OPENPLC_USE_NEW_TRANSPILER=1, compile ~/Downloads/Blink Demo Multilanguage (two TO_UINT blocks at numericId 874561 and 9788614) — should now emit : UINT instead of : ANY and pass STruC++

Shared-surface mirror

emit/pou-graphical.ts is part of the byte-identical shared surface. A matching commit on openplc-web is required (will be opened in tandem so the sync check passes).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Fixed resolution of type-conversion results so temporary "any" types are correctly assigned their destination type for IEC 61131-3-style conversions, preventing unresolved types and incorrect type propagation in graphical diagrams.

…o concrete output types

Reported by user on the desktop editor compiling a project that
worked on web.  STruC++ rejected the program with:

  main.st:15:30: error: Undefined type 'ANY' in PROGRAM 'MAIN'
    15 |     _TMP_TO_UINT874561_OUT : ANY;

`ANY` is a generic placeholder in IEC 61131-3 — valid as a function
argument type but never as a variable declaration type.  STruC++ is
correct to reject it.  The bug is in the editor's JSON→ST
transpiler (PR #843, recently landed): it was leaving synthetic
output temps for polymorphic IEC type-conversion functions
(`TO_INT`, `TO_UINT`, `TO_REAL`, …) at `ANY` instead of resolving
them to their concrete destination types.

The web editor still compiles this project because it uses xml2st
in a worker (PR #482), not the new JSON→ST transpiler — different
compiler, different code path.  Once the web side also migrates to
JSON→ST, this fix needs to be mirrored.

## Root cause

`generateGraphicalPou` (emit/pou-graphical.ts:71) resolves `ANY` in
synthetic vars by:
  1. Looking for a user-defined project function with that name →
     use its `interface.returnType`.
  2. Looking the name up in `data/std_block_catalog.json` via
     `resolveBlockType` → use the formal output port's type.
  3. (Was missing) Fallback: leave as `ANY`.

The catalog has entries for every *source-typed* conversion
(`BOOL_TO_UINT`, `INT_TO_UINT`, `REAL_TO_UINT`, …) — but NOT for the
polymorphic-input shortcuts (`TO_UINT`, `TO_INT`, …).  IEC 61131-3
defines `TO_<TYPE>(in: ANY) → <TYPE>` as a generic conversion whose
output type is fixed by the function name, with the input type
inferred from the connected source.  The catalog's enumerate-every-
combination approach doesn't capture this family; resolveBlockType
returns null, the fallback kept `ANY`, strucpp rejected.

## Fix

Adds a third resolution case after the catalog lookup fails: if
`originBlockTypeName` matches `^TO_([A-Z]+)$` and the suffix is one
of the 21 known IEC destination types (BOOL, INT, UINT, REAL, DINT,
LREAL, BYTE, WORD, DWORD, LWORD, SINT, USINT, LINT, ULINT, UDINT,
BCD, TIME, DATE, TOD, DT, STRING), use the suffix as the output
type.  Otherwise the existing fallback keeps `ANY` and strucpp
reports the clear "Undefined type" error like before — no regression
on truly-unknown names.

The 21-entry allowlist is hard-coded in pou-graphical.ts with a
comment pinning it to the catalog's `*_TO_<X>` set; a comment notes
that future catalog additions need to be reflected here too.  This
is intentionally more visible than deriving the set at runtime —
the allowlist serves as documentation of which polymorphic
conversions the transpiler claims to support.

## Why not a deeper fix

A more principled solution would have the walker carry the block's
own declared output type (which IS present in the project JSON —
the FBD node's `variant.variables` lists OUT as `UINT` for this
exact case) all the way through to synthetic-var emit, replacing
the catalog round-trip entirely.  That's a walker-side refactor
(walker/ld.ts:687-700 currently only forwards typeName +
numericId + formal parameter name).  Out of scope for a targeted
bug fix.

## Verification

`tsc --noEmit` clean.  No existing unit tests in the transpiler
subtree — verification is via the user retesting the project that
hit the bug (`/Users/thiagoralves/Downloads/Blink Demo Multilanguage`,
which has two `TO_UINT` blocks at numericId 874561 and 9788614).
Expected result: ST compiles cleanly, both temps now declared as
`UINT` instead of `ANY`.

## Shared-surface mirror

This file is part of the byte-identical shared surface between
openplc-editor and openplc-web.  A matching commit on the web side
is required before either branch is merged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 583a2dc1-c468-4eea-b56a-c29ab7469fba

📥 Commits

Reviewing files that changed from the base of the PR and between 0838ddd and 87d5ba5.

📒 Files selected for processing (1)
  • src/backend/shared/transpilers/st-transpiler/emit/pou-graphical.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/backend/shared/transpilers/st-transpiler/emit/pou-graphical.ts

Walkthrough

This PR extends graphical POU emission to properly type synthetic variables created from IEC 61131-3 polymorphic TO_<TYPE> conversion function calls. It adds a hard-coded set of valid conversion target types and resolves synthetic ANY types by extracting the destination type from matching block names.

Changes

TO_ Polymorphic Conversion Type Resolution

Layer / File(s) Summary
TO_ conversion target definition and synthetic type resolution
src/backend/shared/transpilers/st-transpiler/emit/pou-graphical.ts
Introduces TO_CONVERSION_TARGETS read-only set listing valid IEC polymorphic conversion destination types, updates function documentation explaining the TO_<TYPE> resolution behavior, and implements conditional type resolution logic that extracts destination types from block names and updates synthetic var types when the destination is valid.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Suggested labels

bug

Suggested reviewers

  • JoaoGSP
  • vmleroy

Poem

A rabbit hops through conversion streams,
TO_INT, TO_REAL—fulfilling dreams!
No more synthetic types left astray,
They find their forms in the IEC way. 🐰✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers the regression, root cause, fix details, and test plan, but is missing most required DOD checklist items and test coverage metrics. Complete the DOD checklist items (self-review, unit/integration/e2e test status, PO acceptance, coverage %) to meet the repository template requirements.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title directly and clearly describes the main change: fixing polymorphic TO_ conversion resolution in the JSON→ST emitter, which matches the core objective of the PR.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/transpiler-polymorphic-to-conversions

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

CI Format Check rejected the compact 3-per-line layout.  `prettier
--write` reflows the 21-element Set to one-per-line.  No semantic
change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@thiagoralves
thiagoralves merged commit ec7e675 into development Jun 8, 2026
12 checks passed
@thiagoralves
thiagoralves deleted the fix/transpiler-polymorphic-to-conversions branch June 8, 2026 19:31
AnayGarodia added a commit to AnayGarodia/openplc-editor that referenced this pull request Jul 21, 2026
…e call names (Autonomy-Logic#944)

Blocks like TO_INT/TO_UINT/TO_REAL are placed on the FBD/LD canvas with
the generic IEC 61131-3 conversion shorthand as their type name, but
that shorthand is not itself a valid ST function -- only the fully
qualified <SRC>_TO_<DST> family exists (std_block_catalog.json never
has a bare TO_INT entry). The JSON->ST walker emitted the shorthand
verbatim as the call name, producing e.g. `TO_INT(O_R)` for a REAL
source, which matiec/strucpp reject as an undefined function -- the
exact "';' missing at the end of statement" parse error reported in
Autonomy-Logic#944 for `TO_INT(O_R)`.

PR Autonomy-Logic#854 already fixed the sibling defect where the synthesized output
temp's *declared type* stayed ANY for these blocks, but explicitly left
the call-name resolution as future work ("computeConnectionTypes port").
This closes that specific gap: emitFunctionCall (walker/ld.ts) now
resolves the block's single wired input to its declared type (from a
new project/POU variable-type index built in pou-graphical.ts) and
substitutes the concrete <SRC>_TO_<DST> name when the catalog has a
matching entry, falling back to the original shorthand otherwise so an
unresolvable case still surfaces the same "undefined function" error
instead of a different wrong one.

Scope: this fixes the in-process JSON->ST transpiler
(backend/shared/transpilers/st-transpiler/), which is currently opt-in
via OPENPLC_USE_NEW_TRANSPILER and is the path PR Autonomy-Logic#854 already targeted.
The default legacy pipeline (XmlGenerator -> external xml2st subprocess)
has the identical defect inherited from the original Python generator
(xml2st's own PLCGenerator.py also passes the block's raw type name
through unresolved) and needs a separate fix -- old-editor/fbd-xml.ts
has no project-wide variable-type context available at its call site
today, so resolving it there needs its own plumbing change. Flagging
that as follow-up scope rather than bundling a larger, untested change
into this fix.

Verification:
- New tests reproduce the exact issue Autonomy-Logic#944 scenario (REAL O_R ->
  TO_INT -> INT output) at both the walker level (ld.test.ts) and the
  full generateGraphicalPou level (pou-graphical.test.ts); each fails
  against the pre-fix code (bare TO_INT(O_R)) and passes with the fix
  (REAL_TO_INT(O_R)) -- confirmed by running both ways.
- Added coverage for: unconnected input, unknown source type, no
  matching catalog entry, non-conversion blocks (ADD-style), sibling
  conversions (TO_DINT from BOOL), multi-input blocks, and global
  vs. local variable shadowing.
- Full suite: 243 suites / 5216 tests, 0 failures, 0 regressions.
- tsc --noEmit and eslint clean on all changed files.

Could not run the Electron GUI or the external matiec/strucpp
compilers in this environment to compile-verify the generated ST
end-to-end; verification is at the transpiler-output level (the
generated ST text matches the exact fully-qualified call the catalog
and STruC++'s own test suite confirm is valid, e.g. REAL_TO_INT).

Co-Authored-By: Claude Sonnet 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