Skip to content

fpc-unleashed/freepascal

 
 

Repository files navigation

FPC Unleashed

FPC Unleashed is a community-driven fork of Free Pascal built for developers who want modern language features today, not after an official release that will likely never include them. The features added here were rejected, ignored, or shelved as "too experimental" by upstream - this fork is your only option for them.

unleashed sign

Table of Contents


Features

Unleashed Mode

Activate: {$mode unleashed} or -Munleashed

A modern Pascal mode based on objfpc with powerful enhancements enabled by default. Instead of toggling individual modeswitches, you get everything at once.

When using Lazarus Unleashed, this mode is enabled by default for all projects and full code completion is supported out of the box.

The following modeswitches are enabled automatically:

Modeswitch Description
statementexpressions Use if, case, and try as expressions
inlinevars Declare variables inline anywhere inside a begin..end block
tuples Anonymous tuple types, literals, and destructuring
match Pattern matching with first-match semantics
multivarinit Initialize several variables of the same type with one value
forstep step N clause in for loops to advance by N each iteration
anonymousfunctions Anonymous procedures and functions
functionreferences Function pointers that capture context
advancedrecords Records with methods, properties, and operators
arrayoperators + arrayequality Direct array comparisons with = and <>
ansistrings Use AnsiString as the default string type
underscoreisseparator Allow underscores in numeric literals (1_000_000)
duplicatelocals Allow reusing identifiers in limited scopes
multilinestrings Allow multi-line string literals without manual concatenation
stringordcast Cast a string literal to an ordinal type (dword('RIFF'))
autofree defer STATEMENT, autofree EXPR, scoped with var x := ...
flexiblearrays C99-style flexible array member as last record field (array[] of T)

Note

For the best code-completion experience, we recommend using Lazarus Unleashed - a fork of Lazarus with full support for unleashed mode. If you are using stock Lazarus, enable the mode via -Munleashed in the project's Custom Options instead of placing {$mode unleashed} directly in the source file, to avoid autocomplete issues and incorrect Code Insight behavior.


Statement Expressions

Activate: available in Unleashed mode.

Allows using if, case, and try as expressions that return a value, enabling a more functional and concise coding style. All branches must return values of the same type.

What it does

Traditionally, if, case, and try are statements - they perform actions but don't produce a value. With statement expressions, they can be used on the right side of an assignment, as function arguments, or anywhere an expression is expected.

If expression

var
  s: string;
begin
  s := if 0 < 1 then 'Foo' else 'Bar';
  // s = 'Foo'
end.

Chained if-expressions work as expected:

s := if x > 100 then 'large' else
     if x > 10  then 'medium' else
     'small';

Only one branch is evaluated - side effects in the other branch are never triggered:

function loadFromDatabase: string;  
begin  
  result := 'data';  
end;

var useCache := false;
var s := if useCache then 'cached' else loadFromDatabase;
// expensive is only called when condition is false

Case expression

type
  TMyEnum = (mefirst, mesecond, melast);
var
  s: string;
begin
  s := case mesecond of
    mefirst:  'Foo';
    mesecond: 'Bar';
    melast:   'FooBar';
  end;
  // s = 'Bar'
end.

Ranges

s := case x of
  0:    'zero';
  1..9: 'single digit';
  else  'large';

Note

When using enums, all values must be covered. Otherwise, the compiler will reject the expression. When using integer or ordinal ranges, provide an else clause.

Try expression

Evaluates a function call and returns a fallback value if an exception occurs:

function conditionalthrow(doraise: boolean): string;
begin
  result := 'OK';
  if doraise then raise TObject.Create;
end;

var
  s: string;
begin
  s := try conditionalthrow(false) except 'Error';
  // s = 'OK'

  s := try conditionalthrow(true) except 'Error';
  // s = 'Error'

  // match specific exception types:
  s := try conditionalthrow(true) except on o: TObject do 'TObject' else 'Error';
  // s = 'TObject'
end.

Note

The try expression must contain a function call - try 'literal' except ... is not valid.


Inline Variables

Activate: available in Unleashed mode.

Declare variables at the point of use inside begin..end blocks instead of in a separate var section at the top. Supports explicit types and type inference.

