RFC: Last words on Primary Type Syntax aka. `ref` return function pointer type sytnax

Quirin Schroll qs.il.paperinik at gmail.com
Thu Jul 3 17:31:17 UTC 2025


I have to apologize to Walter in particular for not posting this 
earlier. Since the meeting where we discussed the question how to 
handle ambiguities related to keywords that have a meaning on 
their own (“lone”) and when given arguments in parentheses and 
possible solutions to it.

For those interested, the ambiguity arises from a type like `ref 
int function()` (one that requires parentheses in many contexts) 
when one wants to use it after such a keyword. A motivating 
example:
```d
static int x;
scope (ref int function()) fp = ref () => x;
```
The above is obviously intended to be a declaration of a scope 
variable, but it could also parse as a scope guard, since by 
design, scope guards take a sequence of tokens with balanced 
parentheses as argument to allow for adding new scope guards.

A comprehensive table. If anyone finds another keyword, let me 
know.

| Keyword       | Lone                   | With arguments         
  |
|---------------|------------------------|-------------------------|
| `scope`       | [scope attribute][1]   | [scope guard][2]       
  |
| `align`       | default [alignment][3] | set [alignment][3]     
  |
| `extern`      | [extern attribute][4]  | [linkage attribute][5] 
  |
| `deprecated`  | [warn on use][6]       | [warn w/ msg on 
use][6] |

| Construct        | Technically applicable to | Useful to apply 
to        |
|------------------|---------------------------|---------------------------|
| scope attribute  | any variable              | locals, 
parameters        |
| scope guard      | statement                 | statement        
          |
| alignment        | any symbol but parameters | 
global/member/local vars† |
| extern attribute | any symbol                | globals          
          |
| linkage          | any symbol or FP/DG type‡ | any symbol or 
FP/DG type  |
| deprecated       | any symbol                | any global 
public symbol  |

(† Alignment is not semantically applied to local variables when 
their type is inferred, which is a bug. It also cannot be applied 
to function parameters.)
(‡ *FP/DG type* stands for *function pointer or delegate type.*)

## The solution proposed in the DIP

### Officially

Do nothing for `align`, `extern`, and `deprecated`. They’re 
greedy as of now and stay like that; that is, if an opening 
parenthesis follows, it’s the argument list to the with-arguments 
construct, thus it’s absolutely never the lone construct.

The `scope` keyword means the `scope` attribute unless: In a 
statement block, if `scope` is the first token of a statement, 
it’s a scope guard if and only if:
    * It follows the pattern `scope` `(` *Token* `)`, where 
*Token* is a single token;
    * or it follows the pattern `scope` `(` *Tokens* `)` `{`, 
where *Tokens* is a sequence of tokens with parentheses balanced.

For reference, the condition for `scope` in the current form of 
the language is: “The `scope` keyword means the `scope` attribute 
unless: In a statement block, if `scope` is the first token of a 
statement.”

### Informally

#### `align`, `extern`, and `deprecated`

If you’re in a hurry, use an attribute block if the attribute is 
used outside of a statement scope. It may not be pretty, but it 
gets the job done.

Specific alternatives that should work in statement and 
declaration scope:

**`align`** ― Use `align(default)`. If you care about alignment, 
you know `align(default)` exists.

**`extern`** ― Add the linkage in scope: `extern extern(D)`, 
which is probably even informative to readers. If you need 
`extern` variables, you also deal with linkage, so this isn’t 
news.

**`deprecated`** ― Add a message.

#### `scope`

Among the goals of the DIP is to enable programmers to declare 
`scope` variables of FP/DG type in the initially presented, 
straightforward manner with no strings attached.

Being statements, scope guards are naturally limited to statement 
blocks, so special-casing `scope` everywhere (e.g. global scope, 
function parameter lists) to fix statements in particular doesn’t 
feel right to me. The DIP retains existing scope guards and even 
allows for potential future scope guards of now two distinct 
forms:
* Single-token scope guards work exactly like the existing ones.
* Multi-token scope guards (should any ever be proposed) will 
have to govern a block statement.

 From the sacrifice in the second bullet point, we gain that the 
