Bug 20116 – Wrong delegate type when taking address of inout member function

Status
NEW
Severity
normal
Priority
P3
Component
dmd
Product
D
Version
D2
Platform
All
OS
All
Creation time
2019-08-08T16:27:31Z
Last change time
2024-12-13T19:04:47Z
Assigned to
No Owner
Creator
Harry Vennik
Moved to GitHub: dmd#19604 →

Comments

Comment #0 by htvennik — 2019-08-08T16:27:31Z
Template function wrapping a delegate fails when the delegate happens to return an inout type. ---- import std.traits : Parameters, ReturnType; class C { private int[] _arr; @property auto arr() inout nothrow @nogc @safe { return _arr; } } ReturnType!DG foo(DG)(scope DG func) if (!is(ReturnType!DG == void) && 0 == Parameters!func.length) { return func(); } void main() { C c; foo(&c.arr); } ---- inout.d(51): Error: inout on return means inout must be on a parameter as well for ReturnType!(inout(int[]) delegate() inout nothrow @nogc @property @safe)(scope DG func) inout.d(60): Error: template instance `inout.foo!(inout(int[]) delegate() inout nothrow @nogc @property @safe)` error instantiating
Comment #1 by htvennik — 2019-08-14T19:23:43Z
Tested once again with an additional pragma statement in main(): void main() { C c; pragma(msg, typeof(&c.arr)); foo(&c.arr); } The pragma outputs: inout(int[]) delegate() inout nothrow @nogc @property @safe Since the delegate's context actually gets typed as inout, foo() does in fact have an inout parameter.
Comment #2 by simen.kjaras — 2019-08-15T17:21:31Z
I've updated the description to give a better description of what's actually wrong here. The actual issue is what typeof(&c.arr) returns - it needs to reflect the constancy of c - inout makes no sense on the context of a delegate. IOW, this: class C { int[] _arr; auto fun() inout { return _arr; } } static assert(is(typeof(&(new C).fun) == int[] delegate())); static assert(is(typeof(&(new const C).fun) == const (int[]) delegate())); static assert(is(typeof(&(new immutable C).fun) == immutable(int[]) delegate())); should compile.
Comment #3 by htvennik — 2019-08-15T21:54:28Z
That is an interesting approach. Looks like that would fix this particular issue. But could you please elaborate on the effects of such semantic in the different, but related, case of Issue 15651?
Comment #4 by simen.kjaras — 2019-08-16T07:15:01Z
It's not just an interesting approach - it's the *right* approach. When you call the delegate, you don't pass it a context, so you can't decide whether the result should be const or immutable at that point - the constancy is set in stone the moment you create it, so that's when the type should be decided. Sadly, this would do nothing for issue 15651 - it's not creating a delegate from a struct or a class instance, so this change would have no impact whatsoever. 15651 is just a case where Unqual isn't doing all the things it perhaps should, or possibly a situation where inout simply breaks down.
Comment #5 by htvennik — 2019-08-16T11:40:51Z
The project I am working on suffers from both issues, so I just wanted to ensure solving one would not worsen the other. Though I agree that the type is to be determined at delegate creation. But what would happen if a delegate is created from a struct or class reference that is already inout? In that case there is no way but making the context inout. Making it const would not be correct, because in that case, the return value of the delegate would also be const if the delegate returns (part of) its context.
Comment #6 by simen.kjaras — 2019-08-16T12:08:26Z
The only case in which you should be able to make an inout delegate would be inside an inout function, with a context that is inout, and it would convert to the correct constancy upon leaving that function. Consider this code: class C { int[] _arr; auto fun() inout { return _arr; } auto dgfun() inout { auto f = () => _arr; return f; } } typeof(&(new C).dgfun) should be int[] delegate() delegate(), even though the type of f inside dgfun should be inout(int[]) delegate() inout. There's also the case of &C.fun - taking the address of a member function without a context. This is already messed up in D today*, but should return inout(int[]) function(inout(C)). Thus, the constancy of the return value depends on the context passed to the function. * it returns inout(int[]) function() inout, which doesn't take a C in any way, shape or form, and is an almost foolproof recipe for memory corruption. It's also an inout function without a context for the inout to apply to, which is just plain broken. Don't use it unless you absolutely have to.
Comment #7 by robert.schadek — 2024-12-13T19:04:47Z
THIS ISSUE HAS BEEN MOVED TO GITHUB https://github.com/dlang/dmd/issues/19604 DO NOT COMMENT HERE ANYMORE, NOBODY WILL SEE IT, THIS ISSUE HAS BEEN MOVED TO GITHUB