What it does

In standard Pascal, all variables must be declared in a var section before the begin keyword. Inline variables let you declare them exactly where they are needed, reducing visual distance between declaration and use, and enabling type inference from the initializer.

Basic declarations

begin
  // explicit type, no initializer
  var x: integer;
  x := 10;

  // explicit type with initializer
  var y: integer := 42;

  // type inference - compiler deduces integer from the literal
  var z := 100;

  // string inference
  var s := 'hello';

  // multiple variables of the same type
  var a, b: integer;
  a := 1;
  b := 2;
end.

For loops

var
  sum: integer;
begin
  sum := 0;

  // explicit type
  for var i: integer := 1 to 5 do
    sum := sum + i;

  // type inference
  for var j := 1 to 5 do
    sum := sum + j;
end.

For-in loops

var
  arr: array[0..2] of integer = (10, 20, 30);
  sum: integer;
begin
  sum := 0;
  for var item in arr do
    sum := sum + item;
  // sum = 60
end.

Note

Inline variables have the same scope as regular local variables - they are visible from the point of declaration until the end of the enclosing routine. They are not block-scoped.

Note

Untyped numeric inline variables default to a 32-bit signed integer (integer).


Anonymous Tuples

Activate: available in Unleashed mode (modeswitch tuples).

Lightweight anonymous record types written in parentheses, with literals, destructuring, comparison, and full record semantics (managed types, copy by value, passing by var/const). Tuples are stored as ordinary internal records, so anything records can do, tuples can do.

Positional fields (auto-named _1, _2, ...)

function GetPair: (integer, integer);
begin
  Result := (10, 20);     // positional literal
end;

var
  p: (integer, string) := (42, 'hello');
begin
  writeln(p._1, ' ', p._2);  // 42 hello
  writeln(p[0], ' ', p[1]);  // same, by constant index
end.

Named fields

function Coords: (x, y: integer);
begin
  Exit(x: 10, y: 20);     // shorthand inside Exit
end;

Destructuring

var
  a, b: integer;
begin
  (a, b) := GetPair;       // unpack tuple into existing vars
end.

See unleashed/docs/tuples.md for the full grammar (named/positional mixing, tuple arrays, comparison operators, IDE hints).


Match Statement

Activate: available in Unleashed mode (modeswitch match).

Pattern matching with first-match semantics. Replaces case..of for non-ordinal subjects (tuples, strings, arbitrary expressions) and adds catch-all, fallthrough, condition-based branches, tuple wildcards, and an expression form.

Subject form

match s of
  'hello': writeln('greeting');
  'bye':   writeln('farewell');
  _:       writeln('unknown');     // catch-all
end;

Condition form (no of)

match
  x > 100: writeln('big');
  x > 10:  writeln('medium');
  x > 0:   writeln('small');
  _:       writeln('non-positive');
end;

Tuple patterns with wildcards

var p: (integer, integer) := (0, 5);
match p of
  (0, 0): writeln('origin');
  (0, _): writeln('on Y axis');     // matches
  (_, 0): writeln('on X axis');
  _:      writeln('other');
end;

Expression form

var label_: string := match x of
  1: 'one';
  2: 'two';
  _: 'many';
end;

See unleashed/docs/match.md for fallthrough mode (match all), leave, range patterns, and exhaustiveness rules.


Multi-Variable Initialization

Activate: available in Unleashed mode (modeswitch multivarinit).

Initialize several variables of the same type with a single value in one declaration. Works in var, typed constants, and inline var. Each variable gets its own independent copy.

var
  a, b, c: integer = 42;             // global var
  ok, done: boolean = false;

const
  MinX, MinY, MinZ: integer = 0;     // typed constants

procedure Bar;
begin
  var p, q: integer := 99;           // inline var
  var i, j   := 10;                  // inline var with inference
end;

The initializer is evaluated once and copied into each variable; a := 100 does not affect b or c. See unleashed/docs/multi-var-init.md for the full evaluation table.


Flexible Array Members

Activate: available in Unleashed mode (modeswitch flexiblearrays).

