Recent

Author Topic: How do 'with' statements actually work?  (Read 949 times)

Aistis

  • New Member
  • *
  • Posts: 29
    • Titbits of mind leakage
How do 'with' statements actually work?
« on: May 07, 2026, 08:09:52 pm »
Given that
Code: Pascal  [Select][+][-]
  1. MyControl: TCustomControl;
why does this 'with' statement compile and work without any issues
Code: Pascal  [Select][+][-]
  1. with MyControl do
  2.   OnMouseMove := @MyControlOnMouseMove;
while this single-line assignment fails to compile
Code: Pascal  [Select][+][-]
  1. MyControl.OnMouseMove := @MyControlOnMouseMove;
and typecasting to the ancestor that introduces OnMouseMove doesn't work either
Code: Pascal  [Select][+][-]
  1. TControl(MyControl).OnMouseMove := @MyControlOnMouseMove;

The 'with' statement doesn't fallback to the 'Self' because I can have multiple TCustomControls that each have their own working OnMouseMove events despite being parented to a single control which itself is also a TCustomControl.

I'm using Lazarus: 4.6, FPC: 3.2.2.
« Last Edit: May 07, 2026, 08:57:29 pm by Aistis »

VisualLab

  • Hero Member
  • *****
  • Posts: 742
Re: How do 'with' statements actually work?
« Reply #1 on: May 07, 2026, 09:24:49 pm »
The OnMouseMove event first appears in the TControl class, in a protected section (TCustomControl and TControl). Therefore, it's not accessible outside the class. Only descendants of this class (i.e., TControl) have access. The descendant is the TWinControl class, but it does not make this event public. The descendant of TWinControl is the TCustomControl class, which also does not expose this event. So, in the TCustomControl class, the OnMouseMove event is still in a protected section. So you need to create a class derived from TCustomControl, e.g. TMyOwnCustomControl, and in this derived class you need to add code that exposes the OnMouseMove event. You would have to do the same for any event that isn't public or published.

Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, ExtCtrls;
  9.  
  10. type
  11.   {TMyVeryOwnControl}
  12.   TMyVeryOwnControl = class(TCustomCOntrol)
  13.   published
  14.     property OnMouseMove;
  15.   end;
  16.  
  17.   {TForm1}
  18.   TForm1 = class(TForm)
  19.     Button1: TButton;
  20.     Label1: TLabel;
  21.     Panel1: TPanel;
  22.     procedure Button1Click(Sender: TObject);
  23.     procedure FormCreate(Sender: TObject);
  24.     procedure FormDestroy(Sender: TObject);
  25.   public
  26.     MyControl: TMyVeryOwnControl;
  27.     procedure MyControlOnMouseMove(Sender: TObject; Shift: TShiftState; X: Integer; Y: Integer);
  28.   end;
  29.  
  30. var
  31.   Form1: TForm1;
  32.  
  33. implementation
  34.  
  35. {$R *.lfm}
  36.  
  37. {TForm1}
  38.  
  39. procedure TForm1.FormCreate(Sender: TObject);
  40. begin
  41.   MyControl := TMyVeryOwnControl.Create(Form1);
  42.   MyControl.Parent := Form1;
  43.   MyControl.Name := 'MyControl';
  44.   MyControl.Width := 200;
  45.   MyControl.Height := 150;
  46.   MyControl.Left := 100;
  47.   MyControl.Top := 50;
  48.   MyControl.Color := clLime;
  49.   MyControl.BorderStyle := bsSingle;
  50. end;
  51.  
  52. procedure TForm1.FormDestroy(Sender: TObject);
  53. begin
  54.   MyControl.Free;
  55. end;
  56.  
  57. procedure TForm1.Button1Click(Sender: TObject);
  58. begin
  59.   MyControl.OnMouseMove := @MyControlOnMouseMove;
  60. end;
  61.  
  62. procedure TForm1.MyControlOnMouseMove(Sender: TObject; Shift: TShiftState;
  63.                                       X: Integer; Y: Integer);
  64. begin
  65.   Label1.Caption := (Sender as TCustomControl).Name + ', X: ' + X.ToString +
  66.                     ', Y: ' + Y.ToString + ', "MyControlOnMouseMove" fired!';
  67. end;
  68.  
  69. end.

