Issue 19125 - IFTI and inout removes head mutability qualifier on by-val parameters
Summary: IFTI and inout removes head mutability qualifier on by-val parameters
Status: NEW
Alias: None
Product: D
Classification: Unclassified
Component: dmd (show other issues)
Version: D2
Hardware: All All
: P1 regression
Assignee: No Owner
URL:
Keywords:
: 19749 (view as issue list)
Depends on:
Blocks:
 
Reported: 2018-07-29 13:14 UTC by Ali Ak
Modified: 2019-07-18 10:26 UTC (History)
1 user (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this issue.
Description Ali Ak 2018-07-29 13:14:42 UTC
From here: https://forum.dlang.org/post/jxcohrtxsfkechfgwrgo@forum.dlang.org


import std.stdio;

struct W(T) {
    T t;
}
auto wrap0(T)(auto ref inout(T) t) {
    return inout(W!T)(t);
}
auto wrap1(T)(auto ref inout(T) t) {
    return t;
}
void main() {
    pragma(msg, typeof(wrap0("foo"))); // removes it
    pragma(msg, typeof(wrap1("bar"))); // keeps it
    immutable int i0;
    pragma(msg, typeof(wrap1(i0))); // keeps it
    string s0 = "baz";
    pragma(msg, typeof(wrap0(s0))); // keeps it
}

prints:

immutable(W!(char[]))
immutable(string)
immutable(int)
W!string
Comment 1 Steven Schveighoffer 2018-07-30 13:16:55 UTC
The issue here is that IFTI applies inout to the head when it shouldn't

If you have a function like this:

struct S(T)
{
   T t;
}

inout(S!T) makeS(T)(inout(T) t)
{
   return inout S!T(t);
}

One expects the type parameter of S to match the type parameter passed in. inout is just saying "I'm not going to change anything"

However, in the case of types that have tail-mutability modifier capability (pointers, arrays), IFTI is pulling the mutability OUT from the tail and applying it to the entire parameter:

auto s1 = makeS("hello");
assert(is(typeof(s1) == immutable(S!(char[])));
auto s2 = makeS("hello".ptr);
assert(is(typeof(s2) == immutable(S!(char *));

But tail-modified values are distinctly more capable in terms of mutability than fully modified values. So IFTI I believe should NOT perform this adjustment, and just match T as string (and therefore the type of t should be inout(string)).

If one wishes to actually match inout to the modifier of the tail, one can do so with a specialization:

auto makeS(T)(inout(T)[] t)
{
   return inout S!(T[])(t);
}

Note that the use of auto ref in the original code is affecting what is inferred, because of the double-indirection rule. This makes it doubly confusing (see the cases for wrap0("foo") and wrap0(s0), which one might expect to be identical ).
Comment 2 Steven Schveighoffer 2019-07-17 15:36:39 UTC
I have another simpler case which is absolutely unacceptable:

Nullable!string s;

string x = s.get(""); // error

This is because Nullable.get is taking the above input as an inout(char[])!

It should be inout(string).

Checking all versions, looks like this worked in 2.065 and prior. Well, the concept worked anyway, since I don't think that form of get was added to Nullable until later.

An equivalent test:

struct S
{
    string boo;
    auto get(U)(inout(U) u) inout
    {
        return boo.length == 0 ? u : boo;
    }
}
void main()
{
    S s;
    string x = s.get("");
}

This fails from 2.066 on. Changing to regression.
Comment 3 Ali Ak 2019-07-18 10:24:29 UTC
*** Issue 19749 has been marked as a duplicate of this issue. ***
Comment 4 Ali Ak 2019-07-18 10:26:16 UTC
Yes. This is super annoying.

It also messes up type inference where you need the types to be inferred properly:

struct S(T) {
    T value = T.init;
}

auto ref make(T)(inout auto ref T value) {
    // T == char[] when infact it's string 
    return inout(S!T)(value);
}

auto ref f(T)(inout auto ref S!T s) {
    return make(s.value);
}

auto a = [make("hello"), S!string("hello")];

(plucked from duplicate issue)