Recent

Author Topic: Inline vars / Re: Conditional ternary operators For FreePascal?  (Read 7158 times)

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 11824
  • Debugger - SynEdit - and more
    • wiki
Re: Inline vars / Re: Conditional ternary operators For FreePascal?
« Reply #15 on: August 10, 2025, 12:36:03 pm »
Limiting it to just one case (the "with ... do") does not make it better. Oh, well actually just 2 cases (with and for) ... Keep counting.

But all the problems remain. So no instead of
Code: Pascal  [Select][+][-]
  1. {...};
  2. begin var x: integer;
  3.   {...}
  4. end;

It becomes
Code: Pascal  [Select][+][-]
  1. {...};
  2. with var x: integer do begin
  3.   {...}
  4. end;

or
Code: Pascal  [Select][+][-]
  1. {...};
  2. for var x: integer := 1 to to 1 do begin
  3.   {...}
  4. end;

Still as bad as any inline var.

And it doesn't solve any issues. As far as I can tell the "use after for" is artificially exaggerated. But even if there is a small amount of cases, where that problem exists and would be solved: What is the point of solving a real small issue by introducing other bigger issues?




But if you must have it, use anonymous functions.

Just replace  (not tested / similar syntax)
"begin" with "procedure uniquename; var ....; begin"
and replace
"end;" with "end();


Warfley

  • Hero Member
  • *****
  • Posts: 2021
Re: Inline vars / Re: Conditional ternary operators For FreePascal?
« Reply #16 on: August 10, 2025, 01:21:50 pm »
There is a big difference, because no one writes:
Code: Pascal  [Select][+][-]
  1. begin var x: integer;
  2.   {...}
  3. end;
Instead you'll see inline variables in the middle of blocks:
Code: Pascal  [Select][+][-]
  1. begin
  2.   {...}
  3.   var x: integer;
  4.   {...}
  5. end;
Where there can be hundreds of lines before and after the use of the variable, making figuring out to which scope it belongs quite hard.

Inline variables are most often not used for scoping, they are used for convinience, you need a new variable, you define it where you are right now. And this is the main issue that defining the variables on top wants to avoid, there should be one place where all the variables are, so you can see them in one glance, and not that you just add variables wherever and figuring out which variables are used by the function becomes a game of where is waldo.

This with construct makes clear that this is a construct for scoping, not for creating new variables. If you need a variable you would still create one normally.
Also note that it does not really add new functionality, With already creates new temporary variables, just without a name:
Code: Pascal  [Select][+][-]
  1. with FuncReturningRecord do
  2. begin
  3.   WriteLn(Field1);
  4.   WriteLn(Field2);
  5. end;
This does not call FuncReturningRecord twice, it only calls it once, stores the result in a temporary variable and lets you reference it in the block. All the new construct does is allow you to give it a name.

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 11824
  • Debugger - SynEdit - and more
    • wiki
Re: Inline vars / Re: Conditional ternary operators For FreePascal?
« Reply #17 on: August 10, 2025, 01:51:14 pm »
Where there can be hundreds of lines before and after the use of the variable, making figuring out to which scope it belongs quite hard.

My point. And the problem isn't just finding out "which scope", but finding them at all.

Quote
Inline variables are most often not used for scoping, they are used for convinience, you need a new variable, you define it where you are right now.
And also my point. It only adds convenience if you write spaghetti code, routines with a great amount of code lines. Because in a small routine its no effort (even without IDE tools) to add them on top (write your 10 lines proc, at the end go up, with one glance see all your vars and add them into the var section / at the same time giving your code a final check, that all your vars a typo free).

"typo free"
So if I have the misspelled "valeu" var, and I later type in "value" and the compiler error takes me to the "value" that isn't declared yet, then - with inline - I would just add there and then "var value" and loose the already assigned value. Making my code buggy. If I had to go to the top, I would have a fair chance to see the "valeu", and realize the real issue.


Quote
And this is the main issue that defining the variables on top wants to avoid, there should be one place where all the variables are, so you can see them in one glance, and not that you just add variables wherever and figuring out which variables are used by the function becomes a game of where is waldo.
+1

