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
28 changes: 8 additions & 20 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# Contributing to Incan

Thank you for your interest in contributing to the Incan programming language! This document provides
guidelines for contributing to the project.
Thank you for your interest in contributing to the Incan programming language! This document provides guidelines for contributing to the project.

## Start Here (Docs)

Expand Down Expand Up @@ -33,8 +32,7 @@ guidelines for contributing to the project.

## Project Structure

The compiler is organized into a **frontend** (lex/parse/typecheck), a **backend** (lowering + Rust
emission), plus CLI and tooling.
The compiler is organized into a **frontend** (lex/parse/typecheck), a **backend** (lowering + Rust emission), plus CLI and tooling.

For an up-to-date module map, see:

Expand All @@ -56,10 +54,8 @@ The workspace uses **Cargo workspace package metadata**, so you only bump versio

Notes:

- The compiler exposes the version as `incan::version::INCAN_VERSION`, backed by
`env!("CARGO_PKG_VERSION")`, so it updates automatically with the Cargo version.
- Codegen snapshots are version-agnostic (they normalize the codegen header to
`v<INCAN_VERSION>`), so version bumps should not churn snapshot files.
- The compiler exposes the version as `incan::version::INCAN_VERSION`, backed by `env!("CARGO_PKG_VERSION")`, so it updates automatically with the Cargo version.
- Codegen snapshots are version-agnostic (they normalize the codegen header to `v<INCAN_VERSION>`), so version bumps should not churn snapshot files.

### Code Generation Overview

Expand Down Expand Up @@ -88,9 +84,7 @@ Key files:

### Type Conversions System

The `conversions` module (`src/backend/ir/conversions.rs`) provides centralized handling of type
conversions and borrow checking during Rust codegen. This is where we handle the mismatch between
Incan's simple `str` type and Rust's `&str` vs `String` split for example.
The `conversions` module (`src/backend/ir/conversions.rs`) provides centralized handling of type conversions and borrow checking during Rust codegen. This is where we handle the mismatch between Incan's simple `str` type and Rust's `&str` vs `String` split for example.

**When to use conversions:**

Expand Down Expand Up @@ -136,8 +130,7 @@ Example:

### Adding a New Expression Type

See [Extending the Language](docs/contributing/extending_language.md) for the up-to-date end-to-end
checklist (lexer → parser/AST → typechecker → lowering → IR → emission).
See [Extending the Language](docs/contributing/extending_language.md) for the up-to-date end-to-end checklist (lexer → parser/AST → typechecker → lowering → IR → emission).

### Running Snapshot Tests

Expand Down Expand Up @@ -183,16 +176,11 @@ From `src/lib.rs`:

### CLI Design

The CLI uses clap with derive macros. Commands return `CliResult<ExitCode>`
instead of calling `process::exit` directly. This makes commands testable.
The CLI uses clap with derive macros. Commands return `CliResult<ExitCode>` instead of calling `process::exit` directly. This makes commands testable.

### Prelude Status

The stdlib surface now compiles through the normal pipeline under `crates/incan_stdlib/stdlib/`.
Source declarations are the primary contract for `std.*` modules, including the prelude-facing
trait definitions. Some behavior is still realized by backend lowering or runtime bridges
(for example derive-backed Rust traits and host-backed stdlib leaves), but the compiler no longer
treats the stdlib as documentation-only stubs.
The stdlib surface now compiles through the normal pipeline under `crates/incan_stdlib/stdlib/`. Source declarations are the primary contract for `std.*` modules, including the prelude-facing trait definitions. Some behavior is still realized by backend lowering or runtime bridges (for example derive-backed Rust traits and host-backed stdlib leaves), but the compiler no longer treats the stdlib as documentation-only stubs.

### Property-Based Testing

Expand Down
13 changes: 0 additions & 13 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 2 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# Incan Programming Language

Incan is a statically typed language for writing clear, high-level application code that compiles to native Rust.
It aims to feel lightweight and expressive while keeping the things that matter in large codebases explicit: types, errors, and mutability.
Incan is a statically typed language for writing clear, high-level application code that compiles to native Rust. It aims to feel lightweight and expressive while keeping the things that matter in large codebases explicit: types, errors, and mutability.

## Positioning

Expand Down Expand Up @@ -96,8 +95,7 @@ make docs-serve

## Performance

Incan compiles to Rust and then to a native binary. Runtime performance can be close to Rust for many workloads,
depending on current codegen and library behavior.
Incan compiles to Rust and then to a native binary. Runtime performance can be close to Rust for many workloads, depending on current codegen and library behavior.

