When tupleof is used on a const type, the types of the resulting symbols are const. However, if the result of tupleof is aliased (either directly within a function or inside an eponymous template), then the constness is lost.
---
void main()
{
import std.meta : AliasSeq;
static struct S
{
int i;
int* ptr;
string str;
}
static assert(is(typeof(S.tupleof) == AliasSeq!(int, int*, string)));
static assert(is(typeof(const(S).tupleof) == AliasSeq!(const int, const int*, const string)));
alias CS = const S;
static assert(is(typeof(CS.tupleof) == AliasSeq!(const int, const int*, const string)));
alias Symbols = S.tupleof;
alias ConstSymbols = CS.tupleof;
static assert(is(typeof(Symbols) == AliasSeq!(int, int*, string)));
// fails
static assert(is(typeof(ConstSymbols) == AliasSeq!(const int, const int*, const string)));
static assert(is(typeof(FieldSymbols!S) == AliasSeq!(int, int*, string)));
// fails
static assert(is(typeof(FieldSymbols!(const S)) == AliasSeq!(const int, const int*, const string)));
}
template FieldSymbols(T)
{
alias FieldSymbols = T.tupleof;
}
---
The lines that are marked with // fails result in
---
test.d(22): Error: static assert: `is((int, int*, string) == (const(int), const(int*), const(string)))` is false
---
indicating that the constness was lost when the result of tupleof was aliased.
Comment #1 by nick — 2024-05-01T14:43:24Z
> S.tupleof
I don't think it makes sense to allow `tupleof` to be a type property. It should be instance only. The spec does not mention its use on a type.
https://dlang.org/spec/class.html#tupleof
Also in dmd/typesem.d, dotExp:
```d
else if (ident == Id._tupleof)
{
if (e.isTypeExp())
{
error(e.loc, "`.tupleof` cannot be used on type `%s`", mt.toChars);
return ErrorExp.get();
}
```
For some reason that is not being triggered.
Comment #2 by issues.dlang — 2024-05-01T22:56:04Z
(In reply to Nick Treleaven from comment #1)
> > S.tupleof
>
> I don't think it makes sense to allow `tupleof` to be a type property. It
> should be instance only. The spec does not mention its use on a type.
> https://dlang.org/spec/class.html#tupleof
The spec uses tupleof on a type right there in the example that you're linking to even before it uses it on a variable:
---
class Foo { int x; long y; }
static assert(__traits(identifier, Foo.tupleof[0]) == "x");
static assert(is(typeof(Foo.tupleof)[1] == long));
---
And both druntime and Phobos having been using tupleof on types for years - e.g. std.traits.Fields. It would break a lot of existing code if tupleof stopped working on types.
Also, in most cases, it's less error-prone to do type introspection on types rather than values or symbols. Some kinds of type introspection have to be done on values or symbols, but pretty much any time that a trait takes an alias rather than a type, it makes it far trickier to write it correctly, and corner cases are frequently a problem. If anything, the fact that type qualifers are being lost when aliasing the result of tupleof is an example of why aliasing symbols when doing type introspection (as opposed to aliasing types) tends to be error-prone. We unfortunately have variety of bugs where weird stuff happens like type qualifiers disappearing when operating on symbols rather than on types, and it's why some of the traits in std.traits are overloaded so that they can explicitly take types or use an alias parameter.
Using tupleof on an instance has some uses, since then you can do stuff like
---
foo.tupleof[0] = 1;
---
but when doing type introspection, it makes far more sense to be operating on the type, and tupleof has worked that way for many years.
The problem in this bug report is that for some reason, aliasing the result of tupleof results in symbols that behave differently from the direct result of tupleof, and the qualifiers are lost. I see no reason why it should be reasonable for that to happen.
Comment #3 by b2.temp — 2024-05-01T23:35:51Z
(In reply to Nick Treleaven from comment #1)
> > S.tupleof
>
> I don't think it makes sense to allow `tupleof` to be a type property. It
> should be instance only. The spec does not mention its use on a type.
> https://dlang.org/spec/class.html#tupleof
>
> Also in dmd/typesem.d, dotExp:
> ```d
> else if (ident == Id._tupleof)
> {
> if (e.isTypeExp())
> {
> error(e.loc, "`.tupleof` cannot be used on type `%s`",
> mt.toChars);
> return ErrorExp.get();
> }
> ```
> For some reason that is not being triggered.
The reason must be that `S` in `S.tupleof` is not a TypeExp it's a DeclExp.
Comment #4 by nick — 2024-05-02T17:16:34Z
> The reason must be that `S` in `S.tupleof` is not a TypeExp it's a DeclExp.
No, the quoted code is for static array tupleof. I tried adding it for classes and structs but dotExp seems to have a phantom expression e which is a VarExp.
Comment #5 by nick — 2024-05-02T17:27:34Z
> The spec uses tupleof on a type right there in the example
Sorry, yes. We shouldn't break code then.
Comment #6 by b2.temp — 2024-05-02T17:56:31Z
(In reply to Nick Treleaven from comment #4)
> > The reason must be that `S` in `S.tupleof` is not a TypeExp it's a DeclExp.
>
> No, the quoted code is for static array tupleof.
I see, the compiler code you quoted is not for `S.tupleof`
> I tried adding it for
> classes and structs but dotExp seems to have a phantom expression e which is a VarExp.
I'm not sure if that helps but, as you might already know ?, the D front-end rewrites the DotExp lhs to what it resolves to e.g either a VarExp, a TypeExp, a DeclExp, etc. If it's not that then maybe that VarExp is used to extract a side-effects or to create a lvalue. but I dont see why this would be done on a type, that would be a bug.
Comment #7 by robert.schadek — 2024-12-13T19:34:48Z