Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .claude/skills/fix-issue.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ to the symptom table below, so the next similar issue costs fewer reads.
| `create or modify entity M.E ( <subset> )` on an entity that already has more attributes **silently drops** every attribute not re-listed (36→2 attrs seen in practice), then widgets/microflows still bound to them fail the build with CE1613 — and the "already exists" error that leads users here recommends the destructive `create or modify` for a partial edit | `create or modify` rebuilds the entity from the statement alone and REPLACEs the stored one, so any omitted attribute is deleted with no warning; the `NewAlreadyExistsMsg` hint pointed at `create or modify` without distinguishing "replace whole" from "add one" | `mdl/executor/cmd_entities.go` (`droppedEntityMembers`, the warn block before `UpdateEntity`, and the `execCreateEntity` already-exists message) | Warn-only (non-blocking, the user asked to modify): before `UpdateEntity`, diff existing vs replacement members (`droppedEntityMembers` — named attrs case-insensitive + the four audit flags) and print what's dropped + point at `alter entity … add attribute` for incremental edits. Fix the already-exists message to recommend `alter entity` for a member change and reserve `create or modify` for a full replace. Bug-test `mdl-examples/bug-tests/f24-create-or-modify-dataloss.mdl`. Findings #24 |
| A microflow expression calls a function that doesn't exist (e.g. `randomInt(9)` — in some docs but not a Mendix built-in): parses, passes `mxcli check`, then fails the build with CE0117 "Error(s) in expression". Also: a Decimal-returning function (`random()`, `secondsBetween`, the duration `*Between` family) assigned to an Integer/Long variable fails CE0117, but `mxcli check` only caught bare `div` | (1) The func checker only validated arity for *known* functions; an unknown call name was ignored. (2) `checkNumericAssignment` used `SourceIsArithmeticDecimal`, which fires only on arithmetic roots, so a Decimal *function* result slipped through. Also the `*Between` duration funcs were mistyped Integer in `funcTable` | `mdl/exprcheck/unknown_funcs.go` (`UnknownFunctionCalls`, `SourceRejectedForIntegerTarget`, `nearestFunc`) + `mdl/exprcheck/func_checker.go` (`funcTable` between-date return kinds) + `mdl/executor/validate_microflow.go` (`checkExprFunctions` → MDL044; `checkNumericAssignment` now uses `SourceRejectedForIntegerTarget`) | Walk each expression for `CallExpr` names not in `funcTable` (which lists *every* built-in — a bare `name(...)` is always a built-in call) → **MDL044** with a Levenshtein/prefix "did you mean" hint. Extend the Decimal-into-Integer check to Decimal-returning non-rounding functions → **MDL041**. Correct `secondsBetween`/`minutesBetween`/`hoursBetween`/`daysBetween`/`weeksBetween` to Decimal (calendar variants stay Integer, millisecondsBetween stays Long). When adding a new Mendix built-in, add it to `funcTable` or MDL044 will false-positive. Bug-tests `f1-unknown-expression-function.fail.mdl`, `f2-decimal-func-into-integer.fail.mdl`. Findings #1, #2 |
| `index name on (cols)` inside `create entity` (or `alter entity add index name on (cols)`) fails with `extraneous input 'on' expecting '('` — the SQL-like form docs/users expect. The bare `index name (cols)` worked | `indexDefinition` had no `ON` token: `IDENTIFIER? LPAREN indexAttributeList RPAREN` | `mdl/grammar/domains/MDLDomainModel.g4` (`indexDefinition`) | Make `ON` optional: `IDENTIFIER? ON? LPAREN indexAttributeList RPAREN`, regen grammar. `buildIndex` reads columns from `IndexAttributeList` only, so ON can't be mistaken for a column and no visitor change is needed. Covers both CREATE (entityOption) and ALTER ADD INDEX (shared rule). Bug-test `mdl-examples/bug-tests/f4-entity-index-on.mdl`. Findings #4 |
| `retrieve … where [Seq = $Game/MoveSeq + 1]` fails with a bare `mismatched input '+' expecting ']'` — no hint that Mendix XPath can't compute values (this is a Mendix limitation, not an mxcli bug) | Mendix XPath constraints take a literal/token/variable/path on the value side, never an arithmetic expression; the parse error named the token but not the cause | `mdl/visitor/visitor.go` (`enhanceErrorMessage`, `looksLikeXPathArithmetic`/`xpathArithmeticRe`) | Do NOT add grammar support (mxbuild would still reject the XPath). Add an error hint keyed on `mismatched input '<+|*|div|mod>' expecting ']'` (`expecting ']'` only occurs inside a `[…]` constraint) explaining the limitation and the workaround: compute into a variable first, then compare. Also documented in `xpath-constraints.md`. Bug-test `mdl-examples/bug-tests/f8-xpath-arithmetic.fail.mdl`. Findings #8 |

---

Expand Down
11 changes: 11 additions & 0 deletions .claude/skills/mendix/xpath-constraints.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,17 @@ This skill provides reference for writing XPath constraint expressions in MDL RE
| Empty check | `= empty`, `!= empty` | `= empty` |
| Token quoting | `'[%CurrentUser%]'` (quoted) | `[%CurrentUser%]` (unquoted) |
| Nested filter | `Assoc/entity[pred]` | Not applicable |
| Arithmetic on value | **not supported** — pre-compute into a variable | `+`, `-`, `*`, `div`, `mod` |

