Bug 14162 – Erratic inference of @safe for lambdas

Status
RESOLVED
Resolution
FIXED
Severity
normal
Priority
P1
Component
dmd
Product
D
Version
D2
Platform
All
OS
All
Creation time
2015-02-10T07:17:00Z
Last change time
2016-10-01T11:47:58Z
Keywords
pull, safe
Assigned to
nobody
Creator
bugzilla

Comments

Comment #0 by bugzilla — 2015-02-10T07:17:38Z
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: --------- @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.
Comment #1 by k.hara.pg — 2015-02-10T12:03:19Z
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.
Comment #2 by bugzilla — 2016-06-23T22:20:19Z
Inference of safety for lambdas should always occur if not explicitly set, because the source is always available.
Comment #3 by bugzilla — 2016-06-23T22:20:43Z
Comment #4 by github-bugzilla — 2016-06-28T12:54:28Z
Commits pushed to master at https://github.com/dlang/dmd https://github.com/dlang/dmd/commit/366378a5b606ee2093eb6625101e88573a7b2960 fix Issue 14162 - Erratic inference of @safe for lambdas https://github.com/dlang/dmd/commit/11165915a80835d0098f719fe0b4629b4837e583 Merge pull request #5881 from WalterBright/fix14162 fix Issue 14162 - Erratic inference of @safe for lambdas
Comment #5 by github-bugzilla — 2016-10-01T11:47:58Z
Commits pushed to stable at https://github.com/dlang/dmd https://github.com/dlang/dmd/commit/366378a5b606ee2093eb6625101e88573a7b2960 fix Issue 14162 - Erratic inference of @safe for lambdas https://github.com/dlang/dmd/commit/11165915a80835d0098f719fe0b4629b4837e583 Merge pull request #5881 from WalterBright/fix14162