Quote
This with construct makes clear that this is a construct for scoping, not for creating new variables. If you need a variable you would still create one normally.

Well first of all, Pascal doesn't allow user defined scopes at the moment (afaik). We know that from the "interface" difference. Delphi releases them asap, FPC only at the end of the function.

If we do have code that is scoping sensitive, then that should be a new construct. ("with" already has a different meaning).

But even if we used "with". Scoping and declaration are separate items.
I.e. then it should be really considered, that you still declare your variable on top, in the var block, maybe with a modifier
Code: Pascal  [Select][+][-]
  1. procedure a;
  2. var foo: integer scoped some_label;
  3. begin {} end;

Scope could then be indicated inside the code.

One idea would be to bind scope to a label, that label could be given after the "scoped" modifier in the declaration.
The scope would be the ONE next statement after the label. (which would usually be a begin...end / either on its on, or as part of a loop/condition/...).

Mind you, I don't really see the need. (Except for helping with the interface ref count hack).
But, if it is about scope, well that way it would be following the Pascal style of pre declaration.

And it would even help the few, who according to their claims constantly use a loop index var without initialising it. Declare it scoped, apply the label before the loop, and the var is scoped to that loop, and that loop only. So this would solve it, and no inline var needed.


Quote
Also note that it does not really add new functionality, With already creates new temporary variables, just without a name:
Code: Pascal  [Select][+][-]
  1. with FuncReturningRecord do
  2.  

Temp vars are used in a lot of places. That is a compiler internal. Some "with" statements do create a temp var, others may not (afaik).

Also in many cases "results" (e.g. managed, or record) of function calls need a temp var, even without "with". (afaik again)




Off course, most of the above does not specify if "scope" means
- visibility only: you can use the identifier in the scoped code only , and outside the scope will get an error
- lifetime: any finalization (if there is) will be guaranteed at the end of the scope

Mind that there are cases where lifetime can be restricting optimization. I.e. forcing the compiler to add a finalize block, even though it could use a later already existing finalize.
But of course, for the interface ref count hack, lifetime would be wanted. (then again, that is a hack...)
« Last Edit: August 10, 2025, 01:56:43 pm by Martin_fr »

Warfley

  • Hero Member
  • *****
  • Posts: 2021
Re: Inline vars / Re: Conditional ternary operators For FreePascal?
« Reply #18 on: August 10, 2025, 02:00:35 pm »
Mind you, I don't really see the need. (Except for helping with the interface ref count hack).
But, if it is about scope, well that way it would be following the Pascal style of pre declaration.

Well it's quite simple, the biggest issue with programming is memory management. Most security vulnerabilities are due to manual memory management. Most crashes are due to use after free or something similar.

So the logical conclusion is to solve that problem to use managed types, which we can create with records. The problem is, if you allocate resources, like file descriptors, you need to be able to control the lifetime of said resources.
This is a real problem, if you have to wait for the function to exit before your file descriptor is closed, you may run into issues with other processes wanting to access the same files. If you allocate large amounts of memory, you want to free them as soon as possible, if you allocate locks for thread synchronization you don't want them to keep locked for more then necessary.

This is why all languages that have automatic memory management usually have some mechanism to do so.
Python has the with statement, C++ has scoped lifetime for inline variables, Java decouples finalization from destructors and uses try-finally.

Pascal only has the last option, which in Java has not really fared so well, which is why no other language replicated it since. So the two other options are to have implicit coupling of lifetime to scope like C++ or to do it using an explicit construct like the python with.
And I'd argue that having something explicit is more in the spirit of pascal than doing some inline variables that are implictly bound to a block that might be defined hundreds of lines away.

Also considering the DFA that was mentioned before, this is not an optimization problem. Optimization does not give guarantees. The DFA can only do heuristics, also it requires some performance, if I'm in a situation where I can't rely on the DFA, I should still be able to give the same guarantees in the code just from the way I write the code.

The only reason this is not necessary is if you don't use managed records a lot, which granted most pascal programmers do right now. But the reason for this is not because manually managed classes are better, just because the tools for making managed records usable are still missing.

Quote
I.e. then it should be really considered, that you still declare your variable on top, in the var block, maybe with a modifier

