Skip to content

Add support for if expressions - #450

Merged
cirras merged 2 commits into
masterfrom
if-expressions
Aug 5, 2026
Merged

Add support for if expressions#450
cirras merged 2 commits into
masterfrom
if-expressions

Conversation

@cirras

@cirras cirras commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

This PR implements if expressions.

Embarcadero Documentation

The language feature is documented by Embarcadero here.

They even kindly provided a table of operand type combinations with the corresponding result type.

Type 1 Type 2 Result type
AnsiChar AnsiChar AnsiChar
AnsiString AnsiString, AnsiChar AnsiString
Array Array Array of the same element type.
Boolean types Boolean types Common Boolean type or Boolean type.
Class reference Class reference, nil Common type, Base type, or TClass.
Dynamic array Dynamic array Common dynamic array type.
Enum type Enum type Common enum type.
File File Error (use pointer to File type).
Instance Instance, nil Common type, Base type, or TObject.
Integral types Integral types Common integral type, or the type that covers both ranges.
Integral types Real types Real type.
Interface Interface, nil Common type, Base type, or IInterface.
nil AnsiChar, ShortString, AnsiString, WideChar, WideString Pointer to WideChar
Object Object Object type.
Pointer to AnsiChar AnsiChar, ShortString, AnsiString literal Pointer to AnsiChar
Pointer to AnsiChar Pointer to WideChar literal Pointer to WideChar
Pointer to WideChar AnsiChar, ShortString, AnsiString, WideChar, WideString, UnicodeString UnicodeString
Pointer types Pointer types Common pointer type, or Pointer type.
Procedure Procedure Procedure type, if they have the same signature.
Procedure of object Procedure of object Procedure for object type if they have the same signature.
Real types Real types The real type with the greater precision.
Record Record Record type.
Reference to procedure Anonymous Method Reference to procedure Anonymous Method Reference to the procedure type, if they have the same signature.
Set Set Common set type, or large set type if list literals.
Short string Short string, AnsiChar Longer short string
Text Text Error (use pointer to Text type).
UnicodeString UnicodeString, WideString, AnsiString, Short string, WideChar, AnsiChar UnicodeString
Variant Variant Variant type.
WideChar WideChar WideChar
WideChar AnsiChar, AnsiString UnicodeString
WideString WideString, AnsiString, WideChar, AnsiChar WideString

Reality

Unfortunately, the documented operand/result type combinations are inaccurate or incomplete in various ways.
Below is what Delphi 13 actually does.

Type 1 Type 2 Result type
AnsiChar AnsiChar AnsiChar
AnsiString AnsiString, AnsiChar Left operand (code pages don't unify)
Array Array Identical type only; else error
Boolean types Boolean types Left operand, never widened
Class reference Class reference, nil Reference to nearest common ancestor; same class -> right operand; nil -> the reference
Dynamic array Dynamic array, nil Identical type only (nil -> the array); else error
Dynamic array [...] constructor Error
Enum type Enum type The enum (subranges unify to it); distinct enums error
File File Error (use pointer to File type).
Instance Instance, nil Nearest common ancestor class, never an interface; strong aliases are siblings of their base; nil -> the class
Instance Interface Error
Integral types Integral types Narrowest type covering both ranges; signed/unsigned tie -> anonymous unsigned subrange, or UInt64
Integral types Real types Extended; Comp/Currency where Extended is 8 bytes
Interface Interface, nil Nearest common ancestor; nil -> the interface
nil AnsiChar, ShortString, AnsiString, WideChar, WideString Error
nil Procedure, procedure of object, reference to The procedural type
nil [...] constructor Anonymous array of T
nil Variant, any value type Error
Object Object Identical type only; else error
Pointer to AnsiChar AnsiChar, ShortString, AnsiString literal Error
Pointer to AnsiChar Pointer to WideChar literal Error
Pointer to WideChar AnsiChar, ShortString, AnsiString, WideChar, WideString, UnicodeString UnicodeString
Pointer types Pointer types Left operand if either is untyped or the right is nil; nil on the left, or unrelated pointers -> Pointer
Procedure Procedure Left operand, if they have the same signature
Procedure of object Procedure of object Left operand, if they have the same signature
Mixed procedural kinds Mixed procedural kinds Error
Real types Real types Extended; Comp/Currency where Extended is 8 bytes
Record Record Identical type only; else error
Reference to procedure Anonymous Method Reference to procedure Anonymous Method The reference to type, if they have the same signature
Set Set The containing set (left on ties); else set of Byte or set of AnsiChar
Set [...] constructor The set, if the elements are compatible
[...] constructor [...] constructor Left operand's set reading, if all elements are ordinal; else error
ShortString ShortString, AnsiChar Longer ShortString
Text Text Error (use pointer to Text type).
UnicodeString UnicodeString, WideString, AnsiString, Short string, WideChar, AnsiChar Left operand
Variant Variant Variant (even for OleVariant mixes)
WideChar WideChar WideChar
WideChar AnsiChar, AnsiString UnicodeString
WideString WideString, AnsiString, WideChar, AnsiChar Left operand

Working out the actual rules required a lot of painful testing against the Delphi 13 compiler, but I think this is pretty accurate now.

It definitely isn't a "least upper bound" as commonly understood, or as documented:

  • operand types can end up stuffed into ill-fitting result types
    • signed integers going into UInt64
    • large booleans going into smaller booleans (whichever the left operand happened to be)
  • ties typically go to the left operand, so ordering of the expression is significant

Fun Facts

  • if b then [] else DynArray crashes the 64-bit compiler with an internal error (F2084).

@fourls fourls left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good for the most part! A couple of minor comments.

@cirras
cirras requested a review from fourls August 5, 2026 05:17
@cirras

cirras commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed review feedback and, in the process, discovered the resolver was handling array constructors incorrectly.
Pulling that thread a little further, it turns out the TypeInferrer was also handling them incorrectly. That was a fun incidental discovery. 😩
Patched up in a separate commit.

@fourls fourls left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks really good! The array element type inference being wrong is a bit of a surprise - I guess it's a fairly niche case.

On top of the comments below, could you also rebase on master to get the Java 25 compatibility fixes?

@cirras
cirras requested a review from fourls August 5, 2026 07:27

@fourls fourls left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! This will go a long way to unblocking Delphi 13 support.

As always, I'm sure there's a bunch of bizarre edge case behavioural quirks that we've missed and will have to slowly discover over time 😅

@cirras
cirras merged commit ad0e4e4 into master Aug 5, 2026
3 checks passed
@cirras
cirras deleted the if-expressions branch August 5, 2026 07:42
@cirras cirras linked an issue Aug 7, 2026 that may be closed by this pull request
2 tasks
@cirras cirras mentioned this pull request Aug 7, 2026
2 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support if expressions

2 participants