On Wednesday, 23 November 2022 at 03:24:30 UTC, Steven Schveighoffer wrote:
> [snip]
But if an implicit with(Role)
is added to the case clause, now it only ever refers to Role.member
. Essentially, you will end up with a switch that looks like:
switch(e) {
case Role.member: ...
case Role.member: ...
case Role.member: ...
default: ...
}
Which will then be a compiler error.
The fix would be to rename the iteration variable to something the user couldn't possibly use. It's not pretty.
TL;DR: Use a version of with
that matches lazily, i.e. when the identifier resolved to nothing else, and not greedily as the current one. [End of TL;DR]
Best thing is, lazy
is already an aptly named keyword and can be combined with with
to form lazy with
(same as static
and if
or foreach
or assert
combine to effectively two-word keywords).
The problem is that with(expr)
is greedy: If it can resolve an identifier ident
as expr.ident
, it will take it and not give it up even if that leads to a fail. (Like Regex greedy matching, thus the name.) The implicit with
must be lazy to be useful: If ident
cannot be resolved otherwise, in the context of lazy with(arg)
an attempt for arg.ident
is made.
In the above code, even if Role
has a member named member
, the function local member
is considered:
import std.stdio;
enum Role
{
guest,
member,
developer,
}
void main()
{
Role r;
lazy with (typeof(r)) // implicitly added?
switch(r)
{
static foreach(member; EnumMembers!Role )
{
case member: // sees foreach variable, therefore lazy with does nothing.
...
}
}
lazy with (typeof(r)) // implicitly added?
switch(r)
{
case guest: // as no other `guest` in scope, lazy with makes it Role.guest
...
case member: // as no other `member` in scope, lazy with makes it Role.member
...
}
}
If I’m not missing something, the only sad case is this:
immutable member = Role.developer; // Who does this anyways?
lazy with (typeof(r))
switch(r)
{
case guest: // lazy with makes it Role.guest
...
case member: // `member` in scope, effectively Role.developer.
...
}
The same can be done for declarations without auto
and maybe assignments:
Role r = member;
r = guest;
is as if:
Role r = () { lazy with(Role) return member; }();
lvalueExpresson = () { lazy with(typeof(lvalueExpresson)) return guest; }(); // maybe?
I guess declarations are unproblematic. For assignments, it might be surprising because lvalue expressions can be complex and a reminder for the type might be appropriate. On the other hand, if that’s the case, nothing stops you from being explicit. Another issue with assignment expressions is that not all left-hand side expressions have a type: Property setters and opIndexAssign don’t trivially give you the supposed right-hand side type. The type might even be a template type parameter. It’s a best effort solution at best.