Proper using of "with" statement can make sources at least more compact and easy to read.
The problem with "with" in it's current form in Pascal is, that it was introduced before OOP became the norm. Today in a modern OOP application you are always within an method and it's implicit scope. E.g. when you write a LCL GUI application, you most likely find yourself in a Method of a TForm inherited class. This means you already have alot of methods and properties in that will be implicetly referenced.
Pre OOP you had a handful of global variables, your parameters and local variables, generally speaking not so much going on. The potential on shadowing is very low. But now when you are in a method there are dozens of functions or properties you may shadow when you open a with.
This gets worth when you deal with classes that share a common ancestor. For example I've found code like this quite often:
with TButton.Create(self) do
begin
Parent := Self;
Caption := ButtonCaption;
Left := ButtonPos.X;
Top := ButtonPos.Y;
OnClick := @ButtonClick;
Tag := ButtonID;
end;
Note that every single property of the button that is set here, is also a property of a Form that is shadowed by the with block. So if you do an innocent little error, e.g. you simply forgett the begin-end, this code will compile just completely fine, but instead of configuring your Button, it will configure the Form it is called on.
Simultaniously, let's say you have code that computes the ButtonPos, that is based on properties of the Form:
ButtonPos := ScreenToClient(CursorPos);
...
With TButton.Create(Self) do
// code from above
Now during refactoring you decide that you want this computation to be close to where it is used, so you move it just before the setting of Left and Top:
with TButton.Create(self) do
begin
...
ButtonPos := ScreenToClient(CursorPos);
Left := ButtonPos.X;
Top := ButtonPos.Y;
...
end;
It's the exact same code, just moved down a few lines, but because the with shadows the ScreenToClient method of your Form with that of the button, the code that worked well just before now doesn't work anymore.
These kinds of bugs are often hard to find, and also hard to avoid. Basically at any point in time you need to know all the methods and properties of 1. the thing you are withing as well as 2nd. the class you are in. So unlike before, where you had a few global variables, and used with on a record with a handfull of fields, this is no problem. But now we are talking about classes with up to hundreds of properties and methods (just checked the documentation for TControl, it has over 400 properties and methods).
Also note that a general problem with with is, that you actually can't reference the object you withed directly, as there is no "with self". So you can't call functions with it as parameter, you can only access members (properties, methods or types). So when it was developed in procedural times, it was actually used not for temporary objects, as it is used often today, but it was used for variables when you were to lazy to write out the variable name.
Thats why more recent approaches in languages that want to incorporate with, have gone a different route, where with is intended to scope temporary objects. Things like this:
with TButton.Create as btn do
begin
btn.Parent := Self;
btn.Caption := ButtonCaption;
...
end;
This still enables to write more concise and more readable code, and not to introduce a function wide variable for some minute things, while also solving both problems, 1. you don't have any shadowing and 2. you can pass the temporary object around through the temporary variable "btn".
Another attempt I have seen to solve this without introducing a new variable is by adding a . For the scoping
with TButton.Create do
begin
.Parent := Self;
.Caption := ButtonCaption;
...
end;
This way you can also avoid shadowing by keeping the original essence of the with statement