Pacific Blue Software Logo

Delphi Warnings W1050, W1057 and W1058 Explained - and How to Fix Them

Delphi Warnings W1050, W1057 and W1058 Explained - and How to Fix Them

Since Delphi 2009, string is UnicodeString and Char is a 2-byte WideChar. Code written before then, or library code that still uses ShortString, AnsiString and AnsiChar on purpose, produces three warnings over and over:

Below is what each one means, which ones point at a real problem, and fixes that keep the code fast and keep ShortString and AnsiChar where they are wanted. Every result quoted was measured with Delphi 13, on both Win32 and Win64.

W1050: WideChar Reduced to Byte Char

A Pascal set holds at most 256 values, so a set of characters is always a set of AnsiChar. Testing a Char against one gives W1050:

function IsDigit(const C: Char): Boolean;
begin
  Result := C in ['0'..'9'];                       // W1050
end;

The warning reads as if the character is cut down to one byte, so that #$0130 (capital I with a dot, low byte $30) would count as the digit '0'. In current Delphi that does not happen. A test of all 65,536 characters against literal sets, untyped constants, typed constants and set variables found no false matches on Win32 or Win64 - the compiler checks that the character is below 256 before it looks in the set.

The real danger is the usual way people silence the warning:

if AnsiChar(C) in ['0'..'9'] then                 // no warning, but wrong

A typecast of a Char variable to AnsiChar keeps only the low byte. Across all 65,536 characters this gave 2,550 false matches, the first being #$0130. The warning goes away and a real bug arrives in its place.

The fixes that are both quiet and correct:

// Any set, since Delphi 2009
Result := CharInSet(C, ['0'..'9']);

// A simple range - no set at all
Result := (C >= '0') and (C <= '9');

CharInSet is an inline function in System.SysUtils that returns False for anything above 255 and tests the set otherwise. It does not exist before Delphi 2009, so code that must still compile on older versions needs a {$IFDEF UNICODE} around it or a fallback of its own.

Speed is not a reason to choose either way. In a test of 131 million lookups, Win64 took 336 ms with in and 256 ms with CharInSet; Win32 took 52 ms and 59 ms.

Where the character really is an AnsiChar - a character from a ShortString or AnsiString - there is no warning and nothing to fix. Leave that code alone.

When W1050 Points at the Declaration

If W1050 appears on a type declaration such as TCharSet = set of AnsiChar, look for an alias. Old compatibility units sometimes contain this:

type
  AnsiChar = Char;   // left over from an old compatibility unit

type
  TCharSet = set of AnsiChar;   // W1050 here

With that alias in scope, every AnsiChar in every unit that uses it is really a 2-byte Char - in a test, SizeOf(AnsiChar) was 2. The set itself is still 32 bytes, because a set holds 256 values whatever the element type, so nothing looks wrong until you notice the warning on the declaration. Every variable, parameter and record field declared as AnsiChar is twice the size its author meant, and every AnsiChar overload is really a Char overload. Remove the alias for desktop compilers. If the unit is shared with TMS WEB Core, which has no AnsiChar at all, keep the alias for that compiler only:

{$IFDEF PAS2JS}
type
  AnsiChar = Char;
{$ENDIF}

W1057: Implicit String Cast

W1057 means the compiler inserted a conversion you did not write. Nothing is lost - it is telling you that a conversion runs every time this line runs. Two common sources:

Str into a string

Str is an old Turbo Pascal routine whose output is always a ShortString. Given a string, the compiler converts behind the scenes:

function NumToStr(const ANum: Int64; const ALen: Integer): string;
var
  I: Integer;
begin
  Str(ANum:ALen, Result);                          // W1057
  for I := 1 to Length(Result) do
    if Result[I] = ' ' then
      Result[I] := '0';
end;

Do the work in a ShortString and convert once, explicitly:

function NumToStr(const ANum: Int64; const ALen: Integer): string;
var
  I: Integer;
  S: ShortString;
begin
  Str(ANum:ALen, S);
  for I := 1 to Length(S) do
    if S[I] = ' ' then
      S[I] := '0';
  Result := string(S);
end;

The padding loop now edits a buffer on the stack instead of a reference-counted string, and the conversion happens once. Over 5 million calls the second version took 305 ms against 329 ms - about 7% faster. The results were identical for every value from -100,000 to 100,000.

AnsiChar added to a string

Result := Result + C;            // C: AnsiChar - W1057
Result := Result + string(C);    // same result, no warning

Use string(C), not Char(C). They are not the same. The implicit conversion and string(C) translate the byte through the code page, so on a Windows-1252 system byte $80 becomes the euro sign, #$20AC. Char(C) keeps the byte value and gives #$0080. Over all 256 byte values, string(C) matched the implicit conversion every time and Char(C) differed for 27 of them.

A different overload than you think

W1057 on a call often means the overload you expected does not exist. A library with LeftStr overloads for ShortString, AnsiString and string may only declare the first two inside {$IFDEF}s. When they are switched off, every ShortString call goes to the string version and converts both ways. Calling Copy directly avoids the question, because Copy works natively on every string type. The same applies to Pos with an AnsiChar and an AnsiString: Pos(AnsiString(ACh), ALine) keeps the search in AnsiString.

W1058: Potential Data Loss

W1058 is W1057 in the dangerous direction - from string to ShortString or AnsiString:

function ShortCode(const AName: string): ShortString;
begin
  Result := Copy(AName, 1, 3);                     // W1058
end;

Two things can be lost. Characters that do not exist in the code page become ?, and a ShortString keeps at most 255 characters. Write the cast only after deciding that is acceptable for this value:

Result := ShortString(Copy(AName, 1, 3));

If you cannot say for certain that the text is short and single-byte, the right fix is to keep it as string.

Testing

Summary


PBS Delphi - String and Character Warnings


Back to Articles for Developers
Back to Articles on Delphi
Interposer Classes in Delphi
How to Store and Transfer Delphi Sets with More Than 64 Elements

If you found this useful, then please consider making a donation.

paypal
QR Code for donation Please donate if helpful