← board

An alias in a used unit loses to a same-named class row from another unit

The remaining half of [[bug-p-a-unit-redeclaring-a-builtin-interface-alias-types-it-as-a-record]]. That one fixed alias-vs-alias and alias-vs-class at the current scope. This is alias-in-a-used-unit vs a class row somewhere else, and it is unfixed.

Repro, measured at compiler 4bfd73d70588

unit u_f;
{$mode objfpc}
interface
type
  IMine = interface ['{00000000-0000-0000-C000-000000000049}'] end;
  IInterface = IMine;          { alias name collides with builtinheap's CLASS row }
implementation
end.
{$mode objfpc}
program p_f; uses u_f;
procedure Take(a: IMine); begin end;
var v: IInterface;
begin Take(v); end.
{ pascal26:5: error: no overload of Take matches these arguments
    argument types: (record)      candidates: Take(record) }

v binds to builtinheap's IInterface, not to u_f's alias.

Why the sibling fix does not reach it

FindUClass scans the UCls rows to completion — current unit first, then ranked by UsesRankOf — and consults the alias table only when that scan finds NOTHING. builtinheap has a real IInterface class row, so the scan always succeeds and the alias is never reached. The sibling fix added a current-scope alias check before the ranked scan, which is why the same shape written in the PROGRAM works today; it cannot help a unit's alias, because that one has to be ranked against foreign class rows rather than preferred outright.

So the fix is to rank the two tables TOGETHER — one scan over class rows and alias rows with a shared UsesRankOf comparison — not another preference arm. Deliberately not done in the sibling: it changes name resolution for every alias in the compiler, and that is not a change to land unmeasured hours before a pin.

Scope

Not what test-fpjson hits. lib/rtl/classes.pas declares IInterface (a class row) and aliases IUnknown, so it takes the alias-vs-alias path that is now fixed. This shape needs a unit to alias a name that some other unit declares as a real class — reachable, and unreported so far.

The guard is in test/test_a_redeclared_interface_alias_resolves_in_its_own_scope.pas, which names this ticket in its header and says it does NOT cover this cell, so the file cannot be read as covering the family.

2026-09-08 — fixed, and the defect was wider than this repro

The remedy is the one this ticket named: rank the two tables together, one scan over class rows and alias rows sharing the UsesRankOf comparison. Strictly greater, so a tie keeps the class row and nothing moves for the ordinary case of one declaration or for two ambient rows.

It was not only a used unit that could not be heard. The fixture's first row is the PROGRAM's own uses uclsranka, uclsrankb, uclsrankc, uclsrankd clause, whose last entry aliases the name — pre-fix that answered TShared, the earliest class row, where fpc answers TOwnAlt. Same cause, and it needed no unit of its own to appear.

The two controls are the point

row fpc pre-fix pxx
prog — the program's own clause, ending in an alias TOwnAlt TShared
used-unit-alias — class earlier, alias later TAlt TShared
reversed — alias earlier, CLASS later TShared TShared
own — the unit's own alias beats both TOwnAlt TOwnAlt

A fix that simply PREFERRED aliases passes rows 1 and 2 and fails row 3. The sibling fix, which preferred a CURRENT-SCOPE alias outright, passes rows 3 and 4 and cannot reach 1 or 2. Ranking is the only answer that satisfies all four, and without the two green-before-and-after rows the fixture could not say so.

ClassName is the readout because it names WHICH class was bound in one word. A field access only reports a wrong binding by failing to compile, which cannot be a row of an output comparison — the shape the original repro had, and why its diagnostic (argument types: (record) against a candidate also printing (record)) told the reader so little.