From f4fa076d533a6c6235f4a0e2725abdd77ae3f530 Mon Sep 17 00:00:00 2001 From: Fabrice Date: Tue, 8 Sep 2026 09:37:31 +0200 Subject: [PATCH 1/6] used a README.md --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index a4c6d766..34758278 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,13 @@ **A typed superset of R** — static type checking, modules, sum types and interfaces, compiled to idiomatic, readable R. +[![Downloads](https://img.shields.io/crates/d/typr)](https://crates.io/crates/typr) +[![Latest release](https://img.shields.io/github/v/release/we-data-ch/typr)](https://github.com/we-data-ch/typr/releases/latest) +[![CI](https://github.com/we-data-ch/typr/actions/workflows/ci.yml/badge.svg)](https://github.com/we-data-ch/typr/actions) +[![Docker Pulls](https://img.shields.io/docker/pulls/fabricehategekimana/typr)](https://hub.docker.com/r/fabricehategekimana/typr) +[![Open VSX](https://img.shields.io/open-vsx/v/wedata-ch.typr-language)](https://open-vsx.org/extension/wedata-ch/typr-language) +[![License](https://img.shields.io/github/license/we-data-ch/typr)](LICENSE) + [Documentation](https://we-data-ch.github.io/typr.github.io/) · [Playground](https://we-data-ch.github.io/typr-playground.github.io/) · [Blog](https://we-data-ch.github.io/typr.github.io/blog) From 5a52042c44eb672c6439a92bcb84c370a6041045 Mon Sep 17 00:00:00 2001 From: Fabrice Date: Tue, 8 Sep 2026 10:54:07 +0200 Subject: [PATCH 2/6] added a better example --- README.md | 45 ++-- syntaxe.md | 730 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 760 insertions(+), 15 deletions(-) create mode 100644 syntaxe.md diff --git a/README.md b/README.md index 34758278..03da5ece 100644 --- a/README.md +++ b/README.md @@ -20,26 +20,41 @@ interfaces, compiled to idiomatic, readable R. --- -TypR is not a new runtime. It is a static verification and desugaring layer that -compiles entirely to conventional R before execution — every generated `R/*.R` -file stays readable and runs on a stock R installation. It targets the step -beyond exploratory scripting: package production, maintenance, and code that has -to survive in production. +**Just add types.** TypR is a typed superset of R that compiles to +readable, stock R. No runtime, no new dependencies — just static +verification that catches wrong argument types, undocumented returns, +and silent coercions **before your code ever runs.** + + + + + + + + + + +
R todayTypR
+ +```r +normalize <- function(x, na.rm = FALSE) { + stopifnot(is.numeric(x), length(x) > 0) + if (na.rm) x <- x[!is.na(x)] + (x - mean(x)) / sd(x) +} +``` -```typr -type Shape <- .Circle(num) | .Square(num); + -let area <- fn(s: Shape): num { - match s { - .Circle(r) => 3.14159 * r * r, - .Square(side) => side * side, - } +```typr +let normalize <- fn(x: [num], na.rm: bool = false): [num] { + if (na.rm) { x <- x[!is.na(x)] }; + (x - mean(x)) / sd(x) }; - -# UFCS, pipes and plain calls are all equivalent -(3.0) |> .Circle() |> area() |> print(); ``` +
+ ## Install | Channel | Command | diff --git a/syntaxe.md b/syntaxe.md new file mode 100644 index 00000000..bccdf0df --- /dev/null +++ b/syntaxe.md @@ -0,0 +1,730 @@ +# TypR — carte de la syntaxe + +> Référence non officielle, établie par lecture directe du parseur (`crates/typr-core/src/processes/parsing/`) +> — reflète l'état du code à la date de génération, pas une spécification figée. +> +> Sources principales : `parsing/mod.rs`, `parsing/elements.rs`, `parsing/types.rs`, +> `components/language/operators.rs`, `parsing/indexation.rs`, `components/language/mod.rs`. + +## Sommaire + +1. [Lexique & littéraux](#1--lexique--littéraux) +2. [Liaisons & mutation](#2--liaisons--mutation) +3. [Système de types](#3--système-de-types) +4. [Alias, opaque, typeconstructor, signatures](#4--alias-opaque-typeconstructor-signatures) +5. [Fonctions](#5--fonctions) +6. [Records & constructeurs](#6--records--constructeurs) +7. [Unions, tags & `match`](#7--unions-tags--match) +8. [Opérateurs & précédence](#8--opérateurs--précédence) +9. [Contrôle de flux](#9--contrôle-de-flux) +10. [Modules & imports](#10--modules--imports) +11. [Interfaces & validation structurelle](#11--interfaces--validation-structurelle) +12. [Échappatoires vers du code brut](#12--échappatoires-vers-du-code-brut) +13. [TypR vs R — ce qui change vraiment](#13--typr-vs-r--ce-qui-change-vraiment) +14. [Ambiguïtés & pièges connus du parseur](#14--ambiguïtés--pièges-connus-du-parseur) + +--- + +## 1 · Lexique & littéraux + +Chaque instruction de haut niveau se termine par `;`. L'oubli est toléré par le parseur mais +signalé (`SyntaxError::ForgottenSemicolon`) — sauf pour la toute dernière expression d'un bloc, +qui joue le rôle de valeur de retour implicite (comme en R). + +| Littéral | Syntaxe | Remarque | +|---|---|---| +| Entier | `42`, `-7` | `Lang::Integer` | +| Nombre | `3.14`, `-0.5` | un point décimal est obligatoire pour `Number`, sinon c'est un `Integer` | +| Chaîne | `"texte"` ou `'texte'` | guillemets simples et doubles interchangeables ; échappe `\" \' \\ \n \t` | +| Booléen | `true` / `TRUE`, `false` / `FALSE` | forme minuscule *et* majuscule acceptées | +| Nul | `null` / `NULL` | `Lang::Null` — distinct de `NA` | +| Manquant | `na` / `NA` | `Lang::NA` — distinct de `null` | +| Identifiant variable | `mon_nom` | doit commencer par `a-z_` ; `snake_case` attendu | +| Identifiant type | `MonType` | `PascalCase` obligatoire pour `type`/`opaque`/alias | +| Identifiant cité | `` `+` ``, `` `nom bizarre` `` | backticks — utile pour nommer un opérateur custom | +| Commentaire | `# commentaire` | `//` n'existe pas — un fichier `.ty` avec `//` échoue silencieusement (piège connu, voir §14) | + +Le parseur distingue explicitement `let NomPascal <- ...` (erreur `LetInsteadOfType`) de +`type nom_snake <- ...` (erreur `TypeInsteadOfLet`) — la casse du premier identifiant n'est donc +pas un simple style, elle est vérifiée et corrigée avec un message dédié. + +--- + +## 2 · Liaisons & mutation + +### Déclaration `let` + +```typr +let x <- 42; +let y: int <- 5; +@pub let z <- "hello"; # exportée : publique + testable +@testable let cache <- state(0); # privée, visible en build --test via M$.test_cache +@export let api <- fn(x: int): int { x }; # @pub + #' @export roxygen2 +``` + +`<-` et `=` sont interchangeables comme opérateur d'affectation dans `let`. Un seul `=` n'est en +revanche **jamais** un opérateur infixe binaire dans une expression — il est réservé aux champs +nommés (`x = 1`), à l'affectation top-level et aux valeurs par défaut de paramètres. + +### Déstructuration de tuple + +```typr +let :{a, b, c} <- :{1, 2, 3}; +let :{a, _, c} <- :{1, 2, 3}; # wildcard : ignore l'élément +``` + +Désucré en une variable temporaire + accès positionnel par point (`__tuple_tmp__.1`, `.2`, ...). + +### Affectation & mutation + +```typr +x <- 10; # Assign sur une variable déjà liée +x <- x + 1; + +# sucre "mutation implicite" : x!; réaffecte x au résultat de l'expression +x |> f() |> g()!; # ≡ x <- x |> f() |> g(); +obj.method()!; # ≡ obj <- obj.method(); +``` + +`expr!;` exige que la tête de la chaîne `.`/`|>` soit une variable assignable ; `3!;` par exemple +est rejeté au parsing. + +--- + +## 3 · Système de types + +### Primitifs + +`int` · `num` · `bool` · `char` · `null` · `na` · `Any` · `Empty` · `Self` + +Les littéraux peuvent aussi apparaître *comme type* (types singleton) : `3`, `3.14`, `true`, +`"chat"` sont des types valides, plus précis que `int`/`num`/`bool`/`char`. + +### Records (`list{...}`) + +```typr +type Point <- list { x: int, y: int }; +type Config <- record { name: char, timeout: int }; # synonyme explicite +``` + +### Tuples + +```typr +tuple{int, char} # explicite +Tuple[int, char] # notation crochets +Tuple[T..., U] # variadique : T... capture une séquence de types +``` + +### Tableaux, vecteurs, dataframes + +| Forme | Exemple | Sens | +|---|---|---| +| Array (S3, court) | `[int]` | tableau d'entiers, taille libre (`Any`) | +| Array (S3, complet) | `[#N, int]` | taille indexée par le générique `#N` | +| `Array[...]` | `Array[3, int]` | variante nommée, taille fixe = 3 | +| `Vec[...]` | `Vec[num]`, `Vec[#N, num]` | vecteur R natif | +| `dataframe[...]{...}` | `df[#N]{ name: char, age: int }` | `df` = alias court de `dataframe` | +| Record générique nommé | `Tibble[3]{ id: int, active: bool }` | nécessite une déclaration `typeconstructor` préalable | + +Règle d'or du parseur : un bloc `{ ... }` qui suit fait toujours de la construction un *record* — +`Tibble[3]` seul (sans accolades) est un simple alias paramétré, `Tibble[3]{ ... }` est un record +constructeur. + +### Génériques & sigils de « kind » + +```typr +let id <- fn(x: T): T { x }; # T majuscule = générique libre +#N # générique "indice" (dimension de tableau) +$T # générique "label" (nom de champ) +%R # générique contraint : doit être un Record +@I # générique contraint : doit être une Interface +^S # générique contraint : doit être un char +?B # générique contraint : doit être un bool +``` + +### Unions & intersections + +```typr +type Shape <- .Circle(num) | .Square(num); # union par tags +type Combined <- Movable & Drawable; # intersection d'interfaces +``` + +### Fonctions & interfaces (en position de type) + +```typr +(int, char) -> bool # type fonction anonyme +(a: int, b: int) -> int # noms de paramètres optionnels, ignorés au typage +interface { view: (Self) -> char } # capacité structurelle +``` + +Écrire `fn(a: int) -> int` en *position de type* (au lieu de `(int) -> int`) est capté +explicitement et renvoie `SyntaxError::FunctionTypeSyntax` — `fn(...)` n'existe qu'au niveau des +expressions, jamais des types. + +### Arithmétique de types & conditions + +```typr +type Combined <- A + B; # Type::Operator sur des index/dimensions +T if T1 in T2 # type conditionnel (raffinement expérimental) +``` + +--- + +## 4 · Alias, opaque, typeconstructor, signatures + +```typr +type Meters <- int; # alias transparent +opaque Meters <- int; # alias opaque : le sous-jacent est caché du typage externe +type Option = .Some(T) | .None; # alias générique — , pas [T] +opaque Factor <- int; # paramètre "fantôme" : L n'apparaît que dans de futures @signature + +typeconstructor Tibble[N] record; # enregistre un constructeur générique de record +typeconstructor Matrix[N, M, T] recursive; +``` + +#### Signatures (déclaration de type sans corps) + +```typr +@map: (a: [#N, T], f: (T) -> U) -> [#N, U]; # générique +@add: (a: int, b: int) -> int; # surcharge : répéter @add avec d'autres types +@add: (a: num, b: num) -> num; +@as__character: (Self) -> char; # "__" → "." en sortie R (as.character) + +@extern stats::sd: (x: [Any, num]) -> num; # fonction R externe, appel réel package::fn +@extern base::readRDS: (path: char) -> Foreign; # nom nu = fonction déjà visible sans préfixe pkg:: +@importFrom dplyr filter select mutate; # hisse un @importFrom roxygen2 +``` + +Une signature `.ty` peut contenir un vrai corps `let nom <- fn(...){...}` et être type-checkée +avec succès — mais ce corps est **silencieusement jeté** : seule la paire `(nom, type)` survit. +Le seul patron qui marche pour la stdlib est signature seule + implémentation R écrite à la main +dans `std.R`. + +#### `Foreign` — valeurs R externes opaques + +`opaque Foreign <- Any;` (`configs/std/foreign.ty`) est le type compagnon idiomatique de +`@extern` pour *nommer* une valeur R qui existe déjà à l'extérieur (objet S3/S4/RC/R6, package +tiers) sans jamais la construire depuis TypR : + +```typr +type LmModel <- Foreign; +@extern base::readRDS: (path: char) -> LmModel; +@extern stats::coef: (m: LmModel) -> Foreign; # accesseur dédié pour toucher au contenu + +let m: LmModel <- readRDS("modele.rds"); +``` + +La valeur traverse `let` annoté, arguments et retours de fonction **sans jamais être touchée** +(pas de `as.X()`/`struct()` appliqué). Deux limites volontaires à connaître : `m.champ`/`m$champ` +ne type-check **jamais** (`Any` n'a aucun champ structurel connu — il faut un accesseur `@extern` +dédié pour chaque champ/slot/méthode) ; et un appel `@extern` est **positionnel uniquement**, donc +impossible d'appeler directement une fonction R qui exige des arguments nommés (`new("X", x=1)`). +Détail complet — mécanisme, quatre bugs trouvés et corrigés, grille de conformité — dans +`CLAUDE.md` § "Foreign R Values" et `interop_matrix.md` (racine du repo). + +--- + +## 5 · Fonctions + +```typr +let add <- fn(a: int, b: int): int { a + b }; +let greet <- fn(name: char, greeting: char = "Hello"): char { greeting }; # défaut, param final seulement +greet("World"); # "Hello" +greet("World", "Hi"); # "Hi" + +let sum_all <- fn(...xs: int): int { /* ... */ }; # variadique + +let sq <- \(x) x * x; # lambda non typée +let add5 <- \add(a = 5); # application partielle d'une fonction +let origin <- \Point:{ x = 0, y = 0 }; # application partielle d'un constructeur de record +``` + +`fn(...)` exige toujours `: Type` de retour — l'omettre panique explicitement ("You forgot to +specify the function return type"). `\(...)` (lambda) n'a en revanche ni type de paramètres ni +type de retour déclarés. + +**Application partielle vs lambda** : les deux utilisent `\`, désambiguïsés par ce qui suit — +`\(` → lambda, `\identifiant(` → application partielle. `PartialApp` se désucre en +`Lang::Function` pendant le typage — il n'atteint jamais la transpilation. + +--- + +## 6 · Records & constructeurs + +```typr +let p <- :{ x = 1, y = 2 }; # record anonyme (nécessite un contexte de type) +let p <- Point:{ x = 1, y = 2 }; # constructeur explicite → transpile en Point(x=1, y=2) +let q <- Point:{ ...p, y = 9 }; # spread "runtime" : fusion structurelle, override après +let r <- mod$Point:{ x = 1, y = 2 }; # constructeur qualifié par chemin de module + +let arr <- IntBox:[1, 2, 3]; # ArrayConstructorCall +``` + +Autres écritures équivalentes du littéral record : `record{...}`, `object{...}`, `list{...}`, +`:{...}` — la forme est choisie par la *forme* des champs, pas par le mot-clé : des champs +`nom = valeur` donnent un record (`Lang::List`), des valeurs positionnelles donnent un tuple +(`Lang::Tuple`) même avec `list{1, 2, 3}`. + +#### Spread — deux mécanismes distincts + +```typr +Point:{ ..source } # spread "statique" nominal — un seul par appel +Point:{ ...source } # spread "runtime" structurel — un seul dans un constructeur, plusieurs dans un record littéral +:{ ...a, ...b, z = 1 } # record littéral : plusieurs spreads runtime autorisés, fusionnés dans l'ordre puis overridés +``` + +#### Named type embedding + +```typr +type Widget <- list { embed coords: Position, label: char }; +``` + +`embed` est un mot-clé « doux » — reconnu seulement devant `nom: Type` avec espace derrière, donc +un champ réellement nommé `embed` reste parsable. + +--- + +## 7 · Unions, tags & `match` + +```typr +type Shape <- .Circle(num) | .Square(num); +let s <- .Circle(3.14); +let n <- .None; # tag sans valeur + +match s { + .Circle(r) => r * 2.0, + .Square(side) => side, +} +``` + +#### Motifs de `match` disponibles + +| Motif | Exemple | +|---|---| +| Tag avec binding | `.Some(a) => a` | +| Tag sans binding | `.None => 0` | +| Motif de type | `x as int => x + 1` | +| Motif record | `:{nom: n, age: a} => a` ou `list(nom = n, age = a) => a` | +| Motif tuple | `:{a, b} => a` | +| Joker | `_ => défaut` | +| Variable simple | `v => v` | + +#### Constructeur d'union qualifié + +```typr +type Color <- .Red | .Blue; +Color.Red # référence qualifiée à un tag sans charge utile (bare, sans `:{...}`) + +type Rgb <- list { r: int, g: int, b: int }; +type Palette <- .Red | .Blue | Rgb; +Palette.Rgb:{ r = 10, g = 20, b = 30 } # Rgb est un alias record utilisé tel quel comme membre + # de l'union (pas un tag `.Rgb(...)`) — son constructeur + # généré accepte bien des champs nommés. +``` + +**Piège** (audit_type_checking.md U1, corrigé) : la syntaxe `Union.Variant:{ field = val }` / +`TagName:{ field = val }` ne fonctionne **que** quand `Variant` est un alias record utilisé +directement comme membre de l'union (cas `Rgb` ci-dessus) — jamais pour un vrai tag `.Variant(...)` +(scalaire ou à corps record), qu'on l'écrive avec ou sans `:{...}`. Le constructeur généré pour un +tag prend toujours **une seule charge utile positionnelle** (`Variant <- function(x) {...}`), jamais +des champs nommés séparés. Pour construire un tag, utiliser `.Variant(valeur)` — et +`.Variant(:{ ... })` quand la charge utile est elle-même un record. Rejeté à la vérification de +type (`TagFieldConstructorNotSupported`) plutôt que de compiler vers du R qui plante. + +--- + +## 8 · Opérateurs & précédence + +Précédence réelle (la plus élevée se regroupe en premier) — notez que l'accès membre / pipe se +lie *plus fort* que l'arithmétique, contrairement à la plupart des langages. + +| Rang | Opérateurs | Rôle | +|---|---|---| +| 4 (fort) | `.` `\|>` `$` `::` `as!` `in` | accès membre / UFCS, pipe, cast validant | +| 3 | `*` `/` `%` `@` | multiplicatif, produit matriciel | +| 2 | `+` `-` | additif | +| 1 (faible) | `== != < > <= >=`, `and/&&/&`, `or/\|\|/\|`, `%op%` | comparaison, logique, opérateurs custom | + +### UFCS — `.` et `|>` + +```typr +x.f(y) # ≡ f(x, y) — appel de méthode uniforme +x |> f() |> g() # pipe — même désucrage +t.1 # accès positionnel d'un tuple (index 1-based) +mod$member # accès champ de record / module — "::" est un alias historique de "$" +``` + +```typr +# TypR +data |> filter(x > 0) |> mean() +``` +```r +# R +data |> filter(x > 0) |> mean() +# ou via magrittr : data %>% filter(...) %>% mean() +``` + +### Cast validant + +```typr +x as! Point # appelle validate_Point(x) à l'exécution +xs as! [Any, int] # cast vers un type structurel inline (pas un alias) +``` + +### Intervalles + +```typr +1:10 # ≡ seq(1, 10, 1) +1:2:10 # ≡ seq(1, 10, 2) — pas explicite au milieu +``` + +Les variantes doublées (`++ -- ** // %% @@ .. $$ |>>`) et `@`/`@@`/`=` en infixe ont été retirées +du tokenizer (`components/language/operators.rs::op()`/`pipe_op()`) — aucune n'avait de bras de +typage/transpilation ni de signature stdlib `` `op` `` pour les porter, contrairement à +`+ - * / % && || ...` qui retombent réellement sur un appel de fonction stdlib. Les laisser +tokeniser silencieusement un typo (ex. un commentaire `//` de style C, TypR n'utilisant que `#`) +en un opérateur "valide" ne faisait que déplacer l'erreur vers un message confus, loin du vrai +problème ; voir §14. `@` en tête de ligne appartient de toute façon aux annotations (`@pub`, +`@extern`...), pas aux opérateurs. + +--- + +## 9 · Contrôle de flux + +```typr +if (x > 0) { "positif" } else if (x < 0) { "négatif" } else { "zéro" } + +for (item in items) { print(item); }; +while (n > 0) { n <- n - 1; }; +loop { /* ... */ break; }; + +next; # continue de boucle +break; +return valeur; +``` + +Toute forme itérable dotée d'une fonction `as_vec: (Self) -> [T]` peut être utilisée dans un +`for (... in ...)`, pas seulement les tableaux. + +--- + +## 10 · Modules & imports + +```typr +module Math { + let pi <- 3.14159; + @pub let pi_approx <- 3.14; + @pub opaque Radians <- num; +}; + +use Math::pi_approx; +use Math::{pi_approx, sin as s}; +use Math::*; + +import Math; +import Math as M; + +mod Utils; # forme d'import historique équivalente +library(dplyr); # dépendance R classique +use("dplyr", c("filter", "select")); # adaptateur legacy +``` + +Un module compile vers un environnement R ; les membres non `@pub` restent invisibles depuis +l'extérieur (sauf en build `--test` via `@testable`, exposés en `M$.test_nom`). + +--- + +## 11 · Interfaces & validation structurelle + +```typr +type Movable <- interface { mv: (Self, int, int) -> Self }; + +let p <- Point:{ x = 1, y = 2 }; +let q <- Movable(p); # validateur compile-time — transpile en `q <- p`, jamais un appel réel +``` + +Une interface décrit une **capacité structurelle** : n'importe quel type dont les fonctions +libres ont ce type comme premier paramètre « l'implémente », sans mot-clé `impl`. Appeler `I(x)` +où `I` est un alias d'interface n'est donc jamais un vrai appel de fonction (les alias vivent dans +un espace de noms séparé des variables) — c'est un contrôle de compatibilité à la compilation qui +échoue avec `InterfaceNotSatisfied` / `IncompatibleInterfaceMethod`. + +--- + +## 12 · Échappatoires vers du code brut + +```typr +extern (x: int, y: char) -> char r#"paste0(x, y)"#; # corps R brut, typé en entrée/sortie + +function(x, y) { x + y } # fonction R brute non typée (RFunction), corps capturé tel quel + +R { # bloc R brut non typé (RBlock) : valeur immédiate, pas un appel + df %>% + dplyr::filter(x > 1) %>% + dplyr::mutate(z = y + 1) +} + +JS { /* ... */ } # bloc JavaScript brut (cible JS) + +Class("data.frame", "tbl") # type dénotant une classe R existante (RClass) + +@{ 1 + x * 2 }@ # bloc vectoriel : re-parsé comme une séquence d'éléments TypR + # (littéraux/appels/variables) — pas du R arbitraire, contrairement à `R { ... }` +``` + +Ce sont les points de sortie délibérés du système de types : `extern` garde une signature +vérifiée par TypR autour d'un corps R opaque ; `function(...)` / `R { ... }` / `JS { ... }` n'ont +aucune vérification du tout. `R { ... }` capture son corps tel quel, accolades équilibrées (comme +`function(...)`), et transpile en un simple bloc R `{ ... }` — qui s'évalue déjà à la valeur de sa +dernière instruction, donc aucun wrapper ni appel n'est émis. C'est la forme à privilégier pour du +R idiomatique difficilement typable (pipes `%>%`/`|>`, NSE dplyr, formules `~`, ...) qu'on veut +juste utiliser pour produire une valeur, sans se soucier du système de types — contrairement à +`@{ ... }@`, dont le contenu doit rester une séquence d'éléments TypR valides. + +--- + +## 13 · TypR vs R — ce qui change vraiment + +| Aspect | R classique | TypR | +|---|---|---| +| Fin d'instruction | saut de ligne suffit | `;` attendu (sinon avertissement de parsing) | +| Typage | dynamique, aucune annotation | statique ; `fn(...)` exige toujours un type de retour | +| Casse des noms | convention libre | vérifiée par le parseur : `snake_case` pour `let`, `PascalCase` pour `type`/alias | +| Listes | `list(a = 1, b = 2)`, sans validation | `list{a=1,b=2}` génère un vrai constructeur + validateur (`as.T`, `validate_T`) | +| Sommes de types | pas de type natif — conventions `class` ad hoc | tags `.A(T) \| .B` + `match` exhaustif | +| Appel de méthode | `UseMethod`/S3, ou `$` sur les objets R6/environnement | UFCS générale : `x.f(y)` ≡ `f(x, y)` pour *tout* type | +| Interfaces | aucune notion formelle | `interface { ... }` + validateur structurel `I(x)` à la compilation | +| Pattern matching | `switch()`, peu typé | `match` avec motifs tag/type/record/tuple/joker | +| Modules | packages / `local()` / environnements ad hoc | `module M { ... }` → environnement R, visibilité `@pub` explicite | +| Génériques | inexistants (dispatch S3 dynamique) | paramètres de type `T`, sigils de kind (`%R @I ^S ?B #N`) | +| Mutation | `x <- f(x)` explicite | sucre `expr!;` équivalent, plus `State` pour la mutation partagée réelle | +| Sortie | du R directement exécuté | compile vers du R idiomatique (`R/*.R`) — 100% du R généré reste lisible et exécutable tel quel | + +> **À retenir** : TypR n'est pas un nouveau runtime — c'est une couche de vérification statique et +> de sucre syntaxique (UFCS, tags, match, interfaces, modules, générique) qui se désucre +> entièrement en R conventionnel avant exécution. Rien de tout ceci n'existe à l'exécution R ; +> tout est résolu par le compilateur TypR. + +--- + +## 14 · Ambiguïtés & pièges connus du parseur + +Le parseur est fait de dizaines de petits combinators `nom` assemblés via `alt()`, sans grammaire +déclarative unique — la plupart des pièges viennent d'un ordre d'essai particulier, ou d'un +tokenizer générique réutilisé dans deux contextes différents. Tout ce qui suit est vérifié +directement dans le code source, pas déduit. + +### Instruction sans `;` avalant la suivante — *corrigé* + +`elements()` assemblait autrefois ses tokens via +`many1(alt((as_excl_operator_token, single_element_token, element_operator_token)))`, sans +imposer l'alternance `Expression (Opérateur Expression)*`. Deux `single_element_token` +consécutifs — donc deux instructions collées faute de `;` — étaient acceptés dans le même vecteur +de tokens sans erreur. `variable_exp` n'excluant aucun mot-clé, la fonction/l'identifiant de +l'instruction suivante finissait comme jeton « expression » surnuméraire, silencieusement absorbé +dans le corps de la première instruction. La seconde instruction disparaissait purement et +simplement de l'AST, sans le moindre message d'erreur. + +```typr +let add_object <- fn(self: Scene, object: Object): Scene { + Scene:{ objects: self.objects.extend(object), ...self } +} // <- ';' manquant ici + +@pub +let print <- fn(self: Scene): Empty { /* ... */ }; // avalé, invisible dans l'AST +``` + +Fixé (`cases/0012`) en remplaçant le tokenizer par +`single_element_token, many0(pair(operator_like_token, single_element_token))` : un jeton +non-opérateur après une expression complète arrête désormais `many0` *sans consommer d'entrée*, +laissant l'instruction suivante au parseur appelant plutôt que de l'avaler. L'oubli d'un `;` +redevient une simple `ForgottenSemicolon` récupérable, pas une perte silencieuse de code. + +### `@` en tête de ligne juste après une instruction non terminée — *corrigé* + +Conséquence directe du piège précédent : le tokenizer générique d'opérateurs reconnaissait aussi +`@`/`@@` comme `Op::At`/`Op::At2` — un opérateur binaire qui n'a jamais eu le moindre bras de +typage ou de transpilation (syntaxe totalement morte à l'exécution). Une instruction sans `;` +suivie d'une déclaration `@pub`/`@export`/`@testable` se retrouvait donc avec son `@` avalé comme +« continuation d'opérateur » du corps précédent, corrompant la première instruction *et* +dépouillant la seconde de son annotation de visibilité. + +Fixé en retirant `@`/`@@` de la liste d'opérateurs infixes génériques +(`components/language/operators.rs::op()`). `@` reste pleinement actif, mais uniquement comme +*préfixe* d'annotation (`@pub`, `@export`, `@testable`, `@extern`, `@nom: Type;`), reconnu par des +parseurs dédiés essayés avant la grammaire d'expression générale. + +### Opérateurs morts du tokenizer — *purgés* + +Suite directe du point précédent : `@`/`@@` n'étaient pas les seuls jetons reconnus par `op()`/ +`pipe_op()` sans avoir le moindre bras de typage/transpilation ni de signature stdlib `` `op` `` +pour les porter. `++ -- ** // %% .. $$ |>>` ainsi qu'un `=` infixe (`Op::Eq2`, déjà exclu de +`bool_op()`) étaient dans le même cas — tombant sur le bras générique de `Lang::Operator` qui +désucre tout opérateur non géré explicitement en appel de `` `op`(e1, e2) ``, sans jamais trouver +de définition correspondante. Le risque concret : un typo courant (`//` pour un commentaire +C-style, alors que TypR n'utilise que `#`) parsait *avec succès* comme une division, et n'échouait +qu'au typage, avec un message éloigné du vrai problème (« Function `` `//` `` not defined »). + +Purgé en retirant ces jetons de `op()`/`pipe_op()` et les variantes `Op` correspondantes +(`Add2/Minus2/Mul2/Div2/Modulo2/Dot2/Dollar2/Pipe2/At/At2/Eq2`) entièrement de l'énum — elles +n'étaient produites nulle part ailleurs dans la base de code une fois le tokenizer purgé. + +Un `//` égaré échoue désormais dès le parsing au lieu de produire un opérateur fantôme — mais pas +comme une erreur brute : un parseur dédié `wrong_comment` (`parsing/mod.rs`, à côté de `comment()`) +le reconnaît explicitement, le traite exactement comme un vrai commentaire `#` (consommé jusqu'à +fin de ligne, ignoré, non fatal) et pousse un `SyntaxError::WrongCommentSyntax` récupérable. Sans +ce traitement dédié, `//` serait retombé sur le piège générique décrit juste en dessous (texte de +fin d'instruction imparsable = **silencieusement tronqué de l'AST**, pas juste mal signalé) — un +typo `//` est un cas assez courant (habitude venue d'autres langages) pour mériter mieux qu'un +`SyntaxError::UnknownElement` générique. + +> ⚠️ **Tentative avortée pour `=` en position de comparaison** (`if (a = b)`) : un plan initial +> prévoyait la même récupération dédiée qu'au-dessus pour `//`, en repêchant un `=` isolé dans +> `op()` lui-même (retomber sur `Op::Eq`, avec un `SyntaxError::SingleEqualsComparison` récupérable). +> Abandonné : `op()` est aussi appelé par la grammaire de *type* (`types.rs::index_operator`/ +> `compute_operators`, pour l'arithmétique de types `type Combined <- A + B;`), qui se trouve juste +> avant le séparateur `= valeur` d'un paramètre par défaut (`greeting: char = "Hello"`). Là, `=` +> n'a aucun sens d'opérateur — le repêcher en `Op::Eq` faisait paniquer `compute_operators` sur une +> combinaison non gérée (`compute_operators`'s `_ => panic!()`, testé via +> `test_non_trailing_default_param_is_an_error` et 3 autres tests `function::tests` cassés). +> `=` reste donc simplement absent du tokenizer, sans erreur dédiée — un futur correctif devrait +> cibler spécifiquement le tokenizer d'expressions (`elements.rs`), pas la primitive `op()` partagée. + +### Découverte connexe — le CLI avalait toutes les erreurs de syntaxe (`typr check`/`build`) — *corrigé* + +En vérifiant le point ci-dessus, découvert que `typr check`/`typr build` (contrairement à +`typr debug --ast`) n'affichaient **aucune** erreur de syntaxe collectée au parsing — +`engine.rs::parse_code`/`parse_code_with_info`/`parse_code_from_str` appelaient +`parse_with_errors()` puis jetaient silencieusement `.errors`, ne gardant que `.ast`. Même chose +une deuxième fois, indépendamment, dans `metaprogramming.rs::ModuleExpander::expand_one` pour +*chaque fichier de module importé* (`mod x;`) : son propre `parse(...)` local jetait aussi ses +erreurs. Concrètement, tout contenu de fin de fichier illisible par la grammaire +(`SyntaxError::UnknownElement`, ou tout autre `push_parse_error` récupérable comme +`WrongCommentSyntax` ci-dessus) était **invisible** via `check_file`/`check_project` dans +`typr-cli/src/project.rs`, alors que `typr debug --ast` les affichait très bien. Reproductible avec +n'importe quel charabia de fin d'instruction, dans le fichier d'entrée ou dans n'importe quel module +importé. + +Corrigé en changeant les signatures de `parse_code`/`parse_code_with_info`/`parse_code_from_str` +(`typr-cli/src/engine.rs`) pour renvoyer aussi `Vec` (entry file + tous les modules +importés, via un nouveau champ `ExpansionInfo::syntax_errors` alimenté par `expand_one`), et en +câblant les 7 points d'appel dans `project.rs` (`check_project`, `check_file`, `build_project_impl`, +`build_file`, `run_file_impl`, `generate_spg`, `document_impl`) sur un nouveau helper +`report_syntax_errors`. Politique de sévérité (décidée explicitement, pas par défaut) : seul +`SyntaxError::UnknownElement` (code réellement perdu de l'AST) fait échouer le pipeline +(`step.fail()` + `exit(1)`) — tout le reste (`ForgottenSemicolon`, `WrongCommentSyntax`, +`LetInsteadOfType`, ...) est toujours entièrement récupéré par le parseur et n'est affiché que +comme avertissement, pour ne pas casser tout projet existant dont le dernier `let` n'a pas de `;` +final (un style aujourd'hui parfaitement toléré). + +En chemin, corrigé un bug d'étiquetage préexistant révélé par ce câblage : le template partagé +`MsgTemplate::Single`/`Double` (`message_template.rs`) avait `"Type error: {text}"` codé en dur — +`SyntaxError::display()` n'avait simplement jamais été appelé en dehors des tests avant cette +correction, donc personne n'avait remarqué qu'une vraie erreur de syntaxe s'affichait comme +« Type error: TypR comments use `#`, not `//` ». `SingleBuilder`/`DoubleBuilder` ont gagné un champ +`kind` (défaut `"Type error"`, préservant les 24 sites d'appel de `type_error.rs` sans changement) ; +les 13 sites de `syntax_error.rs` appellent désormais `.kind("Syntax error")`. + +### Un seul `=` n'est jamais un opérateur binaire + +`bool_op()` exclut délibérément `tag("=")` de ses alternatives, et `op()` n'a plus aucun repli sur +un `=` isolé (voir la tentative avortée documentée juste au-dessus — `compute_operators` paniquerait +dessus, ce qui est exactement ce qui a été observé en essayant). `=` n'existe que dans des positions +de grammaire dédiées : champs nommés (`x = 1`), affectation top-level (`assign()`), valeurs par +défaut de paramètre. Un test d'égalité s'écrit toujours `==`. + +```typr +if (a = b) { /* ... */ } // ne teste PAS l'égalité — utiliser a == b +``` + +### `record` vs `tuple` : la forme des champs décide, pas le mot-clé + +`list{...}`/`:{...}` peuvent produire un `Lang::List` (record) *ou* un `Lang::Tuple` selon la +forme des éléments — `record` est toujours essayé en premier dans `single_element`, mais son +parseur de champ (`argument_val`) exige la forme `nom = valeur` ; s'il échoue sur un premier +élément positionnel, tout le bloc retombe sur `tuple_exp`. + +```typr +list{ a = 1, b = 2 } // → Lang::List (record, champs nommés) +list{ 1, 2, 3 } // → Lang::Tuple (même mot-clé "list", positionnel !) +:{ x = 1, y = 2 } // → Lang::List +:{ 1, 2, 3 } // → Lang::Tuple +``` + +### La « règle d'or » des blocs `{...}` génériques + +Un bloc `{ ... }` qui suit un nom de type paramétré en fait toujours un *record constructeur* +(`Tibble[3]{ id: int }`), jamais un simple usage d'alias. Deux violations de cette règle sont +détectées explicitement plutôt que de mal parser silencieusement : + +```typr +Df[8, int]{ name: char } // SyntaxError::RecordConstructorIndex — un seul index autorisé +Array[5, { a: int }] // SyntaxError::RecordInRecursiveParams — bloc record interdit ici +``` + +Un cas plus subtil couvert par un test de régression dédié : `ltype` ne doit pas confondre le +type de retour d'une fonction avec le corps qui suit — `fn(i: int): Feux { .Rouge }` doit +s'arrêter à `Feux` et laisser `{ .Rouge }` au corps de la fonction, pas l'avaler comme si +`Feux{...}` était un record constructeur. + +### Alias de type PascalCase à une seule lettre : imparsable — *ouvert* + +`pascal_case_no_space` (utilisé par `type_alias`, donc par `type`/`opaque`/`typeconstructor`) est +défini comme `(one_of("A".."Z"), alphanumeric1)` — `alphanumeric1` exige **au moins un** caractère +alphanumérique après la majuscule initiale. Un nom de type d'une seule lettre échoue donc +totalement à ce niveau et retombe ailleurs dans la grammaire (souvent une variable bidon). + +```typr +type A <- int; // n'est PAS un Alias — utiliser au moins 2 caractères +type Ab <- int; // OK +``` + +Asymétrie notable : les noms de *tag*/variante d'union utilisent `pascal_case_helper` +(`opt(alpha1)` — le suffixe est optionnel), donc `.X` (tag à une lettre) parse très bien. Seuls +les noms d'alias de type exigent 2 caractères ou plus. + +### `fn(...)` interdit en position de type + +`fn(args) -> Type` ressemble à un type fonction mais n'est légal qu'au niveau expression ; en +position de type, seule la forme `(Type, Type) -> Type` (sans `fn`) est valide. Le parseur capture +spécifiquement le cas d'erreur (`fn_function_type_error`) pour donner un message ciblé plutôt +qu'un échec générique. + +```typr +let f: fn(a: int) -> int <- ...; // SyntaxError::FunctionTypeSyntax +let f: (int) -> int <- ...; // OK +``` + +### Commentaires `.ty` : uniquement `#` + +`comment()` ne reconnaît que `tag("#")` — il n'existe aucune alternative `//` nulle part dans +`base_parse`. Un fichier de signatures stdlib écrit avec des commentaires `//` n'est pas juste mal +commenté : il échoue à parser et `build_typed_vartype` l'ignore silencieusement en entier +(« Skipped … (syntax not supported) ») — surveiller cette ligne dans la sortie de `typr std`. + +### `use` : un seul mot-clé, quatre grammaires distinctes + +`use` introduit quatre constructions entièrement différentes, désambiguïsées seulement par ce qui +suit immédiatement, dans cet ordre d'essai : + +| Forme | Reconnue si | Produit | +|---|---|---| +| `use("dplyr", c(...));` | `use(` littéral, sans espace | adaptateur R legacy (`Lang::Use`) | +| `use Math::pi;` | un `::` suit le premier identifiant | `Lang::UseModule` | +| `use MonAlias;` | identifiant `PascalCase`, pas de `::` | `Lang::Import` (type) | +| `use ma_variable;` | identifiant `snake_case`, pas de `::` | `Let`/`Alias` (variable) | + +### Détails plus fins + +- `in` comme opérateur de type-condition est tokenisé `tag("in ")` — l'espace final fait partie + du motif, donc `in` immédiatement suivi d'une parenthèse sans espace ne matchera pas. +- `embed` (embedding de type nommé sur un champ de record) est un mot-clé « doux » reconnu + seulement via `tag("embed") + multispace1` — un champ réellement nommé `embed` (suivi + directement de `:`, sans espace) reste donc parsable comme nom de champ ordinaire. +- Le spread statique `..source` (nominal) exige explicitement `not(char('.'))` après les deux + points pour ne jamais être confondu avec `...source` (spread runtime) ou `...` (paramètre + variadique) — les trois partagent un préfixe visuel. +- `nom::alt()` plafonne à 21 alternatives par tuple : `single_element` et `single_type` sont + chacun scindés en deux `alt()` imbriqués pour rester sous la limite. Ajouter un nouveau + combinator à l'un de ces points d'entrée demande de respecter ce découpage, pas d'ajouter une + 22ᵉ branche à plat. From 88d71deb28d8f1a63aa53b7bbfe689bf2afc0ee1 Mon Sep 17 00:00:00 2001 From: Fabrice Date: Tue, 8 Sep 2026 11:00:15 +0200 Subject: [PATCH 3/6] added TypR's logo --- README.md | 4 ++-- src/typr_carre.png | Bin 0 -> 37313 bytes 2 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 src/typr_carre.png diff --git a/README.md b/README.md index 03da5ece..5e479ed2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@
-# TypR +# TypR logo TypR **A typed superset of R** — static type checking, modules, sum types and interfaces, compiled to idiomatic, readable R. @@ -44,7 +44,7 @@ normalize <- function(x, na.rm = FALSE) { -```typr +```r let normalize <- fn(x: [num], na.rm: bool = false): [num] { if (na.rm) { x <- x[!is.na(x)] }; (x - mean(x)) / sd(x) diff --git a/src/typr_carre.png b/src/typr_carre.png new file mode 100644 index 0000000000000000000000000000000000000000..73fe6a601a4ef2dbba3f0a5df80da273cfcf3f03 GIT binary patch literal 37313 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

xJ_WZMe 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 Date: Tue, 8 Sep 2026 11:10:50 +0200 Subject: [PATCH 4/6] centered logo --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5e479ed2..066e7a05 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@

-# TypR logo TypR +TypR logo + +# TypR **A typed superset of R** — static type checking, modules, sum types and interfaces, compiled to idiomatic, readable R. From 3a0fb999893218145116eaeb050398165f961702 Mon Sep 17 00:00:00 2001 From: Fabrice Date: Tue, 8 Sep 2026 11:32:06 +0200 Subject: [PATCH 5/6] adjusted logo size --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 066e7a05..b04399e4 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@
-TypR logo +TypR logo # TypR From b8e97a4beafaee9db7b57ab2eda5cb6fb6c97ace Mon Sep 17 00:00:00 2001 From: Fabrice Date: Tue, 8 Sep 2026 11:33:12 +0200 Subject: [PATCH 6/6] adjusted logo size again --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b04399e4..667e624d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@
-TypR logo +TypR logo # TypR