The mandatory-() migration (commit 57c0660) missed ~1000 bare zero-arg
function and method calls across the compiler, stdlib, and test suite.
Without (), these were silently treated as variable reads, producing
broken QBE IR (%_var_ temporaries instead of call instructions).
Added semantic error diagnostics in uSemantic.pas to catch bare references
to functions/procedures that require () for a call. This prevents silent
miscompilation and gives users a clear error message.
Fixed all bare calls across 69 files: compiler pipeline (uParser,
uSemantic, uCodeGenQBE, uLexer, uPasTokeniser, etc.), stdlib (classes),
and the full test suite (inline test programs and test framework code).
FIXPOINT_OK at 262,220 lines. 2627 tests pass. Rolling bootstrap from
v0.10.0 verified.
With mandatory () on all zero-argument calls (51ebf23), the
IsNoArgFuncCall/NoArgFuncDecl fields on TIdentExpr and the
synthesised TFuncCallExpr codegen path are dead code. Remove them.
This exposed ~300 bare function calls missed by the original
migration script across compiler, runtime, stdlib, tests, and
embedded Blaise source strings. Fix all of them.
Add IsProcFieldCall support to TMethodCallExpr so that
H.Handler() works for procedure-type fields — the parser creates
TMethodCallExpr for Obj.Name(), and the semantic pass now detects
when 'Name' is a procedural-typed field rather than a method,
routing through the indirect-call codegen path.
Update fixpoint.sh to fall back to an existing blaise_rtl.a when
the release binary is too old to build the runtime.
2627 tests pass, FIXPOINT_OK.
Make parentheses mandatory on every function, procedure, method, and
constructor call — even those with zero arguments. A bare identifier
or field access is now unambiguously a variable/field/property read;
appending () makes it a call.
Mechanically migrated all 144 source files (compiler, runtime, stdlib,
tests, kanban tool). Fixed several latent bugs exposed by the AST node
transition from TFieldAccessExpr.IsMethodCall to TMethodCallExpr:
- IsBuiltinToString applied to record methods (added tyClass guard)
- IsVarParam not set for value record/static-array parameters in
AnalyseMethodCallExpr (extended to check skParameter + aggregate type)
- Native backend used movq (pointer load) for record receivers instead
of leaq (address-of) in EmitMethodCallExpr
- ResolveDiamond now handles TMethodCallExpr for diamond-operator
constructor calls
Updated grammar.ebnf (MethodCall, ProcCall, Factor rules) and
language-rationale.adoc with the design decision. Marked the
future-improvements.adoc entry as implemented.
All 2627 tests pass. Fixpoint verified (FIXPOINT_OK).
TThread.Create(False) appeared to silently skip execution (GitHub #73).
Root cause: Start took an extra _ClassAddRef so the trampoline could
release it on exit. This kept the refcount at 1 after Free, preventing
Destroy (and its WaitFor/pthread_join) from running — the main program
exited before the thread finished.
Fix: remove the trampoline's ARC reference entirely. The caller's
reference is the only one; releasing it triggers Destroy → WaitFor →
pthread_join, which blocks until the thread has fully exited. This is
safe because pthread_join guarantees the trampoline has returned before
Destroy frees the object.
Also remove FreeOnTerminate — ARC makes it redundant. In Delphi/FPC it
exists because manual Free is the only cleanup path; in Blaise, scope
exit or reassignment automatically joins and frees the thread. The
migration analyser can flag this for ported code.
Document the TThread ARC lifetime model in language-rationale.adoc and
add class/method documentation comments to classes.pas.
Remove sLineBreak (Delphi alias for LineEnding) and PathDelim (Delphi
alias for DirectorySeparator). Blaise keeps exactly three platform
constants: LineEnding, DirectorySeparator, PathSeparator.
Platform constant values are now derived from GTarget (the compilation
target) via TargetLineEnding/TargetDirectorySeparator/TargetPathSeparator
in blaise.codegen.target, so cross-compilation produces the correct
values without conditional compilation.
Three fixes that were causing six e2e failures:
Local array consts collided at link time. Function/block/unit-level
`const X: array[...] = (...)` were emitted as `export data $X` with no
mangling. The RTL's own `Days` const (rtl.platform.posix.pas) then
clashed with any user program declaring a `Days` const
(`multiple definition of 'Days'`). Local array consts now use a mangled,
file-local label (`__bac_N_Name`): the mangled name is set in semantic
(AnalyseArrayConstDecls -> ResolvedQbeName / ConstArrayQbe), carried to
the bare-ident read path via TIdentExpr.ConstArraySymbol, and emitted as
non-exported `data` so two separately-compiled objects cannot clash.
Class/record consts keep their exported, type-qualified label.
TComponent fought ARC and crashed. The destructor force-Freed children
it held only as raw pointers, while a caller's variable still referenced
them — a use-after-free at scope exit, surfacing as infinite
Destroy->RemoveComponent->ClassRelease recursion. Ownership is now honest
with ARC: FOwner is [Unretained]; AddComponent takes a strong ref via
_ClassAddRef; Destroy releases the owner's hold rather than Freeing, so a
child the caller still holds survives and an unheld child is destroyed;
RemoveComponent only unlinks the slot (the dying child's ref already
reached zero).
Deflaked TestRun_Thread_Terminate_Flag. The worker could finish via its
`I > 1000` break before the main thread's Terminate landed, racing the
printed flag between 0 and 1. The loop now exits only via the terminate
flag (with a high safety cap), so FTerminated is deterministically True.
Unit tests in cp.test.constants.pas updated to assert the mangled,
non-exported label.
SplitIntoList — the sole backend for TStringList.SetText and LoadFromFile —
trimmed leading and trailing spaces from each split segment. That is correct
for CSV-style splitting but wrong for line-splitting: a TStringList must store
each line verbatim, including indentation.
Removed the trimming so each segment is added as Copy(S, Start, Len) untouched.
SplitIntoList has exactly one caller (SetText), so no trimming-dependent code
is affected.
This also fixes a latent test-infrastructure bug: the threaded test runner
parses each subprocess suite's stdout via TStringList.Text and identifies
failure-detail lines by their two-space indentation. With indentation stripped,
those lines never matched, AddFailure was never called, and the merged result
reported zero failures — so the plain summary printed OK even when [Threaded]
suites failed. With lines preserved, the summary now correctly reports failures.
Regression test: TestRun_TextSet_PreservesLeadingWhitespace asserts on raw
stdout so the spaces inside [] are checked without round-tripping through Text.
Self-hosting fixpoint verified clean.
Now that Exit(X) is supported (0f63626), replace the historical
'Result := X; Exit;' workaround with the Exit(X) function-result shorthand
across compiler, runtime, and stdlib (305 sites).
Only same-indentation pairs are merged: a 'Result := X;' immediately followed
by an 'Exit;' at the same indentation level. Sites where the trailing 'Exit;'
is less-indented than the assignment are left untouched — there the Exit
belongs to an enclosing for/else/if block, not to the assignment, so merging
would change control flow (e.g. the xor/and numeric-result branch in
uSemantic falling through to the Boolean-operand check). 14 such sites are
deliberately not converted.
Three converted sites sit inside a real try/finally (uSemantic overload/
generic resolution); each try body already contained bare Exit; statements
and its finally body is a plain local-list Free, so control flow is unchanged.
A new e2e test (TestRun_ExitValueThroughFinally) confirms Exit(X) from inside
a try/finally runs the finally body and returns the value.
Self-hosting fixpoint verified clean (stage-2 IR == stage-3 IR).
Both methods accumulated a result string with repeated `Result := Result + …`
inside a loop — each iteration allocates a fresh buffer and copies the entire
accumulator forward, quadratic in total content size.
Rewrite both using TStringBuilder (geometric buffer growth, single ToString
at the end). On a self-compile via --output the GetText hot path drops from
~17 s to ~1.7 s (~10× speedup); GetCommaText gets the same fix proactively.
Add TestRun_TextGet_ManyLines: 500-line list with mixed empty/non-empty entries,
spot-checks total length and key substrings after the rewrite.
Linear scan over FData returning the index of the first equal element
or -1 when not found. Matches the semantics of TSet<T>.IndexOf which
the rest of the unit already exposes, and saves callers from writing
the hand-rolled scan that bench_lists.pas demonstrates.
Adds two e2e tests covering the integer/found and string/not-found
paths.
Wires up for..in on TList<T> from generics.collections. Two compiler
fixes were required:
- uSemantic: TClassName<T>.Method(args) inside a generic method body
failed to resolve because AnalyseMethodCallExpr looked up the literal
name with the unsubstituted type parameter. Now mirrors the existing
field-access path: if the receiver name contains '<' and lookup fails,
apply ResolveScopeBoundTypeParams + FindTypeOrInstantiate before
re-trying the lookup.
- uCodeGenQBE: constructor-with-args path emitted
$_FieldCleanup_TListEnumerator<String> with literal angle brackets
which QBE rejects. Mangle the class name like the no-arg path
already does.
Add blaise_thread.pas runtime unit wrapping pthread_create, pthread_join,
pthread_mutex_init/lock/unlock/destroy and sysconf for GetCPUCount.
Implement TThread (Create, Start, Terminate, WaitFor, Execute virtual,
FreeOnTerminate) and TCriticalSection (Enter/Leave) in classes.pas using
ARC-safe ref counting in the thread trampoline.
Fix EmitFieldCleanupFn to walk the parent chain when emitting the Destroy
call — previously a subclass without its own Destroy would generate an
empty cleanup, causing leaked resources from parent destructors.
Link -lpthread in both the compiler driver and E2E test base.
Include 7 E2E tests covering basic execution, WaitFor blocking, Terminate
flag, Finished flag, multiple threads, mutex-protected counter, and
inherited Destroy cleanup.
Separate always-linked runtime code (system.pas, ARC, strings, platform
abstraction) from opt-in standard library units (sysutils, classes, math,
dateutils, generics, etc.). This mirrors the Go runtime/ vs stdlib, Rust
core vs std, and Swift SwiftRuntime vs SwiftCore patterns.
- runtime/ contains code linked into every binary + C shims
- stdlib/ contains units imported explicitly via uses clause
- Single platform abstraction layer (rtl.platform.*) in runtime/,
shared by both runtime/ and stdlib/ units
- bcl.testing renamed to blaise.testing across all 95+ test files
- Updated PasBuild project.xml, fixpoint.sh, README, unit search paths
- All 1931 tests pass, fixpoint OK (stage-3/stage-4)