I mean this would also be an idea if there are named blocks in pascal, something I think would be quite valuable also in other situations (think of being able to break or continue other loops than just the most inner one).

The reason I chose with for this, is because it is already used that way a lot. Code like this:
Code: Pascal  [Select][+][-]
  1. with TStringList.Create do
  2. try
  3.   ReadFromFile(FileName);
  4.   WriteLn(Text);
  5. finally
  6.   Free;
  7. end;
Is very common in Pascal, because sometimes you just want to create a temporary object and throw it away afterwards without having to create a new variable for it.

So my idea was to make this also possible for managed types, because as I said before, manual memory management is bad and we should give the tools to use managed records as good as possible so we don't have to rely on manually managed classes as much.

But if you must have it, use anonymous functions.

Just replace  (not tested / similar syntax)
"begin" with "procedure uniquename; var ....; begin"
and replace
"end;" with "end();
Yeah you can create a macro for it, to enable it. But Pascal code should be readable, so relying on Macros is a very bad practice, and writing an inline ananymous function is also not a good solution.

It's a hack and workaround, but it creates bad and unreadable code and I believe our goal should be to make it as easy as possible to write good code. A language should be designed in a way that it's really easy to write good code and hard to write unreadable code.
Using anonymous functions is a terrible solution for this problem

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 11824
  • Debugger - SynEdit - and more
    • wiki
Re: Inline vars / Re: Conditional ternary operators For FreePascal?
« Reply #19 on: August 10, 2025, 02:41:00 pm »
About life time...

Except for the ref-counted interface (and managed types, include below), there is nothing that gets freed, closed, finalized at the end of "scope" however scope is defined.

- An open file handle does not get closed, unless you have a close statement.
- An object (internal pointer) could (if scope was added) be hidden from access after the scope,
  BUT:
  - it wouldn't be auto freed at the end of scope (you still have to do that)
  - there would be no protection against any earlier "destroy" call, in the middle of the scope

to use managed types, which we can create with records.
You mean make every file-handle a record with management operators?

I am not certain that this will really add the security you are looking for (in terms of potentially ending up with other issues, as you need to write the entire wrapper around it). But maybe...

But then - if that was used - you need scope with lifetime restriction (not just visibility).

