fix(parser): enforce mandatory parentheses on statement-position calls (#148)

The "mandatory parentheses on zero-argument calls" rule (language-
rationale.adoc) was enforced in expression position but NOT in statement
position: a bare `Foo;`, `Obj.Method;`, or `Obj.Free;` used as a statement
compiled silently, building a paren-less TProcCall / TMethodCallStmt. Issue
#148 reported `tester.print;` (a unit's global object method call) being
accepted without its mandatory ().

The statement parser now raises the same "requires () for a call" diagnostic
the inherited-call and expression-position paths already use, at the two
fall-through sites:

  * bare unqualified call `Foo` with no '(' (the final ProcCall else-branch);
  * bare `Obj.Method` with no '(' and no further '.' chain.

Field reads, field assignments, indexed writes, and '.'-chains are unaffected
(only a terminating bare reference is rejected). Expression-position calls
were already enforced.

Enforcing the rule required the compiler, RTL, and stdlib to comply first
(self-hosting): swept bare calls in uSemantic (Flush/RepairGenericInstances),
runtime.arc/runtime.exc (_libc_abort), blaise.codegen.native.backend
(FAsm.AppendLine), and a json.writer doc example. The test suite embedded
many bare calls in inline program strings — all updated to carry (); adding
() never changes behaviour since these were always calls.

cp.test.parser.pas gains two parse-error tests (proc and method bare calls);
the old TestProcCall_NoParens, which asserted the bug, is inverted. grammar.ebnf
SubscriptMethodCall made parens mandatory and an example corrected; rationale
notes statement-position enforcement.
This commit is contained in:
Graeme Geldenhuys 2026-06-28 10:15:39 +01:00
parent a205c22136
commit 50b8d75e6d
40 changed files with 169 additions and 125 deletions

View file

@ -160,7 +160,7 @@ end;
procedure TNativeBackend.EmitBlank; procedure TNativeBackend.EmitBlank;
begin begin
FAsm.AppendLine; FAsm.AppendLine();
end; end;
{ The record-return ABI classifier and its leaf predicates now live as shared { The record-return ABI classifier and its leaf predicates now live as shared

View file

@ -119,7 +119,7 @@ begin
_libc_write(STDERR_FD, Msg, Int64(Len)); _libc_write(STDERR_FD, Msg, Int64(Len));
NL := 10; NL := 10;
_libc_write(STDERR_FD, @NL, 1); _libc_write(STDERR_FD, @NL, 1);
_libc_abort; _libc_abort();
end; end;
procedure _AbstractMethodError; procedure _AbstractMethodError;

View file

@ -112,7 +112,7 @@ var
ExcSlot: PPointer; ExcSlot: PPointer;
begin begin
if g_exc_top = nil then if g_exc_top = nil then
_libc_abort; _libc_abort();
g_current_exception := Obj; g_current_exception := Obj;
ExcSlot := g_exc_top + OFS_EXCEPTION; ExcSlot := g_exc_top + OFS_EXCEPTION;
ExcSlot^ := Obj; ExcSlot^ := Obj;
@ -245,7 +245,7 @@ begin
_libc_write(2, Msg, Int64(Len)); _libc_write(2, Msg, Int64(Len));
NL := 10; NL := 10;
_libc_write(2, @NL, 1); _libc_write(2, @NL, 1);
_libc_abort; _libc_abort();
end; end;
procedure _Raise_InvalidCast; procedure _Raise_InvalidCast;

View file

@ -3421,6 +3421,20 @@ begin
if not Check(tkRParen) then if not Check(tkRParen) then
ParseMethodCallArgList(MCall); ParseMethodCallArgList(MCall);
Expect(tkRParen); Expect(tkRParen);
end
else if not Check(tkDot) then
begin
{ Bare 'Obj.Method' with no '(' and no further '.' chain — a
parameterless method call written without its mandatory parentheses.
(A following '.' is a post-call/field chain handled just below; a
terminating bare reference is the error case.) Every call carries ()
even with no arguments — see language-rationale.adoc, "Mandatory
parentheses on zero-argument calls". }
MCall.Free();
MCall := nil;
raise EParseError.Create(Format(
'Bare reference to ''%s.%s'' requires () for a call at line %d col %d in %s',
[Name, SecondIdent, Line, Col, FLexer.Filename]));
end; end;
{ Post-method chain: Name.Method(args).Field := value or .Method2(args) } { Post-method chain: Name.Method(args).Field := value or .Method2(args) }
if Check(tkDot) then if Check(tkDot) then
@ -3595,11 +3609,13 @@ begin
FCallNode.Free(); FCallNode.Free();
Exit(Call); Exit(Call);
end; end;
Call := TProcCall.Create(); { Bare identifier in statement position with no '(' — a parameterless
Call.Line := Line; procedure/function call written without its mandatory parentheses.
Call.Col := Col; Every call carries () even with no arguments (see language-rationale.adoc,
Call.Name := Name; "Mandatory parentheses on zero-argument calls"). }
Result := Call; raise EParseError.Create(Format(
'Bare reference to ''%s'' requires () for a call at line %d col %d in %s',
[Name, Line, Col, FLexer.Filename]));
end; end;
end; end;

View file

@ -1168,7 +1168,7 @@ var
I: Integer; I: Integer;
begin begin
FProg := AProg; FProg := AProg;
FlushPendingGenericInstances; FlushPendingGenericInstances();
FCurrentUnitName := AProg.Name; FCurrentUnitName := AProg.Name;
BuildUsesChain(AProg.UsedUnits); BuildUsesChain(AProg.UsedUnits);
FTable.UsesChainProvider := Self; FTable.UsesChainProvider := Self;
@ -1202,7 +1202,7 @@ var
begin begin
FCurrentUnitName := AUnit.Name; FCurrentUnitName := AUnit.Name;
FCurrentUnit := AUnit; FCurrentUnit := AUnit;
FlushPendingGenericInstances; FlushPendingGenericInstances();
FTable.PushScope(); FTable.PushScope();
try try
{ The unit's own name and the names of directly used units are { The unit's own name and the names of directly used units are
@ -1223,7 +1223,7 @@ begin
*before* any FindTypeOrInstantiate call can clone an instance, or the *before* any FindTypeOrInstantiate call can clone an instance, or the
instance is born with nil bodies and codegen emits no function for it. } instance is born with nil bodies and codegen emits no function for it. }
LinkGenericClassMethodImpls(AUnit.ImplBlock); LinkGenericClassMethodImpls(AUnit.ImplBlock);
RepairEarlyGenericInstances; RepairEarlyGenericInstances();
{ Register interface-section global variables — visible to impl bodies. } { Register interface-section global variables — visible to impl bodies. }
for I := 0 to AUnit.IntfBlock.Decls.Count - 1 do for I := 0 to AUnit.IntfBlock.Decls.Count - 1 do
@ -1534,7 +1534,7 @@ var
begin begin
FCurrentUnitName := AUnit.Name; FCurrentUnitName := AUnit.Name;
FCurrentUnit := AUnit; FCurrentUnit := AUnit;
FlushPendingGenericInstances; FlushPendingGenericInstances();
BuildUsesChain(AUnit.UsedUnits); BuildUsesChain(AUnit.UsedUnits);
FTable.UsesChainProvider := Self; FTable.UsesChainProvider := Self;
{ Auto-tag every global Define within this unit's analysis with the { Auto-tag every global Define within this unit's analysis with the
@ -1557,7 +1557,7 @@ begin
the cloned instance method is born without a body and codegen emits the cloned instance method is born without a body and codegen emits
no function — leaving call sites referencing an undefined symbol. } no function — leaving call sites referencing an undefined symbol. }
LinkGenericClassMethodImpls(AUnit.ImplBlock); LinkGenericClassMethodImpls(AUnit.ImplBlock);
RepairEarlyGenericInstances; RepairEarlyGenericInstances();
{ Register interface-section global variables. Marked IsGlobal so { Register interface-section global variables. Marked IsGlobal so
codegen emits them as data-segment slots rather than stack allocs; codegen emits them as data-segment slots rather than stack allocs;
@ -3971,7 +3971,7 @@ begin
declarations, transferring the body so AnalyseMethodBodies can process it. } declarations, transferring the body so AnalyseMethodBodies can process it. }
LinkClassMethodImpls(ABlock); LinkClassMethodImpls(ABlock);
LinkGenericClassMethodImpls(ABlock); LinkGenericClassMethodImpls(ABlock);
RepairEarlyGenericInstances; RepairEarlyGenericInstances();
{ Register standalone proc/func signatures before class method bodies so that { Register standalone proc/func signatures before class method bodies so that
methods can call free functions declared in the same block. } methods can call free functions declared in the same block. }
AnalyseStandaloneDecls(ABlock); AnalyseStandaloneDecls(ABlock);

View file

@ -687,7 +687,7 @@ begin
f := TFoo.Create() f := TFoo.Create()
end; end;
begin begin
DoIt DoIt()
end. end.
'''); ''');
FnPos := Pos('function $DoIt', IR); FnPos := Pos('function $DoIt', IR);
@ -725,7 +725,7 @@ begin
f := TFoo.Create() f := TFoo.Create()
end; end;
begin begin
DoIt DoIt()
end. end.
'''); ''');
FnPos := Pos('function $DoIt', IR); FnPos := Pos('function $DoIt', IR);
@ -759,7 +759,7 @@ begin
f := TFoo.Create() f := TFoo.Create()
end; end;
begin begin
DoIt DoIt()
end. end.
'''); ''');
FnPos := Pos('function $DoIt', IR); FnPos := Pos('function $DoIt', IR);
@ -800,7 +800,7 @@ begin
F := TFoo(P) F := TFoo(P)
end; end;
begin begin
DoIt DoIt()
end. end.
'''); ''');
FnPos := Pos('function $DoIt', IR); FnPos := Pos('function $DoIt', IR);
@ -832,7 +832,7 @@ const
r := Make() r := Make()
end; end;
begin begin
Run Run()
end. end.
'''; ''';
@ -848,7 +848,7 @@ const
b := a b := a
end; end;
begin begin
Run Run()
end. end.
'''; ''';
@ -868,7 +868,7 @@ const
r := Make() r := Make()
end; end;
begin begin
Run Run()
end. end.
'''; ''';
@ -883,7 +883,7 @@ const
b := a b := a
end; end;
begin begin
Run Run()
end. end.
'''; ''';

