Thread overview
[Issue 17645] `pure` is transitively applied to all delegates inside a pure function
Jul 15, 2017
Vladimir Panteleev
Dec 17, 2022
Iain Buclaw
July 13, 2017
https://issues.dlang.org/show_bug.cgi?id=17645

Tomer Filiba (weka) <tomer@weka.io> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
           Keywords|                            |industry

--
July 13, 2017
https://issues.dlang.org/show_bug.cgi?id=17645

Tomer Filiba (weka) <tomer@weka.io> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
                 CC|                            |tomer@weka.io

--
July 13, 2017
https://issues.dlang.org/show_bug.cgi?id=17645

Steven Schveighoffer <schveiguy@yahoo.com> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
                 CC|                            |schveiguy@yahoo.com

--- Comment #1 from Steven Schveighoffer <schveiguy@yahoo.com> ---
Do you have a better use case? This works:

void f() pure {
    void function() dg = {x++;};
}

i.e. you don't need access to the pure function's stack frame, so you can have a function instead of a delegate.

I'm kind of surprised the compiler inferred dg as a delegate and not a function in the first place.

--
July 15, 2017
https://issues.dlang.org/show_bug.cgi?id=17645

--- Comment #2 from Vladimir Panteleev <dlang-bugzilla@thecybershadow.net> ---
FWIW, the test case works in DMD 2.013 through 2.027 :)

--
July 15, 2017
https://issues.dlang.org/show_bug.cgi?id=17645

--- Comment #3 from Tomer Filiba (weka) <tomer@weka.io> ---
(In reply to Steven Schveighoffer from comment #1)
> Do you have a better use case?

the use case may sound odd, but it's surprisingly common. suppose you have

struct Foo {
    private int x;
    @property int someTrivialProperty() const pure @nogc {
        assert(x > 8, "oh no x=%s".format(x));
        return x;
    }
}

`format` does GC and isn't pure. the property itself is perfectly pure and @nogc and whatnot, but by adding an assert i have to remove these attributes from it. so we have ASSERT which hides away the impurity and GC-ness, since it it blows up we really don't care about the GC or purity.

ASSERT!"oh no x=%s"(x > 8, x);

we practically use it everywhere.

> i.e. you don't need access to the pure function's stack frame, so you can have a function instead of a delegate.

maybe, but it means i can't write it in the same statement, i.e. this won't work

    assumePure({x++});

and i'll have to use

    void function() fn = {x++};
    assumePure(x);

which defeats the purpose of easy-to-spell-out lambdas

--
July 17, 2017
https://issues.dlang.org/show_bug.cgi?id=17645

--- Comment #4 from Steven Schveighoffer <schveiguy@yahoo.com> ---
(In reply to Tomer Filiba (weka) from comment #3)
> `format` does GC and isn't pure.

`format` actually is pure.

void main() pure
{
   auto str = format("oh no x=%s", 5);
}

But you are right that it's not @nogc. However, this isn't the focus of the bug report (or is it?). In your new example, you are actually calling the GC-allocating function from inside a @nogc context, not creating a delegate. Even if you did create a delegate and needed to save the context, that creates a closure, and that definitely needs the GC.

> the property itself is perfectly pure and
> @nogc and whatnot, but by adding an assert i have to remove these attributes
> from it.

I understand. This is more like an enhancement request to have assert ignore the requirements of pure and @nogc for the lazy evaluated arg.

> maybe, but it means i can't write it in the same statement, i.e. this won't work
> 
>     assumePure({x++});

As I said earlier, I think the fact that the literal isn't inferred to be a function instead of a delegate is a bug. but you can force a function literal out of this:

int x;
auto foo() pure
{
   return function() { x++; }; // should be able to omit 'function'
}

void main()
{
   foo()();
}

So you have a few choices here:

1. Update the bug to say that these should be inferred as functions (even if
you don't do this, another bug should be filed).
2. Argue that a delegate with a context of a pure function should be able to be
unpure, and demonstrate why that's needed with an updated use case.
3. Alter the bug report to mean something different.

--
July 17, 2017
https://issues.dlang.org/show_bug.cgi?id=17645

--- Comment #5 from Tomer Filiba (weka) <tomer@weka.io> ---
(In reply to Steven Schveighoffer from comment #4)
> `format` actually is pure.

nitpicking. sure, format may be pure, but the variables i want to log in the assert may be __gshared and then i can't access them. or i may want to invoke some impure functions to add extra info before the process dies. e.g.

assert (cond, "%s x=%s".format(getTimeOfDay(), x));

> In your new example, you are actually calling the
> GC-allocating function from inside a @nogc context, not creating a delegate.
> Even if you did create a delegate and needed to save the context, that
> creates a closure, and that definitely needs the GC.

the ASSERT function takes a `scope delegate` so no closure of GC is needed. unless it blows up, and then i'm ok with that.

> So you have a few choices here:
> 
> 1. Update the bug to say that these should be inferred as functions (even if
> you don't do this, another bug should be filed).
> 2. Argue that a delegate with a context of a pure function should be able to
> be unpure, and demonstrate why that's needed with an updated use case.
> 3. Alter the bug report to mean something different.

opting for 2. i will file another bug for the inference issue, but i don't see why this is not a bug. the fact i *create* a delegate in a pure scope shouldn't force the *delegate* to be pure. i shouldn't be able to *invoke* that impure delegate, just like i shouldn't be able to invoke any other impure function. but that's not the issue. once i have a delegate, i just cast it to a pure one -- and this will happen only when the assert blows up.

and the function(){} hack won't help me, because i require access to arguments/variables on the function's stack.

--
July 17, 2017
https://issues.dlang.org/show_bug.cgi?id=17645

--- Comment #6 from Tomer Filiba (weka) <tomer@weka.io> ---
Opened https://issues.dlang.org/show_bug.cgi?id=17659

--
July 17, 2017
https://issues.dlang.org/show_bug.cgi?id=17645

--- Comment #7 from Steven Schveighoffer <schveiguy@yahoo.com> ---
(In reply to Tomer Filiba (weka) from comment #5)
> the fact i *create* a delegate in a pure scope
> shouldn't force the *delegate* to be pure.

I'm not 100% sure about that, but it seems a plausible possibility. As long as you aren't calling the delegate from within the function (your ASSERT function seems to be, and assert definitely would be), then just accessing the stack frame data should be OK.

As far as @nogc goes, that is simply an issue with the functions you need to call. If you need GC functions within @nogc, then you have to obscure and cast. I think it's possible to argue that assert should allow GC functionality, but not 100% sure of that. It appears that any inner functions inside a pure function are also marked pure. Since there's no "unpure" attribute, it's hard to undo this.

--
December 17, 2022
https://issues.dlang.org/show_bug.cgi?id=17645

Iain Buclaw <ibuclaw@gdcproject.org> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
           Priority|P1                          |P2

--