Skip to content

Release 0.7.0 - #249

Merged
marconetsf merged 10 commits into
mainfrom
development
Sep 28, 2026
Merged

marconetsf merged 10 commits into
mainfrom
development

Conversation

@marconetsf

Copy link
Copy Markdown
Contributor

Promotes development to main for the 0.7.0 release. Tagging v0.7.0 on main afterwards kicks the Build & Release workflow.

Minor, not patch: the header-only runtime now compiles as C++14 instead of C++17, which changes what a consumer's toolchain has to offer.

What is in it

#247 — the runtime compiles as C++14. The generated code is built by the Arduino core's own toolchain, and eleven of the seventeen cores in the catalogue sit below C++17 — the mbed cores hard-code gnu++14. A C++17 runtime could only reach those through a separate pre-compile pass that ran outside arduino-cli, and that pass is what kept a C++ function block from resolving an Arduino #include. Removing the C++17 requirement is what lets the whole sketch go through arduino-cli again.

The port itself: 31 std::trait_v<T> substitutions, if constexpr replaced by tag dispatch in ABS, MOD and iec_convert_value, MAX/MIN folded by recursion, three mutable globals moved behind an accessor, and 14 inline constexpr constants losing the inline.

Three things the first pass left behind, all fixed here:

  • iec_test.hpp still used std::is_same_v, so strucpp --test failed to compile the moment the CLI moved to -std=c++14;
  • the variadic MAX/MIN had been narrowed — MAX(a, b, 0) and MAX(a, b, someDint) stopped compiling, because the recursion could no longer reach the mixed-type overload that exists for OSCAT. Measured against development, the old code silently truncated that call: MAX(3, 9, 70000) returned 4464, since the declared return type forced the promoted value back into 16 bits. It returns 70000 now;
  • the integration suite hard-coded -std=c++17 at 17 call sites, which is why neither showed up. It now goes through a single CXX_STD constant, so the suite compiles at the standard the product ships.

#include <concepts> sat inside namespace strucpp behind a C++20 guard that only opens at gnu++2b — a latent bug the port exposed, since on an ESP32 it pulled the whole of std in under strucpp::. Hoisted to file scope.

Codegen still emits inline variables for VAR_GLOBAL. Replacing them was tried and reverted: the debug table takes each variable's address into PROGMEM, which needs a constant expression, so a reference or a static-local breaks every AVR build of a project that declares a global. The constraint is now written at both emission sites.

#237 — LSP semantic tokens: overlapping tokens for arrays, and the synthetic-type guard narrowed to the inline-array prefixes.

Verification

  • Full suite green at 97 files / 2435 passed / 7 skipped — the first run where that number means something, since the tests no longer compile at C++17.
  • Host A/B against the pre-port build: identical output.
  • Three ST regression programs read out of running PLCs — IEC math, IEC types, and global storage — on Runtime v4, an ESP32 WROOM and the AVR Simulator.
  • 65-board compile matrix: 63/65, unchanged, no core-version drift. The two failures are Arduino Due, whose gcc 4.8.3 does not reach C++14 either.

Why now

openplc-editor#1153 and openplc-web#793 (DOPE-676) pin this release and cannot merge without it: with the pre-compile gone, every strucpp translation unit needs a runtime that builds at the core's own standard.

🤖 Generated with Claude Code

https://claude.ai/code/session_01X3cRP8dMZScXDKhDXvimmM

JoaoGSP and others added 10 commits September 16, 2026 17:34
A client that receives overlapping ranges drops tokens and warns, so
highlighting silently degraded on any line declaring or indexing an
array. Three causes, two of them real defects and one missing guard.

`getChildren` pushed a VariableExpression's `subscripts` and the indices
carried by its `accessChain`. Those are separate clones of the same
indices, so every subscript index was walked twice and emitted twice.
This was not only a highlighting problem: references and rename were
counting index variables twice as well.

`emitTypeReference` painted `name.length` characters from the type's
start column, but an inline array's TypeReference is synthesised —
`__INLINE_ARRAY_INT`, `__VLA_…` — and carries the whole declaration's
span, so it painted 18 characters over the variable name. No token can
be correct for a name that is not in the source, so skip them.

`deltaEncode` now drops fully covered tokens and clips partial overlaps
after the sort, which makes this class of defect unreachable from the
client whatever the emitters do.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P2Wpvri7jZKGPUqgWrpAkE
`startsWith("__")` suppressed more than the synthesised names it was
meant for. `__XWORD` is a real elementary type, so `addr : __XWORD;`
lost its type token entirely, and any user type whose name opens with
underscores lost its highlighting the same way.

Match the two prefixes the AST builder actually mints instead, and cover
both cases with a test.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jjy1Ja62BFtay579KYhBC7
…antic-token-overlaps

fix(lsp): stop emitting overlapping semantic tokens for arrays (RTOP-287)
The runtime is included by strucpp's emitted code, which on an Arduino target
is compiled by the board core's own toolchain at whatever standard that core
picked. Half the catalogue sits below C++17 — the five mbed cores hard-code
`-std=gnu++14`, and avr, megaavr, samd and two vendor cores are on `gnu++11` —
so every C++17 construct reachable from a generated file was a C++ block that
could not build there. Several headers already carried a "KEEP THIS HEADER
C++14-CLEAN" banner for exactly that reason; `iec_std_lib.hpp`, the largest
one, never did and drifted.

What changed, all of it inside the runtime:

- `std::trait_v<T>` -> `std::trait<T>::value`, 31 sites across three headers.
  This alone took the
  full runtime surface from 147 errors to 0 under `-std=c++14`; the rest were
  cascades.