following works as intended. The comment describes the *current* 
parsing.
```d
scope (ref int function()) fp = …; // ill-formed scope guard
// distinct from
scope ref int function() fp = …; // reference variable declaration
```

Timon Gehr in particular has stated interest in putting 
clarifying parentheses around function pointer and delegate types 
that don’t strictly need the parentheses:
```d
scope (int function()) fp = …; // ill-formed scope guard
// same as
scope int function() fp = …; // variable declaration
```

If the type of the variable being declared is expressed a single 
token, e.g. `int` or `FP`, `scope (int)` and `scope (FP)` parse 
as (ill-formed) scope guards. That means, you actually can’t put 
parentheses around a type and get a type in this specific 
context. It’s the only exception to this core principle, and it 
has to exist to retain existing scope guards. Not many will 
notice, for why would you put parentheses around a single-token 
type anyway? On the other hand, double parentheses are absolutely 
guaranteed to work: With the proposed changes, `scope 
((`*Tokens*`))` will never parse as anything but lone `scope` and 
a type. This isn’t relevant for people writing code directly in a 
text editor, but it is relevant for code generation via string 
mixins. The need for generating a `scope` variable can come up 
where the type of that variable cannot be inferred and might be 
single-token or require parentheses. In that case, `"scope ((" ~ 
typeAsString ~ ")) varName"` works for both `typeAsString  = 
"int"` and `typeAsString = "ref int function()"`.

Of course, another way to avoid introducing a scope guard is 
ensuring `scope` isn’t the first token. If other storage classes 
apply, or could be applied without harm, they can be prepended. 
That isn’t possible or pretty in common cases.

## What Walter takes issue with

He didn’t like that the four constructs in question needed *all* 
different approaches. This might have been due to my lack of 
English speaking skills (I’m German) and a good bit of 
nervousness, because it’s not exactly true.

He’s right insofar as it would be bad *if* the four constructs 
actually needed lots of different approaches. The details before 
should have made clear that a one-size-fits-all approach works to 
a large degree: An attribute block solves 3 out of 7 cases: lone 
`align`, `extern`, and `deprecated` at declaration scope. The 
remaining 4 cases are lone `scope`, `align`, `extern`, and 
`deprecated` at statement scope.

If you only consider what’s meaningful, that removes two cases: 
`extern` and `deprecated` at statement scope, i.e. we’re at 3 out 
of 5.

The approach of the DIP for `scope` in statement scope is to make 
it work if you do what’s intuitively clear. Since `scope` is also 
a function parameter attribute, there’s an argument to be had 
that `scope (ref int function())` and `scope (int 
function())`―which are both valid as parameter 
declarations―should both be valid as local variable declarations. 
Of course, that cannot be guaranteed.

The only somewhat sad case is lone `align` in statement scope. 
Since this only affects code that’s yet to be written, it’s not 
clear if lone `align` will be used much, given how much clearer 
`align(default)` is. There’s a good chance lone `align` will be 
deprecated, even.

## What Walter proposed in the meeting

Walter’s idea was to handle those ambiguities by the following 
rule: If what follows one of the four keywords inside parentheses 
pareses as a type and that type *requires* parentheses, then it’s 
the lone construct; otherwise parse as the with-arguments 
construct.

### The upside

It’s a uniform approach and it works. Should I have missed a 
keyword, the likelihood it works for it as well is near 100%.

### The downsides

In short:
1. In some contexts, no type requires parentheses.
2. There are types that require parentheses in some context, but 
not in another context, but some other type does require 
parentheses there.
3. Even from a technical standpoint, determining if a type 
requires parentheses in a specific context isn’t difficult as of 
now.
4. That might be subject to change, though.
5. What types require parentheses isn’t necessarily clear to a 
programmer. Even when it is, one might *want* to use parentheses 
even if they’re not necessary (cf. T. Gehr) for one’s own clarity 
or style.

