Lazarus

Programming => General => Topic started by: Okoba on January 02, 2025, 09:12:00 am

Title: Why with allows assignment
Post by: Okoba on January 02, 2025, 09:12:00 am
Code: Pascal  [Select][+][-]
  1. program Project1;
  2.  
  3. type
  4.   TTest = record
  5.     V: Integer;
  6.   end;
  7.  
  8. var
  9.   X: TTest;
  10.  
  11.   function Test: TTest;
  12.   begin
  13.     Result := X;
  14.   end;
  15.  
  16. var
  17.   T: TTest;
  18. begin
  19.   with Test do
  20.     V := 1; //No Error
  21.   Test.V := 1; //Error: Argument cannot be assigned to
  22. end.                

Why the first test raise no error like the second one?

And on the same topic, is there a way in Pascal that I can return a record by the reference so any change to the result, changes the source?
For example changing V changes the original X.
I know I can return the pointer, but is there any other way?
Title: Re: Why with allows assignment
Post by: egsuh on January 02, 2025, 09:30:04 am
I think  with with Test, function Test is evaluated which means an address (and memory space) is allocated, while in Test.V, Test itself is nil so that it is error.
Title: Re: Why with allows assignment
Post by: cdbc on January 02, 2025, 09:38:12 am
Hi
Quote
And on the same topic, is there a way in Pascal that I can return a record by the reference so any change to the result, changes the source?
For example changing V changes the original X.
I know I can return the pointer, but is there any other way?
Records are copied by value, so no, that's why we use pointers or 'var' parameters, which are basically pointers to the value...
Regards Benny
Title: Re: Why with allows assignment
Post by: alpine on January 02, 2025, 09:39:48 am
The function result is a temporary and the assignment is of no effect. It is debatable whether it is an error, probably there is no such check on with.
See: https://forum.lazarus.freepascal.org/index.php/topic,60561.0.html
Title: Re: Why with allows assignment
Post by: Zvoni on January 02, 2025, 09:52:11 am
https://forum.lazarus.freepascal.org/index.php/topic,39961.msg275471.html#msg275471
Quote
No. The left side of an ":=" statement must have a (non temporary?) memory address and be writable. A function result doesn't.

For the part with "With Test": I'm with egsuh on this.
"With" starts a new scope, and in this case (probably?) evaluates the "Test"-Function and returns the Result, getting an Address in Memory.
The moment the code leaves the "With"-scope that memory is discarded
Title: Re: Why with allows assignment
Post by: Okoba on January 02, 2025, 09:54:14 am
Hi
Quote
And on the same topic, is there a way in Pascal that I can return a record by the reference so any change to the result, changes the source?
For example changing V changes the original X.
I know I can return the pointer, but is there any other way?
Records are copied by value, so no, that's why we use pointers or 'var' parameters, which are basically pointers to the value...
Regards Benny

So you can not make something like String with records?
Title: Re: Why with allows assignment
Post by: Okoba on January 02, 2025, 09:55:04 am
https://forum.lazarus.freepascal.org/index.php/topic,39961.msg275471.html#msg275471
Quote
No. The left side of an ":=" statement must have a (non temporary?) memory address and be writable. A function result doesn't.

For the part with "With Test": I'm with egsuh on this.
"With" starts a new scope, and in this case (probably?) evaluates the "Test"-Function and returns the Result, getting an Address in Memory.
The moment the code leaves the "With"-scope that memory is discarded

Then why the first test using with returns no error?!
Title: Re: Why with allows assignment
Post by: egsuh on January 02, 2025, 10:00:49 am
Quote
Then why the first test using with returns no error?!

At the moment of assignment, there exists memory space. Anyway I think you cannot access the value even within "with" scope because there are no way to know the address.
Title: Re: Why with allows assignment
Post by: Zvoni on January 02, 2025, 10:01:08 am
https://forum.lazarus.freepascal.org/index.php/topic,39961.msg275471.html#msg275471
Quote
No. The left side of an ":=" statement must have a (non temporary?) memory address and be writable. A function result doesn't.

For the part with "With Test": I'm with egsuh on this.
"With" starts a new scope, and in this case (probably?) evaluates the "Test"-Function and returns the Result, getting an Address in Memory.
The moment the code leaves the "With"-scope that memory is discarded
Then why the first test using with returns no error?!
Because it has a valid and writable address in memory, allowing you to access "V" and write to it.

It's the same as
Code: Pascal  [Select][+][-]
  1. With TSomeClass.Create Do
  2.   Begin
  3.     SomeInteger:=42;    //Assign 42 to Field/Property "SomeInteger"
  4.     DoSomething; //Execute Method "DoSomething" of TSomeClass
  5.   End;
