Skip to content

🎯 feat: add checked as-cast expression - #213

Closed
timfennis wants to merge 7 commits into
masterfrom
feature/as-cast
Closed

🎯 feat: add checked as-cast expression#213
timfennis wants to merge 7 commits into
masterfrom
feature/as-cast

Conversation

@timfennis

@timfennis timfennis commented Sep 1, 2026

Copy link
Copy Markdown
Owner

Context

Review feedback on the explicit-numeric-modes PR asked to remove the typed-container scanning from Value::matches_param, since RFC #191 explicitly decided against a sum(Sequence<Any>) fallback. Without it, values whose static type degrades to Any (e.g. Map.keys returning List<Any>) have no way to reach typed overloads. This PR adds the agreed middle ground as a standalone precursor: an explicit, runtime-checked cast — values.keys as List<Int> — so the element scan only happens where the user wrote it, never inside overload dispatch.

Changes

  • Syntax: new as keyword and a postfix cast level between ^ and unary (Rust-like: a + b as Int casts only b). Casts chain, and the type side reuses the existing TypeExpr parser.
  • Semantics: a cast asserts, it never converts — 5 as Float is an error. Casts between types with no subtype relation ("foo" as Int) are rejected at compile time.
  • Analyser: lowers the annotation, types the expression as the target, and clears requires_check when the value's static type already proves the cast, so provable upcasts compile to nothing. This is a correctness rule, not an optimization: runtime iterators report Iterator<Any> and would falsely fail a redundant re-check.
  • VM: new CheckType(Rc<StaticType>) opcode backed by Value::conforms_to, a deep, non-consuming check. Empty containers conform vacuously ([] as List<Int> succeeds). Kept separate from matches_param, whose refusal to scan stays dispatch policy.
  • Tooling: keyword added to the LSP completions, terminal highlighter, TextMate grammar, and tree-sitter grammar (parser regenerated); manual page under Features.

🤖 Generated with Claude Code

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 971a60d03f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread ndc_vm/src/value/mod.rs Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 276046af45

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread ndc_vm/src/compiler.rs Outdated
Comment thread ndc_vm/src/value/mod.rs

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: affb860f8a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread ndc_vm/src/compiler.rs
timfennis added a commit that referenced this pull request Sep 1, 2026
## Context

The compiler had two stack ABIs: most expressions leave one value, but
`while`, `for` (block body), `let`, named `fn`, and `struct` left
nothing. Which ABI applied was tracked by the syntactic `produces_value`
side-channel, and every consumer of an expression had to guess correctly
or corrupt the stack. On master today:

- `print(while false {})` → `error[vm]: Unable to invoke Int as a
function` — the missing argument shifts the stack so the VM calls a
local as the callee.
- `let y = while false {};` → accepted, `y` silently binds garbage.
- `while true { f(1 + continue); }` → leaks two stack values per
iteration; in a `for` loop the leak lands on top of the iterator and
corrupts iteration.

The `as`-cast PR (#213) kept tripping over the same seam (Codex found
three variants), which prompted fixing the root cause instead of
patching consumers. This is a precursor to that PR.

## Changes

- **One invariant**: `compile_expr` leaves exactly one result value on
the stack, on top of any local slots it declared. Diverging expressions
(`break`/`continue`/`return`) are the only exception — control never
returns to observe a result. The invariant is documented on
`compile_expr`.
- `while`, `for` with a block body, `let`, `struct`, and named `fn`
declarations now push `()` as their result; `Statement` unconditionally
pops; **`produces_value` is deleted**.
- **`break`/`continue` clean up pending operands.** Every `OpCode`
declares its stack effect in `OpCode::stack_delta` (an exhaustive match,
so a new opcode cannot be added without it), and `OptimizerIr::write`
simulates the stack depth from those deltas automatically — compiler
arms carry no manual bookkeeping. `compile_expr` re-anchors the
simulation at every expression boundary (instructions reached only by a
jump, like a loop's exit pop, leave the linear simulation off). Each
`LoopContext` records the depth at loop entry, and `break`/`continue`
pop back down to it before jumping. This gives them the same safety
`return` always had via frame truncation, so expression-position
divergence (`f(1 + break)`, and the existing `return (return 5) + 1`
corpus test) is well-defined instead of corrupting.
- The parser no longer consumes the `;` after a `let` inside
`let_statement` — the shared `expression_or_statement` path consumes it
and wraps the declaration in a `Statement` like every other
`;`-terminated expression (the semicolon is still required).
- Repeated inline unit pushes are consolidated into an `emit_unit`
helper.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
timfennis and others added 5 commits September 1, 2026 17:09
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8455662fde

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread ndc_parser/src/parser.rs Outdated
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0b6a426c9e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread ext/tree-sitter-andy-cpp/grammar.js
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a13948b93d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

} => {
let target = self.lower_type_expr(annotation);
let found = self.analyse_with_expected(value, &target);
if found.is_incompatible_with(&target) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Permit casts across overlapping container hierarchies

This bidirectional subtype test rejects types that overlap even though neither is directly a subtype of the other. For example, let xs: List<Any> = [1]; xs as Sequence<Int> is reported as an impossible cast, although the runtime Sequence conformance branch explicitly scans lists and this value satisfies the target; List<Int> is a common subtype of both declared types. Use an overlap/compatibility check that accounts for container hierarchies so these valid checked casts reach CheckType.

Useful? React with 👍 / 👎.

@timfennis

Copy link
Copy Markdown
Owner Author

Superseded by #215, which combines this with the other branch so the precursor lands first. All review findings from this PR are addressed there.

@timfennis timfennis closed this Sep 2, 2026
timfennis added a commit that referenced this pull request Sep 2, 2026
Replaces #209 and #213.

## What changed

The `as` operator asserts that a value has a type without converting it.
The analyser removes checks it can prove from the operand's static type.
The VM checks the remaining casts at the cast site. Container checks
scan nested elements, stop on cyclic containers, and validate map
defaults because a missing-key lookup inserts the default.

The analyser rejects a cast only when the operand and target types
cannot share a value. `List<Any>` and `Sequence<Int>` can both contain
the same `List<Int>`, so that cast reaches the runtime check.
`StaticType::overlaps` models this rule. `Never` overlaps no type
because it has no values.

Dispatch keeps its existing subtype rules. Users can cast widened values
before calls that need a concrete container type. The analyser adds a
cast hint when a same-arity overload could accept narrower argument
types; unknown names, wrong arity, and disjoint argument types keep the
existing error.

The language grammar, tree-sitter grammar, TextMate grammar, CLI
highlighter, LSP traversal, completion list, and diagnostics all
understand `as`. Both parsers prefer generic arguments when the tokens
form a complete type, and otherwise leave `<`, `>`, `>=`, and `>>` to
the expression parser.

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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