Comment #0 by ali.akhtarzada — 2018-07-29T17:44:58Z
struct W(T) {
this(U : T)(auto ref inout(U) val) inout {}
}
auto wrap0(T)(auto ref inout(T) t) {
return inout W!T();
}
auto wrap1(T)(auto ref inout(T) t) {
return t;
}
void main() {
wrap0!int(3); // ok
wrap0!(const int)(3); // ok
wrap0!(immutable int)(3); // Error: inout on return means inout must be on a parameter
wrap1!int(3); // ok
wrap1!(const int)(3); // ok
wrap1!(immutable int)(3); // ok
}
Comment #1 by schveiguy — 2018-07-30T12:50:29Z
Really, the issue here is that the compiler disallows inout returns without inout parameters. If we fixed issue 13006, this would resolve itself.
However, we have the option to fix this issue, or at least fix the error message to indicate what the compiler has done.
Comment #2 by dfj1esp02 — 2018-08-01T14:21:42Z
struct W(T) {
this(inout(T) val) inout {}
}
auto wrap0(T)(inout(T) t) {
return inout W!T(t);
}
void main() {
wrap0!int(3); // ok
wrap0!(const int)(3); // ok
wrap0!(immutable int)(3); // Error: inout on return means inout must be on a parameter
}
The code has a mistake: it should be return inout W!T(t); - the constructor has an inout parameter, but the error is the same. Looks more like a bug than an enhancement.
Comment #3 by dfj1esp02 — 2018-08-01T14:24:52Z
inout on return means inout must be on a parameter as well for pure nothrow @nogc @safe inout(W!(immutable(int)))(immutable(int) t)
Looks like it's a signature of wrap0 function - the name is missing.
Comment #4 by schveiguy — 2018-08-01T14:43:50Z
Aye, the compiler is stripping inout from the type because it knows that inout(immutable(T)) can be rewritten as immutable(T). It's not necessarily a bug in the translation (though the error really is questionable). It's a bug in the diagnostic message -- it's saying you didn't put inout on the parameter, when you clearly did.
The bug in the first comment was not in the original example that showed the error, it was a copy-paste error (as you have discovered, it still fails).
Comment #5 by dfj1esp02 — 2018-08-02T11:03:20Z
(In reply to Steven Schveighoffer from comment #4)
> Aye, the compiler is stripping inout from the type because it knows that
> inout(immutable(T)) can be rewritten as immutable(T).
Then wrap1 would suffer from it too, but it doesn't.
Comment #6 by dfj1esp02 — 2018-08-02T11:04:25Z
Uh, no, it does.
Comment #7 by ali.akhtarzada — 2018-08-03T07:58:00Z
Wait how does wrap1 suffer form it? wrap1 compiles fine?
And if this is not a bug a more diagnostic, what should the error message be here?
Comment #8 by schveiguy — 2018-08-03T09:23:09Z
(In reply to Ali Ak from comment #7)
> Wait how does wrap1 suffer form it? wrap1 compiles fine?
It compiles fine because it returns it's parameter without specifying inout. Essentially, the instantiated function looks like this:
auto wrap1(immutable(int) t) {
return t;
}
> And if this is not a bug a more diagnostic, what should the error message be
> here?
It is a bug that has diagnostic problems. I put the diagnostic tag in because it's telling you something that is false.
What could be done is adding a note to the error like "Note: inout(immutable(int)) is reduced to immutable(int)" when an inout error such as the above occurs.
If we simply got rid of the restriction, everything gets better.
Comment #9 by dfj1esp02 — 2018-08-03T11:49:13Z
See also issue 9983 comment 1 - if you want to invoke a templated inout constructor, you will first need to completely remove inout qualifier from it.
Comment #10 by ali.akhtarzada — 2018-08-03T13:20:35Z
Ahhh this inout just hurts my head. So is fixing inout on return a much bigger bug than the more simple "don't remove inout from a parameter if the return value has inout"?
Currently you can do this and then things behave consistently. Fixes this problem but does't solve the "allowing inout on return" problem.
auto wrap0(T)(inout(T) t) {
static if (is(T == immutable)) {
return immutable W!T();
} else {
return inout W!T();
}
}
When T is an immutable it seems like the compiler should be doing this by itself. If it decides to "replace" inout(immutable) with just immutable, then it should replace inout everywhere with immutable. SO why doesn't inout(W!(immutable int)) become immutable(T!int)? That would also make this error go away right?
Maybe the compiler shouldn't be reducing inout in the first place? It is supposed to be (IIUC) qualifiers in => qualifiers out right? So why does it remove it before the it "lands" somewhere? This makes no sense regardless of immutable being the strongest qualifier. The purposes of inout and immutable seem orthogonal. It's not a mutability qualifier, it's a "transport of mutability information" qualifier right?
Comment #11 by schveiguy — 2018-08-04T11:17:25Z
(In reply to Ali Ak from comment #10)
> Ahhh this inout just hurts my head. So is fixing inout on return a much
> bigger bug than the more simple "don't remove inout from a parameter if the
> return value has inout"?
I'm thinking now that actually, even removing the restriction of saying inout can't be on the return unless it's on the parameter (i.e. issue 13006) doesn't fix the code exactly, but it will move the error to where it makes more sense. In other words, it will error on the line where you try to pass in an immutable(T) to an inout W!T. Then the error is more obvious.
So you are right, it's not as simple as that. My analysis in comment 2 is not correct.
>
> Currently you can do this and then things behave consistently. Fixes this
> problem but does't solve the "allowing inout on return" problem.
>
> auto wrap0(T)(inout(T) t) {
> static if (is(T == immutable)) {
> return immutable W!T();
> } else {
> return inout W!T();
> }
> }
I'm still finding trouble understanding why you want to specify immutable explicitly as the T value. Why not just mutable T? Then you don't need that machinery at all.
> When T is an immutable it seems like the compiler should be doing this by
> itself. If it decides to "replace" inout(immutable) with just immutable,
> then it should replace inout everywhere with immutable. SO why doesn't
> inout(W!(immutable int)) become immutable(T!int)? That would also make this
> error go away right?
But that's not what you specified. inout(SomeTemplate!(immutable(T))) cannot be replaced in all cases with immmutable(SomeTemplate!T). In this case, it would be OK, but the compiler would have to do a lot to prove that.
> Maybe the compiler shouldn't be reducing inout in the first place? It is
> supposed to be (IIUC) qualifiers in => qualifiers out right? So why does it
> remove it before the it "lands" somewhere? This makes no sense regardless of
> immutable being the strongest qualifier. The purposes of inout and immutable
> seem orthogonal. It's not a mutability qualifier, it's a "transport of
> mutability information" qualifier right?
Its behavior is the least common denominator of all 3 mutability parameters. The things you can do with inout are all the things you could do with a mutable, immutable, or const version, and nothing more.
However, any qualifier that is added on top of immutable is immutable, so the compiler is stripping away inout to simplify things.
But it can only do this on types that are fully immutable. In the case of the struct, only the member is immutable, it's not immutable on the whole struct.
Again, the best fix for this is simply not to instantiate with immutable(T). You can achieve everything without it.
Comment #12 by ali.akhtarzada — 2018-08-05T17:27:45Z
(In reply to Steven Schveighoffer from comment #11)
>
> I'm still finding trouble understanding why you want to specify immutable
> explicitly as the T value. Why not just mutable T? Then you don't need that
> machinery at all.
It's not that I want to or don't want to, it's that I think it should work consistently.
>
> > When T is an immutable it seems like the compiler should be doing this by
> > itself. If it decides to "replace" inout(immutable) with just immutable,
> > then it should replace inout everywhere with immutable. SO why doesn't
> > inout(W!(immutable int)) become immutable(T!int)? That would also make this
> > error go away right?
>
> But that's not what you specified. inout(SomeTemplate!(immutable(T))) cannot
> be replaced in all cases with immmutable(SomeTemplate!T). In this case, it
> would be OK, but the compiler would have to do a lot to prove that.
Ah true.
>
> > Maybe the compiler shouldn't be reducing inout in the first place? It is
> > supposed to be (IIUC) qualifiers in => qualifiers out right? So why does it
> > remove it before the it "lands" somewhere? This makes no sense regardless of
> > immutable being the strongest qualifier. The purposes of inout and immutable
> > seem orthogonal. It's not a mutability qualifier, it's a "transport of
> > mutability information" qualifier right?
>
> Its behavior is the least common denominator of all 3 mutability parameters.
> The things you can do with inout are all the things you could do with a
> mutable, immutable, or const version, and nothing more.
Yes, so it feels weird that since it acts as a common denominator, the compiler throws it away before it's done acting O_o
>
> However, any qualifier that is added on top of immutable is immutable, so
> the compiler is stripping away inout to simplify things.
Ya I understand that's what's happening now. It just sounds like the compiler is doing the wrong thing by removing inout in this case. Because it leads to a compiler error with code that should "obviously" work.
It would be nice to just find this simplification line (if it even is a line) in the compiler and see if just making it not "simplify" removal of inout (i guess any inner inouts can be removed) will just keep everything working including this case. I'm just not familiar enough with dmd to be able to give this a try just yet.
>
> But it can only do this on types that are fully immutable. In the case of
> the struct, only the member is immutable, it's not immutable on the whole
> struct.
>
> Again, the best fix for this is simply not to instantiate with immutable(T).
> You can achieve everything without it.
Well I guess that's more a "it works if you do it like this" solution than a fix :) D supports explicit template instantiation as a feature of the language no? But it's not "working" in this case - T can be given any qualified type, inout acts as a common denominator => seems like this should work.
Comment #13 by ali.akhtarzada — 2019-07-18T10:22:55Z
Ran in to this again why writing a library. Removing diagnostic because I don't see why the compiler can't make this edge case work, and marking it as diagnostic seems to miss the point of the issue.
void f(immutable int) {}
f(3) // works
wrap!(const int)(3) // works
so wrap!(immutable int)(3) should work.
and it does with a hack so why not support it properly?
Comment #14 by robert.schadek — 2024-12-13T19:00:00Z