This works because the whole "With"-Block represents a valid Address in memory, for the lifetime of the "With"-Block
Title: Re: Why with allows assignment
Post by: Okoba on January 02, 2025, 10:09:18 am
I think that "I can not set the result of function directly" and "with gives you direct access to that variant". But what I think you are saying is that `with` is copying the result of function so it can allow setting the value of that temp silently. It seems wrong from the point of a reader like me. I get the technicality of it, but it leads to dangerous results.
Title: Re: Why with allows assignment
Post by: Zvoni on January 02, 2025, 10:18:52 am
I think that "I can not set the result of function directly" and "with gives you direct access to that variant". But what I think you are saying is that `with` is copying the result of function so it can allow setting the value of that temp silently. It seems wrong from the point of a reader like me. I get the technicality of it, but it leads to dangerous results.
That's exactly the reason why i have never used "with" in my life.
It confuses the hell out of me
Title: Re: Why with allows assignment
Post by: cdbc on January 02, 2025, 10:24:35 am
Hi
Well, yes the 'with' construct can be a bit /dangerous/ for/to the unwary...
Basically what 'with' does is *keep* the temporary result /alive/ long enough, for you to use it and then discards it :(
Quote
So you can not make something like String with records?
Well, yes and no - it depends on what you're doing...  %)
If you need to *have* the content in the record, then use 'ShortString' or for longer than 256 char strings use e.g.: 'array[0..1023] of char;' Those strings will stay in the record even when copied or written to disk.
Otoh, nothing stops you from using ansistrings or widestrings in records, just as long as you're clear about the fact, that it's *only* a pointer to the actual /string-content/ placed somewhere else. Write such a record to file and you'll see, only the pointer-value of the string is written, *not* the actual content!
HTH
Regards Benny
Title: Re: Why with allows assignment
Post by: 440bx on January 02, 2025, 10:53:14 am
Code: Pascal  [Select][+][-]
  1. program Project1;
  2.  
  3. { removed }
  4.  
  5. var
  6.   T: TTest;
  7. begin
  8.   with Test do
  9.     V := 1; //No Error
  10.   Test.V := 1; //Error: Argument cannot be assigned to
  11. end.                

Why the first test raise no error like the second one?
I believe you found a compiler bug.  "with Test do V := 1;" should be _exactly_ the same as "Test.V := 1;".  "with" is basically a way to abbreviate, it's not supposed to have any side effect.  IMO and formally, that is a bug.

Let's see what the FPC developers say.
Title: Re: Why with allows assignment
Post by: alpine on January 02, 2025, 11:08:04 am
Quote
Let's see what the FPC developers say.
There are posts from PascalDragon in the link I have provided. It is the same topic.
Title: Re: Why with allows assignment
Post by: DragoRosso on January 02, 2025, 11:22:59 am
In Delphi, both the assignment within the WITH and the direct assignment are indicated as errors.
I believe (but I am the last one to give credence to) that a similar situation should be handled and the error should be reported to the developer.
That assignment is an error, and therefore it should be reported, even if it does not produce any effect, and that is precisely the point: the developer writing that code expects it to be executed (ok, he wrote a stupid thing) and the fact that it is not executed (or that it cannot be executed) MUST be reported by the compiler.
Title: Re: Why with allows assignment
Post by: jamie on January 02, 2025, 10:33:45 pm
U have your opinion others have theirs including mine. I see nothing wrong with it and it would break code like it's going out of style otherwise.
Please don't compare d with fpc when d can't do something fpc can.
Title: Re: Why with allows assignment
Post by: 440bx on January 03, 2025, 12:18:49 am
I see nothing wrong with it and it would break code like it's going out of style otherwise.
Other than FPC doing it _wrong_, there is nothing wrong with it.  Fixing that bug can only break code that is wrong... no great loss there and wrong code should go out of style anyway.


Please don't compare d with fpc when d can't do something fpc can.
I remember at least one case where you compared FPC to Delphi to imply there was a bug in FPC but, of course, it's ok when you do it but not others.   
Title: Re: Why with allows assignment
Post by: jamie on January 03, 2025, 12:45:03 am
I see nothing wrong with it and it would break code like it's going out of style otherwise.
Other than FPC doing it _wrong_, there is nothing wrong with it.  Fixing that bug can only break code that is wrong... no great loss there and wrong code should go out of style anyway.


Please don't compare d with fpc when d can't do something fpc can.
I remember at least one case where you compared FPC to Delphi to imply there was a bug in FPC but, of course, it's ok when you do it but not others.

 You say whatever you want but if compiler can do something useful where the other cannot, I prefer that to be a bonus.

 And yes, there are things D can do where FPC still in the back on and it's been demonstrated many times only to get slapped back telling me and others that is the way it is, so you need to do extra work to overcome issues that don't work as expected but works fine in Delphi.

 I won't get into this because it looks like someone wants to start a flame war on something just because they fell, they can but facts are facts.

As for the WITH control statement, I've ported D code over that uses that WITH with returning items from a function that seem to work just fine in D and also FPC. Basic facts about WITH is it creates a local branch of identifiers. In this case a RECORD where that record maybe referenced several times read or written while inside the WITH.

 I get the impression maybe some language features are misunderstood so some would rather have it removed instead of grasping it.


Title: Re: Why with allows assignment
Post by: dbannon on January 03, 2025, 12:52:47 am
Are we over thinking this ? The OP wants to return a record's contents, thats easy. He/she has become confused by using "with" and trying, apparently, to apply it to a function. Drop the "with", its use is almost always confusing and this works fine :

Code: Pascal  [Select][+][-]
  1. program Project1;
  2. {$mode objfpc}   // Note !
  3.  
  4. type
  5.   TTest = record
  6.     V: Integer;
  7.         S : string;
  8.   end;
  9.  
  10. var
  11.   X: TTest;
  12.  
  13.   function Test: TTest;
  14.   begin
  15.         result := X;
  16.   end;
  17.  
  18. var
  19.   T: TTest;
  20. begin
  21.         X.V := 27;
  22.         X.S := 'hello';
  23.         T := Test();
  24.         writeln(T.S, ' ', T.V);
  25. end.          
  26.  

Davo
Title: Re: Why with allows assignment
Post by: 440bx on January 03, 2025, 01:53:47 am
You say whatever you want but if compiler can do something useful where the other cannot, I prefer that to be a bonus.
It's not a bonus, it's a bug.  "with" is simply shorthand to specify a scope for one or more statements.  Whether the scope is specified using a "with" or explicitly in the statement should not make any difference.  FPC is _wrong_, period.  That's not an opinion, it's a fact.
Title: Re: Why with allows assignment
Post by: egsuh on January 03, 2025, 02:29:09 am
I love "with". Especially "with" is convenient in following cases. 

Code: Pascal  [Select][+][-]
  1.    with AForm.create (nil) do begin
  2.        Fill_AForms_Values_If_Necessary;
  3.        if ShowModal then begin
  4.           Fill_In_Variables_Of_Calling_Unit_From_AForm;
  5.        end;
  6.        Free;
  7.     end;
  8.  
  9.      with AClass.Create do begin
  10.            Load_Content_Form_DataBase (Key_Field_Values);
  11.            Do_Operations:
  12.            Save_To_DataBase(Key_Field_Values);
  13.  
  14.            Free;
  15.      end;


With advanced records, I can define methods within it to retrieve or save contents to database, etc. just like AClass in the previous case.
Title: Re: Why with allows assignment
Post by: Thaddy on January 03, 2025, 03:18:18 pm
didn't OP simly made the error that it should be
Code: Pascal  [Select][+][-]
  1. T.V
That code also fails with Delphi.
Title: Re: Why with allows assignment
Post by: PascalDragon on January 03, 2025, 04:04:39 pm
Why the first test raise no error like the second one?

Because for with it's simply not implemented yet. After all from a language point of view both are valid. However current Delphi versions forbid both due to wrong expectations of the users and FPC up to now only errors out on the function result, because it's something that the compiler needs to actively check for.

And on the same topic, is there a way in Pascal that I can return a record by the reference so any change to the result, changes the source?

Use a managed record.

As for the WITH control statement, I've ported D code over that uses that WITH with returning items from a function that seem to work just fine in D and also FPC. Basic facts about WITH is it creates a local branch of identifiers. In this case a RECORD where that record maybe referenced several times read or written while inside the WITH.

*sigh* Current Delphi versions forbid this code as well, because it does not do what you expect it to do.
Title: Re: Why with allows assignment
Post by: Thaddy on January 03, 2025, 06:34:24 pm
Yes. For quite some versions btw.
It was their bug, not ours.
Title: Re: Why with allows assignment
Post by: VisualLab on January 03, 2025, 07:52:24 pm
In Delphi, both the assignment within the WITH and the direct assignment are indicated as errors.
I believe (but I am the last one to give credence to) that a similar situation should be handled and the error should be reported to the developer.
That assignment is an error, and therefore it should be reported, even if it does not produce any effect, and that is precisely the point: the developer writing that code expects it to be executed (ok, he wrote a stupid thing) and the fact that it is not executed (or that it cannot be executed) MUST be reported by the compiler.

I confirm. I just checked this code in Delphi 12. The compiler reports 2 errors in both assignments:
Quote
with Test do
    V := 1; //No Error
  Test.V := 1; //Error: Argument cannot be assigned to



Code: Pascal  [Select][+][-]
  1. program Project1;
  2.  
  3. { removed }
  4.  
  5. var
  6.   T: TTest;
  7. begin
  8.   with Test do
  9.     V := 1; //No Error
  10.   Test.V := 1; //Error: Argument cannot be assigned to
  11. end.                

Why the first test raise no error like the second one?
I believe you found a compiler bug.  "with Test do V := 1;" should be _exactly_ the same as "Test.V := 1;".  "with" is basically a way to abbreviate, it's not supposed to have any side effect.  IMO and formally, that is a bug.

Let's see what the FPC developers say.

I see nothing wrong with it and it would break code like it's going out of style otherwise.
Other than FPC doing it _wrong_, there is nothing wrong with it.  Fixing that bug can only break code that is wrong... no great loss there and wrong code should go out of style anyway.

Please don't compare d with fpc when d can't do something fpc can.
I remember at least one case where you compared FPC to Delphi to imply there was a bug in FPC but, of course, it's ok when you do it but not others.

You say whatever you want but if compiler can do something useful where the other cannot, I prefer that to be a bonus.
It's not a bonus, it's a bug.  "with" is simply shorthand to specify a scope for one or more statements.  Whether the scope is specified using a "with" or explicitly in the statement should not make any difference.  FPC is _wrong_, period.  That's not an opinion, it's a fact.

I fully agree with 440bx that this is a bug in FPC. And I hope it will be fixed in future versions.



Perhaps in FPC (and Lazarus) a compilation switch would be useful, allowing you to disable the use of "with" in the code (report its presence as an error) in the project options.
Title: Re: Why with allows assignment
Post by: Warfley on January 03, 2025, 08:07:38 pm
Pretty simple, with creates a new temporary variable where the function result is copied into. This is intended because the object must be persistent in the with block:
Code: Pascal  [Select][+][-]
  1. with Test do
  2. begin
  3.   V:=42;
  4.   WriteLn(V);
  5. end;

This is not a bug, it is specifically mentioned that this should be possible in a comment in the source of the Compiler. So someone built it this way to make this possible
Title: Re: Why with allows assignment
Post by: 440bx on January 03, 2025, 08:39:51 pm
Pretty simple, with creates a new temporary variable where the function result is copied into.
That's nonsense.

"with" doesn't create anything.  The "with" statement is used to establish a scope, _nothing_ more, nothing less.  It's information for the parser.

The result of "<scope_identifier>.<identifier> := ..." must be _identical_ to "with <scope_identifier> do <identifier> := ..." because these statements _MUST_ be semantically identical.   "with" is just convenient shorthand to apply a scope identifier to multiple statements (sorely missing in C/C++). 

The results obtained in either case MUST be identical. ANY functional difference is a _bug_.  period.
Title: Re: Why with allows assignment
Post by: Warfley on January 03, 2025, 08:57:44 pm
It creates a scope for an object. If the object is referencable it just creates a scope around the existing object. If it is the result of some expression, it copies it into a new temporary object to make it referencable: https://gitlab.com/freepascal.org/fpc/source/-/blob/main/compiler/pstatmnt.pas#L659
Title: Re: Why with allows assignment
Post by: VisualLab on January 03, 2025, 09:20:53 pm
It creates a scope for an object. If the object is referencable it just creates a scope around the existing object. If it is the result of some expression, it copies it into a new temporary object to make it referencable: https://gitlab.com/freepascal.org/fpc/source/-/blob/main/compiler/pstatmnt.pas#L659

I understand that, but it's still a quirk handled by the compiler. I agree with:

"with" doesn't create anything.  The "with" statement is used to establish a scope, _nothing_ more, nothing less.  It's information for the parser.



Apart from the fact that it can be done technically (the compiler supports it), is there any deeper justification for the existence of such language constructs? I.e., does it have any significant/practical use, is it useful for something serious? Because at the moment it seems to be just a technical curiosity, without practical application (and undermining Pascal's clarity and unambiguity).
Title: Re: Why with allows assignment
Post by: 440bx on January 03, 2025, 09:23:39 pm
It creates a scope for an object. If the object is referencable it just creates a scope around the existing object. If it is the result of some expression, it copies it into a new temporary object to make it referencable: https://gitlab.com/freepascal.org/fpc/source/-/blob/main/compiler/pstatmnt.pas#L659
What FPC is doing is simply _wrong_.  The presence or absence of a "with" should _never_ alter the semantics.  If it does, it's a bug, it's that simple.

In addition to that, outside the scope of a function, a function identifier cannot be used as an lvalue, that doesn't make any sense, which seems to be what FPC is doing in the presence of the "with".  The "with" isn't creating a temporary, a temporary may be created to hold the result of a function but that's totally independent of the presence or absence of a "with" statement".
Title: Re: Why with allows assignment
Post by: Warfley on January 03, 2025, 09:43:19 pm
What do you think should be result of this:
Code: Pascal  [Select][+][-]
  1. function Test: TTest;
  2. const
  3.   ctr: Integer = 0;
  4. begin
  5.   Result.I := ctr;
  6.   Inc(ctr);
  7. end;
  8.  
  9. with Test do
  10. begin
  11.   WriteLn(I);
  12.   WriteLn(I);
  13. end;
The current semantics of with is that you access the result of a single Test call, so both WriteLns are giving the same result.
I mean if you personally think, with should be nothing more than a stupid macro, replacing I with Test.I, that's a opinion you can have, but it's specifically not how with is intended to be used for decades

Also it's literally the only part of with which isn't awful and makes it actually a useful feature:
Code: Pascal  [Select][+][-]
  1. with TstringList.Create do
  2. try
  3.   ...
  4. finally
  5.   Free;
  6. end;
Title: Re: Why with allows assignment
Post by: 440bx on January 03, 2025, 10:00:37 pm
that's a opinion you can have, but it's specifically not how with is intended to be used for decades
Nice try.  That's _not_ an opinion. 

Find a formal Pascal standard that supports what you're saying.  Good luck!!  Just in case, FPC is _not_ a Pascal standard.

BTW, do you think assigning a value to a function should be allowed outside of the function ?  Are function results lvalues ?  (looks like in some cases, to FPC, they are.)

Title: Re: Why with allows assignment
Post by: Warfley on January 03, 2025, 10:31:25 pm
Find a formal Pascal standard that supports what you're saying.  Good luck!!  Just in case, FPC is _not_ a Pascal standard.

BTW, do you think assigning a value to a function should be allowed outside of the function ?  Are function results lvalues ?  (looks like in some cases, to FPC, they are.)

I don't care what the Pascal standards state, most people never write standard Pascal. ISO Pascal does not even support units. Pascal, at least the dialects people use in the real world, is not standardized, it's a quasi standard like java, where the major distributions define the functionality through their implementations and documentation.

This is intended behavior for FPC, like it or not, but it's how the language works

And what cases do you mean? I only know of cases where the result of a function will be written into a temporary variable, like with does. Functions don't Return lvalues, but their result can be written to an object which can be used as an lvalue
Title: Re: Why with allows assignment
Post by: Thaddy on January 03, 2025, 10:43:33 pm
What a load of..
back to the question:
Code: Pascal  [Select][+][-]
  1. program Project1;
  2.  
  3. type
  4.   TTest = record
  5.     V: Integer;
  6.   end;
  7.  
  8. var
  9.   X: TTest;
  10.  
  11.   function Test: TTest;
  12.   begin
  13.     Result := X;
  14.   end;
  15.  
  16. var
  17.   T: TTest;
  18. begin
  19.   with Test do
  20.     V := 1; //No Error
  21.   // did everybody overlook this due to the festive days.... >:( >:D
  22.   //Test.V := 1; // of course that's an error, you can't do that with a *function* test
  23.     T.V := 1; // is as it should be
  24. end.
               
Simply stupid error and nothing to do with "with".
 6 days from home and the kids can't even read the question properly.
Title: Re: Why with allows assignment
Post by: 440bx on January 03, 2025, 11:06:12 pm
I don't care what the Pascal standards state,
and that's one of the many reasons Pascal has been going nowhere for years now.

most people never write standard Pascal.
Most people do write standard Pascal, admittedly with a lot of extensions but, a lot of FPC is standard Pascal, e.g, compound statements, if statements, while statements, etc, etc, etc.   Wow!! unknowingly, you've been writing standard Pascal.

ISO Pascal does not even support units. Pascal, at least the dialects people use in the real world, is not standardized, it's a quasi standard like java, where the major distributions define the functionality through their implementations and documentation.
A lot of that is true but, if you knew how to write a compiler you'd know that "with" does _not_ create any temporaries and you'd also know that, outside of a function, function results cannot be treated as lvalues and, not only that, you'd also know why.

This is intended behavior for FPC, like it or not, but it's how the language works
I don't know if it is intended or not but, one thing is absolutely certain, it is wrong. 

And what cases do you mean? I only know of cases where the result of a function will be written into a temporary variable, like with does.
It is NOT the "with" that causes the function result to be held into a temporary variable.  The result will be held in a temporary variable if it does not fit into a register (or sometimes, two) whether or  not there is a "with".  You should know that, it is _obvious_ and the reason why is obvious too.

Functions don't Return lvalues,
Of course they don't and for that very reason a value cannot be assigned to them outside of the function.   You cannot have "FunctionName.FunctionRecordFieldname := somevalue" whether there is a "with" or not because as you stated, function results cannot be treated as lvalues outside of the function.

Title: Re: Why with allows assignment
Post by: Warfley on January 03, 2025, 11:18:08 pm
It is NOT the "with" that causes the function result to be held into a temporary variable.  The result will be held in a temporary variable if it does not fit into a register (or sometimes, two) whether or  not there is a "with".  You should know that, it is _obvious_ and the reason why is obvious too.

I already linked the respective position in the FPC source but here it is:
Code: Pascal  [Select][+][-]
  1.                 if not valid_for_addr(p,false) then
  2.                   begin
  3.                     calltempnode:=ctempcreatenode.create(p.resultdef,p.resultdef.size,tt_persistent,true);
  4.                     addstatement(newstatement,calltempnode);
  5.                     addstatement(newstatement,cassignmentnode.create(
  6.                         ctemprefnode.create(calltempnode),
  7.                         p));
  8.                     p:=ctemprefnode.create(calltempnode);
  9.                     typecheckpass(p);
  10.                   end;
  11.  
Do you see the ctempcreatenode.create? It creates a new temporary variable, then assigns the return from the function to it (cassignmentnode.create), before creating references to this node to be used instead of the call. All of that in the function _with_statement in pstatmnt.pas

I'm sorry but what you wrote is just plainly wrong. With creates a new temporary object for the result of the function, to be referenced within the with scope.
Title: Re: Why with allows assignment
Post by: LV on January 03, 2025, 11:55:12 pm
I was passing by
Title: Re: Why with allows assignment
Post by: Thaddy on January 04, 2025, 12:11:09 am
same thing, you try to access the function test, which is utter nonsense.
You need to do it through the result of the function.
Title: Re: Why with allows assignment
Post by: 440bx on January 04, 2025, 12:12:02 am
With creates a new temporary object for the result of the function, to be referenced within the with scope.
A temporary will be created whether or not "with" is used simply because the function result must be usable whether or not a "with" is present.

The presence of the "with" has nothing to do with the creation of a temporary.  The fact that FPC seems to be creating it while processing the "with" is irrelevant, it would create it anyway had there been no "with".

If you need to see this, simply assign a variable a field of a function that returns a record.  you'll see a temporary whether or not a "with" is present, i.e, somevar := somefunction.recordfield;



Title: Re: Why with allows assignment
Post by: 440bx on January 04, 2025, 12:24:45 am
@LV,

How did you get it to emit an error ?   I am using v3.2.2 and it compiles.

Thanks.
Title: Re: Why with allows assignment
Post by: Warfley on January 04, 2025, 12:40:24 am
With creates a new temporary object for the result of the function, to be referenced within the with scope.
A temporary will be created whether or not "with" is used simply because the function result must be usable whether or not a "with" is present.
No that's simply wrong, you do not need to create a temp node. The FPC node tree is implemented in a way you cann use the call node directly. Infact if you go to line 640 (https://gitlab.com/freepascal.org/fpc/source/-/blob/main/compiler/pstatmnt.pas#L640) and skip the if (just replace the long condition with a simple true) no temp object will be created in the node tree, and with behaves exactly as you intend it to.

The whole block with the temp node is specifically created to handle with statements to not be simple macros. If with was implemented like you want it to be, the whole _with_statement function would be 100 lines shorter

PS: under certain circumstances will the call node create it's own temporary, e.g. when the result is a managed type (e.g. if the record contains an ansistring). In this case the with statement will create an additional temporary and copy the data from one temporary to the other, which is actually quite inefficient.
So yes sometimes a new temporary is always created for the function call, but if that is the case, with will create another one on top
Title: Re: Why with allows assignment
Post by: LV on January 04, 2025, 12:42:10 am
@440bx. I set a breakpoint, ran the program, then pressed F8, F8, and hovered over V.
Lazarus 3.4 (rev lazarus_3_4) FPC 3.2.2 x86_64-win64-win32/win64.
best regards.
Title: Re: Why with allows assignment
Post by: 440bx on January 04, 2025, 01:21:18 am
No that's simply wrong,
Really ??? where do you think the result of a function that returns a type that does not fit in a register goes ???  in a sardine can ? a ziploc bag ? a mailing envelop ???

or might it just be a metaphysical "ethereal' entity ?   of course, it cannot possibly be go into a temporary area... (unless there is a "with")...  unbelievable!!!!



@LV,

I mistakenly thought the yellow popup indicated a compile error.  I realize now, it is a debugger message.
Title: Re: Why with allows assignment
Post by: Warfley on January 04, 2025, 01:27:19 am
Really ??? where do you think the result of a function that returns a type that does not fit in a register goes ???  in a sardine can ? a ziploc bag ? a mailing envelop ???

or might it just be a metaphysical "ethereal' entity ?   of course, it cannot possibly be go into a temporary area... (unless there is a "with")...  unbelievable!!!!

I've been talking about the node tree, the intermediate representation used by the FPC, not the assembly to which it is converted. And the FPC already introduces a new temporary object for the with statement on the node tree, which then can either be a memory location or a register depending on the backend, data type and optimization. But also are temp nodes not the only way the Compiler can allocate memory. They are just a tool to manage internal temporary objects that are needed for some structures such as with statements, type conversions or others. If you don't know about the FPC internals don't lecture me on what the FPC does for which purpose
Title: Re: Why with allows assignment
Post by: 440bx on January 04, 2025, 01:47:42 am
I know very little about FPC internals but, that is neither here nor there.  The subject is what the "with" statement is supposed to do and one of the thing it neither does nor is supposed to do is to create temporary variables _even_ if that's what an _incorrectly_ implemented compiler does.

The presence or absence of a "with" statement should NOT affect the result. 

if it does then it's a bug in the compiler.  It's that simple.    Showing me (or anyone else) buggy code does NOT change that.

BTW, you haven't explained yet how it can be correct to assign a value to a function result outside of the function. 

and just for fun, consider this code:
Code: Pascal  [Select][+][-]
  1.   with Test, Test, Test, Test, Test, Test, Test do
  2.     V := 1; //No Error
  3.  
Is the compiler supposed to create 7 temporary variables ? (remember, according to you, the temporary is created because of the "with" not the fact that the function returns a structured type.)  If that were the case, which one of the temporaries gets assigned the value ??? 

BTW, you could create Test, Test1, Test2, Test3 and so forth and have a "with" statement "with" statement with several _dozens_ of these functions randomly mixed.   How many temporaries would the "with" create ?  which one(s) would get assigned a value ? and why ?

BOTTOM LINE: in the example given by the OP, FPC is not processing the "with" statement correctly.  It is a BUG because among other reasons it is allowing a value to be assigned to a function outside of the function definition.  That alone proves it is a bug, not to mention that (yet again!) the presence or absence of a "with" should make no difference in the result.

LASTLY: I am done explaining this.  Enough is enough!
Title: Re: Why with allows assignment
Post by: flowCRANE on January 04, 2025, 03:03:00 am
This thread is funny. The temporary object is allocated, which is clearly indicated in the compiler sources, many of us have already used it in our programs, and yet I see denial and claim that it is a bug. What you are writing about is not a bug, but "bs".

I've used a with block to create an instance of a class that would do a few things and then be destroyed. Like this:

Code: Pascal  [Select][+][-]
  1. with TSomeClass.Create(nil) do
  2. try
  3.   DoThis();
  4.   DoThat();
  5. finally
  6.   Free();
  7. end;

This is convenient because there is no need to declare an additional local variable since the compiler allocates it automatically. So I suggest stop talking nonsense and don't call something a bug that is not only implemented on purpose, but also useful in practice.

Peace.
Title: Re: Why with allows assignment
Post by: simone on January 04, 2025, 10:53:13 am
This thread is very interesting and clarifies some points for me. I have always interpreted the meaning of the with statement as 440bx does. I think in principle, he is right.

Given my interpretation, I have always wondered why this code (stylistically very questionable) works:

Code: Pascal  [Select][+][-]
  1. with TSomeClass.Create do
  2.   begin
  3.     Mehod1OfTSomeClass;
  4.     Mehod2OfTSomeClass;
  5.     Free;
  6.   end;

I was also amazed to see these results:

Code: Pascal  [Select][+][-]
  1. program test;
  2. uses classes;
  3. begin
  4.   with TObject.Create do
  5.      writeln(GetHashCode);
  6.   with TObject.Create do
  7.      writeln(GetHashCode);
  8.      
  9.   writeln;
  10.  
  11.   with TObject.Create do
  12.     begin
  13.       writeln(GetHashCode);
  14.       writeln(GetHashCode);
  15.     end;
  16.  
  17.   readln;
  18.  end.
  19.  

Output:
22443709157664
22443709157696

22443709157728
22443709157728


The implicit creation of a temporary object, clarified by Warfley, justifies this behavior. I think this is implementation dependent and not correct according to the original language specification. It is not officially documented anywhere. However, like other long-standing deviations, the problem is not solvable, because it would break backwards compatibility.
Title: Re: Why with allows assignment
Post by: Warfley on January 04, 2025, 12:35:19 pm
The implicit creation of a temporary object, clarified by Warfley, justifies this behavior. I think this is implementation dependent and not correct according to the original language specification. It is not officially documented anywhere. However, like other long-standing deviations, the problem is not solvable, because it would break backwards compatibility.

What original language specification? Originally the with statement was only allowed over variables and constants not over return values of functions. But also, original Pascal didn't have units, bit operations and many other stuff.
We are programming in Free Pascal, which is its own version of Pascal with its own Syntax and semantics. If you want to program in ISO or ExtPas, you can do that, you can even do that to some degree with the FPC (but it's not fully compatible), but then you are programming in a different programming language than the rest of us
Title: Re: Why with allows assignment
Post by: VisualLab on January 04, 2025, 12:53:30 pm
This thread is funny. The temporary object is allocated, which is clearly indicated in the compiler sources, many of us have already used it in our programs, and yet I see denial and claim that it is a bug. What you are writing about is not a bug, but "bs".

I don't see anything funny about it. The argument is between: readability + clarity of the language and a quirk present in the compiler.

I've used a with block to create an instance of a class that would do a few things and then be destroyed. Like this:

Code: Pascal  [Select][+][-]
  1. with TSomeClass.Create(nil) do
  2. try
  3.   DoThis();
  4.   DoThat();
  5. finally
  6.   Free();
  7. end;

This is convenient because there is no need to declare an additional local variable since the compiler allocates it automatically. So I suggest stop talking nonsense and don't call something a bug that is not only implemented on purpose, but also useful in practice.

I understand that for some people it is probably convenient. But at the same time it is quite unreadable and weird. Relying on oddities is not a good idea (see: C). The more such "functionalities" in a language, the more confusing and inconsistent it is. The variable will be created anyway, so what's the problem with declaring it explicitly?
Title: Re: Why with allows assignment
Post by: Warfley on January 04, 2025, 01:02:36 pm
I understand that for some people it is probably convenient. But at the same time it is quite unreadable and weird. Relying on oddities is not a good idea (see: C). The more such "functionalities" in a language, the more confusing and inconsistent it is. The variable will be created anyway, so what's the problem with declaring it explicitly?

That's just your personal opinion, if you read with as a macro that's on you, for me it always read like: "with this thing do the following" and not "in the following prepend all the terms with this macro" IMHO if it had the second semantic it should use the keyword prepend not with. Also with allows to read the code from top to bottom, first execute the with clause, then do the following using the result of the with clause. If it was a macro it would be for each line you must look what happens in the with clause to understand how it's working.

Lastly and more importantly, Pascal specifically does not have powerful macros like C does, so it would be extremely untypical to introduce them through with

Also your hint at C, how do I have to understand that? C is, compared to Pascal, standardized, you do never need to rely on some implementation quirks of C because first comes the standard then the implementations follow that standard (I mean I know how to understand this, you have shown time and time again in the past that you fundamentaly.misunderstand C and how standardized languages work, or how standardization committees and meetings are done, I just want to point it out here again)
Title: Re: Why with allows assignment
Post by: flowCRANE on January 04, 2025, 01:24:49 pm
and a quirk present in the compiler.

It's not a quirk, it's a useful feature. I've used this many times without even thinking about who thinks what about it, because it's useful and was intentionally implemented to expand the capabilities of this block. I wouldn't be surprised at the existence of a protest if it was the UB, but since it's a defined feature, stop whining already.

Quote
I understand that for some people it is probably convenient. But at the same time it is quite unreadable and weird.

The with do block is by its nature an abomination that destroys syntactic readability (especially when more than one parameter is involved), which is why I never use it to shorten syntax. The only reason I ever use it is precisely its ability to automatically allocate a temporary object on which I execute several methods or copy data from to other variables. So what bothers you is also the only sensible advantage of the with do block.

But since I gave up using this block many years ago, because it only obfuscates the code, I don't care what you do with it. The only thing that bothers me about this thread is the stupid denial that it is a feature and calling it a quirk or even a bug, which is ridiculous because some of you are gaslighting yourselves. If you really want to remove the automatic allocation of temporary objects from the compiler, then go ahead. Although backward compatibility will be broken, so it's better to think about it (it's a bit too late for this type of discussion).

Quote
The variable will be created anyway, so what's the problem with declaring it explicitly?

Why use this block at all if it does nothing except make the code harder to analyze? Screw it.
Title: Re: Why with allows assignment
Post by: simone on January 04, 2025, 01:47:13 pm
The implicit creation of a temporary object, clarified by Warfley, justifies this behavior. I think this is implementation dependent and not correct according to the original language specification. It is not officially documented anywhere. However, like other long-standing deviations, the problem is not solvable, because it would break backwards compatibility.

What original language specification? Originally the with statement was only allowed over variables and constants not over return values of functions. But also, original Pascal didn't have units, bit operations and many other stuff.
We are programming in Free Pascal, which is its own version of Pascal with its own Syntax and semantics. If you want to program in ISO or ExtPas, you can do that, you can even do that to some degree with the FPC (but it's not fully compatible), but then you are programming in a different programming language than the rest of us

I don't want to use some standard that almost no one in the real world knows. I would like what you describe to be documented. For the rest, I confirm what I think.
Title: Re: Why with allows assignment
Post by: LV on January 04, 2025, 01:57:05 pm
Yes. If you follow the documented features of Free Pascal, there should be no issues.
"The with statement serves to access the elements of a record or object or class, without having to specify the element’s name each time."
https://www.freepascal.org/docs-html/ref/refsu62.html
It seems like the discussion is about undocumented capabilities.
Title: Re: Why with allows assignment
Post by: 440bx on January 04, 2025, 02:31:58 pm
"The with statement serves to access the elements of a record or object or class, without having to specify the element’s name each time."
https://www.freepascal.org/docs-html/ref/refsu62.html
Nice to know the proper behavior is documented.

It seems like the discussion is about undocumented capabilities.
Not just undocumented but atrociously wrong.  When did it become acceptable to assign a value to a function outside the function ?  must have been after "writable constants" became a thing in Pascal (I wonder why no standard has been updated to include this "great feature"... totally genial... kind of like creating temporaries as a result of using a "with" statement... a couple of other such features come to mind... "progress" never stops.)

These are the kinds of things that make it embarrassing to promote Pascal.  That is extremely unfortunate.
Title: Re: Why with allows assignment
Post by: Warfley on January 04, 2025, 02:56:44 pm
I wonder why no standard has been updated to include this "great feature"...
Probably because the last version of the extended Pascal standard is nearly 35 years old. The last update was closer to the invention of Pascal than it is to today. Probably half of all people programming in Pascal today are younger than that standard. It's ancient and the reason it's not updates anymore is because no one uses it.
Title: Re: Why with allows assignment
Post by: simone on January 04, 2025, 03:22:39 pm
As already said, I completely agree with 440bx. I don't care if I'm in the minority.

I sometimes don't understand the philosophy behind fpc's choices. New features, like inline variables (which I don't particularly like either), are rejected, due to inconsistency with the fundamental principles of Pascal, but are tolerated atrocities like this.

Moreover, I don't find it normal that to understand the behavior of an elementary and ancient construct, like "with", one has to examine the compiler internals.
Title: Re: Why with allows assignment
Post by: BrunoK on January 04, 2025, 04:33:54 pm
Expand the following project.

What is it you don't understand with with ?

Rewrite it without if, is it clearer ?

+ there are quite a few natural optimizations done when correctly using with.
Title: Re: Why with allows assignment
Post by: Okoba on January 04, 2025, 04:48:56 pm
Why the first test raise no error like the second one?

Because for with it's simply not implemented yet. After all from a language point of view both are valid. However current Delphi versions forbid both due to wrong expectations of the users and FPC up to now only errors out on the function result, because it's something that the compiler needs to actively check for.

And on the same topic, is there a way in Pascal that I can return a record by the reference so any change to the result, changes the source?

Use a managed record.

As for the WITH control statement, I've ported D code over that uses that WITH with returning items from a function that seem to work just fine in D and also FPC. Basic facts about WITH is it creates a local branch of identifiers. In this case a RECORD where that record maybe referenced several times read or written while inside the WITH.

*sigh* Current Delphi versions forbid this code as well, because it does not do what you expect it to do.

@PascalDragon  can you give me a sample of how it can be done with managed records?
Title: Re: Why with allows assignment
Post by: VisualLab on January 04, 2025, 05:09:42 pm
I understand that for some people it is probably convenient. But at the same time it is quite unreadable and weird. Relying on oddities is not a good idea (see: C). The more such "functionalities" in a language, the more confusing and inconsistent it is. The variable will be created anyway, so what's the problem with declaring it explicitly?

That's just your personal opinion, if you read with as a macro that's on you, for me it always read like: "with this thing do the following" and not "in the following prepend all the terms with this macro" IMHO if it had the second semantic it should use the keyword prepend not with. Also with allows to read the code from top to bottom, first execute the with clause, then do the following using the result of the with clause. If it was a macro it would be for each line you must look what happens in the with clause to understand how it's working.

I have never written anywhere:

Lastly and more importantly, Pascal specifically does not have powerful macros like C does, so it would be extremely untypical to introduce them through with

And it's a good thing that it is. Macros are an invention from the early days of computer science. As "good" as monkey patching. Ad hoc, home-made, and makeshift solutions cause stagnation and are not a good solution.

Also your hint at C, how do I have to understand that? C is, compared to Pascal, standardized, you do never need to rely on some implementation quirks of C because first comes the standard then the implementations follow that standard (I mean I know how to understand this, you have shown time and time again in the past that you fundamentaly.misunderstand C and how standardized languages work, or how standardization committees and meetings are done, I just want to point it out here again)

1. I understand your point of view, but I don't share it entirely. We all know that C is a mixture of good, weird and stupid ideas. And it won't get any better. In my statement, I meant its unreadability and some weird rules.

2. I don't have a religious approach to standards. They are useful, as long as they are well designed. Perhaps GCC or MSVC compilers strictly adhere to the standard. It may be different with others. For example, Microchip itself states in its documentation that you should carefully check the documentation of their C compilers, because there are some differences from the standard. And I'm not surprised at all, because their compilers support microcontrollers, not large computers. So there may be some differences in the case of other C compilers as well.

3. I may not be a C expert, but I can really live without it :) When I have doubts, I check the details in the documentation.
Title: Re: Why with allows assignment
Post by: egsuh on January 05, 2025, 03:00:39 am
I don't know why this issue is a big issue. Personally I like "with" very much.

- Scoping and readability

   Sometimes I was confused of "with" scope, and made some mistakes. But confusion is only when same identifiers are used within and outside "with" scope. Now I understand exactly how it works and love even using nested "with"s. Backward checking is all that is necessary.

I might have different attitude because I do not write many programs but use one program frequently. So once functions/procedures are written correctly, I seldom have to look into it again line by line. Instead, I only have to see it again to find out my "logic" or alrorithm of the routine. In this case, "with" improves readability by large, as I do not have to worry about any inconsistencies within there. Compare following example.

             V1 := SQLQuery1.FieldByName('F1').AsString;
             V2 := SQLQuery1.FieldByName('F2').AsString;
             V3 := SQLQuery1.FieldByName('F3').AsString;
             V4 := SQLQuery1.FieldByName('F4').AsString;
             V5 := SQLQuery2.FieldByName('F5').AsString;

            vs.

            with SQLQuery1 do begin
                   V1 := FieldByName('F1').AsString;
                   V2 := FieldByName('F2').AsString;
                   V3 := FieldByName('F3').AsString;
                   V4 := FieldByName('F4').AsString;
            end;
            V5 := SQLQuery2.FieldByName('F5').AsString;

I can promptly see that V5 comes from different dataset from other variables.


- Regarding Temporary Storage

    Function results are maintained temporarily. But this is not true if a memory space is allocated from heap, not local stack. Object Orinted Programming approach was not easy at first for me. It didn't have much meaning than simply collecting codes in one place. But now I came to a conclusion that it's not that superficial. Instances of classes, i.e. objects, exist at some place in a world created by me - my program space, until I destoy it. Function result is temporary but not the object (or any record or integer to which heap memory has been allocated). So in the case of "with Test do ... " we are not accessing function result but the persistent object created by the function. The object created with "with" is temporary but not always if the object has reference to its container, etc. For example,

Code: Pascal  [Select][+][-]
  1. type
  2.    TMyObject = class
  3.          ...
  4.          FPGMap: TFPGMapObject<integer, TMyObject>;
  5.          var1, var2: string;
  6.          ..
  7.     end;
  8.  
  9. var
  10.      AFPTMapObject : TFPGMapObject<integer, TMyObject>;
  11.  
  12. implementation
  13.  
  14. constructor  TMyObject.Create(AnIndex: integer; FMap: TFPGMapObject);
  15. begin
  16.       inherited;
  17.       FPGMap := FMap;
  18.       FPGMap.Add(AnIndex, Self);  
  19. end;
  20.  
  21. begin
  22.      with DataSet do begin
  23.           First;
  24.           while not eof do begin
  25.                with TMyObject.Create(FieldByName('index').AsInteger, AFPGMapObject)
  26.                do begin            
  27.                   var1 := FieldByName('Title').AsString;
  28.                   var2 := FieldByName('Description').AsString;
  29.                end;
  30.                Next;
  31.           end ; // while
  32.       end; // with DataSet
  33.          .........
  34.  
  35.       AFPGMapObject.Free;
  36. end.
  37.  


I love this kind of codes, as this reduces the number of key typings, and is much easier to understand what this part of codes try to do.


- Overall trends

  Recently popular programming languages seem to allow more features. The only other language I know than Pascal is javascript, but anyway its systax like

                if (a=2) 

is valid and returns always true (which caused me many mistakes. I should have written "a==2").   Pascal has tradition but I believe we don't have to resist new trends too strongly.  Further, sometimes I wish pascal had feature like :

               with (aclass := TMyClass.Create) do begin
                       .......

               end;
               ......
               aclass.free; 


Of course I can write

                aclass := TMyClass.Create;
                with aclass do begin
                             .....
                end;
                aclass.free;


But this requires reading whole routine to find where aclass is created.


Conclusion: I love "with" because it lessens my cognitive efforts, except at the first writing of codes.
Title: Re: Why with allows assignment
Post by: Warfley on January 05, 2025, 04:01:13 pm
With as it was originally is one of these weird features that just shortens typing without enabling any new way of doing things. Simultaneously it introduced shadowing. This was fine in the context where it was conceived, in procedural programs used in functions with a few dozens local and global variables, to access record variables with a single to low double digit number of fields. There shadowing isn't a problem.

But the programming environment has changed. If you now write a method for a form event handler, you are in the context of the TForm class, having hundreds of symbols with very generic names (e.g. name, width, height, top, etc.) and with is used to access other classes which also have hundreds of symbols.
For example, if you use with to access a canvas in the form paint function, a canvas has a width, height, font, etc. All shadowing the properties of the form.

So while with wasn't that much of an issue in the code bases for which it was developed, today it does more harm than good. So in the very most situations I would recommend to just leave with as a relic of the past and not to use it.

That said, with introduces an interesting new concept to Pascal, scope. One example for this is the usage with class instances, as shown already here
Code: Pascal  [Select][+][-]
  1. with TMyClass.Create do
  2. try
  3.   ...
  4. finally
  5.   Free;
  6. end;
This allows something more than just avoiding to write out some code, it specifically limits usable scope of the created object, avoiding things like use after free errors and syntactically binding the try finally to the creation in the with block.
This is an actual benefit to the language.

The only downside is that with is not scoped with respect to lifetime, the temporary object created for with has the same lifetime as the function which is why it's not really usable for managed types. I tried to change it in this MR, but it's not yet finished: https://gitlab.com/freepascal.org/fpc/source/-/merge_requests/860

So IMHO the creation of a scoped temporary object is pretty much the only reason why with is actually useful. Since code completion is a thing, there is no reason to have to avoid writing out a few more characters, so using with just to shorten code is a bad practice
Title: Re: Why with allows assignment
Post by: Bart on January 05, 2025, 07:00:35 pm
There is/was a proposal for something like:
Code: [Select]
  with VarWithVeryLongNameSoLongThatItAlmostFeelsLikeYoureUsingC as myvar do
  begin
    myvar.somefield := somevalue;
   ...
  end;

That could make sense to me (in order to not having to type VarWithVeryLongNameSoLongThatItAlmostFeelsLikeYoureUsingC over and over again, and at the same time not having scope issues (not knowing wether e.g Width points to VarWithVeryLongNameSoLongThatItAlmostFeelsLikeYoureUsingC or to e.g. the form it is in.

Bart
Title: Re: Why with allows assignment
Post by: Warfley on January 05, 2025, 07:11:35 pm
There is/was a proposal for something like:
Code: [Select]
  with VarWithVeryLongNameSoLongThatItAlmostFeelsLikeYoureUsingC as myvar do
  begin
    myvar.somefield := somevalue;
   ...
  end;

That could make sense to me (in order to not having to type VarWithVeryLongNameSoLongThatItAlmostFeelsLikeYoureUsingC over and over again, and at the same time not having scope issues (not knowing wether e.g Width points to VarWithVeryLongNameSoLongThatItAlmostFeelsLikeYoureUsingC or to e.g. the form it is in.

Bart
The as Syntax is not working, because as expressions are already allowed in with statements. But yeah if you look at the MR I've linked above it can do:
Code: Pascal  [Select][+][-]
  1. with const fl = openfile(filename, fmWrite) do
  2.   fl.Write(42);
  3.  
Specifically ensuring that the finalizer of fl is called after the with block (and in this example the file handle would be freed)
Title: Re: Why with allows assignment
Post by: 440bx on January 05, 2025, 07:22:16 pm
With as it was originally is one of these weird features that just shortens typing without enabling any new way of doing things.
"with" is just a way to establish a scope.   Nothing "weird" about it. 

Simultaneously it introduced shadowing.
Shadowing ???  what are you talking about ?  and where did you get that stuff from ?  references please!.

But the programming environment has changed. If you now write a method for a form event handler, you are in the context of the TForm class, having hundreds of symbols with very generic names (e.g. name, width, height, top, etc.) and with is used to access other classes which also have hundreds of symbols.
and magically, somehow, abracadabra... now it is possible to assign a value to a function result _outside_ of the function.  The computer science field needed that about as much as writable constants.  Welcome to Alice in Wondercode.

So IMHO the creation of a scoped temporary object is pretty much the only reason why with is actually useful. Since code completion is a thing, there is no reason to have to avoid writing out a few more characters, so using with just to shorten code is a bad practice
There is absolutely nothing wrong about using "with" but, there are obviously programmers who are totally clueless about how to use it correctly but, why would anyone care about that ? (just in case, that's rethorical.)
Title: Re: Why with allows assignment
Post by: Warfley on January 05, 2025, 08:23:18 pm
Shadowing ???  what are you talking about ?  and where did you get that stuff from ?  references please!.
Code: Pascal  [Select][+][-]
  1. type
  2.   TTest = record
  3.     x: Integer;
  4.   end;
  5.  
  6. procedure Foo(x: Integer);
  7. var
  8.   t: TTest;
  9. begin
  10.   with t do
  11.     x := 42;  // t.x shadows param x
  12. end;

Shadowing means overriding some identifier with another one. Sometimes shadowing can be disambiguated, e.g. when one unit shadows definitions of another unit you can write unit1.x to disambiguate.
Free Pascal does not allow shadowing without disambiguation, e.g. the following is not allowed.for.exactly that reason:
Code: Pascal  [Select][+][-]
  1.  procedure foo(x: Integer);
  2. var
  3.   x: Double;
  4. begin
  5. end;
With statements allow to shadow symbols without disambiguation (as in the example above), which is why it's bad. It's so bad that languages that used to have with, like JavaScript, deprecated it, because it's a syntactic concept that's born out of pure laziness, gives no semantic benefits but due to shadowing can introduce errors.

Quote
and magically, somehow, abracadabra... now it is possible to assign a value to a function result _outside_ of the function.  The computer science field needed that about as much as writable constants.  Welcome to Alice in Wondercode.
Imagine a world in which you can't change any result of a function, then the following would also not work:
Code: Pascal  [Select][+][-]
  1. t := MyFunc;
  2. t.x := 42;
In this example t is the result of a function but is changed outside the function. Oh no hell breaks lose, how can this be acceptable!!!!

There is literally no difference between:
Code: Pascal  [Select][+][-]
  1. t := MyFunc;
  2. with t do
  3.   x := 42;
  4. // And
  5. with MyFunc do
  6.   x := 42;
Just that in the first example the object where the result is written I to has an explicit name, while in the second one it's the result of an anonymous expression.

Just because it doesn't have a name doesn't mean it doesn't exist.

But I mean you do not know what shadowing is, which is absolutely basic knowledge about programming language design, so I guess there is no real reason to continue to argue
Title: Re: Why with allows assignment
Post by: tetrastes on January 05, 2025, 09:05:32 pm
Following this logic consistently, there must be literally no difference between:

Code: Pascal  [Select][+][-]
  1. t := MyFunc;
  2. t.x := 42;
  3. // And
  4. MyFunc.x := 42;

but this is not so. And this inconsistency is the problem with all that stuff.
As they say, either take off the cross or put on your underpants.  :D
Title: Re: Why with allows assignment
Post by: 440bx on January 05, 2025, 09:08:24 pm
Code: Pascal  [Select][+][-]
  1. type
  2.   TTest = record
  3.     x: Integer;
  4.   end;
  5.  
  6. procedure Foo(x: Integer);
  7. var
  8.   t: TTest;
  9. begin
  10.   with t do
  11.     x := 42;  // t.x shadows param x
  12. end;

Shadowing means overriding some identifier with another one. Sometimes shadowing can be disambiguated, e.g. when one unit shadows definitions of another unit you can write unit1.x to disambiguate.
First, what you are calling "shadowing" is normally referred to as _masking_. 

Free Pascal does not allow shadowing without disambiguation, e.g. the following is not allowed.for.exactly that reason:
Code: Pascal  [Select][+][-]
  1.  procedure foo(x: Integer);
  2. var
  3.   x: Double;
  4. begin
  5. end;
With statements allow to shadow symbols without disambiguation (as in the example above), which is why it's bad.
You really need to learn basic compiler construction theory.  In that code there is no masking (or shadowing as you call it), what there is, is a _duplicate_ identifier. No correctly implemented compiler would allow that code because the parameter "x" and the local variable "x" are in the _same_ scope, therefore they cannot use the same name. 

It's so bad that languages that used to have with, like JavaScript, deprecated it, because it's a syntactic concept that's born out of pure laziness, gives no semantic benefits but due to shadowing can introduce errors.
Yes, allowing duplicate identifiers is so bad that no correctly implemented compiler allows it.  Duplicate identifiers have been "deprecated" since the birth of compilers and interpreters.


Imagine a world in which you can't change any result of a function, then the following would also not work:
Code: Pascal  [Select][+][-]
  1. t := MyFunc;
  2. t.x := 42;
In this example t is the result of a function but is changed outside the function. Oh no hell breaks lose, how can this be acceptable!!!!
Nice try but "t" is most definitely NOT the result of a function.  "t" is where the result of the function is copied to.  That's VERY different.  The result of the function is NOT being changed, the location where the result of the function has been COPIED to is being changed.

OTH, FPC is allowinjg this atrocity (which you are trying to hide with the above code which is very different):
Code: Pascal  [Select][+][-]
  1. program Project1;
  2.  
  3. type
  4.   TTest = record
  5.     V: Integer;
  6.   end;
  7.  
  8. var
  9.   X: TTest;
  10.  
  11.   function Test: TTest;
  12.   begin
  13.     Result := X;
  14.   end;
  15.  
  16. var
  17.   T: TTest;
  18. begin
  19.   with Test do
  20.     V := 1; //No Error
  21.   Test.V := 1; //Error: Argument cannot be assigned to
  22. end.                
Line 20 is _changing_ the result of the function because line 20 is semantically identical as line 21 but FPC _incorrectly_ considers them different which is an _atrocity_.

But I mean you do not know what shadowing is, which is absolutely basic knowledge about programming language design, so I guess there is no real reason to continue to argue
Let's engage in "basic knowlege"... it's not called "shadowing" (at least not by those who know a little compiler theory), it's called masking.  if you need confirmation of that, write a C program that masks one variable or parameter and you'll get a hint or warning telling you that some variable is _masking_ another one. 

BUT, please, do prove me wrong...  find a mainstream compiler that refers to a masked variable as a shadowed variable.

I _strongly_ suggest you learn basic compiler construction theory.  I highly recommend the book "Per Brinch Hansen On Pascal Compilers" (unfortunately, that book does not cover the "with" statement), another really good educational compiler is Per Brinch Hansen's SuperPascal (source available online) and I also strongly recommend you read "The P4 Compiler and Interpreter
by Steven Pemberton, Amsterdam, and Martin Daniels", text is available onlne at:
http://pascal.hansotten.com/uploads/pemberton/homepages.cwi.nl/_steven/pascal/book/pascalimplementation.html

After you've learned basic compiler theory, we can resume the discussion about the "with" statement. 

Have fun reading and please learn.

Title: Re: Why with allows assignment
Post by: Warfley on January 05, 2025, 09:40:46 pm
First, what you are calling "shadowing" is normally referred to as _masking_.
No it's not: https://en.m.wikipedia.org/wiki/Variable_shadowing

Quote
You really need to learn basic compiler construction theory.  In that code there is no masking (or shadowing as you call it), what there is, is a _duplicate_ identifier. No correctly implemented compiler would allow that code because the parameter "x" and the local variable "x" are in the _same_ scope, therefore they cannot use the same name.
No it's not, because I know about basic Compiler construction and know that parameters are in their own symtable and there by spawn their own scope. In the FPC sources it's called the paramst.

But if you want another example:
Code: Pascal  [Select][+][-]
  1. type
  2.   TTest = class
  3.     I: Integer;
  4.     procedure foo(I: Integer);
  5.   end;
Also doesn't work (at least in mode ObjFpc) because it forbids shadowing

Quote
Nice try but "t" is most definitely NOT the result of a function.  "t" is where the result of the function is copied to.  That's VERY different.  The result of the function is NOT being changed, the location where the result of the function has been COPIED to is being changed.
First, technically speaking, anything to big for a register will be passed by reference, so the function literally constructs the result in place. But even putting that technicality aside: congratulations you exactly got the point I was making all along. When you use with, you don't change the result of the function, you change a temporary object where the result of the function is assigned to

So thank you for agreeing with me. So what's your problem?
Title: Re: Why with allows assignment
Post by: 440bx on January 05, 2025, 10:38:01 pm
No it's not: https://en.m.wikipedia.org/wiki/Variable_shadowing
Interesting... second time (first was you) I see it referred to as shadowing.  Find a compiler that emits a message stating that some variable is "shadowing" another one... good luck but, you can find a compiler that warns about a variable masking another one (that's what every C compiler I've used emitted as a message.)

All that said, I admit you found a respectable place that refers to it as shadowing.  Now find a compiler that does (just in case, the word shadow or shadowing must appear in the compiler message or documentation.)

No it's not, because I know about basic Compiler construction and know that parameters are in their own symtable and there by spawn their own scope. In the FPC sources it's called the paramst.
I couldn't care less how FPC implements its scope resolution mechanism but that comment proves that you know less about compiler construction than the Pope knows about quantum mechanics.  Locals and parameters are in the same scope which is why locals cannot have the same name as parameters because that would cause a duplicate identifier.

But, if you want to argue that, try compiling this:
Code: Pascal  [Select][+][-]
  1. procedure DuplicateIdentifier(Parameter : integer);
  2. var
  3.   Parameter : boolean;
  4. begin
  5. end;
  6.  
Even FPC will tell you there is a duplicate identifier thereby conclusively proving the parameter and the local are in the same scope.

As I suggested in the previous post, you need to read a few books about compiler construction.

So thank you for agreeing with me. So what's your problem?
I couldn't possibly agree with you.  I don't have a problem but, I suggest, again, you read the books I previously mentioned.

Title: Re: Why with allows assignment
Post by: TRon on January 05, 2025, 10:49:36 pm
Not saying I have any knowledge at all but for sure when reading this kind of arguments one could become confused pretty fast.

imho you are both saying exactly the same only using other words and viewing it from another standpoint/angle (each with their own believes). More problematic is clouding the topic with objects which wasn't part of the original question to begin with.

At one time in history it worked both ways (assigning and accessing) and now it doesn't anymore because of the copy (or so called shadow) thus end of the story: don't use it unless knowing what you're doing. Don't use it in case you' don't like it and use it case you do like it  *period*.
Title: Re: Why with allows assignment
Post by: 440bx on January 05, 2025, 11:15:14 pm
imho you are both saying exactly the same only using other words and viewing it from another standpoint/angle (each with their own believes).
No, what he is saying is very different than what I'm saying and totally opposite of what a correctly implemented compiler would do.

More problematic is clouding the topic with objects which wasn't part of the original question to begin with.
You got that part right.

Creating a temporary as a result of a "with" statement is absolutely incorrect in all cases.  No exceptions.  Period. The _only_ purpose of a "with" is to establish a scope, nothing more and nothing less.

Some people may support Alice in Wonderland compilers, I'm not one of them.
Title: Re: Why with allows assignment
Post by: TRon on January 05, 2025, 11:38:19 pm
No, what he is saying is very different than what I'm saying and totally opposite of what a correctly implemented compiler would do.
What I see/read and believe that matters is the end result which is the same. Aside whether or not that is correct behaviour for a compiler.

Quote
Creating a temporary as a result of a "with" statement is absolutely incorrect in all cases.  No exceptions.  Period. The _only_ purpose of a "with" is to establish a scope, nothing more and nothing less.
If I recall correctly that was a decision made for FPC by being Delphi compatible (which thus changed that behaviour).

And fwiw I do agree with you that there is a problem with regards to scope... but that is for the fine gents to figure out once the anonymous functions gets implemented/used. Without properly addressing scopes anonymous functions can be buried into the landfill of unusability.
Title: Re: Why with allows assignment
Post by: 440bx on January 05, 2025, 11:56:47 pm
What I see/read and believe that matters is the end result which is the same. Aside whether or not that is correct behavior for a compiler.
But the end result isn't the same.  The result is that it is possible to assign a value to a function outside the function which is not just incorrect, it's appalling.  see the OP's code (first post), one statement is accepted while the other isn't in spite of the fact that the two are supposed to be semantically identical.

And that's not the only problem creating a temporary as a result of a "with" statement creates.  if the "with" states multiple scopes, are there supposed to be temporaries for each one ?... it's so wrong, it's beyond belief it is being done.  Is the "with" statement going to create temporaries in some cases and not in others ? ... where are those cases documented ?

The list of problems that incorrect behavior creates is long.

Another problem seems to be that there are people who are very willing to accept/support incorrect behavior because they find it convenient.  That's not a good thing but, admittedly that's a different problem though, it certainly doesn't help matters.


Title: Re: Why with allows assignment
Post by: LV on January 05, 2025, 11:59:14 pm
In comparison to this topic, quantum dynamics appears to be more comprehensible.   :o
Title: Re: Why with allows assignment
Post by: dbannon on January 06, 2025, 12:26:46 am
Thanks Bart, a useful contribution to this thread.

There is/was a proposal for something like:
Code: [Select]
  with VarWithVeryLongNameSoLongThatItAlmostFeelsLikeYoureUsingC as myvar do
  begin
    myvar.somefield := somevalue;
   ...
  end;

That would be good if it accepted a 'qualified' variable name, eg

Code: Pascal  [Select][+][-]
  1. if TKMemoParagraph(KMemo1.Blocks[ABlockNo]).Parastyle.LeftPadding = 0 then
  2.     TKMemoParagraph(KMemo1.Blocks[ABlockNo]).Parastyle.LeftPadding := 42;
verses :
Code: Pascal  [Select][+][-]
  1. with TKMemoParagraph(KMemo1.Blocks[ABlockNo]).Parastyle.LeftPadding as LeftPad do
  2. begin
  3.     if LeftPad = 0 then
  4.         LeftPad := 42;
  5.     X := Y;
  6.     ....
  7. end;

Saves a lot of error prone typing but more importantly, much, much easier to read. The use of "as LeftPad" leaves no doubt what object is being addressed, no one has to wonder the X or Y is also getting the "with treatment".
Davo

EDIT : fixed a typo that, apparently, Bruno could not identify as such. Sorry.
Title: Re: Why with allows assignment
Post by: 440bx on January 06, 2025, 12:41:07 am
Code: Pascal  [Select][+][-]
  1. if TKMemoParagraph(KMemo1.Blocks[ABlockNo]).Parastyle.LeftPadding = 0 then
  2.     TKMemoParagraph(KMemo1.Blocks[ABlockNo]).Parastyle.LeftPadding = 42;
verses :
Code: Pascal  [Select][+][-]
  1. with TKMemoParagraph(KMemo1.Blocks[ABlockNo]).Parastyle.LeftPadding as LeftPad do
  2. begin
  3.     if LeftPad = 0 then
  4.         LeftPad = 42;
  5.     X := Y;
  6.     ....
  7. end;

Saves a lot of error prone typing but more importantly, much, much easier to read. The use of "as LeftPad" leaves no doubt what object is being addressed, no one has to wonder the X or Y is also getting the "with treatment".
Davo
That's already available by defining a 'LeftPad' macro, no need to change the "with" statement to get that funcitonality.

Title: Re: Why with allows assignment
Post by: TRon on January 06, 2025, 12:51:31 am
But the end result isn't the same.
There you are a bit in the wrong my friend or at least not precise enough. The value is assigned to in the scope but not outside that scope.

Outside the scope the value of V is still the value of V as it was before entering the scope.

Ergo the end-result is the same in that the value is not actually stored in(side) the result-value (as returned by the function).

Quote
And that's not the only problem creating a temporary as a result of a "with" statement creates.  if the "with" states multiple scopes, are there supposed to be temporaries for each one ?... it's so wrong, it's beyond belief it is being done.  Is the "with" statement going to create temporaries in some cases and not in others ? ... where are those cases documented ?
Yes, that is indeed problematic (or at least can be especially when in the case of the formentioned anonymous functions which by default introduces a new scope when created).


Quote
Another problem seems to be that there are people who are very willing to accept/support incorrect behavior because they find it convenient.  That's not a good thing but, admittedly that's a different problem though, it certainly doesn't help matters.
The problem is more that the compiler should flag it it as incorrect for both cases in OP's example (but at the same time OP's code is trying to hide the manipulation just as the absolute trick that once allowed for modifying an iterator) . For sure it is confusing which can been seen by the people running into these kind of things (remember/note that the compiler flagged the assignment as illegal only recently (in the grant scheme of its existence that is)).
Title: Re: Why with allows assignment
Post by: VisualLab on January 06, 2025, 12:56:08 am
In comparison to this topic, quantum dynamics appears to be more comprehensible.   :o

It's a matter of agreement between people as to what will be implemented and why. Meanwhile, there are rather two variants here:
This causes the language to become not only less readable but also less unambiguous (and the compiler code swells more and more). It's not even a matter of pragmatism. Because taking care of readability, clarity and unambiguity is very much pragmatism. It's for such reasons that Java or C# have become bloated over time (because you can't lag behind when various "modern" features of languages ​​appear).

I understand the supporters of "overloading" the "with" keyword (i.e. what do they mean). But in my opinion these benefits are ephemeral. Is it impossible to live without it? Because if something can be easily solved in another way, then these additional "overloads" of keywords1) are not even syntactic sugar.

This proposal reminds me of the C-style assignment operators that were introduced in FPC. And which were not needed at all, because we have stopped using slow and unreliable teletypes (instead of keyboards and monitors) for a long time now, to "reducing the number of key typings".



1) I.e. "with". And there were also suggestions to "overload" the "as".
Title: Re: Why with allows assignment
Post by: 440bx on January 06, 2025, 01:05:54 am
There you are a bit in the wrong my friend or at least not precise enough. The value is assigned to in the scope but not outside that scope.
But, inside or outside the scope, a function result, outside the function, is _not_ an lvalue, therefore cannot be assigned to and, there should be no temporary and if there is a temporary representing the function result then it should behave exactly the same as the function result otherwise, the programmer should have copied the function result into a separate variable then, anything goes because it no longer represents the function's result.

Summary: a temporary should most definitely not exist as a result of the "with" but, if for the sake of argument, we accept the existence of a temporary to represent the function's result then it must behave exactly as the function's result which means, it cannot be changed because the temporary isn't in the scope of the function.

ETA: corrected a typo.
Title: Re: Why with allows assignment
Post by: TRon on January 06, 2025, 01:18:56 am
But, inside or outside the scope, a function result, outside the function, is _not_ an lvalue, therefore cannot be assigned to and, there should be no temporary and if there is a temporary representing the function result then it should behave exactly the same as the function result otherwise, the programmer should have copied the function result into a separate variable then, anything goes because it no longer represents the function's result.
Exactly right. We seem to agree on that (so was Warfley as far as I understood)

It is either this or that and not one time this and another time that. But as explained the OP's code obfuscate things probably to such an extend that the compiler is unable to pick up in that. Quite frankly I haven't bothered to see if it the compiler can be tricked in more obvious manners simply because the topic doesn't interest me as much.

Quote
Summary: a temporary should most definitely not exist as a result of the "with" but, if for the sake of argument, we accept the existence of a temporary to represent the function's result then it must behave exactly as the function's result which means, it cannot be changed because the temporary isn't in the scope of the function.
I am very tempted to agree to that but then, I am not a compiler developer or someone calling the shots (nor do I have any idea if the obfuscation of TS's code can be detected in an easy manner).

Note that it took over a decade to detect and fix something as simple as an absolute value pointing to itself (It is the first time me mentioning that for over 12 years).
Title: Re: Why with allows assignment
Post by: egsuh on January 06, 2025, 03:58:27 am
Quote
But the programming environment has changed. If you now write a method for a form event handler, you are in the context of the TForm class, having hundreds of symbols with very generic names (e.g. name, width, height, top, etc.) and with is used to access other classes which also have hundreds of symbols.
For example, if you use with to access a canvas in the form paint function, a canvas has a width, height, font, etc. All shadowing the properties of the form.

I prefer to use "with" just because of this. I do not think one way is right or wrong, nor one way is better than the other. Simply it's programmer's preference.  For example, when I create a subform within mainform,

           with TSubForm.Create(nil) do begin
                  if ShowModal then begin
                     MainForm.Caption := Edit1.text;   // this line
                  end;
                  Free;
           end;


Without "with",   this line should be  Caption := Subform.Edit1.Text;.    Which one is preferable? I think case by case.


Regarding inside/outside of function result...  I'm not a computer scientist, but I remember I overheard that function results exist only in registers, not in memory --- not sure.  No confusion if function result type is integer etc., but what happens if the function returns record type? If function result is object descendants created within the function then the object's storage is definitely not temporary. Only the address of the object is temporary.  Simply wondering. Curious. 
Title: Re: Why with allows assignment
Post by: LV on January 06, 2025, 09:07:49 am
It is either this or that and not one time this and another time that. But as explained the OP's code obfuscate things probably to such an extend that the compiler is unable to pick up in that.

Nice.

One of Wirth's main principles:

Make it as simple as possible, but not simpler. (A. Einstein)

https://people.inf.ethz.ch/wirth/Oberon/Oberon.Report.pdf
Title: Re: Why with allows assignment
Post by: PascalDragon on January 06, 2025, 12:52:10 pm
BUT, please, do prove me wrong...  find a mainstream compiler that refers to a masked variable as a shadowed variable.

To quote GCC's help (https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html):

Quote
-Wshadow

    Warn whenever a local variable or type declaration shadows another variable, parameter, type, class member (in C++), or instance variable (in Objective-C) or whenever a built-in function is shadowed. Note that in C++, the compiler warns if a local variable shadows an explicit typedef, but not if it shadows a struct/class/enum. If this warning is enabled, it includes also all instances of local shadowing. This means that -Wno-shadow=local and -Wno-shadow=compatible-local are ignored when -Wshadow is used. Same as -Wshadow=global.

I think we can agree that GCC is considered a "mainstream compiler"? ;)

But, if you want to argue that, try compiling this:
Code: Pascal  [Select][+][-]
  1. procedure DuplicateIdentifier(Parameter : integer);
  2. var
  3.   Parameter : boolean;
  4. begin
  5. end;
  6.  
Even FPC will tell you there is a duplicate identifier thereby conclusively proving the parameter and the local are in the same scope.

Not really your fault, but to be fair, it's a bad example, cause as far as the implementation in FPC is concerned the parameter and the locals are different symbol tables and the compiler actively needs to check that an identifier inserted into the local symbol table isn't conflicting with one in the parameter symbol table.

Creating a temporary as a result of a "with" statement is absolutely incorrect in all cases.  No exceptions.  Period. The _only_ purpose of a "with" is to establish a scope, nothing more and nothing less.

Some people may support Alice in Wonderland compilers, I'm not one of them.

Then please take your grievances up with the developers at Borland, because Delphi is where this behaviour originates and it is implemented in FPC to follow that behaviour:

Code: Pascal  [Select][+][-]
  1. program twith;
  2.  
  3. {$APPTYPE CONSOLE}
  4.  
  5. var
  6.   cnt: LongInt = 0;
  7.  
  8. type
  9.   TTest = record
  10.     I: LongInt;
  11.   end;
  12.  
  13. function Test: TTest;
  14. begin
  15.   Inc(cnt);
  16.   Result.I := cnt;
  17. end;
  18.  
  19. begin
  20.   with Test do begin
  21.     Writeln(I);
  22.     Writeln(I);
  23.   end;
  24. end.
  25.  

Output (with both Delphi and FPC):

Code: [Select]
1
1

This clearly shows that Test is called once which means that the expressions in the with-clause are only evaluated once and not for each time a member of the corresponding structured type is accessed. If your assumption would be correct then the output would be 1 2 instead.

Oh, and just for the sake of it, replacing the with-statement with the following:

Code: Pascal  [Select][+][-]
  1. with Test, Test do begin


Will result in this output:

Code: [Select]
2
2
Title: Re: Why with allows assignment
Post by: BrunoK on January 06, 2025, 01:26:50 pm
Code: Pascal  [Select][+][-]
  1. with TKMemoParagraph(KMemo1.Blocks[ABlockNo]).Parastyle.LeftPadding as LeftPad do
  2. begin
  3.     if LeftPad = 0 then
  4.         LeftPad := 42; // bk corrected syntax error
  5.     X := Y;
  6.     ....
  7. end;
That's already available by defining a 'LeftPad' macro, no need to change the "with" statement to get that funcitonality.
Out of curiosity what would be the syntax for the said ''LeftPad' macro' ?

Title: Re: Why with allows assignment
Post by: lainz on January 06, 2025, 01:57:02 pm
Code: Pascal  [Select][+][-]
  1. program Project1;
  2.  
  3. uses
  4.  Classes, SysUtils;
  5.  
  6. type
  7.  
  8.   TRecAB = record
  9.     A: integer;
  10.     B: integer;
  11.   end;
  12.  
  13.   { TMyObj }
  14.  
  15.   TMyObj = class
  16.   private
  17.     TRecAB: TRecAB;
  18.   public
  19.     property rec: TRecAB read TRecAB write TRecAB;
  20.   end;
  21.  
  22. var
  23.   Obj: TMyObj;
  24.  
  25. begin
  26.   Obj := TMyObj.Create;
  27.  
  28.   with Obj.rec do
  29.   begin
  30.     A := 10;
  31.     B := 20;
  32.   end;
  33.  
  34.   with Obj.rec do
  35.   begin
  36.     A := 15;
  37.   end;
  38.  
  39.   writeln(Obj.rec.A);
  40.   writeln(Obj.rec.B);
  41.   readln;
  42.  
  43. end.

A is 15 and B is 20 at the end. So it works fine for me...
Title: Re: Why with allows assignment
Post by: BrunoK on January 06, 2025, 01:58:07 pm
Sadly, it seems that the shown example is syntactically incorrect thus not adding anything constructive to the discussion.
Title: Re: Why with allows assignment
Post by: 440bx on January 06, 2025, 02:13:55 pm
To quote GCC's help (https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html):

Quote
-Wshadow

    Warn whenever a local variable or type declaration shadows another variable, parameter, type, class member (in C++), or instance variable (in Objective-C) or whenever a built-in function is shadowed. Note that in C++, the compiler warns if a local variable shadows an explicit typedef, but not if it shadows a struct/class/enum. If this warning is enabled, it includes also all instances of local shadowing. This means that -Wno-shadow=local and -Wno-shadow=compatible-local are ignored when -Wshadow is used. Same as -Wshadow=global.
Ok, I've been programming for over 40 years and this is the third time I came across the term "shadow" to refer to masking but, fair enough, to my surprise, variable shadowing is a thing.

I think we can agree that GCC is considered a "mainstream compiler"? ;)
Yes, we can agree to that.

Not really your fault, but to be fair, it's a bad example, cause as far as the implementation in FPC is concerned the parameter and the locals are different symbol tables and the compiler actively needs to check that an identifier inserted into the local symbol table isn't conflicting with one in the parameter symbol table.
How FPC deals with its symbol tables is completely irrelevant.  What's relevant is that the parameters and the locals are in the same scope.  Slice it however you like but THAT is a fact and, if you compile that, FPC correctly claims there is a duplicate identifier.  Therefore, regardless of how FPC fiddles with its symbols tables, it reaches the correct conclusion: duplicate identifier, which can only happen when two identifiers are in the same scope. 

Then please take your grievances up with the developers at Borland, because Delphi is where this behaviour originates and it is implemented in FPC to follow that behaviour:
You're right, I just tested it with Delphi 2 and it behaves as you stated.  Another "breakthrough" from the folks that contributed writable constants to computer science. 

Oh, and just for the sake of it, replacing the with-statement with the following:

Code: Pascal  [Select][+][-]
  1. with Test, Test do begin


Will result in this output:

Code: [Select]
2
2
I tried it because I had to see it to believe it and, you're right, that's the output.  It's _appalling_.  oh well... I wonder what N. Wirth would say if he saw this stuff.

One thing you are completely right about, I cannot put the blame on FPC for this disgrace, Delphi initiated it.




@BrunoK,

it would be something like this (untested because I cannot test it):

{$define LeftPad:=TKMemoParagraph(KMemo1.Blocks[ABlockNo]).Parastyle.LeftPadding}

and, IMO, it would be better to name the macro "LeftPadding" instead of "LeftPad" as this would make it more evident that the field being referenced is "LeftPadding".
Title: Re: Why with allows assignment
Post by: BrunoK on January 06, 2025, 02:43:51 pm

@BrunoK,

it would be something like this (untested because I cannot test it):

{$define LeftPad:=TKMemoParagraph(KMemo1.Blocks[ABlockNo]).Parastyle.LeftPadding}

and, IMO, it would be better to name the macro "LeftPadding" instead of "LeftPad" as this would make it more evident that the field being referenced is "LeftPadding".

My comments :

1° DBannon's example is syntactically incorrect.

Re 440bx macro.
2° Macro adds complexity in this case.
3° Using macro means that TKMemoParagraph(KMemo1.Blocks[ABlockNo]).Parastyle will be evaluated twice at runtime whereas the with will evaluate the complete TKMemoParagraph(KMemo1.Blocks[ABlockNo]).Parastyle once and use directly the reference to that field TWICE .

In the case of multiple and deeply referenced fields, it can mean much improved response times at runtime. The sample app (https://forum.lazarus.freepascal.org/index.php/https://forum.lazarus.freepascal.org/index.php/topic=69755.msg542480#msg542480 (https://forum.lazarus.freepascal.org/index.php/https://forum.lazarus.freepascal.org/index.php/topic=69755.msg542480#msg542480) working !) I posted before contains quite a few WITH usage, please criticize the code / ask for explanations about the code.

Code: Pascal  [Select][+][-]
  1. unit frmWith;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, BufDataset, DB, Forms, Controls, Graphics, Dialogs,
  9.   DBGrids;
  10.  
  11. type
  12.  
  13.   { TForm1 }
  14.  
  15.   TForm1 = class(TForm)
  16.     BufDataset1: TBufDataset;
  17.     DataSource1: TDataSource;
  18.     DBGrid1: TDBGrid;
  19.     procedure FormActivate(Sender: TObject);
  20.   private
  21.  
  22.   public
  23.  
  24.   end;
  25.  
  26. var
  27.   Form1: TForm1;
  28.  
  29. implementation
  30.  
  31. {$R *.lfm}
  32.  
  33. { TForm1 }
  34.  
  35. procedure TForm1.FormActivate(Sender: TObject);
  36. var
  37.   i: integer;
  38.   lFI1, lFI2: TIntegerField;
  39. begin
  40.   with DBGrid1.DataSource, DataSet do
  41.     if not Active then begin
  42.       DisableControls;
  43.       with FieldDefs do begin
  44.         Clear;
  45.         FieldDefs.Add('Auto', ftAutoInc);
  46.         FieldDefs.Add('I1', ftInteger);
  47.         FieldDefs.Add('I2', ftInteger);
  48.       end;
  49.       TBufDataset(DataSet).CreateDataset;
  50.       Active:=True;
  51.       lFI1 := TIntegerField(FieldByName('I1'));
  52.       lFI2 := TIntegerField(FieldByName('I2'));
  53.       for i := 0 to 9 do begin
  54.         Append;
  55.         lFI1.AsInteger := i;
  56.         lFI2.AsInteger := i;
  57.         Post;
  58.       end;
  59.       EnableControls;
  60.     end;
  61. end;
  62.  
  63. end.
Title: Re: Why with allows assignment
Post by: 440bx on January 06, 2025, 04:49:10 pm
the "with" implementation in Delphi and FPC is an appalling atrocity, it is _irrelevant_ if that atrocity may be convenient to someone, it is wrong.  Just as wrong as the existence of writable constants.

It seems that appalling "with" implementation is to sidestep a performance problem in OOP.  As usual, one bad idea leads to another.

Fortunately, the "with" statement works mostly as expected in the absence of OOP. "Mostly" because the OP showed an example where "with" doesn't work as it should and there is no OOP in the example.

All that said, if you or anyone else wants to suggest adding "as" to the "with" statement, be my guest, it is something that will never be in my code (just like the rest of OOP.)
Title: Re: Why with allows assignment
Post by: dbannon on January 07, 2025, 06:21:04 am
Sadly, it seems that the shown example is syntactically incorrect thus not adding anything constructive to the discussion.

I am not sure how you came to that conclusion Bruno. The first example I showed compiles and behaves exactly as I expect. The second example, based on what Bart mentioned was a "proposal" would, IMHO be correct, would compile and would run as expected if it was implemented. Obviously, it has not been implemented so, bit hard to test right now.

You suggest my example is "syntactically incorrect" in two separate posts, please elaborate ? A typo ? Possible but irrelevant.

@440bx, yes, same thing could be achieved with Macros but quite untidy code IMHO. In the case of my code, I have many identifiers similar to but usually slightly different, so, one macro would not suffice, multiple macros would start to look like C Code , yek !
 
Davo
Title: Re: Why with allows assignment
Post by: Thaddy on January 07, 2025, 11:42:53 am
the "with" implementation in Delphi and FPC is an appalling atrocity, it is _irrelevant_ if that atrocity may be convenient to someone, it is wrong.  Just as wrong as the existence of writable constants.
There is one vaild use of with: to convert between an interface and its underlying class:
Code: Pascal  [Select][+][-]
  1. var Intf:IInterfaced;// this is from my interface tricks
  2. with Intf as TStringlist do begin
The alternative is writing things like
Code: Pascal  [Select][+][-]
  1. (intf as Tstringlist).Add('some string');
all the time.

Typed consts is just a misnomer, it could have been called static variable and static variables are very useful. Of course that should exist in one form or another.
Title: Re: Why with allows assignment
Post by: 440bx on January 07, 2025, 01:32:40 pm
Typed consts is just a misnomer, it could have been called static variable and static variables are very useful. Of course that should exist in one form or another.
Just a misnomer uh ?... it's totally fine to explain to someone (usually a programmer) that Pascal allows writing to constants... makes the language look so great.   Don't worry about it... just a "misnomer".

that FPC allows assigning a value to a function _outside_ the function itself, must be a "misnomer" too.

btw, there is no misnomer in typed constants, there is no reason why a constant couldn't be typed.  OTH, equating typed constants to writable constants is like equating a hammer to canine organic residue on a sidewalk... just a "misnomer", of course.  It must also be a misnomer that such variables are declared in a "const" section.   You're right... why would anyone have an issue with "writable constants" or assigning values to functions anywhere you want as long as you wrap it into a "with" statement... no problem!!!.  That's what computer science is all about... redefining logic and mathematics.

What really matters is that those things are convenient... that makes it right.  why don't other languages follow this "great" example ? particularly those that are standardized ?

I love Pascal, I wouldn't be here otherwise but, some of the things Borland and FPC have added to it are genuinely embarrassing.

Still, I'm very grateful FPC is available.  It would be really nice if some of the "misnomers" were removed from the language or, at least, be _rationally_ implemented (and some that need to be corrected but, that won't happen.)
Title: Re: Why with allows assignment
Post by: BrunoK on January 07, 2025, 01:57:40 pm
I am not sure how you came to that conclusion Bruno. The first example I showed compiles and behaves exactly as I expect.
Code: Pascal  [Select][+][-]
  1.     if TKMemoParagraph(KMemo1.Blocks[ABlockNo]).Parastyle.LeftPadding = 0 then
  2.       TKMemoParagraph(KMemo1.Blocks[ABlockNo]).Parastyle.LeftPadding = 42;
Well, pussyfooting, how do I interpret ... Parastyle.LeftPadding = 42;
Quote
      
The second example, based on what Bart mentioned was a "proposal" would, IMHO be correct, would compile and would run as expected if it was implemented. Obviously, it has not been implemented so, bit hard to test right now.
   
Is problematic because as has the meaning of interpreting some field as a specific type (transtyping). Also in your example the aliased member is a property that has a getter and more specifically a setter which would require that the compiler does some implied macroing to reach the target of aliased variable.
Quote
@440bx, yes, same thing could be achieved with Macros but quite untidy code IMHO. In the case of my code, I have many identifiers similar to but usually slightly different, so, one macro would not suffice, multiple macros would start to look like C Code , yek !
 
Davo
Agree, as mentioned above, macro would loose the interest of the temporary scoped evaluation of the expression
Code: Pascal  [Select][+][-]
  1. with TKMemoParagraph(KMemo1.Blocks[ABlockNo]).Parastyle.LeftPadding
which reference is then used twice, once in the test and once in the assignment.

For aliasing, the following syntax could be interesting
with variable_ref [as type] [as alias] do ...
If something like that existed, instead of :
Code: Pascal  [Select][+][-]
  1.       lFI1 := TIntegerField(FieldByName('I1'));
  2.       lFI2 := TIntegerField(FieldByName('I2'));
  3.       for i := 0 to 9 do begin
  4.         Append;
  5.         lFI1.AsInteger := i;
  6.         lFI2.AsInteger := i;
  7.         Post;
  8.       end;
I could write :
Code: Pascal  [Select][+][-]
  1. with TIntegerField(FieldByName('I1')) alias FldI1,
  2.   TIntegerField(FieldByName('I2')) alias FldI2
  3. do begin
  4.   for i := 0 to 9 do begin
  5.     Append;
  6.     FldI1.AsInteger := i;
  7.     FldI2.AsInteger := i;
  8.     Post;
  9.   end;
  10. end;
  11.  
In case of table loops, resolving the extraction of field using FieldByName before the iterative loop is a very big speed winner when the number of iterations is high. But the lengthy writing / declaring temp variables as done in the non-aliased code above is acceptable.
Title: Re: Why with allows assignment
Post by: simone on January 07, 2025, 02:01:28 pm
I love Pascal, I wouldn't be here otherwise but, some of the things Borland and FPC have added to it are genuinely embarrassing.

Still, I'm very grateful FPC is available.  It would be really nice if some of the "misnomers" were removed from the language or, at least, be _rationally_ implemented (and some that need to be corrected but, that won't happen.)

A "strict" mode could be introduced, in order to not allow some questionable features (to be diplomatic) and, at the same time, ensure backwards compatibility.
Title: Re: Why with allows assignment
Post by: 440bx on January 07, 2025, 02:04:27 pm
A "strict" mode could be introduced, in order to not allow some questionable features (to be diplomatic) and, at the same time, ensure backwards compatibility.
I like the suggestion, it sounds very reasonable to me but, to my chagrin, I don't think the developers are interested in such a mode.

Title: Re: Why with allows assignment
Post by: LV on January 07, 2025, 02:14:28 pm
There is such a trick in Rust.
"
Unsafe Rust
All the code we’ve discussed so far has had Rust’s memory safety guarantees enforced at compile time. However, Rust has a second language hidden inside it that doesn’t enforce these memory safety guarantees: it’s called unsafe Rust and works just like regular Rust, but gives us extra superpowers.
"
Title: Re: Why with allows assignment
Post by: Warfley on January 07, 2025, 03:01:29 pm
I couldn't care less how FPC implements its scope resolution mechanism but that comment proves that you know less about compiler construction than the Pope knows about quantum mechanics.  Locals and parameters are in the same scope which is why locals cannot have the same name as parameters because that would cause a duplicate identifier.
I have worked with multiple compilers, amongst others clang, which I hope you agree is a mainstream compiler. I have never seen the parameter list not being in their own scope, even though it may not be semantically accessible in the language.

For example, while the following does not work right now in fpc:
Code: Pascal  [Select][+][-]
  1. procedure foo(i: Integer);
  2. var j: Integer = i;
  3. begin
  4.  
  5. end;
Because i and j are in different scopes, there is no semantic reason for it not to work. It's mainly that the way the FPC variable initialization is currently implemented it can only read constants directly written into the assembly.
But other languages allow exactly that.

Even FPC will tell you there is a duplicate identifier thereby conclusively proving the parameter and the local are in the same scope.
Do you know where FPC also complains with the exact same error message?
Code: Pascal  [Select][+][-]
  1. {$mode objfpc}{$H+}
  2. {$ModeSwitch advancedrecords}
  3. type
  4.   TTest = record
  5.     i: Integer;
  6.     procedure Foo(i: Integer);
  7.   end;
And I hope we both agree that these two i's are not in the same scope right?

As I suggested in the previous post, you need to read a few books about compiler construction.
I've taken multiple courses in Uni about compiler construction, read multiple books and papers about compiler construction, I've build my own Regex and LALR table generator, I've worked on the source code of multiple compilers including clang.
I regularly create small scripting languages to be used within my programs.

I'm by no means an expert on all areas of compiler construction, infact I rarely work on the backend side but usually do frontend work (i.e. lexing, parsing and AST construction/modification), but I certainly know more than enough about these topics for this discussion here.

Again you didn't know that shadowing is a commonly used term. About mainstream compilers, all common C compilers, gcc, clang, MSVC++ use the term. The Glasgow Haskell Compiler uses the term, the Python Documentation and linters use the term, the OpenJDK Documentation usese the term. Like I do not know how you can use any currently popular programming language and never encounter this term

Honestly I feel that your knowledge on the topic is just severly out of date, because I do not know a single language system (at least one conceived in the past 25 years) which does not use the term.

You tell me to read books, but be honest, have you read anything that came out in the past 10 years on the topic? Knowedge is not something you acquire once, especially in a field like computer science, which is a very young science, it's constantly evolving, to be knowlegable on a topic requires to keep up with the modern developments.
A language like Rust and the Rust compiler could not have existed 20 years ago, because the relevant research in Constraint Satisfaction Theories has just been discovered during the past 15 years. I know that, because I was a student of one of the Professors who did some of the most important discoveries in that area, at that time
Also over the past 10-20 thanks to the widespread access to well versioned and documented open source software, we are now finally able to have meta analysis on language features and how they impact programming behavior, alowing to analyze scientifically what patterns and development paradigms produce more or less bugs, are better maintainable, etc.
Those are things that 20 years ago were simply not possible. If you do not refresh your knowledge but only rely on the books on compiler theory from Wirth or Hoare from the 60s and 70s, while they are a very excellent introduction to the topic, they are just that, the very basics. Today the field is so much bigger, with so much more stuff to know and learn about...

I don't doubt that you once were a great programmer, but from how and what you write, not just here, it feels like your knowledge is stuck in the 80s and 90s, and you still rely on that knowledge alone, ignoring the past 20-30 years of development in the field
Title: Re: Why with allows assignment
Post by: Warfley on January 07, 2025, 03:12:13 pm
Agree, as mentioned above, macro would loose the interest of the temporary scoped evaluation of the expression
Code: Pascal  [Select][+][-]
  1. with TKMemoParagraph(KMemo1.Blocks[ABlockNo]).Parastyle.LeftPadding
which reference is then used twice, once in the test and once in the assignment.

For aliasing, the following syntax could be interesting
with variable_ref [as type] [as alias] do ...
If something like that existed, instead of :
Code: Pascal  [Select][+][-]
  1.       lFI1 := TIntegerField(FieldByName('I1'));
  2.       lFI2 := TIntegerField(FieldByName('I2'));
  3.       for i := 0 to 9 do begin
  4.         Append;
  5.         lFI1.AsInteger := i;
  6.         lFI2.AsInteger := i;
  7.         Post;
  8.       end;
I could write :
Code: Pascal  [Select][+][-]
  1. with TIntegerField(FieldByName('I1')) alias FldI1,
  2.   TIntegerField(FieldByName('I2')) alias FldI2
  3. do begin
  4.   for i := 0 to 9 do begin
  5.     Append;
  6.     FldI1.AsInteger := i;
  7.     FldI2.AsInteger := i;
  8.     Post;
  9.   end;
  10. end;
  11.  
In case of table loops, resolving the extraction of field using FieldByName before the iterative loop is a very big speed winner when the number of iterations is high. But the lengthy writing / declaring temp variables as done in the non-aliased code above is acceptable.

If you want to give it a try, check out this branch: https://gitlab.com/freepascal.org/fpc/source/-/merge_requests/860

Where I built exactly that, if you could test it and give recommendations, report bugs, etc. in that MR I would be very greatful :)

A "strict" mode could be introduced, in order to not allow some questionable features (to be diplomatic) and, at the same time, ensure backwards compatibility.
Thats what modeswitches are for. To change language behavior which is not backwards compatible. BUUUUT the problem with the FPC developers, which you can probably understand partly if you look at the code base, is more of the ever increasing complexity of the fpc source code. For example, take a look at this: https://gitlab.com/freepascal.org/fpc/source/-/blob/main/compiler/pexpr.pas#L373
Because in MacPas, you can use "leave" instead of "break" and "cycle" instead of continue, the FPC needs to basically double the code for continue and break just for that one mode pretty much no one is actively using
Title: Re: Why with allows assignment
Post by: 440bx on January 07, 2025, 04:28:22 pm
For example, while the following does not work right now in fpc:
Code: Pascal  [Select][+][-]
  1. procedure foo(i: Integer);
  2. var j: Integer = i;
  3. begin
  4.  
  5. end;
Because i and j are in different scopes,
No, they are not in different scopes.  They are in the same scope.  However a compiler decides to implement its scopes and how it keeps track of what is in one scope or another is one thing but, the parameters and the locals are in the same scope otherwise you'd have to have a scope identifier to refer to the parameters.

Here is _your_ own code:
Code: Pascal  [Select][+][-]
  1.  procedure foo(x: Integer);
  2. var
  3.   x: Double;
  4. begin
  5. end;
IF, as you (erroneously) claim, the parameter "x" and the variable "x" are in different scopes then, please, explain why the compiler emits an error stating there is a "duplicate identifier".  If those identifiers are not in the same scope then they are not duplicates.  You got some explaining to do.

there is no semantic reason for it not to work.
Yes, that's true.

It's mainly that the way the FPC variable initialization is currently implemented it can only read constants directly written into the assembly.
No, it's because FPC does not automatically generate code to initialize one variable with the value of another or the value of a function (which C supports.)

But other languages allow exactly that.
You're mixing Apples and Mars rovers.

I've taken multiple courses in Uni about compiler construction, read multiple books and papers about compiler construction,
I find that very difficult to believe given that you have demonstrated having no grasp whatsoever of some of the most _basic_ concepts of compilers.  What scope things are in is one of the most basic things there is and, btw, what is/belongs in one scope or another is independent of implementation.  I don't care if you've read clang source or anything else.  It has nothing to do with it.

Again you didn't know that shadowing is a commonly used term. About mainstream compilers, all common C compilers, gcc, clang, MSVC++ use the term.
You can claim it's common all you want BUT, MSVC++ emits a message about a variable masking another one, not shadowing it.  I don't recall what gcc emits and I simply don't know (and don't care) what clang emits.

But, you know what, if it makes you feel good that I was unaware of the term "shadowing", you're right.  feel better now ?  The only compiler construction book I keep at hand at all times is "Per Brinch Hansen On Pascal Compilers" and that one doesn't mention shadowing (at least not that I remember and, I've read that book multiple times... but, I could still be wrong.)  (that book and Sedgewick's Algorithms are the two books that are always on my desk which made me realize just now that Sedgewick's book is not where it's supposed to be... )

Honestly I feel that your knowledge on the topic is just severly out of date, because I do not know a single language system (at least one conceived in the past 25 years) which does not use the term.
My knowledge isn't out of date but my terminology might be.  It also looks like correctness is "out of date" (trivial things like not allowing a function to have values assigned outside the function... things like that.)

You tell me to read books, but be honest, have you read anything that came out in the past 10 years on the topic?
Yes, I have.  The last book I read on compilers is "Engineering a compiler by Cooper and Torczon, 2nd edition" which was published in 2012.   It refers to shadowing when the compiler generates a hidden variable to control iterations in loops. I didn't find an instance where the term is used when one user defined variable mask another user define variable or parameter but, I could have missed it (though, the search function didn't reveal an instance of that.)

but only rely on the books on compiler theory from Wirth or Hoare from the 60s and 70s, while they are a very excellent introduction to the topic, they are just that, the very basics.
The foundations have not changed much (unfortunately.)

When it comes to scanning and parsing, very little has changed in the last 60 or so years.  There have been improvements but nothing that is a game changer.  Moreover, some of the "improvements" have resulted in poorer language quality ("creative" compilers...)
Title: Re: Why with allows assignment
Post by: BrunoK on January 07, 2025, 04:53:56 pm
that FPC allows assigning a value to a function _outside_ the function itself, must be a "misnomer" too.
if you mean the OP program (revised) :
Code: Pascal  [Select][+][-]
  1. program Project1;
  2.  
  3. const
  4.   HighInt64 = int64(int64(-1) shr 1); // Typed const
  5.  
  6. type
  7.   TTest = record
  8.     V: int64;
  9.   end;
  10.  
  11. var
  12.   X: TTest;
  13.  
  14.   function Test: TTest;
  15.   begin
  16.     Result := X;
  17.   end;
  18.  
  19. var
  20.   i: qword;
  21. begin
  22.   with Test do begin
  23.     V := HighInt64; //No Error
  24.     WriteLn(X.V, ' ', {With Test.}V);
  25.   end;
  26.   Readln;
  27. end.
There is no assignment of a function outside it. The assignment is to the temporary TTest record returned by the Test function call. As my little snippet of code shows, the X.V record field is not changed, only the returned temporary record is altered to HighInt64.
Title: Re: Why with allows assignment
Post by: 440bx on January 07, 2025, 05:14:58 pm
that FPC allows assigning a value to a function _outside_ the function itself, must be a "misnomer" too.
if you mean the OP program (revised) :
Code: Pascal  [Select][+][-]
  1. program Project1;
  2.  
  3. const
  4.   HighInt64 = int64(int64(-1) shr 1); // Typed const
  5.  
  6. type
  7.   TTest = record
  8.     V: int64;
  9.   end;
  10.  
  11. var
  12.   X: TTest;
  13.  
  14.   function Test: TTest;
  15.   begin
  16.     Result := X;
  17.   end;
  18.  
  19. var
  20.   i: qword;
  21. begin
  22.   with Test do begin
  23.     V := HighInt64; //No Error
  24.     WriteLn(X.V, ' ', {With Test.}V);
  25.   end;
  26.   Readln;
  27. end.
There is no assignment of a function outside it. The assignment is to the temporary TTest record returned by the Test function call. As my little snippet of code shows, the X.V record field is not changed, only the returned temporary record is altered to HighInt64.
Wrong and unacceptable!  The temporary represents the result of the function therefore it must be treated exactly the same as the result of the function.

The _correct_ way to do what that code atrocity above is doing is to assign the function result to a variable and then change the variable but, of course, that's too much work and worse, it is correct, we can't possibly have that.  In the code you present, V represents the result of the function therefore it MUST be treated as the function's result.

Assign a value to function results outside the function ?... no problem... assign values to constants ?... no problem...   it's all great as long as it's convenient... that's what really matters... correctness ?.... nah... who could possibly need that ?  (just in case, rhetorical.)
Title: Re: Why with allows assignment
Post by: Thaddy on January 07, 2025, 05:19:40 pm
BrunoK and me (very early in this discussion) already pointed out you are barking up the wrong tree. The last contribution of Bruno is exactly the same as I wrote earlier.
We both disagree with you and we think you are wrong, with both the same reasoning.

Of course, with without context is a very bad idea, but there are other scenario's that warrant its use.
Same with your adversity to typed consts, which are simply static variables.
Title: Re: Why with allows assignment
Post by: BrunoK on January 07, 2025, 05:19:49 pm
Functions returning a record do actually cause a copy of the result record to the result field, in this case the with Test temporary scoped variable.
Code: Pascal  [Select][+][-]
  1. program Project1;
  2.  
  3. const
  4.   HighInt64 = int64(int64(-1) shr 1); // Typed const
  5.  
  6. type
  7.   TTest = record
  8.     V: int64;
  9.   end;
  10.  
  11. var
  12.   X: TTest;
  13.  
  14.   function Test: TTest;
  15.   begin
  16.     Result := X;
  17.   end;
  18.  
  19. var
  20.   i: qword;
  21. begin
  22.   with Test do begin
  23.     WriteLn(X.V, ' ', {With Test.}V);
  24.     V := HighInt64; //No Error
  25.     WriteLn(X.V, ' ', {With Test.}V);
  26.   end;
  27.   Readln;
  28. end.
Title: Re: Why with allows assignment
Post by: Thaddy on January 07, 2025, 05:22:13 pm
Yes, with opens a block.
Title: Re: Why with allows assignment
Post by: 440bx on January 07, 2025, 05:33:17 pm
Functions returning a record do actually cause a copy of the result record to the result field, in this case the with Test temporary scoped variable.
The automatic creation of a temporary in the "with" turns the "with" statement into a hidden assignment statement.  The "with" statement is NOT an assignment statement.  It is a scope declaration statement.

god forbid we "force" programmers to assign the result of a function to a variable.  It's so much "easier" to create a hidden variable that represents the result and violate every principle of programming by allowing a value to be assigned to the functions' result outside the function.  Convenience over correctness... that's the way to go.

One thing is for sure, nothing I say is going to make any difference.  It isn't the only thing FPC does wrong that has survived correctness.  In favor of FPC, it must be mentioned that this atrocity was initiated by Delphi, that's the real culprit and compatibility leaves little choice.

Worse thing that has happened to Pascal is the absence of an evolving standard.  That absence is what allows this kind of stuff.  "Convenience Design".
Title: Re: Why with allows assignment
Post by: Thaddy on January 07, 2025, 05:35:38 pm
It is a scope declaration statement.
Yes, intentionally.
For that matter it does not differ from other scope declaration statements except for the exception I described earlier for interfaces. Although that also limits scope to the underlying class.
Title: Re: Why with allows assignment
Post by: BrunoK on January 07, 2025, 05:41:23 pm
"Convenience" is what make a language usable as opposed to dogmatic construct that make things useless.

If it is convenient, in my little living area to use a flat map on a sheet of paper, I'll, ruthlessly use it instead of thinking to the rotundity of the earth.
Title: Re: Why with allows assignment
Post by: Warfley on January 07, 2025, 06:57:32 pm
No, it's because FPC does not automatically generate code to initialize one variable with the value of another or the value of a function (which C supports.)
What I meant it that the way the FPC is currently implemented, initialization cannot be dynamic, because the code that parses the initialization directly spits out an assembly list (there is no intermediate layer like the node tree which allows the initialization to represent more complex concepts). It's literally: Read number -> Write to assembly.
It's very fast, but very limited, so it's not just values of a function, it's anything thats not a static data known at compile time. So for example you can't initialize managed records because the static assignment cannot execute the initialization operator (which is a bug).
It's just a technical limitation on how the FPC is currently build.

I find that very difficult to believe given that you have demonstrated having no grasp whatsoever of some of the most _basic_ concepts of compilers.  What scope things are in is one of the most basic things there is and, btw, what is/belongs in one scope or another is independent of implementation.  I don't care if you've read clang source or anything else.  It has nothing to do with it.
What do you think a scope is? A scope is a set of symbols, represented through a symbol table. Scopes are spanned through syntax structures, in a strictly formal compiler (usually based on parsing tables), they would be strictly associated with AST nodes (which correspond to reduction rules in the grammar definition), while in a less formal compiler like a hand crafted recursive decent LL parser such as the FPC, scopes can be more dynamically managed (e.g. with statements do not have their own representation in the AST, they are basically macros based on an ephemeral symtable). But let's stick to the formal case.
Because you can do forward declarations in Pascal, functions are seperated in a function header and a function body. A forward declaration is a function header without a body and a function implementation is a header plus body.
Code: XML  [Select][+][-]
  1. <Function> ::= <Function Header>
  2.              | <Function Header> <Function Body>
Because the parameter symbols are defined in the function header, it must have it's own symtable independen of that of the function body (which contains any local declarations).

Additionally, if we look at the history of Pascal, while FPC is quite strict with scoping, TurboPascal isnt:
Code: Pascal  [Select][+][-]
  1. {$mode tp}
  2. function foo: Integer;
  3. var
  4.   foo: Double;
  5. begin
  6.  
  7. end;
Here the variable foo shadows the function name (and thereby name of the result variable) foo. This is because the function header and the function body have different scopes.

Quote
When it comes to scanning and parsing, very little has changed in the last 60 or so years.  There have been improvements but nothing that is a game changer.  Moreover, some of the "improvements" have resulted in poorer language quality ("creative" compilers...)

It's not "creative", it's all based on formalisms. The problem is when the formalism doesn't make sense (like in JavaScript [2] == "2"). But even that was a mistake from 30 years ago that pretty much everyone agrees was a bad idea. Most of the current developments are in proving formalistic rules especially over the typesystem. Especially Algebraic typesystems with type inference that allow strict and static typing without losing flexibility in generic code. I mean the first version of that was introduced with ML already in the 80s, and all goes back to Prolog from the 70s, but the current developments both technologically (solving these constraint sets requires a lot of computing power) but also theoretically (as I mentioned before the CST has gone through some major discoveries with respect to new heuristics for efficient solving) make things possible which in the 70s and 80s where not feasable to be used on real world systems.

The automatic creation of a temporary in the "with" turns the "with" statement into a hidden assignment statement.  The "with" statement is NOT an assignment statement.  It is a scope declaration statement.

god forbid we "force" programmers to assign the result of a function to a variable.  It's so much "easier" to create a hidden variable that represents the result and violate every principle of programming by allowing a value to be assigned to the functions' result outside the function.  Convenience over correctness... that's the way to go.
With is what it is built to be. There is no fundamental rule on what with is supposed to be. Yes with declares a scope, but it declares a scope around an object. Either an already existing object, in case of a variable, or a newly allocated temporary object.
In my office I have a big fancy coffee machine. This machine fills coffee into a cup. If you bring your own cup great, it fills coffee in there. If you don't it gives you a new disposable cup to fill coffee into. Does this make the coffee machine now a cup dispenser? Not really, it's just a coffee machine which has a cup dispenser build into to fulfill it's role even if the user does not bring their own cup.
Similar with with. It creates a scope around an object. But if that object does not exist, it creates a new temporary object, limited to the scope of the with statement, to perform the action. Without it it could not provide it's functionality as intended
Title: Re: Why with allows assignment
Post by: 440bx on January 07, 2025, 09:22:44 pm
Nice try but... you claimed that in the following code (your code btw):
Code: Pascal  [Select][+][-]
  1.  procedure foo(x: Integer);
  2. var
  3.   x: Double;
  4. begin
  5. end;
that the parameter "x" and the local variable variable "x" are not in the same scope, I emphasize this is _your_ claim, most definitely NOT mine.

As I asked you in the previous post, IF the parameter "x" is not in the same scope as the local variable "x" then explain why the compiler claims "x" is a duplicate identifier. As I stated in the previous post, you got some explaining to do.

BTW, you are correct that the procedure _name_ is not in the same scope as its parameters and its locals.  I can show you a post that is a few years old where I make that point along with showing that FPC does not always resolve scopes correctly.

I won't put the link to that post to keep the focus on the current subject which is: parameters and local variables are in the same scope. 

Once this has been established as the fact it is, if you are interested, I'll link to the other post that shows FPC's "unusual' scope resolution methods.  OTH, that has nothing to do with Delphi's and FPC's incorrect handling of the "with" statement and, for that reason I'd rather leave that old post alone even though it does show additional "deficiencies" in FPC's scope resolutions methods.


Title: Re: Why with allows assignment
Post by: Warfley on January 07, 2025, 09:56:40 pm
As I asked you in the previous post, IF the parameter "x" is not in the same scope as the local variable "x" then explain why the compiler claims "x" is a duplicate identifier. As I stated in the previous post, you got some explaining to do.
I already answered that. Because the message "duplicate identifier" has nothing to do with scope.

So let's take what you said next:
Quote
BTW, you are correct that the procedure _name_ is not in the same scope as its parameters and its locals.
So we agree that local variables and function name are in a different scope, so this is why the FPC does not throw a "duplicate identifier" error right?
Well...
Code: Pascal  [Select][+][-]
  1. {$mode fpc}
  2. procedure foo;
  3. var foo: Integer;
  4. begin
  5. end;
Same message even though we both agree it's in a different scope.
FPC has shadowing rules which are not dependent on scope. Another example:
Code: Pascal  [Select][+][-]
  1. {$Mode ObjFPC}
  2. TMyObject = object
  3.   i: Integer;
  4.   procedure Foo(i: Integer);
  5. end;
Also not same scope but same error message.

The error message does not give any indication if it's the same scope or not. It's also used for shadowing.

Also with that point:
Quote
BTW, you are correct that the procedure _name_ is not in the same scope as its parameters and its locals.  I can show you a post that is a few years old where I make that point along with showing that FPC does not always resolve scopes correctly.
I was talking about the function name as the result variable. So the function name as function symbol, used to call the function is semantically different from the function name as return variable symbol, depending on the scope. In the scope where the function is registered, it's solely the symbol for calling the function, in the scope of the function body it doubles as a symbol for the result variable.

So you can create a local variable which shadows the local result value variable.

Again two things are in a different scope if they are in a different symtable on the symtable stack. And I have not seen a single compiler so far where the local variables and the parameters are in the same symtable. I don't say it doesn't exist, but it's very uncommon to have local variables and parameters in the same scope.

But also I do not know why we discuss shadowing here to begin with, it was just an example that you want to avoid shadowing, because it introduces errors, infact the example from above:
Code: Pascal  [Select][+][-]
  1. {$Mode TP}
  2. function foo: Integer;
  3. var foo: Double;
  4. begin
  5.  
  6. end;
Completely breaks the function because you cannot return a value if you shadow the return variable.

And because shadowing is bad, and in modern day LCL Component based programs most of the with statements will inevitibly introduce shadowing, using with is not a good idea.
Also it's not even that with gives any benefit (aside from the creation of the temporary object), it's just a concept of lazieness, and potentially introducing errors just to save up a few characters is a bad idea.
Title: Re: Why with allows assignment
Post by: 440bx on January 07, 2025, 10:23:11 pm
As I asked you in the previous post, IF the parameter "x" is not in the same scope as the local variable "x" then explain why the compiler claims "x" is a duplicate identifier. As I stated in the previous post, you got some explaining to do.
I already answered that. Because the message "duplicate identifier" has nothing to do with scope.
No, you did not.  Additionally, it has _everything_ to do with scopes because there can only be duplicates if they are in the same scope.  if they were in different scopes they wouldn't be duplicates... doh!

One more time, given _your_ code:
Code: Pascal  [Select][+][-]
  1.  procedure foo(x: Integer);
  2. var
  3.   x: Double;
  4. begin
  5. end;
Are the parameter "x" and local variable "x" in the same scope ?  Answer that first.

if your answer is "no", which would be obviously incorrect then, how do you explain that the compiler is stating there is a "duplicate identifier" ? 

You got some explaining to do.  Try again.


Title: Re: Why with allows assignment
Post by: Warfley on January 07, 2025, 11:00:43 pm
Are the parameter "x" and local variable "x" in the same scope ?  Answer that first.
I answered this before. The local variable is in the local var symtable and the parameter x is in the param symtable. These two symtables are independent entities in the symbol search hierachy and therefore are different scopes.

Simple as that.

if your answer is "no", which would be obviously incorrect then, how do you explain that the compiler is stating there is a "duplicate identifier" ? 

You got some explaining to do.  Try again.
It's not because the compiler uses the "duplicate identifier" for any kind of name collision, including when two symbols in the same scope have the same name, but also, as I have demonstrated above, if two symbols from different scopes violate the shadowing rules of the FPC.

The message is not wrong, just imprecise.

So now answer my question:
Code: Pascal  [Select][+][-]
  1. {$Mode ObjFpc}
  2. TMyObject = object
  3.   i: Integer;
  4.   procedure Foo(i: Integer);
  5. end;
Is the field i and the parameter i in the same scope? And if the answer is no, then why is the error message "duplicate identifier"?

I answered all your points, but whenever I brought that up 3 times now, so why don't you want to answer that? Because you can't? Or because you only use FPCs error messages as an argument when it agrees with you and when FPC does something that doesn't agree with you it's obviously a bug, because you are absolutely infallable?

Also you always claim that I don't know what a scope is, define what a scope is according to you. I gave my definition above.

And lastly, before you read the following example, do you agree that fundamental principle of a scope is that two identifiers within the scope resolve to the same entity?

Because here I have news for you:
Code: Pascal  [Select][+][-]
  1. type
  2.   TTest = integer;
  3.  
  4. procedure Foo(x: TTest);
  5. type
  6.   TTest = Double;
  7. var
  8.   y: TTest;
  9. begin
  10. end;
The type of x has the same identifier as the type of y, but they resolve to different types. Therefore they cannot be in the same scope.

And if you want to claim that the introduction of the new type creates a new scope,  I surely should be able to rename y to x and not get a duplicate name error right?
Title: Re: Why with allows assignment
Post by: LV on January 07, 2025, 11:04:26 pm
Let's get back to OP's question.
Possible answer: :)

Code: Pascal  [Select][+][-]
  1. program Project1;
  2.  
  3. type
  4.   TTest = record
  5.     V: integer;
  6.   end;
  7.   TP = ^TTest;
  8.  
  9. var
  10.   X: TTest;
  11.  
  12.   function Test: TP;
  13.   begin
  14.     Result := @X;
  15.   end;
  16.  
  17. begin
  18.  
  19.   WriteLn(X.V);
  20.  
  21.   with Test^ do
  22.   begin
  23.     V := 1; //No Error
  24.     WriteLn(X.V);
  25.   end;
  26.  
  27.   WriteLn(X.V);
  28.  
  29.   Test^.V := 2; //No Error
  30.  
  31.   WriteLn(X.V);
  32.  
  33.   readln;
  34. end.
  35.  

Output:
0
1
1
2
Title: Re: Why with allows assignment
Post by: 440bx on January 07, 2025, 11:46:05 pm
I answered this before. The local variable is in the local var symtable and the parameter x is in the param symtable. These two symtables are independent entities in the symbol search hierachy and therefore are different scopes.

Simple as that.
No, you didn't answer then and the above is not an answer.  I don't want implementation b.s.

The question is simple, in the following code, which is _your_ code:
Code: Pascal  [Select][+][-]
  1.  procedure foo(x: Integer);
  2. var
  3.   x: Double;
  4. begin
  5. end;
are the parameter "x" and the local variable "x" in the same scope ?  The answer is either "yes" or "no", nothing else.

if you answer "no" then you must explain why the compiler emits a "duplicate identifier" error message.

That's it.  No more b.s., answer the question. 



Title: Re: Why with allows assignment
Post by: Warfley on January 08, 2025, 12:02:38 am
That's it.  No more b.s., answer the question.

I answered that question multiple times, and if you don't understand the answer thats not my problem. The last three words of my answer to that question before are without any ambiguity an answer to that question:
Quote
are different scopes.
If you cannot comprehend 3 words and need me to boil it down to one, I can't help you.

What I find quite curious, you ask questions over questions and I answer every single one of them, yet you have not answered a single of my questions. Why do you refuse to answer any of my questions? I asked two very simple questions:
1. What is your definition of a scope? I gave my definition and a very detailed explaination (twice) why the parameter and the variables are not in the same scope.
2. If the "duplicate identifier" means that two things are in the same scope, what about the other 2 examples I've shown before that yield the same message, are these in the same scope as well?
Title: Re: Why with allows assignment
Post by: 440bx on January 08, 2025, 01:19:23 am
I answered that question multiple times, and if you don't understand the answer thats not my problem.
I understand, you want to hide your ignorance and mistakes under a thick coat of b.s.

It's really simple, a "yes" or a "no" along with an explanation of why.  This is totally independent of implementation. Your going back every time to implementation details that are not applicable and totally meaningless highlights your awareness of being wrong.

It's that simple.

Any chance you can manage to say "yes" or "no" and explain why the compiler emits an error message stating there is a "duplicate identifier" ?  There you go... I'm giving you one more chance... are you going to offer more b.s or finally do the right thing and admit you are wrong ?  My guess is, most likely not.

Title: Re: Why with allows assignment
Post by: Warfley on January 08, 2025, 01:50:14 am
I understand, you want to hide your ignorance and mistakes under a thick coat of b.s.
You are the one avoiding to answer the questions. I gave you exactly what you ask for, I gave a clear definition of what a scope is and why from that definition it follows that parameters and variables are in a different scope.

You either do not understand that, which granted compiler construction is not an easy topic, it's no shame if it's over your head, or you use a different definition of scope and therefore what I consider to be in the same scope is different than what you consider to be... But then again why do you refuse to tell me what your definition is?

Just quit the b.s. and answer my question already: What is your definition of scope?
Title: Re: Why with allows assignment
Post by: 440bx on January 08, 2025, 02:22:45 am
You provided the following garbage to the question:
I answered this before. The local variable is in the local var symtable and the parameter x is in the param symtable. These two symtables are independent entities in the symbol search hierachy and therefore are different scopes.

Simple as that.
It is irrelevant whether the local variable is in some symbol table or in Beijing.   

What's relevant in the following code (your code):
Code: Pascal  [Select][+][-]
  1.  procedure foo(x: Integer);
  2. var
  3.   x: Double;
  4. begin
  5. end;
is the reason why the compiler emits an error message stating there is a duplicate identifier. 

That's the question.  What's the reason for the compiler emitting a duplicate identifier error message. The answer does not involve implementation details such as symbol table(s) b.s.

I'll tell you why the compiler complains: because the local variable and the parameter are in the same scope.  That fact and the fact that they are named the same is what makes them duplicates.  Compiler construction 0.00000001

Read Per Brinch Hansen On Pascal Compilers.  It covers the basics and, among other things, it explains scopes.  The text is available online at Hansotten's Pascal web site but, I recommend you don't stop there.  There is a lot more than what that introductory book covers.

 
Title: Re: Why with allows assignment
Post by: Warfley on January 08, 2025, 02:28:26 am
Read Per Brinch Hansen On Pascal Compilers.  It covers the basics and, among other things, it explains scopes.  The text is available online at Hansotten's Pascal web site but, I recommend you don't stop there.  There is a lot more than what that introductory book covers.

Just tell me the definition of a scope you use. Don't hide behind me having to buy a book. If you go by the definition of scope in that book, quote it to me... But I know you won't because making such a concrete claim would mean your argument would be falsifiable...

You always assert things without any evidence for it, while discarding anything that's going against your view. Take the shadowing debate earlier... It's really emblematic for your Modus operandi.
I used the term shadowing, and you didn't know it, that's nor an issue, no one knows everything, but instead of googling you asserted that because you didn't know it it doesn't make sense.
Then I showed you the Wikipedia page, and you boltly asserted, again without doing any cross checking that no major compilers uses that term. Then I did a quick search and figured that literally all major compiler and languages use that term and only after being proven wrong by multiple people you conceided.
And it's not just that you didn't know a word and you've been wrong. It's that every single time you made a concrete and falsifiable statement, which you don't do very often (see your refusal to post the definition of a scope), you have been proven wrong, and whenever you are , your first instinct is to question or outright disregard the evidence.

You always assume you are correct and never put on any work. You never provide any resources on your own to make your point but demand from others to provide more and more evidence because you simply cannot fathom being wrong.

So maybe just for once, provide something to underly your point, provide a definition of a scope. I think you cannot do that, because you don't have a concrete definition of a scope. You always operate on the basis of vaguely remembering things or how you feel things should be.

I remember our first discussion, where I literally surveyed all the academic literature on the topic of programming patterns and code quality and you didn't even read a single of the papers Ive referenced, to the point that you claimed things which the papers specifically ruled out in the first paragraphs. I spent tens of hours reading research papers, because I cared about getting the facts right. You didn't even spend 5 minutes reading anything, because you didn't want your worldview challenged
You don't engage with reality if reality doesn't align with you fantasy world. You just avoid and deflect with questions, change the topic, or just belatedly claim things that you've already been proven wrong on either just ignoring the evidence that proved you wrong, or just claim it's wrong without giving any reason why this is the case.
I rarely post anything without verifying that what I'm claiming is correct beforehand. I always try to look up anything I say before posting, you clearly don't do that. Before any of my posts here I've looked up things by looking into the sources of FPC and clang, reading the documentation on other compilers, hell even reading up on the old Compiler construction course scripts from university... Meanwhile you deny shadowing exists even though 2 seconds of googling will find you it being mentioned everywhere in documentation for pretty much any programming system

If you really think you are right, how comes you never have evidence for it?
Title: Re: Why with allows assignment
Post by: 440bx on January 08, 2025, 05:35:44 am
You just can't admit being wrong.

The only reason I insist and will not stop insisting is because you are misleading other readers of this thread.

I've given you many opportunities to correct yourself but, evidently that is beyond you.

By never answering the question clearly, you have proved to be dishonest in addition to rather poorly versed in compiler theory. 
 
For the record, in the following code:
Code: Pascal  [Select][+][-]
  1.  procedure foo(x: Integer);
  2. var
  3.   x: Double;
  4. begin
  5. end;
the parameter "x" and local variable "x" are in the same scope which is the reason any Pascal compiler, including FPC, will declare there is a duplicate identifier.  This is rather simple stuff, no rocket science here.

Nobody should take my word for it, instead read the chapter on scopes in Per Brinch Hansen's book which you can find at:
http://pascal.hansotten.com/uploads/pbh/brinch%20hansen%20on%20pascal%20compilers%20OCR.pdf

The chapter starts at page 95 (book page number) or page 105 (online pdf reader page number)

Specifically, read section "6.3 COMPILATION METHOD" and pay particular attention to Figure 1's first column which clearly shows that parameters, local variables, types and constants are all in the same scope _within_ a procedure or function.  These are pages 110 & 111 (online pdf reader page numbers.)

Anyway, for anyone interested in compilers I highly recommend reading the entire book (several times if possible.)  To see a more complex example Brinch Hansen's SuperPascal is a good second step, after that, I recommend going over the source code of the P4 Pascal compiler guided by Pemberton's book.   After that you'll have a reasonably good foundation to follow with other books.  I'd suggest "Engineering a Compiler" by Cooper and Torczon.    It wouldn't hurt to read the Dragon book, you'll get plenty of useful algorithms there, you'll eventually need them.

Now, let's give the obvious answer: the reason FPC emits a "duplicate identifier" error message is because the parameter and the local variable are in the same scope.  It's that simple.  Period.

It's not rocket science and it has absolutely nothing to do with how a compiler chooses to implement scope resolution.

Title: Re: Why with allows assignment
Post by: LV on January 08, 2025, 10:16:11 am
I reread the documentation https://www.freepascal.org/docs-html/ref/refsu62.html
and made an example according to this documentation.
I don't see any errors.  :-[

Code: Pascal  [Select][+][-]
  1. program Project1;
  2.  
  3. type
  4.   TTest = record
  5.     V: integer;
  6.   end;
  7.   TP = ^TTest;
  8.  
  9. var
  10.   X, Y: TTest;
  11.  
  12.   function Test: TP;
  13.   begin
  14.     Result := @X;
  15.   end;
  16.  
  17.   function Test2: TP;
  18.   begin
  19.     Result := @Y;
  20.   end;
  21.  
  22. begin
  23.  
  24.   WriteLn('X.V = ', X.V);
  25.   WriteLn('Y.V = ', Y.V);
  26.   WriteLn('with Test^, Test2^');
  27.  
  28.   with Test^, Test2^ do
  29.   begin
  30.     V := 1;
  31.     WriteLn('X.V = ', X.V);
  32.     WriteLn('Y.V = ', Y.V);
  33.   end;
  34.   WriteLn('X.V = ', Test^.V);
  35.   WriteLn('Y.V = ', Test2^.V);
  36.   WriteLn('with Test2^, Test^');
  37.  
  38.   with Test2^, Test^ do
  39.   begin
  40.     V := 2;
  41.     WriteLn('X.V = ', X.V);
  42.     WriteLn('Y.V = ', Y.V);
  43.   end;
  44.   WriteLn('X.V = ', Test^.V);
  45.   WriteLn('Y.V = ', Test2^.V);
  46.   WriteLn('with Test^, Test2^ and Test^ := Test2^');
  47.  
  48.   with Test^, Test2^ do
  49.   begin
  50.     V := 3;
  51.     Test^ := Test2^;
  52.     WriteLn('X.V = ', X.V);
  53.     WriteLn('Y.V = ', Y.V);
  54.   end;
  55.   WriteLn('X.V = ', Test^.V);
  56.   WriteLn('Y.V = ', Test2^.V);
  57.   WriteLn('with Test2^, Test^ and Test2^ := Test^');
  58.  
  59.   with Test2^, Test^ do
  60.   begin
  61.     V := 4;
  62.     Test2^ := Test^;
  63.     WriteLn('X.V = ', X.V);
  64.     WriteLn('Y.V = ', Y.V);
  65.   end;
  66.   WriteLn('X.V = ', Test^.V);
  67.   WriteLn('Y.V = ', Test2^.V);
  68.   WriteLn('Test^.V := 5; Test2^.V := 6;');
  69.  
  70.   Test^.V := 5;
  71.   Test2^.V := 6;
  72.  
  73.  
  74.   WriteLn('X.V = ', X.V);
  75.   WriteLn('Y.V = ', Y.V);
  76.  
  77.   readln;
  78. end.    
  79.  

Output:
Code: Text  [Select][+][-]
  1. X.V = 0
  2. Y.V = 0
  3. with Test^, Test2^
  4. X.V = 0
  5. Y.V = 1
  6. X.V = 0
  7. Y.V = 1
  8. with Test2^, Test^
  9. X.V = 2
  10. Y.V = 1
  11. X.V = 2
  12. Y.V = 1
  13. with Test^, Test2^ and Test^ := Test2^
  14. X.V = 3
  15. Y.V = 3
  16. X.V = 3
  17. Y.V = 3
  18. with Test2^, Test^ and Test2^ := Test^
  19. X.V = 4
  20. Y.V = 4
  21. X.V = 4
  22. Y.V = 4
  23. Test^.V := 5; Test2^.V := 6;
  24. X.V = 5
  25. Y.V = 6
  26.  
Title: Re: Why with allows assignment
Post by: TRon on January 08, 2025, 11:05:17 am
I don't see any errors.  :-[
It is not the same situation as used by TS.

Returning pointers allow for manipulating the record-fields. It is not a copy (anymore).
Title: Re: Why with allows assignment
Post by: LV on January 08, 2025, 11:11:48 am
It is not the same situation as used by TS.

Where is this situation in the documentation?
Title: Re: Why with allows assignment
Post by: TRon on January 08, 2025, 11:24:05 am
Where is this situation in the documentation?
afaik it currently isn't described.

However, I seem to remember that when the with behaviour changed It was mentioned in the release notes (But I have no idea what release version that was).
Title: Re: Why with allows assignment
Post by: LV on January 08, 2025, 11:34:57 am
I see, we are talking about undocumented experiments. Thanks for the clarification.
Title: Re: Why with allows assignment
Post by: TRon on January 08, 2025, 11:41:50 am
I see, we are talking about undocumented experiments. Thanks for the clarification.
No, it is not exactly an undocumented experiment. This way of manipulating record fields exist since the existence of the with statement and the way that was shown by TS did work as expected at one time in history. The behaviour was changed because the old hehaviour was unsustainable. Delphi changed it so FPC followed.

That this situation is not explicitly mentioned in the documentation does not by default mean it is an experiment. If that would to be the case then around 50% of my current used code-base would be an experiment (which it isn't). Same would be true for the compiler itself, all the tools, Lazarus and the LCL as you currently know them.
Title: Re: Why with allows assignment
Post by: LV on January 08, 2025, 11:59:30 am
Thank you. I have to be careful about this. Speaking of Delphi https://blogs.embarcadero.com/delphi-with-statements-and-local-variables/
Title: Re: Why with allows assignment
Post by: TRon on January 08, 2025, 12:13:54 pm
Yes, I am aware of marco's blogposts and view on this particular matter but I do thank you for the reminder/link.

I simply do not understand what the fuzz is about. Once it worked, behaviour changed, now it behaves differently. Burned once, burned twice, then never again.

That the compiler is (currently) unable to detect the wrong usage as in TS' example and not warn about it is another matter (there are plenty of other unwanted situations 
that the compiler does not complain/warn about).

I would say: file a report that the compiler is not able to detect the wrong access of the record field(s) and wait for it to be fixed (or fix yourself in case it is upsetting) and be done with it.

It seems that every such situation lately is discussed over and over again while wasting all that time could imho have been better spend. But, that is probably just me.
Title: Re: Why with allows assignment
Post by: Warfley on January 08, 2025, 02:47:14 pm
Nobody should take my word for it, instead read the chapter on scopes in Per Brinch Hansen's book which you can find at:
http://pascal.hansotten.com/uploads/pbh/brinch%20hansen%20on%20pascal%20compilers%20OCR.pdf

The chapter starts at page 95 (book page number) or page 105 (online pdf reader page number)

Specifically, read section "6.3 COMPILATION METHOD" and pay particular attention to Figure 1's first column which clearly shows that parameters, local variables, types and constants are all in the same scope _within_ a procedure or function.  These are pages 110 & 111 (online pdf reader page numbers.)

You actually for the first time in this thread provided some evidence on your own... I'm proud of you. I mean you still did not provide a definition for scope, infact the section you've referenced specifically talks about how you can implement scoping by a stack of symbols...
But wait a minute, haven't you argued that I shouldn't bring up a specific implementation, now you do the exact same. You literally just do the same thing you said was wrong when I did it. You bring up a specific way of implementing a compiler as proof on how scopes should work.
With the small difference that I brought up the implementation in the FPC the compiler for the language this discussion is about, while you bring up the work of a guy who built a pascal compiler 40 years ago that no one is using anymore.

Do you really think that the way Brinch Hansen implemented a pascal compiler nearly half a decade ago is more of an authority on modern day pascal than than the way modern pascal compilers are implemented today?



But because I am a pedant, let's look further in that book, because there is the section "Scoping Rules" which contains a sentence thats most closely to a definition for a scope:
Quote
To make this idea precise we need a set of rules that enable a programmer (and a compiler!) to associate every occurrence of a name with a definition of the corresponding object.
So a scope is some concept that allows for the resolution of names according to a certain set of rules. This is actually the same definition that clang uses, so it's not just some weird thing in a book from the 80s, but something that is acutally used by real world compilers.

So if two objects resolve names differently, they are in different scopes. There are two ways to interpret this, either pedantically, so as soon as there is any discrepency between name resolution of two objects they are in a different scope, e.g.:
Code: Pascal  [Select][+][-]
  1. procedure foo(x: Integer);
  2. var
  3.   y: Integer = sizeof(x);
  4. begin end;
This works fine, but the following doesn't:
Code: Pascal  [Select][+][-]
  1. procedure foo(x: Integer = SizeOf(y));
  2. var
  3.   y: Integer;
  4. begin end;
So x and y clearly have different rules for resolving the name, because y can reference x but x can't reference y. While this would be from a formal point of view probably be the best interpretation, as this allows for formal verification over properties. This hase the side effect that any identifier introduces a new scope:
Code: Pascal  [Select][+][-]
  1. var
  2.   x: Integer;
  3.   y: Integer = sizeof(x);
  4.   z: Integer = sizeof(y);
From a strictly formal perspective x creates a new scope for all following definitions such that x can be used. Same for y and z. So if my goal is to write a formal definition of the Pascal semantic, this would be the way to go.

Now I know why you didn't go with that definition, because this while being very formal, is not very useful in practice when building a compiler (because here you can enforce the scoping rules through the order of parsing the code top to bottom). That said it's great that your own source says that you are wrong :)



