New features and tweaksSeveral things landed in
main: implicit generics syntax in non-Delphi modes, scoped cleanup with
defer and
autofree,
with gets inline vars + autofree, the for-loop counter is now preserved on exit,
with shadowing warnings now cover methods too, and the Lazarus minimap can keep syntax colors. As always, all features are gated by individual modeswitches; many are on by default in
{$mode unleashed}.
Implicit generics in non-Delphi modesModeswitch
implicitgenerics. Off by default in
{$mode unleashed} -- explicit opt-in.
Stock FPC accepts the short generic syntax (no
generic /
specialize keywords, plain
<T> in declarations and specializations) only in
{$mode delphi}. Unleashed lifts that recognition out into its own modeswitch, so you can keep
objfpc (or
unleashed) and just opt into the short syntax.
{$mode objfpc}{$H+}
{$modeswitch implicitgenerics}
type
TList<T> = class
procedure Add(const Item: T);
end;
var
L: TList<integer>;
Without the switch in non-Delphi modes you still write the explicit form (
generic TList<T> /
specialize TList<integer>); the switch only adds the implicit form on top, it does not remove anything.
Scoped cleanup: defer and autofreeModeswitch
autofree. On by default in
{$mode unleashed}. Enables keywords
autofree and
defer.
Scope-based resource management without
try..finally boilerplate. Two new keywords:
- defer STATEMENT; - register a statement to fire when the enclosing block exits
- autofree EXPR - prefix on a class instance, calls Free (or Destroy if no Free) at scope exit
deferMultiple defers fire in
LIFO order. The deferred body is evaluated
at exit time, not at registration - so it sees the variable's last value.
{$mode unleashed}
procedure test;
var
i: Integer;
begin
i := 55;
defer writeln('i = ', i);
i := 123;
// prints "i = 123"
end;
begin
test;
end.
The scope is the enclosing
begin..end block (or a scoped
with body, see below). Defers fire on:
- normal end of the block
- exit / exit(value) (result is computed first, then defers, then the actual return)
- break / continue / goto out of the block
- exception propagating out
They do
not fire on
Halt,
RunError, or signal-based termination.
{$mode unleashed}
procedure foo;
begin
Lock.Enter;
defer Lock.Leave; // released no matter how foo exits
AssignFile(F, 'x.txt');
Reset(F);
defer CloseFile(F);
// ... work ...
end;
autofreeSugar that registers a
Free (or
Destry) defer for a class instance you just allocated. Cleanup uses a nil-guarded pattern, so a manual
x.Free; x := nil; earlier in the same scope makes the auto-cleanup a no-op rather than crashing on a double-free. Works in two forms:
// inline-var form
begin
var x := autofree TStringList.Create;
x.Add('hello');
end;
// x.Free called here
// classic-var form
var
x: TStringList;
begin
x := autofree TStringList.Create;
x.Add('hello');
end;
// x.Free called here
If the class has no
Free method,
Destroy is called instead. Multiple autofrees in the same scope free in LIFO order. If the constructor raises, the auto-Free does not fire on the half-built instance - FPC's normal "automatic destroy on constructor failure" still runs, so each successful
Create is matched by exactly one cleanup.
autofree/defer demo:{$mode unleashed}
type
TMyClass = class
v: Integer;
constructor Create;
destructor Destroy; override;
procedure Free;
procedure doSomething(test: string);
end;
TMyClassWithNoFree = class
v: Integer;
constructor Create;
destructor Destroy; override;
procedure doSomethingWithNoFree(test: string);
end;
{ *** TMyClass *** }
constructor TMyClass.Create;
begin
writeln('TMyClass.Create');
end;
destructor TMyClass.Destroy;
begin
writeln('TMyClass.Destroy');
end;
procedure TMyClass.Free;
begin
writeln('TMyClass.Free');
end;
procedure TMyClass.doSomething(test: string);
begin
writeln('TMyClass.doSomething('+test+')');
end;
{ *** TMyClassWithNoFree *** }
constructor TMyClassWithNoFree.Create;
begin
writeln('TMyClassWithNoFree.Create');
end;
destructor TMyClassWithNoFree.Destroy;
begin
writeln('TMyClassWithNoFree.Destroy');
end;
procedure TMyClassWithNoFree.doSomethingWithNoFree(test: string);
begin
writeln('TMyClassWithNoFree.doSomethingWithNoFree('+test+')');
end;
{ *** tests *** }
procedure t1;
begin
var c := autofree TMyClass.Create;
c.doSomething('t1');
end;
procedure t2;
begin
var c := TMyClass.Create;
defer c.Free;
defer writeln('end of test 2');
c.doSomething('t2');
end;
procedure t3;
begin
var c := TMyClass.Create;
with autofree c do c.doSomething('t3');
end;
procedure t4;
begin
with var c := TMyClass.Create do begin
defer writeln('escaping <with> block');
defer c.Free;
c.doSomething('t4');
end;
end;
procedure t5;
begin
with var c := autofree TMyClass.Create do c.doSomething('t5');
end;
procedure t6;
begin
with autofree TMyClass.Create do doSomething('t6');
end;
procedure t7;
begin
var c := TMyClass.Create;
with c do doSomething('t7');
c.Free;
end;
procedure t8;
begin
with var c1 := autofree TMyClass.Create, var c2 := TMyClass.Create do begin
doSomething('t8');
end;
end;
procedure t9;
begin
with autofree TMyClass.Create, autofree TMyClass.Create do begin
doSomething('t9');
v := 42;
end;
end;
procedure t10;
begin
var c := autofree TMyClassWithNoFree.Create;
c.doSomethingWithNoFree('t10');
end;
begin
writeln('*** test 1 ***'); t1; writeln;
writeln('*** test 2 ***'); t2; writeln;
writeln('*** test 3 ***'); t3; writeln;
writeln('*** test 4 ***'); t4; writeln;
writeln('*** test 5 ***'); t5; writeln;
writeln('*** test 6 ***'); t6; writeln;
writeln('*** test 7 ***'); t7; writeln;
writeln('*** test 8 ***'); t8; writeln;
writeln('*** test 9 ***'); t9; writeln;
writeln('*** test 10 ***'); t10; writeln;
readln;
end.
Output:
*** test 1 ***
TMyClass.Create
TMyClass.doSomething(t1)
TMyClass.Free
*** test 2 ***
TMyClass.Create
TMyClass.doSomething(t2)
end of test 2
TMyClass.Free
*** test 3 ***
TMyClass.Create
TMyClass.doSomething(t3)
TMyClass.Free
*** test 4 ***
TMyClass.Create
TMyClass.doSomething(t4)
TMyClass.Free
escaping <with> block
*** test 5 ***
TMyClass.Create
TMyClass.doSomething(t5)
TMyClass.Free
*** test 6 ***
TMyClass.Create
TMyClass.doSomething(t6)
TMyClass.Free
*** test 7 ***
TMyClass.Create
TMyClass.doSomething(t7)
TMyClass.Free
*** test 8 ***
TMyClass.Create
TMyClass.Create
TMyClass.doSomething(t8)
TMyClass.Free
*** test 9 ***
TMyClass.Create
TMyClass.Create
TMyClass.doSomething(t9)
TMyClass.Free
TMyClass.Free
*** test 10 ***
TMyClassWithNoFree.Create
TMyClassWithNoFree.doSomethingWithNoFree(t10)
TMyClassWithNoFree.Destroy
Scoped with: inline vars and autofreeThe
with statement gets three new clause forms under the
autofree modeswitch. They all bind a class instance to a name (or a hidden holder) that the
with body sees in scope, with optional auto-cleanup. Multiple inline vars per
with are allowed, mixed forms too.
{$mode unleashed}
uses fpHTTPClient;
procedure main;
var
s: String;
begin
// Form A: hidden holder (no name needed in the body)
with autofree TFPHTTPClient.Create(nil) do
s := Get('http://httpbin.org/ip');
// Form B: bind to an existing local
var http: TFPHTTPClient;
with http := autofree TFPHTTPClient.Create(nil) do
s := http.Get('http://httpbin.org/ip');
// Form C: inline-var with optional autofree
with var http := autofree TFPHTTPClient.Create(nil) do
s := http.Get('http://httpbin.org/ip');
// Multiple inline vars + mixed forms
with var a := autofree TFoo.Create,
var b := autofree TBar.Create,
existing_c do
Use(a, b, existing_c);
// LIFO: b.Free first, then a.Free
end;
begin
main;
end.
A
defer written inside the body of a scoped
with is scoped to
that with, not the enclosing routine, and fires before the autofree cleanup. Classic
with foo do ... (no
var /
autofree /
name :=) is unchanged - the new forms are additive.
Tweak: for-loop counter preserved on exitStandard Pascal leaves the for-loop counter
undefined after the loop exits. The optimizer is allowed to leave any value behind; on stock FPC,
for i := 1 to 10 do ; typically leaves
i = 11 because the cheapest exit-condition encoding overshoots by one.
In
{$mode unleashed} the counter keeps its last assigned value:
for i := 1 to N do
if X[i] = target then
break;
// i is guaranteed to equal the index where break fired (or N if never)
{$mode unleashed}
var
i: Integer;
begin
for i := 1 to 10 do {...};
// i is guaranteed to be 10
end.
{$mode unleashed}
var
i: Integer;
begin
for i := 1 to 10 do if i = 5 then break;
// i is guaranteed to be 5
end.
The cost is one extra assignment on the natural exit path. Nothing on
break,
continue, or
exit. No dedicated modeswitch - this is unleashed-only. If you want the standard "undefined on exit" semantics back for a hot loop, switch the mode locally:
{$mode objfpc}
var
i: Integer;
begin
for i := 1 to 10 do {...};
// i is undefined, can contain garbage
end.
Tweak: with shadowing warnings cover methods tooThe
with field-shadowing warning announced earlier now also fires when a method name is shadowed by a later
with entry, not just data fields. Same diagnostic mechanism, broader coverage - catches the case where two records / classes in a
with list expose a same-named method and the later one quietly wins:
with First, Second do
DoIt; // warning if both First.DoIt and Second.DoIt exist
IDE: minimap can keep syntax colorsThe Lazarus Unleashed minimap package now has a
Keep font color unchanged option in its settings. With it on, the minimap shows your normal syntax-highlighted code in miniature instead of recoloring everything to a single font color.
IDE: full autocomplete for new syntaxCodeTools fixes for all the recently landed unleashed syntax. Inline
var declarations (including
autofree),
with var x := ... forms, scoped
with with multiple inline vars,
defer-bound locals, and the surrounding identifier completion / parameter hints all behave correctly.