C99-style records with a variable-length tail. The last field is declared with empty brackets and no upper bound; the record header has a fixed size, the tail extends as far as the allocation says, and sizeof(rec) reports only the fixed part.

type
  PMessage = ^TMessage;
  TMessage = packed record
    code:   integer;
    length: integer;
    data:   array[] of byte;     // flexible array member
  end;

var
  msg: PMessage;
begin
  GetMem(msg, sizeof(TMessage) + 1024);   // header + 1024-byte tail in one block
  msg^.code   := 42;
  msg^.length := 1024;
  msg^.data[1023] := $FF;                 // no range check fires under {$R+}
  FreeMem(msg);
end;

The compiler does not track the run-time length, so indexing skips both compile-time and runtime range checks even with {$rangechecks on} active. There is no separate buffer, no pointer chase, no managed lifetime.

The pattern is what Win32 headers usually express today as array[0..0] of T or ANYSIZE_ARRAY, with the well-known problems (range check fires, sizeof is one element too large, padding is implicit). FAM gives the inline layout, honest sizeof, and a working {$R+} in one feature. Common targets: TOKEN_GROUPS, BITMAPINFO, LOGPALETTE, network frames, file headers with inline payload.

Restrictions enforced at parse time: FAM must be the last field of a plain record with at least one preceding field; no FAMs in classes, objects, variant parts, or as class var / threadvar; FAM-records cannot be embedded in another type, used as array elements, declared on the stack, passed by value, or returned by value. Use PFamRec (a pointer) wherever a FAM-record would otherwise live by value.

See unleashed/docs/flexible-arrays.md for the full rule list, memory layout diagram, comparison with array of T, and PPU notes.


Scoped Cleanup

Activate: available in Unleashed mode (modeswitch autofree).

Three cooperating constructs for scope-based resource management without try..finally boilerplate.

defer STATEMENT

Register a statement to fire when the enclosing begin..end block exits (normal exit, exit, break, continue, exception). Multiple defers fire in LIFO order. Argument expressions are evaluated at exit, not at registration.

procedure CopyFile(const src, dst: string);
begin
  var fin  := TFileStream.Create(src, fmOpenRead);
  defer fin.Free;
  var fout := TFileStream.Create(dst, fmCreate);
  defer fout.Free;

  fout.CopyFrom(fin, 0);
end;
// fout.Free runs first (LIFO), then fin.Free, even if CopyFrom raises

autofree EXPR

Sugar that registers a nil-guarded Free defer for a class instance. Works on inline-var declarations and on assignments to existing locals. The cleanup uses if x<>nil then begin x.Free; x:=nil end, so manual x.Free; x := nil; earlier in the scope does not double-free.

procedure foo;
begin
  var list := autofree TStringList.Create;
  list.Add('hello');
  list.Add('world');
  WriteLn(list.Text);
end;
// list.Free called automatically here

// also works on existing variables
var
  a, b: TFoo;
begin
  a := autofree TFoo.Create(1);
  b := autofree TFoo.Create(2);
  // ... use a, b ...
end;
// b.Free, then a.Free (LIFO)

The right-hand side must be a class derived from TObject. The LHS must be a plain local or inline variable.

Scoped with

The with statement accepts inline-var bindings, with optional autofree. Three forms:

// inline-var with autofree (cleanup at end of with-scope)
with var http := autofree TFPHTTPClient.Create(nil) do
  s := http.Get('http://httpbin.org/ip');

// bind to an existing local
var http: TFPHTTPClient;
with http := autofree TFPHTTPClient.Create(nil) do
  s := http.Get('http://httpbin.org/ip');

// hidden holder (no name; methods reachable through with-symtable)
with autofree TFPHTTPClient.Create(nil) do
  s := Get('http://httpbin.org/ip');

Multi-with works with any combination:

with var a := autofree TFoo.Create,
     var b := autofree TBar.Create do
  Use(a, b);
// b.Free, then a.Free

defer written inside a scoped-with body is scoped to that with (fires before the autofree cleanup), even when the body is a single statement without begin..end.

The classic with X do BODY (no inline-var, no autofree) is unchanged.

