WITH list improvementsFollowing up on
@440bx's suggestion - the compiler now detects duplicate symbols in WITH lists and rejects them outright:
type
TRec = record
a, b, c: string;
end;
procedure main;
var
r1, r2: TRec;
begin
// Error: Duplicate symbol "r1" in WITH list
with r1, r2, r1 do
a := 'foo';
end;
begin
main;
end.
As I mentioned earlier, this only covers plain symbol references. Arbitrary expressions like
with p^, GetRec(), a[i] do are left alone - "duplicate" isn't really decidable there. For the simple case though, duplicates are almost always a mistake, so it's an error rather than a warning.
Field shadowing warningThe second piece of this update - bigger in terms of how much code it took to implement - when two records in a WITH list share a field name, the later one silently shadows the earlier one. Classic footgun. Now the compiler warns:
type
TPerson = record
name: string;
age: integer;
end;
TCompany = record
name: string;
founded: integer;
end;
var
p: TPerson;
c: TCompany;
begin
p.name := 'Alice';
p.age := 30;
c.name := 'Acme';
c.founded := 1999;
// Warning: Field "name" in WITH entry shadows an earlier field with the same name
with p, c do
writeln('name=', name, ' age=', age, ' founded=', founded);
readln;
end.
The
name in the writeln resolves to
c.name (
Acme), not
p.name - exactly the kind of thing you want flagged. Both diagnostics are on by default in unleashed mode.
Enum unboxing in WITHAnother one from
@440bx's wishlist - in unleashed mode the WITH statement now accepts an enum type name, and exposes its members inside the block.
{$mode unleashed}
type
TLogLevel = (LogDebug, LogInfo, LogWarn, LogError);
var
enabled: set of TLogLevel;
begin
with TLogLevel do
enabled := [LogInfo, LogWarn, LogError];
writeln('debug is ', if LogDebug in enabled then 'on' else 'off');
writeln('info is ', if LogInfo in enabled then 'on' else 'off');
writeln('warn is ', if LogWarn in enabled then 'on' else 'off');
writeln('error is ', if LogError in enabled then 'on' else 'off');
end.
{$mode unleashed}
{$scopedenums on}
type
TLogLevel = (LogDebug, LogInfo, LogWarn, LogError);
var
enabled: set of TLogLevel;
begin
with TLogLevel do
enabled := [LogInfo, LogWarn, LogError];
writeln('debug is ', if TLogLevel.LogDebug in enabled then 'on' else 'off');
writeln('info is ', if TLogLevel.LogInfo in enabled then 'on' else 'off');
writeln('warn is ', if TLogLevel.LogWarn in enabled then 'on' else 'off');
writeln('error is ', if TLogLevel.LogError in enabled then 'on' else 'off');
end.
Output:debug is off
info is on
warn is on
error is on
Implementation-wise this is tiny - the compiler already had the
for x in TEnumType do precedent for accepting a type node where an expression was expected, so WITH just needed the same trick plus a direct push of the enum's own symtable onto the scope stack. No
Self/refnode machinery gets involved for this path, because enum members are constants and don't need one.
The inline
if cond then 'on' else 'off' in each writeln is a separate unleashed-mode goodie -
statement expressions - that happens to fit nicely here.