name: xpp-statement-and-type-rules
description: X++ statement-level and type-system rules — switch / break, ternary, no-DB-null sentinels, casting (as / is), using blocks, embedded function declarations. Invoke when writing any non-trivial X++ control flow or type conversion.
applies_when: User intent involves X++ control flow, switch statements, casting, null/empty checks on date/utcDateTime, or IDisposable resource handling.
⛔ NEVER write X++ AOT XML files directly via PowerShell, terminal file commands (
Set-Content,Out-File,New-Item), editor write tools, or any raw text approach. The XML schema (<AxClass>,<AxTable>,<AxForm>,<Methods>,<SourceCode>) is proprietary — LLMs have not been trained on it reliably. ALWAYS used365fo generate …commands to produce correct AOT XML. Ifd365fois unavailable in PATH, stop and ask the user to install it.
X++ statement & type rules
Sources of truth: learn:xpp-conditional and learn:xpp-variables-data-types.
switch / break
breakis required at the end of everycase. Implicit fall-through compiles but is misleading.- To match multiple values to a single branch use the comma-list form — never empty fall-through:
// ✅ CORRECT
switch (mod(year, 4))
{
case 13, 17, 21:
result = "leap-adjacent";
break;
default:
result = "ordinary";
break;
}
Ternary
cond ? a : b — both branches must have the same type. No implicit widening of int ↔ real.
❗ X++ has NO database null
Each primitive has a "null-equivalent" sentinel:
| Type | Null-equivalent value |
|---|---|
int / int64 |
0 |
real |
0.0 |
str |
"" |
date |
1900-01-01 (dateNull()) |
utcDateTime |
date-part 1900-01-01 (utcDateTimeNull()) |
enum |
element with value 0 |
boolean |
false |
RecId |
0 |
In SQL where clauses these compare as false (rows with sentinel values are NOT returned by where field). In plain expressions they are ordinary values.
// ❌ WRONG — there is no null
if (myDate == null) { … }
// ✅ CORRECT
if (!myDate) { … } // boolean test on sentinel
if (myDate == dateNull()) { … } // explicit
Same for utcDateTime — compare against utcDateTimeNull() or use if (!myUtc).
Casting
- Prefer
as(returnsnullon type mismatch) andis(boolean test) over hard down-casts. - Hard down-casts (
(SubClass)objectExpr) on object-typed expressions throwInvalidCastExceptionon mismatch. - Late binding exists for
ObjectandFormRunonly — accept the runtime cost if you use it.
common = ledgerJournalTrans;
LedgerJournalTrans trans = common as LedgerJournalTrans;
if (trans) { trans.update(); }
using blocks for IDisposable
Equivalent to try + finally { x.Dispose(); } but shorter and exception-safe.
using (var reader = new StreamReader(path))
{
line = reader.ReadLine();
}
Embedded function declarations
Local functions inside a method can read variables declared earlier in the enclosing method but cannot leak their own variables out. Prefer them over a private helper method only when the helper truly does not belong to the class API.
Hard "never" list
- Never test
myDate == null— there is no null in X++. - Never rely on switch fall-through — always
break(or use the comma-list form). - Never down-cast an
Objectwithout anisguard (or anas+ null check).