| 
 | 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