Guess the output of this program.

Guess the output of this program.
Is this three Delphi bugs for the price of one?

Compiler mentions: 
[dcc32 Hint] WeirdCaseConstruct.dpr(11): H2077 Value assigned to 'Handled' never used

1. If statement inside case - after ELSE option?
2. Handled is actually used
3. else Test(4) is never called
#wtf  XE7.1 (32-bit, Windows)

program WeirdCaseConstruct;
{$APPTYPE CONSOLE}
{$R *.res}
uses
  System.SysUtils;

procedure Test(const Value: Integer);
var
  Handled: Boolean;
begin
  Handled := True; // <- Hinted
  case Value of
    1: writeln('One');
    2: writeln('Two');
    else Handled := False;
    if not Handled
     then Writeln(Value, ' is unhandled')
      else Test(4);
  end;
end;

begin
  Test(1);
  Test(3);
  Write('Press Enter');
  Readln;
end.

Comments

  1. I had to format it to differently to know what it will do:
    procedure Test(const Value: Integer);
    var
     Handled: Boolean;
    begin
     Handled := True;
     case Value of
      1: writeln('One');
      2: writeln('Two');
     else
      Handled := False;
      if not Handled then
       writeln(Value, ' is unhandled')
      else
       Test(4);
     end;
    end;

    Output should be:
    One
    3 is unhandled
    Press Enter

    PS.
    I don't see a bug.

    ReplyDelete
  2. Let me reformat and re-emphasize the weirdness...
    How can the statement in italics be valid inside a case statement?

    program WeirdCaseConstruct;
    {$APPTYPE CONSOLE}
    {$R *.res}
    uses
      System.SysUtils;

    procedure Test(const Value: Integer);
    var
      Handled: Boolean;
    begin
      Handled := True; // <- Hinted
      case Value of
        1: writeln('One');
        2: writeln('Two');
        else begin
          Handled := False;
        end;
       
      if not Handled
         _then Writeln(Value, ' is unhandled')_
          else Test(4);
      end;
    end;

    begin
      Test(1);
      Test(3);
      Write('Press Enter');
      Readln;
    end.

    ReplyDelete
  3. After a refresh I've seen that you updated your question:
    The Value "True" that is assigned to Handled is never used. This is correct. (Its the value, not the variable!)

    ReplyDelete
  4. The Handled = True is used for case 1 & 2.

    ReplyDelete
  5. As for case:
    There is no "begin" after the else in a case construct, but this is an implicit block.

    ReplyDelete
  6. Lars Fosdal  No it's not.
    case .. of
     ..
    else
      // implicit begin end block here
    end.

    ReplyDelete
  7. The code as it should have been written...
    No hint, else works.

    program WeirdCaseConstruct;
    {$APPTYPE CONSOLE}
    {$R *.res}
    uses
      System.SysUtils;

    procedure Test(const Value: Integer);
    var
      Handled: Boolean;
    begin
      Handled := True;
      case Value of
        1: writeln('One');
        2: writeln('Two');
        else begin
          Handled := False;
        end;
      end;
      if not Handled // <- Outside case
         then Writeln(Value, ' is unhandled')
          else Test(4);
    end;

    begin
      Test(1);
      Test(3);
      Write('Press Enter');
      Readln;
    end.

    ReplyDelete
  8. Lars Fosdal Like I said, there is an implicit begin end block for the "else" in a case statement.
    It's different from it then else ...

    That's the way it is.

    ReplyDelete
  9. That still doesn't explain why the code placed there only partially works.  The else Test(4) is eliminated.

    ReplyDelete
  10. It is eliminated because it can't be reached. Your formating is plaing tricks with you since the else of a case behaves differently.

    ReplyDelete
  11. Test(4) is eliminated because the compiler can prove that Handled = False when Handled is tested. This is an undocumented "feature" of language that has been present for as long as I can remember. I bet it's found in old TP.

    Update Actually, it is now documented: http://docwiki.embarcadero.com/RADStudio/en/Declarations_and_Statements#Case_Statements

    ReplyDelete
  12. That doesn't make sense, IMO.

    if condition
      then DoA
       else DoB

    why would the DoB be unreachable?

    ReplyDelete
  13. Your program is equivalent to this one:

    {$APPTYPE CONSOLE}

    procedure Test(const Value: Integer);
    var
      Handled: Boolean;
    begin
      Handled := True; // <- Hinted
      case Value of
        1:
          writeln('One');
        2:
          writeln('Two');
      else
        begin
          Handled := False;
          if not Handled then
            writeln(Value, ' is unhandled')
          else
            Test(4);
        end;
      end;
    end;

    begin
      Test(1);
      Test(3);
      Write('Press Enter');
      Readln;
    end.

    ReplyDelete
  14. David Heffernan - Yet the output from the original post is
    One
    3 is unhandled
    Press Enter

    ReplyDelete
  15. If you'd asked this on SO, then we could have produced a nice Q&A that would have been curated for anybody else in the future who comes across this issue. As it stands, the value here will be lost.

    ReplyDelete
  16. OK, now I get it - I still find it mindblowing...

    ReplyDelete
  17. I guess it just goes to show that you should really mind the hints in your code, not only the warnings.  Perhaps a warning about code being eliminated would be more appropriate in this case.

    ReplyDelete
  18. Actually, this is now documented (http://docwiki.embarcadero.com/RADStudio/en/Declarations_and_Statements#Case_Statements). It did not use to be. The docs now say:

    A case statement can have a final else clause:

    case selectorExpression of
      caseList1: statement1;
       ...
      caselistn: statementn;
    else
      statements;
    end

    where statements is a semicolon-delimited sequence of statements.

    ReplyDelete
  19. I don't think I've ever used a case else without begin/end - unless it was a single line.
    I am still appalled...

    ReplyDelete
  20. The block nature of the else clause was evident in the TP 1.0 manual. No exposition was given to the topic, but the example in the manual had two lines of code in the else block. And as the code was well formatted, the intention was apparent.

    ReplyDelete
  21. Lars Fosdal  Why be appalled. Where is the ambiguity? There is none. The real problem is the whole single/compound statement. It was "fixed" in Modula-2. Too late for us though.

    ReplyDelete
  22. Lars Fosdal Not else, but there are several other clauses which behave the same: "repeat", "finally", "except" for example.

    ReplyDelete
  23. The ambiguity certainly is/was there in my head.

    ReplyDelete
  24. Asbjørn Heid - My error was to assume that the else in case, behaved like else in if.  The other ones you mention are block defining in their own right.  I never was aware that the case else behaved that way.  Now, I feel silly - and annoyed.

    ReplyDelete
  25. Lars Fosdal they are few exceptions for the usage of begin/end to declare a block of instructions: "repeat ... until", "try ... except...end", "try ..finally...end", and the "case  else ... end".

    ReplyDelete
  26. Lars Fosdal Even in try except end one can have an else without a begin/end and it behaves just like the case statement. ;-)
    try
    except
      on E: EOSError do
        Writeln(E.Message);
    else
      Write('Exception');
      Writeln(' unknown');
    end;

    ReplyDelete
  27. Lars Fosdal So it's a head case? :)

    ReplyDelete
  28. Martin Wienold - good point.
    Wonder why they didn't opt for

    try
    exception E of
      EOSError: ...;
      else
    end;

    Bill Meyer - Clearly, a head case.

    ReplyDelete
  29. David Heffernan I thougt this was in very old documentation as well, but it isn't. Thanks!

    ReplyDelete
  30. Cool. I've always been overly verbose in except on, and especially repeat (a syntactically begin-end-eye-opener). Never thought about the case else the same way.
    It's the same thing apparently. I do not think anyone would mind (except when sitting on loads of legacy code) if begin-end was enforced in these cases.

    ReplyDelete

Post a Comment