Copy as a generic overloaded intrinsic (string + dynarray families)
- Type: feature (compiler)
- Status: backlog
- Owner: — (track A)
- Opened: 2026-06-19 (track B, while delivering string
Copyin lib) - Relation: pairs with the lib
strutilswork (lib/rtl/strutils.pasalready ships the interim AnsiStringCopy); depends on generics or builtin-overload support. Sibling of the same-shape intrinsicsDelete/Insert/Concat(see note below).
Why the lib cannot finish this
lib/rtl/strutils.pas provides Copy(s: AnsiString; index, count): AnsiString
— good enough for the demos that copy substrings, and soon to be
SetLength-optimized (build the result once, no char-by-char append). But the
full FPC Copy is a generic intrinsic overloaded over a type family, which a
single non-generic RTL function cannot express:
- dynamic-array
Copy(arr, index, count)→ sub-array ofarray of T. The real blocker: element-type-aware, generic overT. Not writable as one concrete RTL routine. Needs generics or a compiler intrinsic. - 2-arg form
Copy(s, index)= fromindexto the end (count defaults to the rest). Overload resolution on arity. - string-family overloads:
ShortString,UnicodeString/WideString(if/when those types exist). - call-site resolution by argument type: pick the string vs dynarray
meaning of
Copyfrom the actual argument type.
Scope
- Recognize
Copyas an overloaded intrinsic resolved at the call site by argument type (string family vs dynamic array) and arity (2-arg vs 3-arg). - dynarray form returns a fresh
array of T(element-type-aware copy). - Keep the lib
strutils.Copy(AnsiString)working as the interim path; the intrinsic supersedes it for the generic cases.
Track B note (2026-06-25)
The interim AnsiString Copy (lib/rtl/sysutils.pas) is now SetLength+single
Move, not char-by-char append — the optimization this ticket anticipated.
Behaviour unchanged (make lib-test green). Does not affect the Track A intrinsic
scope above (still needs generics / call-site overload for the dynarray family).
Siblings (same reasoning — mention so they aren't re-discovered)
Delete(s/arr, index, count), Insert(src, dst, index), and Concat(...) are
also intrinsics overloaded over the same string + dynarray families. They will
hit the identical "can't be one non-generic RTL function" wall. Fold them into
this work (or spin a sibling ticket) rather than re-filing each from a demo.
Log
- 2026-06-19 — opened by track B. Interim AnsiString
Copylives inlib/rtl/strutils.pas; this ticket covers the generic intrinsic the lib can't express (dynarrayCopy, 2-arg form, string-family overloads, by-type resolution).
Progress (2026-06-19) — dynamic-array Copy DONE
The real blocker (generic dynamic-array Copy(arr, index, count) → fresh
array of T, element-type-aware) is implemented, plus the 2-arg
Copy(arr, index) form and call-site resolution by argument type:
- New
AN_DYN_COPYnode. The intrinsic fires only when the first argument is a dynamic array; a stringCopykeeps thesysutils.CopyRTL path (resolved whether or not aCopyproc is in scope — handled both at the no-Copy-proc factor point and at the no-overload-match point, so a stringCopyis never shadowed). - Lowered (ir.inc) into: clamp the count to the source bounds (
PXXClampLen), SetLength a fresh dyn-array local of the source element type, then raw-copycount*elemSizebytes fromsource[index](PXXMemCopy). Element size comes from the source symbol, so it is generic overT(validated for Integer and a 24-byte record). Index is 0-based (FPC dynamic-array Copy).
Validated x86-64 (test-core, test/test_dynarray_copy.pas) + arm32 cross suite;
self-host + cross-bootstrap byte-identical. NOTE: a raw byte copy, so an array of
a managed element type (AnsiString / managed record) is shallow — deep element
copy is a later extension.
Landmines recorded: a bare runtime-helper call whose result is unused is NOT emitted (only statement-linked nodes are) — store the result into a temp; and the result must be tagged so the assignment stores the full 8-byte handle.
Remaining: string-family overloads beyond the RTL Copy (ShortString /
UnicodeString), and the dynarray Delete/Insert/Concat variants. On
i386/aarch64 b := Copy(...) is additionally blocked by a separate pre-existing
whole-dynamic-array assignment gap (bug-dynarray-whole-var-assign-cross).
Progress (2026-06-20) — string Delete / Insert / Concat in sysutils
The AnsiString Delete, Insert, and Concat siblings are now in
lib/rtl/sysutils.pas as pure-Pascal library functions (not compiler
intrinsics). Design decision: Copy stays compiler-owned (it has intercept
entanglements with Str); Delete/Insert/Concat are library-side since
the compiler doesn't depend on them internally.
procedure Delete(var s: AnsiString; index, count: Integer)— FPC-compatible no-op on out-of-range or non-positive count; count clamped to end of string.procedure Insert(const src: AnsiString; var dst: AnsiString; index: Integer)— FPC-compatible: index < 1 → 1, index > Length+1 → append, empty src → no-op.function Concat(const s1, s2: AnsiString): AnsiString— 2-arg wrapper over+. Variadic Concat can't be expressed in this compiler;+chains for more.
All three tested in test/lib_sysutils.pas with golden-output verification
(make lib-test). Remaining scope under this ticket: the dynarray
variants of Delete/Insert/Concat (these need compiler intrinsics, same as
dynarray Copy), and string-family overloads for ShortString/UnicodeString.
-
2026-06-22 — string
Delete/InsertDONE (the most-wanted siblings). Both were missing entirely (undefined variable (Delete)). Implemented as statement intrinsics in ParseStatementAST that lower to builtin helpers__pxxStrDelete(var s; index; count)/__pxxStrInsert(const src; var s; index)(compiler/builtin/builtin.pas), built on__pxxStrCopyso managed refcounting is the ordinary var-param + assign path; args eval once. Available with nouses(pre-scan pulls the builtin unit ondelete(/insert(); a user routine of the same name shadows; string dest only (dynarray Delete/Insert and ESP left as follow-up). Testtest/test_string_delete_insert.pas, FPC oracle-matched. make test + cross-bootstrap byte-identical. REMAINING:Concat(...)intrinsic; stringCopyfamily overloads (ShortString/Unicode if/when added); dynamic-arrayDelete/Insert; by-type call-site resolution polish. (2-argCopy(s,i)and dynarrayCopyalready work.) -
2026-06-22 —
ConcatDONE.Concat(s1, ..., sn)folds at parse time (ParseFactor) to chained string+, reusing the working concat codegen; nouses, a userConcatshadows it. Testtest/test_concat_intrinsic.pas, FPC-matched. REMAINING: stringCopyfamily overloads (ShortString/Unicode if/when added); dynamic-arrayDelete/Insert. The common string-mutator set (Copy 2-arg, dynarray Copy, Delete, Insert, Concat) is now complete.
TRIAGE (2026-06-30, multi-agent verify)
SOLVED. Probed: Copy(dynarr,1,2)->len2 [10,20]; Copy(s,1,5)->'hello'; 2-arg Copy(s,7)->'world' all work. Remaining scope is covered elsewhere: dynarray Delete/Insert = [[feature-dynarray-insert-delete]]; ShortString/UnicodeString overloads need types that do not exist yet. The Copy intrinsic itself is complete -> done.