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
121 changes: 93 additions & 28 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -1,28 +1,93 @@
## Ce que fait cette PR

<!-- Une ou deux phrases. -->

## Vérifications

- [ ] `cargo test --workspace` passe en local
- [ ] `cargo fmt --all` appliqué
- [ ] `typr case run` ne montre aucun `REGRESS`

## Si cette PR touche le parseur ou le typage

- [ ] Un cas a été ajouté dans `cases/` (`typr case add`) pour le comportement
corrigé, ou une raison de ne pas en ajouter est donnée ci-dessous

## Si cette PR est une RFC

- [ ] Le fichier est `rfcs/0000-<slug>.md` (le numéro est attribué à la fusion,
c'est celui de cette PR) et suit `rfcs/0000-template.md`

Une RFC ne se relit pas comme du code : les fils de commentaires se posent sur
les phrases. Évite de force-pusher par-dessus une relecture en cours. Voir
[rfcs/README.md](../rfcs/README.md).

## Si cette PR change une version

Les versions ne se modifient pas à la main : `nu publish.nu bump <niveau>` puis
`nu publish.nu sync`. Le job `coherence` échoue sinon. Voir `RELEASING.md`.
<!--
Thank you for contributing to TypR!

Target branch: `develop` or `tester` (NOT `main` since it is the release branch and `nu publish.nu release` refuses to publish from elsewhere).
Read CONTRIBUTING.md before opening a PR if you haven't yet.
Delete sections that don't apply rather than leaving unchecked boxes.
-->

Fixes #NNNN

## Summary

<!-- 1–3 sentences: what changed, and why. -->

## RFC boundary

<!-- CONTRIBUTING.md: if the answer to "What does TypR here?" changes (so a big change in its direction), it's an RFC. If the compiler is only catching up to an already-given answer, it's an issue. -->

- [ ] This PR does **not** change what TypR does. It implements/fixes already-decided behavior (link the issue).
- [ ] This PR **does** change language behavior (new syntax, typing rule, or generated R form). Linked RFC exists in `rfcs/`: #

*(If neither box can honestly be checked, the idea probably belongs in Discussions → Ideas first.)*

## Type of change

<!-- Keep what applies, delete the rest. -->

- [ ] Compiler core (`crates/typr-core`): parsing, type checking, transpilation
- [ ] CLI (`crates/typr-cli`)
- [ ] LSP (`crates/typr-lsp`)
- [ ] MCP server (`crates/typr-mcp`), including `tutor` lesson data
- [ ] Editor integrations (`editors/` VS Code/Positron, RStudio addins, Vim/Neovim)
- [ ] Cases catalog (`cases/`)
- [ ] Packaging / docker / install / release tooling

## Verification checklist (all PRs)

These mirror the CI jobs and they block merge anyway. Check them before so you don't wait on CI:

- [ ] `cargo test --workspace` passes locally.
*(Note: `export RUST_MIN_STACK=8388608` first, type checking recursion overflows the default 2 MB stack.)*
- [ ] `cargo fmt --all` applied.
- [ ] `typr case run` shows no 'REGRESS'.
- [ ] No binaries committed; versions stay aligned (CI `coherence`).
- [ ] Release build compiles (`cargo build --release`, CI `build-check`).
- [ ] If touching `editors/`: VS Code extension compiles and the R package is analyzable (CI `editors`).

## If this PR touches the parser or type system

Per CONTRIBUTING.md: a new case should be added to `cases/` (`typr case add`) for the corrected behavior, **or** a reason for not adding one is given below.

<!-- Briefly explain why a case is not necessary if applicable. -->

**Reason for not adding a case:**

<!-- Delete this section if a case was added. Otherwise, explain here why a case is not needed. -->

## If this PR is an RFC

The file is `rfcs/0000-<slug>.md` (the number is assigned at merge time, which is this PR's number) and follows `rfcs/0000-template.md`.

> An RFC discussion does not work like code: comment threads should be posed on specific sentences. Avoid talking-over-an-existing-review in progress. See [`rfcs/README.md`](./rfcs/README.md).

## Compiler core details

- [ ] Existing R code still behaves identically (TypR is a complement of R, not a fork of its semantics).
- [ ] Generated R stays idiomatic, readers should not see artifacts of this change.
- [ ] Behavior justified: either an issue (already-decided behavior) or an accepted RFC is linked above.

## Editor grammars

Hand-written grammars have historically drifted: highlighting Rust keywords (`impl`, `trait`, `mut`, …) that don't exist in TypR while missing real TypR keywords (`opaque`, `module`, `record`, `object`, `interface`, `typeconstructor`, `recursive`, `embed`, `@export`, kind sigils).

- [ ] If this PR adds/changes TypR keywords or tokens: every editor grammar in `editors/` was updated to match.

## MCP server

The MCP server exposes the compiler to AI agents (Claude Code, Claude Desktop, …), tools run in-process, no filesystem, no `.typr_cache`.

- [ ] Tool behavior and output shape are unchanged, or any change is intentional and documented (agents depend on this contract).
- [ ] If `tutor` lessons were touched: `typr tutor check` still passes for affected lessons.

## Documentation

User-facing documentation lives in the separate `we-data-ch/typr.github.io` repo.

- [ ] User-facing changes have a corresponding PR in `we-data-ch/typr.github.io`.
*(Warning: `onBrokenLinks` is set to `throw` there, a dead link fails that repo's CI before merge, not after.)*
- [ ] Link to the docs PR: #

## Test plan

<!-- How you verified this beyond CI: commands run, cases exercised, editor behavior manually confirmed if applicable. -->
196 changes: 76 additions & 120 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,120 +1,76 @@
# Contribuer à TypR

## Modèle de branches

- `main` — ce qui est publiable. Les tags de release en partent, et
`nu publish.nu release` refuse de publier depuis autre chose.
- `develop` — intégration.
- Les contributions passent par une PR vers `develop`.

`main` devrait être protégée sur GitHub : PR obligatoire, CI verte requise.
Sans ça, la garde de `publish.nu` ne protège que celui qui l'utilise.

## Ce que la CI vérifie

| Job | Vérifie |
|---|---|
| `test` | `cargo test --workspace` — 868 tests, bloquant |
| `build-check` | compilation en `--release` |
| `cases` | rejeu du catalogue `cases/` — échoue sur toute `REGRESS` |
| `coherence` | versions alignées, aucun binaire commité |
| `editors` | l'extension VS Code compile, le package R est analysable |

`RUST_MIN_STACK=8388608` est indispensable : le typage est récursif et déborde
la pile par défaut de 2 Mo sur au moins un test. La CI le définit ; en local,
exporte-le si `cargo test` s'interrompt sur un `stack overflow`.

## Proposer un changement de langage (RFC)

Une correction de bug se discute dans une issue. Un **changement de langage** —
une syntaxe nouvelle, une règle de typage, une autre forme de R engendré — passe
d'abord par une RFC : une proposition écrite, relue en PR, fusionnée dans
`rfcs/` si elle est acceptée. Le but n'est pas la cérémonie, c'est que le
*raisonnement* survive : six mois plus tard, « pourquoi TypR fait comme ça ? » a
une réponse écrite.

La frontière : **si la réponse à « que fait TypR ici ? » change, c'est une
RFC ; si le compilateur ne fait que rattraper une réponse déjà donnée, c'est une
issue.** Une idée encore floue va dans la catégorie
[Ideas](https://github.com/we-data-ch/typr/discussions/categories/ideas) des
Discussions — c'est souvent là qu'elle se règle, et sinon elle en sort meilleure.

Le processus complet, les étiquettes (`rfc-draft` / `rfc-accepted` /
`rfc-rejected`) et le gabarit sont dans [rfcs/README.md](rfcs/README.md). Les
notes de conception du workspace (`spécifications/`, `ai_context/*.md`) en sont
la matière première : une RFC, c'est leur face publique.

## Travailler sur le parseur ou le typage

Le catalogue `cases/` est un ensemble de reproductions curées, chacune avec sa
sortie R attendue. C'est à la fois la documentation d'un comportement et son
test de non-régression.

```bash
typr case list # état du catalogue
typr case run # rejoue tout ; sort en 1 sur REGRESS
typr case add --from <proj> # crée un cas depuis un vrai projet
typr case freeze <nnnn> # fige la sortie actuelle comme golden
```

Une correction de bug sans cas associé n'a rien qui l'empêche de revenir.

## Travailler sur la coloration syntaxique

Les grammaires d'éditeur sont **générées**. La source de vérité unique est le
manifeste de syntaxe, `crates/typr-core/src/components/syntax/mod.rs` : une
liste ordonnée de règles, une entrée par lexème que le parseur reconnaît
réellement.

```bash
typr syntax --json # le manifeste
typr syntax --target tmlanguage
typr syntax --write # régénère les grammaires sous editors/
typr syntax --check # échoue si une grammaire commitée a dérivé
```

N'édite jamais `editors/vscode/syntaxes/typr.tmLanguage.json` à la main : le
fichier porte un en-tête qui le dit, et `cargo test -p typr-cli` le compare à
ce que le manifeste produit.

Deux tests gardent l'invariant dans les deux sens
(`components::syntax::tests`) :

- ajouter un `tag("…")` au parseur sans l'ajouter au manifeste fait échouer
`every_parser_tag_is_in_the_manifest` ;
- mettre dans le manifeste un mot que le parseur ne connaît pas fait échouer
`manifest_claims_no_word_the_parser_does_not_know`.

C'est le second qui manquait : les grammaires écrites à la main coloraient
`impl`, `trait`, `struct`, `enum`, `where`, `mut`, `Option`, `Result` —
copiés d'une grammaire Rust, inexistants en TypR — pendant que `opaque`,
`module`, `record`, `object`, `interface`, `typeconstructor`, `recursive`,
`embed`, `@export` et les sigils de kind n'étaient colorés nulle part.

## Travailler sur la documentation

La documentation vit dans un dépôt séparé : `we-data-ch/typr.github.io`.
Chaque PR y déclenche une construction complète du site — `onBrokenLinks` est
réglé sur `throw`, donc un lien mort fait échouer la CI avant la fusion, pas
après.

Chaque bloc ` ```typr ` du site est passé au vrai compilateur en CI
(`npm run check:examples`) : un exemple qui cesse de compiler bloque la
construction. L'oracle est la **dernière release** — la version que le lecteur a
réellement installée — et un second workflow rejoue la même chose chaque nuit
contre `develop`, en avertissement seulement. C'est le préavis « la prochaine
release va casser tel exemple » : quand il se déclenche, la PR du compilateur et
celle de la doc doivent partir ensemble.

## Versions

Personne ne modifie une version à la main. `Cargo.toml [workspace.package]
version` est la seule source de vérité ; `nu publish.nu sync` la propage à
l'extension VS Code et au runner RStudio, et le job `coherence` échoue en cas
d'écart. Voir [RELEASING.md](RELEASING.md).

## Binaires

Aucun binaire ne doit être commité. Ceux du runner RStudio sont assemblés par la
CI au moment de construire le tarball de release. Le job `coherence` échoue si
un binaire réapparaît dans l'index.
# Contributing to TypR

## Branch model

- `main` holds what is publishable. Release tags are cut from it, and `nu publish.nu release` refuses to publish from anything else.
- `develop` is where integration happens.
- All contributions land through a PR targeting `develop`.

`main` should be protected on GitHub, with mandatory PRs and green CI. Without that, the guard in `publish.nu` only protects whoever happens to use it.

## What the CI checks

| Job | Checks |
|---|---|
| `test` | `cargo test --workspace` (868 tests, blocking) |
| `build-check` | `--release` compilation |
| `cases` | replays the `cases/` catalog and fails on any `REGRESS` |
| `coherence` | aligned versions, no committed binaries |
| `editors` | the VS Code extension compiles, the R package is analyzable |

`RUST_MIN_STACK=8388608` matters more than it looks. Type checking is recursive, and it overflows the default 2 MB stack on at least one test. CI sets it for you. Locally, export it yourself if `cargo test` ever dies with a `stack overflow`.

## Proposing a language change (RFC)

A bug fix is discussed in an issue. A **language change**, meaning new syntax, a typing rule, or a different form of generated R, goes through an RFC first. That means a written proposal, reviewed as a PR, merged into `rfcs/` if accepted. The point is not ceremony. The point is making sure the reasoning survives: six months later, when someone asks why TypR behaves this way, there is a written answer.

Here is the dividing line. If the answer to "what does TypR do here?" changes, it is an RFC. If the compiler is just catching up to an answer that was already given, it is an issue. An idea that is still fuzzy belongs in the [Ideas](https://github.com/we-data-ch/typr/discussions/categories/ideas) category of Discussions. It often gets resolved right there, and when it doesn't, it comes out sharper anyway.

The full process, the labels (`rfc-draft`, `rfc-accepted`, `rfc-rejected`), and the template live in [rfcs/README.md](rfcs/README.md). The workspace design notes (`spécifications/`, `ai_context/*.md`) are its raw material. An RFC is their public face.

## Working on the parser or the type system

The `cases/` catalog is a set of curated reproductions, each with its expected R output. It serves two purposes at once: documenting a behavior and testing that it does not regress.

```bash
typr case list # catalog status
typr case run # replays everything, exits 1 on REGRESS
typr case add --from <proj> # creates a case from a real project
typr case freeze <nnnn> # freezes the current output as golden
```

A bug fix without an associated case has nothing stopping it from coming back.

## Working on syntax highlighting

Editor grammars are **generated**. The single source of truth is the syntax manifest at `crates/typr-core/src/components/syntax/mod.rs`: an ordered list of rules, one entry per token the parser actually recognizes.

```bash
typr syntax --json # the manifest
typr syntax --target tmlanguage
typr syntax --write # regenerates the grammars under editors/
typr syntax --check # fails if a committed grammar has drifted
```

Never edit `editors/vscode/syntaxes/typr.tmLanguage.json` by hand. The file carries a header saying exactly that, and `cargo test -p typr-cli` compares it against what the manifest produces.

Two tests guard the invariant in both directions (`components::syntax::tests`):

- add a `tag("…")` to the parser without adding it to the manifest, and `every_parser_tag_is_in_the_manifest` fails;
- put a word in the manifest the parser does not know, and `manifest_claims_no_word_the_parser_does_not_know` fails.

The second one was missing, and it showed. The hand-written grammars happily highlighted `impl`, `trait`, `struct`, `enum`, `where`, `mut`, `Option`, and `Result`, all copied from a Rust grammar and none of them existing in TypR. Meanwhile `opaque`, `module`, `record`, `object`, `interface`, `typeconstructor`, `recursive`, `embed`, `@export`, and the kind sigils got no highlighting at all.

## Working on the documentation

The documentation lives in a separate repository: `we-data-ch/typr.github.io`. Every PR there triggers a full build of the site. `onBrokenLinks` is set to `throw`, so a dead link breaks the CI before merging rather than after.

Every ` ```typr ` block on the site is run through the real compiler in CI (`npm run check:examples`). If an example stops compiling, the build stops too. The oracle is the **latest release**, meaning the version readers actually have installed. A second workflow replays the same checks every night against `develop`, as warnings only. Think of it as the heads-up that the next release will break a given example. When it fires, the compiler PR and the doc PR should ship together.

## Versions

Nobody bumps a version by hand. `Cargo.toml [workspace.package] version` is the single source of truth. `nu publish.nu sync` propagates it to the VS Code extension and the RStudio runner, and the `coherence` job fails on any mismatch. See [RELEASING.md](RELEASING.md).

## Binaries

Never commit a binary. The RStudio runner's binaries are assembled by CI when building the release tarball, and the `coherence` job fails if one sneaks back into the index.
Loading
Loading