Default attribute blocks/modules

Quirin Schroll qs.il.paperinik at gmail.com
Thu Jul 4 16:04:36 UTC 2024


I already outlined it 
[here](https://forum.dlang.org/post/efluuipmcdcsknlwqyci@forum.dlang.org). In present-day D, for any module, the default function attributes are `@system`, `throw`, [`@gc`](https://forum.dlang.org/thread/ojjsplombwzzjhjymrnw@forum.dlang.org), and impure. The idea is to allow changing the default for a module or in a block of declarations.

**Note:** Changing the default only affects non-inferred types 
and declarations. Attribute inference for templates or `auto` and 
nested functions are unaffected.

### Syntax

For changing the module default, use this:
```d
default @safe
module m;
```

For changing the block default, use this:
```d
default @safe:
// or
default @safe { … }
```

### Semantics

There is a subtle difference between module defaults and block 
defaults:
Block defaults only affect declarations. Module defaults apply to 
anything lexically in the module which can carry the attribute, 
in particular, function pointer and delegate types.

That means that in a `default @safe` module, every function 
declaration and every function pointer or delegate type lexically 
in that module will be `@safe` unless marked `@system`. Blocks 
can override the module default for declarations.

```d
default @safe
module m;

// Note: module defaults apply to functions and function 
pointer/delegate types
// spelled out in the module: `callback` is implicitly `@safe`
int f(int function() callback) => callback();
static assert(is(typeof(&f) == int function(int function() @safe) 
@safe));


// Note: `g` is not inferred; default (@safe) applies
void g()
{
     int* p;
     int x;
     p = &x; // Error: address of variable `x` assigned to `p` 
with longer lifetime
}

// Note: `h` is inferred @system; defaults are irrelevant
auto h()
{
     int* p;
     int x;
     p = &x; // Okay, makes `h` a `@system` function
}
static assert(!is(typeof(&h) : void function() @safe));
```

Default blocks are similar to normal blocks, except they don’t 
directly affect inference:

```d
module m;

default @safe:

// Note: block defaults only apply to declarations (e.g. 
functions),
// but not function pointer/delegate types that are parameters or 
return types.
// For `callback`, the module default applies, which is unset, 
i.e. `@system`.
int f1(int function() callback); // => callback(); // Error
static assert(is(typeof(&f1) == int function(int function() 
@system) @safe));

// An alias is a declaration, so the block default applies,
// and `FP` is `int function() @safe`.
alias FP = int function();
int f2(FP callback) => callback();
static assert(is(typeof(&f2) == int function(int function() 
@safe) @safe));


// Note: `g` is not inferred; default (@safe) applies
// (same as above)
void g()
{
     int* p;
     int x;
     p = &x; // Error: address of variable `x` assigned to `p` 
with longer lifetime
}

// Note: `h` is inferred @system; defaults are irrelevant
// (same as above)
auto h()
{
     int* p;
     int x;
     p = &x; // Okay, makes `h` a `@system` function
}
static assert(!is(typeof(&h) : void function() @safe));
```

The behavior of block defaults is consistent with attribute 
blocks, which likewise affect only declarations, but not function 
parameters or return types of function pointer or delegate types.

```d
// Note: Not a default, this is current-day D semantics.

@safe:

int f1(int function() callback);
static assert(is(typeof(&f1) == int function(int function() 
@system) @safe));

alias FP = int function();
int f2(FP callback) => callback();
static assert(is(typeof(&f2) == int function(int function() 
@safe) @safe));
```

### Grammar

```diff
     ModuleDeclaration:
-       ModuleAttributes? module ModuleFullyQualifiedName ;
+       ModuleAttributes? ModuleDefaultAttributes? module 
ModuleFullyQualifiedName ;
+
+   ModuleDefaultAttributes:
+       default DefaultAttributeList

     ModuleAttributes:
         ModuleAttribute
         ModuleAttribute ModuleAttributes

     ModuleAttribute:
         DeprecatedAttribute
         UserDefinedAttribute
```

```diff
     DeclDef:
         AttributeSpecifier
+       DefaultAttributeSpecifier
         …

+   DefaultAttributeSpecifier:
+       default DefaultAttributeList :
+       default DefaultAttributeList { DeclDefs? }
+
+   DefaultAttributeList:
+       DefaultAttribute DefaultAttributeList?
+
+   DefaultAttribute:
+       pure
+       nothrow
+       @ safe
+       @ nogc

     Attribute:
         …
         const
+       default ( DefaultAttributeList )
         final
         …
```
This grammar allows `pure default @safe @nothrow static` 
(followed by `{…}` or `:`) and by maximum munch, would mean that 
`default` applies to `@safe` and `@nothrow`, but not `static`. 
The language should reject that and require
`pure default(@safe nothrow) static`, or else
`pure{ default @safe nothrow: static: … }` or
`pure: default @safe nothrow: static:`, respectively.
That is, `default` without parentheses is only allowed with 
`default` in front and no trailing that can’t be a default.

The downside of `default(@safe)` is that it somewhat suggests 
it’s an attribute of its own, but it would only be allowed for 
blocks and module declarations, but not on declarations directly, 
where it makes little sense:
* On non-inferred declarations, `default(@safe)` is `@safe`.
* On inferred declarations, `default(@safe)` means nothing.


More information about the dip.ideas mailing list