Details:
1. Those contexts are where nothing but a type is expected (e.g. 
the argument to `cast` or `typeid`) or where parsing a type is 
prioritized (e.g. a template argument). Those are of little 
concern.
2. Those types are FP/DG types with linkage, e.g. `extern(C) int 
function()`. They don’t need parentheses in parameter lists and 
similar contexts, but require parentheses when used e.g. as 
function return types or variable types. See 
[DIP1049 § Linkage][7] for details.
3. The DMD frontend functionality for determining if a type needs 
parentheses would be completely new. It goes somewhat against the 
quest to keep the compiler simple. With this DIP alone, it’s a 
simple check if the type starts with `extern` or `ref`.
4. This is related in particular to tuple DIPs that use 
parentheses to denote tuples. Those make `(T, U)` a type that 
requires parentheses, where a lookahead of arbitrary length is 
needed to find the comma.
5. See below.

If superfluous parentheses proliferate in some styles in some 
contexts, programmers could assume they’re required from 
experience. An example for an assumption of that kind that I had 
for ~10 years: D didn’t require `auto` and `ref` to be adjacent 
until last 2.110, but I thought it did, and it actually did in 
one context. Linters might suggest them where they change 
behavior because they interact with the token preceding them. 
That is, it complicates writing a good linter. The DIP mentions 
`(const int)` as an example of what’s newly allowed, and that of 
course extends to `(const Ptr)` which, if `Ptr` is an alias to a 
type with indirections (pointer, slice, function pointer, 
delegate, class, …), makes sense to use with `scope`. Thus, 
`scope (const Ptr)` might be the preferred style of some 
programmer or linter, but since `const Ptr` doesn’t require 
parentheses, it would render the construct an ill-formed scope 
guard. Of course one *can* use `scope const(Ptr)` or `scope const 
Ptr` or even `const scope Ptr`, but the whole point of this is 
that one might not want to. This is similar to east-const vs. 
west-const in C++, where if you prefer east-const, it’s annoying 
that it’s not available for pointer types:
  * `int const*` west-const available;
  * `const int*` east-const available;
  * `int* const` west-const required, thus
  * there’s no way to express the latter using east-const style.

A more relevant example would be an array or slice of function 
pointers. There’s a stylistic urge to write `(void function())[]` 
instead of `void function()[]`, but the proposed parsing rule 
locks one in: With `scope`, it’s `scope void function()[]` 
without `ref` and `scope (ref void function())[]` or `scope (ref 
void function()[])`, but not `scope ((ref void function())[])` 
with `ref`. The last one is excluded because, given the inner 
parentheses, the outer ones aren’t required anymore.

## Last notes

I implemented the DIP as submitted with one exception that’s 
noted in the DIP: Explicit linkage not supported for template 
lambda, except for alias. Anyone can play around with the 
implementation. I haven’t had time to update it to incorporate 
the latest changes. There are probably a few bugs.

I have made no attempt to implement Walter’s suggestion. 
Something like that requires time I don’t have right now.

---

It’s a long post, there are probably errors and things I could’ve 
explained or phrased better. Sorry in advance. Any sort of 
comment is welcome.

[1]: https://dlang.org/spec/attribute.html#scope
[2]: https://dlang.org/spec/statement.html#scope-guard-statement
[3]: https://dlang.org/spec/attribute.html#align
[4]: https://dlang.org/spec/declaration.html#extern
[5]: https://dlang.org/spec/attribute.html#LinkageAttribute
[6]: https://dlang.org/spec/attribute.html#deprecated
[7]: 
https://github.com/dlang/DIPs/blob/master/DIPs/DIP1049.md#linkage


More information about the Digitalmars-d mailing list