fix(semantic): reject duplicate identifiers across const and var sections

A var declaration shadowing a same-block const (or a const declared
twice in the same block) was silently accepted instead of raising a
Duplicate identifier error.

Root cause: AnalyseBlock registers consts in the outer scope then pushes
a new inner scope before calling AnalyseVarDecls, so FTable.Define
(which only checks the current scope) could not detect the clash.

Fix: AnalyseVarDecls now scans the block's own ConstDecls list before
defining each var name.  AnalyseConstDecls now treats a duplicate as an
error when the collision originates from an earlier const in the same
block; cross-unit const shadowing (e.g. a unit redefining a system.pas
constant) continues to be silently accepted.

Also removes the redundant LineEnding const from blaise.testing.pas —
it is already exported by system.pas.

Fixes: https://github.com/graemeg/blaise/issues/30
This commit is contained in:
Graeme Geldenhuys 2026-05-17 00:02:18 +01:00
parent c17a8b027c
commit 08beb33689
4 changed files with 44 additions and 6 deletions

View file

@ -38,7 +38,7 @@ https://github.com/graemeg/opdebugger[OPDF] debug format support.
== 🚀 Project Status
* **Self-Hosting:** Yes. Blaise bootstraps and recompiles itself with byte-for-byte fixpoint. FPC is no longer required — the entire toolchain runs on Blaise alone.
* **Testing:** 1846 tests and growing (Test-Driven Development from day one). The test suite itself compiles under Blaise.
* **Testing:** 1934 tests and growing (Test-Driven Development from day one). The test suite itself compiles under Blaise.
* **Backends:** Currently utilising a QBE backend, with an LLVM backend in active development.

View file

@ -36,9 +36,6 @@ interface
uses
Classes, SysUtils;
const
LineEnding = #10;
type
{ TRunMethod type of a parameter-less method on any TObject descendant.
Used as the cast target for the published-method dispatch trampoline:

View file

@ -1881,6 +1881,7 @@ var
RefSym: TSymbol;
TD: TTypeDesc;
Resolved: string;
IsSameBlockDup: Boolean;
begin
for I := 0 to ABlock.ConstDecls.Count - 1 do
begin
@ -1927,7 +1928,21 @@ begin
Sym.ConstValue := CD.IntVal;
Sym.ConstString := CD.StrVal;
if not FTable.Define(Sym) then
Sym.Free; { duplicate const — silently ignore }
begin
Sym.Free;
{ Only error for same-block duplicates. Cross-unit const shadowing
(e.g. a unit redefining a system.pas constant) is silently accepted,
matching FPC behaviour and preserving the existing test coverage. }
IsSameBlockDup := False;
for J := 0 to I - 1 do
if SameText(TConstDecl(ABlock.ConstDecls.Items[J]).Name, CD.Name) then
begin
IsSameBlockDup := True;
Break;
end;
if IsSameBlockDup then
SemanticError(Format('Duplicate identifier ''%s''', [CD.Name]), CD.Line, CD.Col);
end;
end;
end;
@ -3370,7 +3385,7 @@ end;
procedure TSemanticAnalyser.AnalyseVarDecls(ABlock: TBlock);
var
I, J: Integer;
I, J, K: Integer;
Decl: TVarDecl;
Typ: TTypeDesc;
VarName: string;
@ -3407,6 +3422,16 @@ begin
for J := 0 to Decl.Names.Count - 1 do
begin
VarName := Decl.Names.Strings[J];
{ Consts from the same block live in the immediately enclosing scope
(AnalyseBlock pushes a new scope before calling AnalyseVarDecls, so
FTable.Define cannot see same-block consts). Scan the block's own
ConstDecls list so we catch only same-block clashes, not legitimate
shadowing of outer-scope or unit-imported consts. }
for K := 0 to ABlock.ConstDecls.Count - 1 do
if SameText(TConstDecl(ABlock.ConstDecls.Items[K]).Name, VarName) then
SemanticError(
Format('Duplicate identifier ''%s''', [VarName]),
Decl.Line, Decl.Col);
Sym := TSymbol.Create(VarName, skVariable, Typ);
Sym.IsWeak := Decl.IsWeak;
Sym.IsGlobal := Decl.IsGlobal;

View file

@ -29,6 +29,10 @@ type
procedure TestVarDecl_MultiName_BothRegistered;
procedure TestVarDecl_UnknownType_RaisesError;
procedure TestVarDecl_Duplicate_RaisesError;
{ Const then var with same name in same block is a duplicate }
procedure TestVarDecl_DuplicatesConst_RaisesError;
{ Const redeclared with same name in same block is a duplicate }
procedure TestConst_Duplicate_RaisesError;
{ Expression type inference }
procedure TestExpr_IntLiteral_TypeIsInteger;
@ -169,6 +173,18 @@ begin
'program P; var x: Integer; x: string; begin end.');
end;
procedure TSemanticTests.TestVarDecl_DuplicatesConst_RaisesError;
begin
AnalyseExpectError(
'program P; const b = 42; var b: Integer; begin end.');
end;
procedure TSemanticTests.TestConst_Duplicate_RaisesError;
begin
AnalyseExpectError(
'program P; const b = 42; b = 99; begin end.');
end;
{ ------------------------------------------------------------------ }
{ Expression type inference }
{ ------------------------------------------------------------------ }