View file

@ -964,7 +964,7 @@ begin
'begin' + LineEnding + 'begin' + LineEnding +
' for I := 1 to 5 do' + LineEnding + ' for I := 1 to 5 do' + LineEnding +
' Write(I);' + LineEnding + ' Write(I);' + LineEnding +
' WriteLn' + LineEnding + ' WriteLn()' + LineEnding +
'end.', 'end.',
Out_, EC) then Out_, EC) then
begin begin
@ -1037,7 +1037,7 @@ begin
' Write(N);' + LineEnding + ' Write(N);' + LineEnding +
' N := N + 1' + LineEnding + ' N := N + 1' + LineEnding +
' end;' + LineEnding + ' end;' + LineEnding +
' WriteLn' + LineEnding + ' WriteLn()' + LineEnding +
'end.', 'end.',
Out_, EC) then Out_, EC) then
begin begin
@ -1163,7 +1163,7 @@ begin
'begin' + LineEnding + 'begin' + LineEnding +
' G := TGreeter.Create;' + LineEnding + ' G := TGreeter.Create;' + LineEnding +
' WriteLn(G.Greet());' + LineEnding + ' WriteLn(G.Greet());' + LineEnding +
' G.Free' + LineEnding + ' G.Free()' + LineEnding +
'end.', 'end.',
Out_, EC) then Out_, EC) then
begin begin

