Changelog
0.0.9 (unreleased)
Langage
- Unions taggées (ADT) :
union Name[T] { Variant(field: T); Nullary; }déclare un type somme fermé. Les variantes avec payload se construisent commeOption.Some(42)et se matchent avecOption.Some{value} => { ... }; les nullaires s'utilisent comme des variantes d'enum (Option.None). Égalité structurelle, variantes hashables (nullaires et payload), toutes truthy. Paramètres génériques et annotations de type parsés (vérification statique à venir). Câblé sur les deux exécuteurs (VM par défaut + AST) via le nouvel opcodeMakeUnion. Voir UNIONS. - Structs hashables : les instances de struct sont utilisables comme clés
dict/ membres deset. Hash structural par défaut, override viaop_hash(self). Définirop ==sansop_hashrend l'instance unhashable (TypeError) pour préserver le contrata == b ⇒ hash(a) == hash(b). Une instance est figée dès qu'elle est hashée : toute mutation ultérieure d'un champ lèveTypeError, garantissant la stabilité du hash pendant la vie de la clé. Voir STRUCTURES § Hashabilité.
Tooling
- Linter
I103exhaustivité : généralisation auxuniontaggées. Le linter émet une diagnostic si unmatchsur une union ne couvre pas toutes ses variantes (et pas de wildcard_), avec la liste des variantes manquantes. Mutualise l'infrastructure existante pourenumet booleans. - Linter
W312dead store (--deep) : détecte une variable assignée puis écrasée avant lecture, via liveness backward sur le CFG. L'analyse construit un CFG par scope (top-level + chaque corps de lambda, paramètres seedés comme defs implicites du block entry), donc fonctionne aussi dans les fonctions. Les bindings implicites (for-var, pattern dematch,except) sont exclus. Les variables jamais lues nulle part restent du ressort de W200. - Linter
W313guard clause hint (--deep) : signaleelif/elserendus redondants par une branche précédente qui termine toujours (return/raise/break/continue). Suggestion : aplatir en guard clause. Severityhint. Lesif/elsesymétriquement terminants (if X { return } else { return }) ne sont pas signalés.
Module http
- Client HTTP :
http.get(url),http.post(url, body),http.put(url, body),http.delete(url)retournent uneResponseavec.status,.headers,.body. Backend ureq (rustls + gzip). Les 4xx/5xx remontent commeResponse, les erreurs réseau lèvent une exception. Body lu jusqu'à 32 MB par défaut. http.request(method, url, opts): dict d'optionsheaders,body,timeout(secondes),max_body(bytes).Response.json()parse le body en dict/list/str/int/float/bool/nil. Grands entiers (> SMALLINT_MAX, u64 > i64::MAX) promus en BigInt sans perte.- Helpers auth :
http.basic_auth(user, pass)→"Basic <base64>".http.bearer(token)→"Bearer <token>". À utiliser dansopts.headers.Authorization. - Serveur mode async :
Server.start()lance un thread accept qui pousse les requêtes dans un channel.Server.recv_async()pop sans bloquer (retourneRequestounil).Server.close()joint le thread proprement, et le drop du Server le fait automatiquement aussi. - Streaming chunked + SSE :
Request.start_chunked(status, content_type)etRequest.start_sse()consomment la requête et retournent unChunkedwriter (send_chunk,send_event(data, event_type?),end()).Dropenvoie le terminator si oublié. Refuse HEAD, HTTP/1.0 et statuses no-body (1xx/204/304) — utiliserrespond()pour ces cas. - Multipart côté serveur :
Request.multipart()retourne une liste de{ name, filename?, content_type?, data: bytes }. Boundary ancré sur les delimiter lines (pas de truncation sur bytes intérieurs), headers et paramètres case-insensitive (RFC 7578). - Cookies côté serveur :
Request.cookiesparse le headerCookie:endict[str, str]. Plusieurs headersCookie:sont fusionnés.
0.0.8 (2026-04-11)
Changements depuis v0.0.7 (2026-03-26).
Langage
- Type énuméré :
enum Name { variant1; variant2 }avec variantes qualifiéesName.variantet pattern matching dansmatch - Statement
import:import('math')bind automatiquementmathdans le scope courant. Les formes expression (m = import('math')) et sélective (import('math', 'sqrt')) restent inchangées - Context managers (
with) :with a = expr { body }garantit__exit__en sortie de bloc. Multi-binding, cleanup en ordre inverse, suppression d'exception si__exit__retourne truthy try/except/finally/raise: gestion d'erreurs avec syntaxe match-like. Clauses typées (e: TypeError => { }), union de types (ValueError | KeyError), wildcard (_ => { }), binding optionnel- Types d'exception built-in :
TypeError,ValueError,NameError,IndexError,KeyError,AttributeError,ZeroDivisionError,RuntimeError,MemoryError - Hiérarchie d'exceptions :
Exception(racine),ArithmeticError,LookupError. Matching via MRO :except ArithmeticErrorcatchZeroDivisionError - Structs d'exception :
struct AppError extends(RuntimeError) { message }fonctionne avec le matching par hiérarchie - Pattern struct dans tuple :
(Point{x, y}, z)avec guards et dispatch par type - Complex natif :
ExtendedValue::Complex(f64, f64)dans la PureVM,TAG_COMPLEXdans la VM principale. Arithmétique,.real/.imag/.conjugate(),abs(),hash(),complex(), interop PyComplex. Les littérauxj/Jcompilent nativement (plus de fallback Python) - Module
http:http.serve("<h1>Hello</h1>")lance un serveur local et ouvre le navigateur.http.Server(addr)pour le contrôle fin (recv, respond). Content-type auto-détecté
Outils
- Formatter source-aware : préserve l'intention du développeur. Layout inline/multiline, if/else, method chains et struct bodies suivent la source. Trailing comma reste le signal pour forcer multiline
- Formatter try/except : support complet du formatage try/except/finally/raise
- Linter : 11 nouvelles règles : code mort (W300, W301, W302), paramètres inutilisés (W201), shadowing (W204), métriques (I200 nesting, I201 cyclomatique, I202 longueur, I203 paramètres, I103 match sans catch-all)
- Seuils configurables :
--max-depth,--max-complexity,--max-length,--max-paramsvia CLI et API Python - Suppression inline
# noqa:# noqa(tous),# noqa: E200(spécifique),# noqa: E200, W200(multiple) - Analyse deep (
--deep) : W310 détecte les variables possiblement non initialisées. W311 détecte le code mort après des branches qui terminent toutes
CLI
- Expansion de fichiers :
catnip lintetcatnip formatacceptent dossiers (récursif*.cat) et globs - Shell completion : fix perte des completions
plainquand une entréefilesuit --versiondétaillé : affiche commit et date de build (-V= version courte)
Bugfixes
- Cache d'import : modules homonymes dans des répertoires différents ne se polluent plus
cached(): fonctions retournantNonecorrectement mémoïséescached():f(a=1, b=2)etf(b=2, a=1)partagent la même entrée de cache--policy sandboxlit correctement[modules.policies.sandbox]danscatnip.toml- Policy :
import('.secret')relatif vérifié contre la policy (plus contournable) - CLI :
catnip bench N script.catetcatnip --no-jit -- script.catfonctionnent correctement - Linter I100 : commentaire trailing après un tail call ne déclenche plus le hint TCO
- Linter W310 : fix faux positifs sur
matchexhaustif avec wildcard_ - Linter W310 : fix faux positifs sur variables définies dans
exceptou avanttry
0.0.7 (2026-03-26)
Changements depuis v0.0.6 (2026-03-02).
Langage
typeof(): intrinsic natif retournant le nom du type (remplacetype())freeze()/thaw(): sérialisation binaireglobals()etlocals(): introspection du scope- Nil-coalescing
??:a ?? bretourneasi non-None, sinon évalueb in/not in: membership operators (list, tuple, dict, set, string)is/is not: identity operators (x is None)and/orretournent bool : utiliser??pour le patternvalue or defaultfoldetreduce: primitives d'agrégation- Import sélectif :
import('math', 'sqrt', 'pi:p') - Noms dotted :
import('mylib.utils')cherchemylib/utils.cat - Packages
lib.toml: répertoire avec manifeste, entry point, filtrage exports - Policies nommées :
--policy <name>avec[modules.policies.<name>]danscatnip.toml NDetRUNTIME: namespaces builtin (ND.thread,RUNTIME.smallint_max, etc.)META.fileetMETA.main: chemin du fichier et détection d'exécution directe- Dot-continuation : chaînage multilignes avec
.en début de ligne - Struct fields :
;après les champs est désormais optionnel - Précédence
**vs-:-x**2donne-(x**2)
Stdlib
- Module
io:print,write,writeln,eprint,input,open(auto-importé en CLI/REPL) - Module
sys:argv,environ,executable,version,platform,cpu_count,exit()
CLI
catnip: binaire unique pour exécution et outils (remplacecatnip-run)catnip lsp: serveur LSP (diagnostics, formatting, rename scope-aware)- Shell completion :
catnip completion bash|zsh|fish -q/--quiet: supprime l'affichage du résultatCATNIP_CONFIG: variable d'environnement pour config alternative- Suffixes
-m:-m math:m(alias),-m io:!(injection globals) - Validation stricte :
-oet pragmas rejettent les valeurs invalides
REPL
- Ctrl+C : interrompt les exécutions longues
- Ctrl+R : recherche inversée dans l'historique
- Complétion d'attributs : après
., propose les attributs réels viadir() - Affichage struct :
Point(x=74, y=5.3)au lieu deNone - Auto-indent selon le niveau d'imbrication
/context: inspecter les variables utilisateur- Multiline paste : les lignes commençant par
.sont jointes à la précédente
Outils
- MCP server (
catnip-mcp) : parse, eval, check, format, debug via Model Context Protocol - Formatter : espacement, alignement en colonne, préservation multilignes, indentation des chaînes postfix
- Linter W200 scope-aware : "variable non utilisée" ne s'applique plus au scope global
- Debugger : sous-mode
repldans le scope du point d'arrêt
Performance
- ND recursion ~200x plus rapide
- JIT warm-start : traces compilées chargées depuis le cache disque
Bugfixes
- Segfault dans les boucles
foravec mutation conditionnelle - Crash réassignation struct entre types différents
float('nan')retournait0- F-strings :
f"{x}"utilise maintenantstr(x)correctement - Closures capturent les variables englobantes dans
fold,map, etc. - Variables locales préservées après exécution JIT
- Positions UTF-8 correctes dans les messages d'erreur
- Import sélectif atomique (pas de globals partiellement modifié en cas d'erreur)
- Breakpoints dynamiques fonctionnels pendant une session debug active
- Overflow arithmétique détecté au lieu de wrapping silencieux