I am attaching a sample project (below as an attachment).


Paolo

  • Hero Member
  • *****
  • Posts: 717
Re: How do 'with' statements actually work?
« Reply #2 on: May 08, 2026, 08:42:01 am »
@visuallab, but this doesn't explain why code with "with" works and without "with" doesn't. Unless the code with "with" is managing onousemove of another control. Aistis can check if removing "with" compiles ?  Just remove the row 1 in the code.
« Last Edit: May 08, 2026, 08:46:22 am by Paolo »

Thaddy

  • Hero Member
  • *****
  • Posts: 19235
  • Glad to be alive.
Re: How do 'with' statements actually work?
« Reply #3 on: May 08, 2026, 09:02:30 am »
That behaviour is a bug in "with": it does not respect the visibility specifiers.
This is a known bug, I am not sure it is already fixed.
Unfortunately some of us use this behaviour as a "feature" after discovering it by accident.
It was already discussed on this forum a couple of years ago too, 2022ish.

The compiler is right in refusing the direct assignment, since its visibility is too low, as VisualLab wrote, it is protected.
So he explained basically he same thing.

The compiler is wrong when using "with", since "with" ignores the visibility specifier.

So please don't use this construct. Furthermore It may have already been fixed.
« Last Edit: May 08, 2026, 09:35:26 am by Thaddy »
objects are fine constructs. You can even initialize them with constructors.

VisualLab

  • Hero Member
  • *****
  • Posts: 742
Re: How do 'with' statements actually work?
« Reply #4 on: May 08, 2026, 03:52:56 pm »
@visuallab, but this doesn't explain why code with "with" works and without "with" doesn't. Unless the code with "with" is managing onousemove of another control. Aistis can check if removing "with" compiles ?  Just remove the row 1 in the code.

As Thaddy explained, the "with" statement's behavior with a class variable is flawed. I was not aware of this error because I have been avoiding this instruction in my code for many, many years. It causes more problems than benefits.

I did a little test. I've included the code below.

Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, ExtCtrls;
  9.  
  10. type
  11.   {TForm1}
  12.   TForm1 = class(TForm)
  13.     Button1: TButton;
  14.     Memo1: TMemo;
  15.     procedure Button1Click(Sender: TObject);
  16.     procedure FormCreate(Sender: TObject);
  17.     procedure FormDestroy(Sender: TObject);
  18.   public
  19.     MyControl: TCustomControl;
  20.     procedure MyControlOnMouseMove(Sender: TObject; Shift: TShiftState; X: Integer; Y: Integer);
  21.   end;
  22.  
  23. var
  24.   Form1: TForm1;
  25.  
  26. implementation
  27.  
  28. {$R *.lfm}
  29.  
  30. {TForm1}
  31.  
  32. procedure TForm1.FormCreate(Sender: TObject);
  33. begin
  34.   MyControl := TCustomControl.Create(Form1);
  35.   MyControl.Parent := Form1;
  36.   MyControl.Name := 'MyRawCustomControl';
  37.   MyControl.Width := 200;
  38.   MyControl.Height := 150;
  39.   MyControl.Left := 100;
  40.   MyControl.Top := 50;
  41.   MyControl.Color := clRed;
  42.   MyControl.BorderStyle := bsSingle;
  43. end;
  44.  
  45. procedure TForm1.FormDestroy(Sender: TObject);
  46. begin
  47.   MyControl.Free;
  48. end;
  49.  
  50. procedure TForm1.Button1Click(Sender: TObject);
  51. begin
  52.   with MyControl do
  53.    begin
  54.     Memo1.Lines.Clear;
  55.     Memo1.Lines.Add('class name: ' + ClassName + ', name: ' + Name);
  56.     Text := 'Any text for testing this control!'; // this shouldn't compile! but the compiler has a different opinion on this :)
  57.     OnMouseMove := @MyControlOnMouseMove;         // this shouldn't compile either!
  58.    end;
  59. end;
  60.  
  61. procedure TForm1.MyControlOnMouseMove(Sender: TObject; Shift: TShiftState;
  62.                                       X: Integer; Y: Integer);
  63. begin
  64.   Memo1.Lines.Clear;
  65.   Memo1.Lines.Add('class name: ' + Sender.ClassName);
  66.   Memo1.Lines.Add('name: ' + (Sender as TCustomControl).Name);
  67.  // Memo1.Lines.Add('text: ' + (Sender as TCustomControl).Text); <- this property (i.e. "Text") is protected, so it cannot be accessed from outside the class
  68.   Memo1.Lines.Add('X: ' + X.ToString);
  69.   Memo1.Lines.Add('Y: ' + Y.ToString);
  70.   Memo1.Lines.Add('"MyRawControlOnMouseMove" fired!');
  71. end;
  72.  
  73. end.

