Discussion Thread: DIP 1044--Enum Type Inference--Community Review Round 1

Quirin Schroll qs.il.paperinik at gmail.com
Wed Nov 23 09:38:06 UTC 2022


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:
>
> ```d
> 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:

```d
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:
```d
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:
```d
Role r = member;
r = guest;
```
is as if:
```d
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.


More information about the Digitalmars-d mailing list