# §11 · Deliberate non-features

> **Status: DRAFT.** Every refusal below is probed and run by CI (`SpecProbeGateTests`). The *reasons* are
> informative; the refusals are normative.

Most specifications list what a language has. This section lists what Osy# **refuses**, and why — because in every
case here the reason is a property of the execution model rather than a matter of taste, and a reader who knows the
reason can predict the next refusal instead of discovering it.

⚠ **These are not "not yet".** Each is a decision. Where a construct might arrive later, this section says so
explicitly; where it cannot, it says that too.

---

## 11.1 `out` and `ref` PARAMETERS

**Normative.** A parameter declaration MUST NOT carry `out` or `ref`.

```osy probe=refuses PARSE_ERROR
int TryIt(string s, out int value) { value = 1; return 0; }
```

**Why, and it is the model rather than the taste.** A parameter passes a value **in**; only the return comes back
out. An Osy# call can **suspend and resume in a different process** (§9), so there is no caller frame guaranteed to
still be waiting for a write-back when the callee finishes. `out` is not merely unidiomatic here — its meaning
depends on a stack frame the execution model does not promise to keep.

⚑ **A declaration cannot arrive later.** Its signature is the thing the durability model forbids, so this half is
excluded by §9 rather than awaiting demand. Recorded as an owner decision, 2026-08-26.

### 11.1a ⛔ CORRECTION — a `TryParse` CALL SITE compiles, and this section said it could not

**Normative.** `TryTTT(…, out var x)` on a stdlib parser IS accepted.

```osy probe=accepts
decimal Parsed(string s) {
  if (decimal.TryParse(s, out var v)) { return v; }
  return 0m;
}
```

Until 2026-08-29 this section asserted three things that are now false, and they are written out rather than
edited away because a spec is read as authority:

| it said | the truth |
|---|---|
| "A call site MUST NOT pass one" | a stdlib `TryParse` call site may |
| "**there is no `TryParse`**" | there is |
| the `Convert.ToInt` / `int.Parse` table was "the two real forms" | there are three |

⚠ **AND MY OWN GATE PASSED THROUGH THE CHANGE.** `SpecProbeGateTests` is built to fail in both directions —
including on "a refusal the language no longer makes", which the plan calls the more dangerous kind of stale
text. It did not fire, because **the probe was narrower than the claim**: the claim covered a declaration AND a
call site; the probe only ever exercised a declaration. A gate is only as wide as the narrowest thing it
actually runs, and a normative sentence with no probe under it is prose.

⚑ **The lesson for the rest of this document, and it is a rule now:** every clause of a normative sentence needs
its own probe. Two clauses joined by "and" are two claims, and pinning one of them proves nothing about the
other.

### 11.1b How it works, given `out` is refused

**`out` is never accepted — the call is REWRITTEN before resolution.** `decimal.TryParse(s, out var v)` becomes,
hoisted ahead of its statement:

```
decimal? v = null; try { v = decimal.Parse(s); } catch { }
```

with `v != null` left in the call's place. So no `out` reaches the resolved tree: there is no write-back, no
caller frame to survive a suspension, and nothing for §9 to forbid. **The nullable local is the did-it-work
channel** — the shape Osy# already uses everywhere — and `TryParse` is C# spelling the compiler translates into
it.

*Informative — why the two halves differ.* A DECLARATION is a signature the author writes and the durability
model cannot honour. A CALL SITE is a shape the compiler owns end to end, so it can be rewritten out of
existence. The durability argument bites on the first and has nothing to say about the second.

⚑ **And it declines by POSITION, which is the tell that this is a rewrite rather than support.** The hoist needs
somewhere to go, so a loop CONDITION or a lambda BODY is refused — there the parse would have to happen per
iteration or per element rather than once before. The diagnostic prints the whole mechanism and the remedy for
each position, which is why this section can describe it without reading the resolver.

---

## 11.2 `await` on an ordinary call

**Normative.** `await` MUST NOT be applied to an ordinary function call.

```osy probe=refuses RESOLVE_ERROR
int Inner() { return 1; }
int Outer() { var v = await Inner(); return v; }
```

**Why.** Effects run **in place**. There is no async/await colouring in Osy#, because there is nothing for it to
distinguish: a call that performs I/O and a call that does not are written and read the same way, and the runtime
decides how to execute them.

**The one exception is `await Workflow.Run(...)`** — a Saga-style durable wait on a *child workflow* (§9). That is
not the C# meaning of `await`. It does not yield a thread and resume shortly after; it **parks the run durably**,
possibly for days, and resumes it in whatever process picks it up. The keyword is reused because the reading is
right — "continue when that finishes" — but the mechanism is the durable engine, not a task scheduler.

*Informative.* This is why §11.1 and §11.2 belong together: both follow from a call not owning a stack frame for
its own lifetime.

---

## 11.3 `Now`

**Normative.** The identifier `Now` MUST NOT resolve. It is not merely deprecated — the name is absent from
app-source resolution entirely, and answers exactly what any undefined name answers.

```osy probe=refuses RESOLVE_ERROR
DateTime When() { return Now.Utc; }
```

**Write `DateTime.UtcNow`.** The compiler lowers it to the ambient clock, so it is testable (`TestClock.*`) and
correct inside a `live var`, a render slot, and a durable body alike.

⚑ **Why the name is hidden rather than deprecated.** A retirement *message* was tried first and made things worse:
`Osysharp.Now` is a real class in the model, so with the refusal removed `Now.Utc` resolved far enough to answer
*"'Osysharp.Now.Utc' is an instance member"* — which still tells the reader the class is there. Worse, a typo'd
`Nowt` produced *"Did you mean 'Osysharp.Now'?"*: the compiler proactively recommending a name it would then refuse.
Hiding the name from resolution is what makes the removal complete.

---

## 11.4 A retired spelling is deleted, not aliased

**Normative.** A renamed surface MUST NOT keep its old spelling as an alias.

| retired | current |
|---|---|
| `[Display]` | `[Label]` |
| `Modes(light:, dark:)` | `Modes.Of(light:, dark:)` |
| `<hash>.migration.osy` | `<hash>.migration` |

*Informative.* Osy# has no released versions and no deployed programs outside this repository, so an alias would
preserve compatibility with nothing while doubling the surface a reader must learn and a diagnostic must consider.
The rule is expected to change when the language ships a stable release; §13 will say how.

---

## 11.5 What is NOT in this section

Two things are frequently assumed to be deliberate refusals and are not:

- **`[Server]` / `[Client]` annotations.** These exist. They are a forcing hatch, and reaching for one is almost
  always the wrong instinct — the side is inferred (§6). They were de-advertised across 174 files on 2026-08-27
  precisely because their presence in examples taught readers to write them. Absent from your source is the
  normal case, not a refusal.
- **A scalar array on an entity** (`string[] Tags;`). Refused today, with a diagnostic naming the two real options
  — but this is a **gap**, not a decision, and it is tracked as such. **UNVERIFIED** here: no probe written.

---

**Next:** §6 The execution-side model and §9 Durability semantics — the two rules §11.1 and §11.2 both
depend on. **Both drafted.**
