Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add @default attribute #236

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
242 changes: 242 additions & 0 deletions DIPs/1NNN-DK.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
# `@default` attribute

| Field | Value |
|-----------------|-----------------------------------------------------------------|
| DIP: | (number/id -- assigned by DIP Manager) |
| Review Count: | 0 (edited by DIP Manager) |
| Author: | Dennis Korpel ([email protected]) |
| Implementation: | |
| Status: | Will be set by the DIP manager (e.g. "Approved" or "Rejected") |

## Abstract

Add a `@default` attribute, which removes the effect of lexically preceding attributes.

## Contents
* [Rationale](#rationale)
* [Prior Work](#prior-work)
* [Description](#description)
* [Breaking Changes and Deprecations](#breaking-changes-and-deprecations)
* [Reference](#reference)
* [Copyright & License](#copyright--license)
* [Reviews](#reviews)

## Rationale

D supports [many attributes](https://dlang.org/spec/attribute.html).
When a multitude of attributes needs to apply to a multitude of declarations, it can lead to visual noise from
repeating the attribute names, colloquially called 'attribute soup'.

```D
struct S
{
private void f() const @nogc nothrow pure @safe
{

}

private void g() const @nogc nothrow pure @safe
{

}

// ...
}
```

One way to mitigate this is by using the `Attribute:` syntax, which will make the attribute apply to all following declarations until the end of the scope the attribute appears in.

```D
struct S
{
@nogc nothrow pure @safe:
private:
const:

void f()
{

}

void g()
{

}

// ...
}
// attributes' influence ends here
```

This reduces the repetition of attributes, but it becomes a problem when there are a few declarations that need an exception.
Sometimes this can be solved by annotating the exceptional declaration with an inverse attribute:

| Attribute | Undone by |
|--------------------------|---------------------|
| `private` | `public` |
| `@system` | `@safe` |
| `extern(C)` | `extern(D)` |
| `nothrow` | `throw` |
| `pragma(inline, true)` | `pragma(inline)` |

However, most attributes do not have an inverse that resets it to the default:

- `align()`
- `deprecated`
- `static`
- `extern`
- `abstract`
- `final`
- `override`
- `synchronized`
- `scope`
- `const`
- `immutable`
- `inout`
- `shared`
- `__gshared`
- `pure`
- `@disable`
- `@nogc`
- `@property`
- `@UserDefinedAttribute`

Moreover, because function attributes are inferred in template functions, forcing them back to the default is not always desirable either.
Consider:

```
struct S
{
@nogc nothrow pure @safe:

void f() { }

void g() { }

void toString(O)(ref O sink) if (isOutputRange!(O, char))
{
}
}
```

In this case, the `toString` method should not force the provided Output Range to have an implementation of `sink.put` with all function attributes, but
neither should `toString` be denied those attributes when it can infer them.
The only option here is to place all template methods above the `@nogc nothrow pure @safe:` line, or go back to 'attribute soup' by repeating all function attributes for individual members.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When writing the only, there should come 1 option, not two. Also, a third option exists: @attribute { … }.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When writing the only, there should come 1 option, not two. Also, a third option exists: @attribute { … }.

Having to end an attribute{ /*...*/ } block and start it again just for 1 declaration is a hassle. Meanwhile, inverting an attribute (@gc/throw) or using @default helps the reader understand that the block they're in has a @nogc/nothrow or non-default attribute applied to it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When writing the only, there should come 1 option, not two. Also, a third option exists: @attribute { … }.

You're right, I'll rewrite it


What is really desired is something to specify that attributes on `toString` should go back to the default state,
which this DIP proposes in the form of the `@default` attribute.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Especially for the case of templates, alternatively, we could change the language so that attributes are inferred unless they are applied directly to the template. Colon and brace attributes need not matter. With the proposal, @default is a niche thing I have to remember.


## Prior Work

- [DIP 1029 - Add throw as Function Attribute](http://dlang.org/dips/1029)
The `throw` attribute was added to address the lack of a way to invert the effect of `nothrow:`.
However, it has not been implemented yet, and can be superseded by this DIP.

- [DIP 1012 - Function Attributes](http://dlang.org/dips/1012)
Proposes to move function attributes to `core.attribute` enabling traditional methods of attribute manipulation (`AliasSeq`) and introspection.
This DIP has been abandoned.

## Alternatives

It has been proposed before that attributes could take a boolean parameter evaluated at CTFE (e.g. `@safe(x && y)`), or reset to the default by passing the `default` keyword as a parameter.
This allows more precision, but it is more complex and verbose:

```D
@nogc nothrow pure @safe:

// ...

void f(T)(T x) @nogc(default) @nothrow(default) pure(default) @safe(default)
Copy link
Contributor

@Bolpat Bolpat Dec 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On a template, it is not clear what @safe(default) even means. On a regular function, @safe(default) is the same as @system, but on a template, it seems to be mean infer. Better: Just use “infer” instead of “default” everywhere or make “default” mean “infer attributes” everywhere.
Inferring could be done with @safe(auto) or something.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not part of the proposal, it's listed as an alternative and criticized for its verbosity. Whether you call it default or infer or auto, it doesn't change the point.

{

}
```

Another option is to use `@default` to specify default function attributes for regular functions, but not functions with attribute inference.
```D
struct S
{
@default @nogc nothrow pure @safe:

void f() {} // attributes apply here

auto g(T)() {} // does not apply here
}
```
However, this does not provide a way to invert attributes that aren't inferred.
Neither does it provide a way to invert function attributes on functions without attribute inference.

```
struct S
{
@default @nogc const:

void usesGc() {} // out of luck, not a template

void mutates(T)() {} // out of luck, `const` is not inferred
}
```

### Syntax

The syntax `@default` is chosen because `default` is already a reserved keyword, but it may not be the clearest name.
A different name, such as `@reset`, could be considered, despite it potentially being used as a User Defined Attribute already.

The `@` could be omitted when using the `default` keyword, but it is suggested to keep it to make it clear it's related to attributes, and not types or the `default` statement.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a declaration context, nothing is nowhere near a statement, including default statement in D. A lot of function attributes have no @ in front of them.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't get what you're saying. ("nothing is nowhere near a statement"?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A lot of function attributes have no @ in front of them.

That's the converse of what I'm saying. Things starting with @ imply attributes, that doesn't mean it holds the other way around as well.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default keyword is only used for the default: case in a switch statement; the spec calls this a DefaultStatement. Function attributes are quite far removed from switch statements, there is no possible conflation. (I hope this clarified the confusing sentence.)
Having a keyword after @ would be quite novel, actually. The @ attributes were introduced as a workaround because the desired keyword (safe, trusted, system, nogc, property, disable, and live) was a legal identifier and not already a keyword, and making it one would have resulted in breakage, and because Walter opposes contextual keywords. With the @, it resulted in (almost) no breakage. For default, this reasoning does not apply. When return became a function attribute, it was not @return. I just don’t see why default warrants an exception. The position where default would appear implies “attribute” according to D syntax.


## Description

The `@default` attribute is added as an `AtAttribute`.

```diff
AtAttribute:
+ @ default
@ disable
@ nogc
@ live
Property
@ safe
@ system
@ trusted
UserDefinedAttribute
```

The `@default` attribute is not attached to any declarations.
Instead, it prevents any lexically preceding attributes from applying to the declarations that would have the `@default` attribute applied to them.

```
@safe pure nothrow:
private:

// ...

@default @safe:

void normalFunc() // public, and only @safe
{

}

@default private void templateFunc(R)(R range) // private, all attributes inferred
{
foreach(e; range) {}
}

private @safe pure @default void noAt(); // no attributes apply
```

## Breaking Changes and Deprecations

Since `default` is a keyword, existing User Defined Attributes cannot be called `@default`, so there is no breakage.

## Reference
- [Attributes in the specification](https://dlang.org/spec/attribute.html)

## Copyright & License
Copyright (c) 2022 by the D Language Foundation

Licensed under [Creative Commons Zero 1.0](https://creativecommons.org/publicdomain/zero/1.0/legalcode.txt)

## Reviews
The DIP Manager will supplement this section with a summary of each review stage
of the DIP process beyond the Draft Review.