From frederic at kehrein.org Tue Sep 29 00:09:15 2026 From: frederic at kehrein.org (Frederic Kehrein) Date: Tue, 29 Sep 2026 00:09:15 +0200 Subject: [fpc-announce] [Feature Announcement] Statement Expressions Message-ID: <33d855a0-0b5b-4d05-8a69-806911195bde@kehrein.org> 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 :)