Skip to content

dsc#92: give bytes a member catalog starting at .length - #106

Merged
samifouad merged 1 commit into
mainfrom
fix/bytes-member-catalog-92
Sep 10, 2026
Merged

dsc#92: give bytes a member catalog starting at .length#106
samifouad merged 1 commit into
mainfrom
fix/bytes-member-catalog-92

Conversation

@samifouad

Copy link
Copy Markdown
Member

Closes dsc#92. Tracker: dsc#66. Related: dsc#89, dsc#88, dsc#90, deka#756.

What

bytes gets a member catalog with exactly one member: .length. This is the entire change to the language surface.

const r = unsafe<bytes> { new Uint8Array([65, 66, 67]) }
echo(match (r) {
  Ok(v) => `${v.length}:${v[0]}:${v[2]}`,   // "3:65:67"
  Err(e) => `err: ${e.message}`,
})

RFD 15 justification, member by member

RFD 15 (dekaruntime/rfd#15) specifies the bytes operations as len, slice, concat, to_hex, from_hex, to_base64, from_base64, from_string, and a fallible to_string. None of the operation names are specified as member-method shape — the RFD never commits to member access vs free functions, and how each lands is deka#756's call. This PR adds only what is unambiguous and provably missing:

  • .length — added. This is RFD 15's len operation. It is surfaced as the .length property to match the convention string.length and array.length already establish (primitive_member in crates/deka_syntax/src/typeck/expr.rs), not as a len member that no other primitive has. The bytes package already exposes free-function len (tests/testsuite/packages/bytes_from_string_len), so the operation exists on both shapes the RFD's naming leaves open.
  • slice, concat — deliberately not added. Both are in the RFD's operation list, but exposing them as members now would pre-empt deka#756's design decision (member vs free function, and for slice, whether bounds behaviour gets DekaScript semantics rather than JS's). The unknown-member diagnostic names what exists, so a user who reaches for b.slice gets `bytes` has no field `slice` (available: `length`) at check time, not a silent gap.
  • to_hex/from_hex/to_base64/from_base64/from_string/to_string — not added. These are conversions with open design questions in the RFD itself (fallible shape: Option vs Result is RFD 15 open question 1). Landing them here would ship ahead of the design; that is deka#756.

Nothing here contradicts or exceeds RFD 15: the one member added is the one operation the RFD guarantees, under the spelling the rest of the language's primitives use.

The catalog is closed

resolve_primitive_field gains a dedicated bytes arm, so a nonexistent member is a check-time error with a span and a message naming the one available member (the JsError diagnostic pattern). Unit tests pin both directions: bytes.length is a number property (and rejects flow into string), and b.slice errors at line 2, column 10. Indexing (b[0], dsc#88) is untouched.

Emission cost: zero

bytes is a Uint8Array at runtime and the emitter's FieldAccess arm is transparent, so v.length compiles to exactly v.length — what you would hand-write. A transpile test (bytes_length_transpiles_verbatim in crates/cli/tests/transpile.rs) pins that: the emitted JS contains b.length and no __deka helper.

Fixtures

  • tests/testsuite/data_types/bytes_length — executed (stage run), exact stdout 3:65:67, exercising the dsc#89 regression case directly: ${v.length} inside template interpolation on a narrowed bytes.
  • tests/testsuite/types/bytes_unknown_member — negative fixture, unknown member rejected with the expected diagnostic.
  • Once this ships and deka bumps its scripts/dsc-version pin past it, the two ratcheted lines in deka's expected-failures.txt (unsafe-textencoder-encode, unsafe-uint8array-literal) can come out per their stated removal condition.

Test output

cargo test --workspace — all suites green (incl. the two new checker tests and the new transpile test; the only warnings are two pre-existing unused-variable ones also present on main).

test result: ok. 26 passed ... (all 28 suites ok, 0 failed)

Testsuite against the pinned runtime (0.46.0) plus this branch's dsc:

native CLI: deka 0.46.0 | fixtures: 874  jobs: 8
 Passed: 719 | Failed: 0 | Known: 1 | Skipped: 154 | Total: 874

Both new fixtures verified individually: bytes_length passes at stage run with stdout 3:65:67; bytes_unknown_member fails check with [transpile] 2:10: byteshas no fieldslice(available:length).

-kimi

`bytes` is a Uint8Array view (dsc#88): it indexes but had no member
catalog, so `bytes.length` failed check after dsc#89 closed template
interpolation. Add `("bytes", "length")` to the primitive member
table, matching the string/array property convention; emission is the
transparent field access, so the runtime cost is zero.

RFD 15 lists `len` among the bytes operations; it is surfaced as the
`.length` property rather than a `len` member no other primitive has.
The rest of the RFD 15 operations (slice, concat, hex/base64,
conversions) stay out of the catalog until deka#756 implements the RFD,
so the catalog is deliberately closed at one member and the
unknown-member diagnostic says so: `bytes` has no field `x`
(available: `length`).
@samifouad
samifouad merged commit 5e9f1e1 into main Sep 10, 2026
4 checks passed
@samifouad
samifouad deleted the fix/bytes-member-catalog-92 branch September 10, 2026 08:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant