sonar-delphi icon indicating copy to clipboard operation
sonar-delphi copied to clipboard

Implement support for constant expressions

Open Cirras opened this issue 1 year ago • 0 comments

Prerequisites

  • [X] This improvement has not already been suggested.
  • [X] This improvement would be generally useful, not specific to my code or setup.

Engine area

Delphi language support

Improvement description

Background

The Delphi compiler implements the following optimizations around constant expressions:

  • constant folding
  • constant propagation

This has significant implications for type resolution - see example.

Example

// overload for each integer type:
procedure X(B: Byte); overload;     begin WriteLn('Byte');     end;
procedure X(S: ShortInt); overload; begin WriteLn('ShortInt'); end;
procedure X(S: SmallInt); overload; begin WriteLn('SmallInt'); end;
procedure X(W: Word); overload;     begin WriteLn('Word');     end;
procedure X(I: Integer); overload;  begin WriteLn('Integer');  end;
procedure X(C: Cardinal); overload; begin WriteLn('Cardinal'); end;
procedure X(I: Int64); overload;    begin WriteLn('Int64');    end;
procedure X(U: UInt64); overload;   begin WriteLn('UInt64');   end;

const
  CFive = 5;         // technically ShortInt, but the value "5" is propagated to constant expressions
  CHundred = 25 * 4; // technically ShortInt, but the value "100" is propagated to constant expressions

procedure Test;
var
  I8: ShortInt;
  I16: SmallInt;
  I32: Integer;
  I64: Int64;
  U8: Byte;
  U16: Word;
  U32: Cardinal;
  U64: UInt64;
begin
  // For variables, all we need to do to lose the original type is tack on a unary `+`.
  // This is because the expression is evaluated at runtime, and only the following options are available at runtime:
  //
  // Positive(Integer): Integer
  // Positive(Cardinal): Cardinal
  // Positive(Int64): Int64
  // Positive(UInt64): UInt64

  X(+I8);  // Integer
  X(+I16); // Integer
  X(+I32); // Integer
  X(+I64); // Int64

  X(+U8);  // Integer
  X(+U16); // Integer
  X(+U32); // Cardinal
  X(+U64); // UInt64

  // But for constant expressions, that unary `+` (and indeed, all other arithmetic operators) are done at compile-time.
  // The type is resolved *after* the expression is evaluated, based on the value.

  X(+(0));                   // ShortInt
  X(+(128));                 // Byte
  X(+(256));                 // SmallInt
  X(+(32768));               // Word
  X(+(65536));               // Integer
  X(+(2147483648));          // Cardinal
  X(+(4294967296));          // Int64
  X(+(9223372036854775808)); // UInt64

  // The following constant expressions feature constant propagation.

  X(+CFive);                 // ShortInt
  X(+CHundred);              // ShortInt
  X(+(CHundred + CHundred)); // Byte
  X(+(256 - CFive));         // Byte
end;

See Also

Rationale

This is required for fully accurate type resolution.

Known affected areas

  • Type conversions from array constructor -> set
  • Expression type resolution (especially unary -)
  • Subrange type modeling (low and high are constant expressions)

Other benefits

  • Having access to evaluated constant expressions would be extremely useful for some rules.
    • Really, any rule where we might want to raise an issue based on the contents of a string
    • see: https://github.com/integrated-application-development/sonar-delphi/issues/94#issuecomment-1825031266

Cirras avatar Nov 30 '23 06:11 Cirras