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.