View file

@ -102,7 +102,7 @@ begin
Proc.WaitOnExit(); Proc.WaitOnExit();
Code := Proc.ExitCode Code := Proc.ExitCode
finally finally
Proc.Free Proc.Free()
end; end;
if Code <> 0 then if Code <> 0 then

View file

@ -480,7 +480,7 @@ begin
x := Threshold x := Threshold
end; end;
begin begin
DoWork DoWork()
end. end.
''' '''
); );

View file

@ -517,7 +517,7 @@ const
Freed := 0; Freed := 0;
L := TObjectList.Create(True); L := TObjectList.Create(True);
L.Add(TThing.Create()); L.Add(TThing.Create());
Borrow; Borrow();
WriteLn(Freed); WriteLn(Freed);
L.Free(); L.Free();
WriteLn(Freed) WriteLn(Freed)

View file

@ -389,8 +389,8 @@ const
OL := TObjectList.Create(False); OL := TObjectList.Create(False);
OL.Add(SL); OL.Add(SL);
WriteLn(TStringList(OL[0])[1]); // chained: cast(OL default) then default WriteLn(TStringList(OL[0])[1]); // chained: cast(OL default) then default
SL.Free; SL.Free();
OL.Free OL.Free()
end. end.
'''; ''';
var Output: string; RCode: Integer; var Output: string; RCode: Integer;

View file

@ -192,11 +192,11 @@ const
end; end;
begin begin
Counter := 0; Counter := 0;
Inner; Inner();
WriteLn(Counter); WriteLn(Counter);
end; end;
begin begin
Outer; Outer();
end. end.
'''; ''';
@ -311,7 +311,7 @@ const
end; end;
Write(r); Write(' '); Write(r); Write(' ');
end; end;
WriteLn WriteLn()
end. end.
'''; ''';
@ -326,7 +326,7 @@ const
5, 7..9: Write('b'); 5, 7..9: Write('b');
else Write('.'); else Write('.');
end; end;
WriteLn WriteLn()
end. end.
'''; ''';

View file