- Benchmarks: `benchmarks/`
- Results: `benchmarks/results/results.md`
Expand Down
24 changes: 6 additions & 18 deletions crates/incan_core/src/bin/generate_lang_reference.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,7 @@ fn write_language_reference(path: &Path) {
let mut out = String::new();
out.push_str("# Incan language reference\n\n");
out.push_str("!!! warning \"Generated file\"\n");
out.push_str(" Do not edit this page by hand.\n");
out.push_str(" If it looks wrong/outdated, regenerate it from source and commit the result.\n");
out.push_str(" Do not edit this page by hand. If it looks wrong/outdated, regenerate it from source and commit the result.\n");
out.push('\n');
out.push_str(" Regenerate with: `cargo run -p incan_core --bin generate_lang_reference`\n\n");

Expand Down Expand Up @@ -152,9 +151,8 @@ fn write_feature_inventory_reference(path: &Path) {
let mut out = String::new();
out.push_str("# Incan feature inventory\n\n");
out.push_str("!!! warning \"Generated file\"\n");
out.push_str(" Do not edit this page by hand.\n");
out.push_str(
" If it looks wrong/outdated, update `crates/incan_core/src/lang/features.rs` and regenerate it.\n",
" Do not edit this page by hand. If it looks wrong/outdated, update `crates/incan_core/src/lang/features.rs` and regenerate it.\n",
);
out.push('\n');
out.push_str(" Regenerate with: `cargo run -p incan_core --bin generate_lang_reference`\n\n");
Expand Down Expand Up @@ -483,9 +481,7 @@ fn render_decorators_section(out: &mut String) {
start_section(out, "## Decorators");

out.push_str(
r#"User-defined decorators are valid on top-level `def` / `async def` declarations and instance methods. A
decorator is an ordinary callable value that receives the decorated function value and returns the binding that should
replace it:
r#"User-defined decorators are valid on top-level `def` / `async def` declarations and instance methods. A decorator is an ordinary callable value that receives the decorated function value and returns the binding that should replace it:

```incan
def parse(value: int) -> int:
Expand All @@ -502,19 +498,11 @@ def main() -> None:
result = label(1) # int
```

Stacked decorators apply bottom-up, matching Python's declaration model: the decorator closest to `def` receives the
original function value first, and the outer decorators receive each previous result. Decorator factories such as
`@logged("name")` are checked by first evaluating the factory expression as a callable-producing expression and then
applying the produced decorator to the function value.
Stacked decorators apply bottom-up, matching Python's declaration model: the decorator closest to `def` receives the original function value first, and the outer decorators receive each previous result. Decorator factories such as `@logged("name")` are checked by first evaluating the factory expression as a callable-producing expression and then applying the produced decorator to the function value.

Method decorators receive an unbound callable shape with the receiver first. A decorator on
`def label(self, value: int) -> str` sees `(&Box, int) -> str`; a decorator on
`def bump(mut self, value: int) -> int` sees `(&mut Box, int) -> int`. The wrapper passes the actual receiver borrow
through to the decorated callable, so method decorators do not require cloning the receiver.
Method decorators receive an unbound callable shape with the receiver first. A decorator on `def label(self, value: int) -> str` sees `(&Box, int) -> str`; a decorator on `def bump(mut self, value: int) -> int` sees `(&mut Box, int) -> int`. The wrapper passes the actual receiver borrow through to the decorated callable, so method decorators do not require cloning the receiver.

Class, model, trait, enum, newtype, field, alias, and module decorators remain limited to compiler-owned decorators.
Compiler-owned decorators such as `@derive`, `@route`, `@rust.extern`, `@rust.allow`, `@staticmethod`, `@classmethod`,
and `@requires` keep their existing special behavior.
Class, model, trait, enum, newtype, field, alias, and module decorators remain limited to compiler-owned decorators. Compiler-owned decorators such as `@derive`, `@route`, `@rust.extern`, `@rust.allow`, `@staticmethod`, `@classmethod`, and `@requires` keep their existing special behavior.

"#,
);
Expand Down
2 changes: 1 addition & 1 deletion crates/incan_core/src/lang/surface/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub type SurfaceFnInfo = LangItemInfo<SurfaceFnId>;
pub const SURFACE_FUNCTIONS: &[SurfaceFnInfo] = &[
info(
SurfaceFnId::SleepMs,
// TODO: consider mergeing sleep and sleep_ms or at least moving them together...
// TODO: consider merging sleep and sleep_ms or at least moving them together...
"sleep_ms",
&[],
"Sleep for N milliseconds.",
Expand Down
2 changes: 1 addition & 1 deletion crates/incan_core/src/lang/types/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Builtin type vocabularies.
//!
//! This module defines registries for builtin/blesed type names (and their aliases) that are
//! This module defines registries for builtin/blessed type names (and their aliases) that are
//! recognized by the compiler.
//!
//! ## Notes
Expand Down
1 change: 0 additions & 1 deletion crates/incan_stdlib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ serde_json = { version = "1.0", optional = true }
axum = { version = "0.8", optional = true }
tokio = { version = "1", optional = true, features = ["rt-multi-thread", "macros", "time", "sync", "net"] }
inventory = { version = "0.3", optional = true }
regex = "1.0"
xxhash_rust = { package = "xxhash-rust", version = "0.8", features = ["xxh3"] }

[features]
Expand Down
13 changes: 4 additions & 9 deletions crates/incan_stdlib/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,11 @@ Enables the current Axum-backed host runtime for generated Incan web programs.

### Incan stdlib stubs

The Incan-source stdlib stubs live under `crates/incan_stdlib/stdlib/` (for example
`crates/incan_stdlib/stdlib/testing.incn`).
The Incan-source stdlib stubs live under `crates/incan_stdlib/stdlib/` (for example `crates/incan_stdlib/stdlib/testing.incn`).

These files define the user-facing `std.*` API surface and are parsed by the compiler for signature/validation metadata.

For `std.testing`, this also includes marker semantics metadata consumed by `incan test` discovery/execution.
The Rust runtime in `src/testing.rs` only provides irreducible host boundaries declared via `@rust.extern`.
The language `assert` statement is always available without importing `std.testing`; the stdlib assertion helpers mirror that behavior for call-style assertions and unwrap-style helpers.
For `std.testing`, this also includes marker semantics metadata consumed by `incan test` discovery/execution. The Rust runtime in `src/testing.rs` only provides irreducible host boundaries declared via `@rust.extern`. The language `assert` statement is always available without importing `std.testing`; the stdlib assertion helpers mirror that behavior for call-style assertions and unwrap-style helpers.

### Transitional web runtime

Expand All @@ -97,8 +94,7 @@ The `__private` module re-exports host crates needed by generated Rust. It is to

### Why a Separate Crate?

Generated Incan programs need access to these traits and utilities, but shouldn't depend on the entire compiler.
This crate provides a minimal, stable API surface for compiled code.
Generated Incan programs need access to these traits and utilities, but shouldn't depend on the entire compiler. This crate provides a minimal, stable API surface for compiled code.

### Relationship with `incan_derive`

Expand All @@ -124,8 +120,7 @@ pub struct User {

## Version Compatibility

This crate follows the Incan compiler version. For example, when you compile with `incan v0.1.0`, the generated code
depends on `incan_stdlib v0.1.0`.
This crate follows the Incan compiler version. For example, when you compile with `incan v0.1.0`, the generated code depends on `incan_stdlib v0.1.0`.

## License

Expand Down
2 changes: 1 addition & 1 deletion crates/incan_stdlib/src/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
//! are still settling. Public items here should be treated as generated-code support unless the Incan stdlib stubs and
//! language docs explicitly expose them.
// FIXME: this module need to be rewritten in incan once the appropriate RFCs are implemented
// FIXME: this module needs to be rewritten in Incan once the appropriate RFCs are implemented.

use std::net::SocketAddr;

Expand Down
33 changes: 9 additions & 24 deletions crates/incan_stdlib/stdlib/collections.incn
Original file line number Diff line number Diff line change
Expand Up @@ -874,8 +874,7 @@ pub model Counter[T with (Clone, Eq)]:
count: New non-negative count. A zero count removes the value.
"""
if count < 0:
_runtime_error("Counter counts must be non-negative")
return
return raise_value_error("Counter counts must be non-negative")
index = self.index_of(key)
if count == 0:
if index >= 0:
Expand All @@ -895,8 +894,7 @@ pub model Counter[T with (Clone, Eq)]:
amount: Non-negative amount to add.
"""
if amount < 0:
_runtime_error("Counter.increment requires a non-negative amount")
return
return raise_value_error("Counter.increment requires a non-negative amount")
self.set(key, self.get(key) + amount)

def decrement(mut self, key: T, amount: int = 1) -> None:
Expand All @@ -908,8 +906,7 @@ pub model Counter[T with (Clone, Eq)]:
amount: Non-negative amount to subtract.
"""
if amount < 0:
_runtime_error("Counter.decrement requires a non-negative amount")
return
return raise_value_error("Counter.decrement requires a non-negative amount")
current = self.get(key)
if amount >= current:
self.set(key, 0)
Expand Down Expand Up @@ -1221,7 +1218,7 @@ pub model DefaultDict[K with (Clone, Eq), V with Clone]:
None => pass
match self.default_value:
Some(value) => return value
None => return _missing_value[V]()
None => return raise_key_error("DefaultDict key is not present")

def index_of(self, key: K) -> int:
return _entry_index(self.entries, key)
Expand Down Expand Up @@ -1306,7 +1303,7 @@ pub model OrderedDict[K with (Clone, Eq), V with Clone]:
"""
index = self.index_of(key)
if index < 0:
return _missing_value[V]()
return raise_key_error("OrderedDict key is not present")
return self.entries[index].value

def __setitem__(mut self, key: K, value: V) -> None:
Expand Down Expand Up @@ -1360,7 +1357,7 @@ pub model OrderedDict[K with (Clone, Eq), V with Clone]:
"""
index = self.index_of(key)
if index < 0:
return _missing_value[V]()
return raise_key_error("OrderedDict key is not present")
value = self.entries[index].value
self.entries.remove(index)
return value
Expand Down Expand Up @@ -1588,7 +1585,7 @@ pub model SortedDict[K with (Clone, Ord), V with Clone]:
"""
index = self.index_of(key)
if index < 0:
return _missing_value[V]()
return raise_key_error("SortedDict key is not present")
return self.entries[index].value

def __setitem__(mut self, key: K, value: V) -> None:
Expand Down Expand Up @@ -1643,7 +1640,7 @@ pub model SortedDict[K with (Clone, Ord), V with Clone]:
"""
index = self.index_of(key)
if index < 0:
return _missing_value[V]()
return raise_key_error("SortedDict key is not present")
value = self.entries[index].value
self.entries.remove(index)
return value
Expand Down Expand Up @@ -1940,7 +1937,7 @@ pub model ChainMap[K with (Clone, Eq), V with Clone]:
match layer.get(key):
Some(value) => return value
None => pass
return _missing_value[V]()
return raise_key_error("ChainMap key is not present")

def __setitem__(mut self, key: K, value: V) -> None:
"""
Expand Down Expand Up @@ -2375,7 +2372,6 @@ def _merge_ordinal_records[K with (Clone, OrdinalKey)](
right_index += 1
return out


def _ordinal_map_magic() -> bytes:
"""
Return the fixed `OrdinalMap` container magic prefix.
Expand Down Expand Up @@ -2785,14 +2781,3 @@ def _chain_items[K with (Clone, Eq), V with Clone](layers: list[OrderedDict[K, V
break
None => pass
return out


def _missing_value[T with Clone]() -> T:
missing: list[T] = []
return missing[0]


def _runtime_error(message: str) -> None:
_ = message
missing: list[int] = []
missing[0]
3 changes: 1 addition & 2 deletions crates/rust_inspect/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,7 @@ The stable architectural rule is the phase boundary: extraction happens before h

Structured logging for durable diagnostics uses `tracing` (for example disk-cache parse failures and failed persists).

The on-disk cache filename is `.incan_rust_inspect_cache.json`. The cache loader still reads the older
`.incan_rust_metadata_cache.json` filename for backward compatibility.
The on-disk cache filename is `.incan_rust_inspect_cache.json`. The cache loader still reads the older `.incan_rust_metadata_cache.json` filename for backward compatibility.

## Limitations

Expand Down
12 changes: 4 additions & 8 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,10 @@ Projects aimed at library and tooling authors: companion crates, vocab/desugarin

Notable pro Rust interop example:

- `pro/rust_interop_pro.incn` - RFC 041 authoring surface (`rusttype`, `interop`, `std.rust` bounds, async wrappers).
Includes a "new to Rust" mental model that explains `rusttype`, `...` method declarations, and `interop:` edges.
- `pro/vocab_querykit` - runnable RFC 040 vocab companion example for query blocks, leading-dot fields, and
leading-dot fields in registered method arguments.
- `pro/vocab_routekit` - runnable RFC 040 vocab companion example for block headers, nested block-context clauses, and
scoped operator-like glyphs.
- `pro/vocab_studiokit` - runnable RFC 040 vocab companion example for workflow-shaped blocks and scoped fallback
glyphs.
- `pro/rust_interop_pro.incn` - RFC 041 authoring surface (`rusttype`, `interop`, `std.rust` bounds, async wrappers). Includes a "new to Rust" mental model that explains `rusttype`, `...` method declarations, and `interop:` edges.
- `pro/vocab_querykit` - runnable RFC 040 vocab companion example for query blocks, leading-dot fields, and leading-dot fields in registered method arguments.
- `pro/vocab_routekit` - runnable RFC 040 vocab companion example for block headers, nested block-context clauses, and scoped operator-like glyphs.
- `pro/vocab_studiokit` - runnable RFC 040 vocab companion example for workflow-shaped blocks and scoped fallback glyphs.

### `web/`

Expand Down
Loading
Loading