Thread overview
D Primary Type Syntax is Essentially Implemented!
May 23, 2023
Quirin Schroll
May 24, 2023
Andrej Mitrovic
May 24, 2023
Olivier Pisano
May 24, 2023
Quirin Schroll
May 24, 2023
Quirin Schroll
May 24, 2023
Basile B.
May 24, 2023
Quirin Schroll
May 23, 2023

Recap

“D primary type syntax” is this recurrence:

TypeBasicType
BasicTypeTypeCtor? (Type) (The novelty is that TypeCtor is optional.)

This allows to add these additional grammar rules:

Typeref TypeCtors? BasicType CallableSuffix TypeSuffixes?
CallableSuffixdelegate Parameters MemberFunctionAttributes?
CallableSuffixfunction Parameters FunctionAttributes?

The goal was to make e.g. (ref const int function() @safe)[] parse as a type. As of today, this is possible.

I borrowed the nomenclature of “primary types” from “primary expressions”:

  • Primary expressions are something like fundamental or parenthesized expressions.
  • Primary types (in D’s grammar: basic types) are something like fundamental types or parenthesized types.

Basic Intro

Parameters

// `a` takes its parameter by value;     the parameter returns by reference.
// `b` takes its parameter by reference; the parameter returns by value.
void a( ref (int function()) ) { }
void b((ref  int function()) ) { }
// Note: The parameter storage class has priority
//       over the value category of the parameter’s return type.

// `c` is `a` without clarifiying parentheses.
void c( ref  int function()  ) { }

static assert(!is( typeof(&a) == typeof(&b) ));
static assert( is( typeof(&a) == typeof(&c) ));

Return types

// `x` returns by reference; the returned function ptr returns an `int` by value.
// `y` returns by vale;      the returned function ptr returns an `int` by reference.
 ref (int function()) x() { static typeof(return) fp = null; return fp; }
(ref  int function()) y() => null;
// Note: The value category of the declared function has priority
//       over the value category of the return type.

// `z` is `x` without clarifiying parentheses.
 ref  int function()  z() => x();

static assert(!is( typeof(&x) == typeof(&y) ));
static assert( is( typeof(&x) == typeof(&z) ));

Satisfying the Inner Monk

Especially pleasing is the congruence between function declaration and function type:

static int i = 0;
 ref int funcName() @safe  => i;
(ref int delegate() @safe) fptr = &funcName;

The parentheses are necessary. Not only is that an artifact of the implementation, but also the way I think it’s clearer. If we ever add ref local variables, they would change meaning.

Error Messages vs. Source Files

I also changed the pretty-print so that ref appears at the beginning of a function pointer or delegate type. This is where it should be according to my eye and the D grammar, but pretty-print gave us ref among pure and @safe. This meant that if you copied the type from the error message to your source file, it would not parse. Now, it will parse. (It might still not compile for other reasons, such as visibility.)

With pragma(msg):

pragma(msg, typeof( function ref int { static x = 0; return x; } ));
// Old: int function() nothrow @nogc ref @safe
// New: (ref int function() nothrow @nogc @safe)

With .stringof:

// Old:
alias FP = ref int function();
static assert(FP.stringof == "int function() ref");

// New:
static assert((ref int function()).stringof == "(ref int function())" );
// No alias required!

The parentheses on the right are somewhat intentional. I didn’t even try to make the pretty-print use parentheses only when necessary because it costs time and is not the main issue. As of now, it mindlessly inserts them. The pretty-print can be improved later.


All the examples are tested and work as intended on my local machine. The code is here. (I know the branch has a stupid name.)

I hope you like it.

May 24, 2023
Good work cleaning this up.

On 24/05/2023 10:18 AM, Quirin Schroll wrote:
> |// `a` takes its parameter by value; the parameter returns by reference. // `b` takes its parameter by reference; the parameter returns by value. void a( ref (int function()) ) { } void b((ref int function()) ) { }|

Are you sure that this is correct?

I read this as `a` takes its parameter by reference; the argument returns by value.

If this isn't what you intended its a little worrying because this is going to cause problems.
May 24, 2023

