On Friday, 20 January 2023 at 05:48:26 UTC, Steven Schveighoffer wrote:
>On 1/19/23 10:38 PM, Walter Bright wrote:
>On 1/19/2023 6:18 PM, Adam D Ruppe wrote:
>Ideally, you'd be able to say process
inherits the nogc-ness of userData
.
That's exactly what template attribute inference does.
[…]
What we want is the effective attributes of the function to be the most restrictive possible of both the code inside the function, and the argument to the function.
The compiler would need a way to specify this for non-inferred functions, but it could be inferred for templates and auto functions.
I wrote a DIP (Attributes for Higher-Order Functions, AfHOF) for that, but didn’t make it. I tried to be pragmatic and squeeze it into the current syntax via doubling-down on a bug in the type system (i.e. making it a ‘feature’). Timon Gehr made me aware of the bug. Writing the DIP made me aware that higher-order functions are a blocker for making e.g. @safe
the default: If you have void f(void delegate() dg)
, there is no consistent way to add @safe
to f
and/or dg
that won’t break properly annotated @system
code.
Mathias Lang has a work-in-progress DIP called Argument dependent attributes (AdA). It introduces syntax to specify that the attributes of higher-order function (i.e. a function with callbacks) depend on the attributes of the callback arguments. It’s a half-way solution that only targets parameters of delegate or function pointer type. A full solution would include parameters of composed types that include delegate or function pointer types somewhere: Slices of delegate or function pointer type could be considered reasonably common. Attribute variables would do that.
- D has runtime value variables, e.g. your good old
x
inint x;
. - D has type variables, e.g. you good old
T
intemplate t(T){..}
- D has symbol variables, e.g. template
alias
parameters. - D has 1 type constructor variable:
inout
. (D were more consistent if it allowed for two or more independent type constructor variables, but that’s rarely needed practically.) - D has no attribute variables.
If D added 1 fixed-name attribute variable per attribute (cf. inout
), they could be spelled @safety
, purity
, @gcness
, and throwiness
.
void callInSequence(
void delegate() @safety purity @gcness throwiness[] dgs
) @safety purity @gcness throwiness
{
foreach (dg; dgs) dg();
}
When called, the argument is a slice of delegates that are @safe
or @system
– @safety
is replaced by that; that are pure
or not – purity is replaced by that; that are @nogc
or not – @gcness
is replaced by that; that may throw or not – throwiness
by throw
or nothrow
.
This works the same way as when inout(int)[] f() inout
is called on a mutable, const
or immutable
object, its return type will be int[]
, const(int)[]
, or immutable(int)[]
after replacing inout
with the call-side type constructor applied to the object’s type.