See unleashed/docs/autofree.md for the full grammar, lowering details, error catalogue, and edge cases.


For-Step

Activate: available in Unleashed mode (modeswitch forstep).

Advance the loop counter by an arbitrary positive amount on each iteration with the step clause. Works with both to and downto, and with inline var.

step is a context-sensitive keyword - it is only recognized between the to/downto expression and do. Anywhere else (variable name, function name, record field) step stays an ordinary identifier, so existing code with a step symbol keeps compiling. Even mixed: for i := 0 to step step 1 do parses correctly - the upper bound is the step variable, the keyword step introduces the increment.

for i := 1 to 10 step 2 do
  write(i, ' ');                  // 1 3 5 7 9

for i := 20 downto 1 step 3 do
  write(i, ' ');                  // 20 17 14 11 8 5 2

for var k := 5 to 50 step 5 do
  write(k, ' ');                  // 5 10 15 ... 50

The step expression must be of an ordinal type and must be a positive integer. Use downto for descending loops; the step itself is always positive. The expression is evaluated once before the loop starts, so calls with side effects fire only one time:

for i := 0 to 12 step ComputeStep() do  // ComputeStep called exactly once
  ...

Constant step 1 folds back to a regular for-loop, so all the usual optimizations apply. break, continue, exit and raise work the same as in a regular for loop. step is rejected in for-in loops.


Tweaks

Activate: unleashed-mode-only (no separate modeswitch).

Small semantic adjustments to make existing Pascal constructs behave the way most people expect them to.

Preserved for-loop counter

In standard Pascal the for-loop counter is undefined after the loop exits - the optimizer is free to leave any value behind, and Delphi/FPC docs explicitly warn not to rely on it. That bites every time you write for i := 1 to N do if X then break; and then try to use i.

In Unleashed mode the counter is guaranteed to keep its last assigned value:

for i := 1 to 100 do
  if X[i] = target then
    break;
{ i now holds the index of the match (or 100 if nothing matched) }

for i := 1 to 10 do ;
{ i = 10 (the last in-range value), not 11 (overshoot) }

This matches the intuitive behavior of C, Python, JavaScript and Go. Cost is one extra assignment on the natural exit path; nothing on break/continue/exit.

is not and not in operators

Delphi-style shorthand for negated runtime type checks and set membership tests:

if Obj is not TFoo then ...           // same as: if not (Obj is TFoo)
if x not in [Apple, Orange] then ...  // same as: if not (x in [Apple, Orange])

Compiles to the same node tree as the parenthesised form, so semantics and runtime cost are unchanged. Available in unleashed mode only.

See unleashed/docs/tweaks.md for the catalogue and the exact rules each tweak applies.


Multiline Strings

Activate: available in Unleashed mode (modeswitch multilinestrings).

Two delimiter forms let a string literal span multiple source lines without manual + or LineEnding.

Backtick form

const
  banner =
`========================================
=         FCF Fibonacci Demo           =
========================================`;

A normal string literal extended to tolerate embedded newlines.

Triple-quote form

const
  sql =
    '''
    select id, name
    from users
    where active = 1
    ''';

A Delphi-11-style textblock literal. The opener (''' followed by a newline) and the closer (''' on its own line) must each sit alone; the indentation of the closing delimiter defines the column that gets stripped from every content line.

The two forms differ in tokenization, indentation handling, and how they compose in expressions. See unleashed/docs/multiline-strings.md for the details. (Stock FPC actually accepts these too but never documented them.)


Array Equality

Activate: {$modeswitch arrayequality} (requires arrayoperators to also be active; both are enabled in {$mode unleashed})

Adds support for = and <> comparison operators between arrays.

What it does

Standard Free Pascal with arrayoperators allows + (concatenation) on dynamic arrays, but does not allow direct equality comparison. This modeswitch fills that gap - you can compare two arrays element-by-element using = and <>.

Example

{$mode unleashed}
var
  a, b: array of integer;
begin
  a := [1, 2, 3];
  b := [1, 2, 3];

  if a = b then
    writeln('Arrays are equal');    // this is printed

  b := [1, 2, 4];
  if a <> b then
    writeln('Arrays are different'); // this is printed