@ -85,7 +85,7 @@ const
program Prg; program Prg;
var a: array of string; i: Integer; var a: array of string; i: Integer;
begin SetLength(a, 3); a[0] := 'x'; a[1] := 'y'; a[2] := 'z'; begin SetLength(a, 3); a[0] := 'x'; a[1] := 'y'; a[2] := 'z';
for i := 0 to 2 do Write(a[i]); WriteLn end. for i := 0 to 2 do Write(a[i]); WriteLn() end.
'''; ''';
SrcRefSem = ''' SrcRefSem = '''
@ -125,7 +125,7 @@ const
SetLength(m, 2); SetLength(m[0], 2); SetLength(m[1], 2); SetLength(m, 2); SetLength(m[0], 2); SetLength(m[1], 2);
m[0][0] := 1; m[0][1] := 2; m[1][0] := 3; m[1][1] := 4; m[0][0] := 1; m[0][1] := 2; m[1][0] := 3; m[1][1] := 4;
for i := 0 to 1 do for j := 0 to 1 do Write(m[i][j]); for i := 0 to 1 do for j := 0 to 1 do Write(m[i][j]);
WriteLn WriteLn()
end. end.
'''; ''';
@ -137,7 +137,7 @@ const
m[0][0] := 'a'; m[0][1] := 'b'; m[1][0] := 'c'; m[1][1] := 'd'; m[0][0] := 'a'; m[0][1] := 'b'; m[1][0] := 'c'; m[1][1] := 'd';
m[0][1] := 'B'; m[0][1] := 'B';
for i := 0 to 1 do for j := 0 to 1 do Write(m[i][j]); for i := 0 to 1 do for j := 0 to 1 do Write(m[i][j]);
WriteLn WriteLn()
end. end.
'''; ''';
@ -154,7 +154,7 @@ const
c[i][j][k] := i*4 + j*2 + k; c[i][j][k] := i*4 + j*2 + k;
for i := 0 to 1 do for j := 0 to 1 do for k := 0 to 1 do for i := 0 to 1 do for j := 0 to 1 do for k := 0 to 1 do
Write(c[i][j][k]); Write(c[i][j][k]);
WriteLn WriteLn()
end. end.
'''; ''';
@ -212,7 +212,7 @@ const
SetLength(G, Length(H)); SetLength(G, Length(H));
for I := 0 to High(G) do G[I] := 9; for I := 0 to High(G) do G[I] := 9;
for I := 0 to High(G) do Write(G[I]); for I := 0 to High(G) do Write(G[I]);
WriteLn WriteLn()
end. end.
'''; ''';
@ -226,7 +226,7 @@ const
SetLength(A, Length(B)); SetLength(A, Length(B));
for I := 0 to High(A) do A[I] := 8; for I := 0 to High(A) do A[I] := 8;
for I := 0 to High(A) do Write(A[I]); for I := 0 to High(A) do Write(A[I]);
WriteLn WriteLn()
end. end.
'''; ''';
@ -241,7 +241,7 @@ const
SetLength(A, Pick()); SetLength(A, Pick());
for I := 0 to High(A) do A[I] := 6; for I := 0 to High(A) do A[I] := 6;
for I := 0 to High(A) do Write(A[I]); for I := 0 to High(A) do Write(A[I]);
WriteLn WriteLn()
end. end.
'''; ''';

View file

@ -372,9 +372,9 @@ const
begin WriteLn(g.Greet(), ' / ', g.Who()) end; begin WriteLn(g.Greet(), ' / ', g.Who()) end;
var per: TPerson; lou: TLoud; vl: TVeryLoud; var per: TPerson; lou: TLoud; vl: TVeryLoud;
begin begin
per := TPerson.Create; Use(per); per.Free; per := TPerson.Create; Use(per); per.Free();
lou := TLoud.Create; Use(lou); lou.Free; lou := TLoud.Create; Use(lou); lou.Free();
vl := TVeryLoud.Create; Use(vl); vl.Free vl := TVeryLoud.Create; Use(vl); vl.Free()
end. end.
'''; ''';
begin begin
@ -406,7 +406,7 @@ const
i := d; // assignment to interface i := d; // assignment to interface
i.Hello(); i.Hello();
Use(d); // parameter passing Use(d); // parameter passing
d.Free d.Free()
end. end.
'''; ''';
begin begin

View file

@ -123,7 +123,7 @@ begin
' for e in s do n := n + 1;' + LE + ' for e in s do n := n + 1;' + LE +
' WriteLn(n);' + LE + { 4 } ' WriteLn(n);' + LE + { 4 }
' for e in s do Write(Ord(e), '' '');' + LE + ' for e in s do Write(Ord(e), '' '');' + LE +
' WriteLn' + LE + ' WriteLn()' + LE +
'end.'; 'end.';
AssertRunsOnAll(Src, '4' + LE + '5 40 70 79 ' + LE, 0); AssertRunsOnAll(Src, '4' + LE + '5 40 70 79 ' + LE, 0);
end; end;

View file

@ -126,7 +126,7 @@ const
end end
end; end;
begin begin
DoIt; DoIt();
WriteLn('done') WriteLn('done')
end. end.
'''; ''';

View file

@ -347,7 +347,7 @@ const
Obj := TFoo.Create(); Obj := TFoo.Create();
Obj.FVal := 55; Obj.FVal := 55;
M := @Obj.Print; M := @Obj.Print;
M; M();
Obj.Free() Obj.Free()
end. end.
'''; ''';
@ -991,11 +991,11 @@ const
begin begin
x := 5; x := 5;
WriteLn(IntToStr(x)); WriteLn(IntToStr(x));
Inner; Inner();
WriteLn(IntToStr(x)) WriteLn(IntToStr(x))
end; end;
begin begin
Outer Outer()
end. end.
'''; ''';
var Output: string; RCode: Integer; var Output: string; RCode: Integer;
@ -1022,7 +1022,7 @@ const
R.B := Sum * 2 R.B := Sum * 2
end; end;
begin begin
Inner Inner()
end; end;
var Rec: TRec; var Rec: TRec;
begin begin
@ -1050,11 +1050,11 @@ const
end; end;
begin begin
R.A := 1; R.B := 2; R.A := 1; R.B := 2;
Inner; Inner();
WriteLn(IntToStr(R.A), ' ', IntToStr(R.B)) WriteLn(IntToStr(R.A), ' ', IntToStr(R.B))
end; end;
begin begin
Outer Outer()
end. end.
'''; ''';
begin begin
@ -1073,7 +1073,7 @@ const
A[0] := 10; A[1] := 20; A[2] := A[0] + A[1] A[0] := 10; A[1] := 20; A[2] := A[0] + A[1]
end; end;
begin begin
Inner Inner()
end; end;
var Ar: TArr; var Ar: TArr;
begin begin

