Skip to content

Commit abd8729

Browse files
olwangclaude
andcommitted
docs(spec): realign v0.6 spec with implemented stdlib and protocols
Spec had drifted from the compiler/stdlib. Updates: - §14.6: document compiler-owned value protocols (Clone, Eq, Ord, Hashable) and the derives(...) clause (accepted set, field-checking, resource rules, Float is Clone but not Eq/Ord/Hashable). - §18.2: add Math module (Int/Float intrinsics), scalar conversions/inspection (Int.to_float, Float predicates), the Hashable bound on Map/Set keys, and representative core signatures for File/Directory, Json, Clock/Env/Random/ Process, Encoding/Regex/Log, and Url/Csv facades. - §20.1 L: relabel value derives from future direction to IMPLEMENTED. - Add "Changes since the v0.6 baseline" changelog block. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent c7d850d commit abd8729

1 file changed

Lines changed: 299 additions & 2 deletions

File tree

docs/RSScript_v0.6_Spec.md

Lines changed: 299 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,35 @@ Package/REIR integration:
3636
propagation.
3737
```
3838

39+
### Changes since the v0.6 baseline
40+
41+
```text
42+
Value-semantics protocols and derives (§14.6, §20.1 L) — IMPLEMENTED:
43+
- Compiler-owned protocols `Clone`, `Eq`, `Ord`, `Hashable` with their
44+
contracts in stdlib (`clone/`, `cmp/`, `hash/`).
45+
- `derives(...)` clause on struct/sum: accepted set is Debug, Clone, Eq,
46+
Ord, Hash, JsonEncode, JsonDecode, Schema, ReviewSchema. Field-level
47+
derive support is checked in RSScript before lowering; resources accept
48+
only Debug/Schema/ReviewSchema.
49+
- `Clone.clone<T>(self: read x) -> fresh T` closes the read-borrow clone
50+
gap; `String.clone` and per-type synthesized `.clone()` are the same
51+
managed-value copy.
52+
- `Map<K,V>` keys and `Set<K>` elements are bound by `Hashable`; a
53+
non-hashable key is rejected in RSScript with a `derives(Eq, Hash)` fix.
54+
55+
Numeric surface (§18.2) — IMPLEMENTED:
56+
- `Math` module: Int intrinsics (abs/min/max/clamp/pow) and Float intrinsics
57+
(abs/min/max/clamp/pow, exp/exp2/log/log2/sin/cos/tanh, sqrt, trunc_float,
58+
floor/ceil/round).
59+
- Scalar conversions/inspection: `Int.to_float`, `Int.to_string`,
60+
`Float.to_string`, `Float.is_nan`/`is_finite`/`is_infinite`,
61+
`String.from_float`, `String.parse_float`.
62+
63+
Soundness/feature fixes folded into the prose (see BUGS.md RSS-1…RSS-13):
64+
- Nested generics, sound payload-variant construction, borrowed-match
65+
payload extraction, callable clone, let-mut clone of read-param init.
66+
```
67+
3968
---
4069

4170
## Constitution
@@ -2986,6 +3015,82 @@ safety, blanket implementations, associated types, and auto method resolution
29863015
must not leak into source, diagnostics, `.rssi` contracts, package metadata, or
29873016
review evidence.
29883017

3018+
#### Compiler-owned protocols and `derives`
3019+
3020+
A small, fixed set of protocols are **compiler-owned**: their contract is
3021+
declared in `stdlib/` but satisfaction is produced by the compiler from a
3022+
`derives(...)` clause on a type declaration rather than written by hand. This is
3023+
the implemented form of the "compiler-owned derives" direction20.1 L) for the
3024+
value-semantics protocols. The shipped derives are:
3025+
3026+
```text
3027+
Debug structural debug rendering (the implicit default on every type,
3028+
including resources)
3029+
Clone deep, explicit value copy (protocol Clone, §below)
3030+
Eq total structural equality (protocol Eq)
3031+
Ord lexicographic ordering over declared fields/variants; implies Eq
3032+
Hash structural hash; with Eq this satisfies protocol Hashable
3033+
JsonEncode compiler-owned serialization marker (lowers to audited serde)
3034+
JsonDecode compiler-owned deserialization marker (lowers to audited serde)
3035+
Schema review/schema marker; introduces no executable behavior
3036+
ReviewSchema review/schema marker; introduces no executable behavior
3037+
```
3038+
3039+
The value protocols and their contracts are:
3040+
3041+
```rust
3042+
protocol Clone { fn clone(self: read Self) -> fresh Self }
3043+
protocol Eq { fn equals(self: read Self, other: read Self) -> Bool }
3044+
protocol Ord { fn compare(self: read Self, other: read Self) -> Int }
3045+
protocol Hashable { fn hash_value(self: read Self) -> Int }
3046+
```
3047+
3048+
A managed `struct` or `sum` satisfies one of these by listing it in
3049+
`derives(...)`; the compiler expands the derive into a structural implementation
3050+
over every field. The builtin value scalars satisfy these protocols directly
3051+
with no derive, with one deliberate exception: every value scalar (`Int`,
3052+
`Float`, `String`, `Bool`, `Char`, …) is `Clone`, but `Float`/`Float32`/`Float64`
3053+
are **not** `Eq`, `Ord`, or `Hashable` (IEEE-754 equality is not total), so a
3054+
`Float` may not be a `Map`/`Set` key and a `derives(Eq)` over a `Float` field is
3055+
rejected.
3056+
3057+
```rust
3058+
struct User derives(Eq, Hash, Clone) {
3059+
id: Int
3060+
name: String
3061+
}
3062+
```
3063+
3064+
Rules that hold for compiler-owned derives:
3065+
3066+
```text
3067+
closed set only the names listed above are accepted; any other name is
3068+
rejected with an "unsupported derive" diagnostic.
3069+
field-checked every field of a derived type must itself support the derive.
3070+
A derive over a non-supporting field (e.g. `Eq` over a `Float`
3071+
field) is rejected with an RSScript diagnostic before lowering,
3072+
not as a leaked rustc trait-bound error.
3073+
Ord implies Eq `derives(Ord)` also provides `Eq`; `Hashable` requires
3074+
`derives(Hash)` together with `Eq` (so `derives(Eq, Hash)` or
3075+
`derives(Ord, Hash)`), enforcing "equal values hash equal".
3076+
resources a `resource` supports only `Debug`, `Schema`, and `ReviewSchema`.
3077+
Value derives (`Clone`, `Eq`, `Ord`, `Hash`, `JsonEncode`,
3078+
`JsonDecode`) on a resource are rejected: they would copy or
3079+
compare a move-only RAII value.
3080+
pure contract these protocols introduce no mutation, retention, native, or
3081+
unsafe boundary, and add no capability gate.
3082+
```
3083+
3084+
`Clone.clone<T>(self: read x)` copies a value out of a shared (`read`) borrow
3085+
into a `fresh` owned value. This closes the "clone gap" where a `read` field or
3086+
`read` parameter cannot otherwise be moved into a constructor or a collection;
3087+
`String.clone` and the per-type `.clone()` synthesized for any `derives(Clone)`
3088+
type are the same managed-value copy.
3089+
3090+
`Hashable` is the bound on `Map<K, V>` keys and `Set<K>` elements (§18.2): a
3091+
non-hashable key is rejected in RSScript with a `derives(Eq, Hash)` suggestion
3092+
instead of leaking a trait-bound error from the Rust backend.
3093+
29893094
#### 14.6.1 Receiver-call shorthand
29903095

29913096
A **receiver-call expression** is a syntactic shorthand for a qualified function
@@ -3764,6 +3869,7 @@ Unit
37643869
Bool
37653870
Int
37663871
Float
3872+
Math
37673873
String
37683874
Bytes
37693875
Buffer
@@ -3800,6 +3906,7 @@ Span
38003906
Log
38013907
Test
38023908
Assert
3909+
Clone / Eq / Ord / Hashable (compiler-owned value protocols, §14.6)
38033910
```
38043911

