According to the syntax diagram in the reference guide's section on procedural types (
https://www.freepascal.org/docs-html/ref/refse17.html) they are mutually exclusive modifiers.
The entire reason these two modifiers exist in the first place is to capture the implicit changes to the function signature of methods and nested procedures.
Namely, object methods have the
Self pointer as their actual first argument, making the following technically equivalent:
function TFoo.Bar(Alpha: Integer): Boolean; // Method
function TFoo_Bar(const Self: TFoo; Alpha: Integer): Boolean; // Free-standing function
The special thing about nested procedures is that they can access the enclosing function's local variables & parameters. This is made possible by passing the parent function's frame pointer (describing the general location of local variables) as the first parameter:
function BarOuter(Alpha: Integer): Boolean;
function BarInner(): Boolean; // Nested; Can access "Alpha"
function BarOuter_BarInner(const ParentFP: Pointer): Boolean; // Free-standing; Resolves "Alpha" by adding an offset to "ParentFP"
This single requirement (the ability to access the enclosing function's local variables and parameters) is enough to make nested procedures work inside methods,
because Self is just another parameter/local variable of the parent function; there's no need to explicitly combine these two modifiers -
is nested; is enough.
Knowing all this, we can demonstrate why
{$modeswitch nestedprocvars} can be a bad idea if one isn't careful enough when passing around such function pointers. The crux lies in the loss of type & memory safety when accessing parent local variables (implicitly through
ParentFP); the following code compiles without any warnings and demonstrates why
nested procedure variables aren't closures - surrounding variables are not captured:
program NestedProcedureVariableAbuse;
{$mode objfpc}
{$modeswitch nestedprocvars}
type
TNestedBar = function(X: Integer): Integer is nested;
function GoodBar(): TNestedBar;
function Bar(X: Integer): Integer; // Does not use parent's local variables - OK
begin
Exit(X + 8);
end;
begin
Result := @Bar;
end;
function BadBar(): TNestedBar;
var
Alpha: Integer = 8;
function Bar(X: Integer): Integer; // Uses parent's local variables - DANGEROUS
begin // "Alpha" is equivalent to PInteger(ParentFP)[ARBITRARY_OFFSET]
Exit(X + Alpha); // Optimizations WILL break this in most other contexts
end;
begin
Result := @Bar;
end;
procedure Main();
begin
WriteLn(GoodBar()(42)); // Outputs "50" as expected
WriteLn(BadBar()(42)); // Undefined behaviour; in fact doesn't even work when there's a local Integer variable defined
end;
In essence, procedural function variables are fine to be passed around and called
as long as they don't use parent local variables - otherwise they become incredibly fragile and dangerous. Nowadays function references (
https://forum.lazarus.freepascal.org/index.php?topic=59468.0) are the way to go in these cases.