View file

@ -1226,7 +1226,7 @@ const
WriteLn(D) WriteLn(D)
end; end;
begin begin
ShowDouble ShowDouble()
end. end.
'''; ''';
@ -1487,7 +1487,7 @@ const
WriteLn(B.Data[4]); WriteLn(B.Data[4]);
WriteLn(B.Data[I + 2]); WriteLn(B.Data[I + 2]);
WriteLn(B.At(1)); WriteLn(B.At(1));
B.Free; B.Free();
end. end.
'''; ''';
@ -2843,13 +2843,13 @@ const
end; end;
procedure Tmi.use; procedure Tmi.use;
begin begin
im.print; im.print();
end; end;
var var
im: Tmi; im: Tmi;
begin begin
im := Tmi.Create(Toutput.Create()); im := Tmi.Create(Toutput.Create());
im.use; im.use();
end. end.
'''; ''';
@ -4966,7 +4966,7 @@ begin
begin begin
B := TBox.Create(99); B := TBox.Create(99);
WriteLn(B.Val); WriteLn(B.Val);
B.Free B.Free()
end. end.
''', ''',
'99' + LE, 0); '99' + LE, 0);
@ -4992,7 +4992,7 @@ begin
A := TArr.Create; A := TArr.Create;
WriteLn(A.Items[0]); WriteLn(A.Items[0]);
WriteLn(A.Items[1]); WriteLn(A.Items[1]);
A.Free A.Free()
end. end.
''', ''',
'10' + LE + '30' + LE, 0); '10' + LE + '30' + LE, 0);
@ -5018,7 +5018,7 @@ begin
B := TBox.Create; B := TBox.Create;
B.Val := 42; B.Val := 42;
WriteLn(B.Val); WriteLn(B.Val);
B.Free B.Free()
end. end.
''', ''',
'42' + LE, 0); '42' + LE, 0);
@ -5046,7 +5046,7 @@ begin
A.Items[1] := 30; A.Items[1] := 30;
WriteLn(A.Items[0]); WriteLn(A.Items[0]);
WriteLn(A.Items[1]); WriteLn(A.Items[1]);
A.Free A.Free()
end. end.
''', ''',
'10' + LE + '30' + LE, 0); '10' + LE + '30' + LE, 0);
@ -5062,7 +5062,7 @@ begin
begin begin
F := TFoo.Create; F := TFoo.Create;
WriteLn(F.ClassName); WriteLn(F.ClassName);
F.Free F.Free()
end. end.
''', ''',
'TFoo' + LE, 0); 'TFoo' + LE, 0);
@ -5084,7 +5084,7 @@ begin
begin begin
Obj := TCalc.Create; Obj := TCalc.Create;
WriteLn(Obj.Sum6(1, 2, 3, 4, 5, 6)); WriteLn(Obj.Sum6(1, 2, 3, 4, 5, 6));
Obj.Free Obj.Free()
end. end.
''', ''',
'21' + LE, 0); '21' + LE, 0);
@ -5109,8 +5109,8 @@ begin
O := TOuter.Create; O := TOuter.Create;
O.FInner := I; O.FInner := I;
WriteLn(O.GetVal()); WriteLn(O.GetVal());
O.Free; O.Free();
I.Free I.Free()
end. end.
''', ''',
'42' + LE, 0); '42' + LE, 0);
@ -5137,8 +5137,8 @@ begin
begin begin
O := TMyObj.Create; O := TMyObj.Create;
O.FVal := 99; O.FVal := 99;
O.Show; O.Show();
O.Free O.Free()
end. end.
''', ''',
'99' + LE, 0); '99' + LE, 0);
@ -5176,7 +5176,7 @@ begin
begin begin
B := TChild.Create; B := TChild.Create;
WriteLn(B.GetVal()); WriteLn(B.GetVal());
B.Free B.Free()
end. end.
''', ''',
'42' + LE, 0); '42' + LE, 0);
@ -5322,7 +5322,7 @@ begin
procedure TCalc.Run; procedure TCalc.Run;
begin begin
FVal := 5; FVal := 5;
Step; Step();
WriteLn(FVal); WriteLn(FVal);
WriteLn(Double()) WriteLn(Double())
end; end;
@ -5360,8 +5360,8 @@ begin
var P: TParser; var P: TParser;
begin begin
P := TParser.Create(); P := TParser.Create();
P.Setup; P.Setup();
P.Advance; P.Advance();
WriteLn(P.FA.Kind, ':', P.FA.Value, ':', P.FA.Line, ':', P.FA.Col); WriteLn(P.FA.Kind, ':', P.FA.Value, ':', P.FA.Line, ':', P.FA.Col);
WriteLn(P.FB.Kind, ':', P.FB.Value, ':', P.FB.Line, ':', P.FB.Col); WriteLn(P.FB.Kind, ':', P.FB.Value, ':', P.FB.Line, ':', P.FB.Col);
P.Free() P.Free()

