[fpc-devel] C-block reference syntax (blocker for 3.2)

Blaise at blaise.ru Blaise at blaise.ru
Sat Dec 7 01:00:00 CET 2019

Beforehand, to provide a context, here are some excerpts from private correspondence:

On 11.12.2016 22:00:37 +0300, Blaise at blaise.ru wrote in 1c51a35a-bbbc-c99d-232f-0bf6529dfd65 at blaise.ru:
> my progress thus far
> 9) Method reference directives are now allowed:
> 	type M = reference to procedure varargs cdecl;
> DCC-style syntax is used
On 27.06.2017 11:44:40 +0200, Sven Barth wrote in CAFMUeB96Piu=ZntCLASvZM9_pYEkqscFOm_+iRbXJeoNekYpGw at mail.gmail.com:
> the syntax for cblock references was selected on purpose to be compatible with the syntax for Delphi style "reference to" method pointers, only with "cdecl" as calling convention.
On 10.02.2018 14:34:42 +0100, Sven Barth wrote in d86fc6bb-789b-2963-55e9-16bd2a770c7e at googlemail.com:
> It seems that anonymous functions do indeed support calling conventions... dang it... I had hoped to use the calling convention as a marker for the difference which is why I told Jonas to implement cblocks this way. But on the other hand I'd say that it is really unusual to use a calling convention with anonymous functions
On 23.08.2019 06:54:21 +0200, Sven Barth wrote in b1a88e8d-e11e-f430-8a90-2b0b0b27b384 at googlemail.com:
> There isn't much use for "reference to XXX; cdecl;" in the field for normal anonymous functions. Yes, it works, but really, who uses it that way?
> So the solution is simply to have "reference to XXX; cdecl;" be parsed as a cblock if modeswitch CBLOCKS is set and as a normal anonymous function reference otherwise. This way it can be controlled on a per-unit base.

1) In my professional opinion, the current "solution" is a horrible yucky hack: different (albeit similar) entities look exactly the same, and they cannot be used simultaneously.
2) As a compiler developer, my job is to provide orthogonal building blocks and ensure that every conceivable semantically valid combination of them actually works. When one applies reasoning akin to "who uses it that way", they inevitably reduce the power of a language to a subset defined by their often limited understanding of what can and should be possible.
3) My first example back in 2016 intentionally featured a valid use case: CDECL is crucial for VARARGS. (Somehow, not only that case, but the whole point that DCC accepts calling conventions went unnoticed.)

Seeing that introducing this syntax collision was clearly unintentional, I cannot understand the seeming reluctance to remedy it.
I have a couple of proposals:
1) Instead of the CDecl directive, use the C directive:
	type M = reference to procedure c;
Pros: Since DCC does not support this MacPas directive, and old MacPas code does not have the REFERENCE TO syntax, there would be no collision whatsoever.
Cons: Since C and CDECL are semantically the same, these syntaxes still look confusingly the same:
	type M = reference to procedure cdecl;	// closure
	type M = reference to procedure c;	// C-block
I wonder from where that desire to have the Delphi-like syntax for C-block references came. If it stemmed from the assumption that having the same syntax would simplify the compiler, then we have a solid basis for changing it, because that assumption was wrong. Jonas parses them via procvar_dec, and I parse closure reference types via parse_proc_dec under symtablestack.push(invokable_interface.symtable), and it makes perfect sense since such types /are/ interfaces (to the point that they are directly implementable on classes).
2) Same as (1), but without REFERENCE TO:
	type M = procedure c;
Pros: No collision with DCC. Easy to parse.
Cons: Collision with procedural types in MacPas. Since, presumably, MacPas is for compatibility with legacy code, this collision is less of a problem than the collision with DCC; but a collision nevertheless.
3) Attributes:
	type [CBlock] M = reference to procedure;
	type [CBlock] M = procedure;
Pros: In my book, the most elegant solution.
Cons: Attributes are applied to symbols, not type definitions; which is problematical for nameless type definitions, parsing, and semantics.
4) Distinct syntax, without adding new keywords:
	type M = procedure with var;	// a closure is literally a "procedure with [captured outer] variables"
	type M = procedure with record;	// captured variables are kept in a kind of record
	type M = procedure with out name;	// clever!
	type M = procedure is c;	// meaning "is [a] c[-block]"
5) Distinct syntax with a new keyword:
	type M = procedure cblock;

If there is a consensus that this is a blocker, I am ready to do the work ASAP. provided we select the syntax. Personally, I am inclined towards these three:
	type M = procedure (const N: Integer) with var;
	type M = procedure (const N: Integer) cblock;
	type M = reference to procedure (const N: Integer) c;
Again, in the last case (like with the current collision), the non-existent unified C-block/closure reference parser would not be able to reuse as much existing infrastructure as two separate parsers currently do reuse (but do not coöperate).
(Also, it appears that there is a couple of C-block-related bugs, which I am ready to tackle as well.)


More information about the fpc-devel mailing list