On Tuesday, 23 May 2023 at 22:18:35 UTC, Quirin Schroll wrote:

>

[...]
I hope you like it.

I like this solution more than the alterantivey proposed.

Both work but yours try to be a bit more faithful to the language. The alternative can look more seducing because it's implemented by someone who knows his shits, so yeah it looks nicer, the checks are greens but it is based on a special case.

I'm very "pro" your way, problem is that currently the implementation is not perfect
(red checks).

May 24, 2023
On Tuesday, 23 May 2023 at 22:39:44 UTC, Richard (Rikki) Andrew Cattermole wrote:
> Good work cleaning this up.
>
> On 24/05/2023 10:18 AM, Quirin Schroll wrote:
>> |// `a` takes its parameter by value; the parameter returns by reference. // `b` takes its parameter by reference; the parameter returns by value. void a( ref (int function()) ) { } void b((ref int function()) ) { }|
>
> Are you sure that this is correct?
>
> I read this as `a` takes its parameter by reference; the argument returns by value.
>
> If this isn't what you intended its a little worrying because this is going to cause problems.

Same, this is completely counter-intuitive to me.
May 24, 2023
On Wednesday, 24 May 2023 at 03:48:38 UTC, Andrej Mitrovic wrote:
> On Tuesday, 23 May 2023 at 22:39:44 UTC, Richard (Rikki) Andrew Cattermole wrote:
>> Good work cleaning this up.
>>
>> On 24/05/2023 10:18 AM, Quirin Schroll wrote:
>>> |// `a` takes its parameter by value; the parameter returns by reference. // `b` takes its parameter by reference; the parameter returns by value. void a( ref (int function()) ) { } void b((ref int function()) ) { }|
>>
>> Are you sure that this is correct?
>>
>> I read this as `a` takes its parameter by reference; the argument returns by value.
>>
>> If this isn't what you intended its a little worrying because this is going to cause problems.
>
> Same, this is completely counter-intuitive to me.

Same here.
May 24, 2023

On Tuesday, 23 May 2023 at 22:39:44 UTC, Richard (Rikki) Andrew Cattermole wrote:

>

Good work cleaning this up.

On 24/05/2023 10:18 AM, Quirin Schroll wrote:

>

|// a takes its parameter by value; the parameter returns by reference. // b takes its parameter by reference; the parameter returns by value. void a( ref (int function()) ) { } void b((ref int function()) ) { }|

Are you sure that this is correct?

I read this as a takes its parameter by reference; the argument returns by value.

If this isn't what you intended its a little worrying because this is going to cause problems.

You're 100% right, I mixed it up. It's exactly the other way around.

May 24, 2023

On Wednesday, 24 May 2023 at 05:15:27 UTC, Olivier Pisano wrote:

>

On Wednesday, 24 May 2023 at 03:48:38 UTC, Andrej Mitrovic wrote:

>

On Tuesday, 23 May 2023 at 22:39:44 UTC, Richard (Rikki) Andrew Cattermole wrote:

>

Good work cleaning this up.

On 24/05/2023 10:18 AM, Quirin Schroll wrote:

>

|// a takes its parameter by value; the parameter returns by reference. // b takes its parameter by reference; the parameter returns by value. void a( ref (int function()) ) { } void b((ref int function()) ) { }|

Are you sure that this is correct?

I read this as a takes its parameter by reference; the argument returns by value.

If this isn't what you intended its a little worrying because this is going to cause problems.

Same, this is completely counter-intuitive to me.

Same here.

It's only the comments for a and b that are reversed. Still a and c are the same, which should have cleared things up. I'm sorry for the confusion. This is so embarrassing. The implementation is sane in your and the other commenters opinion. Unfortunately, the compiler has no AI to check if comments make sense. All the code is tested.

May 24, 2023

On Wednesday, 24 May 2023 at 00:24:38 UTC, Basile B. wrote:

>

On Tuesday, 23 May 2023 at 22:18:35 UTC, Quirin Schroll wrote:

>

[...]
I hope you like it.

I like this solution more than the alterantivey proposed.

Did you link the right PR? It seems you linked my PR calling it the alternative to the post.

>

