Namespace duplication & conflicts: System.* overlaps Os.* (arg/arg_count/env/exit + streams); consolidate #40

Closed
opened 2026-08-30 21:32:50 +02:00 by orkun · 1 comment
Owner

Summary

System.* and Os.* are two public, documented namespaces that cover the same
concern — the process/environment around the game — and four members are
duplicated with byte-for-byte identical lowering
. This is both a user-facing
ambiguity ("which one do I call?") and a maintenance hazard (the two copies will
drift the moment one is touched). A quick sweep turned up one secondary doc-level
overlap (Text vs Unicode case mapping) and a set of benign same-verb/different-subject
collisions that need no action. Details below.

Primary finding — System.* ⊃ Os.* duplication

Both namespaces are shipped and documented (docs/language/system/,
docs/language/os/). These members exist in both, with the same semantics:

member System.* lowering Os.* lowering identical IR?
arg_count() bare arg_count → load i32, ptr @L_argc (emit_intrin.ludic:60) inline load i32, ptr @L_argc (emit_os.ludic:47) yes
arg(i) bare arg → load @L_argv (emit_intrin.ludic:61) inline load @L_argv (emit_os.ludic:48) yes
env(name) bare getenv → @getenv (emit_intrin.ludic:80) inline @getenv (emit_os.ludic:61) yes
exit(code) bare exit → @exit + unreachable (emit_intrin.ludic:67) inline @exit + unreachable (emit_os.ludic:54) yes

The dispatch for System.* lives at emit_expr.ludic:346
(aliases each method to a bare intrinsic), and Os.* at
emit_expr.ludic:250 → emit_os_ns.

Beyond the exact duplicates, the two namespaces overlap on the same domain with
slightly different shapes:

  • Standard streams: System.stdout / System.stderr return a raw FILE*
    handle; Os.stdout_write(s) / Os.stderr_write(s) write a string directly.
    Same concern, two spellings.
  • Os.* additionally owns the richer, curated surface: args(), env_or,
    has_env, set_env, unset_env, platform, arch, and the known-folder
    helpers (save_dir/config_dir/cache_dir/temp_dir).
  • System.* additionally owns the low-level file/process bits: file_open/read/ write/seek/tell/close, read_char, run.