It shows that the "with" statement bypasses or ignores the visibility scope of components (properties, events, and perhaps methods) placed in the protected class section. Unfortunately, I don't know the details of how the FPC compiler works to be able to say anything more. In any case, it's a rather serious compiler bug.



EDIT: I am attaching a screenshot of the program compiled and run in Lazarus 4.6 (64-bit) in Windows 10 (also 64-bit).
« Last Edit: May 08, 2026, 09:49:51 pm by VisualLab »

Paolo

  • Hero Member
  • *****
  • Posts: 717
Re: How do 'with' statements actually work?
« Reply #5 on: May 08, 2026, 05:01:58 pm »
Thanks, good to know. I removed almost everywhere "with" in my code.

Handoko

  • Hero Member
  • *****
  • Posts: 5543
  • My goal: build my own game engine using Lazarus
Re: How do 'with' statements actually work?
« Reply #6 on: May 08, 2026, 05:36:04 pm »
Unfortunately some of us use this behaviour as a "feature" after discovering it by accident.

I'm a bad programmer :-[
I'm one the users who use this flaw as a feature. I didn't discover it by accident. I saw someone posted sample code in this forum.

I don't have Delphi installed. Maybe because Delphi has it, FPC has it too. Can anyone help testing this behavior on Delphi?

I quickly tested on my Lazarus 4.0, using "strict" keyword can prevent the code to be compiled. I always use strict private and strict protected instead of private and protected.
« Last Edit: May 08, 2026, 05:49:00 pm by Handoko »

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 12386
  • Debugger - SynEdit - and more
    • wiki
Re: How do 'with' statements actually work?
« Reply #7 on: May 08, 2026, 05:52:36 pm »
Has anyone tested with FPC 3.3.1 or at least 3.2.3?

Because they did at some point have fixes to some privacy section related stuff...

So it might well be that this wont compile any longer. Or, it still compiles, but no longer accesses the "with" object's property, but self of the outer function (as this code should).

If I put this "with" statement, into a method of a "TFoo = class(TObject)", then it does not compile in 3.2.3.  (Then "self" does not have it, and with does not make it visible for the TComponent).

egsuh

  • Hero Member
  • *****
  • Posts: 1800
Re: How do 'with' statements actually work?
« Reply #8 on: May 08, 2026, 05:55:47 pm »
With VisualLab's code,

Code: Pascal  [Select][+][-]
  1.  with MyControl do
  2.    begin
  3.     Memo1.Lines.Clear;
  4.     Memo1.Lines.Add('class name: ' + ClassName + ', name: ' + Name);
  5.     Text := 'Any text for testing this control!'; // this shouldn't compile! but the compiler has a different opinion on this :)
  6.     OnMouseMove := @MyControlOnMouseMove;         // this shouldn't compile either!
  7.    end;

Generally if MyControl does not have property named OnMouseMove, then OnMouseMove is linked to upper scope, TForm1 in this case. So, this code should be compiled without error.
Is it clear that the event occurs with mycontro"'s event, not TForm1's event?

Paolo

  • Hero Member
  • *****
  • Posts: 717
