Thread overview
Type Inference in @safe unittests
Aug 22, 2017
jmh530
Aug 22, 2017
Jonathan M Davis
Aug 22, 2017
jmh530
Aug 22, 2017
jmh530
August 22, 2017
I'm not sure if this is a bug or not.

I was playing around with printing out some member types with unittests and I was noticing some strange results when they were in @safe unittests rather than normal unittests. The first one prints out what I would expect, but the @safe unittest puts @safe @nogc nothrow and pure on them, as if it is re-writing the struct as a template (or maybe just the functions as templates, I don't know).

private enum isPrivate(T, string member) = !__traits(compiles, __traits(getMember, T, member));

void printMemberTypes(alias T)()
{
    foreach(memberName; __traits(allMembers, T))
    {
        static if(!isPrivate!(T, memberName)) {
            writeln(typeid(typeof(__traits(getMember, T, memberName))));
        }
    }
}

unittest
{
    struct Foo {
        int foo(int i, string s) @safe { return 0; }
        double foo2(string s) @safe { return 0; }
    }

    printMemberTypes!(Foo);
}

@safe unittest
{
    struct Foo {
        int foo(int i, string s) @safe { return 0; }
        double foo2(string s) @safe { return 0; }
    }

    printMemberTypes!(Foo);
}
August 22, 2017
On Tuesday, August 22, 2017 16:11:11 jmh530 via Digitalmars-d-learn wrote:
> I'm not sure if this is a bug or not.
>
> I was playing around with printing out some member types with unittests and I was noticing some strange results when they were in @safe unittests rather than normal unittests. The first one prints out what I would expect, but the @safe unittest puts @safe @nogc nothrow and pure on them, as if it is re-writing the struct as a template (or maybe just the functions as templates, I don't know).
>
> private enum isPrivate(T, string member) = !__traits(compiles,
> __traits(getMember, T, member));
>
> void printMemberTypes(alias T)()
> {
>      foreach(memberName; __traits(allMembers, T))
>      {
>          static if(!isPrivate!(T, memberName)) {
>              writeln(typeid(typeof(__traits(getMember, T,
> memberName))));
>          }
>      }
> }
>
> unittest
> {
>      struct Foo {
>          int foo(int i, string s) @safe { return 0; }
>          double foo2(string s) @safe { return 0; }
>      }
>
>      printMemberTypes!(Foo);
> }
>
> @safe unittest
> {
>      struct Foo {
>          int foo(int i, string s) @safe { return 0; }
>          double foo2(string s) @safe { return 0; }
>      }
>
>      printMemberTypes!(Foo);
> }

Well, templates aren't the only case where we have attribute inference anymore (e.g. auto return functions have it), and I'm pretty sure that there have been several requests for fixing issues regards to local declarations so that they have inference (in particular, I think that there have been complaints about marking a function as pure having issues with internal declarations then not being treated as pure even though they could be). And for better or worse, the trend has been towards adding inference in cases where it's guaranteed that the code will always be available and will be available to any code using that code - and in the case of a declaration inside of a function like that, it's guaranted that anything referencing it is going to have access to the code. So, it doesn't surprise me at all if attribute inference has been added to local declarations like this. If you want to guarantee that no inference is happening, then you'll probably have to declare it directly in the module where it can't be infered due to the fact that a .di file could redeclare it without any function bodies.

- Jonathan M Davis

August 22, 2017
On Tuesday, 22 August 2017 at 16:27:05 UTC, Jonathan M Davis wrote:
>
> Well, templates aren't the only case where we have attribute inference anymore (e.g. auto return functions have it), and I'm pretty sure that there have been several requests for fixing issues regards to local declarations so that they have inference (in particular, I think that there have been complaints about marking a function as pure having issues with internal declarations then not being treated as pure even though they could be). And for better or worse, the trend has been towards adding inference in cases where it's guaranteed that the code will always be available and will be available to any code using that code - and in the case of a declaration inside of a function like that, it's guaranted that anything referencing it is going to have access to the code. So, it doesn't surprise me at all if attribute inference has been added to local declarations like this. If you want to guarantee that no inference is happening, then you'll probably have to declare it directly in the module where it can't be infered due to the fact that a .di file could redeclare it without any function bodies.
>
> - Jonathan M Davis

Yeah, this happens with @safe main also (below), but not for more regular local blocks. Anyway, I found it very confusing as that's not how I assumed @safe applied to unittests or main worked.

@safe void main()
{
    struct Foo {
        int foo(int i, string s) @safe { return 0; }
        double foo2(string s) @safe { return 0; }
    }
    printMemberTypes!(Foo);
}
August 22, 2017
On 8/22/17 2:19 PM, jmh530 wrote:

> Yeah, this happens with @safe main also (below), but not for more regular local blocks. Anyway, I found it very confusing as that's not how I assumed @safe applied to unittests or main worked.
> 
> @safe void main()
> {
>      struct Foo {
>          int foo(int i, string s) @safe { return 0; }
>          double foo2(string s) @safe { return 0; }
>      }
>      printMemberTypes!(Foo);
> }

The surprising part to me is that non-@safe main doesn't infer anything. Is that true?

-Steve
August 22, 2017
On Tuesday, 22 August 2017 at 18:25:31 UTC, Steven Schveighoffer wrote:
>> 
>> @safe void main()
>> {
>>      struct Foo {
>>          int foo(int i, string s) @safe { return 0; }
>>          double foo2(string s) @safe { return 0; }
>>      }
>>      printMemberTypes!(Foo);
>> }
>
> The surprising part to me is that non-@safe main doesn't infer anything. Is that true?
>
> -Steve

They aren't auto functions or templates, just normal member functions of a struct in main. I thought inferring function attributes was only for auto functions and templates. I never thought it would for member functions of structs in main. But yeah, I'm pretty those functions are @safe and not @safe @nogc nothrow pure.
August 22, 2017
On 8/22/17 5:44 PM, jmh530 wrote:
> On Tuesday, 22 August 2017 at 18:25:31 UTC, Steven Schveighoffer wrote:
>>>
>>> @safe void main()
>>> {
>>>      struct Foo {
>>>          int foo(int i, string s) @safe { return 0; }
>>>          double foo2(string s) @safe { return 0; }
>>>      }
>>>      printMemberTypes!(Foo);
>>> }
>>
>> The surprising part to me is that non-@safe main doesn't infer anything. Is that true?
>>
> 
> They aren't auto functions or templates, just normal member functions of a struct in main. I thought inferring function attributes was only for auto functions and templates. I never thought it would for member functions of structs in main. But yeah, I'm pretty those functions are @safe and not @safe @nogc nothrow pure.

Logically, any internal struct's code is available for examination wherever it could possibly be used. If it's returned, then the function return must be auto (and the function code available). If it's passed as a template parameter, it is available.

Inference could happen on any internal function anywhere, not just inside internal structs.

I'm not sure why the marking of the enclosing function should make a difference, either they all infer or they all don't infer. To me, this seems like a bug (the inconsistency).

-Steve