Skip to content

Commit 39a53bd

Browse files
Patrick Quistclaude
andcommitted
Give ntConstant an end position
A constant declaration produced a node with no end position at all, so a consumer working in line ranges could not tell how far the declaration reached. That silently truncates any constant whose value spans lines: const Banner = 'first part ' + 'second part'; reported only the first line, and a tool slicing that range dropped the continuation. ntConstant was built with FStack.Push, so it was a plain TSyntaxNode with nowhere to record an end. Two changes are needed, because the node the caller finally sees is not the node that was parsed: ConstantDeclaration now pushes a compound node and records its end, the same way TypeDeclaration already does. This gives the intermediate ConstList an accurate extent. ConstSection rebuilds each constant from that ConstList, so it also has to push a compound node and inherit the end from it. The start still comes from the name, which is what a caller looking for the constant expects; only the end is new. TCompoundSyntaxNode.AssignEndPositionFrom is the counterpart to the existing AssignPositionFrom, for exactly this rebuild case. Single-line constants keep ending on their own line - the new test asserts both directions, since an end that ran on to the next declaration would be no more useful than one that stopped short. Verified against the existing suite: 42 passing, with the one pre-existing Serialization.BinaryRoundTrip failure unchanged (line_seq holds a pointer value that does not survive a round trip, unrelated to this change). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent e89ce1f commit 39a53bd

3 files changed

Lines changed: 49 additions & 2 deletions

File tree

‎Source/DelphiAST.Classes.pas‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@ TCompoundSyntaxNode = class(TSyntaxNode)
104104
FEndLine: Integer;
105105
public
106106
function Clone: TSyntaxNode; override;
107+
//counterpart to AssignPositionFrom, for a node rebuilt from a parsed one
108+
procedure AssignEndPositionFrom(const Node: TCompoundSyntaxNode);
107109

108110
property EndCol: Integer read FEndCol write FEndCol;
109111
property EndLine: Integer read FEndLine write FEndLine;
@@ -629,6 +631,12 @@ procedure TSyntaxNode.AssignPositionFrom(const Node: TSyntaxNode);
629631

630632
{ TCompoundSyntaxNode }
631633

634+
procedure TCompoundSyntaxNode.AssignEndPositionFrom(const Node: TCompoundSyntaxNode);
635+
begin
636+
FEndCol := Node.EndCol;
637+
FEndLine := Node.EndLine;
638+
end;
639+
632640
function TCompoundSyntaxNode.Clone: TSyntaxNode;
633641
begin
634642
Result := inherited;

‎Source/DelphiAST.pas‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1195,9 +1195,11 @@ procedure TPasSyntaxTreeBuilder.CompoundStatement;
11951195

11961196
procedure TPasSyntaxTreeBuilder.ConstantDeclaration;
11971197
begin
1198-
FStack.Push(ntConstant);
1198+
//compound: a constant's value can span lines, so record where it ends. as TypeDeclaration.
1199+
FStack.PushCompoundSyntaxNode(ntConstant);
11991200
try
12001201
inherited;
1202+
SetCurrentCompoundNodesEndPosition;
12011203
finally
12021204
FStack.Pop;
12031205
end;
@@ -1329,9 +1331,12 @@ procedure TPasSyntaxTreeBuilder.ConstSection;
13291331
if Constant.Typ <> ntName then
13301332
Continue;
13311333

1332-
Temp := FStack.Push(ConstList.Typ);
1334+
//compound: start from the name, end from the ConstList that measured the value.
1335+
Temp := FStack.PushCompoundSyntaxNode(ConstList.Typ);
13331336
try
13341337
Temp.AssignPositionFrom(Constant);
1338+
if ConstList is TCompoundSyntaxNode then
1339+
TCompoundSyntaxNode(Temp).AssignEndPositionFrom(TCompoundSyntaxNode(ConstList));
13351340

13361341
FStack.AddChild(Constant.Clone);
13371342
if Assigned(TypeInfo) then

‎Test/UnitTests/DelphiAST.Tests.pas‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,39 @@ procedure TestSourcePositions;
177177
end;
178178
end;
179179

180+
procedure TestConstantEndPosition;
181+
var
182+
Root, ConstsNode, Node, Spanning, Single: TSyntaxNode;
183+
begin
184+
Root := ParseSource('unit Consts;' + sLineBreak + 'interface' + sLineBreak + 'const' +
185+
sLineBreak + ' Spanning = ''first part '' +' + sLineBreak + ' ''second part'';' +
186+
sLineBreak + ' Single = 42;' + sLineBreak + 'implementation' + sLineBreak + 'end.');
187+
try
188+
ConstsNode := FindDescendant(Root, ntConstants);
189+
AssertNotNil(ConstsNode, 'No const section was produced');
190+
Spanning := nil;
191+
Single := nil;
192+
for Node in ConstsNode.ChildNodes do
193+
if Node.Typ = ntConstant then
194+
if Node.Line = 4 then
195+
Spanning := Node
196+
else if Node.Line = 6 then
197+
Single := Node;
198+
199+
AssertNotNil(Spanning, 'No constant starting on line 4');
200+
AssertTrue(Spanning is TCompoundSyntaxNode,
201+
'Constant node must be compound so it can carry an end position');
202+
AssertEquals(5, TCompoundSyntaxNode(Spanning).EndLine,
203+
'A multi-line constant must reach the last line of its value');
204+
205+
AssertNotNil(Single, 'No constant starting on line 6');
206+
AssertEquals(6, TCompoundSyntaxNode(Single).EndLine,
207+
'A single-line constant must end on its own line, not run on to what follows');
208+
finally
209+
Root.Free;
210+
end;
211+
end;
212+
180213
procedure TestInvalidSyntax;
181214
var
182215
Root: TSyntaxNode;
@@ -229,6 +262,7 @@ procedure RunAllTests;
229262
RunTest('AST.GenericRecordAndProperty', TestGenericRecordAndProperty);
230263
RunTest('Writer.LiteralsUnicodeAndXmlEscaping', TestLiteralsAndUnicode);
231264
RunTest('AST.SourcePositions', TestSourcePositions);
265+
RunTest('AST.ConstantEndPosition', TestConstantEndPosition);
232266
RunTest('Parser.InvalidSyntax', TestInvalidSyntax);
233267
{$IFNDEF FPC}
234268
RunTest('Serialization.BinaryRoundTrip', TestBinarySerializationRoundTrip);

0 commit comments

Comments
 (0)