And lastly, because you bring that very bad argument up again and again not realizing how it makes you look stupid:

For the record, in the following code:
Code: Pascal  [Select][+][-]
  1.  procedure foo(x: Integer);
  2. var
  3.   x: Double;
  4. begin
  5. end;
the parameter "x" and local variable "x" are in the same scope which is the reason any Pascal compiler, including FPC, will declare there is a duplicate identifier.  This is rather simple stuff, no rocket science here.

[...]


Now, let's give the obvious answer: the reason FPC emits a "duplicate identifier" error message is because the parameter and the local variable are in the same scope.  It's that simple.  Period.
Again this argument is just fallacious. Your argument is formalized:
1. If two objects in the same scope have the same name, they will cause a duplicate identifier error
2. the parameter and variable in this example cause a duplicate identifier error
3. Therefore these two objects are in the same scope.

But 3 does not follow from 1 and 2, because this is just an implication not an equivalence. If two objects are in the same scope they get a duplicate identifier. But this does not mean that if you get duplicate identifier they are in the same scope. To prove that this argument is fallacious a simple proof by contradiction is enough, in that I just need to find a single example where I get the same error but the two variables are not in the same scope:
Code: Pascal  [Select][+][-]
  1. type
  2.   TTest = object
  3.     i: Integer;
  4.     procedure foo(i: Integer);
  5.   end;
Here the parameter i and the object field i are not in the same scope, the parameter is in the scope of the function definition and the field is in the scope of the object. Still there is a duplicate identifier. Therefore you're argumentation is wrong. Just flat out provably wrong.

Yeah and you are right, it's not rocket science, it's a logical fallacy you are proposing here. I know implication vs equivalence is something many first semester students struggle with, so I don't feel to bad about missing this part
Title: Re: Why with allows assignment
Post by: 440bx on January 08, 2025, 02:56:04 pm
What I had to say, I said in my previous post and I stand by every word in it.

Do the world a favor, stop posting your garbage.
Title: Re: Why with allows assignment
Post by: Warfley on January 08, 2025, 02:57:44 pm
What I had to say, I said in my previous post and I stand by every word in it.

Do the world a favor, stop posting your garbage.

Of course you do, you never accept any evidence that goes against your believes, you never do.

One last thing: you like to talk about books, but people who write books are usually not the people who maintain compilers. Brinch Hansen is a great example, he was a professor, a researcher. Sure he worked on a  few compilers in his time, but compilers weren't even his main work.
But also then very little people who do research on compilers acutally write compilers used in the real world. Look at Wirth, he pumped out programming languages with reference compilers like no one else. Yet any of his languages only found actual use after others picked up the work and built their own compilers updating the languages and compilers to be more align with the needs real programmers face in the real world.

