Skip to content

Commit e54db2d

Browse files
author
Quirin Schroll
committed
Fix Bugzilla Issues 23666, 17953, and 24633
1 parent 2e3bb9e commit e54db2d

File tree

1 file changed

+182
-68
lines changed

1 file changed

+182
-68
lines changed

spec/statement.dd

+182-68
Original file line numberDiff line numberDiff line change
@@ -698,88 +698,100 @@ foreach (string s, double d; aa)
698698

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

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

709709
$(GRAMMAR
710-
$(GNAME OpApplyDeclaration):
711-
`int opApply` `(` `scope` `int delegate` `(` $(I OpApplyParameters) `)` `dg` `)` `;`
710+
`int opApply` `(` `scope` `int delegate` `(` $(I OpApplyParameters) `)` `body` `)` `;`
712711

713712
$(GNAME OpApplyParameters):
714-
*OpApplyParameter*
715-
*OpApplyParameter*, *OpApplyParameters*
713+
$(GLINK ParameterDeclaration)
714+
$(GLINK ParameterDeclaration), *OpApplyParameters*
716715

717716
$(GNAME OpApplyParameter):
718-
$(GLINK ForeachTypeAttributes)$(OPT) $(GLINK2 type, BasicType) $(GLINK2 declaration, Declarator)
717+
$(GLINK ParameterStorageClass)$(OPT) $(GLINK2 type, BasicType) $(GLINK2 declaration, Declarator)
719718
)
720719

721-
$(P where each $(I OpApplyParameter) of `dg` must match a $(GLINK ForeachType)
722-
in a $(GLINK ForeachStatement),
723-
otherwise the *ForeachStatement* will cause an error.)
724-
725-
$(P Any *ForeachTypeAttribute* cannot be `enum`.)
726-
727720
$(PANEL
728721
To support a `ref` iteration variable, the delegate must take a `ref` parameter:
729722

730723
$(SPEC_RUNNABLE_EXAMPLE_COMPILE
731724
---
732725
struct S
733726
{
734-
int opApply(scope int delegate(ref uint n) dg);
727+
uint n;
728+
int opApply(scope int delegate(ref uint) body) => body(n);
735729
}
736-
void f(S s)
730+
void main()
737731
{
732+
S s;
738733
foreach (ref uint i; s)
739-
i++;
734+
{
735+
i++; // effectively s.n++
736+
}
737+
foreach (i; s)
738+
{
739+
static assert(is(typeof(i) == uint));
740+
assert(i == 1);
741+
}
740742
}
741743
---
742744
)
743-
Above, `opApply` is still matched when `i` is not `ref`, so by using
744-
a `ref` delegate parameter both forms are supported.
745-
)
745+
Above, `opApply` is matched both when `i` is and is not `ref`, so by using
746+
a `ref` delegate parameter, both forms are supported.
746747

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

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

