Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
73d6b54
rfc-0000: resolve the three open design questions
fabriceHategekimana Sep 12, 2026
5aadbb7
rfc-0028: accept and rename to PR number
fabriceHategekimana Sep 12, 2026
e184721
Merge pull request #28 from we-data-ch/rfc-0000-resolve-open-questions
fabriceHategekimana Sep 12, 2026
d85f8d9
Implement RFC 0028: make untyped R functions callable
fabriceHategekimana Sep 12, 2026
303004e
Merge pull request #30 from we-data-ch/implement-rfc-0028-untyped-r-f…
fabriceHategekimana Sep 12, 2026
6164464
rfc-0000: propose external type definitions (J2)
fabriceHategekimana Sep 12, 2026
c57588e
rfc-0000: fill in the PR number now that #31 is open
fabriceHategekimana Sep 12, 2026
f36a5bf
update
fabriceHategekimana Sep 12, 2026
260d5aa
rfc-0031: accept External type definitions (J2)
fabriceHategekimana Sep 12, 2026
3ada507
Merge pull request #31 from we-data-ch/rfc-external-type-definitions
fabriceHategekimana Sep 12, 2026
60f84af
rfc-0031: fill in tracking issue #32
fabriceHategekimana Sep 12, 2026
61cadd3
rfc-0031: freeze the typr-def.toml manifest format
fabriceHategekimana Sep 12, 2026
de421a0
Merge pull request #33 from we-data-ch/implement-rfc-0031-manifest-fo…
fabriceHategekimana Sep 12, 2026
02782cb
update
fabriceHategekimana Sep 13, 2026
8840af9
update
fabriceHategekimana Sep 13, 2026
d1f2b03
update
fabriceHategekimana Sep 14, 2026
c56e52b
update
fabriceHategekimana Sep 14, 2026
a5944d3
Support monorepo subdirectories in RepoSpec (registry.md §13 J5 prereq)
fabriceHategekimana Sep 14, 2026
ce87ec2
Add `typr types submit`: open a registry PR via `gh` (registry.md §13…
fabriceHategekimana Sep 14, 2026
7163903
update
fabriceHategekimana Sep 14, 2026
0c4b74d
added styler
fabriceHategekimana Sep 15, 2026
586211c
fixed styler performance with cache and targeted file
fabriceHategekimana Sep 15, 2026
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
74 changes: 73 additions & 1 deletion Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ rand = "0.10.0"
anyhow = "1.0.100"
countmap = "0.2"
toml = "0.8"
sha2 = "0.10"

clap = { version = "4.5", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
Expand Down
6 changes: 6 additions & 0 deletions cases/0062-untyped-r-function-callable/case.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
title = "untyped R function (`function(a, b)`) is callable, arity-checked, result `Any` (RFC 0028)"
cmd = "build"
layer = "type"
status = "fixed"
created = "2026-09-12"
origin = "perso"
28 changes: 28 additions & 0 deletions cases/0062-untyped-r-function-callable/expect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# untyped-r-function-callable

Source: `rfcs/0028-calling-untyped-r-functions.md`, accepted 2026-09-12
(we-data-ch/typr#28, tracking we-data-ch/typr#29).

## Ce qui devrait se passer

`function(a, b) { ... }` parses to `Lang::RFunction` and used to type as the placeholder
`Type::UnknownFunction`, which carries no parameter list — so any call with at least one
argument failed with `No signature of function 'my_addition' matches this call ... () ->
UnknownFunction`, even though the emitted R was already correct. Per the RFC, `Lang::RFunction`
with `n` parameters now types as `(Any, ..., Any) -> Any`: arity is checked (exactly `n`
arguments), argument types are not, and the result must be brought back into the type system
explicitly with `as!` — the same shape as the rest of TypR's opaque values.

This is the exact `docs/philosophy/intro.md` example ("weak on safety, strong on freedom"),
which the RFC's Motivation section shows failing to compile on 0.5.10.

## Vérification

Implemented alongside this case: `Lang::RFunction`'s typing rule
(`processes/type_checking/mod.rs`) now builds a real `Type::Function` instead of
`Type::UnknownFunction`. The emitted R is unchanged — `my_addition(num1, num2)` — since only the
type checker ever rejected this call.

## Statut

Kept as a regression net for the RFC's core guarantee: an untyped R function must stay callable.
15 changes: 15 additions & 0 deletions cases/0062-untyped-r-function-callable/expect.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[[rule]]
file = "@run"
must_contain = "successful"

[[rule]]
file = "@run"
must_not_contain = "Type errors found"

[[rule]]
file = "@run"
must_not_contain = "doesn't match type"

[[rule]]
file = "R/main.R"
must_contain = "my_addition(num1, num2)"
13 changes: 13 additions & 0 deletions cases/0062-untyped-r-function-callable/repro/TypR/main.ty
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#@case untyped-r-function-callable: RFC 0028 (0028-calling-untyped-r-functions)
# makes an untyped R function (`function(a, b) {...}`) callable: arity is
# checked, argument types are not, and the result is typed Any. This is the
# exact motivating example from the RFC and from docs/philosophy/intro.md.
let num1 <- 3;
let num2 <- 7;

let my_addition <- function(a, b) {
a + b
};

let total: int <- my_addition(num1, num2) as! int;
print(total);
6 changes: 6 additions & 0 deletions cases/0063-untyped-r-function-arity-error/case.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
title = "wrong arity on an untyped R function reports a dedicated message, not `UnknownFunction`"
cmd = "check"
layer = "type"
status = "fixed"
created = "2026-09-12"
origin = "perso"
37 changes: 37 additions & 0 deletions cases/0063-untyped-r-function-arity-error/expect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# untyped-r-function-arity-error

Source: `rfcs/0028-calling-untyped-r-functions.md`, accepted 2026-09-12
(we-data-ch/typr#28, tracking we-data-ch/typr#29), Reference-level explanation, "Error messages".

## Ce qui devrait se passer

Before the RFC, any call to an untyped R function was rejected by the *arity* check that
`Type::UnknownFunction` (0-ary) implicitly enforced, and the message printed the internal
placeholder back at the user:

```
Type error: No signature of function 'my_addition' matches this call.
help: 'my_addition' exists but none of its signature(s) accepts these arguments:
() -> UnknownFunction
```

Now that the function is callable, the only thing left to check is arity, and the RFC specifies
a dedicated message for it instead of reusing the generic no-matching-signature wording:

```
Type error: 'my_addition' is an untyped R function taking 2 argument(s), called with 3.
help: its body is not type-checked; only the number of arguments is.
```

## Vérification

Implemented alongside case `untyped-r-function-callable`: `TypeError::UntypedFunctionArity`
(`components/error_message/type_error.rs`, code `T044`), raised in
`processes/type_checking/function_application.rs` when a call's argument count doesn't match a
signature that is exactly one, non-variadic, all-`Any` parameters returning `Any` — the shape
`Lang::RFunction`'s typing rule now produces (`FunctionType::is_r_function`).

## Statut

Kept as a regression net: the internal `UnknownFunction` placeholder must never resurface in a
user-facing error message again.
15 changes: 15 additions & 0 deletions cases/0063-untyped-r-function-arity-error/expect.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[[rule]]
file = "@run"
must_contain = "Type errors found"

[[rule]]
file = "@run"
must_contain = "is an untyped R function taking 2 argument(s)"

[[rule]]
file = "@run"
must_contain = "only the number of arguments is"

[[rule]]
file = "@run"
must_not_contain = "UnknownFunction"
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#@case untyped-r-function-arity-error: RFC 0028 - calling an untyped R
# function with the wrong number of arguments must report a specific arity
# error, not the generic "no signature matches" message that exposed the
# internal UnknownFunction placeholder.
let my_addition <- function(a, b) {
a + b
};

my_addition(1, 2, 3)
6 changes: 6 additions & 0 deletions cases/0064-untyped-preloaded-builtin-variadic/case.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
title = "preloaded untyped base-R builtins (`Position`, `t`, `Reduce`, …) become variadic (RFC 0028)"
cmd = "check"
layer = "type"
status = "fixed"
created = "2026-09-12"
origin = "perso"
29 changes: 29 additions & 0 deletions cases/0064-untyped-preloaded-builtin-variadic/expect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# untyped-preloaded-builtin-variadic

Source: `rfcs/0028-calling-untyped-r-functions.md`, accepted 2026-09-12
(we-data-ch/typr#28, tracking we-data-ch/typr#29), Motivation and point 4 of the Reference-level
explanation ("Point 4 is the load-bearing half").

## Ce qui devrait se passer

The untyped names from `functions_R.txt` (`Position`, `t`, `Reduce`, …) are preloaded as
`(Any, UnknownFunction)` so that referring to them is not an "undefined variable" error.
Before the RFC, `Type::UnknownFunction` was effectively 0-ary (`FunctionType::new(VecType::Empty,
vec![], ...)`), so `Position()` type-checked but `Position(1, 2)` failed the same way a
user-written `function(...)` did. Fixing only `Lang::RFunction` (case
`untyped-r-function-callable`) without also fixing `UnknownFunction` would leave the same bug
reachable through every untyped base-R name — this is the "confusing version of the bug rather
than a fix" the RFC warns about. `UnknownFunction` must be variadic: any number of arguments,
result `Any`.

## Vérification

Implemented alongside case `untyped-r-function-callable`: `Type::UnknownFunction`'s conversion to
`FunctionType` (`components/type/mod.rs`'s `to_function_type`, and the matching
`TryFrom<Type> for FunctionType` arm in `components/type/function_type.rs`) now produces a single
variadic `Any` parameter returning `Any`, instead of zero parameters.

## Statut

Kept as a regression net: every untyped base-R builtin must stay callable with arguments, not
just referenceable by name.
11 changes: 11 additions & 0 deletions cases/0064-untyped-preloaded-builtin-variadic/expect.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[[rule]]
file = "@run"
must_contain = "successful"

[[rule]]
file = "@run"
must_not_contain = "Type errors found"

[[rule]]
file = "@run"
must_not_contain = "UnknownFunction"
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#@case untyped-preloaded-builtin-variadic: RFC 0028 - preloaded untyped
# base-R builtins (functions_R.txt: Position, t, Reduce, ...) are preloaded
# as `(Any, UnknownFunction)`; `UnknownFunction` must be variadic so calling
# them with arguments type-checks, not just referencing the bare name.
let x: int <- Position(1, 2) as! int;
print(x);
7 changes: 7 additions & 0 deletions cases/0065-reassign-widen-literal/case.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
title = "reassignment narrows a mutable variable to the RHS literal's singleton type"
cmd = "check"
layer = "type"
status = "fixed"
created = "2026-09-12"
origin = "perso"
checked = false
36 changes: 36 additions & 0 deletions cases/0065-reassign-widen-literal/expect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# reassign-widen-literal

Source: found while updating `docs/reference/bindings-mutation.md` on `typr.github.io` — its
"Reassignment & mutation" example (`let x <- 0; x <- 10; x <- x + 1; x |> f() |> g()!;`) no
longer type-checks.

## Ce qui devrait se passer

Reassigning an already-bound `let`-declared variable (`x <- 10;`, no `let`) to a new value of the
same base kind should keep working across any number of reassignments — that's the entire point
of `docs/reference/bindings-mutation.md`'s "Reassignment & mutation" section and of the `expr!;`
implicit-mutation sugar (`ai_context/in_place.md`), which desugars to exactly this form
(`lhs <- expr`).

## Anomalies

`observed.txt` shows three failures for one linear sequence of reassignments to a single `int`
variable:

1. `let x <- 0;` (no annotation) infers `x` as the *singleton* literal type `Integer(Val(0))` —
confirmed deliberate elsewhere (`test_let_expression0`,
`crates/typr-core/src/processes/type_checking/let_expression.rs`: `let a <- 5;` types `a` as
`integer_type(5)`). Reassigning to a *different* literal (`x <- 10;`) then fails the subtype
check outright: "type 0 doesn't match type 10".
2. Even past that (e.g. with an explicit `let x: int <- 0;` instead), the *first* successful
assignment rebinds `x`'s type in context to the literal RHS type (`Integer(Val(10))`) rather
than keeping it widened — so the *next* reassignment fails just the same:
"type 10 doesn't match type int" — `x`'s type keeps narrowing one literal at a time instead
of staying at its base kind.
3. The `!;` implicit-mutation sugar (`x |> f() |> g()!;`) desugars to the same `Assign` node
(`ai_context/in_place.md` §5.2), so it inherits the same failure once `x` has narrowed.

Root cause: `Lang::Assign`'s typing arm (`processes/type_checking/mod.rs`) stores the RHS's raw
type as the variable's new type on every successful assignment, instead of widening a literal
singleton to its base kind the way `Type::generalize()` already does elsewhere (e.g.
`cases/0017-char-if-widening`).
Loading
Loading