fix(validation): flag wrong assignment direction at call sites#1710
fix(validation): flag wrong assignment direction at call sites#1710ghaith wants to merge 4 commits into
Conversation
Build Artifacts🐧 Linux
From workflow run 🪟 Windows
From workflow run |
mhasel
left a comment
There was a problem hiding this comment.
Note: this review was generated by Claude (via Claude Code) and posted on my behalf. Treat the suggestions as machine-generated input — verify before acting. — Michael
Overview
Adds two new validation diagnostics for direction-keyed assignment operator misuse at call sites, per IEC 61131-3:
- E134 (Error):
:=used for anOUTPUTparameter - E135 (Ignore):
=>used for anINPUTorIN_OUTparameter
The check is a small addition to validate_call's parameter-resolution loop and is keyed off existing helpers. Resolves #1228.
What I like
- Asymmetric severity is well-justified. Making E134 an error (silently-broken code: captured variable never receives output) and E135 ignore-by-default (tolerated by some implementations historically) reflects real-world practice. Users who want strict mode can opt in to E135 via
--error-config. - The check is small and uses existing primitives. Four lines of meaningful condition logic, no new helpers. Sits inside the existing parameter-resolution
Some(left)branch, so it can't fire on unresolved parameters or positional args. - Documentation is solid. Both E134.md and E135.md include erroneous + corrected snippets, and E135.md spells out the
--error-configopt-in JSON. That answers the "how do I turn this on?" question without forcing a user into the codebase. - Test technique for E135 is clean. Using
parse_and_validate(rawVec<Diagnostic>) to assert the diagnostic is emitted internally, alongsideparse_and_validate_bufferedto assert it's filtered out by default — that's the right way to test severity independently of the registry pipeline. Worth replicating elsewhere. - The E135 IN_OUT path also asserts the message string ("in-out" vs "input"), which is a nice anchor against future refactor drift.
- Existing-fixture correction (
byRefOutput := x[1]→byRefOutput => x[1]) is exactly the kind of latent bug a new validator should turn up. The test still exercises array-by-ref semantics, just with the correct operator.
Suggestions
- Consider an E135 severity of
Warningrather thanIgnore. If the registry supports Warning, defaulting E135 to Warning would surface the issue without breaking builds — closer to "tolerated but not encouraged." Ignore-by-default means many users will never see it. If the registry only has Error/Ignore today, ignore the suggestion. (Worth a quickgrepforWarningin the severity enum.) - One additional test worth pinning: E134 for a method call. All current tests use FB instance calls (
instance(...)). Ainstance.method(out_val := x)case (qualified-call-with-named-args path) would lock in that the new check fires through method-call lowering as well. Probably already works since the path converges, but worth one test. - REFERENCE TO output parameters. If a parameter is
VAR_OUTPUTof typeREFERENCE TO X, does:=still trigger E134? It should (the direction check is independent of the type), but a test would be cheap insurance. (#1701 is currently in the area, so it's worth being explicit.) is_output_assignmentvsis_assignmentare exhaustive but not stated. A short comment invalidate_callsaying "positional args reach this branch as neither, so they fall through cleanly" would future-proof the check against someone refactoringis_assignment/is_output_assignmentand inadvertently broadening the predicate.- The example in E134.md uses inline
// ❌and// ✅emojis. Skim of the surroundingerror_codes/*.mdfiles (E131, E132, E133) would tell you whether this matches the house style. If not, plain// invalid/// correctwould be safer for terminals/renderers that don't display emoji well.
Risks
- E134 is a new error. Any project that had
output_param := varsilently compiling will now fail. The PR body documents this clearly. Worth a release-notes call-out — and worth checking that the broken codegen the old path produced wasn't being relied on for some bizarre fallthrough behavior (almost certainly not, but worth thinking). - The corrected internal fixture proves this PR is breaking a real-world pattern that was hiding in tests. That's a feature, not a bug — but it raises the prior probability that user code in the wild has the same shape. Consider adding the migration tip ("if you see E134 after upgrading, replace
:=with=>for the named OUTPUT argument") to release notes. - The check correctly lives inside the resolved-parameter branch and skips positional args; verified by the dedicated mixed-args test.
Verdict
LGTM. Clean addition with well-chosen severities, good test coverage, and clear user docs. The Warning-vs-Ignore question for E135 is worth a one-line answer from the team; the rest are nits.
Review followups on PR #1712: - Renumber E134 to E136 to avoid collision with PR #1710 (which reserves E134 and E135 for wrong-direction call-arg assignments). - Widen the diagnostic span to cover `name AT %I*` rather than just the variable name, so the underline points at the actual offence. - Add a regression test confirming VAR_INPUT/VAR_OUTPUT/VAR_IN_OUT parameters in FUNCTION and METHOD do not trip the check. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…drop emojis Review followups on PR #1710: - Comment in validate_call documenting that positional args reach this branch as neither is_assignment nor is_output_assignment, so they fall through both arms cleanly. - Add `assignment_to_method_output_parameter_is_rejected` to lock the qualified-call path (`instance.method(out_val := x)`). - Add `assignment_to_reference_to_output_parameter_is_rejected` to confirm VAR_OUTPUT of `REFERENCE TO X` still trips E134. - Replace ❌/✅ emojis in E134.md and E135.md with plain `invalid` / `correct` markers — only E133 used emojis before this PR. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
| E132, Ignore, include_str!("./error_codes/E132.md"), // Mixing implicit and explicit call parameters | ||
| E133, Error, include_str!("./error_codes/E133.md"), // AND_THEN/OR_ELSE with non-boolean operands | ||
| E134, Error, include_str!("./error_codes/E134.md"), // ':=' used for an output parameter | ||
| E135, Ignore, include_str!("./error_codes/E135.md"), // '=>' used for a non-output parameter |
There was a problem hiding this comment.
Why is this marked as ignore while the other one isn't?
There was a problem hiding this comment.
Missed this "This diagnostic defaults to severity Ignore because some IEC implementations historically tolerate the mistake; promote it via --error-config if you want it surfaced.". Are we talking about codesys here?
There was a problem hiding this comment.
yes, but maybe we warn?
| To opt in to this diagnostic as an error, pass `--error-config` with a JSON file like: | ||
|
|
||
| ```json | ||
| { | ||
| "error": ["E135"] | ||
| } | ||
| ``` |
There was a problem hiding this comment.
Not sure this should be part of the markdown file?
Calls today silently accept a direction-mismatched assignment operator on a named argument: `foo(out_val := x)` where `out_val` is `VAR_OUTPUT` compiles and produces broken code, with no diagnostic at parse, validate, or `--check` time. Per IEC 61131-3 the operators are direction-keyed: `:=` writes into `INPUT` / `IN_OUT`, `=>` captures from `OUTPUT`. `validate_call`'s per-argument loop now checks the direction: - **E134 (Error):** `:=` used for an `OUTPUT` parameter — names the parameter and points to `=>`. - **E135 (Ignore by default):** `=>` used for an `INPUT` or `IN_OUT` parameter — symmetrical wording, opt in via `--error-config`. Some IEC implementations historically tolerate it, so it's not surfaced by default; users who want it can promote it. The check uses existing helpers (`AstNode::is_assignment` / `is_output_assignment`, `VariableIndexEntry::is_output` / `is_inout`) and runs inside the existing parameter-resolution block, so positional arguments and unresolved-parameter cases are unaffected. One existing test fixture (`validate_array_elements_passed_to_functions_by_ref`) had a latent direction mismatch on `byRefOutput := x[1]` that this PR catches; the fixture is corrected to `byRefOutput => x[1]`. Fixes #1228 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…drop emojis Review followups on PR #1710: - Comment in validate_call documenting that positional args reach this branch as neither is_assignment nor is_output_assignment, so they fall through both arms cleanly. - Add `assignment_to_method_output_parameter_is_rejected` to lock the qualified-call path (`instance.method(out_val := x)`). - Add `assignment_to_reference_to_output_parameter_is_rejected` to confirm VAR_OUTPUT of `REFERENCE TO X` still trips E134. - Replace ❌/✅ emojis in E134.md and E135.md with plain `invalid` / `correct` markers — only E133 used emojis before this PR. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…-in JSON from doc
- E135 ('=>' on a non-output parameter) is now severity Warning so the
direction mismatch surfaces by default. Users who want to treat it as
an error can still promote it via --error-config; users who want it
silenced can demote to Ignore by the same mechanism.
- Strip the "defaults to Ignore" rationale and the opt-in JSON example
from E135.md — that level of configuration detail does not belong in
per-code documentation.
- Replace the two `*_is_ignore_by_default` tests with `*_warns` tests
that pin the user-visible diagnostic. Drops the now-unused
parse_and_validate import.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5bbab09 to
4a61260
Compare
Summary
Calls today silently accept a direction-mismatched assignment operator on a named argument:
foo(out_val := x)whereout_valisVAR_OUTPUTcompiles and produces broken code, with no diagnostic at parse, validate, or--checktime. Per IEC 61131-3 the operators are direction-keyed::=writes intoINPUT/IN_OUT,=>captures fromOUTPUT.validate_call's per-argument loop now checks the direction::=used for anOUTPUTparameter. Surfaces at--checkand aborts compile. Names the parameter and points to=>.=>used for anINPUTorIN_OUTparameter. Symmetrical wording. Some IEC implementations historically tolerate it, so it's not surfaced by default; users who want it can promote via--error-config '{"error":["E135"]}'.Behaviour change for users
Before:
After (E134 / OUTPUT direction):
After (E135 / INPUT direction, default):
After (E135 / INPUT direction, opt-in):
Notes
AstNode::is_assignment/is_output_assignment,VariableIndexEntry::is_output/is_inout) and runs inside the existing parameter-resolution block invalidate_call. Positional arguments naturally skip the check (they aren'tAssignment/OutputAssignmentnodes).validate_array_elements_passed_to_functions_by_ref) had a latent direction mismatch onbyRefOutput := x[1]that this PR catches; the fixture is corrected tobyRefOutput => x[1].=>on bothINPUTandIN_OUT(the IEC rule that=>is only valid forOUTPUTis symmetric across both). The diagnostic message names the actual direction (inputvsin-out) for clarity.--error-configmid-test promotion. Tests useparse_and_validate(rawVec<Diagnostic>) to assert E135 is emitted internally even when the buffered (Ignore-filtered) view is empty. The promotion mechanism itself is already covered by the registry's own unit tests.Fixes #1228
Test plan
assignment_validation_tests.rscovering: E134 surfaces with parameter name; correct directions stay clean; E135 silent by default but emitted for opt-in (Input + IN_OUT cases); mixed named/positional calls don't double-report.cargo test --workspaceclean (2500/2500).cargo xtask litclean (347/347).cargo fmt --all+cargo clippy --workspace -- -Dwarningsclean.plc <file> --checkexits 1 with E134 on the issue's case;plc <file> --check --error-config '{"error":["E135"]}'exits 1 with E135 on the mirror case; default config keeps E135 silent.🤖 Generated with Claude Code