Books are great to get an insight into how compilers work. But if you want to learn really how compilers operate, you need to look at the actual compilers in the field. And the great thing is, today most languages and compilers are open source. You want to look how a C compiler works, go and look at the clang or g++ source. You want to know how a Pascal compiler operates, go and look at the FPC sources.

A person who only has their knowledge from books can never really know how things work practically. So maybe instead of reading 40 year old books over and over again, go and crack open some actual compilers and start working with them... Then you may get some actual knowledge about compiler construction.
Title: Re: Why with allows assignment
Post by: 440bx on January 08, 2025, 02:59:05 pm
Re-read my previous post as many times as necessary.
Title: Re: Why with allows assignment
Post by: Warfley on January 08, 2025, 03:01:46 pm
Re-read my previous post as many times as necessary.

Do you really believe your logical fallacies of not knowing the difference between implication and equivalence get less wrong when I read them again?

Like all claims you made were provably wrong
Title: Re: Why with allows assignment
Post by: Warfley on January 08, 2025, 03:16:22 pm
Infact, let me make a list of things you claimed in this thread that were provably wrong:
1. That with is writable is a bug:
Provably wrong as you can see by the comments in the code it's intended behavior
2. With doesn't create a temporary object
Provably wrong with the fpc code creating a tempnode
3. it's not with that creates a temporary variable for the function result
Provably wrong from the fpc source
4. Every function call creates a tempnode it just happens that this is in with
Provably wrong as you don't understand the difference between the node tree and the assembly (btw. great look on your knowledge about compiler construction if you don't even know what an AST is)
5. Saying shadowing isn't a real term used
Provably wrong by it having even it's own wikipedia page
6. Saying no major compiler is using the term shadowing
Provably wrong by at least 5 major compilers I've found in 10 minutes using that term
7. With changes the return value of a function
Provably wrong, it changes a local copy of the return value of a function
8. The ide that "duplicate identifier" error only occurs when two identifiers are in the same scope
Provably wrong by counter example
9. The whole discussion on scope
Provably wrong by your own source

