Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion compiler/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ pub enum PathSeg {
pub struct Target {
pub name: String,
pub path: Vec<PathSeg>,
/// `use name := …` (RG-7, O-4). `None`: not written by this form (an `optimistic` target).
/// `use name := …` (RG-7, O-4). `None`: not written by this form (a `guess` target).
pub ctx: Option<bool>,
}
impl Target {
Expand Down
8 changes: 4 additions & 4 deletions compiler/src/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2262,9 +2262,9 @@ impl<'a> Checker<'a> {
for f in run_findings(stmts, action, bound, inferred) {
let n = &f.name;
match f.code {
Code::HeldWrite => self.err("HELD_WRITE", "AC-1c", format!("{n} := … comes before an `await` in a body that is an action because it writes and awaits (AC-1b), so it is held until the body ends (AC-1): for state that shows while the work runs, `optimistic {n} := …` (a source with a question, ST-5c); for a reset that lands now, `onFlush(() => {{ {n} := … }})` (EF-8); to hold it on purpose, declare the action (`action name() {{ … }}`); or write it after the `await`")),
Code::HeldWrite => self.err("HELD_WRITE", "AC-1c", format!("{n} := … comes before an `await` in a body that is an action because it writes and awaits (AC-1b), so it is held until the body ends (AC-1): for state that shows while the work runs, `guess {n} := …` (a source with a question, ST-5c); for a reset that lands now, `onFlush(() => {{ {n} := … }})` (EF-8); to hold it on purpose, declare the action (`action name() {{ … }}`); or write it after the `await`")),
Code::StaleOwnRead => self.err("STALE_OWN_READ", "AC-1d", format!("{n} is read after this change wrote it: the read sees the committed value, not the write (W-4) — keep the value you wrote in a local (`let v = …; {n} := v`) and read that, or write functionally (`{n} := v => …`, W-6)")),
Code::StaleWriteBack => self.err("STALE_WRITE_BACK", "AC-1e", format!("{n} := … after an `await` is computed from {n} as it was read before that `await`: whatever changed {n} in between (another action, a reload) is overwritten — write functionally (`{n} := v => …`, W-6), which applies to {n} as it stands at the write, or guess it (`optimistic`, AC-3) and let the answer replace the guess")),
Code::StaleWriteBack => self.err("STALE_WRITE_BACK", "AC-1e", format!("{n} := … after an `await` is computed from {n} as it was read before that `await`: whatever changed {n} in between (another action, a reload) is overwritten — write functionally (`{n} := v => …`, W-6), which applies to {n} as it stands at the write, or guess it (`guess`, AC-3) and let the answer replace the guess")),
}
}
}
Expand Down Expand Up @@ -2351,12 +2351,12 @@ impl<'a> Checker<'a> {
}
if op == "optimistic" {
if !ctx.in_action {
self.err("OPTIMISTIC_OUTSIDE_HOLD", "AC-3", "optimistic outside an action: a guess needs a hold to live in — an apply block is never an action (its return is the cleanup, EF-5): declare one, `action save(v) { optimistic … }`, and call it from the block".into());
self.err("GUESS_OUTSIDE_HOLD", "AC-3", "a guess outside an action: it needs a hold to live in — an apply block is never an action (its return is the cleanup, EF-5): declare one, `action save(v) { guess … }`, and call it from the block".into());
}
if let Some(t) = tsym {
if self.syms[t].kind == SymKind::Source && !self.syms[t].question {
self.err(
"OPTIMISTIC_NO_QUESTION",
"GUESS_NO_QUESTION",
"AC-3",
format!("{} is declared with := — a guess needs a question to confirm it (source {} = …, ST-5c)", target.name, target.name),
);
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/infer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ pub enum Trigger {
pub fn trigger_text(t: Trigger) -> &'static str {
match t {
Trigger::WriteAwait => "it writes and awaits",
Trigger::Optimistic => "it has `optimistic`",
Trigger::Optimistic => "it has `guess`",
Trigger::Affects => "it has `affects`",
}
}
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ re!(LINE_COMMENT, r"//.*$");

static KEYWORDS: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
[
"source", "effect", "component", "ref", "await", "latest", "loading", "errored", "fallback", "on", "deferred", "action", "optimistic", "refresh", "affects", "if", "else", "pre", "peek", "fn", "let", "for", "in", "true", "false", "undefined",
"source", "effect", "component", "ref", "await", "latest", "loading", "errored", "fallback", "on", "deferred", "action", "guess", "refresh", "affects", "if", "else", "pre", "peek", "fn", "let", "for", "in", "true", "false", "undefined",
"null", "provide", "context", "use", "import", "export", "from", "pure", "return", "type", "interface", "try", "catch", "finally", "throw", "portal",
]
.into_iter()
Expand Down Expand Up @@ -925,7 +925,7 @@ fn parse_path(c: &mut Cursor) -> R<Vec<PathSeg>> {
fn parse_stmt(c: &mut Cursor) -> R<Stmt> {
c.ws(true);
let start = c.i;
if c.keyword("optimistic") {
if c.keyword("guess") {
c.ws(true);
let name = c.ident_or("target")?;
let path = parse_path(c)?;
Expand Down
2 changes: 1 addition & 1 deletion eval/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ predicted every frame right; the briefs here are stricter.
- `tracker`: `assignToMe` cleared its error with a plain write (`assignError := ""`) inside an action that awaits,
and a plain write in an async action is held until the action ends (AC-1), so the old error stayed up until the
server answered. The fix is the `settings` app's own idiom: `assignError` a question source,
`optimistic assignError := ""`.
`guess assignError := ""` (since 2026-11-11 the references clear errors with `onFlush`, EF-8).

## Layout

Expand Down
6 changes: 3 additions & 3 deletions eval/reference/chain1/rx/comments.rx
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@ export component Comments(props: { id: number; user: string }) {
source comments = await fetchComments(props.id)
source sending: { id: number; author: string; text: string }[] = []
shownSending = sending.filter(s => s.id == props.id)
source postError: string | null = null
source postError: string | null := null
source commentInput: string := ""

// a post error belongs to the issue it was made on: selecting another clears it
effect (props.id) { postError := null }

action postComment(id: number, author: string, text: string) {
optimistic postError := null
onFlush(() => { postError := null })
try {
optimistic sending := [...sending, { id: id, author: author, text: text }]
guess sending := [...sending, { id: id, author: author, text: text }]
await addComment(id, author, text)
refresh comments
} catch (e) {
Expand Down
20 changes: 10 additions & 10 deletions eval/reference/chain1/rx/issues.rx
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ export component IssuesPage() {
source issues key id = await fetchIssues(filter)
source detail = selected == null ? null : found(await fetchIssue(selected), selected)
source titleInput: string := ""
source assignError: string = ""
source renameError: string = ""
source closeError: string = ""
source assignError: string := ""
source renameError: string := ""
source closeError: string := ""

action assignTo(id: number, who: string) {
optimistic assignError := ""
onFlush(() => { assignError := "" })
try {
let i = issues.findIndex(t => t.id == id)
optimistic issues[i].assignee := who
guess issues[i].assignee := who
await assign(id, who)
refresh issues
} catch (e) {
Expand All @@ -33,14 +33,14 @@ export component IssuesPage() {
}

action renameTo(id: number, title: string) {
optimistic renameError := ""
onFlush(() => { renameError := "" })
try {
let i = issues.findIndex(t => t.id == id)
if (i >= 0) {
optimistic issues[i].title := title
guess issues[i].title := title
}
if (detail != null && detail.id == id) {
optimistic detail.title := title
guess detail.title := title
}
await renameIssue(id, title)
refresh issues
Expand All @@ -51,9 +51,9 @@ export component IssuesPage() {
}

action closeOne(id: number) {
optimistic closeError := ""
onFlush(() => { closeError := "" })
try {
optimistic issues := issues.filter(t => t.id != id)
guess issues := issues.filter(t => t.id != id)
await closeIssue(id)
refresh issues
} catch (e) {
Expand Down
10 changes: 5 additions & 5 deletions eval/reference/chain1/rx/labels.rx
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,16 @@ export component Labels(props: { id: number }) {
...labels.filter(l => !removing.some(r => r.id == props.id && r.label == l)),
...adding.filter(a => a.id == props.id).map(a => a.label),
]
source labelError: string = ""
source labelError: string := ""
source labelInput: string := ""

// a label error belongs to the issue it was made on: selecting another clears it
effect (props.id) { labelError := "" }

action add(id: number, label: string) {
optimistic labelError := ""
onFlush(() => { labelError := "" })
try {
optimistic adding := [...adding, { id: id, label: label }]
guess adding := [...adding, { id: id, label: label }]
await addLabel(id, label)
refresh labels
} catch (e) {
Expand All @@ -30,9 +30,9 @@ export component Labels(props: { id: number }) {
}

action remove(id: number, label: string) {
optimistic labelError := ""
onFlush(() => { labelError := "" })
try {
optimistic removing := [...removing, { id: id, label: label }]
guess removing := [...removing, { id: id, label: label }]
await removeLabel(id, label)
refresh labels
} catch (e) {
Expand Down
6 changes: 3 additions & 3 deletions eval/reference/chain1/rx/settings.rx
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ fn validName(name: string): string =>

export component SettingsPage() {
source settings: Settings = await loadSettings()
source saveError: string = ""
source saveError: string := ""

action save(next: Settings) {
optimistic settings := next
optimistic saveError := ""
guess settings := next
onFlush(() => { saveError := "" })
affects settings
try {
let saved = await saveSettings(next)
Expand Down
6 changes: 3 additions & 3 deletions eval/reference/chain2/rx/comments.rx
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@ export component Comments(props: { id: number; user: string }) {
source comments = await fetchComments(props.id)
source sending: { id: number; author: string; text: string }[] = []
shownSending = sending.filter(s => s.id == props.id)
source postError: string | null = null
source postError: string | null := null
source commentInput: string := ""

// a post error belongs to the issue it was made on: selecting another clears it
effect (props.id) { postError := null }

action postComment(id: number, author: string, text: string) {
optimistic postError := null
onFlush(() => { postError := null })
try {
optimistic sending := [...sending, { id: id, author: author, text: text }]
guess sending := [...sending, { id: id, author: author, text: text }]
await addComment(id, author, text)
refresh comments
} catch (e) {
Expand Down
20 changes: 10 additions & 10 deletions eval/reference/chain2/rx/issues.rx
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,16 @@ export component IssuesPage(props: { onOpenCount: (n: number) => void }) {
source issues key id = await fetchIssues(filter)
source detail = selected == null ? null : found(await fetchIssue(selected), selected)
source titleInput: string := ""
source assignError: string = ""
source renameError: string = ""
source closeError: string = ""
source assignError: string := ""
source renameError: string := ""
source closeError: string := ""


action assignTo(id: number, who: string) {
optimistic assignError := ""
onFlush(() => { assignError := "" })
try {
let i = issues.findIndex(t => t.id == id)
optimistic issues[i].assignee := who
guess issues[i].assignee := who
await assign(id, who)
refresh issues
} catch (e) {
Expand All @@ -42,14 +42,14 @@ export component IssuesPage(props: { onOpenCount: (n: number) => void }) {
}

action renameTo(id: number, title: string) {
optimistic renameError := ""
onFlush(() => { renameError := "" })
try {
let i = issues.findIndex(t => t.id == id)
if (i >= 0) {
optimistic issues[i].title := title
guess issues[i].title := title
}
if (detail != null && detail.id == id) {
optimistic detail.title := title
guess detail.title := title
}
await renameIssue(id, title)
refresh issues
Expand All @@ -60,9 +60,9 @@ export component IssuesPage(props: { onOpenCount: (n: number) => void }) {
}

action closeOne(id: number) {
optimistic closeError := ""
onFlush(() => { closeError := "" })
try {
optimistic issues := issues.filter(t => t.id != id)
guess issues := issues.filter(t => t.id != id)
await closeIssue(id)
refresh issues
} catch (e) {
Expand Down
10 changes: 5 additions & 5 deletions eval/reference/chain2/rx/labels.rx
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,16 @@ export component Labels(props: { id: number }) {
...labels.filter(l => !removing.some(r => r.id == props.id && r.label == l)),
...adding.filter(a => a.id == props.id).map(a => a.label),
]
source labelError: string = ""
source labelError: string := ""
source labelInput: string := ""

// a label error belongs to the issue it was made on: selecting another clears it
effect (props.id) { labelError := "" }

action add(id: number, label: string) {
optimistic labelError := ""
onFlush(() => { labelError := "" })
try {
optimistic adding := [...adding, { id: id, label: label }]
guess adding := [...adding, { id: id, label: label }]
await addLabel(id, label)
refresh labels
} catch (e) {
Expand All @@ -30,9 +30,9 @@ export component Labels(props: { id: number }) {
}

action remove(id: number, label: string) {
optimistic labelError := ""
onFlush(() => { labelError := "" })
try {
optimistic removing := [...removing, { id: id, label: label }]
guess removing := [...removing, { id: id, label: label }]
await removeLabel(id, label)
refresh labels
} catch (e) {
Expand Down
6 changes: 3 additions & 3 deletions eval/reference/chain2/rx/settings.rx
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ fn validName(name: string): string =>

export component SettingsPage() {
source settings: Settings = await loadSettings()
source saveError: string = ""
source saveError: string := ""

action save(next: Settings) {
optimistic settings := next
optimistic saveError := ""
guess settings := next
onFlush(() => { saveError := "" })
affects settings
try {
let saved = await saveSettings(next)
Expand Down
6 changes: 3 additions & 3 deletions eval/reference/chain3/rx/comments.rx
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@ export component Comments(props: { id: number; user: string }) {
source comments = await fetchComments(props.id)
source sending: { id: number; author: string; text: string }[] = []
shownSending = sending.filter(s => s.id == props.id)
source postError: string | null = null
source postError: string | null := null
source commentInput: string := ""

// a post error belongs to the issue it was made on: selecting another clears it
effect (props.id) { postError := null }

action postComment(id: number, author: string, text: string) {
optimistic postError := null
onFlush(() => { postError := null })
try {
optimistic sending := [...sending, { id: id, author: author, text: text }]
guess sending := [...sending, { id: id, author: author, text: text }]
await addComment(id, author, text)
refresh comments
} catch (e) {
Expand Down
20 changes: 10 additions & 10 deletions eval/reference/chain3/rx/issues.rx
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,16 @@ export component IssuesPage(props: { onOpenCount: (n: number) => void; onOpenHel
source issues key id = await fetchIssues(filter)
source detail = selected == null ? null : found(await fetchIssue(selected), selected)
source titleInput: string := ""
source assignError: string = ""
source renameError: string = ""
source closeError: string = ""
source assignError: string := ""
source renameError: string := ""
source closeError: string := ""


action assignTo(id: number, who: string) {
optimistic assignError := ""
onFlush(() => { assignError := "" })
try {
let i = issues.findIndex(t => t.id == id)
optimistic issues[i].assignee := who
guess issues[i].assignee := who
await assign(id, who)
refresh issues
} catch (e) {
Expand All @@ -43,14 +43,14 @@ export component IssuesPage(props: { onOpenCount: (n: number) => void; onOpenHel
}

action renameTo(id: number, title: string) {
optimistic renameError := ""
onFlush(() => { renameError := "" })
try {
let i = issues.findIndex(t => t.id == id)
if (i >= 0) {
optimistic issues[i].title := title
guess issues[i].title := title
}
if (detail != null && detail.id == id) {
optimistic detail.title := title
guess detail.title := title
}
await renameIssue(id, title)
refresh issues
Expand All @@ -61,9 +61,9 @@ export component IssuesPage(props: { onOpenCount: (n: number) => void; onOpenHel
}

action closeOne(id: number) {
optimistic closeError := ""
onFlush(() => { closeError := "" })
try {
optimistic issues := issues.filter(t => t.id != id)
guess issues := issues.filter(t => t.id != id)
await closeIssue(id)
refresh issues
} catch (e) {
Expand Down
Loading
Loading