Bug 16301 – CTFE execution of opApply keeps wrong "this" context in foreach's body

Status
NEW
Severity
normal
Priority
P3
Component
dmd
Product
D
Version
D2
Platform
All
OS
All
Creation time
2016-07-20T08:34:46Z
Last change time
2024-12-13T18:48:59Z
Keywords
CTFE, industry
Assigned to
uplink.coder
Creator
Eyal
Moved to GitHub: dmd#19159 →

Comments

Comment #0 by eyal — 2016-07-20T08:34:46Z
struct OpApply { int opApply(scope int delegate(int) dlg) { return dlg(1); } } struct Foo { int i; this(int x) { foreach(_; OpApply()) { i = 0; // Error: couldn't find field i of type int in OpApply() } } } enum x = Foo(1); (also happens with this.i = 0).
Comment #1 by bugzilla — 2017-05-12T16:33:48Z
The same error: test.d(10): Error: couldn't find field i of type int in OpApply() test.d(3): called from here: dlg(1) test.d(9): called from here: OpApply().opApply(delegate int(int _) => 0) test.d(14): called from here: Foo(0).this(1) happens when `enum` is replaced with `auto` so it is not specific to CTFE.
Comment #2 by ag0aep6g — 2017-05-12T18:53:38Z
(In reply to Walter Bright from comment #1) > The same error: [...] > happens when `enum` is replaced with `auto` so it is not specific to CTFE. I think you're mistaken. If you leave x at the module level, it's still initialized statically, i.e. CTFE is involved. The error goes away when x is a function-local variable.
Comment #3 by bugzilla — 2017-05-12T21:32:04Z
(In reply to ag0aep6g from comment #2) > I think you're mistaken. You're right. It is a CTFE problem.
Comment #4 by bugzilla — 2017-05-12T22:07:33Z
The following simpler code reproduces the problem: ------------------------ struct OpApply { void opApply(void delegate() dlg) { dlg(); } } struct Foo { int i; int abc() { auto o = OpApply(); void dg() { i = 0; } // couldn't find field i of type int in OpApply() o.opApply(&dg); return 0; } } void bar() { enum x = Foo().abc(); } --------------------------- This code fails in a similar manner, and also works outside of CTFE (replace enum with auto and it compiles): --------------------------- void opApply(void delegate() dlg) { dlg(); } struct Foo { int i; int abc() { void dg() { i = 0; } // value of 'this' is not known at compile time opApply(&dg); return 0; } } void bar() { enum x = Foo().abc(); } ------------------------- This stuff is pretty deep in the bowels of CTFE, which I don't really understand :-(
Comment #5 by uplink.coder — 2017-05-19T09:31:22Z
We generally have no support for closures at ctfe.
Comment #6 by uplink.coder — 2017-05-19T11:38:19Z
I figured out what is happing. The delegate support code in the ctfe engine takes the parents scope to try and extract a thisPtr. in the case of walters example struct Foo { int i; int abc() { void dg() { i = 0; } // value of 'this' is not known at compile time // dg's direct parent is the function abc, which // does not have a this (because it's a function) opApply(&dg); return 0; } } void bar() { enum x = Foo().abc(); } I am working on a solution. It should work in a couple of days.
Comment #7 by bugzilla — 2017-05-19T17:16:02Z
Don Clugston writes about this problem: I bet the problem is around dinterpret.d line 4966. --- else if (ecall.op == TOKdelegate) { // Calling a delegate fd = (cast(DelegateExp)ecall).func; pthis = (cast(DelegateExp)ecall).e1; // Special handling for: &nestedfunc --> DelegateExp(VarExp(nestedfunc), nestedfunc) if (pthis.op == TOKvar && (cast(VarExp)pthis).var == fd) pthis = null; // context is not necessary for CTFE --- "Context is not necessary for CTFE". I doubt very much that that is true.
Comment #8 by uplink.coder — 2017-05-22T13:55:48Z
please try if https://github.com/dlang/dmd/pull/6813 gives you the expected results.
Comment #9 by eyal — 2017-05-23T23:59:20Z
In the code review I saw that the PR changed the CTFE to use dynamic scoping lookup instead of lexical scoping so after much IRC back and forth I eventually managed to distill the problematic example to: @safe: unittest { struct Foo { @safe: int i; int whoami() { return i; } int call(int delegate() @safe dlg) { return dlg(); } } int func() { auto foo1 = Foo(1); auto foo2 = Foo(2); assert(1 == foo2.call(&foo1.whoami)); return 0; } enum F = func(); }
Comment #10 by eyal — 2017-05-24T00:04:00Z
Sorry, this is the actual example that showed the dynamic scoping lookup in the PR is incorrect: @safe: unittest { enum Caller { OpApply, Other } struct OpApply { @safe: int delegate(Caller) @safe myDlg; int opApply(int delegate(Caller) @safe dlg) { myDlg = dlg; return dlg(Caller.OpApply); } } struct Foo { @safe: OpApply o; int i; this(int x) { i = x; o = OpApply(); foreach(caller; o) { final switch(caller) { case Caller.OpApply: if(i == 1) { auto foo2 = Foo(2); assert(2 == foo2.call(o.myDlg)); assert(i == 0); } break; case Caller.Other: i = 0; break; } } } int call(int delegate(Caller) @safe dlg) { dlg(Caller.Other); return i; } } Foo(1); } The distilled one somehow
Comment #11 by robert.schadek — 2024-12-13T18:48:59Z
THIS ISSUE HAS BEEN MOVED TO GITHUB https://github.com/dlang/dmd/issues/19159 DO NOT COMMENT HERE ANYMORE, NOBODY WILL SEE IT, THIS ISSUE HAS BEEN MOVED TO GITHUB