|
| 1 | +# Argument-dependent attributes |
| 2 | + |
| 3 | +| Field | Value | |
| 4 | +|-----------------|-----------------------------------------------------------------| |
| 5 | +| DIP: | (number/id -- assigned by DIP Manager) | |
| 6 | +| Review Count: | 0 (edited by DIP Manager) | |
| 7 | +| Author: | Mathias 'Geod24' Lang <at gmail dot com> | |
| 8 | +| Implementation: | Work in Progress | |
| 9 | +| Status: | Will be set by the DIP manager (e.g. "Approved" or "Rejected") | |
| 10 | + |
| 11 | +## Abstract |
| 12 | + |
| 13 | +Argument-dependent attributes are a means to express a function's attributes dependence |
| 14 | +on one or more delegate parameter. |
| 15 | + |
| 16 | +They are a backward compatible change, extending the attributes syntax with an optional |
| 17 | +set of parenthesis containing an identifier list, in a fashion similar to that of UDAs. |
| 18 | + |
| 19 | +A funtion fully-utilizing ADAs could look like this: |
| 20 | +```D |
| 21 | +void toString (scope void delegate(in char[]) sink) const |
| 22 | + @safe(sink) pure(sink) nothrow(sink) @nogc(sink) |
| 23 | +{ |
| 24 | + sink("Hello World"); |
| 25 | +} |
| 26 | +``` |
| 27 | + |
| 28 | +## Contents |
| 29 | +* [Rationale](#rationale) |
| 30 | +* [Prior Work](#prior-work) |
| 31 | +* [Description](#description) |
| 32 | +* [Breaking Changes and Deprecations](#breaking-changes-and-deprecations) |
| 33 | +* [Reference](#reference) |
| 34 | +* [Copyright & License](#copyright--license) |
| 35 | +* [Reviews](#reviews) |
| 36 | + |
| 37 | +## Rationale |
| 38 | + |
| 39 | +As of v2.095.0, there is no easy way to write a non-templated function that accepts |
| 40 | +a delegate parameter and allow a wide range of attributes. |
| 41 | + |
| 42 | +For example, when writing a `@safe` function, one is faced with a restrictive choice: |
| 43 | +either marks the delegate as `@safe`, and force the caller to use `@trusted` in some occasions, |
| 44 | +or avoid marking the function itself `@safe`, and not be callable from `@safe` code. |
| 45 | +In general, the former seems to be the prefered approach, as it makes the most sense. |
| 46 | + |
| 47 | +However, this choice is much less obvious for other attributes: forcing `nothrow` |
| 48 | +or `pure`ness is a less-accepted practice, let alone how restrictive forcing `@nogc` is. |
| 49 | + |
| 50 | +This problem can be seen in many widely used library, such as Vibe.d's delegate-accepting |
| 51 | +[`requestHTTP`](https://vibed.org/api/vibe.http.client/requestHTTP), |
| 52 | +or even druntime's [`Throwable.toString`](https://github.com/dlang/druntime/blob/d97ec4093b108dc2fa95f1fa04b1114e6e0611f8/src/object.d#L2020-L2026). |
| 53 | +It is also commonly seen when implementing `opApply`, as one usually has to choose between working type-inferencex |
| 54 | +(e.g. `foreach (varname; container)` as opposed to `foreach (type varname; container)`), |
| 55 | +which only works if the delegate type is known and not templated, or supporting multiple |
| 56 | +attributes, which is done by templating the delegate type. |
| 57 | + |
| 58 | +This proposal adds the ability to express the common dependency between a delegate's |
| 59 | +attributes and a function's attribute. |
| 60 | + |
| 61 | +## Prior Work |
| 62 | + |
| 63 | +[DIP1032](DIP1032.md) has proposed to always have the delegate parameters take the |
| 64 | +attributes of the function that receives it. |
| 65 | +However, this does not address the problem presented here: instead, it forces |
| 66 | +the user to require from the caller the attributes it supports. |
| 67 | + |
| 68 | +Doing so usually leads to attributes being faked, or the routine being unusable: |
| 69 | +for example, iterating over a collection might be a simple operation which is |
| 70 | +`pure` and `@nogc`, but requiring the delegate to also be is too limitating. |
| 71 | + |
| 72 | +## Description |
| 73 | + |
| 74 | +### Grammar |
| 75 | + |
| 76 | +The following changes to the grammar are proposed: |
| 77 | +```diff |
| 78 | + StorageClass: |
| 79 | + LinkageAttribute |
| 80 | + AlignAttribute |
| 81 | + deprecated |
| 82 | + enum |
| 83 | + static |
| 84 | + extern |
| 85 | + abstract |
| 86 | + final |
| 87 | + override |
| 88 | + synchronized |
| 89 | + auto |
| 90 | + scope |
| 91 | + const |
| 92 | + immutable |
| 93 | + inout |
| 94 | + shared |
| 95 | + __gshared |
| 96 | + Property |
| 97 | + nothrow |
| 98 | ++ nothrow AttributeDeclDef |
| 99 | + pure |
| 100 | ++ pure AttributeDeclDef |
| 101 | + ref |
| 102 | + |
| 103 | + AtAttribute: |
| 104 | + @ disable |
| 105 | + @ nogc |
| 106 | ++ @ nogc AttributeDeclDef |
| 107 | + @ live |
| 108 | + Property |
| 109 | + @ safe |
| 110 | ++ @ safe AttributeDeclDef |
| 111 | + @ system |
| 112 | + @ trusted |
| 113 | + UserDefinedAttribute |
| 114 | + |
| 115 | ++AttributeDeclDef: |
| 116 | ++ ( * ) |
| 117 | ++ ( AttributeDeclDefs $(OPT) ) |
| 118 | + |
| 119 | ++AttributeDeclDefs: |
| 120 | ++ AssignExpression |
| 121 | ++ AssignExpression, AttributeDeclDefs ... |
| 122 | +``` |
| 123 | + |
| 124 | +This syntax was choosen as it should feel familiar to the user, |
| 125 | +who can already encounter it when using UDAs. |
| 126 | + |
| 127 | +### Basic semantic rules |
| 128 | + |
| 129 | +If the argument list form is used, the argument must be either identifiers |
| 130 | +or integers. If identifiers, they must match the identifiers of one of |
| 131 | +the function's arguments, and this argument must be a delegate or function pointer. |
| 132 | +If integers, the value must be positive and at most one less than the function's |
| 133 | +arity (number of parameter), included. The value must be the 0-based index of the |
| 134 | +argument the attribute depends on. Likewise, this argumment needs to be a |
| 135 | +delegate or function pointer. |
| 136 | +To avoid special cases in meta-programming code, we follow the widespread practice |
| 137 | +of allowing empty lists and trailing commas. |
| 138 | + |
| 139 | +Hence, the following are valid: |
| 140 | +```D |
| 141 | +// Basic usage, with nothrow and @safe |
| 142 | +void func0(void delegate(int) sink) @safe(sink) nothrow(sink); |
| 143 | +// Empty argument list, equivalent to @safe |
| 144 | +void func1() @safe(); |
| 145 | +// Equivalent to func0 |
| 146 | +void func2(void delegate(int)) @safe(*) nothrow(*); |
| 147 | +// Equivalent to func0 |
| 148 | +void func3(void delegate(int) arg) @safe(arg,) nothrow(*); |
| 149 | +// Equivalent to func1 |
| 150 | +void func4(int) @safe(*); |
| 151 | +// Equivalent to func0 |
| 152 | +void func3(void delegate(int) arg) @safe(0) nothrow(0,); |
| 153 | +``` |
| 154 | + |
| 155 | +However, the following are not valid: |
| 156 | +```D |
| 157 | +// Argument name does not exists |
| 158 | +void err0(void delegate() sink) @safe(snk); |
| 159 | +// Integer out of bound |
| 160 | +void err1(void delegate() sink) @safe(1); |
| 161 | +// Argument is not a delegate or function pointer |
| 162 | +void err2(int arg) @safe(arg); |
| 163 | +// Argument is not a delegate or function pointer |
| 164 | +void err3(int arg) @safe(0); |
| 165 | +``` |
| 166 | + |
| 167 | +### Call site checks vs callee checks |
| 168 | + |
| 169 | +When checking the content of a function with ADA, the compiler must enforce the attributes |
| 170 | +applied to the function, *except* when calling the delegate argument. |
| 171 | +Hence, the following would error out: |
| 172 | +```D |
| 173 | +void semanticError(void delegate(in char[]) sink) nothrow(*) |
| 174 | +{ |
| 175 | + if (sink is null) |
| 176 | + throw new Exception("Sink cannot be null"); // Error: `nothrow` function might `throw` |
| 177 | + sink("Hello World"); |
| 178 | +} |
| 179 | +``` |
| 180 | + |
| 181 | +This is invalid because the function is not guaranteed to be `nothrow` even if `sink` is `nothrow`. |
| 182 | +However, as the attribute checks is pushed one level up (to the caller), the following code |
| 183 | +which currently errors out will now succeeds: |
| 184 | +```D |
| 185 | +void func(void delegate(in char[]) sink) nothrow(*) |
| 186 | +{ |
| 187 | + sink("Hello World"); |
| 188 | +} |
| 189 | +
|
| 190 | +void main() nothrow |
| 191 | +{ |
| 192 | + func((in char[] arg) { |
| 193 | + printf("%.*s\n", cast(int) arg.length, arg.ptr); |
| 194 | + }); |
| 195 | +} |
| 196 | +``` |
| 197 | + |
| 198 | +Finally, the check being performed in the caller means that invalid usage will still fail, |
| 199 | +such as in the following example: |
| 200 | +```D |
| 201 | +void func(void delegate(in char[]) sink) nothrow(*) |
| 202 | +{ |
| 203 | + sink("Hello World"); |
| 204 | +} |
| 205 | +
|
| 206 | +void main() nothrow |
| 207 | +{ |
| 208 | + func((in char[] arg) { |
| 209 | + if (!arg.length) |
| 210 | + { |
| 211 | + // Error: `delegate` argument to `func` must be `nothrow` but may `throw` |
| 212 | + // `func` is called from `nothrow` context `main` |
| 213 | + throw new Exception("Length cannot be 0"); |
| 214 | + } |
| 215 | + printf("%.*s\n", cast(int) arg.length, arg.ptr); |
| 216 | + }); |
| 217 | +} |
| 218 | +``` |
| 219 | + |
| 220 | +### Interation with templates |
| 221 | + |
| 222 | +The essence of ADAs targets non-templated code, as if the delegate type is templated, |
| 223 | +then the function's attributes can be infered definitively. |
| 224 | +However, there is nothing preventing the use of ADAs along with templates, |
| 225 | +for example, when the delegate type is not templated (or simply template-dependent): |
| 226 | +```D |
| 227 | +struct Container |
| 228 | +{ |
| 229 | + int opApply (T) (scope void delegate(T)) @safe(*) nothrow(*); |
| 230 | +} |
| 231 | +``` |
| 232 | +In this example, ADAs provide an improvement over the traditional approach: |
| 233 | +the delegate type is not templated, hence deduction is easier and error messages |
| 234 | +are more relevant, and only one function is generated per `T`. |
| 235 | + |
| 236 | +This DIP does not recommend to make ADAs be inferred by the compiler. |
| 237 | +While the idea is attractive, the author estimate that the challenges posed |
| 238 | +by such a feature would at least double the amount of work required to get ADAs working. |
| 239 | + |
| 240 | +## Breaking Changes and Deprecations |
| 241 | + |
| 242 | +The change is purely additive, so no breaking changes are anticipated. |
| 243 | + |
| 244 | +Additionally, an already annotated function can relax its requirements, |
| 245 | +switching from hard attributes to ADA in |
| 246 | + |
| 247 | +## Reference |
| 248 | + |
| 249 | +This idea, and the rationale, was presented with a focus on the problem space at DConf: |
| 250 | +- Presentation: https://dconf.org/2020/online/index.html#mathias |
| 251 | +- Live Q&A in parallel: **TODO** |
| 252 | + |
| 253 | +## Copyright & License |
| 254 | +Copyright (c) 2020 by the D Language Foundation |
| 255 | + |
| 256 | +Licensed under [Creative Commons Zero 1.0](https://creativecommons.org/publicdomain/zero/1.0/legalcode.txt) |
| 257 | + |
| 258 | +## Reviews |
| 259 | +The DIP Manager will supplement this section with a summary of each review stage |
| 260 | +of the DIP process beyond the Draft Review. |
0 commit comments