switch: using an expicit type as total is dangerous

forax at univ-mlv.fr forax at univ-mlv.fr
Sun Aug 30 11:37:35 UTC 2020


----- Mail original -----
> De: "Brian Goetz" <brian.goetz at oracle.com>
> À: "Remi Forax" <forax at univ-mlv.fr>, "amber-spec-experts" <amber-spec-experts at openjdk.java.net>
> Envoyé: Lundi 24 Août 2020 20:57:03
> Objet: Re: switch: using an expicit type as total is dangerous

>> 2/ using an explicit type for a total type is a footgun because the semantics
>> will change if the hierarchy or the return type of a method switched upon
>> change.
> 
> Sorry, I think this argument is a pure red herring.   I get why this is
> one of those "scary the first time you see it" issues, but I think the
> fear has been overblown to near-panic proportions.  We've spent a lot of
> time talking about it and, the more we talk, the less worried I am.

good for you,
the more i talk about it, the more i'm worried because you don't seem to understand that having the semantics that change underneath you is bad.

> 
> The conditions that have to combine for this to happen are already
> individually rare:
>     - a hierarchy change, combined with
>     - enough use-site type inference that is not obvious what the type
> dependencies are, combined with
>     - null actually being a member of the domain, combined with
>     - users not realizing null is a member of the domain.


nope, you don't need a hierarchy change, changing the return type (as noticed by Tagir) and null being part of the domain is enough. 

> 
> Then, for it to actually be a problem, not only do all of the above have
> to happen, but an unhandled null has to actually show up.
> 
> Even then, the severity of this case is low -- most likely, the NPE gets
> moved from one place to another.

nope see below

> 
> Even then, the remediation cost is trivial.

for having remediation, as a user you have to first see the change of semantics, but you don't.


Ok, let's take an example, i've written a method getLiteral()
  Number getLiteral(String token) {
    if (token.equals("null")) {
      return null; // null is part of the domain
    }
    try {
      return Integer.parseInt(token);
    } catch(NumberFormatException e) {
      return Double.parseDouble(token);
    }
  }

and a statement switch in another package/module
  switch(getLiteral(token)) {
    case Integer -> System.out.println("Integer"); 
    case Double -> System.out.println("Double"); 
    case Number -> System.out.println("null");
  }

but now i change getLiteral() to add string literal
  Object getLiteral(String token) {
    if (token.equals("null")) {
      return null; // null is part of the domain
    }
    if (token.startsWith("\"") {
      return token.substring(1, token.length() - 1);
    }
    try {
      return Integer.parseInt(token);
    } catch(NumberFormatException e) {
      return Double.parseDouble(token);
    }
  }

If i only recompile getLiteral(), and run the code containing the switch, i get a ICCE at runtime because the signature of getLiteral() has changed, which is good,
but if i now recompile the switch, the code compiles without any error but with a different semantics, duh ?

Using "case var _" as the last case at least keep the same semantics, using "default Number" does not compile.

[...]

Rémi


More information about the amber-spec-experts mailing list