So System.* is really an older low-level grab-bag; Os.* (added in #21) is the
newer curated environment interface. The env/args/exit/streams slice of System.*
is now redundant with Os.*, and the file slice is exactly what the open Fs/Path
proposal (#10) intends to wrap ("Some low-level pieces already exist as bare builtins
(file_*, read_char) … wrap them into one safe, ergonomic, namespaced API").

Why it matters

  • Ambiguity: System.env and Os.env have identical docs/signatures. A newcomer
    has no way to know they're the same call.
  • Drift risk: four lowerings are copy-pasted. Fix a bug in Os.exit's codegen and
    System.exit silently keeps the old behavior (and vice-versa).
  • Docs bloat: two full doc trees describe one capability.

Proposed direction (for discussion)

Make Os.* the canonical environment/process namespace and retire the overlapping
System.* members:

  1. Route the file/IO members (file_*, read_char, run) into the Fs/Path
    design (#10) rather than leaving them under System.*.
  2. Collapse System.{arg,arg_count,env,exit} and System.{stdout,stderr} onto the
    Os.* equivalents — either delete them, or make System.* a thin deprecated alias
    that calls the same emit_os_ns path so the lowering exists in exactly one place.
  3. Remove the duplicated docs/language/system/* pages for anything that moves.

At minimum, even if System.* is kept for now, the four duplicated methods should
share one codegen path instead of two copy-pasted ones.

Secondary finding — Text.upper/lower vs Unicode.upper/lower

Text.upper is ASCII-only (fn_str_upper, emit_text.ludic:53)
while Unicode.upper does full Unicode case mapping (fn_uni_case,
emit_unicode.ludic:60) — genuinely different behavior,
but their docs are indistinguishable (sig: … -> string, tip "Uppercase a string").
Not code duplication, but a real trap: pick the wrong one and non-ASCII text is
silently mishandled. The docs should state the ASCII-only limit on Text.upper/lower
and cross-link to Unicode.*. (Text vs Unicode for char_at/len/length is
fine — the tips already say "byte" vs "code point".)

Benign collisions (no action — listed for completeness)

Method names shared across namespaces that are correctly "same verb, different subject
/ receiver type": Math/Vector/Color.lerp, Date/DateTime.{year,month,day,weekday},
Map/World.size, Math/Random.sign, DateTime/Uuid.parse, Date/Uuid.new,
List/Text.{contains,index_of}, List/Screen.clear, Clock/Time.now
(simulated DateTime vs wall-clock epoch int — distinct domains), etc. These are
fine as-is.


Investigation covered every documented namespace method (docs/language/*/*.md,
279 members) plus the emit_*_ns dispatch in selfhost/.

## Summary `System.*` and `Os.*` are two public, documented namespaces that cover the same concern — the process/environment around the game — and **four members are duplicated with byte-for-byte identical lowering**. This is both a user-facing ambiguity ("which one do I call?") and a maintenance hazard (the two copies will drift the moment one is touched). A quick sweep turned up one secondary doc-level overlap (`Text` vs `Unicode` case mapping) and a set of benign same-verb/different-subject collisions that need no action. Details below. ## Primary finding — `System.*` ⊃ `Os.*` duplication Both namespaces are shipped and documented (`docs/language/system/`, `docs/language/os/`). These members exist in **both**, with the same semantics: | member | `System.*` lowering | `Os.*` lowering | identical IR? | |---|---|---|---| | `arg_count()` | bare `arg_count` → `load i32, ptr @L_argc` ([emit_intrin.ludic:60](selfhost/emit_intrin.ludic)) | inline `load i32, ptr @L_argc` ([emit_os.ludic:47](selfhost/emit_os.ludic)) | **yes** | | `arg(i)` | bare `arg` → load `@L_argv` ([emit_intrin.ludic:61](selfhost/emit_intrin.ludic)) | inline load `@L_argv` ([emit_os.ludic:48](selfhost/emit_os.ludic)) | **yes** | | `env(name)` | bare `getenv` → `@getenv` ([emit_intrin.ludic:80](selfhost/emit_intrin.ludic)) | inline `@getenv` ([emit_os.ludic:61](selfhost/emit_os.ludic)) | **yes** | | `exit(code)` | bare `exit` → `@exit` + `unreachable` ([emit_intrin.ludic:67](selfhost/emit_intrin.ludic)) | inline `@exit` + `unreachable` ([emit_os.ludic:54](selfhost/emit_os.ludic)) | **yes** | The dispatch for `System.*` lives at [emit_expr.ludic:346](selfhost/emit_expr.ludic) (aliases each method to a bare intrinsic), and `Os.*` at [emit_expr.ludic:250](selfhost/emit_expr.ludic) → `emit_os_ns`. Beyond the exact duplicates, the two namespaces **overlap on the same domain** with slightly different shapes: - **Standard streams:** `System.stdout` / `System.stderr` return a raw `FILE*` handle; `Os.stdout_write(s)` / `Os.stderr_write(s)` write a string directly. Same concern, two spellings. - `Os.*` additionally owns the richer, curated surface: `args()`, `env_or`, `has_env`, `set_env`, `unset_env`, `platform`, `arch`, and the known-folder helpers (`save_dir`/`config_dir`/`cache_dir`/`temp_dir`). - `System.*` additionally owns the low-level file/process bits: `file_open/read/ write/seek/tell/close`, `read_char`, `run`. So `System.*` is really an older low-level grab-bag; `Os.*` (added in #21) is the newer curated environment interface. The env/args/exit/streams slice of `System.*` is now redundant with `Os.*`, and the file slice is exactly what the open **Fs/Path** proposal (#10) intends to wrap ("Some low-level pieces already exist as bare builtins (`file_*`, `read_char`) … wrap them into one safe, ergonomic, namespaced API"). ### Why it matters - **Ambiguity:** `System.env` and `Os.env` have identical docs/signatures. A newcomer has no way to know they're the same call. - **Drift risk:** four lowerings are copy-pasted. Fix a bug in `Os.exit`'s codegen and `System.exit` silently keeps the old behavior (and vice-versa). - **Docs bloat:** two full doc trees describe one capability. ### Proposed direction (for discussion) Make **`Os.*` the canonical** environment/process namespace and retire the overlapping `System.*` members: 1. Route the file/IO members (`file_*`, `read_char`, `run`) into the **Fs/Path** design (#10) rather than leaving them under `System.*`. 2. Collapse `System.{arg,arg_count,env,exit}` and `System.{stdout,stderr}` onto the `Os.*` equivalents — either delete them, or make `System.*` a thin deprecated alias that calls the same `emit_os_ns` path so the lowering exists in exactly one place. 3. Remove the duplicated `docs/language/system/*` pages for anything that moves. At minimum, even if `System.*` is kept for now, the four duplicated methods should share one codegen path instead of two copy-pasted ones. ## Secondary finding — `Text.upper/lower` vs `Unicode.upper/lower` `Text.upper` is **ASCII-only** (`fn_str_upper`, [emit_text.ludic:53](selfhost/emit_text.ludic)) while `Unicode.upper` does **full Unicode case mapping** (`fn_uni_case`, [emit_unicode.ludic:60](selfhost/emit_unicode.ludic)) — genuinely different behavior, but their docs are indistinguishable (`sig: … -> string`, tip "Uppercase a string"). Not code duplication, but a real trap: pick the wrong one and non-ASCII text is silently mishandled. The docs should state the ASCII-only limit on `Text.upper/lower` and cross-link to `Unicode.*`. (`Text` vs `Unicode` for `char_at`/`len`/`length` is fine — the tips already say "byte" vs "code point".) ## Benign collisions (no action — listed for completeness) Method names shared across namespaces that are correctly "same verb, different subject / receiver type": `Math`/`Vector`/`Color`.`lerp`, `Date`/`DateTime`.`{year,month,day,weekday}`, `Map`/`World`.`size`, `Math`/`Random`.`sign`, `DateTime`/`Uuid`.`parse`, `Date`/`Uuid`.`new`, `List`/`Text`.`{contains,index_of}`, `List`/`Screen`.`clear`, `Clock`/`Time`.`now` (simulated `DateTime` vs wall-clock epoch `int` — distinct domains), etc. These are fine as-is. --- _Investigation covered every documented namespace method (`docs/language/*/*.md`, 279 members) plus the `emit_*_ns` dispatch in `selfhost/`._
orkun added the
priority:medium
area:stdlib
labels 2026-08-30 21:32:50 +02:00
orkun closed this issue 2026-08-30 21:54:37 +02:00
Author
Owner

Done in ac1e8d1.

Direction taken: made Os.* the canonical environment/process namespace and retired the overlapping System.* members rather than aliasing them — the strongest of the proposed options, since it removes the ambiguity outright instead of leaving two spellings.

Retired (all now route to Os.*): System.arg → Os.arg, System.arg_count → Os.arg_count, System.env → Os.env, System.exit → Os.exit, and the stream pair System.stdout/System.stderr → Os.stdout_write/Os.stderr_write. Calling a retired member is now a hard compile error (unknown builtin System.env), not silent duplication.

Kept under System.*: the genuinely-unique low-level surface — the raw FILE* handle API (file_open/read/write/seek/tell/close), read_char, and run. These are exactly the "grab-bag" the Fs/Path work (#10, now shipped as Fs.*/Path.*) wraps at a higher level; the raw handles stay as the escape hatch.

On the "one codegen path" ask: the four duplicated lowerings are no longer copy-pasted across two user-facing namespaces. The bare arg/exit/getenv/file_stdout/file_stderr intrinsics remain — but as the primitive layer the self-hosted compiler itself compiles against (main.ludic et al. call them directly), not as a second public spelling.

Secondary finding (Text vs Unicode): Text.upper/Text.lower docs now state the ASCII-only limit explicitly and cross-link to Unicode.upper/Unicode.lower for full Unicode case mapping.

Removed the six now-dead docs/language/system/* pages, retuned the section blurb, and updated inventory.json + the LSP signature table. Reseeded; the C-free bootstrap fixpoint holds. All suites green (56 test / 29 selfhost / 29 test-tools) and docs still cover every implemented feature (291 namespace methods).

The benign same-verb/different-subject collisions listed in the issue were left as-is, as recommended.

Done in ac1e8d1. **Direction taken:** made `Os.*` the canonical environment/process namespace and *retired* the overlapping `System.*` members rather than aliasing them — the strongest of the proposed options, since it removes the ambiguity outright instead of leaving two spellings. **Retired** (all now route to `Os.*`): `System.arg` → `Os.arg`, `System.arg_count` → `Os.arg_count`, `System.env` → `Os.env`, `System.exit` → `Os.exit`, and the stream pair `System.stdout`/`System.stderr` → `Os.stdout_write`/`Os.stderr_write`. Calling a retired member is now a hard compile error (`unknown builtin System.env`), not silent duplication. **Kept under `System.*`:** the genuinely-unique low-level surface — the raw `FILE*` handle API (`file_open`/`read`/`write`/`seek`/`tell`/`close`), `read_char`, and `run`. These are exactly the "grab-bag" the Fs/Path work (#10, now shipped as `Fs.*`/`Path.*`) wraps at a higher level; the raw handles stay as the escape hatch. **On the "one codegen path" ask:** the four duplicated lowerings are no longer copy-pasted across two *user-facing* namespaces. The bare `arg`/`exit`/`getenv`/`file_stdout`/`file_stderr` intrinsics remain — but as the primitive layer the self-hosted compiler itself compiles against (`main.ludic` et al. call them directly), not as a second public spelling. **Secondary finding (Text vs Unicode):** `Text.upper`/`Text.lower` docs now state the ASCII-only limit explicitly and cross-link to `Unicode.upper`/`Unicode.lower` for full Unicode case mapping. Removed the six now-dead `docs/language/system/*` pages, retuned the section blurb, and updated `inventory.json` + the LSP signature table. Reseeded; the C-free bootstrap fixpoint holds. All suites green (56 test / 29 selfhost / 29 test-tools) and docs still cover every implemented feature (291 namespace methods). The benign same-verb/different-subject collisions listed in the issue were left as-is, as recommended.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: workshopsoft/ludic#40
No description provided.