This commit is contained in:
2026-09-13 10:20:16 +02:00
parent 019c8ad335
commit b72193d656
38 changed files with 1523 additions and 837 deletions

View File

@@ -1,25 +1,33 @@
# 18. `Result`, erreurs et exceptions
# 18. `Result`, erreurs, exceptions et faults
## 18.1 Hiérarchie Core — V1 REQUIS — FIGÉ
La hiérarchie d'erreurs V1 est :
La hiérarchie V1 est :
```text
Object
└── Error
├── ResultError
── Exception
── Exception
└── Fault
```
Les trois classes racines sont abstraites et ne sont donc jamais instanciées directement.
Les quatre classes racines sont abstraites et ne sont jamais instanciées directement.
`Error` est la racine abstraite commune des erreurs Saselang. Elle ne détermine pas à elle seule le mécanisme de propagation.
`Error` est la racine commune. Elle ne choisit pas à elle seule le mécanisme de propagation.
`ResultError` représente exclusivement la famille des erreurs transportables par `Result<T,E>`.
```text
ResultError
échec transporté explicitement comme une valeur dans Result<T,E>
`Exception` représente exclusivement la famille des erreurs propagées par `throw` / `throws` / `try` / `catch` / `finally`.
Exception
échec checked propagé par throw / throws
`ResultError` et `Exception` sont des branches sœurs. Une classe utilisateur destinée à un `Result` hérite directement ou indirectement de `ResultError`. Une exception utilisateur hérite directement ou indirectement de `Exception`.
Fault
échec unchecked, capturable, pouvant être documenté par faults
```
Les trois branches sont sœurs. Aucune n'est implicitement convertible vers une autre.
Exemple :
@@ -29,9 +37,12 @@ public final class ParseError extends ResultError {
public final class FileNotFoundException extends Exception {
}
public final class IndexOutOfBoundsFault extends Fault {
}
```
L'interface `Throwable` ne fait pas partie de Saselang V1. Elle pourra être réintroduite ultérieurement si un besoin réel apparaît sans modifier la règle fondamentale : `Result` transporte des `ResultError`, tandis que `throw` transporte des `Exception`.
Il n'existe pas d'interface `Throwable` en V1.
## 18.2 Informations communes de `Error` — V1 REQUIS — FIGÉ EN PRINCIPE
@@ -51,7 +62,7 @@ message
cause
erreur causale purement informative
peut contenir un ResultError ou une Exception
peut contenir un ResultError, une Exception ou un Fault
n'est pas automatiquement propagée
i18nMessage
@@ -59,13 +70,11 @@ i18nMessage
ne contient pas un tableau de traductions
```
`cause` est de type `Option<Error>` et non `Nullable<Error>` : l'absence de cause est une absence sémantique. Une cause de type statique `Error` ne peut pas être passée directement à `throw`; il faut d'abord prouver par `is` qu'elle est une `Exception`, ou l'encapsuler explicitement dans une nouvelle exception.
`cause` n'accorde aucun droit de propagation. Une valeur statiquement typée `Error` ne peut être utilisée ni avec `throw` ni avec `fault` sans preuve préalable de sa branche exacte.
`message`, `cause` et `i18nMessage` sont initialisés lors de la construction et ne sont pas publiquement mutables.
`Error` ne porte pas de propriétés universelles supplémentaires telles que timestamp, thread id, process id, host, severity ou code plateforme. Ces informations appartiennent aux sous-classes, SDKs ou couches de logging/diagnostic lorsqu'elles sont pertinentes.
Le type exact `I18nMessage`, sa clé et la représentation de ses paramètres seront finalisés avec Core/SDK et le système de formatting/i18n.
`Error` ne porte pas de propriétés universelles supplémentaires telles que timestamp, thread id, process id, host, severity ou code plateforme. Ces informations appartiennent aux sous-classes, au SDK ou aux couches de logging/diagnostic lorsqu'elles sont pertinentes.
## 18.3 `ResultError` et code machine-readable — V1 REQUIS — FIGÉ EN PRINCIPE
@@ -78,26 +87,31 @@ code: ResultErrorCode
Le rôle de `ResultErrorCode` est distinct de `message` :
```text
code -> stable, machine-readable, non localisé
message -> humain, canonique / fallback
code -> stable, machine-readable, non localisé
message -> humain, canonique / fallback
i18nMessage -> localisation externe
```
La représentation exacte de `ResultErrorCode` sera finalisée avec Core/SDK. `Exception` n'a pas de code obligatoire.
La représentation exacte de `ResultErrorCode` sera finalisée avec Core/SDK.
## 18.4 `Exception` et stack trace — V1 REQUIS — FIGÉ EN PRINCIPE
Ni `Exception` ni `Fault` n'ont de code universel obligatoire ; leurs sous-types peuvent naturellement en définir lorsqu'il est utile.
`Exception` reste généraliste. Elle peut recevoir des informations de stack trace liées à sa propagation.
## 18.4 Diagnostic de `Exception` et `Fault` — V1 REQUIS — FIGÉ EN PRINCIPE
La création d'un objet `Exception` ne doit pas obligatoirement capturer immédiatement une stack trace. Le contexte de propagation peut être attaché ou capturé lors de :
`Exception` et `Fault` peuvent recevoir des informations de stack trace liées à leur propagation runtime.
La création de l'objet ne doit pas obligatoirement capturer immédiatement une stack trace. Le contexte peut être attaché ou capturé lors de :
```text
throw exception;
fault someFault;
```
La représentation exacte (`StackTrace`, `StackFrame`, capture lazy ou autre optimisation) relève de Core/runtime. Cette information reste diagnostique et n'intervient pas dans la sélection des `catch`.
ou lorsqu'un `Fault` est produit intrinsèquement par le runtime/Core.
`Error` et `ResultError` n'ont pas de stack trace automatique obligatoire.
La représentation exacte (`StackTrace`, `StackFrame`, capture lazy ou autre optimisation) relève du Core/runtime. Cette information reste diagnostique et n'intervient pas dans la sélection des `catch`.
`ResultError` n'a pas de stack trace automatique obligatoire.
## 18.5 `Result<T,E>` — V1 REQUIS — FIGÉ
@@ -127,6 +141,7 @@ Sont invalides :
```text
Result<Data,Error>
Result<Data,Exception>
Result<Data,Fault>
Result<Data,FileNotFoundException>
```
@@ -142,7 +157,7 @@ est équivalente à :
Result<T,ResultError>
```
`Result<Void,E>` et `Result<Void>` sont autorisés pour représenter un succès sans payload utile. `Option<Void>` reste interdit.
`Result<Void,E>` et `Result<Void>` sont autorisés. `Option<Void>` reste interdit.
Il n'existe aucune conversion implicite de `T` vers `Result<T,E>` ni de `E` vers `Result<T,E>` :
@@ -151,73 +166,72 @@ return Result::Ok(value);
return Result::Err(error);
```
sont explicites.
restent explicites.
`Result` suit les règles générales des enums algébriques. Son inspection et l'extraction sûre de ses payloads utilisent `match` avec bindings typés. V1 n'introduit ni `unwrap`, ni opérateur `?`, ni extraction implicite d'une variante.
`Result` suit les règles générales des enums algébriques. V1 n'introduit ni `unwrap`, ni opérateur `?`, ni extraction implicite d'une variante.
Des helpers Core futurs tels que :
## 18.6 Choix du mécanisme d'échec — V1 REQUIS — FIGÉ EN PRINCIPE
Les trois branches ne sont pas trois orthographes du même concept.
```text
result::expectOk(...)
result::expectErr(...)
ResultError / Result
l'échec est une donnée normale du résultat et l'appelant doit l'inspecter explicitement
Exception
l'échec appartient au contrat checked de la callable et doit être capturé ou propagé par throws
Fault
l'échec est unchecked ; l'appelant peut le capturer mais n'y est pas obligé
```
peuvent être étudiés si un besoin réel apparaît. Ils resteraient une API de `Result`, pas une syntaxe du langage, et leur comportement d'échec devrait être explicitement spécifié.
Une API directe n'est donc plus obligée de retourner `Result` uniquement parce qu'elle peut échouer.
## 18.6 Séparation `ResultError` / `Exception` — V1 REQUIS — FIGÉ
Aucune conversion automatique n'existe entre les deux branches :
Exemple :
```text
ResultError -> Exception
Exception -> ResultError
method elementAt(uint64 index) -> T
faults IndexOutOfBoundsFault
```
La traduction d'un mécanisme vers l'autre se fait par encapsulation explicite dans une nouvelle erreur de la branche cible, éventuellement en conservant l'erreur source dans `cause`.
peut retourner directement `T`.
Exemples conceptuels :
Inversement, lorsqu'une absence ou un échec constitue une donnée normale du domaine, `Option` ou `Result` reste préférable :
```text
throw ParseException(..., Option::Some(parseError));
map::get(key) -> Option<V>
parseExternalInput(...) -> Result<Value, ParseError>
```
ou :
Le Core ne doit pas créer mécaniquement un couple `op()` / `tryOp()` uniquement pour offrir d'un côté une valeur directe et de l'autre un `Result`. Deux opérations coexistantes doivent avoir des sémantiques réellement distinctes.
```text
catch (FileNotFoundException error) {
return Result::Err(FileResultError(..., Option::Some(error)));
}
```
Un simple cast ne transforme pas un `ResultError` en `Exception` ni l'inverse.
Aucune conversion automatique n'existe entre `ResultError`, `Exception` et `Fault`. Une traduction entre branches se fait par encapsulation explicite, éventuellement via `cause`.
## 18.7 `throws` — V1 REQUIS — FIGÉ
`throws` est interdit sur une `func` / `method` / `clsmethod` à retour direct. Il n'est permis que si le retour est `Result<T>` ou `Result<T,E>`.
`throws` est indépendant du type de retour.
Sont valides :
```text
T + throws -> interdit
Result<T,E> -> valide sans throws
Result<T,E> + throws -> valide
T
T throws SomeException
Result<T,E>
Result<T,E> throws SomeException
Void throws SomeException
```
Tout type déclaré dans `throws` doit être `Exception` ou un descendant de `Exception`.
Une callable doit déclarer toute famille d'exception susceptible d'atteindre sa frontière sans être gérée localement. Cette obligation est transitive : une exception déclarée par une callable appelée doit être soit capturée localement, soit couverte par le `throws` de l'appelant.
Un type déclaré dans `throws` couvre ses descendants. Une clause :
Un type déclaré dans `throws` couvre ses descendants. Une même clause ne doit pas contenir de types redondants lorsqu'un type déclaré couvre déjà entièrement un autre type de la liste.
```text
throws IOException
```
peut donc couvrir `FileNotFoundException` si cette dernière hérite de `IOException`.
Une même clause `throws` ne doit pas contenir de types redondants lorsqu'un type déclaré couvre déjà entièrement un autre type de la liste.
`Fault`, `Error` et `ResultError` sont interdits dans `throws`.
## 18.8 `throws` dans les contrats, interfaces et overrides — V1 REQUIS — FIGÉ
`throws` ne fait pas partie de la signature d'overload. Il fait partie du contrat de la callable.
`throws` ne fait pas partie de la signature d'overload. Il fait partie du contrat checked de la callable.
Un override ou une implémentation peut :
@@ -229,21 +243,20 @@ gérer localement tout ou partie des exceptions du contrat parent
Il ne peut jamais ajouter une exception non couverte par le contrat parent ni élargir une famille déclarée.
La même règle s'applique aux contrats d'interface. Lorsque plusieurs interfaces portent la même signature, l'implémentation doit satisfaire simultanément tous leurs contrats `throws`. Une implémentation sans exception sortante est toujours compatible avec un contrat qui en autorise.
La même règle s'applique aux contrats d'interface. Une implémentation sans exception sortante est toujours compatible avec un contrat qui en autorise.
## 18.9 `throw` — V1 REQUIS — FIGÉ
`throw` ne peut lancer qu'une valeur dont le type statique est `Exception` ou un descendant de `Exception`.
```text
throw FileNotFoundException(...); // valide
throw ParseError(...); // erreur
throw Error(...); // erreur
throw FileNotFoundException(...); // OK
throw ParseError(...); // ERROR
throw IndexOutOfBoundsFault(...); // ERROR
throw Error(...); // ERROR
```
Les racines abstraites `Error`, `ResultError` et `Exception` ne sont jamais instanciables directement.
La forme de repropagation reste explicite :
La repropagation reste explicite :
```text
catch (IOException error) {
@@ -253,7 +266,69 @@ catch (IOException error) {
Il n'existe pas de forme spéciale `throw;` en V1.
## 18.10 `try` / `catch` / `finally` — V1 REQUIS — FIGÉ
## 18.10 `fault` et `faults` — V1 REQUIS — FIGÉ EN PRINCIPE
`fault` produit explicitement un `Fault` :
```text
fault InvalidStateFault(...);
```
Le type statique de la valeur doit être `Fault` ou un descendant de `Fault`.
```text
fault InvalidStateFault(...); // OK
fault FileNotFoundException(...); // ERROR
fault ParseError(...); // ERROR
```
Une callable peut documenter des faults significatifs directement dans sa signature :
```text
method elementAt(uint64 index) -> T
faults IndexOutOfBoundsFault
```
Plusieurs familles peuvent être listées explicitement :
```text
faults FirstFault, SecondFault
```
Chaque type listé doit être `Fault` ou un descendant de `Fault`; une liste ne doit pas contenir de famille redondante déjà couverte par un parent déclaré.
La clause `faults` est **optionnelle et non exhaustive**.
Elle signifie que les faults listés font explicitement partie du contrat/documentation utile de l'API. Elle ne signifie pas qu'aucun autre fault runtime ne peut survenir.
Contrairement à `throws` :
```text
aucun catch n'est obligatoire
aucune propagation de la clause faults n'est obligatoire
un appelant n'a pas à recopier les faults d'une callable appelée
```
Exemple valide :
```text
func outer() -> Void {
inner(); // inner peut déclarer faults SomeFault
return Void;
}
```
`outer` peut, mais n'est pas obligé de déclarer à son tour :
```text
faults SomeFault
```
`faults` ne fait pas partie de la signature d'overload et n'impose pas les restrictions de covariance contractuelle de `throws`. Un override peut documenter un ensemble différent de faults, puisque l'absence d'un fault dans la clause n'est jamais une garantie statique d'impossibilité.
Une API publique qui produit explicitement un fault important devrait normalement le documenter avec `faults`; un outil/linter peut signaler les omissions sans en faire une erreur de compilation du langage.
## 18.11 `try` / `catch` / `finally` — V1 REQUIS — FIGÉ
Un `try` doit être suivi d'au moins un `catch`.
@@ -272,7 +347,7 @@ try {
...
} catch (SpecificException error) {
...
} catch (ParentException error) {
} catch (SomeFault faultValue) {
...
} finally {
...
@@ -281,75 +356,95 @@ try {
`finally` est facultatif et ne peut apparaître qu'après au moins un `catch`.
Chaque `catch` contient exactement un type explicite d'`Exception` ou descendant. Il n'existe pas de multi-catch `A | B` en V1 : des blocs `catch` distincts sont utilisés.
Les `catch` sont évalués dans l'ordre source. Le chevauchement par héritage est normal et utile : les cas spécifiques peuvent précéder un fallback de famille générale.
Un `catch` entièrement couvert par un `catch` précédent est une erreur de compilation, pas un simple warning.
Exemple valide :
Le type explicite d'un `catch` doit être :
```text
catch (FileNotFoundException error) {
...
} catch (IOException error) {
...
}
Exception ou un descendant de Exception
Fault ou un descendant de Fault
```
Exemple invalide si `FileNotFoundException extends IOException` :
Sont donc interdits :
```text
catch (IOException error) {
...
} catch (FileNotFoundException error) {
...
}
catch (Error error)
catch (ResultError error)
```
Un ensemble de `catch` n'a pas à être exhaustif. Toute `Exception` non capturée continue à se propager et doit être couverte par le contrat `throws` de la callable englobante.
Il n'existe pas de multi-catch `A | B` en V1.
Les variables de `catch` suivent les règles normales de scope et de non-shadowing. Des `catch` distincts peuvent réutiliser le même nom parce que leurs scopes sont disjoints.
Les `catch` sont évalués dans l'ordre source. Un `catch` entièrement couvert par un précédent dans la même branche d'héritage est une erreur de compilation.
## 18.11 `finally` non-escaping — V1 REQUIS — FIGÉ
Une `Exception` non capturée continue à se propager et doit être couverte par `throws`.
`finally` exécute un bloc commun avant de quitter la construction `try/catch`, qu'il y ait :
Un `Fault` non capturé continue à se propager de façon unchecked ; aucune clause `faults` n'est exigée sur les callables traversées.
Les variables de `catch` suivent les règles normales de scope et de non-shadowing.
## 18.12 `finally` non-escaping — V1 REQUIS — FIGÉ EN PRINCIPE
`finally` exécute un bloc commun avant de quitter la construction `try/catch`, notamment sur :
```text
fin normale du try
fin normale d'un catch
return traversant la construction
throw propagé
Fault propagé
break / continue traversant la construction
Exception non capturée par les catch
```
`finally` ne doit jamais remplacer une sortie déjà en cours. Les sorties structurées suivantes y sont interdites :
`finally` ne doit pas remplacer volontairement une sortie déjà en cours. Les sorties structurées explicites suivantes y sont interdites :
```text
return
throw
fault
break
continue
emit
```
Aucune `Exception` non capturée ne peut sortir indirectement d'un `finally`. Toute callable appelée depuis `finally` dont le contrat comporte `throws` doit avoir ses exceptions entièrement gérées à l'intérieur du `finally`.
Toute `Exception` produite indirectement depuis `finally` doit être gérée localement selon les règles checked de `throws`.
Une faute runtime non récupérable reste distincte de ce contrat d'exception.
Un `Fault` dynamique peut néanmoins survenir dans le code exécuté par `finally`, puisqu'il est unchecked. L'interaction exacte entre un tel fault, l'unwind, les destructeurs et une sortie déjà en cours reste à fermer avec le modèle mémoire/runtime.
## 18.12 Faute runtime / panic — V1 REQUIS — À FINALISER
## 18.13 Sémantique runtime des `Fault` — V1 REQUIS — FIGÉ EN PRINCIPE
Les violations de contrat du langage telles que :
Les `Fault` couvrent notamment les échecs unchecked et violations runtime tels que :
```text
index hors limites
division entière par zéro
index dynamique hors limites
division entière dynamique par zéro
overflow checked
représentation mémoire invalide
borne dynamique invalide lors d'une construction directe lorsque la règle du type le définit
iterator invalidé
clé absente lors d'un accès strict map[key]
doublon lors d'un insert strict
conversion explicite directe dont les préconditions runtime ne sont pas satisfaites
représentation/encodage invalide lors d'une construction directe validée
```
ne sont pas nécessairement des `ResultError` ou des `Exception` récupérables.
Lorsqu'une violation d'une précondition intrinsèque est démontrable statiquement, le compilateur doit la diagnostiquer plutôt que générer un programme condamné à fauter.
Le modèle exact de faute runtime/fatal error/panic et son interaction avec cleanup/destruction doit être défini avant la finalisation V1.
Exemple :
```text
StaticArray<int32,3> values = [1, 2, 3];
int32 value = values[5]; // ERROR compilation
```
Cette règle ne rend évidemment pas illégal un statement `fault` explicite : `fault SomeFault(...)` est un mécanisme volontaire du programme et peut être utilisé pour implémenter une API.
Lorsqu'un `Fault` ne peut être déterminé qu'au runtime :
```text
capturé par catch
-> le catch correspondant s'exécute
non capturé
-> le fault remonte sans contrat checked
-> à la frontière de l'unité d'exécution, il provoque un échec runtime diagnostiqué
```
La définition exacte de « l'unité d'exécution » (processus, thread, task, isolate éventuel) et l'ordre final cleanup/destruction lors d'un fault seront fermés avec les chapitres mémoire et concurrence.
---