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.
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.
I had to format it to differently to know what it will do:
ReplyDeleteprocedure 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.
Let me reformat and re-emphasize the weirdness...
ReplyDeleteHow 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.
After a refresh I've seen that you updated your question:
ReplyDeleteThe Value "True" that is assigned to Handled is never used. This is correct. (Its the value, not the variable!)
The Handled = True is used for case 1 & 2.
ReplyDeleteAs for case:
ReplyDeleteThere is no "begin" after the else in a case construct, but this is an implicit block.
Lars Fosdal No it's not.
ReplyDeletecase .. of
..
else
// implicit begin end block here
end.
Why doesn't the else work?
ReplyDeleteThe code as it should have been written...
ReplyDeleteNo 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.
Lars Fosdal Like I said, there is an implicit begin end block for the "else" in a case statement.
ReplyDeleteIt's different from it then else ...
That's the way it is.
That still doesn't explain why the code placed there only partially works. The else Test(4) is eliminated.
ReplyDeleteIt is eliminated because it can't be reached. Your formating is plaing tricks with you since the else of a case behaves differently.
ReplyDeleteTest(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.
ReplyDeleteUpdate Actually, it is now documented: http://docwiki.embarcadero.com/RADStudio/en/Declarations_and_Statements#Case_Statements
That doesn't make sense, IMO.
ReplyDeleteif condition
then DoA
else DoB
why would the DoB be unreachable?
Your program is equivalent to this one:
ReplyDelete{$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.
David Heffernan - Yet the output from the original post is
ReplyDeleteOne
3 is unhandled
Press Enter
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.
ReplyDeleteOK, now I get it - I still find it mindblowing...
ReplyDeleteI 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.
ReplyDeleteActually, 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:
ReplyDeleteA 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.
I don't think I've ever used a case else without begin/end - unless it was a single line.
ReplyDeleteI am still appalled...
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.
ReplyDeleteLars 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.
ReplyDeleteLars Fosdal Not else, but there are several other clauses which behave the same: "repeat", "finally", "except" for example.
ReplyDeleteThe ambiguity certainly is/was there in my head.
ReplyDeleteAsbjø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.
ReplyDeleteLars 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".
ReplyDeleteLars Fosdal Even in try except end one can have an else without a begin/end and it behaves just like the case statement. ;-)
ReplyDeletetry
except
on E: EOSError do
Writeln(E.Message);
else
Write('Exception');
Writeln(' unknown');
end;
Lars Fosdal So it's a head case? :)
ReplyDeleteMartin Wienold - good point.
ReplyDeleteWonder why they didn't opt for
try
exception E of
EOSError: ...;
else
end;
Bill Meyer - Clearly, a head case.
David Heffernan I thougt this was in very old documentation as well, but it isn't. Thanks!
ReplyDeleteCool. 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.
ReplyDeleteIt'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.