Skip to content

First-class expressions for expression-typed MDL properties (avoid quoted-string escaping) #750

Description

@ako

Summary

Several MDL properties are typed as an expression but currently take a quoted string, so any single quote inside the expression must be doubled (''). This produces runs of successive quotes that are hard to read, and it signals nothing to the reader/tooling that the value is an expression (it's treated as an opaque string, not syntax-checked).

We should let these properties accept a first-class expression — inner single quotes are literal string delimiters, no escaping — while a quoted '…' continues to work (backward-compatible) and reads as a plain string.

The pain (real examples from the skills/examples today)

-- dynamicclasses: quoted string, single quotes doubled → 5 quotes in a row for an empty else
dynamicclasses: 'if $currentObject/Featured then ''is-featured'' else '''''

-- DynamicCellClass: same
DynamicCellClass: 'if($currentObject/Price > 100) then ''highlight'' else '''' '

The else ''''' (five quotes) is else '' (empty string) inside a doubled-quote string. Easy to get wrong, hard to read.

The language is already inconsistent — first-class forms exist

Some expression contexts already avoid escaping, which proves this is feasible:

  • Microflow if / while / return use the real expression grammar rule (MDLMicroflow.g4): if $x = 'y' then … — single quotes are literal, no doubling.
  • Widget visible: / editable: accept a bracket form (xpathConstraint: LBRACKET xpathExpr RBRACKET, buildConditionalExpression in visitor_page_v3.go): visible: [Name != ''], editable: [CanEdit] — inner quotes literal.

But other expression-typed properties are generic quoted-string properties with no first-class form.

Proposed

Where a property is known to expect an expression, accept a delimited first-class expression (e.g. reuse the [ … ] form that visible:/editable: already use, or the microflow expression grammar) in which single quotes are literal:

-- Proposed — reads cleanly, no escaping:
dynamicclasses: [ if $currentObject/Featured then 'is-featured' else '' ]
DynamicCellClass: [ if $currentObject/Price > 100 then 'highlight' else '' ]

A quoted '…' stays valid and is treated as a plain string literal (backward compatible; existing scripts unchanged).

Expression-typed properties to cover

  • dynamicclasses: (widget) and DynamicCellClass: (datagrid column)
  • Page variable defaults: variables: { $v: Boolean = <expr> }
  • Calculated attributes / calculation expressions
  • REST / OData contexts that carry an expression (filter / mapping / condition)
  • (Audit for any other property whose value is a Mendix expression)

Benefits

  • Readability — no ''''' runs.
  • Signals intent + enables checking — a first-class expression can be parsed and syntax/type-checked instead of stored as an opaque string. Directly complements docs/11-proposals/PROPOSAL_expression_type_checking.md.
  • Consistency — one expression story across microflows, visibility, and these properties.

Design questions to nail down in a proposal

  1. Delimiter: reuse [ … ] (matches visible:/editable:) vs a distinct delimiter. Note [ … ] today parses xpathExpr (XPath-flavored), whereas dynamicclasses is a full Mendix microflow expression (if … then … else, functions). The microflow expression grammar is the real currency — decide whether the first-class form reuses it.
  2. Backward compatibility: keep the quoted-string form working everywhere it works today.
  3. DESCRIBE: emit the first-class form (cleaner round-trip) while still parsing the legacy quoted form.
  4. Scope: which properties in the first cut (dynamicclasses/DynamicCellClass are the highest-value, most-escaped).

Feasibility

Proven — the mechanism exists (buildConditionalExpression for visible:/editable:; the microflow expression rule). This is an additive grammar/visitor change per property, not a rewrite.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions