Recent

Author Topic: FPC Unleashed (inline vars, statement expr, tuples, match, indexed/lazy labels)  (Read 41723 times)

Fibonacci

  • Hero Member
  • *****
  • Posts: 1002
  • Behold, I bring salvation - FPC Unleashed
FPC Unleashed

Upstream said no. FPC Unleashed says yes.

An experimental fork of Free Pascal with language features that won't make it into the official compiler - rejected, ignored, or shelved as "too experimental" by upstream. Inline variables (with block scoping), statement expressions, multi-variable init, and more - all under the new {$mode unleashed}.

Built for developers who want modern Pascal today, not after an official release that will likely never come. Grab a nightly build for Windows or Linux, or install via fpcupdeluxe in minutes.

Source: Free Pascal Unleashed + Lazarus IDE Unleashed.



Get it via Unleashed Installer

The simplest way to install the Unleashed Bundle (FPC + IDE) is to use the Installer. It works on 64-bit Windows and Linux, can build several cross-compilers (Win32, Win64, Linux32, Linux64, WASM), and ships with a few IDE addons. Just tick the checkboxes for the options you want your IDE to come with.

Forum post: Unleashed Installer
Source code: https://github.com/fpc-unleashed/installer
Download: https://github.com/fpc-unleashed/installer/releases



Other ways to install

Other ways to install (including fpcupdeluxe): https://github.com/fpc-unleashed/freepascal/tree/main#installation



Roadmap & community


What's new
« Last Edit: Today at 07:55:55 am by Fibonacci »
FPC Unleashed - inline vars, tuples, statement expressions, array equality, compound assignments, indexed/lazy labels, no-RTTI & more. ⭐ Star it on GitHub!

Fibonacci

  • Hero Member
  • *****
  • Posts: 1002
  • Behold, I bring salvation - FPC Unleashed
Features

All features below are available in {$mode unleashed} unless noted otherwise. Unleashed mode is based on objfpc with extras enabled by default.



Inline variables

Declare variables at the point of use, with optional type inference.

Code: Pascal  [Select][+][-]
  1. var s := 'hello';              // inferred as string
  2. var i: integer := 42;          // explicit type
  3. var a, b := 10;                // multi-variable (see below)
  4.  
  5. for var j := 1 to 5 do writeln(j);
  6. for var item in arr do writeln(item);

Variables declared inside nested begin..end blocks are block-scoped - not visible outside that block.



Statement expressions

Use if, case, and try as expressions that return values.

Code: Pascal  [Select][+][-]
  1. var s := if x > 0 then 'positive' else 'negative';
  2.  
  3. var name := case lang of
  4.   0: 'Pascal';
  5.   1: 'C';
  6.   else 'unknown';
  7. end;
  8.  
  9. var val := try StrToInt(input) except 0 end;



Multi-variable initialization

Initialize multiple variables with a single value.

Code: Pascal  [Select][+][-]
  1. var a, b, c: integer = 0;     // var section
  2. var x, y := 10;               // inline, type inferred
  3. const MinX, MinY: integer = 0;
  4.  

Also supports multi-variable assignment:

Code: Pascal  [Select][+][-]
  1. a, b, c := 0;



Tuples

Anonymous record types with destructuring, comparison, and structural typing. Full walkthrough in this post.

Code: Pascal  [Select][+][-]
  1. function GetCoords: (x, y: integer);
  2. begin
  3.   result := (x: 10, y: 20);
  4. end;
  5.  
  6. var (x, y) := GetCoords;      // destructuring
  7. var p: (integer, string);     // positional (fields _1, _2)
  8. p := (42, 'hello');
  9.  
  10. if (1, 2) < (1, 3) then ...;  // lexicographic comparison



Match statement (and expression)

Pattern matching with non-constant labels, strings, tuple patterns and conditions. First-match by default, explicit opt-in fallthrough via match all. Usable as a statement or on the RHS of an assignment. Full walkthrough in this post.

Code: Pascal  [Select][+][-]
  1. match dir of
  2.   0: name := 'north';
  3.   1: name := 'east';
  4.   _: name := 'unknown';
  5. end;
  6.  
  7. match s of
  8.   'hello':      Greet;
  9.   'bye', 'q':   Quit;         // comma-separated patterns
  10.   _:            Help;
  11. end;
  12.  
  13. // no subject - boolean branches, replaces if/else if ladders
  14. match
  15.   x > 100: writeln('huge');
  16.   x > 10:  writeln('medium');
  17.   _:       writeln('small');
  18. end;
  19.  
  20. // tuple patterns with per-field wildcards
  21. match p of
  22.   (0, 0): writeln('origin');
  23.   (0, _): writeln('on Y axis');
  24.   _:      writeln('elsewhere');
  25. end;
  26.  
  27. // as expression (requires exhaustive coverage)
  28. var name := match dir of
  29.   0: 'north';
  30.   1: 'east';
  31.   _: 'unknown';
  32. end;

match all runs every matching branch in source order. leave exits the match early without breaking an enclosing loop.



Array equality

Compare arrays directly with = and <>. Element-by-element comparison.

Code: Pascal  [Select][+][-]
  1. var a: array of integer = (1, 2, 3);
  2. var b: array of integer = (1, 2, 3);
  3. if a = b then writeln('equal');



Compound assignment for Pascal operators

Available in all modes, no modeswitch needed. No space between keyword and =.

Code: Pascal  [Select][+][-]
  1. i div= 3;       // i := i div 3
  2. i mod= 10;      // i := i mod 10
  3. flags and= $0F;
  4. flags or=  $30;
  5. flags xor= $05;
  6. flags shl= 4;
  7. flags shr= 2;



Indexed labels

Labels with constant indexes. Requires {$goto on}.

Code: Pascal  [Select][+][-]
  1. label state[0..3];
  2.  
  3. goto state[2];
  4.  
  5. state[0]: writeln('idle');
  6. state[1]: writeln('running');
  7. state[2]: writeln('paused');
  8. state[3]: writeln('stopped');

Supports numeric ranges, enumerations, and string keys.



Lazy label declarations

Labels don't need a label section - the compiler creates them on first goto reference.

Code: Pascal  [Select][+][-]
  1. goto skip;
  2. writeln('skipped');
  3. skip:
  4. writeln('here');



Implicit Generics modeswitch

TODO...



Autofree + Defer

TODO...



Flexible Array Members (FAM)

TODO...



Composable Records

TODO...



array[N] size shorthand

TODO...



Static local variables

TODO...



Multiline strings

String literals can span multiple lines using triple single quotes. Enabled by default in unleashed mode.

Code: Pascal  [Select][+][-]
  1. var sql :=
  2.   '''
  3.  SELECT id, name, email
  4.  FROM users
  5.  WHERE active = 1
  6.  ORDER BY name
  7.  ''';



Numeric underscores

Underscores in numeric literals for readability. Enabled by default in unleashed mode.

Code: Pascal  [Select][+][-]
  1. var x := 1_000_000;
  2. var h := $FF_FF_00_00;



No RTTI mode

Strips RTTI type name strings from the binary. Not enabled by default - opt in with {$modeswitch nortti}.

Currently being redesigned - more details soon.



Fixes

In the Unleashed fork

Bugs fixed directly in Unleashed that are not tied to any unleashed feature:
  • IE 200204176 in tarraydef.getmangledparaname for static arrays. On subsequent builds, PPU-loaded static array types can have typesym=nil when their mangled name is requested. The old code only handled array-of-const and open/dynamic arrays and fell through to an internal error. Static arrays now go through the same anonymous-name path as the other cases.



Pending upstream (open merge requests)

These are still open MRs against the official FPC repo. Once they get merged, I'll sync unleashed with upstream and they'll land here too
  • !1051 - Incorrect records alignment for 64-bit in jwatlhelp32.pas
  • !1421 - Fix IE 2012101001 when specializing generic type aliases (#41700)
  • !1424 - Fix IE 200108231: with + anonymous function capture (#40600)
  • !1425 - Fix IE 2006111510 when taking address of volatile() with optimizations (#41704)
  • !1426 - Fix funcret regvar relocation across inlined blocks hiding fc_exit (#41558)
  • !1427 - Force memory for absolute var when target type differs (#41031)
  • !1429 - Accept empty parens in postfix recovery after undefineddef member (#41519)
  • !1435 - Allow generic method call inside with in mode delphi (#41712)
  • !1451 - Fix #40661: internal error 200612311 when specializing overloaded generic procedure
Merged
  • !1379 - JwaIpTypes: fix record size for x64
  • !1415 - Reject non-lvalue argument to Volatile/Aligned/Unaligned
  • !1430 - Re-init varargs `paraloc` in `redoalinaparams`
  • !1442 - Fix `normalize()` IE 200108231 under `-O3 -OoDEADSTORE` (#41682)
« Last Edit: June 06, 2026, 09:57:43 am by Fibonacci »
FPC Unleashed - inline vars, tuples, statement expressions, array equality, compound assignments, indexed/lazy labels, no-RTTI & more. ⭐ Star it on GitHub!

n7800

  • Hero Member
  • *****
  • Posts: 710
  • Lazarus IDE contributor
    • GitLab profile
It seems like a mode switch is required to use the "new" features, but your example code doesn't have one. From what I've read, it's not required when using your Lazarus fork, but perhaps not everyone will install it. In any case, to make this more demonstrative, it's worth adding it. This will make it clear to people that the "incompatibility" will only occur if they explicitly opt-in.

As far as I can tell, this is a fork of the FPC trunk from about a year ago?

Thaddy

  • Hero Member
  • *****
  • Posts: 19273
  • Glad to be alive.
Why is two years worth of bug fixes and new essential libraries not synced?
Is it the intention that I need to downgrade? because that's what this fork is in its current state.
If you fork, plz also sync.
objects are fine constructs. You can even initialize them with constructors.

n7800

  • Hero Member
  • *****
  • Posts: 710
  • Lazarus IDE contributor
    • GitLab profile
It's a shame Lazarus also requires a fork. As far as I can see, the problem lies in parsing the new code by the IDE. I'm not sure if CodeTools can be extended to accommodate the new mode switches, or even if you can replace the package with your own custom version... Requiring a separate IDE will complicate the use of the FPC fork.

n7800

  • Hero Member
  • *****
  • Posts: 710
  • Lazarus IDE contributor
    • GitLab profile
(reserved for feature descriptions)

You can simply edit the first post in the topic ))

Fibonacci

  • Hero Member
  • *****
  • Posts: 1002
  • Behold, I bring salvation - FPC Unleashed
It seems like a mode switch is required to use the "new" features, but your example code doesn't have one. From what I've read, it's not required when using your Lazarus fork, but perhaps not everyone will install it. In any case, to make this more demonstrative, it's worth adding it. This will make it clear to people that the "incompatibility" will only occur if they explicitly opt-in.

Or you can use modeswitches. Trunk users should already be familiar with enabling new features via modeswitches (anonymous functions, function references, or array operators).

Good point though, I updated the example. Thank you. BTW. For the demo/screenshot I was using regular Lazarus, so I added "--unleashed" to the project custom options.

As far as I can tell, this is a fork of the FPC trunk from about a year ago?

Why is two years worth of bug fixes and new essential libraries not synced?
Is it the intention that I need to downgrade? because that's what this fork is in its current state.
If you fork, plz also sync.

About 9 months. It will be synced.
FPC Unleashed - inline vars, tuples, statement expressions, array equality, compound assignments, indexed/lazy labels, no-RTTI & more. ⭐ Star it on GitHub!

Joanna

  • Hero Member
  • *****
  • Posts: 1461
Why can’t the variables be placed in the usual place they have always been? I don’t see the purpose for this  %)

However forking is certainly better than complaining. This is the beauty of opensource software.  :D

Fibonacci

  • Hero Member
  • *****
  • Posts: 1002
  • Behold, I bring salvation - FPC Unleashed
Why can’t the variables be placed in the usual place they have always been?

They can. Nothing changes in that regard, you can still declare variables in the usual place if you prefer. Use whatever style you want in your own projects. Just like some people prefer writing in ALL CAPS (you know what I mean...) - it looks unusual to others, but it is their style and that is fine. It's weird, but it is fine :D

I don’t see the purpose for this  %)

Many do.

I often use helper variables that are more convenient to keep close to where they are used.

For example, when writing a quick loop that should try something a few times (retry on failure), I know Lazarus has a shortcut for declaring variables, but I am not a fan of how it works - for example it inserts the type name capitalized and I prefer all-lowercase, so I still end up fixing it manually. I used to scroll to the top and carefully arrange the variable list to look nice, sorted by category, name length, type, whatever, and that was just a waste of time. Now I prefer to declare the variable directly where it is needed. Just a personal preference, and somewhat mental deviance with that sorting thing (lol)...

This is also useful for "for" loops, for timeout counters near a "while" loop, or for other short-lived helper variables that are only used in a small part of the code.
FPC Unleashed - inline vars, tuples, statement expressions, array equality, compound assignments, indexed/lazy labels, no-RTTI & more. ⭐ Star it on GitHub!

440bx

  • Hero Member
  • *****
  • Posts: 6540
From "Contributing" ...

Quote
We welcome bold ideas and experimental features that push Pascal forward.
I've got a few bold ideas that push Pascal forward.  I'm looking forward to see if they are actually welcome.  Keep reading...

Quote
FPC Unleashed is a home for innovation
Honestly, I don't see any innovation there yet.  In my mind, innovation means something "new" and novel.  What I see so far are features present in other languages that have been imported into Pascal.  I want to make it clear that I value the act of importing useful features from other languages but, I would not characterize them as "innovations" since those are already existing and proven features in other languages.   All that said, I have a few bold innovations to suggest.  Keep reading...

Quote
New language ideas - propose modeswitches, syntax extensions, or compiler enhancements via GitHub Issues or Discussions. Even if you don't have an implementation yet, a well-described idea with use cases is valuable.[/size]
Got plenty of those.  I'll mention a few in this post.  Keep reading...

Quote
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 documentation of the feature.
"production-grade code" is what should always be expected in a compiler.  Though I have a very intense dislike of OOP (which is used in FPC and this fork), I _may_ try to get over it to contribute some features that are dear to my heart.   Please note the emphasis on the word "_may_".

Quote
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.

I consider that a problem. 

Progress is more often achieved by a long string of small improvements than one or two "revolutionary" features.  I'll mention a few of those... keep reading...
 


With the above comments out of the way, I want to make it clear that I welcome an FPC fork and really hope it will be a bit more active and "sensible" than its root.

I said "keep reading" multiple times above... here is why:

If this branch of FPC is a home for innovation then the entire inline variables needs to be redesigned because as its stands: 1. it definitely lacks innovation (just a poor copy of what's in C) and 2.) it is handicapped in ways that severely limit its usefulness.  For those who genuinely want innovation, here is what "inline variables" should be:

It should be a separate block/scope.  The reason for this are the following:

1. not only should the programmer be able to declare variables as needed but, the programmer should also be able to declare types and constants as needed.  The raison d'etre of inline variables is to have full locality and that applies to constants and types too, not just variables.  Inline variables, as implemented in Delphi and the "unleashed" version of FPC are simply a castrated C feature.

2. inline variables (and types and constants) are, by definition specific to a block of code in its own scope.  Because of that, the full and correct implementation of the feature must include a mechanism to exit the scope.  That's why "exit" and "break" exist, to exit scopes.  The scope that contains inline variables (and hopefully types and constants should also be exit-able.)

3. it would keep/force the inline variables/types/constants to be declared at the top of the scope just as it currently is.  IOW, inline variables should NOT be allowed to just happen anywhere in a block of code.

As far as "innovation(s)", I'll limit my comments to just that one for the time being.

New language ideas... something really new...

_surprisingly_, a switch/compiler directive that initializes _all_ of the uninitialized locals to binary zeroes (including inline variables), along with an option to select whether the switch is global or local.  Such a switch would ensure a _known_ starting state for every variable which could make the detection of a bug much simpler (because there would no longer be uninitialized variables with unpredictable and non-repeatable values.)

Complete, high-quality implementations - I very strongly believe that every feature should have a declared set of owners with a primary owner (and owner history.)  Those owners willingly commit to support, maintain, debug and create a complete set of independent, easily identifiable and verifiable test cases for the feature.  The owners must be ready and willing to _commit_ whatever time is necessary to fully and correctly implement the feature(s) they own.  Otherwise, don't "contribute" because any other "contribution" is just the addition of problems instead of solutions.

proper test coverage... more than once I've looked in the FPC source looking for the tests applied to a particular feature and, sadly, either found nothing or what I found was, putting it very kindly, "incomplete".

As far as, "we do not accept minor convenience patches, trivial reformats, or small tweaks that only scratch a personal itch.", I agree with the "personal" part.  Improvements should benefit the entire community but, that doesn't preclude "trivial reformats", for instance, some identifiers in the FPC source code are in German, it's trivial and _nice_ to have all identifiers in English.  A set of formatting standards the source should adhere to might not be a bad idea even though it could be considered to just be a minor convenience (along with the patch or patches that make a few corrections to meet the adopted standard.)

As far as small features/improvements I'd really like to see are:

1. The ability to explicitly initialize more than one variable.  Currently only single variable declarations may be initialized.

2. A reasonably logical way of declaring static variables, instead of the ridiculous (not to mention embarrassing) "writable constants".

3. A simple way of aligning records and record fields to a specific boundary.  Currently, attempting to align records and/or record fields to a boundary greater than 8 can't be done even sacrificing your first hard drive on George Boole's tomb.  (though, TBH, I haven't tried that one yet.)

4. The thousands separator, which is already implemented in FPC but isn't part of the next version because it is a new feature, not just a bug fix.

Lastly and most importantly:

No matter what features a compiler has, it is totally worthless if the compiler is bug-ridden.  IMO, the first and foremost goal of a compiler should be to be bug-free and after that include/adopt useful features (ideally without introducing new bugs in the process.)

IOW, if new features means more bugs, count me out.  I'm already saturated dealing with current FPC bugs.

That's a start.  My $0.25 (it would have been $0.05 about 30 days ago.)
FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

n7800

  • Hero Member
  • *****
  • Posts: 710
  • Lazarus IDE contributor
    • GitLab profile
proper test coverage... more than once I've looked in the FPC source looking for the tests applied to a particular feature and, sadly, either found nothing or what I found was, putting it very kindly, "incomplete".

Please report specific missing tests on the FPC bug tracker.

n7800

  • Hero Member
  • *****
  • Posts: 710
  • Lazarus IDE contributor
    • GitLab profile
The description states:
Quote
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.

I think this needs to be changed. I'm not particularly a fan of the "inlinevars" feature, with the exception of loops, which I find not only convenient but also error-free. I'm primarily referring to the error of using loop variables outside of a loop.

440bx

  • Hero Member
  • *****
  • Posts: 6540
Please report specific missing tests on the FPC bug tracker.
TBH, the majority of the times I look for tests for one feature or another, I usually find it hard to find them and often just give up in frustration.  What I'm saying is, I don't really know if the tests are there or not, they may be someplace but, I simply looked in all the wrong places and as a result didn't find them.

The last time I was looking for tests was when looking for tests related to declaring bit fields.  I looked for those tests because I was surprised there were still bugs in v3.2.2 in spite of the fact that I reported some bitfield bugs in v3.0.4.

The problem at this point is that, I'm a bit frustrated and discouraged, as a result, I give up more easily.  Also, I have stopped reporting what could be considered "deficiencies".



The description states:
Quote
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.

I think this needs to be changed.
I completely agree.  Inline variables should be a mechanism to enhance locality and modularity.  The fact that they have the same scope as regular local variables creates "spaghetti" data.  It's a significant step _backwards_ and it is also the opposite of how they are used in other languages that support inline variables, e.g, C/C++
FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 12429
  • Debugger - SynEdit - and more
    • wiki
Well, something slightly off topic.

Debugging properties of objects. (And other shortcomings).

This is something that should go into the main fpc, but maybe if developed on the fork could be merged back? That is if the creator(s) of the fork have time and interest to work on that.

It would all be about the dwarf generated by FPC. (and some to be added to FpDebug, but that goes to my todo list).

Zvoni

  • Hero Member
  • *****
  • Posts: 3399
in regards to inline var: everything necessary has been said.

I feature i really miss (and i don't consider it a personal itch):

e.g. in Visual Basic the following is possible
(and as far as i know it's not possible in Pascal)
Code: [Select]
Dim i As Long
    Select Case True
        Case i = 0
            Debug.Print "i equals zero"
        Case i > 100
            Debug.Print "i is greater than 100"
        Case i < -50
            Debug.Print "i is smaller than -5"
        Case Else
            Debug.Print "Doh!"
    End Select

As Aircode in Pascal it would look like this
Code: Pascal  [Select][+][-]
  1. Var i:Integer; //or whatever other Datatype
  2. //......
  3.   Case True Of
  4.     i=0    : Writeln('i equals zero');
  5.     i>100 : Writeln('i is greater than 100');
  6.     i<-50 : Writeln('i is smaller than -50');
  7.     Else Writeln('Doh!');
  8.   End;

As of right now, something like above is currently only achievable with a full "If..Then...Else If"-Tree

As a second feature for "Case..Of":
Fallthrough. Maybe with a modifier
Kinda like (and yes: I'm aware you have to be careful to set a "Break" in the correct place)
Code: Pascal  [Select][+][-]
  1. Var i:Integer;
  2.   i:=10;
  3.   Case i Of FallThrough
  4.      0 : Writeln('i=0');  //No execution
  5.      5 : Writeln('i=5');  //No execution
  6.      10: Writeln('i=10');  //Gets executed
  7.      20: Writeln('i=20');  //Gets executed
  8.   End;
  9.  
or like in C++17 (though there it's more to do with supressing a warning, since Fallthrough is the Standard-behavior)
Code: Pascal  [Select][+][-]
  1. Var i:Integer;
  2.   i:=10;
  3.   Case i Of
  4.      0 : Writeln('i=0');  //No execution          
  5.      5 : Writeln('i=5');  //No execution
  6.      10: Begin
  7.               Writeln('i=10');  //Gets executed
  8.               FallThrough;  //!!!
  9.            End;
  10.      20: Writeln('i=20');  //Gets executed!!!!
  11.   End;
  12.  

Now if you take both "features" from above, something like this becomes possible
(instead of writing a Christmas-Tree of "If.. Then")
(Aircode)
Code: Pascal  [Select][+][-]
  1. Case True Of FallThrough
  2.   i=0 : Writeln('i=0');
  3.   s='Zvoni' : Writeln('User=Zvoni');
  4.   d>1.5 : writeln('d>1.5');
  5. End;

EDIT: I forgot: in Visual Basic it's possible to use multiple Boolean-Expressions inside a Case-label
Code: [Select]
Dim i As Long
    Select Case True
        Case i = 0
            Debug.Print "i equals zero"
        Case i > 100 And User="Zvoni"  //<-- THIS ONE
            Debug.Print "i is greater than 100 and the User=Zvoni"
        Case i < -50 Or User="Laura"   //<-- THIS ONE
            Debug.Print "i is smaller than -5 or the User=Laura"
        Case Else
            Debug.Print "Doh!"
    End Select
Now imagine the usecase of this feature
« Last Edit: March 19, 2026, 03:01:03 pm by Zvoni »
One System to rule them all, One Code to find them,
One IDE to bring them all, and to the Framework bind them,
in the Land of Redmond, where the Windows lie
---------------------------------------------------------------------
Code is like a joke: If you have to explain it, it's bad

 

TinyPortal © 2005-2018