356 lines
11 KiB
Markdown
356 lines
11 KiB
Markdown
# 18. `Result`, erreurs et exceptions
|
|
|
|
## 18.1 Hiérarchie Core — V1 REQUIS — FIGÉ
|
|
|
|
La hiérarchie d'erreurs V1 est :
|
|
|
|
```text
|
|
Object
|
|
└── Error
|
|
├── ResultError
|
|
└── Exception
|
|
```
|
|
|
|
Les trois classes racines sont abstraites et ne sont donc 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.
|
|
|
|
`ResultError` représente exclusivement la famille des erreurs transportables par `Result<T,E>`.
|
|
|
|
`Exception` représente exclusivement la famille des erreurs propagées par `throw` / `throws` / `try` / `catch` / `finally`.
|
|
|
|
`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`.
|
|
|
|
Exemple :
|
|
|
|
```text
|
|
public final class ParseError extends ResultError {
|
|
}
|
|
|
|
public final class FileNotFoundException extends Exception {
|
|
}
|
|
```
|
|
|
|
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`.
|
|
|
|
## 18.2 Informations communes de `Error` — V1 REQUIS — FIGÉ EN PRINCIPE
|
|
|
|
`Error` reste volontairement généraliste et minimal. Il porte conceptuellement :
|
|
|
|
```text
|
|
message: String
|
|
cause: Option<Error>
|
|
i18nMessage: Option<I18nMessage>
|
|
```
|
|
|
|
Rôles :
|
|
|
|
```text
|
|
message
|
|
message humain canonique / fallback
|
|
|
|
cause
|
|
erreur causale purement informative
|
|
peut contenir un ResultError ou une Exception
|
|
n'est pas automatiquement propagée
|
|
|
|
i18nMessage
|
|
clé et paramètres de localisation
|
|
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.
|
|
|
|
`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.
|
|
|
|
## 18.3 `ResultError` et code machine-readable — V1 REQUIS — FIGÉ EN PRINCIPE
|
|
|
|
`ResultError` ajoute conceptuellement un code stable, non localisé et exploitable par le programme :
|
|
|
|
```text
|
|
code: ResultErrorCode
|
|
```
|
|
|
|
Le rôle de `ResultErrorCode` est distinct de `message` :
|
|
|
|
```text
|
|
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.
|
|
|
|
## 18.4 `Exception` et stack trace — V1 REQUIS — FIGÉ EN PRINCIPE
|
|
|
|
`Exception` reste généraliste. Elle peut recevoir des informations de stack trace liées à sa propagation.
|
|
|
|
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 :
|
|
|
|
```text
|
|
throw exception;
|
|
```
|
|
|
|
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`.
|
|
|
|
`Error` et `ResultError` n'ont pas de stack trace automatique obligatoire.
|
|
|
|
## 18.5 `Result<T,E>` — V1 REQUIS — FIGÉ
|
|
|
|
`Result<T,E>` est un type Core algébrique avec exactement deux variantes conceptuelles :
|
|
|
|
```text
|
|
Result::Ok(T)
|
|
Result::Err(E)
|
|
```
|
|
|
|
Le paramètre d'erreur est contraint :
|
|
|
|
```text
|
|
E doit être ResultError ou un descendant de ResultError
|
|
```
|
|
|
|
Sont valides :
|
|
|
|
```text
|
|
Result<Data,ResultError>
|
|
Result<Data,ParseError>
|
|
Result<Void,ResultError>
|
|
```
|
|
|
|
Sont invalides :
|
|
|
|
```text
|
|
Result<Data,Error>
|
|
Result<Data,Exception>
|
|
Result<Data,FileNotFoundException>
|
|
```
|
|
|
|
La forme courte :
|
|
|
|
```text
|
|
Result<T>
|
|
```
|
|
|
|
est équivalente à :
|
|
|
|
```text
|
|
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.
|
|
|
|
Il n'existe aucune conversion implicite de `T` vers `Result<T,E>` ni de `E` vers `Result<T,E>` :
|
|
|
|
```text
|
|
return Result::Ok(value);
|
|
return Result::Err(error);
|
|
```
|
|
|
|
sont 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.
|
|
|
|
Des helpers Core futurs tels que :
|
|
|
|
```text
|
|
result::expectOk(...)
|
|
result::expectErr(...)
|
|
```
|
|
|
|
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é.
|
|
|
|
## 18.6 Séparation `ResultError` / `Exception` — V1 REQUIS — FIGÉ
|
|
|
|
Aucune conversion automatique n'existe entre les deux branches :
|
|
|
|
```text
|
|
ResultError -> Exception
|
|
Exception -> ResultError
|
|
```
|
|
|
|
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`.
|
|
|
|
Exemples conceptuels :
|
|
|
|
```text
|
|
throw ParseException(..., Option::Some(parseError));
|
|
```
|
|
|
|
ou :
|
|
|
|
```text
|
|
catch (FileNotFoundException error) {
|
|
return Result::Err(FileResultError(..., Option::Some(error)));
|
|
}
|
|
```
|
|
|
|
Un simple cast ne transforme pas un `ResultError` en `Exception` ni l'inverse.
|
|
|
|
## 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>`.
|
|
|
|
```text
|
|
T + throws -> interdit
|
|
Result<T,E> -> valide sans throws
|
|
Result<T,E> + throws -> valide
|
|
```
|
|
|
|
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 :
|
|
|
|
```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.
|
|
|
|
## 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.
|
|
|
|
Un override ou une implémentation peut :
|
|
|
|
```text
|
|
supprimer entièrement des exceptions déclarées
|
|
restreindre une famille à une ou plusieurs sous-familles compatibles
|
|
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.
|
|
|
|
## 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
|
|
```
|
|
|
|
Les racines abstraites `Error`, `ResultError` et `Exception` ne sont jamais instanciables directement.
|
|
|
|
La forme de repropagation reste explicite :
|
|
|
|
```text
|
|
catch (IOException error) {
|
|
throw error;
|
|
}
|
|
```
|
|
|
|
Il n'existe pas de forme spéciale `throw;` en V1.
|
|
|
|
## 18.10 `try` / `catch` / `finally` — V1 REQUIS — FIGÉ
|
|
|
|
Un `try` doit être suivi d'au moins un `catch`.
|
|
|
|
Sont interdits :
|
|
|
|
```text
|
|
try { ... }
|
|
try { ... } finally { ... }
|
|
finally { ... }
|
|
```
|
|
|
|
La forme générale est :
|
|
|
|
```text
|
|
try {
|
|
...
|
|
} catch (SpecificException error) {
|
|
...
|
|
} catch (ParentException error) {
|
|
...
|
|
} finally {
|
|
...
|
|
}
|
|
```
|
|
|
|
`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 :
|
|
|
|
```text
|
|
catch (FileNotFoundException error) {
|
|
...
|
|
} catch (IOException error) {
|
|
...
|
|
}
|
|
```
|
|
|
|
Exemple invalide si `FileNotFoundException extends IOException` :
|
|
|
|
```text
|
|
catch (IOException error) {
|
|
...
|
|
} catch (FileNotFoundException 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.
|
|
|
|
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.
|
|
|
|
## 18.11 `finally` non-escaping — V1 REQUIS — FIGÉ
|
|
|
|
`finally` exécute un bloc commun avant de quitter la construction `try/catch`, qu'il y ait :
|
|
|
|
```text
|
|
fin normale du try
|
|
fin normale d'un catch
|
|
return traversant la construction
|
|
throw 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 :
|
|
|
|
```text
|
|
return
|
|
throw
|
|
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`.
|
|
|
|
Une faute runtime non récupérable reste distincte de ce contrat d'exception.
|
|
|
|
## 18.12 Faute runtime / panic — V1 REQUIS — À FINALISER
|
|
|
|
Les violations de contrat du langage telles que :
|
|
|
|
```text
|
|
index hors limites
|
|
division entière 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
|
|
```
|
|
|
|
ne sont pas nécessairement des `ResultError` ou des `Exception` récupérables.
|
|
|
|
Le modèle exact de faute runtime/fatal error/panic et son interaction avec cleanup/destruction doit être défini avant la finalisation V1.
|