To me "with" does not guarantee that. And because explicit lifetime restriction means the compiler can never (in future) optimize code (by joining internal finalize calls), IMHO such a restriction should not be added using something that is so non-explicit (i.e. that does not really tell the user its about lifetime.

IHMO here scoped blocks would be perfect
Code: Pascal  [Select][+][-]
  1. procedure foo;
  2. label MyScope; // could maybe be omitted and be auto-created by the scope below
  3. var
  4.   ManagedVar: TMyMgmtType scoped MyScope finalize; // the finalize is optional, and explicitly states to finalize this value at the end of its scope
  5. begin
  6.   {...}
  7.   MyScope: begin
  8.     ManagedVar := CreateManagedVal();
  9.     {...}
  10.   end; // ManagedVar will be finalized
  11.   {...}
  12. end;



Quote
I.e. then it should be really considered, that you still declare your variable on top, in the var block, maybe with a modifier

I mean this would also be an idea if there are named blocks in pascal

Not sure what you mean by "named block" that would exceed putting a label (as it exists already) in front of a statement (where the statement can be a block).

Of course, the compiler now needs to treat the (block) statement, as "the labeled code" -- rather than just making the label a point in front of the code.
But because the label has been declared in a "scoped" modifier for a variable, that would be possible.


And yes, IMHO "break Label" falls into a similar group...  It needs considerations on "Break" in general... But different topic.


The reason I chose with for this, is because it is already used that way a lot.
But you explicitly add the "finally".

The "with" only limits visibility (of the members). So "with" is a visibility scope. Bad idea to mix it with life-time scoping.

Also further more, the "with try finally" does not protect you from freeing the value at any point before the finally (leaving you wide open for use-after-free errors).

While I am not sure if it is a good idea (IMHO takes things to far), the "scoped" modifier could even be extended to
Code: Pascal  [Select][+][-]
  1. var  ManagedVar: TMyMgmtType scoped MyScope {finalize} immutable;
immutable really must include finalize.

That would mean you couldn't accidentally finallize your managed type by assigning any other value to it.
It would need to be initialized once, at the start of the statement. (maybe before the first conditional, so the compiler is guaranteed to be able to track it, yet you could have several scoped vars within the same block / gets a bit muddy here / need some refining)

Of course note that normal objects aren't managed types. So they don't fall into that at all...




Another variant of the above would be to make the finalize nature even more visible. This could be done, by requiring that any block (combined statement) for a "scoped foo finalize" variable must use the "try" keyword. But it may omit except finally.

So you would have
Code: Pascal  [Select][+][-]
  1. procedure foo;
  2. label MyScope; // could maybe be omitted and be auto-created by the scope below
  3. var
  4.   ManagedVar: TMyMgmtType scoped MyScope finalize; // the finalize is optional, and explicitly states to finalize this value at the end of its scope
  5. begin
  6.   {...}
  7.   MyScope: try
  8.     ManagedVar := CreateManagedVal();
  9.     {...}
  10.   end; // ManagedVar will be finalized
  11.   {...}
  12. end;

It does look a bit odd ...

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 11824
  • Debugger - SynEdit - and more
    • wiki
Re: Inline vars / Re: Conditional ternary operators For FreePascal?
« Reply #20 on: August 10, 2025, 02:47:48 pm »
Maybe more like your code (but not to my personal taste) would be a "try do finally" statement.

Mind you, still requiring declaration of the variable on top.

Code: Pascal  [Select][+][-]
  1. procedure foo;
  2. {label MyScope; }
  3. var
  4.   ManagedVar: TMyMgmtType scoped {MyScope};// the finalize is optional, and explicitly states to finalize this value at the end of its scope
  5. begin
  6.   {...}
  7.   try
  8.     ManagedVar := CreateManagedVal();
  9.   do
  10.     {...}
  11.   //  finally optional for other finally tasks in this "try"
  12.   end; // ManagedVar will be finalized
  13.   {...}
  14. end;

You still need the scope, to declare the var is only visible in one block.

The label might be skipped, the compiler should be able to find the block (or blocks, if you have multiple "try do")

Using "try" instead of "with" indicates that this is about lifetime scope.



It does introduce the need to recognize the "do" in the code. That is a bigger syntax change. (Hence I don't like it personally)

The other solution only added modifiers in the var section.


Also IIRC this had been discussed and rejected before.

Warfley

  • Hero Member
  • *****
  • Posts: 2021
Re: Inline vars / Re: Conditional ternary operators For FreePascal?
« Reply #21 on: August 10, 2025, 06:34:43 pm »
You mean make every file-handle a record with management operators?

I am not certain that this will really add the security you are looking for (in terms of potentially ending up with other issues, as you need to write the entire wrapper around it). But maybe...
There are a lot of things you can do with it, the file handle is just an obvious example, another one would be the following:
Code: Pascal  [Select][+][-]
  1. type
  2.   TAcquiredLock = record
  3.   private
  4.     Lock: PRTLCriticalSection;
  5.     class operator Initialize(var lock: TAcquiredLock);
  6.     class operator Copy(constref from: TAcquiredLock; var dest: TAcquiredLock);
  7.     class operator AddRef(var lock: TAcquiredLock);
  8.     class operator Finalize(var lock: TAcquiredLock);
  9.   end;
  10.  
  11. class operator TAcquiredLock.Initialize(var lock: TAcquiredLock);
  12. begin
  13.   lock.lock:=nil;
  14. end;
  15.  
  16. class operator TAcquiredLock.Copy(constref from: TAcquiredLock;
  17.   var dest: TAcquiredLock);
  18. begin
  19.   if from.lock<>nil then // Move semantics will take care that this is not called on function return
  20.     raise Exception.Create('Can''t copy acuired locks');
  21. end;
  22.  
  23. class operator TAcquiredLock.AddRef(var lock: TAcquiredLock);
  24. begin
  25.   if lock.lock<>nil then
  26.     raise Exception.Create('Can''t copy acuired locks');
  27. end;
  28.  
  29. class operator TAcquiredLock.Finalize(var lock: TAcquiredLock);
  30. begin
  31.   if Assigned(lock.lock);
  32.   LeaveCriticalSection(lock.lock^);
  33. end;
  34.  
  35. function Lock(var cs: TRTLCriticalSection): TAcquiredLock;
  36. begin
  37.   EnterCriticalSection(cs);
  38.   Result.Lock:=@cs;
  39. end;
  40.  

Now you can easily write critical section code with with:
Code: Pascal  [Select][+][-]
  1. with lock(mycs) do
  2. begin
  3.   // Critical section acquired
  4. end; // As soon as with block is left, critical section is freed
There are many more things you can do with it, managing file handles and critical sections are just one example.

To me "with" does not guarantee that. And because explicit lifetime restriction means the compiler can never (in future) optimize code (by joining internal finalize calls), IMHO such a restriction should not be added using something that is so non-explicit (i.e. that does not really tell the user its about lifetime.
Thats why I made the MR to change this. With is a terrible construct that pretty much all languages that have copied this over from pascal have either changed (like VB which requires a . or Python which uses it for scoping) or removed/deprecated like JS (a feature must be really really bad if it is deprecated in JavaScript, a language that completely reintroduced a new math library in parallel to the existing one because they didn't want to fix bugs that code relies on).

This MR mainly wanted to take this frankly horrible construct and give it some use that is not horrible.
Quote
Not sure what you mean by "named block" that would exceed putting a label (as it exists already) in front of a statement (where the statement can be a block).
I guess ada has this, but I'm not sure, basically something like this:
Code: Pascal  [Select][+][-]
  1. begin BaseBlock
  2.   while Condi1 do
  3.   begin WhileBlock
  4.     for i in MyArray do
  5.     begin // Unnamed block
  6.       {...}
  7.       continue WhileBlock; // This does the continue jump but not in the for loop but the while loop
  8.       {...}
  9.       break BaseBlock; // Jump out of baseblock
  10.       {...}
  11.       Break; // No block info breaks the current block (for)
  12.     end;
  13.   end WhileBlock;
  14. end BaseBlock;

Quote
But you explicitly add the "finally".
Yeah because I have to, as I said, it already creates a temporary variable, so why not make the lifetime of that temporary variable well defined?

Like I said it would even just add to the normal with statement:
Code: Pascal  [Select][+][-]
  1. with RecordReturningFunc() do
  2.   // use record
  3. // After with statement the temporary variable should be finalized if it is managed
I think it is pretty sensible if the lifetime of a temporary object is well established, to also enforce the management operators within that lifetime. There is no way to access the temporary object after the with anymore anyway, so why keep it around?

Quote
The "with" only limits visibility (of the members). So "with" is a visibility scope. Bad idea to mix it with life-time scoping.
It kinda does more than that, because it creates a temporary object, as my example with the try-finally above shows, it already can (and is in many code-bases) used for creating temporary objects.
The MR just extends the concept that already exists.

I actually like the idea with the scoping to blocks though, if I ever come around implementing named blocks, this would be an idea

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 11824
  • Debugger - SynEdit - and more
    • wiki
Re: Inline vars / Re: Conditional ternary operators For FreePascal?
« Reply #22 on: August 10, 2025, 08:18:09 pm »
Now you can easily write critical section code with with:
Code: Pascal  [Select][+][-]
  1. with lock(mycs) do
  2. begin
  3.   // Critical section acquired
  4. end; // As soon as with block is left, critical section is freed
There are many more things you can do with it, managing file handles and critical sections are just one example.

As I said, "with" to me as a reader would not indicate that there is a finalization. If I read that, I always would think it is a "leak". (in the sense that the finalization is done later than it should).

Also frankly, you aren't doing anything "WITH" the acquired lock. You do something "WHILE" you have it. So the "with" does not describe the use case.

As for the overall syntax, (but that is a personal taste issue), I do like to see the explicit LeaveCriticalSection statement (or some "unlock". So I know what is going on. The above code hides too much. "end" can just mean anything. But of course that is the same in my proposed "scoped" modifier code.

Quote
I guess ada has this, but I'm not sure, basically something like this:
Interesting, why would you put the name at the end?

Otherwise, prefixing a block by a label, and then using that as the name would do the same.
And it would make sense: break or continue the loop statement at/after the label (i.e. goto there and break/continue).

Quote

Quote
But you explicitly add the "finally".
Yeah because I have to, as I said, it already creates a temporary variable, so why not make the lifetime of that temporary variable well defined?
Because other people may have managed vars that wont make a diff about the time at which they are finalized, as long as they eventually are.
And then (in the future) the optimizer can bundle the auto-created internal finalize calls. And the code becomes more efficient.

If you have a reason, that the optimizer should not optimize that code, then you need to tell the optimizer.

Quote
There is no way to access the temporary object after the with anymore anyway, so why keep it around?
Because forcing immediate release costs time. Time that may not need to be spend.

Keeping it may (or may not) be free of cost (or at a lower cost).
Besides (see below) its internal undocumented, meaning it can be better optimized in future.

If you have a function returning a string, and you assign that to a variable, then there will be a temp var to hold the result.
If you have 20 such calls in your code (some maybe conditional), then you still only need the one temp var. And it does not need to be released after each call. Only if a new call is made (if a condition is entered), or at the end of the routine.
That can save some time.

Quote
Quote
The "with" only limits visibility (of the members). So "with" is a visibility scope. Bad idea to mix it with life-time scoping.
It kinda does more than that, because it creates a temporary object,

Again, not always.

Code: Pascal  [Select][+][-]
  1. program project1;{$Mode objfpc}
  2. type T = record a,b: integer; end;
  3. var x: T;
  4. begin
  5.   with x do begin
  6.     a := 1;
  7.     b := 2;
  8.   end;
  9. end.
  10.  

This did not create any temp var when I compiled it.

In your example it may be that calling a function created a temp var for "result" => which by the way happens without the "with" too (depending on the type of result).

So question: Can you get "with" to create a temp var where this would not be created by a function (if a function call is involved).  That is at -O- / not hidden by the optimizer.
But even if you can, then at best you can say "in some case with creates a temp var"
And if you can't, well then "with" does not create a temp var at all.

And then see my above statement, about when temp var for function results are released. Because if it is a record (with/without management), then it just takes some space on the stack. That space is allocated anyway. So there is no cost to defer finalization.
Of course if the optimizer can use that space shared for other temp vars, then the optimizer could decide otherwise. But that is about when its best for performance or stack usage.

In any case, the current behaviour (not finalizing immediately) is actually a feature (even if the optimizer doesn't do much yet / most of it is a "reserved" feature). So it must not be removed. It must be kept an undocumented implementation detail, so future optimizations remain possible.


Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 11824
  • Debugger - SynEdit - and more
    • wiki
Re: Inline vars / Re: Conditional ternary operators For FreePascal?
« Reply #23 on: August 10, 2025, 08:22:57 pm »
So question: Can you get "with" to create a temp var where this would not be created by a function (if a function call is involved).  That is at -O- / not hidden by the optimizer.

And to elaborate, of course you can have an expression in the with. That also creates a temp var.

And in this case it is less clear, who creates the temp var. Some expressions do create temp vars (at least for intermediate results). They may not create a temp var if they can use an existing real var directly. But if they can't, then who is creating the temp var? It probably can't be told, or maybe only by inspecting the compiler sources (maybe)....

PascalDragon

  • Hero Member
  • *****
  • Posts: 6195
  • Compiler Developer
Re: Inline vars / Re: Conditional ternary operators For FreePascal?
« Reply #24 on: August 10, 2025, 09:40:46 pm »
If FreePascal lacks Delphi syntax,  it does not have a
Code: [Select]
{$mode delphi} but a
Code: [Select]
{$mode delphi-like}

The Delphi mode already is such, because it does not support all Delphi features (even before inline variables). And if it never will that is okay as well.

Warfley

  • Hero Member
  • *****
  • Posts: 2021
Re: Inline vars / Re: Conditional ternary operators For FreePascal?
« Reply #25 on: August 10, 2025, 11:11:56 pm »
Interesting, why would you put the name at the end?

Otherwise, prefixing a block by a label, and then using that as the name would do the same.
And it would make sense: break or continue the loop statement at/after the label (i.e. goto there and break/continue).
Basically because I half remembered the Ada syntax xD

Code: Pascal  [Select][+][-]
  1. begin -- blockname
  2.   ...
  3. end blockname;

Adding a token (--) before makes sense because otherwise the parser could not distinguish between a block name or an identifier as part of a statement. So I guess in FPC a better solution would be:
Code: Pascal  [Select][+][-]
  1. begin : blockname
  2.   ...
  3. end blockname;
to not introduce a new token (and afaik there is no statement that starts with a colon)

Quote
In your example it may be that calling a function created a temp var for "result" => which by the way happens without the "with" too (depending on the type of result).
Kinda but also not really, the temp var for with is created whenever there is an object of which the address cannot be taken for one reason or another (it's also a bit more complicated than this). Functions can (especially in the case of managed return types) create additional temporary variables in the process as well. So there can be multiple temporary objects for this single line of code, I mention this temporary object specifically because it is managed by the with construct and associated with the with symbol table that is used in the parser. So it kinda belongs together.

Quote
So question: Can you get "with" to create a temp var where this would not be created by a function (if a function call is involved).  That is at -O- / not hidden by the optimizer.
But even if you can, then at best you can say "in some case with creates a temp var"
And if you can't, well then "with" does not create a temp var at all.
Sure :)
Code: Pascal  [Select][+][-]
  1. {$Mode ObjFpc}{$H+}
  2.  
  3. type
  4.   TTest = class
  5.   public function GetSelf: TTest;
  6.   end;
  7.  
  8. function TTest.GetSelf: TTest;
  9. begin
  10.   Result:=Self;
  11. end;
  12.  
  13. begin
  14.   with TTest(nil) do
  15.     WriteLn(IntPtr(GetSelf));
  16. end.
  17.  
From the tree.log (fpc -vp -O- test.pas):
Code: Pascal  [Select][+][-]
  1.          (statementn, resultdef = <nil>, pos = (14,19), loc = LOC_INVALID, expectloc = LOC_VOID, flags = [], cmplx = 255
  2.             (tempcreaten, resultdef = $void = "untyped", pos = (14,19), loc = LOC_INVALID, expectloc = LOC_VOID, flags = [nf_pass1_done], cmplx = 0
  3.                size = 4, temptypedef = TTest = "TTest", tempinfo = $4F124E18
  4.                [ti_may_be_in_reg]
  5.                tempinit =
  6.                nil
  7.             )
  8.  
  9.          )
  10.          (statementn, resultdef = <nil>, pos = (14,19), loc = LOC_INVALID, expectloc = LOC_VOID, flags = [], cmplx = 255
  11.             (assignn, resultdef = $void = "untyped", pos = (14,19), loc = LOC_INVALID, expectloc = LOC_VOID, flags = [nf_pass1_done], cmplx = 1
  12.                (temprefn, resultdef = TTest = "TTest", pos = (14,19), loc = LOC_INVALID, expectloc = LOC_CREG, flags = [nf_pass1_done,nf_write], cmplx = 1
  13.                   temptypedef = TTest = "TTest", (tempinfo = $4F124E18 flags = [ti_may_be_in_reg])
  14.                )
  15.                (temprefn, resultdef = TTest = "TTest", pos = (14,19), loc = LOC_INVALID, expectloc = LOC_CREG, flags = [nf_pass1_done], cmplx = 1
  16.                   temptypedef = TTest = "TTest", (tempinfo = $4F124DB8 flags = [ti_may_be_in_reg])
  17.                )
  18.             )
  19.  
  20.          )
Because nil is a constant, the compiler cannot take the address of it, and therefore has to create a tempnode.

I mean the rule is quite simple, if the thing written in the with expression is anything but a load of an existing object, a new object will be created.
And yeah while this is not the main purpose of with, for which it was designed, I'd argue that it's a very common usage of with for people wanting a temporary object. As I said before, people use this property of with already with classes, quite extensively. So why not take it and make it a real and documented feature instead?
Like I keep saying it doesn't do anything new, it just takes something that already exists and is used exactly for that purpose and makes it better :)

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 12536
  • FPC developer.
Re: Inline vars / Re: Conditional ternary operators For FreePascal?
« Reply #26 on: August 10, 2025, 11:27:57 pm »
Well first of all, Pascal doesn't allow user defined scopes at the moment (afaik). We know that from the "interface" difference. Delphi releases them asap, FPC only at the end of the function.

(Afaik other way around (Delphi saves them longer to end of procedure or block in very large procedures) and that changed with the inline var versions and beyond, probably because they reworked the stack frame code)

https://blog.synopse.info/?post/2021/09/21/Delphi-10.4-/-Delphi-11-Alexandria-Breaking-Changes
« Last Edit: August 10, 2025, 11:30:15 pm by marcov »

linuxfan

  • Jr. Member
  • **
  • Posts: 60
Declare variables in function body?
« Reply #27 on: August 11, 2025, 12:20:25 pm »
I suspect this post could raise angry replies (hope not  :)), but I would, if nothing else, know something about.

I use Lazarus for desktop, and I am very happy with it, and with freepascal. But most of my work is done in C because my job is to write firmware for electronic boards.

Before C99, C variables were declared exactly like in pascal, their declaration should precede any statement.

Now, C99 is widely used, and I get used to a few improvements, the bigger of them being the possibility to create variables on the fly, like this:

Quote
         for (int i=0; i<5; i++) ...

or, more simply, things like:

Quote
byte code = getkeyboard();

Now, every time I start to code in pascal, I miss this functionality. I am in the middle of a function, and realise that I need a new variable. So i move the cursor to the VAR section above, write the declaration of the var, then I go back down to the point were I was before to write the code about the new var.

This is quite more typing and it's tiring to scroll up and down whatching well in order to identify the two spots (VAR section and appropriate location in function body).

Now I ask: would it be blasfeme to introduce this capability in freepascal too? I think the code written that way also gains in clarity, because temp variables could be written outside the VAR section, which would contain only important ("significative") variables...

I tried to search on the internet and through this forum, by found nothing.

Consider that I am also a lover of the purity of pascal (in fact I formatted pascal as "code", and C as "quote" :-)), but this very thing should not dirten (dirten?) the language. It would be simple like:

Code: Pascal  [Select][+][-]
  1. var temp: integer = getkeyboard();

or, even better would be to omit the type declaration, that could be inferred by the right-hand part of the assignment:

Code: Pascal  [Select][+][-]
  1. var temp = getkeyboard();

I don't mean to add on-the-fly variables also in other constructs like C, it would suffice the simple and clean way of declaring a *single* variable in one line, as shown.


marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 12536
  • FPC developer.
Re: Inline vars / Re: Conditional ternary operators For FreePascal?
« Reply #28 on: August 11, 2025, 12:33:53 pm »
(Moderator notice, I merged a redundant question about inline vars)

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 11824
  • Debugger - SynEdit - and more
    • wiki
Re: Declare variables in function body?
« Reply #29 on: August 11, 2025, 12:55:04 pm »
Now, every time I start to code in pascal, I miss this functionality

That is a point I tried to counter earlier...

Generic excurse (not just inline var / but anything)

Whenever anyone switches between languages, if they have used the other language extensively enough, then there is a good chance they miss something from that other language (been there myself).

Not (or no always / not most of the times) because there wasn't a good enough feature in Pascal (or whatever other language someone switches too), but because when you use one language, you start doing things in certain ways: workflows, think patterns, all of that is influenced by the language.
And all of that needs to be changed when the language is changed.
And the human brain seems to like to stick with the old ways...

But, if we embraced the above as argument, then the only answer would be to reduce/extend everything (every language) into one single "mix it all" - language.
That obviously isn't a good goal.

So, we need to step back, what we miss when we switch between languages. Because we will always miss things then, even if there are perfect and even much better alternatives.



So what does "its too much work to always go to the var section" tell us?

It might tell us:
- the routine body is way to long, so the distance is to big
- there are way to many local vars, so we can't add them up front, or in one batch at the end (checking their correctness when we add them)
- ...

Meaning, there is a good chance, that when we want to use inline vars, we are hiding another issue.
« Last Edit: August 11, 2025, 01:02:02 pm by Martin_fr »

 

TinyPortal © 2005-2018