Reputation: 1555
I still can't figure out how to get rid of warnings about uninitialized variables whenever I use the following structure, even though I know that this can never happen.
TCustomEnum = (ceValue1, ceValue2, ceValue3);
function DoSomething(LI_Enum: TCustomEnum): Integer;
var
lNumber : Integer;
begin
case LI_Enum of
ceValue1 : lNumber := 1;
ceValue2 : lNumber := 2;
ceValue3 : lNumber := 3;
end;
Result := 2 * lNumber;
end;
W1036 Variable 'lNumber' might not have been initialized
I found 3 solutions, but i don't like any of them. Especially with more variables or statements. Is there any other way how to avoid this?
{$WARN USE_BEFORE_DEF OFF}
and {$WARN USE_BEFORE_DEF ON}
else Exit;
with Result := 0
on the beginningUpvotes: 1
Views: 490
Reputation: 6013
By doing something like the following
function DoSomething(LI_Enum: TCustomEnum): Integer;
var
lNumber : Integer;
begin
case LI_Enum of
ceValue1 : lNumber := 1;
ceValue2 : lNumber := 2;
ceValue3 : lNumber := 3;
else raise exception.create('Oops I forgot one of the LI_Enum values')
end;
Result := 2 * lNumber;
end;
Perhaps a better exception text or even not raising an exception at all (and assigning a different value to lNumber), but raising an exception does have the benefit of prompting you if, say, six months down the line you add a new case value.
The point really is that the compiler is correct. The underlying structure for an enum is some form of (unsigned) integer so it is perfectly possible for the enum to contain an illegal value, say 27, for example. There are lots of ways this can arise in practice. So you need to cater for that possibility if you are writing complete code. The compiler is just warning you that you have not catered for that possibility.
Upvotes: 6
Reputation: 612993
I find this compiler warning a little disappointing. After all, surely the compiler can detect that you have covered all possible values of the enumerated type. I don't believe that it should be worrying about you having put an invalid ordinal in the enumerated type, if indeed that is the thinking behind this warning.
In any case, I personally use the following helper methods to deal with this:
procedure RaiseAssertionFailed; overload;
procedure RaiseAssertionFailed(var v1); overload;
procedure RaiseAssertionFailed(var v1,v2); overload;
....
procedure DoRaiseAssertionFailed;
begin
raise EAssertionFailed.CreateFmt(
'A critical error has occurred:'+ sLineBreak + sLineBreak +
' Assertion failed at %p.'+ sLineBreak + sLineBreak +
'In order to avoid invalid results or data corruption please close the program and report '+
'the above error code along with any other information relating to this problem.',
[ReturnAddress]
) at ReturnAddress;
end;
procedure RaiseAssertionFailed;
asm
JMP DoRaiseAssertionFailed;
end;
procedure RaiseAssertionFailed(var v1);
asm
JMP DoRaiseAssertionFailed;
end;
procedure RaiseAssertionFailed(var v1,v2);
asm
JMP DoRaiseAssertionFailed;
end;
Your code would then become:
function DoSomething(LI_Enum: TCustomEnum): Integer;
var
lNumber : Integer;
begin
case LI_Enum of
ceValue1 : lNumber := 1;
ceValue2 : lNumber := 2;
ceValue3 : lNumber := 3;
else
RaiseAssertionFailed(lNumber);
end;
Result := 2 * lNumber;
end;
This is very similar to the approach outlined by @Dsm. If you use that approach then compiler can see that you are raising an exception, and knows that lNumber
does not need to be initialized.
I prefer though to wrap the raising of the exception into a shared function. That way I don't need to write the same error message again and again. An application of the DRY principle.
However, if you do this, and move the raise into a shared function, then the compiler is not capable of determining that the function will raise an exception. Hence the untyped var parameters. This allows you to mark the variable as being potentially modified and so suppress the compiler warning.
Yet another approach would be to declare an exception class that supplied the text in its parameterless constructor.
type
EInternalError = class(Exception)
public
constructor Create;
end;
constructor EInternalError.Create;
begin
inherited Create(
'...' // your text goes here
);
end;
Then your code becomes:
function DoSomething(LI_Enum: TCustomEnum): Integer;
var
lNumber : Integer;
begin
case LI_Enum of
ceValue1 : lNumber := 1;
ceValue2 : lNumber := 2;
ceValue3 : lNumber := 3;
else
raise EInternalError.Create;
end;
Result := 2 * lNumber;
end;
Upvotes: 3