2. Simplifying long expression and function calls
Being able to introduce easily a tight-scoped variable can be a massive boost on readability AND debuggability
begin
var x := ...some longish expression that computes x...;
var y := ...some expression with side effects that computes y...;
MyFunction(x, y);
end;
Even with just two variables, the above code allows to spread parameter evaluations cleanly, and it becomes trivial to breakpoint the code after a particular argument is computed and eval the value.
It also brings to the table the ability to name the argument, which can be a significant boost to code readability for functions with many parameters, or functions you're unfamiliar with the order of parameters.
It seems to me that you don't want actual variables here, but defines:
{$define x:=long expression1}
{$define y:=long expression2}
fun(x, y);
No need to add any new featrue to the compiler for this
3. Record initialization
Rather than the legacy variable + fillchar dance, with risk for error and need to read multiple lines of the code, you can just do
var myDescriptor := Default(TSomeDescriptor);
myDescriptor.Field:= 123;
...
I took the descriptor structure exemple, as it's common in APIs. The inline declaration makes everything local and obvious to whoever will read the code.
Since FPC supports managed types, you should avoid any use of FillChar to begin with. And also, don't use Default,
it's broken. If you want to correctly initialize a local variable call finalize-initialize:
finalize(myVar);
initialize(myVar);
But also, thats what record constructors are for. Just create a function that takes the values for the fields as argument and creates you a record from it:
myDescriptor := Descriptor(123);
No need to introduce temporary variables to begin with.
4. Elimination of the "catalog of variables" anti-pattern
Every function implementation where you have many variables (like in the above points) quickly becomes a massive catalog of variables declaration, followed by implementation. This declarations are not visible without scrolling, and since you have many of them, they're not always readable.
When faced with unfamiliar code, the catalog anti-pattern can also make it non-obvious what a symbol is when reading code: is it a local variable ? an argument ? a property ? something else ?
This can be alleviated by prefixing symbol names, but there will still remain a degree of ambiguity about variable type and scope (esp with nested functions, anonymous procs, where the prefixing is not enough).
Bringing the declaration close to usage just cleans everything up, and makes local portion of code readable and understandable in isolation, which is a huge boon.
To quote the Linux Kernel style guide:
Another measure of the function is the number of local variables. They shouldn’t exceed 5-10, or you’re doing something wrong. Re-think the function, and split it into smaller pieces.
So yes a catalog of variables is bad, but not because they are all defined in one place, but because having complex functions is bad. If you have to many local variables, the solution is not to hide the problem by putting them all in inline variables, the solution is to restructure your code.
The anti-pattern here is writing to complex functions and trying to hide the complexity. Not the language feature that makes assessing the complexity of a function very easy.
5. Avoiding uninitialized variables
I'm placing this last because the compiler can help here, so it's not always fatal. Though there will still be a degree of ambiguity when a variable is passed by reference, or when a variable goes out of logic scope, since it can't get out of compiler scope.
Using nested functions is the only mitigation with legacy Pascal, but it's like using a hammer to swipe a mosquito, and it can be detrimental to readability, as it will break the flow of code.
It's actually not really an issue at least when you compile with -Oodfa (data flow analysis). This will show you when a variable is not initialized. Note that passing by reference via a "var" parameter, still technically requires initialization, only passing by reference in an "out" parameter is for uninitialized variables (that said functions like Move use var instead of out, probably due to backwards compatibility, this of course causes an issue)
There are only two good use-cases for inline variables, one is scoping for managed types, which I would much rather see with a specific construct such as python with:
with OpenFile as fl do
begin
// Use fl
end; //After with block fl will be finalized
And for loops, because loop variables are usually quite meaningless (as often indicated by meaningless names such as "i", "j", "k", etc.)