757758
$(UL
758-
$(LI If the result is nonzero, apply must cease
759+
$(LI If the result is nonzero, the apply function must cease
759760
iterating and return that value.)
760-
$(LI If the result is 0, then iteration should continue.
761+
$(LI If the result is `0`, then iteration should continue.
761762
If there are no more elements to iterate,
762-
apply must return 0.)
763+
the apply function must return `0`.)
763764
)
764765

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

770-
$(P For example, consider a class that is a container for two elements:)
771+
$(P The $(D opApply) and $(D opApplyReverse) member functions can be overloaded.
772+
Selection works similar to overload resolution by
773+
comparing the number of `foreach` variables and number of parameters of `body` and,
774+
if more than one overload remains unique overload,
775+
matching the parameter types of `body` and each $(I ForeachType)
776+
declared in the $(I ForeachStatement) when given.)
777+
778+
$(BEST_PRACTICE Overload apply functions only with delegates differing in number of parameters
779+
to enable type inference for `foreach` variables.
780+
)
781+
782+
$(P For example, consider a class that is a container for some elements:)
771783

772784
$(SPEC_RUNNABLE_EXAMPLE_RUN
773785
--------------
774786
class Foo
775787
{
776788
uint[] array;
777789

778-
int opApply(scope int delegate(ref uint) dg)
790+
int opApply(scope int delegate(ref uint) body)
779791
{
780792
foreach (e; array)
781793
{
782-
int result = dg(e);
794+
int result = body(e);
783795
if (result)
784796
return result;
785797
}
@@ -811,57 +823,159 @@ $(CONSOLE
811823
73
812824
82
813825
)
814-
$(PANEL
815-
The `scope` storage class on the $(D dg) parameter means that the delegate does
816-
not escape the scope of the $(D opApply) function (an example would be assigning $(D dg) to a
817-
global variable). If it cannot be statically guaranteed that $(D dg) does not escape, a closure may
818-
be allocated for it on the heap instead of the stack.
819826

820827
$(BEST_PRACTICE Annotate delegate parameters to `opApply` functions with `scope` when possible.)
828+
829+
$(PANEL
830+
The `scope` storage class on the `body` parameter means that the delegate does
831+
not escape the scope of the apply function (an example would be assigning`body` to a
832+
global variable). If it cannot be statically guaranteed that `body` does not escape, a closure may
833+
be allocated for it on the heap instead of the stack.
821834
)
822835

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

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

831-
$(P $(D opApply) can also be a templated function,
832-
which will infer the types of parameters based on the $(I ForeachStatement).
833-
For example:)
844+
$(P `opApply` and `opApplyReverse` can also be a function templates,
845+
which can optionally infer the types of parameters based on the $(I ForeachStatement).
846+
)
847+
848+
$(P $(B Note:) An apply function template cannot infer `foreach` variable types.)
834849

835850
$(SPEC_RUNNABLE_EXAMPLE_RUN
836851
--------------
837852
struct S
838853
{
839-
import std.traits : ParameterTypeTuple; // introspection template
840-
import std.stdio;
841-
842-
int opApply(Dg)(scope Dg dg)
843-
if (ParameterTypeTuple!Dg.length == 2) // foreach with 2 parameters
854+
int opApply(Body)(scope Body body)
844855
{
845-
writeln(2);
846-
return 0;
847-
}
848-
849-
int opApply(Dg)(scope Dg dg)
850-
if (ParameterTypeTuple!Dg.length == 3) // foreach with 3 parameters
851-
{
852-
writeln(3);
856+
pragma(msg, Body);
853857
return 0;
854858
}
855859
}
856860

857861
void main()
858862
{
859-
foreach (int a, int b; S()) { } // calls first opApply function
860-
foreach (int a, int b, float c; S()) { } // calls second opApply function
863+
foreach (int a, int b; S()) { } // int delegate(ref int, ref int) pure nothrow @nogc @safe
864+
foreach (bool b, string s; S()) { } // int delegate(ref bool, ref string) pure nothrow @nogc @safe
865+
foreach (x; S()) { } // Error: cannot infer type for `foreach` variable `x`, perhaps set it explicitly
861866
}
862867
--------------
863868
)
864869

870+
$(H4 $(LNAME2 template-instance-op-apply, `opApply` as an alias to a explicit function template instance))
871+
872+
$(P `opApply` and `opApplyReverse` can be aliases to an appropriate function or function template.
873+
However, special treatment is given to apply functions that are aliases of an appropriate function template instance.)
874+
875+
$(P In that case, the function template instance is used for overload selection and `foreach` variable type inference,
876+
but the template is instantiated again with the actual delegate type of the `foreach` body.
877+
This way, apply functions can infer attributes depending on the attributes of `body` delegate.)
878+
879+
$(SPEC_RUNNABLE_EXAMPLE_RUN
880+
--------------
881+
struct A
882+
{
883+
int opApply(scope int delegate(long) body) => body(42);
884+
}
885+
struct B
886+
{
887+
int opApply(Body)(scope Body body) => body(42);
888+
}
889+
struct C
890+
{
891+
int opApplyImpl(Body)(scope Body body) => body(42);
892+
alias opApply = opApplyImpl!(int delegate(long));
893+
}
894+
void main() @nogc nothrow pure @safe
895+
{
896+
// Error: `@nogc` function `D main` cannot call non-@nogc function `onlineapp.A.opApply`
897+
// Error: `pure` function `D main` cannot call impure function `onlineapp.A.opApply`
898+
// Error: `@safe` function `D main` cannot call `@system` function `onlineapp.A.opApply`
899+
// Error: function `onlineapp.A.opApply` is not `nothrow`
900+
static assert(!__traits(compiles, () @safe {
901+
foreach (x; A()) { }
902+
}));
903+
904+
// Error: cannot infer type for `foreach` variable `x`, perhaps set it explicitly
905+
static assert(!__traits(compiles, {
906+
foreach (x; B()) { }
907+
}));
908+
909+
// Good:
910+
foreach (x; C())
911+
{
912+
static assert(is(typeof(x) == long));
913+
assert(x == 42);
914+
}
915+
}
916+
--------------
917+
)
918+
919+
$(P The `opApplyImpl` pattern is generally preferable to
920+
overloading many apply functions with all possible combinations of attributes.)
921+
922+
$(P Multiple apply function aliases can exist, and selection and `foreach` variable type inference work:)
923+
924+
--------------
925+
class Tree(T)
926+
{
927+
private T label;
928+
private Tree[] children;
929+
930+
this(T label, Tree[] children = null)
931+
{
932+
this.label = label;
933+
this.children = children;
934+
}
935+
936+
alias opApply = opApplyImpl!(int delegate(ref T label));
937+
alias opApply = opApplyImpl!(int delegate(size_t depth, ref T label));
938+
alias opApply = opApplyImpl!(int delegate(size_t depth, bool isLastChild, ref T label));
939+
940+
int opApplyImpl(Body)(scope Body body) => opApplyImpl2(0, true, body);
941+
int opApplyImpl2(Body)(size_t depth, bool lastChild, scope Body body)
942+
{
943+
import std.meta : AliasSeq;
944+
static if (is(Body : int delegate(size_t, bool, ref T)))
945+
alias args = AliasSeq!(depth, lastChild, label);
946+
else static if (is(Body : int delegate(size_t, ref T)))
947+
alias args = AliasSeq!(depth, label);
948+
else
949+
alias args = label;
950+
if (auto result = body(args)) return result;
951+
foreach (i, child; children)
952+
{
953+
if (auto result = child.opApplyImpl2!Body(depth + 1, i + 1 == children.length, body))
954+
return result;
955+
}
956+
return 0;
957+
}
958+
}
959+
960+
void printTree(Tree)(Tree tree)
961+
{
962+
// Selects the unique one with 2 parameters.
963+
// Infers types: size_t and whatever label type the tree has.
964+
foreach (depth, ref label; tree)
965+
{
966+
import std.stdio;
967+
foreach (_; 0 .. depth) write(" ");
968+
writeln(label);
969+
}
970+
}
971+
972+
void main() @safe
973+
{
974+
alias T = Tree!int;
975+
printTree(new T(0, [new T(1, [new T(2), new T(3)]), new T(4, [new T(5)])]));
976+
}
977+
--------------
978+
865979
$(H3 $(LEGACY_LNAME2 foreach_with_ranges, foreach-with-ranges, Foreach over Structs and Classes with Ranges))
866980

867981
$(P If the aggregate expression is a struct or class object, but the
@@ -1028,16 +1142,16 @@ $(H3 $(LNAME2 foreach_over_delegates, Foreach over Delegates))
10281142
$(SPEC_RUNNABLE_EXAMPLE_RUN
10291143
--------------
10301144
// Custom loop implementation, that iterates over powers of 2 with
1031-
// alternating sign. The foreach loop body is passed in dg.
1032-
int myLoop(scope int delegate(int) dg)
1145+
// alternating sign. The foreach loop body is passed in `body`.
1146+
int myLoop(scope int delegate(int) body)
10331147
{
10341148
for (int z = 1; z < 128; z *= -2)
10351149
{
1036-
auto ret = dg(z);
1150+
auto result = body(z);
10371151

1038-
// If the loop body contains a break, ret will be non-zero.
1039-
if (ret != 0)
1040-
return ret;
1152+
// If the loop body contains a break, result will be non-zero.
1153+
if (result != 0)
1154+
return result;
10411155
}
10421156
return 0;
10431157
}
@@ -1583,7 +1697,7 @@ foreach (item; list)
15831697
$(P Any intervening $(RELATIVE_LINK2 try-statement, `finally`) clauses are executed,
15841698
and any intervening synchronization objects are released.)
15851699

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

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

0 commit comments

Comments
 (0)