Another more out-of-the-box example that came up in parsing Delphi (and ‘New Delphi’) code that hits near to the idea of exhaustive cases … that I didn’t predict … is compiler directives.
Claude mentioned the cases, in the process of working on a parser for the syntax I want to be able to use. (Work in progress). And then I got it to gather the data for this post.
(Currently only targeting Delphi 13+ .. and might be making some Windows-only assumptions atm.)
A quick note that a ‘total function’ is one that is defined for all inputs.
ie inc( byte ) if values wrap around without error, or writeln( string ) …
vs (x div y) using integers - which is undefined for y=0
We surveyed about 2,000 .pas/.dpr files:
- the Delphi 13 RTL and VCL (with DUnit and DUnitX),
- Spring4D
- DPM
- Devcpp
- fluent4d
- Rudy’s BigNumbers.
They contain 19,443 conditional groups.
About 7,700 of those are for platforms and compilers outside of our consideration (CLR, FPC, GPC, NEXTGEN, TMT Pascal, older Delphi versions) and asm blocks.
That leaves 11,755 live ones.
About half of those are really “if X then A else B”, but more are implied than stated:
- 20% say so with
{$ELSE}. - 28–36% are two separate conditionals that the writer means as one choice.
Some are a flag and its negation ({$IFDEF S} … {$ENDIF} {$IFNDEF S} … {$ENDIF}).
Others are a pair of platforms side by side:
{$IFDEF MSWINDOWS} … {$ENDIF} {$IFDEF POSIX} … {$ENDIF}
That second shape (13% of live conditionals) is assumed to be total, but is not even necessarily mutually exclusive.
Nothing visible enforces that one of them must hold, or that only one can.
An {$ELSEIF} chain without an {$ELSE} is at least exclusive, but may not be total.
The total form exists, and it’s the compile-time twin of the else raise … GetName(EnumVar) described here: Exhaustive Enumeration Case
{$IF Defined(MSWINDOWS)}stdcall{$ELSEIF Defined(POSIX)}cdecl{$ELSE}{$MESSAGE FATAL '…'}{$IFEND};
It’s rare: 139 groups in 18 files.
The rest depend on hidden assumptions about which flags cover the whole design space.
285 two-way choices test a single CPU or bitness symbol and let {$ELSE} mean “the other one”:
- 74 -
{$IFDEF CPUX86} … {$ELSE} … - 45 -
{$IFDEF CPUX64} … {$ELSE} … - 75 -
{$IFDEF WIN32}and{$IFDEF WIN64}with an{$ELSE} - 91 -
CPU64BITSandCPU32BITS
The bitness ones still succeed with Windows on ARM64.
It seems the other nearly 200 send it into an {$ELSE} that was written for x86 or x64 - without any warning.
There are also redundant definitions.
In System.SysUtils, LINUX defines USE_LIBICU, and the chains then test USE_LIBICU before LINUX, so 24 {$ELSEIF defined(LINUX)} arms are redundant.
This is what makes me mention total functions above.
But not all conditional cases are a ‘must decide’ choice. Some are single possible inclusions.
But in other cases, a conditional is a function eg from the platform to code, and it should cover every platform.
And maybe we could do with some new syntax for this.
For example:
- Declared bounds instead of
{$DEFINE}chains:
{$DEFINE Platform} = (
Win32, // WIN32
Win64, // WIN64 and CPUX64
WinARM64, // MSWINDOWS and CPUARM64
macOS64, // MACOS and CPUX64 (not IOS)
macOSARM64, // MACOS and CPUARM64 (not IOS)
iOS64, // IOS and CPUARM64
Android32, // ANDROID and CPUARM32
Android64, // ANDROID and CPUARM64
Linux64 // LINUX and CPUX64 (not ANDROID)
)
{$DEFINE UseLibICU} = Platform in [Linux64, Android32, Android64]
- New syntax for choices. (Just throwing mud at the wall)
function SQLAllocConnect(…): SQLRETURN {$CASE Platform of} {Windows} stdcall {Posix} cdecl {$ENDCASE};
{$DEFINE OSName} = {$CASE Platform of}
{Win32, Win64, WinARM64} 'Windows'
{macOS64, macOSARM64} 'macOS'
{iOS64} 'iOS'
{Android32, Android64} 'Android'
{Linux64} 'Linux'
{$ENDCASE};
System.pas currently contains the following, which is pretty thorough but not perfect.
{$IFNDEF PC_MAPPED_EXCEPTIONS}
{$IF defined(ANDROID)}
{$DEFINE ZCX_BASED_EXCEPTIONS}
{$ELSEIF defined(MACOS)}
{$IF defined(CPUARM32)}
{$DEFINE SJLJ_BASED_EXCEPTIONS}
{$ELSEIF defined(CPUARM64)}
{$DEFINE ZCX_BASED_EXCEPTIONS_MACOS}
{$ELSEIF defined(CPUX64)}
{$DEFINE ZCX_BASED_EXCEPTIONS_MACOS}
{$ENDIF}
{$ELSEIF defined(MSWINDOWS)}
{$IFDEF LLVM}
{$IFDEF MSWINDOWS}
{$DEFINE ZCX_BASED_EXCEPTIONS_WIN}
{$ELSE}
{$DEFINE ZCX_BASED_EXCEPTIONS}
{$ENDIF}
{$ELSE}
{$IF defined(CPUX86)}
{$DEFINE STACK_BASED_EXCEPTIONS}
{$ELSEIF defined(CPUX64)}
{$DEFINE TABLE_BASED_EXCEPTIONS}
{$ELSE}
{$MESSAGE ERROR 'Unknown platform'}
{$ENDIF}
{$ENDIF !LLVM}
{$ELSEIF defined(LINUX)}
{$IFDEF LLVM}
{$DEFINE ZCX_BASED_EXCEPTIONS}
{$ELSE}
{$DEFINE PC_MAPPED_EXCEPTIONS}
{$ENDIF}
{$ELSE}
{$MESSAGE ERROR 'Unknown platform'}
{$ENDIF}
{$ENDIF !PC_MAPPED_EXCEPTIONS}
From Claude …
- Inside it, the MACOS chain has no {$ELSE}. A new Mac CPU would silently define no exception model.
- One branch is impossible. The {$IFDEF MSWINDOWS} inside {$ELSEIF defined(MSWINDOWS)} can never take its {$ELSE} branch.