-
-
Notifications
You must be signed in to change notification settings - Fork 97
DIP4242 (?): Argument dependent attributes (ADAs) #198
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,290 @@ | ||
| # Argument-dependent attributes | ||
|
|
||
| | Field | Value | | ||
| |-----------------|-----------------------------------------------------------------| | ||
| | DIP: | (number/id -- assigned by DIP Manager) | | ||
| | Review Count: | 0 (edited by DIP Manager) | | ||
| | Author: | Mathias 'Geod24' Lang <at gmail dot com> | | ||
| | Implementation: | Work in Progress | | ||
| | Status: | Will be set by the DIP manager (e.g. "Approved" or "Rejected") | | ||
|
|
||
| ## Abstract | ||
|
|
||
| Argument-dependent attributes are a means to express a function's attributes dependence | ||
| on one or more delegate parameter. | ||
|
|
||
| They are a backward compatible change, extending the attributes syntax with an optional | ||
| set of parenthesis containing an identifier list, in a fashion similar to that of UDAs. | ||
|
|
||
| A funtion fully-utilizing ADAs could look like this: | ||
| ```D | ||
| void toString (scope void delegate(in char[]) sink) const | ||
| @safe(sink) pure(sink) nothrow(sink) @nogc(sink) | ||
| { | ||
| sink("Hello World"); | ||
| } | ||
| ``` | ||
|
|
||
| ## 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 | ||
|
|
||
| As of v2.095.0, there is no easy way to write a non-templated function that accepts | ||
| a delegate parameter and allow a wide range of attributes. | ||
|
|
||
| For example, when writing a `@safe` function, one is faced with a restrictive choice: | ||
| either marks the delegate as `@safe`, and force the caller to use `@trusted` in some occasions, | ||
| or avoid marking the function itself `@safe`, and not be callable from `@safe` code. | ||
| In general, the former seems to be the prefered approach, as it makes the most sense. | ||
|
|
||
| However, this choice is much less obvious for other attributes: forcing `nothrow` | ||
| or `pure`ness is a less-accepted practice, let alone how restrictive forcing `@nogc` is. | ||
|
|
||
| This problem can be seen in many widely used library, such as Vibe.d's delegate-accepting | ||
| [`requestHTTP`](https://vibed.org/api/vibe.http.client/requestHTTP), | ||
| or even druntime's [`Throwable.toString`](https://github.com/dlang/druntime/blob/d97ec4093b108dc2fa95f1fa04b1114e6e0611f8/src/object.d#L2020-L2026). | ||
| It is also commonly seen when implementing `opApply`, as one usually has to choose between working type-inferencex | ||
| (e.g. `foreach (varname; container)` as opposed to `foreach (type varname; container)`), | ||
| which only works if the delegate type is known and not templated, or supporting multiple | ||
| attributes, which is done by templating the delegate type. | ||
|
|
||
| This proposal adds the ability to express the common dependency between a delegate's | ||
| attributes and a function's attribute. | ||
|
|
||
| ## Prior Work | ||
|
|
||
| [DIP1032](DIP1032.md) has proposed to always have the delegate parameters take the | ||
| attributes of the function that receives it. | ||
| However, this does not address the problem presented here: instead, it forces | ||
| the user to require from the caller the attributes it supports. | ||
|
|
||
| Doing so usually leads to attributes being faked, or the routine being unusable: | ||
| for example, iterating over a collection might be a simple operation which is | ||
| `pure` and `@nogc`, but requiring the delegate to also be is too limitating. | ||
|
|
||
| ## Description | ||
|
|
||
| ### Grammar | ||
|
|
||
| The following changes to the grammar are proposed: | ||
| ```diff | ||
| StorageClass: | ||
| LinkageAttribute | ||
| AlignAttribute | ||
| deprecated | ||
| enum | ||
| static | ||
| extern | ||
| abstract | ||
| final | ||
| override | ||
| synchronized | ||
| auto | ||
| scope | ||
| const | ||
| immutable | ||
| inout | ||
| shared | ||
| __gshared | ||
| Property | ||
| nothrow | ||
| + nothrow AttributeDeclDef | ||
| pure | ||
| + pure AttributeDeclDef | ||
| ref | ||
|
|
||
| AtAttribute: | ||
| @ disable | ||
| @ nogc | ||
| + @ nogc AttributeDeclDef | ||
| @ live | ||
| Property | ||
| @ safe | ||
| + @ safe AttributeDeclDef | ||
| @ system | ||
| @ trusted | ||
| UserDefinedAttribute | ||
|
|
||
| +AttributeDeclDef: | ||
| + ( * ) | ||
| + ( AttributeDeclDefs $(OPT) ) | ||
|
|
||
| +AttributeDeclDefs: | ||
| + AssignExpression | ||
| + AssignExpression, AttributeDeclDefs ... | ||
| ``` | ||
|
|
||
| This syntax was choosen as it should feel familiar to the user, | ||
| who can already encounter it when using UDAs. | ||
|
|
||
| ### Basic semantic rules | ||
|
|
||
| If the `AttributeDeclDefs` form is used, the argument must be either identifiers or integers. | ||
|
|
||
| If identifiers, they must match the identifiers of one of the function's arguments, | ||
| and this argument must be a delegate or function pointer. | ||
|
|
||
| If integers, the value must be positive and at most one less than the function's | ||
| arity (number of parameter), included. The value must be the 0-based index of the | ||
| argument the attribute depends on. Likewise, this argumment needs to be a | ||
| delegate or function pointer. | ||
|
|
||
| To avoid special cases in meta-programming code, we follow the widespread practice | ||
| of allowing empty lists and trailing commas. | ||
|
|
||
| For user's convenience, and to accomodate what is predicted to be the common case, | ||
| the special token `*` can be used, which indicates that all delegate arguments of | ||
| the functions are to be taken into account. Note that this is valid even if the | ||
| function doesn't have any delegate argument. | ||
|
|
||
| Hence, the following are valid: | ||
| ```D | ||
| // Basic usage, with nothrow and @safe | ||
| void func0(void delegate(int) sink) @safe(sink) nothrow(sink); | ||
| // Empty argument list, equivalent to @safe | ||
| void func1() @safe(); | ||
| // Equivalent to func0 | ||
| void func2(void delegate(int)) @safe(*) nothrow(*); | ||
| // Equivalent to func0 | ||
| void func3(void delegate(int) arg) @safe(arg,) nothrow(*); | ||
| // Equivalent to func1 | ||
| void func4(int) @safe(*); | ||
| // Equivalent to func0 | ||
| void func3(void delegate(int) arg) @safe(0) nothrow(0,); | ||
| ``` | ||
|
|
||
| However, the following are not valid: | ||
| ```D | ||
| // Argument name does not exists | ||
| void err0(void delegate() sink) @safe(snk); | ||
| // Integer out of bound | ||
| void err1(void delegate() sink) @safe(1); | ||
| // Argument is not a delegate or function pointer | ||
| void err2(int arg) @safe(arg); | ||
| // Argument is not a delegate or function pointer | ||
| void err3(int arg) @safe(0); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The index version is quite confusing because of implicit boolean conversion (this looks like
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Didn't think of that. The reason it's in is to support meta programming, where you really don't want to have to mess with identifiers. Sometimes you declare a function with a type tuple and in this case having ADAs support indexes is quite handy. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Couldn't the same be achieved by allowing
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't see how ? Or is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Given the following function void foo(T...)(T args) {}Your current proposal suggests a plain index to specifiy a single parameter: void foo(T...)(T args) @safe(0) {}But this would be more expressive: void foo(T...)(T args) @safe(args[0]) {}Also a plain index doesn't work well with multiple variadic template parameters: template bar(A...)
{
void bar(B...)(A a, B b) @safe(1) {} // Might refer to an element of a or b
}
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hum, those are good points. Index based ADAs are a special case for when an argument has no identifier. Since the grammar allows it, I figured there should be a way to handle it. But even if that's the intended usage, the semantics should be made clearer. And the tuple example is quite neat and more expressive, I agree. Let me think this over for a bit. |
||
| ``` | ||
|
|
||
| ### Call site checks vs callee checks | ||
|
|
||
| When checking the content of a function with ADA, the compiler must enforce the attributes | ||
| applied to the function, *except* when calling the delegate argument. | ||
| Hence, the following would error out: | ||
| ```D | ||
| void semanticError(void delegate(in char[]) sink) nothrow(*) | ||
| { | ||
| if (sink is null) | ||
| throw new Exception("Sink cannot be null"); // Error: `nothrow` function might `throw` | ||
| sink("Hello World"); | ||
| } | ||
| ``` | ||
|
|
||
| This is invalid because the function is not guaranteed to be `nothrow` even if `sink` is `nothrow`. | ||
| However, as the attribute checks is pushed one level up (to the caller), the following code | ||
| which currently errors out will now succeeds: | ||
| ```D | ||
| void func(void delegate(in char[]) sink) nothrow(*) | ||
| { | ||
| sink("Hello World"); | ||
| } | ||
|
|
||
| void main() nothrow | ||
| { | ||
| func((in char[] arg) { | ||
| printf("%.*s\n", cast(int) arg.length, arg.ptr); | ||
| }); | ||
| } | ||
| ``` | ||
|
|
||
| Finally, the check being performed in the caller means that invalid usage will still fail, | ||
| such as in the following example: | ||
| ```D | ||
| void func(void delegate(in char[]) sink) nothrow(*) | ||
| { | ||
| sink("Hello World"); | ||
| } | ||
|
|
||
| void main() nothrow | ||
| { | ||
| func((in char[] arg) { | ||
| if (!arg.length) | ||
| { | ||
| // Error: `delegate` argument to `func` must be `nothrow` but may `throw` | ||
| // `func` is called from `nothrow` context `main` | ||
| throw new Exception("Length cannot be 0"); | ||
| } | ||
| printf("%.*s\n", cast(int) arg.length, arg.ptr); | ||
| }); | ||
| } | ||
| ``` | ||
|
|
||
| ### Interation with templates | ||
|
|
||
| The essence of ADAs targets non-templated code, as if the delegate type is templated, | ||
| then the function's attributes can be infered definitively. | ||
| However, there is nothing preventing the use of ADAs along with templates, | ||
| for example, when the delegate type is not templated (or simply template-dependent): | ||
| ```D | ||
| struct Container | ||
| { | ||
| int opApply (T) (scope void delegate(T)) @safe(*) nothrow(*); | ||
| } | ||
| ``` | ||
| In this example, ADAs provide an improvement over the traditional approach: | ||
| the delegate type is not templated, hence deduction is easier and error messages | ||
| are more relevant, and only one function is generated per `T`. | ||
|
|
||
| This DIP does not recommend to make ADAs be inferred by the compiler. | ||
| While the idea is attractive, the author estimate that the challenges posed | ||
| by such a feature would at least double the amount of work required to get ADAs working. | ||
|
|
||
| ### Other callable types | ||
|
|
||
| The scope of this DIP is intentionally restricted to delegate and functions. | ||
| However, the technique could be extended to other callables, such as `struct` or `class` | ||
| that defines an `opCall`. Such an extension would address a similar issue | ||
| encountered when writing OOP code, where using `interface` or base `class` | ||
| and attributes does not compose well. | ||
|
|
||
| Such an extension could make the following code possible: | ||
| ```D | ||
| interface Callable { void opCall (); } | ||
| class Foo : Callable { override void opCall () nothrow {} } | ||
|
|
||
| // The next line currently doesn't compile because `Callable.opCall` is not `nothrow` | ||
| void func (Callable c) @nothrow(*) { c(); } | ||
| void main () nothrow | ||
| { | ||
| Foo f = new Foo(); | ||
| func(f); // This could work | ||
| } | ||
| ``` | ||
|
|
||
| ## Breaking Changes and Deprecations | ||
|
|
||
| The change is purely additive, so no breaking changes are anticipated. | ||
|
|
||
| Additionally, an already annotated function can relax its requirements, | ||
| switching from hard attributes to ADA in | ||
|
|
||
| ## Reference | ||
|
|
||
| This idea, and the rationale, was presented with a focus on the problem space at DConf: | ||
| - Presentation: https://dconf.org/2020/online/index.html#mathias | ||
| - Live Q&A in parallel: **TODO** | ||
|
|
||
| ## Copyright & License | ||
| Copyright (c) 2020 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. | ||
Uh oh!
There was an error while loading. Please reload this page.