Résumé. Construire un catalogue unique et curaté des fonctions de la standard library R (
base, puisstats,utils, …), typées pour TypR, qui alimente deux artefacts distincts :
- la doc technique consommée par le MCP (riche, tous les degrés de confiance, jamais chargée par le compilateur) ;
- la surcouche typée embarquée dans le compilateur (sous-ensemble « haute confiance », seul à finir dans les
.bin).La dépendance est inversée : la stdlib compilée devient un sous-produit du catalogue de doc, pas l'inverse.
Offrir aux utilisateurs (et au MCP) des signatures typées pour les fonctions R classiques, pour ne pas avoir à écrire de signature ni mémoriser les types. Deux bénéficiaires, deux tolérances différentes :
| Bénéficiaire | A besoin de | Tolérance à l'erreur |
|---|---|---|
| MCP / doc | signatures + sémantique (params, exemples, règles de coercion, pièges) | faible — un mauvais exemple induit en erreur |
| Compilateur | signatures vérifiables uniquement | très faible — une signature fausse produit des erreurs de type déroutantes ou des casts R erronés |
Le risque principal n'est jamais la mémoire — c'est la soundness. Une signature fausse est pire que pas de signature.
Le mécanisme est en place et fonctionnel. Voici l'état après Phase 0 + 1 + 2 + 3 :
- Noms non typés :
crates/typr-cli/configs/src/functions_R.txt(761 noms) →.std_r.bin(build_function_list_vartype, chaque nom =UnknownFunction). - Introspection R :
configs/src/r_name_db.json(1283 noms, seed base-R, pkg, s3_generic, s4_generic).
Tous les fichiers .ty portent désormais des annotations #! (pkg, tier,
param, ret, coercion, example, seealso) conformes au format RFC.
Sink B — compilateur (R_T1_SOURCES, → .std_r_typed.bin) :
| Fichier | Signatures @ |
Let bindings | Description |
|---|---|---|---|
std_R.ty |
29 | 0 | Opérateurs (+,-,*,/,%%,&&,||), sum, print, reduce/fold/extend, testing, interop |
default.ty |
20 | 0 | TypR-specific : as_vec, map, filter, set_at, add/minus/mul/div, get, seq, join, startsWith, endsWith, contains |
file.ty |
11 | 0 | Système de fichiers (getwd, setwd, dir, file__exists, ...) |
option.ty |
3 | 5 | Option type + unwrap/expect/is_some/is_none |
factor.ty |
6 | 0 | Factor opaque + factor/levels/nlevels |
state.ty |
7 | 0 | State mutable cell + get/set/update/map/derive |
ord.ty |
2 | 0 | Eq/Ord interfaces + unique/sort |
lin_alg.ty |
2 | 2 | dot, t, lvec, cvec |
plot.ty |
1 | 2 | plot signature + Plot record (champ kind), bplot, show |
system.ty |
3 | 0 | system2, bsystem2, exec |
foreign.ty |
0 | 0 | Foreign opaque type |
Sink A — MCP/doc uniquement (R_DOC_ONLY_SOURCES) :
| Fichier | Signatures @ |
Description |
|---|---|---|
base.ty |
128 | Fonctions R base (math, stats, comparaison, string, set, matrix, vector) — doc SPG seulement |
stats.ty |
103 | Fonctions du package stats (dnorm…rhyper, t.test, cor.test, lm, …) — doc SPG seulement |
utils.ty |
55 | Fonctions du package utils (read.csv, write.csv, head, …) — doc SPG seulement |
⚠️ Contrainte architecturale :base.tyest dansR_DOC_ONLY_SOURCES(pasR_T1_SOURCES) carstdlib_declared_names()ne doit contenir que les fonctions TypR-propriété (celles pour lesquelles TypR fournit sa propre implémentation dansstd.Rou la codegen du transpileur). Les fonctions R base (abs,sqrt,mean, etc.) ne sont pas TypR-propriété — TypR ne fait que les annoter en types pour la doc MCP. Les inclure dansstdlib_declared_names()casserait le teststdlib_declared_names_covers_the_bundled_ty_signatures.
StdlibMeta(model.rs) : champs optionnelstier,param_docs,ret_doc,coercion_notes,examples,seealso,pkg— tousOption<>, rétro-compatibles.stdlib_meta.rs: parserparse_meta_from_source()— accumulate les blocs#!jusqu'à la ligne@name:, puis attache au nom. 8 tests unitaires.doc_attach.rs: lit les annotations#!des.ty(attach par nom).build_stdlib_docs(): chaîneR_T1_SOURCES+R_DOC_ONLY_SOURCES→ SPG enrichi avec méta.- Tests : 7 tests
standard_library+ 8 testsstdlib_meta= 15/15 ✅.
typr std: régénère.std_r.bin+.std_r_typed.bin+.std_js.bin+.std_js_typed.bin.typr std doc: émet le SPG JSON-LD enrichi (T1+T2, avec méta).typr std doc --format md: émet un digest markdown compact (groupé par package, badge T1/T2, signature, 1 doc, 1 exemple, seealso). Sortie : stdout ou--output <fichier>.
- Digest markdown généré par
typr std doc --format md, embarqué viainclude_str!danscrates/typr-mcp/data/stdlib.md(~60 Ko, 386 entités, 14 packages). - Exposé comme ressource lecture seule (
typr://stdlib) — le MCP la récupère à la demande, jamais chargée d'office → zéro surcharge de contexte. - Régénérer après tout changement dans
configs/std/*.ty:typr std doc --format md --output crates/typr-mcp/data/stdlib.md
load_r()/load_typed_r()danscrates/typr-core/src/components/context/vartype.rs, blobs bincode embarqués parinclude_bytes!dansconfigs/bin/.
- 60 noms refusés (
c,lapply,sapply,rep,str,cat,length, …) danscrates/typr-core/src/utils/standard_library.rs(+not_in_blacklist/validate_vectorization).
plot.tyetsystem.tydéclarent des records (type Plot = record{ … },type System2 = record{ … }). Le parser accepte la forme expliciterecord{ … }(spec §2.2) ; la forme nue{ … }au niveau ltype top n'est pas supportée (c'était la cause réelle du SKIPPED historique des deux fichiers, pas le type-cast ni le champ). Phase 2 les a adaptés en conséquence (record{…}, renommage du champtype→kindpour éviter une collision sémantique avec le mot-clé de déclaration) : les deux fichiers compilent désormais, zéro SKIPPED.
⚠️ Constat clé : la blacklist n'existe pas pour la mémoire mais pour la soundness.c(),lapply()… sont intrinsèquement non typables fidèlement en base R (coercion, recycling,..., dispatch S3). Tout le plan doit préserver ce principe.
- Une seule source de vérité : le catalogue (fichiers
.tyenrichis, un par package). Rien n'est obtenu ailleurs — ni les.bin, ni le SPG doc. - Deux sinks, deux exigences :
- Sink A — MCP/doc : consomme tout (T1 + T2), enrichi (exemples, notes de coercion, relations). Jamais chargé par le compilateur.
- Sink B — compilateur : ne charge que le sous-ensemble T1
(haute confiance) dans
.std_*_typed.bin.
- Dépendance inversée : on ne dérive pas la doc de la stdlib compilée ; on dérive la stdlib compilée du catalogue de doc.
- Soundness over coverage : en cas de doute →
UnknownFunction+ pas d'erreur (l'utilisateur n'est jamais forcé d'écrire une signature, mais on ne ment jamais sur un type). - Le compilateur est l'oracle du MCP : la doc réduit l'espace de recherche
du LLM,
typr checkreste l'arbitre final (philosophie déjà appliquée en CI parnpm run check:examples). - TypR-propriété vs R base : seules les fonctions pour lesquelles TypR
fournit sa propre implémentation (std.R, codegen transpileur) entrent dans
stdlib_declared_names(). Les fonctions R base annotées en types vont dansR_DOC_ONLY_SOURCES(doc SPG uniquement).
CATALOGUE (source de vérité)
configs/std/<package>.ty — signatures @ + métadonnées
(base.ty, stats.ty, utils.ty, …) TIERs T1/T2/T3
│
┌───────────────┴────────────────┐
▼ ▼
Sink A — MCP/doc Sink B — compilateur
typr std doc typr std
SPG JSON-LD enrichi .std_r_typed.bin (T1 seul)
(T1+T2, exemples, pièges) .std_r.bin (UnknownFunction, T3)
│ │
▼ ▼
typr std doc --format md vartype.rs load_typed_r()
→ crates/typr-mcp/data/stdlib.md │
│ ▼
▼ Context typechecking
ressource MCP typr://stdlib
(lecture seule, lookup à la demande)
│
└──► MCP propose avec la doc, VÉRIFIE avec `typr check`
Règles de flux :
| Symbole | Dans functions_*.txt |
Dans .ty T1 |
Dans .ty T2 |
Doc MCP |
|---|---|---|---|---|
pur, typable (ex. abs, sqrt, nrow) |
oui | oui | — | oui |
typable mais souple (ex. paste T2, unions/Any) |
oui | non | oui | oui |
non typable (ex. c, lapply, with) |
oui | non | non | oui, signale « à typer soi-même » |
- Le compilateur ne voit jamais T2 ni T3 : on garde zéro coût mémoire pour
les tiers 2–3 (un symbole T2/T3 =
UnknownFunction, pas de blob en plus). - Le MCP voit tout, y compris T3, avec l'avertissement approprié.
| Constante | Fichiers | Consommé par |
|---|---|---|
R_T1_SOURCES |
std_R.ty, default.ty, file.ty, option.ty, lin_alg.ty, factor.ty, state.ty, ord.ty, plot.ty, system.ty | typr std (compilateur) + typr std doc (SPG) |
R_DOC_ONLY_SOURCES |
base.ty, stats.ty, utils.ty | typr std doc (SPG uniquement) |
Un fichier .ty par package R : base.ty, stats.ty, utils.ty, puis
graphics.ty, grDevices.ty, methods.ty (S4, faible priorité), enfin
datasets.ty. On s'inspire de la structure existante (configs/std/*.ty).
Réutiliser la syntaxe existante @name: (T) -> U; — les pipeline et tests
(preprocess_ty_source, build_typed_vartype) fonctionnent déjà avec.
Format #! implémenté et testé. Chaque bloc #! précède la ligne @name: :
#! pkg: base
#! tier: T1
#! param x: Valeurs à sommer, NA exclues si na.rm = true
#! ret: NA si x vide et na.rm = false
#! coercion: logical/int -> num par R (TypR: exiger num explicitement ?)
#! example: sum(c(1, 2, 3)) # -> 6
#! example: sum(c(1, NA), na.rm = true) # -> 1 (décisif : comportement NA)
#! seealso: cumsum, prod, rowSums
@sum: (vec[N, num]) -> num;
Champs disponibles : pkg, tier, param × n, ret, coercion/note,
example × n, seealso. Le parser (stdlib_meta.rs) gère les formes
compactes #! key:value et #! key value.
- Boucler la première liste de candidats depuis
functions_R.txt(761 noms)r_name_db.json(pkg, s3_generic). 1283 noms base-R identifiés.
- Classer T1/T2/T3 — classification heuristique disponible via
r_name_db.json(s3_generic, s4_generic) + liste noire 60 noms. - Créer
configs/std/base.ty— squelette vide avec conventions#!✅.
model.rs:StdlibMetaavec tous les champsOption<>✅.stdlib_meta.rs: parserparse_meta_from_source()avec 8 tests ✅.doc_attach.rs: lit les annotations#!✅.build_stdlib_docs(): chaîne T1+T2, SPG enrichi ✅.- Tests :
spg_nodes_carry_stdlib_meta_end_to_end,doc_only_sources_are_in_spg_but_not_in_typed_bin✅.
Migration des signatures existantes ✅ :
- Tous les fichiers
.ty(std_R.ty, default.ty, option.ty, factor.ty, state.ty, file.ty, system.ty, plot.ty, lin_alg.ty, ord.ty, foreign.ty) portent désormais des annotations#!complètes. - 10 doublons inter-fichiers identifiés et résolus (substr, sub, gsub, strsplit, tolower, toupper, grepl → base.ty ; seq → default.ty ; unique, sort → ord.ty).
Catalogue R base ✅ :
base.ty: 128 signatures R base (math, stats, comparaison, string, set, matrix, vector, type-checking, type-coercion) avec annotations#!complètes (pkg, tier, param, ret, coercion, example, seealso).stats.ty: 103 signatures du package stats (dnorm…rhyper, pwilcox, t.test, cor.test, IQR, …), annotations#!complètes.utils.ty: 55 signatures du package utils (read.csv, write.csv, head, adist, …), annotations#!complètes.- Positionnement
R_DOC_ONLY_SOURCES(doc SPG uniquement, pas compilateur).
Noms pointés / variadiques : les signatures dont le nom de fonction contient un
.(read.csv,cor.test, …) sont quantifiées par des backticks (@`read.csv` : …) et les paramètres variadiques...name:sont conservés tels quels — le préprocesseur ne strip que les vrais noms de paramètres (na.rm,row.names), jamais le marqueur variadique. Les types 3D ([#N, #M, int]) et les listes[#, T]sont ré-écrits sous forme imbriquée[#N, [#M, int]](le parseur les rejette sinon, et une ligne rejetée empoisonne silencieusement tout ce qui suit dans le fichier).
Reste à faire :
-
stats.ty: fonctions du package stats (rnorm, dnorm, t.test, lm, …) -
utils.ty: fonctions du package utils (read.csv, write.csv, …) -
plot.ty/system.ty: records déclarés viarecord{ … }(forme nue{ … }rejetée), champtyperenommékind→ zéro SKIPPED (typr stdcompilent tous les fichiers, exit 0, 761 entrées). - Déterminisme du cache incrémental : clés de cache désormais stables
entre processus (les
HashSetdes variantsRecord/Interface/RClassétaient Debug-sérialisés en ordre aléatoire ;Context::fingerprintles rend dans un ordre canonique trié). - Validation
typr case run(aucun REGRESS) - Circuit de validation :
cargo test --workspacevert -
typr std docrégénéré et référencé (pack MCP, Phase 3)
Garde-fou :
build_typed_vartypeSKIP silencieusement tout fichier qui panique (standard_library.rs). Un.tytrop ambitieux qui fait paniquer le parseur ⇒ entrées absentes du binaire ⇒ régression silencieuse. Toujours traiter un SKIPPED comme un bug critique dans ce circuit.
- Digest compact ✅ :
typr std doc --format md— renderer SPG→markdown dansmd_renderer.rs(suivant le patternrd_renderer.rs). Sortie : digest groupé par package (## \pkg` package), chaque entrée =- `T1` `name` `(params) -> ret` — 1 ligne doc` + 1 bloc example- seealso. 7 tests unitaires.
- Fichiers de référence ✅ :
typr std doc --format md --output crates/typr-mcp/data/stdlib.md— digest vendored (~60 Ko, 386 entités, 14 packages, 1723 lignes). Régénérable à volonté. - Ressource MCP ✅ :
typr://stdlibexposée danstypr-mcp/src/lib.rs(même pattern quetypr://lexicon/typr://operators:include_str!,syntax_resources(),read_syntax_resource()). 2 tests MCP ajoutés. - Oracle ✅ (pré-existant) :
typr check/typr buildsont déjà des outils du MCP (toolschecketbuild) — pas de travail supplémentaire nécessaire. - Vérification des exemples : les
#! example:du catalogue passent dans un circuit de validation (cf. Phase 4) — jamais de faux exemple dans la doc.
- Tests Rust dans
crates/typr-cli(suitestandard_library.rs) ✅ :- toute signature
@…du catalogue parse et type-check (zéro SKIPPED) (all_catalog_signatures_parse_and_typecheck_without_skipped) ; stdlib_declared_names()⊇ noms.tyTypR-propriété (pas R base) (stdlib_declared_names_covers_bundled_typr_owned_signatures) ;- cohérence des tiers : un nom T3 dans
functions_R.txtn'est dans aucun.tyT1 (tier_consistency_blacklisted_or_t3_names_not_in_t1_sources) ; - tous les
#! example:sont compilables partypr checkou annotés par# noplayground:(all_hash_bang_examples_are_typecheckable).
- toute signature
- Consigne blacklist ↔ catalogue ✅ : un nom blacklisté qui « se typifie un jour » doit passer par un audit explicite (
tier_consistency_blacklisted_or_t3_names_not_in_t1_sources). - Nettoyage Markdown ✅ :
md_renderer.rsfiltre# noplayground:pour conserver une doc propre sur la ressource MCPtypr://stdlib.
Même mécanisme pour dplyr, ggplot2, stringr, … via
configs/std/ext/dplyr.ty et la résolution @extern pkg::name: déjà supportée
(stdlib_declared_names). La doc MCP s'étend, le compilateur reste à T1
seulement, et pour les packages externes on exige une preuve (test case
cases/) par entrée T1 promue.
Une fonction T2 ne monte en T1 que si toutes les cases sont cochées :
- comportement pur et stable (pas de dispatch S3 variable sur args) ;
- pas de
..., ou variadique strictement contraint et vérifiable ; - pas de coercion silencieuse entre types TypR distincts ;
- comportement
NA/NULL/vide documenté (na.rm, empreintevec[0]) ; - au moins un
#! example:qui verrouille un piège (NA, recycling…) ; - le compilateur, avec la signature, ne crée aucune erreur nouvelle sur
cases/+ suite complètecargo test --workspace.
Promouvoir sans passer la porte = régression soundness.
Quantifié : une entrée typée ≈ 100–300 octets bincode. Même 800 fonctions T1 ≈
quelques centaines de Ko au pire, chargées une fois via Arc et partagées —
négligeable en natif comme en WASM (le blob .std_r_typed.bin fait déjà ça).
Pour minimiser malgré tout :
- T2/T3 ne pèsent rien dans le compilateur (jamais embarqués) ;
- la surcouche T1 reste un blob séparé → on peut la passer en chargement paresseux plus tard (désérialisation à la demande par symbole) sans changer le format ;
- les
<package>.tyne sont jamais embarqués dans le binaire : ils sont compilés en.binune seule fois (typr std), comme aujourd'hui.
Donc : le poids n'est pas un argument pour restreindre la couverture — la soundness l'est.
| Risque | Mitigation |
|---|---|
| Signature fausse → erreurs de type / casts erronés | Porte de promotion T1 (section 7), blacklist maintenue |
.ty qui panique → entrées absentes du binaire |
SKIPPED = erreur critique ; test « zéro SKIPPED » en CI |
| Exemple faux dans la doc MCP | Circuit #! example: → typr check en CI (Phase 4) |
Régression suite cases/ |
typr case run bloque ; toute promotion vérifie zéro REGRESS |
| Dérive catalogue ↔ code compilé | .bin régénérés et committés à chaque changement ; tests de cohérence |
| Clés de cache incrémental instables entre processus | Context::fingerprint() rend les Type via un renderer canonique (variants Record/Interface/RClass triés à chaque niveau), testé par module_cache_output_is_byte_identical_to_clean_build |
| Surcharge de contexte MCP | Pack lookup-à-la-demande (ressource typr://stdlib, read-only), jamais inondé d'office |
Fonctions R base dans stdlib_declared_names() |
base.ty dans R_DOC_ONLY_SOURCES, exclu de stdlib_declared_names() |
stdlib.md déconnecté du catalogue |
Fichier vendored régénérable par typr std doc --format md ; même pattern que lexicon.md / operators.md (pas de CI de resync automatique, refresh à la main) |
typr std+typr std docrégénèrent, sans SKIPPED non voulus, des artefacts qui passentcargo test --workspaceettypr case run.- Un utilisateur peut écrire
sum(x)/mean(x, na.rm = true)sans signature et le compilateur infèrenum(par exemple), avec une erreur claire si l'argument est manifestement mal typé. - Le MCP, face à « comment fait-on X en TypR ? », répond correctement en
s'appuyant sur le digest et vérifie son code par
typr check(oracle). - Zéro régression de soundness : la suite
cases/ne comporte aucun REGRESS à la fin de chaque phase. - Le catalogue couvre base (T1+T2) puis s'étend à stats/utils sans nouveau coût mémoire compilateur.
- Typage « parfait » de
c()et des foncteurs dynamiques (lapply,do.call) — restent enUnknownFunction+ note doc. - S4
methodscomplet. - Génération de la doc site (
typr.github.io) depuis le catalogue — c'est un renderer de plus sur le SPG, à faire après le pack MCP. - Chargement paresseux du blob T1 (optimisation, future).