38053912
The prototype ships these signatures as ordinary bundled `.rssi` files under
@@ -4039,9 +4146,60 @@ pub fn Json.array_fold<T: Struct>(
40394146
) -> Result<fresh T, JsonError>
40404147
```
40414148

4149+
The scalar conversion and `Float` inspection surface covers `Int`/`Float`/`String`
4150+
interchange and floating-point classification. All are `pure`:
4151+
4152+
```rust
4153+
pub fn Int.to_string(value: read Int) -> fresh String
4154+
pub fn Int.to_float(value: read Int) -> Float
4155+
4156+
pub fn Float.to_string(value: read Float) -> fresh String
4157+
pub fn Float.is_nan(value: read Float) -> Bool
4158+
pub fn Float.is_finite(value: read Float) -> Bool
4159+
pub fn Float.is_infinite(value: read Float) -> Bool
4160+
4161+
pub fn String.from_float(value: Float) -> fresh String
4162+
pub fn String.parse_float(value: read String) -> Option<Float>
4163+
```
4164+
4165+
The `Math` core surface provides `Int` and `Float` numeric intrinsics. They are
4166+
pure value functions with no capability gate, lowering to ordinary Rust `f64`/`i64`
4167+
operations in the backend (and to direct VM opcodes under the register VM):
4168+
4169+
```rust
4170+
pub fn Math.abs(value: Int) -> Int
4171+
pub fn Math.min(left: Int, right: Int) -> Int
4172+
pub fn Math.max(left: Int, right: Int) -> Int
4173+
pub fn Math.clamp(value: Int, min: Int, max: Int) -> Int
4174+
pub fn Math.pow(base: Int, exponent: Int) -> Int
4175+
4176+
pub fn Math.abs_float(value: Float) -> Float
4177+
pub fn Math.min_float(left: Float, right: Float) -> Float
4178+
pub fn Math.max_float(left: Float, right: Float) -> Float
4179+
pub fn Math.clamp_float(value: Float, min: Float, max: Float) -> Float
4180+
pub fn Math.pow_float(base: Float, exponent: Float) -> Float
4181+
4182+
pub fn Math.exp(value: Float) -> Float
4183+
pub fn Math.exp2(value: Float) -> Float
4184+
pub fn Math.log(value: Float) -> Float
4185+
pub fn Math.log2(value: Float) -> Float
4186+
pub fn Math.sin(value: Float) -> Float
4187+
pub fn Math.cos(value: Float) -> Float
4188+
pub fn Math.tanh(value: Float) -> Float
4189+
4190+
pub fn Math.sqrt(value: Float) -> Float
4191+
pub fn Math.trunc_float(value: Float) -> Float
4192+
pub fn Math.floor(value: Float) -> Int
4193+
pub fn Math.ceil(value: Float) -> Int
4194+
pub fn Math.round(value: Float) -> Int
4195+
```
4196+
40424197
The minimum `Path`, `Map`, and `Set` core surfaces cover package-manager-style
4043-
path inspection and ordinary keyed/indexed working sets. `Map` and `Set` retain
4044-
inserted non-Copy values by contract:
4198+
path inspection and ordinary keyed/indexed working sets. A `Map<K, V>` key and a
4199+
`Set<K>` element must satisfy the compiler-owned `Hashable` protocol (§14.6):
4200+
a builtin scalar key, or a managed `struct`/`sum` that `derives(Eq, Hash)`. A
4201+
non-hashable key is rejected in RSScript, not by the Rust backend. `Map` and
4202+
`Set` retain inserted non-Copy values by contract:
40454203

40464204
```rust
40474205
pub fn Path.from_string(value: read String) -> fresh Path
@@ -4180,6 +4338,136 @@ Error composition in RSScript is always explicit at the call site. Unlike Rust's
41804338
visible `map_error` call that a reviewer can audit. The `?` operator propagates
41814339
errors within the same error type; crossing error types requires explicit mapping.
41824340