View file

@ -671,7 +671,7 @@ const
K := TMaker.Create; K := TMaker.Create;
R := K.Make(0.25); R := K.Make(0.25);
WriteLn(R.X); WriteLn(R.X);
K.Free K.Free()
end. end.
'''; ''';
begin begin

View file

@ -967,7 +967,7 @@ const
var c: TC; var c: TC;
begin begin
c := TC.Create(); c := TC.Create();
c.Fill; c.Fill();
writeln(c.A[0]); writeln(c.A[0]);
writeln(c.A[1]); writeln(c.A[1]);
writeln(c.A[2]); writeln(c.A[2]);

View file

@ -198,7 +198,7 @@ const
G[2] := L[2] G[2] := L[2]
end; end;
begin begin
Fill; Fill();
WriteLn(G[0]); WriteLn(G[0]);
WriteLn(G[2]) WriteLn(G[2])
end. end.

View file

@ -254,7 +254,7 @@ const
end end
end; end;
begin begin
Run Run()
end. end.
'''; ''';

View file

@ -171,7 +171,7 @@ const
Exit(5) Exit(5)
end; end;
begin begin
DoIt DoIt()
end. end.
'''; ''';

View file

@ -793,7 +793,7 @@ const
program Prg; program Prg;
uses UPair; uses UPair;
begin begin
InitPair; InitPair();
end. end.
'''; ''';

View file

@ -477,7 +477,7 @@ begin
var C: TChild; var C: TChild;
begin begin
C := TChild.Create(); C := TChild.Create();
C.NoSuchMethod C.NoSuchMethod()
end. end.
'''); ''');
end; end;

View file

@ -525,7 +525,7 @@ const
end; end;
procedure Tmi.use; procedure Tmi.use;
begin begin
im.print; im.print();
end; end;
var var
im: Tmi; im: Tmi;

View file

@ -376,7 +376,7 @@ begin
var F: TFoo; var F: TFoo;
begin begin
F := TFoo.Create(); F := TFoo.Create();
F.NoSuchMethod F.NoSuchMethod()
end. end.
'''); ''');
end; end;

View file

@ -452,7 +452,7 @@ const
procedure ext_abort; external name 'abort'; procedure ext_abort; external name 'abort';
procedure DoA; procedure DoA;
begin begin
ext_abort ext_abort()
end; end;
end. end.
'''; ''';
@ -465,7 +465,7 @@ const
procedure ext_abort; external name 'abort'; procedure ext_abort; external name 'abort';
procedure DoB; procedure DoB;
begin begin
ext_abort ext_abort()
end; end;
end. end.
'''; ''';
@ -474,7 +474,7 @@ const
program TestP; program TestP;
uses AltA, AltB; uses AltA, AltB;
begin begin
DoA; DoB DoA(); DoB()
end. end.
'''; ''';
var var

View file

