[Issue 14162] Erratic inference of @safe for lambdas

via Digitalmars-d-bugs digitalmars-d-bugs at puremagic.com
Tue Feb 10 04:03:19 PST 2015


https://issues.dlang.org/show_bug.cgi?id=14162

--- Comment #1 from Kenji Hara <k.hara.pg at gmail.com> ---
I also noticed this difference recently. But I think the safe violation error
in the first case is incorrect.

(In reply to Walter Bright from comment #0)
> Consider the code:
> ---------
> @trusted auto trusted(alias fun)() { return fun(); }
> 
> @safe void func()
> {
>     char[3] s = "abc";
>     string t = trusted!(() => cast(string)(s[]));
> //test.d(6): Error: cast from char[] to string not allowed in safe code
>     assert(t == "abc");
> }
> 
> @safe void test() { func(); }
> ----------
> The error is correct because the lambda is evaluated in the context of @safe
> func(), not in the context of @trusted trusted(). But turn func() into a
> template function:

Normally a nested function inherits @safe attribute from the enclosing
function. But lambda function attribute should inferred from the body
statement. Currently the inherited @safe is preferred, but as I'll explain
later, it's not good behavior.

> ---------
> @trusted auto trusted(alias fun)() { return fun(); }
> 
> @safe void func()() // only change is add () to make it a template
> {
>     char[3] s = "abc";
>     string t = trusted!(() => cast(string)(s[]));
>     assert(t == "abc");
> }
> 
> @safe void test() { func(); }
> ----------
> And it now incorrectly compiles without error.

Inside template function, the attribute inference result is priority than the
inherited @safe on the lambda. Then the lambda is marked as @system.

========

The latter case is at least intended behavior. See FuncDeclaration::semantic()
in func.c:

    if (sc->func)
    {
        /* If the parent is @safe, then this function defaults to safe too.
         */
        if (tf->trust == TRUSTdefault)
        {
            FuncDeclaration *fd = sc->func;

            /* If the parent's @safe-ty is inferred, then this function's
@safe-ty needs
             * to be inferred first.
             * If this function's @safe-ty is inferred, then it needs to be
infeerd first.
             * (local template function inside @safe function can be inferred
to @system).
             */
            if (fd->isSafeBypassingInference() && !isInstantiated())
                tf->trust = TRUSTsafe;              // default to @safe
        }

The behavior is necessary to support a lambda idiom. For example:

struct S { this(this) {} }
import std.traits: isSafe;
void foo(T)() @safe
{
    static if (isSafe!((ref T t){ T t2 = t; }))
    {
        pragma(msg, true);
    }
    else
    {
        pragma(msg, false);
    }
}
void main() { foo!S(); }

Lambda is used to check whether the T's copy operation is really safe. If the
lambda inherits @safe attribute from the enclosing foo, unsafe T copy will
cause safe violation error so the check won't work.

And more, D allows to declare @system function inside @safe function.

void foo() @safe
{
    static void bar() @system
    {
    }
}

>From the fact, even if a lambda inside @safe function is deduced to @system, it
won't cause safety violation. Only when the lambda is actually called in the
@safe function, it should be an error.

void foo() @safe
{
    auto dg = { return systemCall() };   // should be OK
    auto ret = { return systemCall() }();  // system cannot call in safe
function
}

As a conclusion, I think the former case should be fixed, and the behavior
should be same with the latter.

--


More information about the Digitalmars-d-bugs mailing list