Both work but yours try to be a bit more faithful to the language. The alternative can look more seducing because it's implemented by someone who knows his shits, so yeah it looks nicer, the checks are greens but it is based on a special case.

This is why I took every minute of time I had to draft an implementation. This was stupidly hard! The compiler (parser at least) isn’t exactly well-documented. I did that because there’s a tendency of quick and dirty solutions to stick. Nichts ist zählebiger als ein Provisorium. (German proverb; “Nothing’s more tenacious than a makeshift.”)

>

I'm very "pro" your way, problem is that currently the implementation is not perfect
(red checks).

This was my first DMD PR that could be called a meaningful attempt. I definitely need help by “someone who knows his shits.” I tried to fix things so that the problems would be solved.


If you (Basile B. or anyone reading this) would like to help, here’s a summary of the failed tests:

  1. CyberShadow/DAutoTest checks that all the examples on the dlang.org website work. The one that fails is an intended breaking: static assert(__traits(isSame, (e), 3)) (where e is an enum with value 3) used to work but now fails.
  2. ci/circleci: build has errors because “template instance core.stdc.config._Complex!float cannot resolve forward reference”.
  3. I worked on the fail_compilation, but they still fail. (This accounts for a lot of failed tests, maybe even all except 1. and 2.)

Comments:

  1. This due to how isSame works: In essence, the reason is that, with the new grammar, (e) is not (necessarily) a primary expression anymore, therefore just putting parentheses around it is unsuited to force the evaluation as an expression; (e) becomes exactly equivalent to e, and e is a symbol, and isSame is always false if you compare a symbol with a literal. There’s two ways to tackle this: Change the example (e.g. use a cast) or change isSame. I’d prefer the former, but I don’t care much.
  2. I don’t know what this means and I cannot reproduce this on my machine. The DMD files I changed I only touch the parser, but for all that I can tell this is a semantic problem. It even says that happened when instantiating the template, which has nothing to do with parsing.
  3. The logs do not show the discrepancies of the expected and actual error messages, so it’s hard to know what is wrong. Maybe someone here knows how to get them.
December 21

On Tuesday, 23 May 2023 at 22:18:35 UTC, Quirin Schroll wrote:

>

Parameters

// `a` takes its parameter by value;     the parameter returns by reference.
// `b` takes its parameter by reference; the parameter returns by value.
void a( ref (int function()) ) { }
void b((ref  int function()) ) { }
// Note: The parameter storage class has priority
//       over the value category of the parameter’s return type.

// `c` is `a` without clarifiying parentheses.
void c( ref  int function()  ) { }

static assert(!is( typeof(&a) == typeof(&b) ));
static assert( is( typeof(&a) == typeof(&c) ));

Return types

// `x` returns by reference; the returned function ptr returns an `int` by value.
// `y` returns by vale;      the returned function ptr returns an `int` by reference.
 ref (int function()) x() { static typeof(return) fp = null; return fp; }
(ref  int function()) y() => null;
// Note: The value category of the declared function has priority
//       over the value category of the return type.

// `z` is `x` without clarifiying parentheses.
 ref  int function()  z() => x();

static assert(!is( typeof(&x) == typeof(&y) ));
static assert( is( typeof(&x) == typeof(&z) ));

The solution seems to be more than what is required to solve the ref problem. It also means we introduce multiple ways of spelling the same type. And as you found, it can break code:
https://forum.dlang.org/post/eguzeiayudwqcvaencdf@forum.dlang.org

Instead, why not implement just the subset of this proposal that is actually needed - add this rule:

BasicType:
    `(` `ref` Type `)`

This rule doesn't allow a or x, yet allows b and y, and works for variable declarations. It doesn't conflict with any expression AFAIK.

And if the full proposal is wanted later, the subset can be extended without breakage.

December 21

On Thursday, 21 December 2023 at 15:34:11 UTC, Nick Treleaven wrote:

>
BasicType:
    `(` `ref` Type `)`

Sorry, that allows invalid types. Should be:

BasicType:
    ( ref Type delegate Parameters MemberFunctionAttributes? )
    ( ref Type function Parameters FunctionAttributes? )