- Inline variables: the 14 constants become plain `constexpr` (internal
  linkage per TU, the pre-C++17 idiom), and the three mutable slots
  — `__CURRENT_TIME_NS` in both its threaded and single-threaded forms, and
  `__CURRENT_DT_NS` — become a function-local static reached through a
  reference, so no use site changes.
- `if constexpr`: `ABS`, `iec_convert_value` and `MOD` move to tag dispatch;
  `MAX` and `MIN` recurse onto the two-argument overload that already existed
  in the file instead of testing `sizeof...(rest)`.

`iec_test.hpp` includes `iec_std_lib.hpp` rather than forward-declaring
`__CURRENT_TIME_NS`: the slot is no longer a plain object a declaration can
restate, and the header is the honest dependency.

The CLI now drives g++ at `-std=c++14` for both `test` and `repl`, so the
suite is the regression guard — a C++17 construct reintroduced into the
runtime fails there rather than in a user's firmware build.

Verified: `-std=c++14 -pedantic` clean (0 errors, 0 warnings) and `-std=c++17`
unchanged at 0; the full suite green at 2435 passed / 7 skipped across 97
files, including the integration tests that compile and run the generated C++.

Not addressed: `arduino:sam` (Arduino Due) stays out. Its gcc 4.8.3 tops out
at `gnu++1y` and implements neither relaxed constexpr nor the C++14 rule that
a constexpr member function is not implicitly const, and it rejects the
generic lambda the structure-initializer codegen emits.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X3cRP8dMZScXDKhDXvimmM
`iec_traits.hpp` and `iec_types.hpp` declare their C++20 concepts inside
`namespace strucpp`, and the `#include <concepts>` that those declarations need
sat there with them, behind `#if __cplusplus >= 202002L`. Including a standard
header from inside a namespace nests everything it declares — and everything it
pulls in transitively — under that namespace.

The guard never opened before, so the bug never fired: generated code was
compiled at a standard the editor pinned, and it pinned C++17. Letting the
board's core choose the standard is enough to open it, and an ESP32 compiles the
sketch at `gnu++2b`. What comes out is the whole of `std` reachable only as
`strucpp::std`, with the failure surfacing deep in libstdc++:

    error: 'is_constant_evaluated' is not a member of 'std';
           did you mean 'strucpp::std::is_constant_evaluated'?

The include moves to file scope in both headers, keeping the version guard. The
runtime now compiles clean at C++14, 17, 20 and 23 — a range that was never
exercised, because nothing built it above 17.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X3cRP8dMZScXDKhDXvimmM
…est standard

Three things the port left behind, all of them only reachable once something
actually compiled at C++14.

`iec_test.hpp` still used `std::is_same_v` in `assert_eq` / `assert_neq`, so
`strucpp --test` failed to compile the moment the CLI moved to `-std=c++14`.
Those were the last two `_v<>` spellings in the runtime.

The variadic `MAX` / `MIN` had been narrowed: dropping `if constexpr` in favour
of recursion introduced a named `T third`, which forced the third argument to
the same type as the first two. `MAX(a, b, 0)` and `MAX(a, b, someDint)`
compiled before and stopped compiling, at both c++14 and c++17, because the
recursion could no longer reach the mixed-type two-argument overload that
exists for OSCAT. Generated ST is unaffected — codegen casts every argument —
but hand-written C++ calling into the runtime is. `third` now carries its own
type again.

The integration suite hard-coded `-std=c++17` at 17 call sites, which is why
neither showed up: the suite stayed green by never compiling at the standard
the product ships. It now goes through a single `CXX_STD` constant.

Codegen still emits `inline` variables for VAR_GLOBAL, which are C++17 in C++14
code. GCC takes them as an extension and warns; strict C++14 and MSVC reject
them. They stay, and the reason is now written at both emission sites: the
debug table takes each variable's address into PROGMEM, which needs a constant
expression, so a reference or a static-local does not work — on AVR a project
declaring a global then fails with "variable 'debug_arr_0' with dynamic
initialization put into program memory area". Replacing them needs `extern` in
the header plus a definition in one designated translation unit, and codegen
currently spreads implementation across several. `integer-literal-exact-cpp`
carries `-Wno-c++17-extensions` for the same reason, since it opts into
`-pedantic-errors` for an unrelated check.

Also narrows the README toolchain claim to what is exercised (g++ 7.3+), and
corrects comments describing `inline constexpr` constants that are plain
`constexpr` now, and therefore one copy per translation unit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X3cRP8dMZScXDKhDXvimmM
The linkage note was added but the two sentences it contradicts were left in
place: the table is no longer `inline`, so "kept inline so no separate .cpp is
required" and "whatever `inline constexpr` suggests" both described a spelling
that is gone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X3cRP8dMZScXDKhDXvimmM
refactor(runtime): compile the header-only runtime as C++14 (DOPE-676)
A minor rather than a patch: the header-only runtime now compiles as C++14
instead of C++17, which changes what a consumer's toolchain has to offer.

Produced with `npm version 0.7.0 --no-git-tag-version` plus a build, so both
`package-lock.json` entries move together and `src/version-build.ts` is
regenerated by `rebuild-libs.mjs` rather than edited — the version string the
npm tarball ships comes from that file, and a hand edit is what leaves a
re-bundling consumer reporting 0.0.0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X3cRP8dMZScXDKhDXvimmM
@marconetsf
marconetsf merged commit 72dfcb8 into main Sep 28, 2026
6 checks passed
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.

3 participants