Triber
Triber

Reputation: 1555

Uninitialized variable in case statement

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?

  1. Wrap function with {$WARN USE_BEFORE_DEF OFF} and {$WARN USE_BEFORE_DEF ON}
  2. In every case statement use else Exit; with Result := 0 on the beginning
  3. Initialize every variable although the value will be never used

Upvotes: 1

Views: 490

Answers (2)

Dsm
Dsm

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.

Edit

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

David Heffernan
David Heffernan

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

Related Questions