Even if you don't concede the last one, all of the ones above were just plainly and provably wrong claims by you about the function of the FPC compiler, the use of the term shadowing, the implementation of with, etc. Pretty much every single post of you at least in the first half of this thread contained at least one provably wrong statement.
Title: Re: Why with allows assignment
Post by: Martin_fr on January 08, 2025, 03:52:28 pm
2. With doesn't create a temporary object
Provably wrong with the fpc code creating a tempnode

Now I am intrigued. Is that so? (And I don't actually know, so that is an open question).
But, I suspect it may not... Yet, it may even depend on some unclear parts in that statement.


First of all, it should be easy for you to show, that this is the case with the FPC compiler (at a version of your choice, with settings of your choice).
- Provide the Pascal code for which this happens
- Provide the fpc version and command line args
- Provide the generate asm with register allocation  -alr



Anyway, before you do, lets talk about what constitutes "WITH creates a temp var" => How to we determine that the temp var (if there is one) was created by WITH?

Let me explain in an example:
Code: Pascal  [Select][+][-]
  1. function Foo: record_or_classInstance;

If you call
Code: Pascal  [Select][+][-]
  1. MyVar := Foo;
then there may or may not be a temp var. If there is, then we would say that for such a case the temp var is created for the call to foo, and when doing "with Foo do" such a temp var would still be for the call, and not for the "with". Agreed?

But then
Code: Pascal  [Select][+][-]
  1. Foo().b := 1; {or} if foo().b = 1 then

There is no "with" but if that creates a temp var, then that means a temp var was created for operating on the result of the call to foo.
From that again we should IMHO then say that "with foo do" is just operating on the result, and that therefore it is not the "with" but the  for operating on the result that creates the temp var. Still agreed?

If not agreed, then include an explanation as to why not.

Then based on that, show the code as asked above. Thanks.


---
Also, a register (from which a value is used) is as much a temp var, as a slot allocated on the stack. The diff is a matter of optimization. Both are a place of storage for the value.
Title: Re: Why with allows assignment
Post by: 440bx on January 08, 2025, 03:56:36 pm
Do you really believe your logical fallacies of not knowing the difference between implication and equivalence get less wrong when I read them again?

Like all claims you made were provably wrong
As I previously stated: Do the world a favor, stop posting your garbage.
Title: Re: Why with allows assignment
Post by: lainz on January 08, 2025, 04:01:54 pm
But then
Code: Pascal  [Select][+][-]
  1. Foo().b := 1; {or} if foo().b = 1 then

There is no "with" but if that creates a temp var, then that means a temp var was created for operating on the result of the call to foo.
From that again we should IMHO then say that "with foo do" is just operating on the result, and that therefore it is not the "with" but the  for operating on the result that creates the temp var. Still agreed?

Hi, good explanation, that can apply to this the same?

Code: Pascal  [Select][+][-]
  1. with TStringList.Create do
  2. begin
  3.   Free;
  4. end;

Because I don't know really...

I think is the same, because TStringList.Create created the variable, and with only operates that..

Edit: also in my previous example
https://forum.lazarus.freepascal.org/index.php/topic,69755.msg542669.html#msg542669

It has sense now, because it doesn't create the variable, but uses it, so the values are no stepped on..
Title: Re: Why with allows assignment
Post by: Warfley on January 08, 2025, 04:36:56 pm
Now I am intrigued. Is that so? (And I don't actually know, so that is an open question).
But, I suspect it may not... Yet, it may even depend on some unclear parts in that statement.


First of all, it should be easy for you to show, that this is the case with the FPC compiler (at a version of your choice, with settings of your choice).
- Provide the Pascal code for which this happens
- Provide the fpc version and command line args
- Provide the generate asm with register allocation  -alr

It's in the node tree. Looking at the fpc source:
Code: Pascal  [Select][+][-]
  1.             if (hp.nodetype=loadn) and
  2.                (
  3.                 (tloadnode(hp).symtable=current_procinfo.procdef.localst) or
  4.                 (tloadnode(hp).symtable=current_procinfo.procdef.parast) or
  5.                 (tloadnode(hp).symtable.symtabletype in [staticsymtable,globalsymtable])
  6.                ) and
  7.                { MacPas objects are mapped to classes, and the MacPas compilers
  8.                  interpret with-statements with MacPas objects the same way
  9.                  as records (the object referenced by the with-statement
  10.                  must remain constant)
  11.                }
  12.                not(is_class(hp.resultdef) and
  13.                    (m_mac in current_settings.modeswitches)) then
  14.               begin
  15.                 { simple load, we can reference direct }
  16.                 refnode:=p;
  17.               end
  18.             else
  19.               begin
  20.                 { complex load, load in temp first }
  21.                 newblock:=internalstatements(newstatement);
  22.                 { when we can't take the address of p, load it in a temp }
  23.                 { since we may need its address later on                 }
  24.                 if not valid_for_addr(p,false) then
  25.                   begin
  26.                     calltempnode:=ctempcreatenode.create(p.resultdef,p.resultdef.size,tt_persistent,true);
  27.                     addstatement(newstatement,calltempnode);
  28.                     addstatement(newstatement,cassignmentnode.create(
  29.                         ctemprefnode.create(calltempnode),
  30.                         p));
  31.                     p:=ctemprefnode.create(calltempnode);
  32.                     typecheckpass(p);
  33.                   end;
  34.  

So the with statement distinguishes between two cases. First with over some expression which is just a load of an existing object, so some existing memory address. In this case with is literally just a macro for the load of this object (refnode:=p). If it's not, it is checked if the object returned by the expression can be taken the address from, i.e. if the object has a persistent memory location. If that is also not the case, a new tempnode will be created (which can be either a memory location or a register), and the result of the expression is written into it.

You can see that when compiling the following test program:
Code: Pascal  [Select][+][-]
  1. type
  2.   TTest = record
  3.     i: Integer;
  4.   end;
  5.  
  6. function Foo: TTest;
  7. begin
  8. end;
  9.  
  10. begin
  11.   With Foo do
  12.     i:=42;
  13. end.

With the switch -vp to generate the tree.log, which prints the internal node tree. Looking at the nodes right before the code generation step (so after most optimizations):
Code: Pascal  [Select][+][-]
  1.          (statementn, resultdef = <nil>, pos = (11,12), loc = LOC_INVALID, expectloc = LOC_VOID, flags = [], cmplx = 255
  2.             (tempcreaten, resultdef = $void = "untyped", pos = (11,12), loc = LOC_INVALID, expectloc = LOC_VOID, flags = [nf_pass1_done], cmplx = 0
  3.                size = 2, temptypedef = TTest = "<record type>", tempinfo = $3F9BB6E8
  4.                [ti_addr_taken]
  5.                tempinit =
  6.                nil
  7.             )
  8.  
  9.          )
  10.          (statementn, resultdef = <nil>, pos = (11,12), loc = LOC_INVALID, expectloc = LOC_VOID, flags = [], cmplx = 255
  11.             (assignn, resultdef = $void = "untyped", pos = (11,12), loc = LOC_INVALID, expectloc = LOC_VOID, flags = [nf_pass1_done], cmplx = 255
  12.                (temprefn, resultdef = TTest = "<record type>", pos = (11,12), loc = LOC_INVALID, expectloc = LOC_REF, flags = [nf_pass1_done,nf_write], cmplx = 1
  13.                   temptypedef = TTest = "<record type>", (tempinfo = $3F9BB6E8 flags = [ti_addr_taken])
  14.                )
  15.                (calln, resultdef = TTest = "<record type>", pos = (11,8), loc = LOC_INVALID, expectloc = LOC_REG, flags = [nf_pass1_done], cmplx = 255
  16.                   proc = Foo:<record type>;
  17.                )
  18.             )
  19.  
  20.          )
We see first a tempcreatenode which creates a new temporary object of type 'TTest = "<record type>"' and it is later assigned the resullt of the call to the function Foo.
Infact the fpc generates two temporaries, because the next part in the node tree is:
Code: Pascal  [Select][+][-]
  1.             (tempcreaten, resultdef = $void = "untyped", pos = (11,12), loc = LOC_INVALID, expectloc = LOC_VOID, flags = [nf_pass1_done], cmplx = 0
  2.                size = 4, temptypedef = <no type symbol> = "^TTest", tempinfo = $3F9BB7A8
  3.                [ti_may_be_in_reg]
  4.                tempinit =
  5.                nil
  6.             )
  7.  
  8.          )
  9.          (statementn, resultdef = <nil>, pos = (11,12), loc = LOC_INVALID, expectloc = LOC_VOID, flags = [], cmplx = 4
  10.             (assignn, resultdef = $void = "untyped", pos = (11,12), loc = LOC_INVALID, expectloc = LOC_VOID, flags = [nf_pass1_done], cmplx = 1
  11.                (temprefn, resultdef = <no type symbol> = "^TTest", pos = (11,12), loc = LOC_INVALID, expectloc = LOC_CREG, flags = [nf_pass1_done,nf_write], cmplx = 1
  12.                   temptypedef = <no type symbol> = "^TTest", (tempinfo = $3F9BB7A8 flags = [ti_may_be_in_reg])
  13.                )
  14.                (addrn, resultdef = <no type symbol> = "^TTest", pos = (11,12), loc = LOC_INVALID, expectloc = LOC_REG, flags = [nf_pass1_done,nf_internal], cmplx = 1, addrnodeflags = [anf_typedaddr]
  15.                   (temprefn, resultdef = TTest = "<record type>", pos = (11,12), loc = LOC_INVALID, expectloc = LOC_REF, flags = [nf_pass1_done,nf_address_taken], cmplx = 1
  16.                      temptypedef = TTest = "<record type>", (tempinfo = $3F9BB6E8 flags = [ti_addr_taken])
  17.                   )
  18.                )
  19.             )
This temporary object then contains the address to the first temporary object, which is then used for the actual dereferencing in the with statement.

The main point here is that in order to not having to re evaluate the with expression on each access, if it is not a memory object (but the result of a expression) a new temporary will be created and the result of the expression is loaded into that temporary.
Title: Re: Why with allows assignment
Post by: Martin_fr on January 08, 2025, 05:04:33 pm
Hi, good explanation, that can apply to this the same?

Code: Pascal  [Select][+][-]
  1. with TStringList.Create do
  2. begin
  3.   Free;
  4. end;

Is the same as:
Code: Pascal  [Select][+][-]
  1. TStringList.Create.Free
So: Yes.

Of course without "with", that temp var can only be used for one further statement.





It's in the node tree. Looking at the fpc source:

There is a fundamental difference between. "With creates the temp variable" and "The compilers implementation for with has code that creates a temp variable."

I am sure that, if you go through the code of the compiler you can find a lot of things that are done within code for something else.

In that case, "with" is one of many ways to operate on the result of a function call. So (a copy of) code that is needed for that is included in the implementation of "with".

Reminder, your statement was
2. With doesn't create a temporary object
Provably wrong with the fpc code creating a tempnode

You did not say "the fpc compiler implements a copy of the code for operate on the result in the code handling with".

You said, that it was the "with" itself that creates the temp variable.


I will give you that, using "with" probably always constituted operating on the result. Therefore using "with" will always include all the effects that operating on the result has. But afaik, it is operating on the result that creates the temp var.



If I said "rounding a floating number creates a new stackframe" most people would (rightly) say that I am wrong.

Of course
Code: Pascal  [Select][+][-]
  1. b := round(a);
calls a function, and calling a function creates a stackframe. But that doesn't mean that the statement above is suddenly correct.

The statement tells a partial true, in such a way that the reader will be led to wrong conclusions. That makes it a false statement.

==> Just to say, if round ends up an intrinsic => chose any other function that actually gets called.
Title: Re: Why with allows assignment
Post by: BrunoK on January 08, 2025, 05:21:52 pm
Given the following program :
Code: Pascal  [Select][+][-]
  1. program Project1;
  2.  
  3. { Int64 to demonstrate that the returned record is effectively copied to
  4.   the local stack space }
  5. type
  6.   RTest = record
  7.     V0: int64;
  8.     V1: int64;
  9.   end;
  10.   PRtest = ^RTest;
  11.  
  12.   { TTest }
  13.  
  14.   TTest = class
  15.     V0: int64;
  16.     V1: int64;
  17.     function GetSelfPtr: TTest;
  18.     property SelfInst: TTest read GetSelfPtr;
  19.     constructor Create;
  20.   end;
  21.  
  22.   function FooRec: RTest; forward;
  23.   function FooInst: TTest; forward;
  24.  
  25. var
  26.   vRec: RTest;
  27.   vInst: TTest = nil;
  28.  
  29.   function FooRec: RTest;
  30.   begin
  31.     Result := vRec;
  32.   end;
  33.  
  34.   function FooInst: TTest;
  35.   begin
  36.     if not Assigned(vInst) then
  37.       vInst := TTest.Create;
  38.     Result := vInst;
  39.   end;
  40.  
  41.   { TTest }
  42.  
  43.   function TTest.GetSelfPtr: TTest;
  44.   begin
  45.     Result := Self;
  46.   end;
  47.  
  48.   constructor TTest.Create;
  49.   begin
  50.     V0 := -11;
  51.     V1 := -11;
  52.   end;
  53.  
  54. begin
  55.   { Init record vRec }
  56.   with vRec do begin
  57.     V0 := -1;
  58.     V1 := -2;
  59.   end;
  60.  
  61.   { Where program variables are stored }
  62.  
  63.   WriteLn('With Record', LineEnding, '===========');
  64.   WriteLn(HexStr(UIntPtr(@vRec), SizeOf(Pointer) * 2), ' ', vRec.V0, ' ', vRec.V1);
  65.  
  66.   with FooRec do begin
  67.     // FooRec returns a pointer used to copy record to "with" cached record
  68.     V0 := 1;
  69.     v1 := 2;
  70.     WriteLn(V0, ' ', V1);
  71.     with PRtest(@V0)^ do
  72.       WriteLn(HexStr(UIntPtr(@V0), SizeOf(Pointer) * 2), ' ', V0, ' ', V1);
  73.   end;
  74.   { Show we didnt alter the record vRec }
  75.   with vRec do
  76.     WriteLn(HexStr(UIntPtr(@vRec.V0), SizeOf(Pointer) * 2), ' ', V0, ' ', V1);
  77.  
  78.   WriteLn('With Class instance',
  79.     LineEnding, '===================');
  80.   WriteLn(HexStr(UIntPtr(@vInst), SizeOf(Pointer) * 2), ' ',
  81.     HexStr(UIntPtr(@PPointer(vInst)^), SizeOf(Pointer) * 2));
  82.   with FooInst do begin
  83.     // FooRec returns a pointer used to copy record to "with" cached record
  84.     V0 := 11;
  85.     v1 := 12;
  86.     WriteLn(V0, ' ', V1);
  87.     with SelfInst do
  88.       WriteLn(HexStr(UIntPtr(SelfInst), SizeOf(Pointer) * 2), ' ', V0, ' ', V1);
  89.   end;
  90.   { Show we worked on the instance returned FooInst and altered vInst itsel via
  91.     the returned TObject }
  92.   with vInst do
  93.     WriteLn(HexStr(UIntPtr(vInst), SizeOf(Pointer) * 2), ' ',
  94.       HexStr(UIntPtr(PPointer(vInst)^), SizeOf(Pointer) * 2), ' ', V0, ' ', V1);
  95.  
  96.   ReadLn;
  97.  
  98.   vInst.Free;
  99. end.
Compiled for x86_64, O1:
At line 66 with FooRec do begin the compiler generates
000000010000186B 488D4DE0                 lea rcx,[rbp-$20]
000000010000186F E86CFDFFFF               call -$00000294    # $00000001000015E0 FooRec project1.lpr:30
0000000100001874 488B45E0                 mov rax,[rbp-$20]
0000000100001878 488945F0                 mov [rbp-$10],rax
000000010000187C 488B45E8                 mov rax,[rbp-$18]
0000000100001880 488945F8                 mov [rbp-$08],rax
that is, it calls the FooRec function that returns a pointer to the record which is then FULLY copied to the local stack (not a pointer !) because it cannot store it in a register. 

At line 82 with FooInst do begin the compiler generates :
0000000100001AF1 E81AFBFFFF               call -$000004E6    # $0000000100001610 FooInst project1.lpr:35
0000000100001AF6 4889C3                   mov rbx,rax
0000000100001AF9 4889DE                   mov rsi,rbx
Here the Instance address value is not stored on the stack because it can be kept (cached, or so I suppose) in a register (rsi in this case) for the scope of the with. Note that a object instance is just a pointer.

Title: Re: Why with allows assignment
Post by: Warfley on January 08, 2025, 05:34:07 pm
There is a fundamental difference between. "With creates the temp variable" and "The compilers implementation for with has code that creates a temp variable."
But the temporary variable is one of the key features of with. It is missing in the documentation of with (it currently only mentions variable expressions), but the fact that if the expression is non variable, a local copy of the result of that expression is created is really important to the semantics of with. It has two major effects:
1. The expression in with will always be evaluated, even if it is never used:
Code: Pascal  [Select][+][-]
  1. function Foo: TTest;
  2. begin
  3.   WriteLn('Foo');
  4. end;
  5.  
  6. begin
  7.   With Foo do
  8.     WriteLn('Bar');
  9. end.
Even though the result of Foo is never accessed in the with, it is still executed. Also through the temporary object it is guaranteed that Foo will only be executed exactly once, independently how many accesses there are to the result. The fact that with creates a local copy of any non variable statement, is an essential part of the with semantic. It's the reason why the with MyClass.Create do try ... finally Free end; works.
With over a complex term creates a temporary copy of the result of that term to operate on. With ensures that the with expression is a variable, if it already is, it does nothing if it is a complex expression it creates a new temporary variable to operate on
Title: Re: Why with allows assignment
Post by: Martin_fr on January 08, 2025, 05:47:44 pm
There is a fundamental difference between. "With creates the temp variable" and "The compilers implementation for with has code that creates a temp variable."
But the temporary variable is one of the key features of with. It is missing in the documentation of with (it currently only mentions variable expressions), but the fact that if the expression is non variable, a local copy of the result of that expression is created is really important to the semantics of with. It has two major effects:
1. The expression in with will always be evaluated, even if it is never used:

The compiler could guarantee the "always eval", even without storing the result. (Still the difference between "with creates" and "the compiler does in its current implementation" => you said the former, but then tried to argue with the latter)

So, if we had
Code: Pascal  [Select][+][-]
  1. with foo() do {empty};
  2. with foo() do begin {empty} end;

Then for the correct functionality of "with" the temp var is not needed. If that optimization is lacking from the current implementation, then that still only allows a statement on the "implementation of with", but does not make it true to say that "with creates a temp var".



Besides, you did not show the complete code of the compiler. You showed code that creates a node for that temp var, but what about any code that later removes it (or the effects of it) again? So that looking at the compiler as a whole at the end of generating code for "with" there may not ("not always") be a temp var?

Feels like cheating? Because any optimizer business is a general business and not related to to "with" in particular? Well, its in line of saying the general "operating on the result" is not general when it happens within a "with" block. Or is it not?
Title: Re: Why with allows assignment
Post by: Warfley on January 08, 2025, 10:37:18 pm
Besides, you did not show the complete code of the compiler. You showed code that creates a node for that temp var, but what about any code that later removes it (or the effects of it) again? So that looking at the compiler as a whole at the end of generating code for "with" there may not ("not always") be a temp var?

Feels like cheating? Because any optimizer business is a general business and not related to to "with" in particular? Well, its in line of saying the general "operating on the result" is not general when it happens within a "with" block. Or is it not?
Well then the question becomes what is a temporary object? Some memory object that's in the assembly? Does a register count? This discussion gets quite philosophical. So why not look at the context of my posts and what I actually described when saying this.
Well my posts only ever concerned the internal representation in the FPC, so in the node tree which represents the semantic of the program. With "object" I specifically meant an persistent data representation in the node tree (as I clarified multiple times in this thread already).

Because optimizations are just semantic preserving transformations, but the semantic is defined in the node tree. So if the node tree creates a temporary object it means semantically there is this object. Then optimizations can decide if the same semantic can be preserved by removing that object later on.
But that's why there is the distinction between the Frontend, which encodes the semantic of a program in the AST (node tree), and the backend which then translates this semantic into machine vode
Title: Re: Why with allows assignment
Post by: TBMan on January 09, 2025, 01:25:39 am
The syntax is a little crazy. I'd do this instead

Type

Point_type = record
   X, y: integer;
End;

Var
Point: point_type;

Function test(a,b:integer):integer;
Begin
     Test := a+b;
End;

Begin
With point do
 Begin
     x := test(100,5);
    y := test(5,5);
 End;
End.

You have to code things in a way to improve readabilty so if you
have to go back a week, or a month later you won't be staring at
headlights like a deer.
Title: Re: Why with allows assignment
Post by: jamie on January 09, 2025, 01:39:20 am
its handy when you want to set a bunch of properties deep within a class, for example the brush values of a canvas which lives in a form or some graphics control.

Example
 
Code: Pascal  [Select][+][-]
  1.  
  2.  With Form1.Canvas.Brush do
  3.  Begin
  4.    style =...
  5.    color =....
  6. end;
  7.  
Title: Re: Why with allows assignment
Post by: PascalDragon on January 09, 2025, 10:05:51 pm
Code: Pascal  [Select][+][-]
  1. program Project1;
  2.  
  3. uses
  4.  Classes, SysUtils;
  5.  
  6. type
  7.  
  8.   TRecAB = record
  9.     A: integer;
  10.     B: integer;
  11.   end;
  12.  
  13.   { TMyObj }
  14.  
  15.   TMyObj = class
  16.   private
  17.     TRecAB: TRecAB;
  18.   public
  19.     property rec: TRecAB read TRecAB write TRecAB;
  20.   end;
  21.  
  22. var
  23.   Obj: TMyObj;
  24.  
  25. begin
  26.   Obj := TMyObj.Create;
  27.  
  28.   with Obj.rec do
  29.   begin
  30.     A := 10;
  31.     B := 20;
  32.   end;
  33.  
  34.   with Obj.rec do
  35.   begin
  36.     A := 15;
  37.   end;
  38.  
  39.   writeln(Obj.rec.A);
  40.   writeln(Obj.rec.B);
  41.   readln;
  42.  
  43. end.

A is 15 and B is 20 at the end. So it works fine for me...

Please note that this only works as long as the property uses field getter and setter. It will fail as soon as the getter is also a method. And how the property is implemented is not always in control of the developer (e.g. when using third party code). Which is another reason why Delphi decided to forbid this and FPC will follow suit sooner or later.

that FPC allows assigning a value to a function _outside_ the function itself, must be a "misnomer" too.
if you mean the OP program (revised) :
Code: Pascal  [Select][+][-]
  1. program Project1;
  2.  
  3. const
  4.   HighInt64 = int64(int64(-1) shr 1); // Typed const
  5.  
  6. type
  7.   TTest = record
  8.     V: int64;
  9.   end;
  10.  
  11. var
  12.   X: TTest;
  13.  
  14.   function Test: TTest;
  15.   begin
  16.     Result := X;
  17.   end;
  18.  
  19. var
  20.   i: qword;
  21. begin
  22.   with Test do begin
  23.     V := HighInt64; //No Error
  24.     WriteLn(X.V, ' ', {With Test.}V);
  25.   end;
  26.   Readln;
  27. end.
There is no assignment of a function outside it. The assignment is to the temporary TTest record returned by the Test function call. As my little snippet of code shows, the X.V record field is not changed, only the returned temporary record is altered to HighInt64.
Wrong and unacceptable!  The temporary represents the result of the function therefore it must be treated exactly the same as the result of the function.

The _correct_ way to do what that code atrocity above is doing is to assign the function result to a variable and then change the variable but, of course, that's too much work and worse, it is correct, we can't possibly have that.  In the code you present, V represents the result of the function therefore it MUST be treated as the function's result.

Assign a value to function results outside the function ?... no problem... assign values to constants ?... no problem...   it's all great as long as it's convenient... that's what really matters... correctness ?.... nah... who could possibly need that ?  (just in case, rhetorical.)

That's the whole original point of this thread: Delphi nowadays forbids changing fields of a record that is a return value of a function to matter if with is used or not. Older Delphi versions did not, just like older FPC versions or TP did not. FPC nowadays forbids it directly for the function result, but not yet if the function result is captured in a with-statement. But that is only because that is simply not implemented in FPC, because it requires explicit code to forbid this and doesn't stem from the language itself.

The syntax is a little crazy. I'd do this instead

Type

Point_type = record
   X, y: integer;
End;

Var
Point: point_type;

Function test(a,b:integer):integer;
Begin
     Test := a+b;
End;

Begin
With point do
 Begin
     x := test(100,5);
    y := test(5,5);
 End;
End.

You have to code things in a way to improve readabilty so if you
have to go back a week, or a month later you won't be staring at
headlights like a deer.

Speaking of readability: please use [code=pascal][/code] to improve readabililty and to avoid the forum software interpreting the code.
Title: Re: Why with allows assignment
Post by: 440bx on January 10, 2025, 12:11:04 am
Delphi nowadays forbids changing fields of a record that is a return value of a function to matter if with is used or not. Older Delphi versions did not, just like older FPC versions or TP did not. FPC nowadays forbids it directly for the function result, but not yet if the function result is captured in a with-statement. But that is only because that is simply not implemented in FPC, because it requires explicit code to forbid this and doesn't stem from the language itself.
I just want to make sure I am interpreting the above corretly.

Does the above mean that FPC will "eventually" forbid "changing fields of a record that is a return value of a function to matter if with is used or not." ?

A "yes" answer would definitely be encouraging.
Title: Re: Why with allows assignment
Post by: egsuh on January 10, 2025, 03:38:01 am
Anyway whether to use "with" or not is programmers' choice, while directly changing record-type function result had better be avoided.
Title: Re: Why with allows assignment
Post by: egsuh on January 11, 2025, 06:54:15 am
I have no intention of re-igniting debates here, but I made a simple test, both with record and object. Both seem to work correctly.

Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4. {$modeSwitch advancedRecords}
  5.  
  6. interface
  7.  
  8. uses
  9.   Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls;
  10.  
  11. type
  12.  
  13.   { RRec }
  14.  
  15.   RRec = record
  16.   private
  17.     function getString: string;
  18.     procedure setString(AValue: string);
  19.   public
  20.     min, max : integer;
  21.     property AsString: string read getString write setString;
  22.   end;
  23.  
  24.   TRec = object
  25.   private
  26.     function getString: string;
  27.     procedure setString(AValue: string);
  28.   public
  29.     min, max : integer;
  30.     property AsString: string read getString write setString;
  31.   end;
  32.  
  33.   { TForm1 }
  34.  
  35.   TForm1 = class(TForm)
  36.     Button1: TButton;
  37.     Button2: TButton;
  38.     procedure Button1Click(Sender: TObject);
  39.     procedure Button2Click(Sender: TObject);
  40.   private
  41.  
  42.   public
  43.  
  44.   end;
  45.  
  46. var
  47.   Form1: TForm1;
  48.  
  49. function GetRange : RRec;
  50. function GetRangeObj: TRec;
  51.  
  52. implementation
  53.  
  54. function GetRange: RRec;
  55. begin
  56.  
  57. end;
  58.  
  59. function GetRangeObj: TRec;
  60. begin
  61.  
  62. end;
  63.  
  64. {$R *.lfm}
  65.  
  66. { RRec }
  67.  
  68. function RRec.getString: string;
  69. begin
  70.   Result := Format('%d..%d', [min, max]);
  71. end;
  72.  
  73. procedure RRec.setString(AValue: string);
  74. var
  75.   tempres : TStringArray;
  76. begin
  77.   tempres := AValue.Split(['..']);
  78.   min := StrToInt(tempres[0]);
  79.   max := StrToInt(tempres[1]);
  80. end;
  81.  
  82. { RRec }
  83.  
  84. function TRec.getString: string;
  85. begin
  86.   Result := Format('%d..%d', [min, max]);
  87. end;
  88.  
  89. procedure TRec.setString(AValue: string);
  90. var
  91.   tempres : TStringArray;
  92. begin
  93.   tempres := AValue.Split(['..']);
  94.   min := StrToInt(tempres[0]);
  95.   max := StrToInt(tempres[1]);
  96. end;
  97.  
  98. { TForm1 }
  99.  
  100. procedure TForm1.Button1Click(Sender: TObject);
  101. begin
  102.    with GetRange do begin
  103.       AsString := '12..16';
  104.       ShowMessage(IntToStr(Min));
  105.    end;
  106. end;
  107.  
  108. procedure TForm1.Button2Click(Sender: TObject);
  109. begin
  110.   with GetRangeObj do begin
  111.      AsString := '12..16';
  112.      ShowMessage(IntToStr(Min));
  113.   end;
  114. end;
  115.  
  116. end.
  117.  

I have no interest at all in what compilers do nor what is the philosopy under the programming language development.

The difference between defining a variable (I can write as follows) and calling function is that variable may last long after their values are not needed anymore. In that sense, I think calling function with "with" whose result I can operate on is quite useful feature.

          var
               ARange: RRec;
          begin
               with ARange do begin
                      ....
               end;
          end; 
Title: Re: Why with allows assignment
Post by: 440bx on January 11, 2025, 07:36:12 am
I think calling function with "with" whose result I can operate on is quite useful feature.
Writable constants are useful too.   Now, we have a language that allows assigning to a function result outside the function, it's useful, therefore... no problem!.

OTH, @PascalDragon's last post gives the impression that this "useful" feature may not be around in the future.  Of course, he is the only one who can confirm that but, if the feature isn't going to be around in the future, that really makes it "less useful".

Title: Re: Why with allows assignment
Post by: PascalDragon on January 11, 2025, 03:46:47 pm
Delphi nowadays forbids changing fields of a record that is a return value of a function to matter if with is used or not. Older Delphi versions did not, just like older FPC versions or TP did not. FPC nowadays forbids it directly for the function result, but not yet if the function result is captured in a with-statement. But that is only because that is simply not implemented in FPC, because it requires explicit code to forbid this and doesn't stem from the language itself.
I just want to make sure I am interpreting the above corretly.

Does the above mean that FPC will "eventually" forbid "changing fields of a record that is a return value of a function to matter if with is used or not." ?

A "yes" answer would definitely be encouraging.

The answer is indeed yes. When is a different question however. 😅

I have no intention of re-igniting debates here, but I made a simple test, both with record and object. Both seem to work correctly.

This will stop working in the future, because the value returned by the functions (in your case GetRange and GetRangeObj) will only be temporary and won't propagate back to the original memory. So in that case one needs to explicitly capture the returned value in a variable and then use with. Users using this accidentally or wondering why it doesn't work the way they expect outweigh useful cases like this.
Title: Re: Why with allows assignment
Post by: dseligo on January 11, 2025, 04:00:25 pm
This will stop working in the future, because the value returned by the functions (in your case GetRange and GetRangeObj) will only be temporary and won't propagate back to the original memory. So in that case one needs to explicitly capture the returned value in a variable and then use with. Users using this accidentally or wondering why it doesn't work the way they expect outweigh useful cases like this.

Will this also stop working in the future?

Code: Pascal  [Select][+][-]
  1.   with TfrmVIES.Create(nil) do
  2.   try
  3.     ShowModal;
  4.   finally
  5.     Release;
  6.   end;
Title: Re: Why with allows assignment
Post by: jamie on January 11, 2025, 05:04:25 pm
Have you ever heard the term "Bricking" / "Brick" ?

Jamie
Title: Re: Why with allows assignment
Post by: BrunoK on January 11, 2025, 05:23:40 pm
Have you ever heard the term "Bricking" / "Brick" ?

Jamie
You mean as in https://en.wikipedia.org/wiki/Brick_(electronics) (https://en.wikipedia.org/wiki/Brick_(electronics)) ?

If that's so, we need to make sure to never update to their trunk fantasies.

They are not even able to correctly multiply 2 currency values togehter correctly ...
Title: Re: Why with allows assignment
Post by: TBMan on January 11, 2025, 05:49:06 pm

Speaking of readability: please use [code=pascal][/code] to improve readabililty and to avoid the forum software interpreting the code.

The forum software is a compiler that will run code in a post?
Title: Re: Why with allows assignment
Post by: 440bx on January 11, 2025, 06:24:29 pm
The answer is indeed yes. When is a different question however. 😅
It's good to know assigning a result to a function outside the function itself will eventually be disallowed.

Of course, the sooner the better but, as you said, that's a different issue/question.
Title: Re: Why with allows assignment
Post by: jamie on January 11, 2025, 06:44:41 pm
Have you ever heard the term "Bricking" / "Brick" ?

Jamie
You mean as in https://en.wikipedia.org/wiki/Brick_(electronics) (https://en.wikipedia.org/wiki/Brick_(electronics)) ?

If that's so, we need to make sure to never update to their trunk fantasies.

They are not even able to correctly multiply 2 currency values togehter correctly ...
Some people aren't happy unless they can break something. With changes comes unintended behaviors and years to fix.

 You have some old timers that would rather copy and paste the same line over and over to get to multiple deep properties, it boosts their ego of total size of source files they create in a project.

Jamie


Title: Re: Why with allows assignment
Post by: dbannon on January 12, 2025, 01:12:44 am
PascalDragon, I confess I have not followed this thread closely because of the unpleasantness exhibited. But I do use TestOne below and would like you to assure me it is future safe.

Code: Pascal  [Select][+][-]
  1. program Project1;
  2. {$mode objfpc}
  3.  
  4. type
  5.     TaRec = record
  6.         TheInt : integer;
  7.         TheString : string;
  8.     end;
  9.      
  10. function Test() : TaRec;
  11. begin
  12.     result.TheInt := 42;
  13.     result.TheString := 'FortyTwo';
  14. end;
  15.    
  16. var
  17.         aRec : TaRec;
  18. begin
  19.     aRec := Test();          // Test One
  20.     WriteLn('Test One TheInt ', aRec.TheInt);
  21.     writeln('Test One TheString ', aRec.TheString);
  22.  
  23.     with Test() do begin     // Test Two
  24.         WriteLn('Test Two TheInt ', TheInt);
  25.         writeln('Test Two TheString ', TheString);
  26.     end;
  27. end.

Thanks,
Davo
Title: Re: Why with allows assignment
Post by: 440bx on January 12, 2025, 02:37:18 am
@dbannon,

I know you didn't ask me but, Test One should most definitely keep working.  There is no reason why it should not.

I believe Test Two will keep working as well but, for that one I believe the documentation should be very detailed as to _how_ it works.  Any details PascalDragon can offer about how it works would definitely be most welcome.

What will eventually stop working, which currently works, is to assign a value _in the scope of the "with"_ to the function's result.  That will eventually not work (PascalDragon stated that much in his posts above.)

ETA:

Added the qualifier "in the scope of the "with""
Title: Re: Why with allows assignment
Post by: jamie on January 12, 2025, 02:41:29 am
yeah well, I have a rather large app that I was able to get fpc to compile that uses lots of code like this.

Code: Pascal  [Select][+][-]
  1.  
  2.   with GetNodeByIndex(1{one for now}) do
  3.   begin
  4.     NormalizeValues;
  5.     if X = -1 then X := Adefault; //Constant for now but changes.
  6.     if Y = -1 then Y := ADefault; //Place Null nodes to a common place.
  7.     if Z = -1 then Z := ADefault;
  8.     ProcessNode;
  9.     // more code after this.
  10.   end;                                              
  11.  
  12.  

That function GetNodeByIndex returns a Record, in Delphi mode to get advanced records and as you can see, I have procedures, and I do change the values within.
That does not mean they get changed in the original source, they don't.

 But it appears to be too much for some to handle, what a shame, or should I say sham.

Jamie
Title: Re: Why with allows assignment
Post by: 440bx on January 12, 2025, 02:53:17 am
That does not mean they get changed in the original source, they don't.
Semantically, that code you posted shows that the values returned by the function are being changed.  That's what that code means and that is not only wrong, it is non-sensical not to mention mathematically appalling.

You want to do that kind of stuff then do it right, assign the function's result to a temporary variable.  Since the temporary variable is NOT the function's result (only a copy of its values) you can do whatever you want with it.

Declaring a variable to hold a function's result isn't a big deal nor a whole lot of work and, the explicit assignment to the temporary makes the code's intent explicit and thus a lot clearer.

Title: Re: Why with allows assignment
Post by: jamie on January 12, 2025, 03:37:29 pm
The blind leading the blind. The destroyer of clean and easy syntax and bloat generator.

Have a good day, I've had enough of this non-sense.

Jamie
Title: Re: Why with allows assignment
Post by: alpine on January 12, 2025, 04:04:22 pm
The blind leading the blind. The destroyer of clean and easy syntax and bloat generator.
+1

I don't see anything wrong with functions returning a temporary record result, the case when it is a property accessor maybe is the only case in which assigning the field doesn't bring the desired side effect. But then that's what functions are for - (ideally) to be side-effect free. They (Delphi) could have made it a warning instead of an error, or at least checked for the particular case. They chose the easy way.
Title: Re: Why with allows assignment
Post by: 440bx on January 12, 2025, 06:08:39 pm
The blind leading the blind. The destroyer of clean and easy syntax and bloat generator.

Have a good day, I've had enough of this non-sense.

Jamie
It's more like "the blind ignorants are whining about the future loss of their defective toy".... awwww... poor things!

Have all a wonderful day... enjoy your non-sense and ignorance, don't forget to feel miserable about the future loss of your toy.  :D



Title: Re: Why with allows assignment
Post by: jamie on January 12, 2025, 06:17:47 pm
neo luddism.
Title: Re: Why with allows assignment
Post by: Martin_fr on January 12, 2025, 06:30:31 pm
Does the above mean that FPC will "eventually" forbid "changing fields of a record that is a return value of a function to matter if with is used or not." ?

The answer is indeed yes. When is a different question however. 😅

Out of interest:
Is that "forbid direct assignment" or "forbid changing fields of ..."? Or where (and maybe how) will that line be drawn.

Because, of course read access remains allowed.
Code: Pascal  [Select][+][-]
  1. if GetPoint().x > 1 then
is valid.

But then, there are typeHelpers (and advanced records.
Code: Pascal  [Select][+][-]
  1. if MyFuncForRecord.MethodOfRecordOrHelper(1) > 1 then
is that valid?

Because such a method could modify the fields in the record. But it could also be something like "toString" which prints the record into a string, and surely should be allowed.



Of course, if such method calls are allowed, then the original issue remains

Code: Pascal  [Select][+][-]
  1. MyObject.PropertyWithGetterForSomeTPoint().SetX(42);

has exactly the same potential of confusion as
Code: Pascal  [Select][+][-]
  1. MyObject.PropertyWithGetterForSomeTPoint().X := 42;
Title: Re: Why with allows assignment
Post by: 440bx on January 12, 2025, 06:51:41 pm
Of course, if such method calls are allowed, then the original issue remains

Code: Pascal  [Select][+][-]
  1. MyObject.PropertyWithGetterForSomeTPoint().SetX(42);

has exactly the same potential of confusion as
Code: Pascal  [Select][+][-]
  1. MyObject.PropertyWithGetterForSomeTPoint().X := 42;
I just want to make sure I understand the pair of code snippets above correctly.  In the first case, SetX invokes some code that changes the value of a field, obviously that is perfectly fine since no function result is being modified.

If the second piece of code is really/fully equivalent then "X" is not part of a function's result and, if it isn't then it's fine.

Did I interpret the code you showed correctly ?
Title: Re: Why with allows assignment
Post by: Martin_fr on January 12, 2025, 07:53:10 pm
Of course, if such method calls are allowed, then the original issue remains

Code: Pascal  [Select][+][-]
  1. MyObject.PropertyWithGetterForSomeTPoint().SetX(42);

has exactly the same potential of confusion as
Code: Pascal  [Select][+][-]
  1. MyObject.PropertyWithGetterForSomeTPoint().X := 42;
I just want to make sure I understand the pair of code snippets above correctly.  In the first case, SetX invokes some code that changes the value of a field, obviously that is perfectly fine since no function result is being modified.

If the second piece of code is really/fully equivalent then "X" is not part of a function's result and, if it isn't then it's fine.

Did I interpret the code you showed correctly ?

"SetX(1)" modifies the X field in the temporary storage of the function result.

In both cases the function returns a TPoint (record), and in both cases, the result is held in a temp var.


The 2nd example obviously already does not compile. Because it has no "with".
But the same examples can be done using a "with" block. And within such a with block you should (afaik) be allowed to
- read fields of the temp var holding the function results
- call methods or helper methods (like "toString")
Title: Re: Why with allows assignment
Post by: BrunoK on January 12, 2025, 09:26:15 pm
neo luddism.
+++ You're quite in an accurate sarcasm vein lately ...
Title: Re: Why with allows assignment
Post by: LV on January 12, 2025, 11:01:40 pm
Code: Pascal  [Select][+][-]
  1.   Test.V := 1; //Error: Argument cannot be assigned to
  2.  

And on the same topic, is there a way in Pascal that I can return a record by the reference so any change to the result, changes the source?
For example changing V changes the original X.
I know I can return the pointer, but is there any other way?

The answer is no without using a pointer or reference type object.
Use a class. Class is inherently a reference type. Changes to its fields affect the original object:

Code: Pascal  [Select][+][-]
  1. program Project1;
  2. type
  3.   TTest = class
  4.     V: Integer;
  5.   end;
  6.  
  7. var
  8.   X: TTest;
  9.  
  10. function Test: TTest;
  11. begin
  12.   Result := X; // Return the reference to the object
  13. end;
  14.  
  15. begin
  16.   X := TTest.Create;
  17.   Test.V := 1; // Directly modifies X.V
  18. end.  
  19.  
Title: Re: Why with allows assignment
Post by: Warfley on January 13, 2025, 01:12:14 am
If the second piece of code is really/fully equivalent then "X" is not part of a function's result and, if it isn't then it's fine.

Did I interpret the code you showed correctly ?

See the following full code example: https://godbolt.org/z/6eTnofqee
Code: Pascal  [Select][+][-]
  1. {$Mode ObjFPC}
  2. {$ModeSwitch TypeHelpers}
  3.  
  4. type
  5.   TMyRec = record
  6.     X: Integer;
  7.   end;
  8.  
  9.   TMyHelper = type helper for TMyRec
  10.     procedure SetX(const NewX: Integer); inline;
  11.   end;
  12.  
  13. procedure TMyHelper.SetX(const NewX: Integer);
  14. begin
  15.   Self.X := NewX;
  16. end;
  17.  
  18. function Test: TMyRec;
  19. begin
  20.  
  21. end;
  22.  
  23. procedure TestWithSetResult;
  24. begin
  25.   with Test do
  26.     X := 42;
  27. end;
  28.  
  29. procedure TestHelperSetResult;
  30. begin
  31.   Test.SetX(42);
  32. end;

Type Helpers are just syntactic sugar and because pascal does not have a concept of constness for method calls (unlike for example C++), if you look at the assembly generated:
Code: ASM  [Select][+][-]
  1. testwithsetresult():
  2.         leaq    -8(%rsp),%rsp
  3.         call    test()
  4.         movl    %eax,(%rsp)
  5.         movq    %rsp,%rax
  6.         movl    $42,(%rax)
  7.         leaq    8(%rsp),%rsp
  8.         ret
  9.  
  10. testhelpersetresult():
  11.         leaq    -8(%rsp),%rsp
  12.         call    test()
  13.         movl    %eax,(%rsp)
  14.         movq    %rsp,%rax
  15.         movl    $42,(%rax)
  16.         leaq    8(%rsp),%rsp
  17.         ret
The resulting assembly code is bitwise identical.

So there is absolutely nothing that not allowing to set the field of a function result (with or without with) would prevent that is not otherwise already possible.
Title: Re: Why with allows assignment
Post by: Martin_fr on January 13, 2025, 01:51:59 am
So there is absolutely nothing that not allowing to set the field of a function result (with or without with) would prevent that is not otherwise already possible.

Not the field of the original data. It was never my intend to say that. And I actually don't think I did say it.

You can however write to the temporary storage (the non-declared helper variable in which fpc holds the value for read access, until its out of scope).

As it was discussed, when using "with" this is currently allowed (despite it more often is done due to mislead ideas, rather than actually in fashion of a temp local var)
At least such misleading potential was stated, and given as reason for one day changing the "with" abilities.

I merely was curious, because I do not see how this could easily be extended to cover *all* possible cases. And how it would affect method calls that could still do exactly the behaviour that is said to be of such misleading nature.

All I wanted to know is, if there was any comment on that.

No judgment if the planned change is good or not, or anything else. Merely: is it known to be incomplete, or are there ways (that I can not see) to make it complete?


Somehow, the only replies I got where entirely offtopic to that question. :(
Title: Re: Why with allows assignment
Post by: 440bx on January 13, 2025, 03:02:26 am
Somehow, the only replies I got where entirely offtopic to that question. :(
My reply was more than anything an attempt to understand the behavior of the code you posted.

Your question is good because, as you pointed out, there are other ways to modify the function's result.
Title: Re: Why with allows assignment
Post by: Warfley on January 13, 2025, 01:27:42 pm
I merely was curious, because I do not see how this could easily be extended to cover *all* possible cases. And how it would affect method calls that could still do exactly the behaviour that is said to be of such misleading nature.

All I wanted to know is, if there was any comment on that.

No judgment if the planned change is good or not, or anything else. Merely: is it known to be incomplete, or are there ways (that I can not see) to make it complete?

I think it would be interesting to extend the concept of constness to methods, e.g. in C++ you can have the following:
Code: C  [Select][+][-]
  1. class MyClass {
  2.   ...
  3.   void myMethod() const {
  4.     ...
  5.   }
  6. }
In myMethod "this" is const. It would be equivalent to the function in Pascal being:
Code: Pascal  [Select][+][-]
  1. procedure myVoid(const self: TMyClass);
instead of:
Code: Pascal  [Select][+][-]
  1. procedure myVoid(self: TMyClass);
Which is how methods usually work right now.

The problem is that this is not really backwards compatible, e.g. if you make the change that on a constant "object" (not to be confused with class instances or object types, just some thing in memory) can only call const methods, would result in breaking a lot of existing code with type helpers and advanced records, which currently rely on const (but not tagged as such) methods. Maybe static analysis to implicitly tag methods as const could be used, but this would be quite a big project
Title: Re: Why with allows assignment
Post by: Martin_fr on January 13, 2025, 02:10:52 pm
I think it would be interesting to extend the concept of constness to methods, e.g. in C++ you can have the following:

Maybe check the discussion on "pure" functions on the mail list...?

Though, for the case of fields in a temp, its not an exact match. You can call
   MyFunc().toString;

And toString could be allowed to modify e.g. a global var, counting how often it was called.

It just mustn't modify its own "self" (the temp record var). But then you also need to ensure it does not by calling anything else. And take old style "type foo=object" with virtual methods. You must be sure no inherited method does => and they may not even be known to the compiler at that time.

Title: Re: Why with allows assignment
Post by: Warfley on January 13, 2025, 02:49:58 pm
Yeah the pure functions are interesting especially as it allows some really interesting concepts (optimizations of recursive functions using fixpoint analysis, compile time evaluation of constants, automated paralelization, etc.), but it's of course a lot stricter than "just" self constness.

But the thing with enforcing constness is not just backwards compatibility, but also that it can be very restrictive. I have worked in C++ projects with some newcomers to C++ who were not that familiar with the concept of constness, and just forgot to declare many methods as const, so they couldn't be called from const contexts.

So while being potentially quite a powerful concept, to be useful it needs to be consistently enforced, which carries it's own problems. Theres a reason many C based languages discarded the concept of constness from C
Title: Re: Why with allows assignment
Post by: PascalDragon on January 13, 2025, 09:53:19 pm
This will stop working in the future, because the value returned by the functions (in your case GetRange and GetRangeObj) will only be temporary and won't propagate back to the original memory. So in that case one needs to explicitly capture the returned value in a variable and then use with. Users using this accidentally or wondering why it doesn't work the way they expect outweigh useful cases like this.

Will this also stop working in the future?

Code: Pascal  [Select][+][-]
  1.   with TfrmVIES.Create(nil) do
  2.   try
  3.     ShowModal;
  4.   finally
  5.     Release;
  6.   end;

Classes are implicit pointer types. This change is about directly accessing records and objects.


Speaking of readability: please use [code=pascal][/code] to improve readabililty and to avoid the forum software interpreting the code.

The forum software is a compiler that will run code in a post?

No, but it has an interpreter for its own functionality. For example if you have a Pascal code that reads SomeArray[i] then the forum software will interpret the [i] as the start of an italic text block, thus all following text will be italic. Similiar if an emoticon is encountered: e.g. SomeCode(Foo, 8) will turn into SomeCode(Foo, 8).
In addition to that [code][/code] allows for better readability due to syntax highlighting (especilly if the correct language is selected, e.g. with [code=pascal][/code]).

PascalDragon, I confess I have not followed this thread closely because of the unpleasantness exhibited. But I do use TestOne below and would like you to assure me it is future safe.

Code: Pascal  [Select][+][-]
  1. program Project1;
  2. {$mode objfpc}
  3.  
  4. type
  5.     TaRec = record
  6.         TheInt : integer;
  7.         TheString : string;
  8.     end;
  9.      
  10. function Test() : TaRec;
  11. begin
  12.     result.TheInt := 42;
  13.     result.TheString := 'FortyTwo';
  14. end;
  15.    
  16. var
  17.         aRec : TaRec;
  18. begin
  19.     aRec := Test();          // Test One
  20.     WriteLn('Test One TheInt ', aRec.TheInt);
  21.     writeln('Test One TheString ', aRec.TheString);
  22.  
  23.     with Test() do begin     // Test Two
  24.         WriteLn('Test Two TheInt ', TheInt);
  25.         writeln('Test Two TheString ', TheString);
  26.     end;
  27. end.

Read access is safe.

yeah well, I have a rather large app that I was able to get fpc to compile that uses lots of code like this.

Code: Pascal  [Select][+][-]
  1.  
  2.   with GetNodeByIndex(1{one for now}) do
  3.   begin
  4.     NormalizeValues;
  5.     if X = -1 then X := Adefault; //Constant for now but changes.
  6.     if Y = -1 then Y := ADefault; //Place Null nodes to a common place.
  7.     if Z = -1 then Z := ADefault;
  8.     ProcessNode;
  9.     // more code after this.
  10.   end;                                              
  11.  
  12.  

That function GetNodeByIndex returns a Record, in Delphi mode to get advanced records and as you can see, I have procedures, and I do change the values within.
That does not mean they get changed in the original source, they don't.

The point is that there are enough people that assume that this will change their original value and then are confused when it doesn't. Due to there being more people that stumble upon this than people that can make productive use of this the Delphi developers decided to forbid it to directly modify fields/properties of records that are accessed using a with-statements that directly captures a function result. So your code will fail in current Delphi versions as well.

Does the above mean that FPC will "eventually" forbid "changing fields of a record that is a return value of a function to matter if with is used or not." ?

The answer is indeed yes. When is a different question however. 😅

Out of interest:
Is that "forbid direct assignment" or "forbid changing fields of ..."? Or where (and maybe how) will that line be drawn.

In the end it will follow what Delphi does. There it's assignments to fields and properties that are forbidden if the record (or object) is accessed using a with-statement directly capturing a function result.
Title: Re: Why with allows assignment
Post by: egsuh on January 14, 2025, 06:46:30 am
Quote
There it's assignments to fields and properties that are forbidden if the record (or object) is accessed using a with-statement directly capturing a function result.

Does this mean calling methods will be allowed?
Title: Re: Why with allows assignment
Post by: PascalDragon on January 16, 2025, 09:17:50 pm
Quote
There it's assignments to fields and properties that are forbidden if the record (or object) is accessed using a with-statement directly capturing a function result.

Does this mean calling methods will be allowed?

If Delphi allows them, then yes.
TinyPortal © 2005-2018