Re: How do 'with' statements actually work?
« Reply #9 on: May 08, 2026, 07:36:06 pm »
@egsuh, this is also what I said, potentially it can be valid code if the event is not of MyControl.

Remy Lebeau

  • Hero Member
  • *****
  • Posts: 1594
    • Lebeau Software
Re: How do 'with' statements actually work?
« Reply #10 on: May 08, 2026, 08:03:01 pm »
but this doesn't explain why code with "with" works and without "with" doesn't. Unless the code with "with" is managing onousemove of another control.

That is exactly what is happpening. Since the MyControl.OnMouseMove event is not accessible due to its visibility, with will then look for other OnMouseMove identifiers that are in scope, and it finds one - Self.OnMouseMove - because 1) TForm is a TControl descendant and thus has access to TControl's protected members, and 2) OnMouseMove is published in TForm, so it is publicly accessible anyway.

So, this code:

Code: Pascal  [Select][+][-]
  1. with MyControl do
  2.   OnMouseMove := @MyControlOnMouseMove;

Ends up getting translated into this:

Code: Pascal  [Select][+][-]
  1. Self.OnMouseMove := @Self.MyControlOnMouseMove;

The code inside of a with block is not restricted to just the object(s) being referenced. It has full access to all other identifiers that are in scope (otherwise you wouldn't be able to refer to MyControlOnMouseMove).
« Last Edit: May 08, 2026, 08:10:24 pm by Remy Lebeau »
Remy Lebeau
Lebeau Software - Owner, Developer
Internet Direct (Indy) - Admin, Developer (Support forum)

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 12386
  • Debugger - SynEdit - and more
    • wiki
Re: How do 'with' statements actually work?
« Reply #11 on: May 08, 2026, 08:12:57 pm »
So I did some tests:
Code: Pascal  [Select][+][-]
  1. procedure TForm1.FormCreate(Sender: TObject);
  2. var b: TControl;
  3. begin
  4.   b:= Button1;
  5.   with b do
  6.     OnMouseMove := @DoFoo; // sets the BUTTON property
  7.  
  8.   b.OnMouseMove := @DoFoo; // works in 3.3.1 but not in 3.2.3
  9. end;
  10.  

Paolo

  • Hero Member
  • *****
  • Posts: 717
Re: How do 'with' statements actually work?
« Reply #12 on: May 08, 2026, 08:21:46 pm »
So now TControl exposed the event ? If both work...

VisualLab

  • Hero Member
  • *****
  • Posts: 742
Re: How do 'with' statements actually work?
« Reply #13 on: May 08, 2026, 09:42:13 pm »
I don't have Delphi installed. Maybe because Delphi has it, FPC has it too. Can anyone help testing this behavior on Delphi?

I tested the code in Delphi that I attached in reply #4. I ran the test in Delphi 13 (Florence), compiled as 64-bit. The code is slightly modified:

Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. interface
  4.  
  5. uses
  6.   Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  7.   Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
  8.  
  9. type
  10.   TForm1 = class(TForm)
  11.     Button1: TButton;
  12.     Memo1: TMemo;
  13.     procedure Button1Click(Sender: TObject);
  14.     procedure FormCreate(Sender: TObject);
  15.     procedure FormDestroy(Sender: TObject);
  16.   public
  17.     MyControl: TCustomControl;
  18.     procedure MyControlOnMouseMove(Sender: TObject; Shift: TShiftState; X: Integer; Y: Integer);
  19.   end;
  20.  
  21. var
  22.   Form1: TForm1;
  23.  
  24. implementation
  25.  
  26. {$R *.dfm}
  27.  
  28. procedure TForm1.FormCreate(Sender: TObject);
  29. begin
  30.   MyControl := TCustomControl.Create(Form1);
  31.   MyControl.Parent := Form1;
  32.   MyControl.Name := 'MyRawCustomControl';
  33.   MyControl.Width := 200;
  34.   MyControl.Height := 150;
  35.   MyControl.Left := 100;
  36.   MyControl.Top := 50;
  37.  // MyControl.Color := clRed; // <- in Delphi this property in the TCustomControl classes and its ancestors is placed in a protected section
  38.   MyControl.Brush.Color := clLime; // <- setting the control color so that it is visible after compiling and running the program
  39.  // MyControl.BorderStyle := bsSingle; <- // in Delphi, there is no such property in the TCustomControl classes and its ancestors
  40. end;
  41.  
  42. procedure TForm1.FormDestroy(Sender: TObject);
  43. begin
  44.   MyControl.Free;
  45. end;
  46.  
  47. procedure TForm1.Button1Click(Sender: TObject);
  48. begin
  49.   with MyControl do
  50.    begin
  51.     Memo1.Lines.Clear;
  52.     Memo1.Lines.Add('class name: ' + ClassName + ', name: ' + Name);
  53.     Text := 'Any text for testing this control!'; // this shouldn't compile! but the compiler has a different opinion on this :)
  54.     OnMouseMove := MyControlOnMouseMove;          // this shouldn't compile either!
  55.    end;
  56. end;
  57.  
  58. procedure TForm1.MyControlOnMouseMove(Sender: TObject; Shift: TShiftState;
  59.                                       X: Integer; Y: Integer);
  60. begin
  61.   Memo1.Lines.Clear;
  62.   Memo1.Lines.Add('class name: ' + Sender.ClassName);
  63.   if Sender is TCustomControl // in Delphi without this check, moving the mouse over the control area results in an error message
  64.    then Memo1.Lines.Add('name: ' + (Sender as TCustomControl).Name);
  65.  // Memo1.Lines.Add('text: ' + (Sender as TCustomControl).Text); <- this property (i.e. "Text") is protected, so it cannot be accessed from outside the class
  66.   Memo1.Lines.Add('X: ' + X.ToString);
  67.   Memo1.Lines.Add('Y: ' + Y.ToString);
  68.   Memo1.Lines.Add('"MyRawControlOnMouseMove" fired!');
  69. end;
  70.  
  71. end.

The program's behavior when moving the mouse cursor in the area of ​​the created control (TCustomControl type) is shown in the first screenshot. I also attach the source code of the project for Delphi 13.

If the type checking of a class (control) is disabled (commented out) as below:

Code: Pascal  [Select][+][-]
  1. procedure TForm1.MyControlOnMouseMove(Sender: TObject; Shift: TShiftState;
  2.                                       X: Integer; Y: Integer);
  3. begin
  4.   Memo1.Lines.Clear;
  5.   Memo1.Lines.Add('class name: ' + Sender.ClassName);
  6.  { if Sender is TCustomControl // in Delphi without this check, moving the mouse over the control area results in an error message
  7.    then }Memo1.Lines.Add('name: ' + (Sender as TCustomControl).Name);
  8.  // Memo1.Lines.Add('text: ' + (Sender as TCustomControl).Text); <- this property (i.e. "Text") is protected, so it cannot be accessed from outside the class
  9.   Memo1.Lines.Add('X: ' + X.ToString);
  10.   Memo1.Lines.Add('Y: ' + Y.ToString);
  11.   Memo1.Lines.Add('"MyRawControlOnMouseMove" fired!');
  12. end;

then during the test (using the debugger) an error message will appear, which is attached as the second screenshot. This is confirmation of what Remy Lebeau wrote. In answer 4 I attached a screenshot of the program compiled and run in Lazarus 4.6 (64-bit). In my opinion, the behavior of the "with" statement in Delphi and FPC is different. Just look at both screenshots (i.e., from Lazarus and Delphi) and compare them (as well as the code handling the OnMouseMove event, which is practically the same in both versions).
« Last Edit: May 08, 2026, 09:56:33 pm by VisualLab »

egsuh

  • Hero Member
  • *****
  • Posts: 1800
Re: How do 'with' statements actually work?
« Reply #14 on: May 09, 2026, 02:30:59 am »
If the event is catched by TForm1 and fired while the sender is MyCustomControl then the explanation is simple.

 

TinyPortal © 2005-2018