> **XPath constraints cannot compute values.** `where [Seq = $Game/MoveSeq + 1]`
> is a parse error (Mendix XPath has no arithmetic on the value side). Compute the
> value first, then compare against the variable:
> ```mdl
> $Next = $Game/MoveSeq + 1;
> retrieve $M from Mod.Move where [Seq = $Next] limit 1;
> ```
> `mxcli check` explains this and shows the workaround when it sees `+`/`*`/`div`/
> `mod` inside a constraint.

## Syntax Reference

Expand Down
9 changes: 9 additions & 0 deletions mdl-examples/bug-tests/f8-xpath-arithmetic.fail.mdl
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-- Findings #8: Mendix XPath constraints cannot compute values, so an arithmetic
-- operator on the value side (`+ 1`) is a parse error. check now explains the
-- limitation and the workaround (compute into a variable first, then compare).
-- Negative test: `mxcli check` MUST fail here.
create microflow Bug8.MF_NextMove (Game: Bug8.Game) returns Bug8.Move
begin
retrieve $M from Bug8.Move where [Seq = $Game/MoveSeq + 1] limit 1;
return $M;
end;
24 changes: 24 additions & 0 deletions mdl/visitor/visitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,17 @@ func enhanceErrorMessage(msg, offendingLine string) string {
" create enumeration Mod.E (Value1 = 'Caption 1'); (wrong — causes parse error)", msg)
}

// Arithmetic inside an XPath constraint. Mendix XPath can't compute values
// (no +, -, *, div, mod on the value side) — compute into a variable first,
// then compare against it. (sudoku findings #8)
if looksLikeXPathArithmetic(msg) {
return fmt.Sprintf("%s\n\n Mendix XPath constraints cannot compute values (no +, -, *, div, mod).\n"+
" Compute the value into a variable first, then compare against it:\n"+
" $Next = $Game/MoveSeq + 1;\n"+
" retrieve $M from Mod.Move where [Seq = $Next] limit 1; (correct)\n"+
" retrieve $M from Mod.Move where [Seq = $Game/MoveSeq + 1] limit 1; (wrong — XPath can't compute)", msg)
}

// Check for a misplaced EXTENDS / GENERALIZATION clause. It must precede the
// attribute list — `create entity Mod.Child extends Mod.Parent ( ... )` — but
// users often append it after the closing parenthesis, where ANTLR reports a
Expand Down Expand Up @@ -182,6 +193,19 @@ func enhanceErrorMessage(msg, offendingLine string) string {
// stay false-positive-free (it won't fire on `not(...)`, `is not null`, etc.).
var bareNotRe = regexp.MustCompile(`(?i)\bnot\s+\$`)

// xpathArithmeticRe matches an arithmetic operator that ANTLR rejected inside a
// bracketed XPath constraint. `expecting ']'` only occurs inside `[…]`, so a
// stray arithmetic operator there is a compute-in-constraint attempt. The op set
// is quoted-literal to avoid matching, say, a `-` inside a normal expression.
var xpathArithmeticRe = regexp.MustCompile(`mismatched input '(\+|\*|div|mod)' expecting '\]'`)

// looksLikeXPathArithmetic detects an arithmetic operator used on the value side
// of an XPath constraint (e.g. `[Seq = $Game/MoveSeq + 1]`). Mendix XPath cannot
// compute values, so this must be pre-computed into a variable. (findings #8)
func looksLikeXPathArithmetic(msg string) bool {
return xpathArithmeticRe.MatchString(msg)
}

// looksLikeMisplacedExtends detects ANTLR errors caused by an EXTENDS /
// GENERALIZATION clause placed after the entity's attribute parentheses instead
// of before them. The offending token surfaces as extraneous/mismatched input.
Expand Down
18 changes: 18 additions & 0 deletions mdl/visitor/visitor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2432,3 +2432,21 @@ func TestAlterEntityAddIndexOnKeyword(t *testing.T) {
t.Errorf("expected column [Col], got %+v", alt.Index.Columns)
}
}

// TestEnhanceErrorMessage_XPathArithmetic verifies the XPath-arithmetic hint
// fires for an arithmetic operator inside a bracketed constraint (findings #8)
// and not for arithmetic in a normal expression.
func TestEnhanceErrorMessage_XPathArithmetic(t *testing.T) {
xhint := "cannot compute values"
// Inside a constraint: `expecting ']'` + arithmetic op → hint.
for _, op := range []string{"+", "*", "div", "mod"} {
msg := "mismatched input '" + op + "' expecting ']'"
if got := enhanceErrorMessage(msg, ""); !strings.Contains(got, xhint) {
t.Errorf("op %q: expected XPath-arithmetic hint, got:\n%s", op, got)
}
}
// A normal expression arithmetic error (not `expecting ']'`) must not fire.
if got := enhanceErrorMessage("mismatched input '+' expecting ';'", ""); strings.Contains(got, xhint) {
t.Errorf("did not expect XPath hint for a non-constraint arithmetic error:\n%s", got)
}
}
Loading