Skip to content

Commit

Permalink
Fix Bugzilla Issues 23666, 17953, and 24633
Browse files Browse the repository at this point in the history
  • Loading branch information
Quirin Schroll committed Jun 26, 2024
1 parent 2e3bb9e commit b0528ec
Showing 1 changed file with 182 additions and 68 deletions.
250 changes: 182 additions & 68 deletions spec/statement.dd
Original file line number Diff line number Diff line change
Expand Up @@ -698,88 +698,100 @@ foreach (string s, double d; aa)

$(H3 $(LNAME2 foreach_over_struct_and_classes, Foreach over Structs and Classes with `opApply`))

$(P If the aggregate expression is a struct or class object,
the $(D foreach) is defined by the
special $(LEGACY_LNAME2 opApply, op-apply, $(D opApply)) member function, and the
`foreach_reverse` behavior is defined by the special
$(P If the aggregate expression is a `struct` or `class` object,
iteration with $(D foreach) can be defined by implementing the
$(LEGACY_LNAME2 opApply, op-apply, $(D opApply)) member function, and the
`foreach_reverse` behavior is defined by the
$(LEGACY_LNAME2 opApplyReverse, op-apply-reverse, $(D opApplyReverse)) member function.
These functions must each have the signature below:
)

$(GRAMMAR
$(GNAME OpApplyDeclaration):
`int opApply` `(` `scope` `int delegate` `(` $(I OpApplyParameters) `)` `dg` `)` `;`
`int opApply` `(` `scope` `int delegate` `(` $(I OpApplyParameters) `)` `body` `)` `;`

$(GNAME OpApplyParameters):
*OpApplyParameter*
*OpApplyParameter*, *OpApplyParameters*
$(GLINK ParameterDeclaration)
$(GLINK ParameterDeclaration), *OpApplyParameters*

$(GNAME OpApplyParameter):
$(GLINK ForeachTypeAttributes)$(OPT) $(GLINK2 type, BasicType) $(GLINK2 declaration, Declarator)
$(GLINK ParameterStorageClass)$(OPT) $(GLINK2 type, BasicType) $(GLINK2 declaration, Declarator)
)

$(P where each $(I OpApplyParameter) of `dg` must match a $(GLINK ForeachType)
in a $(GLINK ForeachStatement),
otherwise the *ForeachStatement* will cause an error.)

$(P Any *ForeachTypeAttribute* cannot be `enum`.)

$(PANEL
To support a `ref` iteration variable, the delegate must take a `ref` parameter:

$(SPEC_RUNNABLE_EXAMPLE_COMPILE
---
struct S
{
int opApply(scope int delegate(ref uint n) dg);
uint n;
int opApply(scope int delegate(ref uint) body) => body(n);
}
void f(S s)
void main()
{
S s;
foreach (ref uint i; s)
i++;
{
i++; // effectively s.n++
}
foreach (i; s)
{
static assert(is(typeof(i) == uint));
assert(i == 1);
}
}
---
)
Above, `opApply` is still matched when `i` is not `ref`, so by using
a `ref` delegate parameter both forms are supported.
)
Above, `opApply` is matched both when `i` is and is not `ref`, so by using
a `ref` delegate parameter, both forms are supported.

$(P There can be multiple $(D opApply) and $(D opApplyReverse) functions -
one is selected
by matching each parameter of `dg` to each $(I ForeachType)
declared in the $(I ForeachStatement).)
Stating the type of the variable like in the first loop is optional,
as is showcased in the second loop, where the type is inferred.
)

$(P The body of the apply
function iterates over the elements it aggregates, passing each one
in successive calls to the `dg` delegate. The delegate return value
determines whether to interrupt iteration:)
$(P The apply
function iterates over the elements it aggregates by passing each one
in successive calls to the `body` delegate. The delegate return value
of the `body` delegate
determines how to proceed iteration:)