4341+
The remaining bundled facades are review-visible library boundaries. Facades that
4342+
touch the filesystem, time, environment, subprocess, randomness, encoding/regex
4343+
engines, or telemetry are `native` (the effect is in every signature); pure value
4344+
facades such as `Url` parsing and `Csv` row parsing are not. The signatures below
4345+
are representative core surfaces, not the full bundled `.rssi`; the authoritative
4346+
contracts live under `stdlib/`.
4347+
4348+
Filesystem — `File` is a `resource` (move-only RAII, §6.4); path-level helpers are
4349+
free functions:
4350+
4351+
```rust
4352+
resource File
4353+
4354+
pub fn File.open(path: read Path) -> Result<File, FileError>
4355+
pub fn File.open_read(path: read Path) -> Result<File, FileError>
4356+
pub fn File.open_write(path: read Path) -> Result<File, FileError>
4357+
pub fn File.read_all_string(file: mut File) -> Result<String, FileError>
4358+
pub fn File.read_into(file: mut File, buffer: mut Buffer) -> Result<Bool, FileError>
4359+
pub fn File.write_string(file: mut File, text: read String) -> Result<Unit, FileError>
4360+
pub fn File.write_buffer(file: mut File, buffer: read Buffer) -> Result<Unit, FileError>
4361+
4362+
pub fn File.exists(path: read Path) -> Bool effects(native)
4363+
pub fn File.read_string(path: read Path) -> Result<String, FileError> effects(native)
4364+
pub fn File.write_string_to_path(path: read Path, text: read String) -> Result<Unit, FileError>
4365+
effects(native)
4366+
pub fn File.write_atomic(path: read Path, text: read String) -> Result<Unit, FileError>
4367+
effects(native)
4368+
pub fn FileError.message(error: read FileError) -> String effects(pure)
4369+
4370+
pub native fn Directory.exists(path: read Path) -> Bool effects(native)
4371+
pub native fn Directory.is_dir(path: read Path) -> Bool effects(native)
4372+
pub native fn Directory.create_all(path: read Path) -> Result<Unit, FileError>
4373+
effects(native)
4374+
pub native fn Directory.list_paths(path: read Path) -> Result<fresh List<Path>, FileError>
4375+
effects(native)
4376+
pub native fn Directory.remove_dir_all(path: read Path) -> Result<Unit, FileError>
4377+
effects(native)
4378+
pub native fn Directory.rename(from: read Path, to: read Path) -> Result<Unit, FileError>
4379+
effects(native)
4380+
```
4381+
4382+
JSON — parsing and typed field/path access return typed `JsonError`; encoding and
4383+
field-builder helpers are `pure`. The full surface adds `field_*`/`at_*` accessors
4384+
and `at_*_or` fallbacks:
4385+
4386+
```rust
4387+
struct JsonValue
4388+
struct JsonLiteral
4389+
4390+
pub fn Json.parse(text: read String) -> Result<fresh JsonValue, JsonError>
4391+
pub fn Json.parse_file(path: read Path) -> Result<fresh JsonValue, JsonError>
4392+
pub fn Json.decode<T: Struct>(value: read JsonValue) -> Result<fresh T, JsonError>
4393+
pub fn Json.decode_text<T: Struct>(text: read String) -> Result<fresh T, JsonError>
4394+
pub fn Json.to_string(value: read JsonValue) -> fresh String effects(pure)
4395+
pub fn Json.field(value: read JsonValue, name: read String) -> Result<fresh JsonValue, JsonError>
4396+
pub fn Json.field_string(value: read JsonValue, name: read String) -> Result<String, JsonError>
4397+
pub fn Json.at(value: read JsonValue, path: read String) -> Result<fresh JsonValue, JsonError>
4398+
pub fn Json.at_int_or(value: read JsonValue, path: read String, fallback: Int) -> Int
4399+
effects(pure)
4400+
```
4401+
4402+
Time, environment, randomness, and subprocess facades are `native`:
4403+
4404+
```rust
4405+
struct Instant
4406+
struct Duration
4407+
4408+
pub native fn Clock.now() -> fresh Instant effects(native)
4409+
pub native fn Clock.system_unix_ms() -> Int effects(native)
4410+
pub native fn Instant.elapsed(start: read Instant) -> fresh Duration effects(native)
4411+
pub native fn Duration.ms(value: Int) -> fresh Duration effects(native)
4412+
pub native fn Duration.as_ms(value: read Duration) -> Int effects(native)
4413+
4414+
pub native fn Env.get(name: read String) -> Option<fresh String> effects(native)
4415+
pub native fn Env.get_or_default(name: read String, default: read String) -> fresh String
4416+
effects(native)
4417+
pub native fn Env.current_dir() -> Result<fresh Path, FileError> effects(native)
4418+
pub native fn Env.home_dir() -> Option<fresh Path> effects(native)
4419+
4420+
pub native fn Uuid.new_v4() -> fresh String effects(native)
4421+
pub native fn Random.int(min: Int, max: Int) -> Int effects(native)
4422+
pub native fn Random.float() -> Float effects(native)
4423+
pub native fn Random.bytes(len: Int) -> fresh Bytes effects(native)
4424+
4425+
pub native fn Process.run_stdout(command: read String, args: read List<String>)
4426+
-> Result<String, String> effects(native)
4427+
pub native fn Process.run(command: read String, args: read List<String>)
4428+
-> Result<fresh ProcessOutput, String> effects(native)
4429+
pub native fn Process.run_timeout(
4430+
command: read String, args: read List<String>, timeout_ms: Int,
4431+
) -> Result<fresh ProcessOutput, String> effects(native)
4432+
```
4433+
4434+
Encoding, regex, and logging facades. `Base64`/`Hex`/`Url` component encoding and
4435+
`Regex` are `native`; `Log` emission is a `native` telemetry boundary:
4436+
4437+
```rust
4438+
pub native fn Base64.encode(value: read String) -> fresh String effects(native)
4439+
pub native fn Base64.decode(text: read String) -> Result<fresh Bytes, DecodeError>
4440+
effects(native)
4441+
pub native fn Hex.encode(value: read Bytes) -> fresh String effects(native)
4442+
pub native fn Url.encode_component(value: read String) -> fresh String effects(native)
4443+
4444+
pub native fn Regex.compile(pattern: read String) -> Result<fresh Regex, RegexError>
4445+
effects(native)
4446+
pub native fn Regex.is_match(regex: read Regex, value: read String) -> Bool
4447+
effects(native)
4448+
pub native fn Regex.captures(regex: read Regex, value: read String) -> fresh List<String>
4449+
effects(native)
4450+
pub native fn Regex.replace_all(
4451+
regex: read Regex, value: read String, replacement: read String,
4452+
) -> fresh String effects(native)
4453+
4454+
pub fn Log.write(message: read String) -> Unit effects(native)
4455+
pub fn Log.error(message: read String) -> Unit effects(native)
4456+
pub fn Log.write_json(value: read JsonValue) -> Unit effects(native)
4457+
```
4458+
4459+
Pure value facades — `Url` parsing and `Csv` row parsing carry no `native` effect:
4460+
4461+
```rust
4462+
pub fn Url.from_string(value: read String) -> fresh Url
4463+
pub fn Url.to_string(url: read Url) -> fresh String effects(pure)
4464+
4465+
pub fn Csv.open_read(path: read Path) -> Result<File, CsvError>
4466+
pub fn Csv.read_into(file: mut File, buffer: mut RowBuffer) -> Result<Unit, CsvError>
4467+
pub fn Csv.parse_row(buffer: read RowBuffer) -> Result<fresh Row, CsvError>
4468+
pub fn Row.field_string(row: read Row, index: Int) -> Result<String, CsvError>
4469+
```
4470+
41834471
Agent, GPU, model-client, and domain-specific network clients are use-case
41844472
libraries, not language core. The bundled HTTP surface is intentionally only a
41854473
small façade: `stdlib/http/http.rssi` covers handler request/response values, and
@@ -4534,11 +4822,20 @@ K. Explicit error composition (design principle — v0.6 implemented)
45344822
anyhow/eyre-style erased errors (these hide error provenance from review).
45354823
45364824
L. Compiler-owned derives (restricted code generation)
4825+
- Status: the value-semantics derives (`Clone`, `Eq`, `Ord`, `Hash`) and the
4826+
implicit `Debug` are IMPLEMENTED, together with the `Clone`/`Eq`/`Ord`/
4827+
`Hashable` protocols they satisfy (§14.6). `JsonEncode`/`JsonDecode`/
4828+
`Schema`/`ReviewSchema` are accepted derive names with serde/review-marker
4829+
semantics; remaining work here is package-level serialization review facts.
45374830
- RSScript supports a limited set of compiler-owned derive directives on
45384831
structs and sums:
45394832
45404833
struct User derives(Debug, Clone, Eq, Ord, Hash, JsonEncode, JsonDecode, Schema) { ... }
45414834
4835+
- the accepted set is exactly Debug, Clone, Eq, Ord, Hash, JsonEncode,
4836+
JsonDecode, Schema, ReviewSchema; any other name is rejected. Every field of
4837+
a derived type must itself support the derive (checked in RSScript, not by
4838+
the Rust backend). Resources support only Debug, Schema, and ReviewSchema.
45424839
- generated code must produce visible .rssi signatures and review facts.
45434840
- generated code must not introduce hidden control flow, mutation, or
45444841
resource acquisition.

0 commit comments

Comments
 (0)