[fpc-announce] [Feature Announcement] Statement Expressions

Frederic Kehrein frederic at kehrein.org
Tue Sep 29 00:09:15 CEST 2026


Dear FreePascal Community,

we are pleased to announce a new feature now available on fpc main: 
*Statement Expressions*.

Statement Expressions introduces new expression types based of control 
flow statements, namely *if-then-else*, *case-of* and *try-except*. All 
three are enabled via the `{$ModeSwitch StatementExpressions}`.

**Expressions**

If-Then-Else is the tenary if-operator that is known from other 
languages and also available in Delphi now:
```
absValue := if Value < 0 then -Value else Value;
```

Besides this, this MR also extends this concept to the case-of statement:
```
ActiveDuplicates := case StringList.Duplicates of
     dupIgnore: 'Does not add duplicates';
     dupAccept: 'Adds duplicates as well';
     dupError: 'Throws error on duplicate';
end;
```
Similar to the if-then-else the case-of can be used to return a value 
based on a conditional. To ensure the return value is always well 
defined the case-of must be exhaustive. In the example above this is 
given because the case-of covers all 3 possible values from the enum 
`TDuplicates`. If that is not the case an `else` or `otherwise` is required:
```
AgeDescription := case Age of
     0..1: 'Newborn';
     1..3: 'Toddler';
     4..12: 'Child';
     13..19: 'Teenager';
     otherwise 'Adult';
     { Alternative:
     else 'Adult';
     }
end;
```
Note that unlike the if-then-else statement the case-of requires an 
`end` after the expression.

Try-except allows to try to evaluate an expression but if an exception 
is raised during the evaluation, a fallback can be returned. The most 
generic version without specific exception type handling is:
```
MyValue := try StrToInt(StringInput) except -1 end;
```
As different exceptions can be thrown and it may be desired to get 
different results based on the different exception types. In this case 
the except on ... syntax can be used:
```
Result := try ReceiveStr(Socket) except
     on EConnectionClosedException do 'Connection Closed';
     on E: ESocketError do 'Socket Error %d'.Format([E.Code.ToString]);
     else 'Unknown Error'
   end;
```
Note that here as well an `end` is required.

**Type Promotion**

While these features are relatively self-explainatory, there is one 
thing worth noting, the underlying type selection. If-then-else has to 
branches with potentially two different types. Case-Of and Try-Except 
can have an arbitrary number of branches and types. Requiring all 
branches to have the exact same type is not feasible, as it would 
already make problems when dealing with something like:
```
var AValue: Double;
...
AValue := if AValue > 0 then AValue else 0;
```
Because here the if-branch is of type Double (AValue) while the 
else-branch is of type Integer.

As such fpc tries to find a common type between a process that I like to 
call type promotion, as it always tries to promote to the "larger" type:
* If one branch is of type char and the other one is of type string, the 
result is promoted to string
* AnsiChar or AnsiString can be promoted to WideChar or WideString
* Shortstrings of different sizes are promoted to the largest size of 
any branch
* Objects and Classes are promoted to the most recent common ancestor in 
their inheritance tree
* Any two other types are promoted to the type with the "easiest" conversion

Type promotion always occurs between two branches at a time, going from 
top to bottom. This can lead to edge cases where the order of the 
branches could change the result type of the total expression.
When in doubt, use casts to enforce specific types.

Have fun and enjoy building things :)



More information about the fpc-announce mailing list