$(UL
$(LI If the result is nonzero, apply must cease
$(LI If the result is nonzero, the apply function must cease
iterating and return that value.)
$(LI If the result is 0, then iteration should continue.
$(LI If the result is `0`, then iteration should continue.
If there are no more elements to iterate,
apply must return 0.)
the apply function must return `0`.)
)

$(P The result of calling the delegate will be nonzero if the *ForeachStatement*
$(P The result of the `body` delegate will be nonzero if the *ForeachStatement*
body executes a matching $(GLINK BreakStatement), $(GLINK ReturnStatement), or
$(GLINK GotoStatement) whose matching label is outside the *ForeachStatement*.
)

$(P For example, consider a class that is a container for two elements:)
$(P The $(D opApply) and $(D opApplyReverse) member functions can be overloaded.
Selection works similar to overload resolution by
comparing the number of `foreach` variables and number of parameters of `body` and,
if more than one overload remains unique overload,
matching the parameter types of `body` and each $(I ForeachType)
declared in the $(I ForeachStatement) when given.)

$(BEST_PRACTICE Overload apply functions only with delegates differing in number of parameters
to enable type inference for `foreach` variables.
)

$(P For example, consider a class that is a container for some elements:)

$(SPEC_RUNNABLE_EXAMPLE_RUN
--------------
class Foo
{
uint[] array;

int opApply(scope int delegate(ref uint) dg)
int opApply(scope int delegate(ref uint) body)
{
foreach (e; array)
{
int result = dg(e);
int result = body(e);
if (result)
return result;
}
Expand Down Expand Up @@ -811,57 +823,159 @@ $(CONSOLE
73
82
)
$(PANEL
The `scope` storage class on the $(D dg) parameter means that the delegate does
not escape the scope of the $(D opApply) function (an example would be assigning $(D dg) to a
global variable). If it cannot be statically guaranteed that $(D dg) does not escape, a closure may
be allocated for it on the heap instead of the stack.

$(BEST_PRACTICE Annotate delegate parameters to `opApply` functions with `scope` when possible.)

$(PANEL
The `scope` storage class on the `body` parameter means that the delegate does
not escape the scope of the apply function (an example would be assigning`body` to a
global variable). If it cannot be statically guaranteed that `body` does not escape, a closure may
be allocated for it on the heap instead of the stack.
)

$(P $(B Important:) If $(D opApply) catches any exceptions, ensure that those
exceptions did not originate from the delegate passed to $(D opApply). The user would expect
$(P $(B Important:) If apply functions catch any exceptions, ensure that those
exceptions did not originate from the delegate. The user would expect
exceptions thrown from a `foreach` body to both terminate the loop, and propagate outside
the `foreach` body.
)

$(H4 $(LNAME2 template-op-apply, Template `opApply`))

$(P $(D opApply) can also be a templated function,
which will infer the types of parameters based on the $(I ForeachStatement).
For example:)
$(P `opApply` and `opApplyReverse` can also be a function templates,
which can optionally infer the types of parameters based on the $(I ForeachStatement).
)

$(P $(B Note:) An apply function template cannot infer `foreach` variable types.)

$(SPEC_RUNNABLE_EXAMPLE_RUN
--------------
struct S
{
import std.traits : ParameterTypeTuple; // introspection template
import std.stdio;

int opApply(Dg)(scope Dg dg)
if (ParameterTypeTuple!Dg.length == 2) // foreach with 2 parameters
int opApply(Body)(scope Body body)
{
writeln(2);
return 0;
}

int opApply(Dg)(scope Dg dg)
if (ParameterTypeTuple!Dg.length == 3) // foreach with 3 parameters
{
writeln(3);
pragma(msg, Body);
return 0;
}
}

void main()
{
foreach (int a, int b; S()) { } // calls first opApply function
foreach (int a, int b, float c; S()) { } // calls second opApply function
foreach (int a, int b; S()) { } // int delegate(ref int, ref int) pure nothrow @nogc @safe
foreach (bool b, string s; S()) { } // int delegate(ref bool, ref string) pure nothrow @nogc @safe
//foreach (x; S()) { } // Error: cannot infer type for `foreach` variable `x`, perhaps set it explicitly
}
--------------
)

$(H4 $(LNAME2 template-instance-op-apply, `opApply` as an alias to a explicit function template instance))

$(P `opApply` and `opApplyReverse` can be aliases to an appropriate function or function template.
However, special treatment is given to apply functions that are aliases of an appropriate function template instance.)

$(P In that case, the function template instance is used for overload selection and `foreach` variable type inference,
but the template is instantiated again with the actual delegate type of the `foreach` body.
This way, apply functions can infer attributes depending on the attributes of `body` delegate.)

$(SPEC_RUNNABLE_EXAMPLE_RUN
--------------
struct A
{
int opApply(scope int delegate(long) body) => body(42);
}
struct B
{
int opApply(Body)(scope Body body) => body(42);
}
struct C
{
int opApplyImpl(Body)(scope Body body) => body(42);
alias opApply = opApplyImpl!(int delegate(long));
}
void main() @nogc nothrow pure @safe
{
// Error: `@nogc` function `D main` cannot call non-@nogc function `onlineapp.A.opApply`
// Error: `pure` function `D main` cannot call impure function `onlineapp.A.opApply`
// Error: `@safe` function `D main` cannot call `@system` function `onlineapp.A.opApply`
// Error: function `onlineapp.A.opApply` is not `nothrow`
static assert(!__traits(compiles, () @safe {
foreach (x; A()) { }
}));

// Error: cannot infer type for `foreach` variable `x`, perhaps set it explicitly
static assert(!__traits(compiles, {
foreach (x; B()) { }
}));

// Good:
foreach (x; C())
{
static assert(is(typeof(x) == long));
assert(x == 42);
}
}
--------------
)

$(P The `opApplyImpl` pattern is generally preferable to
overloading many apply functions with all possible combinations of attributes.)

$(P Multiple apply function aliases can exist, and selection and `foreach` variable type inference work:)

--------------
class Tree(T)
{
private T label;
private Tree[] children;

this(T label, Tree[] children = null)
{
this.label = label;
this.children = children;
}

alias opApply = opApplyImpl!(int delegate(ref T label));
alias opApply = opApplyImpl!(int delegate(size_t depth, ref T label));
alias opApply = opApplyImpl!(int delegate(size_t depth, bool isLastChild, ref T label));

int opApplyImpl(Body)(scope Body body) => opApplyImpl2(0, true, body);
int opApplyImpl2(Body)(size_t depth, bool lastChild, scope Body body)
{
import std.meta : AliasSeq;
static if (is(Body : int delegate(size_t, bool, ref T)))
alias args = AliasSeq!(depth, lastChild, label);
else static if (is(Body : int delegate(size_t, ref T)))
alias args = AliasSeq!(depth, label);
else
alias args = label;
if (auto result = body(args)) return result;
foreach (i, child; children)
{
if (auto result = child.opApplyImpl2!Body(depth + 1, i + 1 == children.length, body))
return result;
}
return 0;
}
}

void printTree(Tree)(Tree tree)
{
// Selects the unique one with 2 parameters.
// Infers types: size_t and whatever label type the tree has.
foreach (depth, ref label; tree)
{
import std.stdio;
foreach (_; 0 .. depth) write(" ");
writeln(label);
}
}

void main() @safe
{
alias T = Tree!int;
printTree(new T(0, [new T(1, [new T(2), new T(3)]), new T(4, [new T(5)])]));
}
--------------

$(H3 $(LEGACY_LNAME2 foreach_with_ranges, foreach-with-ranges, Foreach over Structs and Classes with Ranges))

$(P If the aggregate expression is a struct or class object, but the
Expand Down Expand Up @@ -1028,16 +1142,16 @@ $(H3 $(LNAME2 foreach_over_delegates, Foreach over Delegates))
$(SPEC_RUNNABLE_EXAMPLE_RUN
--------------
// Custom loop implementation, that iterates over powers of 2 with
// alternating sign. The foreach loop body is passed in dg.
int myLoop(scope int delegate(int) dg)
// alternating sign. The foreach loop body is passed in `body`.
int myLoop(scope int delegate(int) body)
{
for (int z = 1; z < 128; z *= -2)
{
auto ret = dg(z);
auto result = body(z);

// If the loop body contains a break, ret will be non-zero.
if (ret != 0)
return ret;
// If the loop body contains a break, result will be non-zero.
if (result != 0)
return result;
}
return 0;
}
Expand Down Expand Up @@ -1583,7 +1697,7 @@ foreach (item; list)
$(P Any intervening $(RELATIVE_LINK2 try-statement, `finally`) clauses are executed,
and any intervening synchronization objects are released.)

$(P $(D Note:) If a `finally` clause executes a `throw` out of the finally
$(P $(B Note:) If a `finally` clause executes a `throw` out of the finally
clause, the continue target is never reached.)

$(H2 $(LEGACY_LNAME2 BreakStatement, break-statement, Break Statement))
Expand Down

0 comments on commit b0528ec

Please sign in to comment.