Fenner
A linguagem

Avisos opt-in de código sem uso

Verificações de limpeza que vêm desligadas: campo sem uso, atribuída e não lida, código inalcançável, case incompleto e outras.

O que é e por que existe#

Código morto acumula: um campo private que ninguém lê, um parâmetro sem uso, um uses que sobrou. Esses diagnósticos acusam as sobras, mas todos vêm desligados, porque custam análise e alguns exigem julgamento. Quem não liga nenhum não paga nada: o padrão do compilador não muda.

Cada um tem a sua diretiva {$config Nome nível}, com ignore, hint, warning, error ou fatal (e os apelidos off, warn, on).

{$config …}O que acusa
UnusedPrivateFieldcampo private que ninguém usa
UnusedPrivateMethodmétodo private que ninguém chama
AssignedNotReadvariável local que recebe valor e nunca é lida
UnreachableCodecódigo depois de Exit, raise, Break, Continue ou goto
CaseNotExhaustivecase sobre enum, sem else, que não cobre todos os membros
UnusedParamparâmetro nunca usado (se a assinatura é imposta, chame-o _B)
UnusedUsesmódulo no uses de que nenhum nome é citado (o --diag=json leva a edição que o remove)
UnusedGlobalvariável global nunca usada
ForVarModifiedvariável do for alterada dentro do laço
FloatEqualsigualdade exata entre reais (compare com tolerância)
TupleNamesnomes de item de tupla que não casam
rc-optin.fxp
program OptIn;
// Demo: avisos "opt-in" de código sem uso. Todos vêm DESLIGADOS; ligue com {$config Nome nível}.
// Úteis para limpar o código: cada um acusa um tipo de sobra ou descuido.

{$config UnusedPrivateField hint}     // campo private que ninguém usa
{$config UnusedPrivateMethod hint}    // método private que ninguém chama
{$config AssignedNotRead hint}        // variável que recebe valor mas nunca é lida
{$config UnreachableCode hint}        // código depois de Exit/raise/Break/Continue
{$config CaseNotExhaustive hint}      // case de enum sem else que não cobre todos os membros

type
  TColor = (cRed, cGreen, cBlue);

  TBox = class
  private
    FUsed: Int32;
    FDead: Int32;                     // Dica: campo nunca usado
    procedure Helper;                 // Dica: método nunca chamado
  public
    procedure Run;
  end class;

procedure TBox.Helper;
begin
end procedure;

procedure TBox.Run;
begin
  FUsed := 1;
  WriteLn('FUsed = ', FUsed);
end procedure;

function ColorName(AColor: TColor): UString;
begin
  Result := '?';
  case AColor of                      // Dica: faltam cBlue
    cRed: Result := 'vermelho';
    cGreen: Result := 'verde';
  end case;
end function;

procedure Stuff;
var
  LTmp: Int32;
begin
  LTmp := 5;                          // Dica: recebe valor mas nunca é lida
  Exit;
  WriteLn('nunca roda');              // Dica: código inalcançável
end procedure;

begin
  TBox.Create.Run;
  WriteLn(ColorName(cRed), ', ', ColorName(cBlue));
  Stuff;
end.
Saída
FUsed = 1
vermelho, ?

Demo 51. As cinco primeiras ligadas como Dica; o programa roda normalmente e as dicas saem na compilação.

Dicas da compilação do demo:

texto
OptIn.fxp(36,3): Dica: o case sobre "TColor" nao tem else e nao cobre todos os membros: faltam cBlue
OptIn.fxp(48,10): Dica: codigo inalcancavel: o comando anterior (Exit, raise, Break, Continue ou goto) sempre desvia o fluxo
OptIn.fxp(17,5): Dica: o campo private "TBox.FDead" nunca e usado
OptIn.fxp(18,5): Dica: o metodo private "TBox.Helper" nunca e chamado
OptIn.fxp(44,3): Dica: a variavel "LTmp" recebe valor mas nunca e lida

No projeto inteiro: Lint#

Em vez de uma diretiva por arquivo, o fc.config (ou o <projeto>.config) liga tudo de uma vez, em todos os módulos do projeto (a RTL nunca é cobrada):

config
Config  All  Lint  on                  # o conjunto sensato, como dica
Config  All  Lint  strict              # todos, como aviso
Config  All  UnusedParam  hint         # uma chave individual

Lint on liga campo/método private, atribuída-não-lida, inalcançável, UnusedUses, ForVarModified e UnusedGlobal; warning é o mesmo como aviso; strict liga todos (inclui os de julgamento: CaseNotExhaustive, UnusedParam, FloatEquals). Precedência, da mais fraca para a mais forte: Lint do .config, chave individual do .config, diretiva no fonte.

O que fica de fora, de propósito#

Para evitar falso positivo, não são cobrados: membros de classe genérica, da RTL, que implementam interface ou têm asm/llvm, virtual/override/abstract, published, construtores, destrutores, operadores e external. Campo só escrito conta como usado. Local de tipo classe, interface ou record só avisa se toda atribuição for trivial (nil, literal, cópia): X := TFoo.Create fica fora, porque guardar a referência é o efeito (ARC).

Armadilhas#

  • A diretiva vale no arquivo onde está, do ponto em que aparece para a frente. Para cobrir um projeto, use a chave do .config.
  • Com error ou fatal ligados em código de terceiros a compilação cai, por design.
  • x.f := 1 e x[i] := 1 contam como leitura de x. Um método private só chamado por outro igualmente morto conta como usado.
  • CaseNotExhaustive sozinho acha dezenas de casos no próprio compilador: por isso o padrão é ignore.

Veja também#