Search code examples
delphidelphi-xe6

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

Solution

  • 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;