@ -155,7 +155,7 @@ begin
''' '''
program P; program P;
begin begin
WriteLn WriteLn()
end. end.
'''); ''');
AssertTrue('empty WriteLn still emits newline', AssertTrue('empty WriteLn still emits newline',

View file

@ -175,7 +175,7 @@ const
WriteLn(N) WriteLn(N)
end; end;
begin begin
Greet; Greet();
Greet(42) Greet(42)
end. end.
'''; ''';

View file

@ -47,7 +47,8 @@ type
procedure TestAssignment_IntLit; procedure TestAssignment_IntLit;
procedure TestAssignment_StringLit; procedure TestAssignment_StringLit;
procedure TestProcCall_NoArgs; procedure TestProcCall_NoArgs;
procedure TestProcCall_NoParens; procedure TestProcCall_BareNoParens_RequiresParens;
procedure TestMethodCall_BareNoParens_RequiresParens;
procedure TestProcCall_OneStringArg; procedure TestProcCall_OneStringArg;
procedure TestProcCall_OneIntArg; procedure TestProcCall_OneIntArg;
@ -406,18 +407,37 @@ begin
end; end;
end; end;
procedure TParserTests.TestProcCall_NoParens; procedure TParserTests.TestProcCall_BareNoParens_RequiresParens;
var
Prog: TProgram;
Call: TProcCall;
begin begin
Prog := ParseSource('program P; begin WriteLn end.'); { Issue #148: a parameterless call in statement position requires its
mandatory () (see language-rationale.adoc, "Mandatory parentheses on
zero-argument calls"). `WriteLn` without () is a parse error. }
try try
Call := TProcCall(Prog.Block.Stmts.Items[0]); ParseSource('program P; begin WriteLn end.').Free();
AssertEquals('Name', 'WriteLn', Call.Name); Fail('Expected EParseError for bare parameterless call');
AssertEquals('0 args', 0, Call.Args.Count); except
finally on E: EParseError do
Prog.Free(); AssertTrue('mentions mandatory parens',
Pos('requires () for a call', E.Message) >= 0);
end;
end;
procedure TParserTests.TestMethodCall_BareNoParens_RequiresParens;
begin
{ Issue #148: a parameterless METHOD call in statement position likewise
requires (). `tester.print` (no parens) is a parse error. }
try
ParseSource(
'program P;' +
' type TC = class procedure P; end;' +
' procedure TC.P; begin end;' +
' var c: TC;' +
' begin c := TC.Create(); c.P end.').Free();
Fail('Expected EParseError for bare parameterless method call');
except
on E: EParseError do
AssertTrue('mentions mandatory parens',
Pos('requires () for a call', E.Message) >= 0);
end; end;
end; end;

View file

@ -588,10 +588,10 @@ const
begin begin
end; end;
begin begin
Inner; Inner();
end; end;
begin begin
Outer; Outer();
end. end.
'''; ''';
var IR: string; var IR: string;
@ -619,10 +619,10 @@ const
end; end;
begin begin
x := 0; x := 0;
Inner; Inner();
end; end;
begin begin
Outer; Outer();
end. end.
'''; ''';
var IR: string; var IR: string;
@ -650,7 +650,7 @@ const
WriteLn(1); WriteLn(1);
end; end;
begin begin
Inner; Inner();
end; end;
procedure OuterB; procedure OuterB;
procedure Inner; procedure Inner;
@ -658,11 +658,11 @@ const
WriteLn(2); WriteLn(2);
end; end;
begin begin
Inner; Inner();
end; end;
begin begin
OuterA; OuterA();
OuterB; OuterB();
end. end.
'''; ''';
var var

View file

@ -494,7 +494,7 @@ begin
begin begin
end; end;
begin begin
UseFn; UseFn();
end. end.
''' '''
); );

View file

@ -413,7 +413,7 @@ const
program P; program P;
type TM = procedure of object; type TM = procedure of object;
var G: TM; var G: TM;
begin G end. begin G() end.
'''; ''';
var IR: string; var IR: string;
begin begin
@ -448,7 +448,7 @@ const
M.Code := MethodAddress(F, 'SayHi'); M.Code := MethodAddress(F, 'SayHi');
M.Data := F; M.Data := F;
G := TGreet(M); G := TGreet(M);
G; G();
F.Free() F.Free()
end. end.
'''; ''';
@ -509,7 +509,7 @@ const
M.Code := MethodAddress(C, 'Print'); M.Code := MethodAddress(C, 'Print');
M.Data := C; M.Data := C;
G := TPrintMethod(M); G := TPrintMethod(M);
G; G();
C.Free() C.Free()
end. end.
'''; ''';

View file

@ -473,7 +473,7 @@ end;
procedure TSemanticTests.TestProcCall_WriteLn_NoArgs_OK; procedure TSemanticTests.TestProcCall_WriteLn_NoArgs_OK;
begin begin
Analyse('program P; begin WriteLn end.').Free(); Analyse('program P; begin WriteLn() end.').Free();
end; end;
procedure TSemanticTests.TestProcCall_WriteLn_StringArg_OK; procedure TSemanticTests.TestProcCall_WriteLn_StringArg_OK;
@ -520,7 +520,7 @@ end;
procedure TSemanticTests.TestProcCall_UndeclaredProc_RaisesError; procedure TSemanticTests.TestProcCall_UndeclaredProc_RaisesError;
begin begin
AnalyseExpectError('program P; begin NoSuchProc end.'); AnalyseExpectError('program P; begin NoSuchProc() end.');
end; end;
{ ------------------------------------------------------------------ } { ------------------------------------------------------------------ }

View file

@ -854,7 +854,7 @@ StaticSubscriptAssign
* assignment (SubscriptFieldAssign) or a method call on the element * assignment (SubscriptFieldAssign) or a method call on the element
* (SubscriptMethodCall). Examples: * (SubscriptMethodCall). Examples:
* a[0].Name := 'x'; a[i].Inner.N := 5; * a[0].Name := 'x'; a[i].Inner.N := 5;
* a[0].Bump; a[i].Items.Add(x); * a[0].Bump(); a[i].Items.Add(x);
* a[i].Arr[j] := v; — element write into an array-typed field * a[i].Arr[j] := v; — element write into an array-typed field
* A terminating subscript directly over another subscript (a[i][j] := v, or * A terminating subscript directly over another subscript (a[i][j] := v, or
* the comma form a[i, j] := v) is a multi-dimensional element write — see * the comma form a[i, j] := v) is a multi-dimensional element write — see
@ -866,7 +866,7 @@ SubscriptFieldAssign
SubscriptMethodCall SubscriptMethodCall
= IDENT LBRACKET Expr RBRACKET { SubscriptSuffix } = IDENT LBRACKET Expr RBRACKET { SubscriptSuffix }
DOT IDENT [ LPAREN [ ArgList ] RPAREN ] DOT IDENT LPAREN [ ArgList ] RPAREN
; ;
(* A subscript suffix indexes one or more dimensions. The comma form (* A subscript suffix indexes one or more dimensions. The comma form

View file

@ -3954,6 +3954,14 @@ the lexical level: `Foo()` is always a call, `Foo` is always a read. The
compiler AST becomes simpler — `TMethodCallExpr` for calls with explicit compiler AST becomes simpler — `TMethodCallExpr` for calls with explicit
parentheses, `TFieldAccessExpr` only for field/property reads. parentheses, `TFieldAccessExpr` only for field/property reads.
The rule applies in *statement* position as well as expression position: a
bare `Foo;`, `Obj.Method;`, or `Obj.Free;` used as a statement is a parse
error (issue #148). Earlier the statement parser silently accepted the
bare form — expression position was already enforced, but statement-position
parameterless calls slipped through and built a paren-less `TProcCall` /
`TMethodCallStmt`. Both now raise the same "requires () for a call"
diagnostic the `inherited` and expression-position paths use.
=== Alternatives considered === Alternatives considered
* *Deprecation warning period* — rejected. Blaise is in alpha; there is no * *Deprecation warning period* — rejected. Blaise is in alpha; there is no

View file

@ -20,10 +20,10 @@
W.WriteBool('stable', True); W.WriteBool('stable', True);
2. Array elements - value only, no name (the '...Value' forms): 2. Array elements - value only, no name (the '...Value' forms):
W.BeginArray; W.BeginArray();
W.WriteStringValue('a'); W.WriteStringValue('a');
W.WriteIntValue(1); W.WriteIntValue(1);
W.EndArray; W.EndArray();
3. Structure / escape hatch - when an object member's value is itself a 3. Structure / escape hatch - when an object member's value is itself a
nested object or array, write the key, then open the container: nested object or array, write the key, then open the container:

View file

@ -92,7 +92,7 @@ begin
Result := N.FValue; Result := N.FValue;
Self.FHead := N.FNext; Self.FHead := N.FNext;
Self.FCount := Self.FCount - 1; Self.FCount := Self.FCount - 1;
N.Free N.Free()
end; end;
procedure TLinkedList.Walk; procedure TLinkedList.Walk;
@ -120,7 +120,7 @@ begin
while N <> nil do while N <> nil do
begin begin
Next := N.FNext; Next := N.FNext;
N.Free; N.Free();
N := Next N := Next
end; end;
Self.FHead := nil; Self.FHead := nil;
@ -145,7 +145,7 @@ begin
WriteLn('count=', List.Count); { 4 } WriteLn('count=', List.Count); { 4 }
WriteLn('--- walk ---'); WriteLn('--- walk ---');
List.Walk; { 40/tag0, 30/tag1/marked, 20/tag0, 10/tag0 } List.Walk(); { 40/tag0, 30/tag1/marked, 20/tag0, 10/tag0 }
{ Pop two values off the front } { Pop two values off the front }
V := List.Pop(); V := List.Pop();
@ -155,7 +155,7 @@ begin
WriteLn('count_after_pops=', List.Count); { 2 } WriteLn('count_after_pops=', List.Count); { 2 }
finally finally
List.Clear; List.Clear();
List.Free List.Free()
end end
end. end.

View file

@ -56,7 +56,7 @@ var
Dest: ^Integer; Dest: ^Integer;
begin begin
if Self.FCount = Self.FCapacity then if Self.FCount = Self.FCapacity then
Self.Grow; Self.Grow();
Dest := Self.FData + Self.FCount * SizeOf(Integer); Dest := Self.FData + Self.FCount * SizeOf(Integer);
Dest^ := Value; Dest^ := Value;
Self.FCount := Self.FCount + 1 Self.FCount := Self.FCount + 1
@ -147,7 +147,7 @@ begin
else else
begin begin
if Self.FCount = Self.FCapacity then if Self.FCount = Self.FCapacity then
Self.Grow; Self.Grow();
KPtr := Self.FKeys + Self.FCount * SizeOf(string); KPtr := Self.FKeys + Self.FCount * SizeOf(string);
VPtr := Self.FValues + Self.FCount * SizeOf(Integer); VPtr := Self.FValues + Self.FCount * SizeOf(Integer);
KPtr^ := Key; KPtr^ := Key;
@ -251,7 +251,7 @@ begin
WriteLn('count_after_delete=', List.Count); { 4 } WriteLn('count_after_delete=', List.Count); { 4 }
WriteLn('list[1]_after_delete=', List.Get(1)); { 30 } WriteLn('list[1]_after_delete=', List.Get(1)); { 30 }
List.Free; List.Free();
{ --- TDictionary<string,Integer> --- } { --- TDictionary<string,Integer> --- }
Dict := TStrIntDict.Create; Dict := TStrIntDict.Create;
@ -277,5 +277,5 @@ begin
Found := Dict.ContainsKey('alpha'); Found := Dict.ContainsKey('alpha');
WriteLn('has_alpha_after_remove=', Found); { 0 } WriteLn('has_alpha_after_remove=', Found); { 0 }
Dict.Free Dict.Free()
end. end.