end.

Strip RTTI

Activate: {$modeswitch striprtti}

Important

This modeswitch is not enabled by default in unleashed mode. It must be opted into explicitly.

What it does

When enabled, all RTTI strings (type names of custom structures like records, classes, etc.) are stripped from the binary - they are replaced with empty strings. RTTI structures still exist and cannot be fully removed, but the most obvious fingerprint - plain-text type identifiers - is gone.

Why

Sometimes one may want to avoid exposing an application's internal structure, especially when a simple ASCII dump can reveal type names and identifiers, and with them, the true purpose of the program.

For instance, in the context of game cheats, embedding a name like TGameWallhack in the binary can immediately reveal the nature of the software.

📄 $\color{Yellow}{FULL \ CODE \ EXAMPLE \ -\ click \ to\ expand}$
program MyCoolCheat;

{$modeswitch striprtti}

type
  TMyAwesomeCheatBase = class
    process_id: dword;
  end;

  TEnemy = record
    playername: string;
  end;

  TTargetList = array of TEnemy;

  TGameAimbot = class(TMyAwesomeCheatBase)
  private
    ftargets: TTargetList;
  public
    property targets: TTargetList read ftargets;
    constructor create;
    procedure addtarget(playername: string);
    procedure start;
    procedure stop;
  end;

  TGameWallhack = class(TMyAwesomeCheatBase)
    enabled: boolean;
  end;

  TCheat = class
    aimbot: TGameAimbot;
    wallhack: TGameWallhack;
    constructor create;
    destructor destroy; override;
  end;

constructor TGameAimbot.create;
begin
  setlength(ftargets, 0);
end;

procedure TGameAimbot.addtarget(playername: string);
var
  newtarget: TEnemy;
  i: integer;
begin
  newtarget.playername := playername;
  i := length(ftargets);
  setlength(ftargets, i+1);
  ftargets[i] := newtarget;
end;

procedure TGameAimbot.start;
begin
end;

procedure TGameAimbot.stop;
begin
end;

constructor TCheat.create;
begin
  inherited;
  aimbot := TGameAimbot.create;
  wallhack := TGameWallhack.create;
end;

destructor TCheat.destroy;
begin
  aimbot.free;
  wallhack.free;
  inherited;
end;

procedure list_enemies(const cheat: TCheat);
var
  enemy: TEnemy;
begin
  for enemy in cheat.aimbot.targets do writeln(enemy.playername);
end;

var
  cheat: TCheat;

begin
  // initialize game cheat
  cheat := TCheat.create;
  // add enemies
  cheat.aimbot.addtarget('Enemy Player');
  cheat.aimbot.addtarget('Another Enemy');
  // enable wallhack and start aimbot
  cheat.wallhack.enabled := true;
  cheat.aimbot.start;
  // print enemies list
  list_enemies(cheat);
  // stop the cheat
  cheat.aimbot.stop;
  cheat.free;
end.

ASCII dump comparison

Standard With {$modeswitch striprtti}
Offset Size String
acf0   10   0123456789ABCDEF
af20   29   FPC 3.3.1 [2025/06/18] for x86_64 - Win64
b0d1   13   TMyAwesomeCheatBase
b1c1   0b   TGameAimbot
b2a9   0d   TGameWallhack
b3b8   0c   Enemy Player
b3d8   0d   Another Enemy
b3ea   13   TMyAwesomeCheatBase
b418   0b   MyCoolCheat
b47a   0b   TTargetList
b4aa   0b   MyCoolCheat
b4c2   0b   TGameAimbot
b512   0b   TGameAimbot
b538   0b   MyCoolCheat
b552   0d   TGameWallhack
b57a   0b   MyCoolCheat
b5bb   0b   MyCoolCheat
Offset Size String
acf0   10   0123456789ABCDEF
af20   29   FPC 3.3.1 [2025/06/18] for x86_64 - Win64
b398   0c   Enemy Player
b3b8   0d   Another Enemy

Type names like TGameAimbot, TGameWallhack, or MyCoolCheat are no longer present, making the binary significantly less identifiable at first glance. Only actual string data (like player names) remains.

Side effects

Compiling a typical LCL application with striprtti enabled will likely result in a startup failure, because code such as:

application.createform(TForm1, form1);

will search for "" (empty string) in the resources instead of TForm1, and fail.

Workaround

Three ways are provided to selectively whitelist types that should keep their RTTI name:

1. expose keyword - placed directly before a type declaration in unleashed mode:

type
  expose TForm1 = class(TForm)
    // ...
  end;

The keyword is contextual and only recognized in {$mode unleashed}. It is parsed even when striprtti is off (no-op then), so you can leave the keyword in place while temporarily disabling stripping.

2. {$rttiexpose} directive - per-unit list of glob patterns. Whitespace and/or comma separated:

{$rttiexpose TForm* TButton*, TPanelMain}

3. --rttiexpose= CLI flag - global list of glob patterns, applied to every compiled unit. Repeatable:

fpc --rttiexpose=TForm*,TButton* --rttiexpose=TPanelMain ...

Useful for whitelisting types you do not control (LCL, RTL).

CLI patterns and per-unit directive patterns are merged (union); the directive can only widen the whitelist for its own unit, never narrow CLI.

Note

The {$modeswitch striprtti} directive works on a per-unit basis. You can enable it only in the units where you want to hide type names, while leaving it disabled in others - for example, in units that contain forms or require RTTI to function correctly.

See unleashed/docs/strip-rtti.md for the full list of stripped fields, edge cases (forwards, generics, aliases), interaction with PPU, and implementation notes.


Indexed Labels

Labels now support indexes.

Example

label
  mylabel1,
  mylabel2[1, 2, 3],
  mylabel3[1..10],
  mylabel4['foo', 'bar'];

begin
  goto mylabel4['foo'];

  writeln('you should not see this');

  mylabel1:
  mylabel2[2]:
  mylabel3[10]:
  mylabel4['foo']:

  writeln('hello!');
end.

Lazy Label Declarations

Labels no longer need to be declared before use.

Example

begin
  goto mylabel;

  writeln('you should not see this');

  mylabel:

  writeln('hello!');
end.

Compound Assignment for Pascal Operators

Compound assignment is now supported for Pascal operators such as div, mod, and xor, without requiring {$COPERATORS ON}.

Example

var
  i: integer = 10;
begin
  i div= 2;   // equivalent to: i := i div 2
  writeln(i); // prints "5"
end.

Available: div=, mod=, and=, or=, xor=, shl= and shr= .


Custom Binary Metadata

Three CLI flags override metadata that ends up in the produced binary. Useful for branding releases, hiding the toolchain you used to build the binary, or simply controlling what inspection tools display - it is your binary, set the fields to whatever you want.

  • --fpcsignature=<str> - replaces the ident string in the .fpc.version section. Cross-platform; every target emits this section. Default is FPC Unleashed <version> [<date>] for <cpu> - <target>. Passing an empty string (--fpcsignature=) drops the section entirely - the produced binary carries no FPC ident marker at all.
  • --linkerversion=<Major.Minor> - sets MajorLinkerVersion / MinorLinkerVersion in the PE optional header. Windows PE only. Default derived from FPC version (e.g. 3.31 for FPC 3.3.1).
  • --osversion=<spec> - sets MajorOperatingSystemVersion / MinorOperatingSystemVersion in the PE optional header. Windows PE only. spec is either an OS name (XP, Win11, Vista, 7, 8.1, ...) resolved via a built-in table, or numeric Major.Minor (10.0, 6.3). Default is 4.0 unless -WP is set.

Examples:

fpc --fpcsignature="MyApp 1.0" --linkerversion=14.39 --osversion=Win11 my_program.pas
fpc --fpcsignature="FPC" my_program.pas       # keep "FPC", drop version + date
fpc --fpcsignature="" my_program.pas          # no signature section in the binary
fpc --osversion=10.0 my_program.pas
fpc --osversion=XP --linkerversion=14.0 my_program.pas

The OS-name table is case-insensitive and accepts an optional Win prefix (Win11, WinXP work just like 11, XP).

Important

--linkerversion and --osversion are Windows PE only (targets win32, win64, wince). Other binary formats do not carry these fields:

  • ELF (Linux, BSD, Solaris, Haiku, Android) - no linker version or OS version in the header.
  • Mach-O (macOS, iOS) - has LC_BUILD_VERSION / LC_VERSION_MIN_* but FPC delegates linking to the system ld, which fills these from the SDK.
  • NE / OMF / WASM / NLM / AmigaOS hunk / Atari TOS - either no such field or hardcoded for compatibility.

Passing the flags on a non-PE target compiles cleanly but the values are silently ignored. --fpcsignature works on every target.

See unleashed/docs/binary-metadata.md for full per-flag rationale, the OS-name table, and cross-platform notes.


Extra Improvements

Smaller, targeted improvements that unlock Pascal patterns standard FPC modes reject. Each is gated on its own modeswitch (some are on by default in unleashed, others must be opted into):

  • stringordcast - cast a string literal to an ordinal type at compile time, e.g. dword('RIFF') or word('MZ'). Useful for signature checks. On by default in unleashed.
  • typehelpers - type helper for T on any named type, not just classes and records.
  • multihelpers - several helpers for the same type visible in one scope (instead of "last one wins").
  • implicitgenerics - Delphi-style implicit generic / specialize syntax (TList<T> without keywords). Stock FPC locks this to {$mode delphi}; the modeswitch makes it usable in any mode.

Full descriptions and examples in unleashed/docs/extra-improvements.md.


Detailed Documentation

Each feature has a dedicated reference page in unleashed/docs/ with the full grammar, semantics, edge cases, and IDE notes. Start at the index: unleashed/docs/README.md.

Installation

Option 1: Fresh install (FPC + Lazarus via fpcupdeluxe)

  1. Download fpcupdeluxe and run it once to generate the fpcup.ini file.
  2. Edit fpcup.ini and add the following under [ALIASfpcURL]:
[ALIASfpcURL]
unleashed.git=https://github.com/fpc-unleashed/freepascal.git

And, for Lazarus Unleashed (with autocomplete support for some of the new features), add the following under [ALIASlazURL]:

[ALIASlazURL]
unleashed.git=https://github.com/fpc-unleashed/lazarus.git
  1. Reopen fpcupdeluxe, uncheck GitLab, and select fpc-unleashed.git as your FPC version.
  2. Choose any Lazarus version you like.

fpcupdeluxe

  1. Click Install/update FPC+Lazarus.
  2. Optionally install cross-compilers via the Cross tab.

Option 2: Upgrade an existing fpcupdeluxe setup

  1. Make sure your existing FPC + Lazarus installation was created with fpcupdeluxe.
  2. In your installation directory, delete or rename the fpcsrc folder.
  3. Clone the FPC Unleashed repo into the fpcsrc directory:
git clone https://github.com/fpc-unleashed/freepascal.git fpcsrc
  1. In fpcupdeluxe, go to Setup+, check FPC/Laz rebuild only, and confirm.
  2. Click Only FPC to rebuild the compiler and RTL.
  3. Optionally install cross-compilers via the Cross tab.

Contributing

We welcome bold ideas and experimental features that push Pascal forward.

FPC Unleashed is a home for innovation. If you have built a language feature that was considered too experimental or not standard enough for upstream, this is where it belongs.

What we are looking for

  • New language ideas - Propose modeswitches, syntax extensions, or compiler enhancements via GitHub Issues or Discussions. Even if you do not have an implementation yet, a well-described idea with clear use cases is valuable.
  • Complete, high-quality implementations - We accept pull requests for new language constructs, compiler enhancements, and RTL improvements. We expect production-grade code: clean implementation, proper test coverage, and clear documentation of the feature.

What we are not looking for

We do not accept minor convenience patches, trivial reformats, or small tweaks that only scratch a personal itch. Every change to a compiler carries weight - if you are contributing code, it should be a meaningful feature or fix that benefits the broader community.

Packages

 
 
 

Contributors

Languages

  • Pascal 87.3%
  • Makefile 9.4%
  • Assembly 1.8%
  • WebIDL 0.5%
  • C++ 0.2%
  • Pawn 0.2%
  • Other 0.6%