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

@@ -22,6 +22,8 @@ Principe directeur :
> **boring but working** : une règle stable et prévisible vaut mieux qu'une syntaxe plus courte mais ambiguë.
Lorsqu'une forme légèrement plus longue supprime un implicite ou une ambiguïté réelle, Saselang privilégie l'explicite. La concision n'est pas un objectif supérieur à la lisibilité du contrat.
## 2.2 Mots-clés — V1 REQUIS — FIGÉ
Les mots-clés du langage sont en anglais.

View File

@@ -17,7 +17,7 @@ unions
tuples
generics
contrôle de flux
erreurs/exceptions
erreurs/exceptions/faults
opérateurs
unsafe
scope
@@ -42,6 +42,7 @@ Result<T,E>
Error
ResultError
Exception
Fault
Option<T>
Nullable<T>
Range<T>
@@ -50,10 +51,18 @@ RangeStep<T,Step>
RangeReverse<T>
RangeReverseStep<T,Step>
RangeProgression<T,Step>
PartialOrdering
Ordering
Comparable<T>
Comparator<T>
Op...
Iterable<T>
Iterator<T>
View<T>
Collection<T>
List<T>
Set<T>
Map<K,V>
Array<T>
StaticArray<T,N>
TypeInfo
@@ -71,7 +80,7 @@ Une capacité Core peut être conditionnée par un target lorsqu'elle ne peut ra
Le SDK fournit les fonctionnalités de bibliothèque qui ne justifient pas une sémantique spéciale du compilateur :
```text
collections
collections concrètes/spécialisées au-delà des contrats fondamentaux Core
encodages explicites
regex
filesystem

View File

@@ -49,6 +49,6 @@ Si tous les éléments permettent l'égalité pertinente, le tuple peut être co
Si tous les éléments sont comparables, l'ordre du tuple est lexicographique.
Si un composant comparé retourne `Ordering::Unordered`, le tuple est `Unordered`.
Si un composant comparé retourne `PartialOrdering::Unordered`, le résultat de comparaison du tuple est `PartialOrdering::Unordered`.
---

View File

@@ -41,10 +41,12 @@ type de retour
visibilité
fallibilité / Result
throws
contraintes génériques pertinentes
qualification const et contraintes génériques pertinentes
modificateurs contractuels pertinents
```
`faults` reste visible dans la déclaration et dans la documentation, mais sa nature optionnelle/non exhaustive signifie qu'il n'impose pas la même compatibilité d'override que `throws`.
Les futurs pré/post-contrats formels, s'ils existent, devront également participer à cette notion.
## 13.5 Conflits d'héritage multiple — V1 REQUIS — FIGÉ

View File

@@ -9,37 +9,42 @@ clsmethod méthode de classe
operator implémentation d'un contrat opérateur
```
## 15.2 Retours directs et `Result` — V1 REQUIS — FIGÉ
## 15.2 Retours directs, `Result`, `throws` et `faults` — V1 REQUIS — FIGÉ
L'ancienne règle « toute fonction/méthode retourne `Result` » est supprimée.
Le type de retour et le mécanisme d'échec sont des dimensions distinctes.
Règle V1 :
Une callable peut retourner directement `T` même si elle peut produire une `Exception` checked ou un `Fault` unchecked :
```text
opération infaillible
-> retourne directement T
func readConfig(String path) -> Config
throws IOException
opération pouvant produire une erreur récupérable
-> retourne Result<T>
ou Result<T,E>
method elementAt(uint64 index) -> T
faults IndexOutOfBoundsFault
```
Exemples infaillibles :
`Result<T,E>` est utilisé lorsque l'échec doit être transporté explicitement comme une valeur et inspecté par l'appelant :
```text
method length() -> uint64
method containsKey(K key) -> bool
Object::sameInstance(Object other) -> bool
func min(int32 a, int32 b) -> int32
func parseExternalInput(String input) -> Result<Value, ParseError>
```
Exemples faillibles :
Les combinaisons sont indépendantes :
```text
func readFile(String path) -> Result<String, IoError>
method parse(String input) -> Result<Value, ParseError>
T
T throws SomeException
T faults SomeFault
T throws SomeException faults SomeFault
Result<T,E>
Result<T,E> throws SomeException
Result<T,E> faults SomeFault
```
Saselang ne force donc plus une callable à retourner `Result` uniquement parce qu'elle peut échouer. À l'inverse, une API ne doit pas remplacer systématiquement un échec normal du domaine par un `Fault` si `Option` ou `Result` exprime mieux le contrat.
Le Core ne crée pas mécaniquement des paires `op()` / `tryOp()` ayant pour seule différence « valeur directe avec fault » versus « même opération dans Result ». Deux opérations distinctes doivent porter des sémantiques réellement distinctes.
## 15.3 Retour explicite — V1 REQUIS — FIGÉ
Pas de retour implicite de dernière expression.

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.
---

View File

@@ -27,7 +27,7 @@ Un `if` qui contient `emit` devient un `if` producteur de valeur. Dans ce cas :
- le résultat du `if` doit obligatoirement être affecté à une destination ;
- un bloc `else` final est obligatoire ;
- chaque voie de terminaison normale de chaque branche doit produire explicitement une valeur via `emit` ;
- une voie que le compilateur prouve comme terminant définitivement le contrôle (`return`, `throw`, runtime fault certain ou non-terminaison prouvée) n'a pas à exécuter `emit` ;
- une voie que le compilateur prouve comme terminant définitivement le contrôle (`return`, `throw`, `fault` explicite ou non-terminaison prouvée) n'a pas à exécuter `emit` ;
- les valeurs émises doivent être compatibles avec le type de la destination ;
- aucune valeur de secours n'est implicite, y compris pour `Option<T>`.

View File

@@ -6,13 +6,15 @@ Les opérateurs utilisateur ne peuvent être fournis que via un ensemble fermé
L'utilisateur ne peut pas inventer de nouveaux symboles opérateurs.
Les `operator` contracts :
Les contrats `operator` :
- retournent directement leur valeur ;
- ne retournent jamais `Result` ;
- ne déclarent jamais `throws`.
- ne déclarent jamais `throws` ;
- peuvent produire des `Fault` unchecked lorsque le contrat de l'opération le prévoit ;
- peuvent documenter ces faults par une clause `faults`.
Une faute de contrat du langage peut néanmoins déclencher une faute runtime déterministe.
Une opération dont la précondition est statiquement prouvée fausse est rejetée à la compilation lorsque la règle du langage permet cette preuve. Une violation seulement dynamique produit le `Fault` correspondant.
## 20.2 Interfaces arithmétiques — V1 REQUIS — FIGÉ
@@ -124,29 +126,38 @@ Les classes ne reçoivent aucune comparaison champ-à-champ automatique : elles
## 20.6 Comparaison — V1 REQUIS — FIGÉ EN PRINCIPE
Core :
Core distingue l'ordre partiel de l'ordre total :
```text
public enum Ordering {
public enum PartialOrdering {
Less,
Equal,
Greater,
Unordered
}
public enum Ordering {
Less,
Equal,
Greater
}
```
Interfaces :
Interfaces opérateur :
```text
OpPartialCompare<Lhs,Rhs>
-> PartialOrdering
OpCompare<T>
-> Ordering
```
`OpCompare<T>` renforce `OpPartialCompare<T,T>` et garantit un ordre total, donc pas de `Ordering::Unordered`.
`OpCompare<T>` garantit un ordre total. Les floats utilisent l'ordre partiel pour leurs opérateurs généraux en raison de `NaN`; les entiers peuvent utiliser l'ordre total.
Les floats utilisent l'ordre partiel ; les entiers peuvent utiliser l'ordre total.
`Ordering::Equal` signifie équivalence dans la relation d'ordre considérée. Il ne doit pas être confondu automatiquement avec l'égalité générale `==`.
Le lien exact entre égalité et comparaison pour éviter deux contrats contradictoires sur une même paire doit être verrouillé dans la spécification finale des interfaces `Op`.
Les contrats applicatifs `Comparable<T>` et `Comparator<T>` des collections ordonnées utilisent `Ordering` et sont définis au chapitre 22.
## 20.7 Comparaisons hétérogènes — V1 REQUIS — FIGÉ EN PRINCIPE
@@ -187,21 +198,25 @@ D'autres index numériques pourront être supportés ultérieurement sans modifi
Un type utilisateur peut utiliser n'importe quel type d'index pertinent.
## 20.10 Map — V1 REQUIS — DIRECTION FIGÉE
## 20.10 Map — V1 REQUIS — FIGÉ EN PRINCIPE
Direction recommandée :
Le contrat des maps est défini au chapitre 22.
L'indexation est stricte :
```text
Map<K,V>
OpIndex<K,Option<V>>
OpIndexMut<K,V>
map[key] -> V
```
Lecture : absent -> `None` ; présent -> `Some(value)`.
La clé doit exister ; une absence dynamique produit `KeyNotFoundFault`.
Écriture : insertion si absent, remplacement si présent.
L'accès conditionnel est explicite et distinct :
Une méthode `containsKey` peut compléter l'API sans être obligatoire avant toute lecture.
```text
map::get(key) -> Option<V>
```
`OpIndexMut<K,V>` remplace uniquement la valeur d'une clé existante ; l'affectation indexée n'insère jamais implicitement une nouvelle clé.
## 20.11 Affectation — V1 REQUIS — FIGÉ

View File

@@ -95,26 +95,37 @@ Aucun `Utf8CodeUnit` / `Utf16CodeUnit` / `Utf32CodeUnit` distinct n'est introdui
Aucun transcodage implicite n'existe.
Les conversions totales utilisent `to...`.
Les constructions ou réductions pouvant échouer utilisent `tryFrom...` / `tryTo...`.
Les conversions directes retournent la destination attendue. Lorsqu'une validation runtime peut échouer, la callable peut déclarer `faults UnicodeEncodingFault` au lieu de forcer un `ResultError` ou une variante `try...` parallèle.
Exemples :
```text
char::toUtf8Char()
Utf8Char::toChar()
Utf8Char::toUtf16Char()
char::toUtf8Char() -> Utf8Char
Utf8Char::toChar() -> char
Utf8Char::toUtf16Char() -> Utf16Char
Utf8Char::tryFrom(uint8)
Utf16Char::tryFrom(uint16)
Utf32Char::tryFrom(uint32)
Utf8Char::from(uint8) -> Utf8Char
faults UnicodeEncodingFault
Utf8Char::tryToUint8()
Utf16Char::tryToUint16()
Utf32Char::toUint32()
Utf16Char::from(uint16) -> Utf16Char
faults UnicodeEncodingFault
Utf32Char::from(uint32) -> Utf32Char
faults UnicodeEncodingFault
Utf8Char::toUint8() -> uint8
faults UnicodeEncodingFault
Utf16Char::toUint16() -> uint16
faults UnicodeEncodingFault
Utf32Char::toUint32() -> uint32
```
`UnicodeEncodingFault extends Fault` est le nom de travail du fault Core associé aux données encodées invalides ou aux réductions impossibles.
Le Core n'expose pas simultanément `from(...)` et `tryFrom(...)` lorsque les deux opérations auraient exactement la même sémantique et ne différeraient que par le canal d'échec.
La matrice normative détaillée est définie dans `annexes/B-unicode-encoding-conversions.md`.
## 21.5 Aucun mélange implicite de types — V1 REQUIS — FIGÉ

View File

@@ -135,9 +135,9 @@ Les indices d'une sous-slice sont relatifs à la slice source.
Les interfaces décrivent des capacités réellement garanties. Les classes/structs génériques fournissent le stockage et l'implémentation.
Saselang ne retient pas le modèle d'opérations optionnelles qui existent dans une interface mais échouent ensuite au runtime comme « unsupported ».
Saselang ne retient pas le modèle d'opérations optionnelles présentes dans une interface mais susceptibles d'échouer ensuite comme « unsupported ».
Hiérarchie principale retenue :
Hiérarchie principale :
```text
Iterable<T>
@@ -151,21 +151,84 @@ SettableList<T>
ResizableList<T>
```
`Set<T>` et `Map<K,V>` sont des branches distinctes à préciser.
`Set<T>` et `Map<K,V>` forment des branches sémantiques distinctes.
### 22.4.1 `Iterable<T>` / `Iterator<T>`
### 22.4.1 `Iterable<T>` et `Iterator<T>`
Interfaces Core reconnues par `foreach`.
`foreach` dépend uniquement de `Iterable<T>`.
Elles ne portent pas le préfixe `Op`, car `foreach` est une construction du langage et non un opérateur symbolique.
```text
interface Iterable<T> {
const method iterator() -> Iterator<T>;
}
```
Indexabilité et itérabilité sont indépendantes.
Créer un iterator ne constitue pas une mutation sémantique de la source.
### 22.4.2 `Collection<T>`
`Iterator<T>` représente une itération particulière et possède un état de progression mutable :
```text
interface Iterator<T> {
method next() -> Option<T>;
}
```
`Some(value)` fournit l'élément suivant ; `None` marque la fin normale de l'itération.
Saselang n'impose pas le couple `hasNext()` / `next()`. `Option<T>` rend l'état de fin explicite dans une seule opération.
`Iterator<T>` n'étend pas `Iterable<T>` par défaut. Une source capable de créer des itérations et une itération déjà en cours sont deux capacités différentes, même si certains types concrets peuvent éventuellement fournir les deux.
Pour les collections ordinaires non concurrentes, une mutation structurelle invalide par défaut les iterators actifs lorsque le type concret ne garantit pas explicitement une autre politique.
```text
insert / remove / clear / append / changement structurel
-> peut invalider l'iterator
utilisation ultérieure d'un iterator invalidé
-> IteratorInvalidatedFault
```
Le simple remplacement d'une valeur existante n'est pas automatiquement une mutation structurelle. Le type concret documente sa politique exacte.
Les collections concurrentes ou spécialisées peuvent fournir une autre politique (`snapshot`, stabilité, weak consistency, etc.) ; elle doit être explicite.
### 22.4.2 `View<T>`
`View<T>` représente une vue légère sur une source existante :
```text
interface View<T> extends Iterable<T> {
const method count() -> uint64;
const method isEmpty() -> bool;
}
```
`View<T>` n'étend pas `Collection<T>`.
Le contrat général ne contient pas `contains()`. Une classe ou une interface plus spécialisée peut l'ajouter si elle en a réellement besoin.
Une `View<T>` :
```text
n'impose aucune copie des éléments
n'impose aucun stockage indépendant
n'impose aucune contiguïté
ne possède pas nécessairement les données qu'elle expose
ne peut jamais augmenter les permissions const de sa source
```
Par défaut, une vue est **vivante**, pas un snapshot : une nouvelle itération peut observer les modifications ultérieures de la source selon le contrat du type concret.
Le runtime/compilateur garantit que le stockage ou l'état source nécessaire reste valide aussi longtemps que la vue l'exige, sans syntaxe de lifetime utilisateur.
Le comportement d'un iterator déjà créé avant une modification reste régi par la politique d'invalidation/stabilité du type concret.
### 22.4.3 `Collection<T>`
`Collection<T>` étend `Iterable<T>` et représente une collection finie d'éléments.
Contrat minimal retenu :
Contrat minimal :
```text
count() -> uint64
@@ -175,7 +238,7 @@ contains(const T value) -> bool
`count()` exprime le nombre d'éléments au sens général de collection.
### 22.4.3 `List<T>`
### 22.4.4 `List<T>`
`List<T>` étend `Collection<T>` et `OpIndex<uint64,T>`.
@@ -193,33 +256,39 @@ Pour une `List<T>` :
count() == length()
```
Les deux noms sont néanmoins conservés parce qu'ils expriment des concepts différents : cardinalité générale de collection et longueur d'une séquence indexable.
Les deux noms restent distincts parce qu'ils expriment la cardinalité générale d'une collection et la longueur d'une séquence indexable.
`List<T>` ne garantit ni remplacement d'un élément, ni redimensionnement, ni complexité algorithmique particulière de l'accès indexé.
`List<T>` ne garantit ni remplacement, ni redimensionnement, ni complexité algorithmique particulière de l'accès indexé.
### 22.4.4 `SettableList<T>`
### 22.4.5 `SettableList<T>`
`SettableList<T>` étend `List<T>` et `OpIndexMut<uint64,T>`.
Elle garantit le remplacement d'un élément existant sans modification de la longueur.
Elle garantit le remplacement d'un élément existant sans modification de longueur :
```text
list[index] = value;
```
ne signifie jamais `append` lorsque `index == length()`.
`index` doit désigner une case existante ; l'affectation ne signifie jamais `append`.
### 22.4.5 `ResizableList<T>`
### 22.4.6 `ResizableList<T>`
`ResizableList<T>` étend `SettableList<T>`.
`ResizableList<T>` étend `SettableList<T>` et garantit des opérations structurelles capables de modifier la longueur.
Elle garantit des opérations structurelles capables de modifier la longueur, par exemple ajout, insertion et suppression.
Les signatures exactes seront figées avec `Vector<T>`.
Les signatures exactes seront figées lors de la définition du type concret redimensionnable.
Un `resize(newLength)` sans information d'initialisation n'est pas retenu implicitement : agrandir une collection doit définir comment les nouveaux éléments sont construits.
Un `resize(newLength)` sans information d'initialisation n'est pas retenu implicitement : agrandir une collection doit toujours définir comment les nouveaux éléments sont construits.
Lorsqu'il existe, `clear()` suit la convention :
### 22.4.6 Capacité du type et permission `const`
```text
clear() -> uint64
```
et retourne le nombre d'éléments effectivement supprimés.
### 22.4.7 Capacité du type et permission `const`
La capacité intrinsèque du type et la permission d'un accès sont deux dimensions distinctes.
@@ -230,9 +299,9 @@ const SettableList<User> readonly = users;
Le type sait remplacer des éléments, mais l'accès `readonly` ne peut pas utiliser cette capacité.
La propagation générale de `const` reste applicable aux éléments obtenus via cet accès.
La même règle s'applique à `Map`, `Set`, `View` et aux éléments obtenus à travers ces accès : une projection ne peut jamais augmenter les droits reçus de sa source.
### 22.4.7 Classification initiale
### 22.4.8 Classification initiale des séquences
```text
StaticArray<T,N>
@@ -258,9 +327,271 @@ Vector<T>
`Vector<T>` reste à définir précisément.
Un type persistant/immutable par nature n'a aucune obligation d'implémenter `List<T>`. Par exemple, un futur `PersistentList<T>` peut implémenter uniquement `Collection<T>` et fournir ses propres opérations si cela correspond mieux à sa sémantique.
Un type persistant/immutable par nature n'a aucune obligation d'implémenter `List<T>` s'il ne garantit pas sa sémantique.
Une interface Saselang n'est jamais implémentée uniquement parce qu'un type ressemble conceptuellement à une autre famille de types.
### 22.4.9 `Set<T>` et `ResizableSet<T>`
`Set<T>` étend `Collection<T>`.
Il garantit :
```text
unicité des éléments
aucun index ordinal
aucun ordre d'itération général garanti
```
La capacité structurelle est séparée :
```text
interface ResizableSet<T> extends Set<T> {
method add(T value) -> bool;
method remove(const T value) -> bool;
method clear() -> uint64;
}
```
Sémantique :
```text
add(value)
true -> élément ajouté
false -> élément déjà présent, set inchangé
remove(value)
true -> élément supprimé
false -> élément absent
clear()
-> nombre d'éléments effectivement supprimés
```
Il n'existe pas de niveau `SettableSet<T>` : remplacer un élément d'un set équivaut conceptuellement à une modification structurelle de son ensemble de valeurs.
### 22.4.10 `Map<K,V>`, `SettableMap<K,V>` et `ResizableMap<K,V>`
`Map<K,V>` n'étend pas `Collection<T>` et n'implémente pas directement `Iterable<...>`. Une map possède trois projections naturelles différentes : clés, valeurs et entrées ; aucune ne doit être choisie implicitement comme itération « par défaut ».
Contrat de lecture :
```text
interface Map<K,V> extends OpIndex<K,V> {
const method count() -> uint64;
const method isEmpty() -> bool;
const method containsKey(const K key) -> bool;
const method get(const K key) -> Option<V>;
const method keys() -> View<K>;
const method values() -> View<V>;
const method entries() -> View<MapEntry<K,V>>;
}
```
Deux intentions sont séparées :
```text
map[key] -> V
accès strict
la clé doit exister
absence dynamique -> KeyNotFoundFault
map::get(key) -> Option<V>
accès conditionnel
absence normale -> None
```
`SettableMap<K,V>` étend `Map<K,V>` et `OpIndexMut<K,V>`.
```text
map[key] = value;
```
remplace uniquement la valeur d'une clé existante. Cette syntaxe n'insère jamais implicitement une nouvelle clé. Une clé absente produit `KeyNotFoundFault`.
`ResizableMap<K,V>` étend `SettableMap<K,V>` et ajoute les changements structurels :
```text
insert(K key, V value) -> Void
faults DuplicateKeyFault
remove(const K key) -> Void
faults KeyNotFoundFault
clear() -> uint64
```
`insert` affirme que la clé est nouvelle ; `remove` affirme qu'elle existe. Leurs violations dynamiques sont des `Fault` unchecked et capturables.
Le Core ne fournit pas mécaniquement `tryInsert` / `tryRemove` ayant pour seule fonction de dupliquer ces opérations dans `Result` ou `bool`. Une variante conditionnelle ne sera ajoutée que si elle porte une sémantique réellement utile distincte.
Il n'existe pas de `put()` implicite « insert ou replace » tant qu'un besoin concret ne justifie pas cette troisième intention.
### 22.4.11 `MapEntry<K,V>`
`MapEntry<K,V>` représente une paire clé/valeur observée lors d'une projection `entries()`.
Conceptuellement :
```text
struct MapEntry<K,V> {
const method key() -> K;
const method value() -> V;
}
```
`MapEntry` n'est pas un proxy mutable vers un bucket interne de la map et n'expose pas de `setValue()` implicite.
La mutation d'une map reste explicite via :
```text
map[key] = value;
```
La sémantique de copie/référence de `K` et `V` suit les règles normales de leurs types.
### 22.4.12 Ordre : `Ordering`, `Comparable<T>` et `Comparator<T>`
L'ordre total applicatif utilise :
```text
enum Ordering {
Less,
Equal,
Greater
}
```
`Comparable<T>` exprime l'ordre naturel ou principal du type dans son domaine :
```text
interface Comparable<T> {
const method compareTo(const T other) -> Ordering;
}
```
Un type peut tout à fait implémenter `Comparable<T>` tout en supportant plusieurs ordres alternatifs via des comparators. Par exemple, un `User` d'un jeu peut avoir un classement principal naturel et rester comparable autrement par nom, niveau ou date.
`Comparator<T>` exprime un ordre externe/alternatif :
```text
interface Comparator<T> {
const method compare(const T left, const T right) -> Ordering;
}
```
Règle de sélection :
```text
Comparator explicitement fourni
-> utilisé exclusivement
aucun Comparator fourni
-> Comparable<T> requis
```
Aucun mélange/fallback entre les deux n'est effectué pendant une même opération ou dans une même collection ordonnée.
`Ordering::Equal` signifie équivalence selon cette relation d'ordre. Il n'implique pas nécessairement `left == right`.
Les implémentations de `Comparable` et `Comparator` doivent respecter cohérence, symétrie d'ordre et transitivité. Le compilateur n'est pas tenu de prouver ces propriétés générales.
### 22.4.13 `SortedSet<T>` et `SortedMap<K,V>`
`SortedSet<T>` étend `Set<T>` et garantit un ordre total stable selon le comparator actif :
```text
interface SortedSet<T> extends Set<T> {
const method comparator() -> Option<Comparator<T>>;
const method first() -> Option<T>;
const method last() -> Option<T>;
}
```
`None` pour `comparator()` signifie que l'ordre naturel `Comparable<T>` est utilisé. `Some(comparator)` expose l'ordre externe choisi pour cette instance.
`SortedMap<K,V>` étend `Map<K,V>` et ordonne les entrées par clé :
```text
interface SortedMap<K,V> extends Map<K,V> {
const method comparator() -> Option<Comparator<K>>;
const method firstKey() -> Option<K>;
const method lastKey() -> Option<K>;
}
```
Pour une `SortedMap`, les vues `keys()`, `values()` et `entries()` parcourent les données dans l'ordre des clés.
Les opérations de bornes/ranges (`floor`, `ceiling`, `lower`, `higher` ou noms définitifs) seront fermées séparément afin de ne pas introduire de noms ambigus par simple imitation d'une autre plateforme.
### 22.4.14 `TreeSet<T>` et `TreeMap<K,V>`
Les types standard ordonnés généraux sont nommés :
```text
TreeSet<T>
TreeMap<K,V>
```
Le nom n'impose pas une structure arborescente précise au niveau ABI/implémentation tant que le contrat observable reste respecté.
Un futur `BTreeMap<K,V>` peut exister comme implémentation explicitement fondée sur un B-tree ; il ne remplace pas le nom général `TreeMap`.
Comme Saselang n'autorise qu'un `construct` par classe, les deux modes de création passent par des factories de classe distinctes :
```text
TreeSet<T>::natural()
requiert T implements Comparable<T>
TreeSet<T>::withComparator(Comparator<T> comparator)
n'exige pas Comparable<T>
TreeMap<K,V>::natural()
requiert K implements Comparable<K>
TreeMap<K,V>::withComparator(Comparator<K> comparator)
n'exige pas Comparable<K>
```
Les contraintes appartiennent aux factories concernées et non nécessairement au type générique entier.
### 22.4.15 Contraintes des implémentations concrètes
Les interfaces abstraites `Set<T>` et `Map<K,V>` n'imposent pas une stratégie de stockage particulière.
Ainsi :
```text
HashSet<T> / HashMap<K,V>
peuvent exiger des capacités de hash/égalité
TreeSet<T> / TreeMap<K,V>
utilisent Comparable ou Comparator
```
`Hashable`, l'égalité et leurs contrats exacts seront fermés dans le bloc dédié. Ils ne sont pas imposés à `Set<T>` ou `Map<K,V>` eux-mêmes.
### 22.4.16 Collections concurrentes
Les garanties concurrentes sont orthogonales aux capacités structurelles et relèvent du SDK.
Une future :
```text
ConcurrentMap<K,V>
```
exprime des garanties de concurrence/atomicité et ne remplace pas `ResizableMap<K,V>`.
Un type concret peut par exemple implémenter les deux :
```text
ConcurrentHashMap<K,V>
implements ResizableMap<K,V>, ConcurrentMap<K,V>
```
Le SDK n'introduit pas une variante `ConcurrentX` pour chaque type uniquement par symétrie. Les interfaces concurrentes ne sont ajoutées que lorsqu'un contrat atomique/concurrent concret existe.
Une opération composée n'est pas atomique simplement parce que chaque appel individuel est thread-safe. Les futures opérations atomiques (`putIfAbsent`, comparaison/remplacement, etc.) seront spécifiées avec le modèle de concurrence.
## 22.5 `Range<T>` — V1 REQUIS — FIGÉ EN PRINCIPE
@@ -308,7 +639,7 @@ valeur finie autorisée
Les bornes sont évaluées de gauche à droite, exactement une fois chacune, avant la construction du range.
Une borne statiquement prouvée invalide provoque une erreur de compilation. Une borne calculée dynamiquement mais invalide selon le contrat de `Range<T>` provoque un runtime fault lors de la construction directe. Une API Core explicite retournant `Result` pourra être fournie lorsqu'une validation récupérable est souhaitée.
Une borne statiquement prouvée invalide provoque une erreur de compilation. Une borne calculée dynamiquement mais invalide selon le contrat de `Range<T>` provoque un `Fault` runtime lors de la construction directe. Le nom concret du fault sera fixé avec l'API Core ; aucune variante `try... -> Result` parallèle n'est créée automatiquement pour la même validation.
`lower > upper` ne constitue pas une erreur : le résultat est un range vide.
@@ -392,7 +723,7 @@ RangeStep<Date,Duration>
et de refuser des couples qui introduiraient une perte ou n'auraient pas de sens. Par exemple, un range `float16` peut recevoir seulement les types de pas explicitement supportés sans imposer qu'un `float32` plus large soit accepté.
Le pas exprime une magnitude de progression, pas une direction. Pour les types numériques Core, il doit être strictement supérieur à zéro. Un pas statiquement nul ou négatif est une erreur de compilation ; une valeur dynamique invalide provoque un runtime fault lors de la construction directe de la progression. Une API explicite en `Result` pourra exister pour une validation récupérable.
Le pas exprime une magnitude de progression, pas une direction. Pour les types numériques Core, il doit être strictement supérieur à zéro. Un pas statiquement nul ou négatif est une erreur de compilation ; une valeur dynamique invalide provoque un `Fault` runtime lors de la construction directe de la progression. Aucune variante `try... -> Result` n'est ajoutée uniquement pour dupliquer ce contrôle.
Pour les types utilisateur, la validité du pas est définie par le contrat Range correspondant et n'est pas réduite artificiellement à une comparaison numérique avec zéro.

View File

@@ -20,7 +20,7 @@ Il n'existe aucune promotion numérique implicite générale entre deux valeurs
```text
int8 small = ...;
int32 large = small; // ERROR
int32 large = small; // ERROR
int32 explicitLarge = small::toInt32(); // OK
```
@@ -48,63 +48,140 @@ if (animal is Dog) {
}
```
Lorsqu'un test du type runtime **exact** est nécessaire, `instanceof` est utilisé.
Lorsqu'un test du type runtime exact est nécessaire, `instanceof` est utilisé.
Saselang n'introduit pas pour le moment de syntaxe générale concurrente telle que `(Dog)value`, `value as Dog` ou `cast<Dog>(value)`. Une telle opération ne pourra être ajoutée que si un besoin distinct de `is`/`instanceof` + refinement est démontré et si son contrat d'échec est explicite.
## 24.5 API numérique du Core — V1 REQUIS — DIRECTION FIGÉE
## 24.5 API numérique du Core — V1 REQUIS — FIGÉ EN PRINCIPE
Les conversions numériques sont exposées comme membres standard des primitives définis par le Core. Les noms de méthodes ne sont pas des mots-clés du langage.
Le compilateur connaît les règles nécessaires pour typer, vérifier, constant-fold et abaisser ces opérations vers Sase IR, mais l'inventaire exhaustif de la surface API appartient au Core.
Principe de nommage :
L'introduction de `Fault` supprime l'obligation historique de créer une variante `try... -> Result<T,NumericConversionError>` uniquement parce qu'une conversion directe peut échouer.
Principe de nommage V1 :
```text
toTarget()
conversion exacte et totale
tryToTarget()
conversion exacte pour la valeur courante, récupérable si impossible
conversion exacte
totale si tout le domaine source est représentable
sinon valeur directe + faults NumericConversionFault
roundToTarget()
perte de précision explicitement acceptée, conversion totale
tryRoundToTarget()
perte de précision explicitement acceptée, mais conversion pouvant échouer
perte de précision / arrondi explicitement accepté
peut fault si une autre précondition reste violée, par exemple le domaine fini destination
saturateToTarget()
saturation explicite lorsqu'aucun arrondi supplémentaire n'est nécessaire
saturation explicite lorsque la politique de plage est le seul ajustement demandé
saturatingRoundToTarget()
arrondi + saturation explicitement annoncés
arrondi + saturation explicitement annoncés lorsque les deux sont nécessaires
wrapToTarget()
wrapping entier explicite
```
Une forme n'existe que si elle apporte une sémantique observable différente d'une autre forme déjà disponible pour la paire source/destination.
Une opération n'existe que si elle apporte une sémantique observable différente d'une autre forme disponible pour la paire source/destination.
Ainsi, lorsqu'un `toTarget()` exact et total existe, les variantes `tryToTarget()`, `saturateToTarget()` ou `wrapToTarget()` qui produiraient exactement le même résultat pour tout le domaine source sont absentes.
Le Core ne duplique pas mécaniquement :
```text
toTarget()
tryToTarget()
```
lorsque la seule différence serait que la seconde transporte le même échec dans `Result`.
Une API spécialisée peut toujours choisir volontairement `Result` si l'échec doit devenir une donnée normale du domaine, mais cela ne fait pas partie de la matrice canonique des conversions primitives.
Règle de composition : deux opérations Core ne sont fusionnées dans un même nom que si leur séparation en opérations successives modifierait la sémantique, perdrait de l'information ou empêcherait d'exprimer le même contrat.
Lorsqu'une composition existante est strictement équivalente, aucun alias combiné n'est ajouté au Core.
## 24.6 `float -> integer` — V1 REQUIS — DIRECTION FIGÉE
## 24.6 Entier -> entier — V1 REQUIS — FIGÉ EN PRINCIPE
Un flottant ne possède jamais un simple `toIntXX()` ou `toUintXX()`.
La conversion exacte stricte utilise :
Si toutes les valeurs source sont représentables exactement dans la destination :
```text
tryToIntXX()
tryToUintXX()
toTarget() -> Target
```
Elle réussit uniquement si la valeur source est finie, mathématiquement entière et représentable exactement dans le type destination. Sinon elle retourne `Result::Err(NumericConversionError(...))`.
est total et ne déclare aucun `NumericConversionFault` lié à la plage.
Les politiques mathématiques de traitement de la partie fractionnaire restent des opérations Core séparées :
Lorsque certaines valeurs source ne sont pas représentables :
```text
toTarget() -> Target
faults NumericConversionFault
```
avec `OutOfRange` lorsque la valeur courante n'entre pas dans le domaine destination.
Les politiques alternatives restent explicites :
```text
saturateToTarget()
wrapToTarget()
```
Elles ne sont présentes que lorsqu'elles peuvent produire un résultat différent de `toTarget()` pour cette paire.
Aucun wrapping ni saturation n'est implicite.
## 24.7 Entier -> flottant — V1 REQUIS — FIGÉ EN PRINCIPE
`toFloatXX()` exige une représentation exacte de la valeur entière.
```text
toFloatXX() -> floatXX
faults NumericConversionFault
```
n'a une clause `faults` que lorsque certaines valeurs source peuvent être inexactes ou hors du domaine fini destination.
`roundToFloatXX()` accepte explicitement l'arrondi canonique `nearest, ties to even` :
```text
roundToFloatXX() -> floatXX
```
Il peut encore produire `OutOfRange` si une valeur entière finie dépasse le domaine fini de la destination.
Lorsque l'arrondi et la saturation doivent être annoncés ensemble :
```text
saturatingRoundToFloatXX()
```
borne une magnitude finie trop grande au plus grand fini de même signe puis applique la précision destination.
Il n'existe pas de `wrapToFloatXX()`.
## 24.8 `float -> integer` — V1 REQUIS — FIGÉ EN PRINCIPE
La conversion stricte utilise désormais directement :
```text
toIntXX() -> intXX
faults NumericConversionFault
toUintXX() -> uintXX
faults NumericConversionFault
```
Elle réussit uniquement si la valeur source est :
```text
finie
mathématiquement entière
dans le domaine de la destination
exactement représentable comme entier destination
```
Sinon le fault porte la catégorie appropriée.
Les politiques mathématiques de traitement de la partie fractionnaire restent des opérations séparées :
```text
floor()
@@ -116,30 +193,38 @@ truncate()
Elles se composent avec la conversion :
```text
value::floor()::tryToInt32()
value::ceil()::tryToInt32()
value::round()::tryToInt32()
value::truncate()::tryToInt32()
value::floor()::toInt32()
value::ceil()::toInt32()
value::round()::toInt32()
value::truncate()::toInt32()
```
Saselang n'introduit donc pas les alias redondants `tryFloorToInt32()`, `tryCeilToInt32()`, `tryRoundToInt32()` ou `tryTruncateToInt32()` lorsque ces compositions possèdent exactement la même sémantique.
Saselang n'introduit pas les alias redondants `floorToInt32()`, `ceilToInt32()`, `roundToInt32()` ou `truncateToInt32()` lorsque ces compositions possèdent exactement la même sémantique.
Les politiques de dépassement de domaine suivent la même règle :
Les politiques de dépassement de domaine se composent de la même façon :
```text
value::floor()::trySaturateToInt32()
value::round()::tryWrapToInt32()
value::floor()::saturateToInt32()
value::round()::wrapToInt32()
```
`trySaturateToIntXX()` exige une valeur finie et mathématiquement entière, puis sature aux bornes du type destination. Les échecs qui ne relèvent pas de la plage, par exemple `NaN`, une infinité ou une valeur fractionnaire, restent des `NumericConversionError`.
Pour `float -> integer` :
`tryWrapToIntXX()` exige également une valeur finie et mathématiquement entière, puis applique le wrapping entier modulo `2^N` défini par Saselang.
```text
saturateToTarget()
exige encore une valeur finie et mathématiquement entière
faults NotFinite / NotIntegral si nécessaire
sature uniquement la plage
Une méthode combinée reste admise lorsqu'une décomposition changerait le contrat ou perdrait l'information nécessaire. C'est notamment le cas de `saturatingRoundToFloat32()` pour certains narrowings flottants : un `roundToFloat32()` séparé pourrait échouer avant que la saturation ne puisse être appliquée.
wrapToTarget()
exige encore une valeur finie et mathématiquement entière
faults NotFinite / NotIntegral si nécessaire
applique le wrapping modulo 2^N à l'entier mathématique fini
```
Aucun comportement de conversion ne dépend d'un profil, d'un linter ou d'un backend.
Une méthode combinée reste admise uniquement lorsqu'une décomposition changerait le contrat ou perdrait l'information nécessaire.
## 24.7 `float -> float`, valeurs IEEE spéciales — V1 REQUIS — FIGÉ
## 24.9 `float -> float`, valeurs IEEE spéciales — V1 REQUIS — FIGÉ
Toute conversion de valeur flottante préserve les catégories sémantiques suivantes lorsque la destination est un type flottant Saselang :
@@ -162,30 +247,33 @@ signe du NaN
représentation binaire exacte
```
Ces propriétés relèvent d'un éventuel contrat binaire distinct ; `bitcast<T>` ne peut les préserver que lorsque ses propres contraintes, notamment de taille, sont satisfaites.
Ces propriétés relèvent d'un contrat binaire distinct.
`tryToFloatXX()` traite `NaN` et les infinités comme des valeurs sémantiques représentables du type flottant destination. Le mot « exact » désigne ici l'exactitude de la valeur sémantique Saselang, pas l'identité bit-à-bit d'un payload NaN.
Pour une valeur finie :
Pour une réduction de format :
```text
tryToFloatXX()
exige une représentation exacte
toFloatXX()
exige l'exactitude de la valeur sémantique
faults Inexact ou OutOfRange pour une valeur finie si nécessaire
tryRoundToFloatXX()
roundToFloatXX()
accepte l'arrondi canonique
refuse une valeur finie hors domaine fini destination
faults OutOfRange si une valeur finie dépasse le domaine fini destination
saturatingRoundToFloatXX()
accepte l'arrondi canonique
sature une valeur finie hors domaine vers +/-Target::Max
```
Les infinités ne sont pas saturées puisqu'elles sont déjà des valeurs représentables du format flottant destination.
`NaN` et les infinities sont déjà représentables dans les formats flottants Saselang et ne sont donc pas saturés.
## 24.8 `NumericConversionError` — V1 REQUIS — NOM DE TRAVAIL / CODES FIGÉS
## 24.10 `NumericConversionFault` — V1 REQUIS — NOM DE TRAVAIL / CODES FIGÉS
`NumericConversionError` est retenu comme nom de travail du `ResultError` Core utilisé par les conversions numériques récupérables.
`NumericConversionFault` est retenu comme nom de travail du `Fault` Core utilisé par les conversions numériques directes dont les préconditions runtime peuvent échouer.
```text
NumericConversionFault extends Fault
```
Les catégories sémantiques V1 sont :
@@ -213,7 +301,7 @@ Inexact
alors que l'opération exige l'exactitude
```
`NumericConversionError` reste volontairement généraliste et minimal. Il n'ajoute pas automatiquement :
`NumericConversionFault` reste volontairement minimal. Il n'ajoute pas automatiquement :
```text
sourceType
@@ -224,21 +312,19 @@ backend
roundingMode
```
au-delà de l'état commun fourni par `ResultError`.
au-delà de l'état commun fourni par `Error`/`Fault`.
Le nom concret et la représentation numérique interne des codes pourront encore être ajustés avant stabilisation publique, mais les catégories sémantiques ci-dessus font partie du contrat V1.
## 24.9 Réduction anti-doublon par paire — V1 REQUIS — FIGÉ
## 24.11 Réduction anti-doublon par paire — V1 REQUIS — FIGÉ
La matrice examine chaque paire `Source -> Target` et n'expose que les opérations dont le comportement peut réellement être distingué sur le domaine source.
Ainsi, pour une paire `float -> integer` dont toutes les valeurs finies mathématiquement entières sont déjà dans le domaine signé destination, `trySaturateToTarget()` et `tryWrapToTarget()` seraient des doublons de `tryToTarget()` et n'existent pas.
Lorsqu'une opération directe est totale pour toute la paire, elle ne déclare aucun fault inutile.
Pour une destination non signée, les valeurs négatives suffisent généralement à rendre les politiques checked, saturating et wrapping distinctes.
Lorsqu'une politique `saturate` ou `wrap` ne peut jamais différer de la conversion exacte sur cette paire, elle est absente.
La matrice exhaustive en annexe est normative sur ce point.
La disparition des variantes `try...` ne modifie pas cette règle de non-redondance ; elle supprime seulement les duplications qui ne différaient que par le canal d'échec `ResultError` versus valeur directe.
## 24.10 Disponibilité Core et target — V1 REQUIS — FIGÉ EN PRINCIPE
## 24.12 Disponibilité Core et target — V1 REQUIS — FIGÉ EN PRINCIPE
Les conversions fondamentales de la matrice sont des capacités Core obligatoires dès lors que les types source et destination sont supportés par le target.
@@ -248,9 +334,9 @@ Un target réduit peut ne pas supporter un type fondamental donné. Dans ce cas
Voir également le chapitre 40.
## 24.11 Matrice exhaustive — ANNEXE NORMATIVE EN COURS DE GEL
## 24.13 Matrice exhaustive — ANNEXE NORMATIVE EN COURS DE GEL
L'inventaire source destination des opérations de conversion est maintenu séparément afin d'éviter les oublis, doublons et incohérences.
L'inventaire source -> destination des opérations de conversion est maintenu séparément afin d'éviter les oublis, doublons et incohérences.
Voir :
@@ -258,6 +344,6 @@ Voir :
annexes/A-numeric-conversions.md
```
L'annexe doit lister le maximum de possibilités sémantiquement distinctes avant réduction finale de l'API Core.
L'annexe liste les possibilités sémantiquement distinctes après suppression des variantes `try...` purement liées à l'ancien canal `ResultError`.
---

View File

@@ -13,11 +13,13 @@ threads natifs
structured concurrency éventuelle
cancellation
synchronisation
interaction avec Result/throws
interaction avec Result/throws/faults
interaction avec destruction déterministe
runtime minimal
```
Aucune dépendance obligatoire à un executor monolithique ne doit être supposée sans justification.
Les collections concurrentes spécialisées relèvent du SDK et non d'une symétrie artificielle du Core. Une future `ConcurrentMap<K,V>` doit exprimer des garanties de concurrence/atomicité orthogonales aux capacités structurelles `Map` / `SettableMap` / `ResizableMap`. Des types tels que `ConcurrentHashMap<K,V>` ne seront introduits que lorsqu'un contrat concurrent concret le justifie.
---

View File

@@ -66,11 +66,51 @@ unsafe field
Le caractère dangereux est porté par le type/opération.
## 27.4 Raw pointers — V1 REQUIS — À FINALISER
## 27.4 Pointeurs et accès mémoire bas niveau — V1 REQUIS — À FINALISER CRITIQUE
Les pointeurs bruts existent explicitement et leurs opérations de dereference/arithmétique autorisées doivent être précisément définies.
Le modèle mémoire contient un sous-ensemble dédié aux pointeurs ; il ne doit pas être confondu avec les références de classe ou `Slice<T>`.
Les noms des types (`Ptr<T>`, `PtrMut<T>` ou autre) restent à figer.
La spécification devra distinguer au minimum :
```text
référence de classe
accès sûr ordinaire vers un objet
Slice<T>
vue sûre, bornée et contiguë sur un stockage existant
pointeur Saselang
adresse explicite avec contrat de type/mutabilité à définir
pointeur brut / FFI
accès mémoire bas niveau potentiellement unsafe
function pointer
représentation ABI d'une cible callable, distincte des callables/closures ordinaires
```
Les noms exacts des types (`Ptr<T>`, `PtrMut<T>` ou autre) restent à figer.
Doivent être définis explicitement :
```text
nullabilité et non-null par défaut éventuel
const T pointé et constness du pointeur lui-même
dereference
address-of
arithmétique de pointeurs
alignement
allocation/libération manuelles
casts entre pointeurs
conversion pointeur <-> entier si elle existe
pointeur opaque / équivalent éventuel de void*
function pointers
FFI C
ownership ou absence d'ownership attaché au pointeur
interaction avec allocator, lifetime interne et thread safety
```
Le code ordinaire ne doit pas avoir besoin de pointeurs explicites lorsque les références de classe, arrays, slices ou autres abstractions sûres suffisent. Les opérations dangereuses doivent rester dans l'inventaire fermé `unsafe`.
## 27.5 Allocator — V1 REQUIS — À FINALISER CRITIQUE

View File

@@ -37,6 +37,7 @@ Les sorties qui déclenchent les `defer` du scope traversé comprennent notammen
fin normale
return
throw
Fault propagé
break
continue
emit quittant le scope concerné
@@ -46,12 +47,12 @@ La valeur d'un `return` ou d'un `emit` est déterminée avant l'exécution des c
## 28.2 Unwind et ordre avec `catch` / `finally` — V1 REQUIS — FIGÉ
Lorsqu'une `Exception` quitte un scope, les `defer` des scopes abandonnés sont exécutés avant l'entrée dans le `catch` correspondant.
Lorsqu'une `Exception` ou un `Fault` capturable quitte un scope, les `defer` des scopes abandonnés sont exécutés avant l'entrée dans le `catch` correspondant, sous réserve des détails d'unwind de `Fault` encore à fermer avec le runtime.
Ordre conceptuel :
```text
throw
throw / Fault propagé
-> unwind des scopes quittés
-> defer de ces scopes, LIFO
-> catch correspondant éventuel
@@ -73,6 +74,7 @@ Sont interdits dans un `defer` lorsqu'ils quittent le bloc :
```text
return
throw
fault
break
continue
emit
@@ -80,6 +82,8 @@ emit
Aucune `Exception` non capturée ne peut sortir indirectement d'un `defer`. Une callable appelée depuis un `defer` et susceptible de lancer une `Exception` doit voir cette exception entièrement gérée à l'intérieur du bloc `defer`.
Un `fault` explicite est interdit dans un `defer` pour la même raison qu'un `throw` explicite : le cleanup ne doit pas volontairement remplacer une sortie déjà en cours. Un `Fault` dynamique produit indirectement reste possible car il est unchecked ; son interaction exacte avec l'unwind/destruction fait partie du modèle mémoire encore à fermer.
Un `ResultError` reste une simple valeur : un appel retournant `Result<T,E>` est autorisé dans un `defer`, sous réserve des règles normales de traitement de cette valeur.
## 28.4 `finally` versus `defer` — V1 REQUIS — FIGÉ
@@ -94,4 +98,4 @@ Un `try/finally` sans `catch` est interdit précisément parce que `defer` couvr
Pas de `errdefer` séparé en V1.
`catch` gère les chemins d'`Exception`; `defer` gère le cleanup systématique du scope. Un troisième mécanisme serait redondant.
`catch` gère les chemins d'`Exception` et de `Fault`; `defer` gère le cleanup systématique du scope. Un troisième mécanisme dédié uniquement à l'échec serait redondant.

View File

@@ -214,7 +214,7 @@ désactiver les contrôles d'overflow définis par Saselang
modifier la sémantique d'un index hors limites
modifier l'ordre d'évaluation
modifier la représentation sémantique des types
changer les règles Result/throws
changer les règles Result/throws/faults
désactiver les vérifications de sûreté du langage
faire varier la validité d'un programme Saselang autrement que par une limite propre au backend/target
```

View File

@@ -12,7 +12,9 @@ Pas de visibilité explicite.
`main` ne peut pas déclarer `throws`, même s'il retourne `Result`.
L'environnement top-level ne possède pas d'appelant Saselang capable de l'envelopper dans un `try/catch`.
L'environnement top-level ne possède pas d'appelant Saselang capable d'assumer un contrat checked : toute `Exception` doit donc être gérée avant de quitter `main`.
`main` peut documenter des `Fault` par `faults`, comme toute callable. Un `Fault` non capturé atteint la frontière de l'unité d'exécution et provoque l'échec runtime correspondant.
## 42.2 `ExitCode` — V1 REQUIS — FIGÉ EN PRINCIPE

View File

@@ -74,7 +74,7 @@ Les commentaires documentaires sont :
/** documentation de bloc */
```
Ils ont la même sémantique documentaire avec deux ergonomies d'écriture différentes. Saselang ne reprend pas la redondance Javadoc/PHPDoc consistant à dupliquer systématiquement la signature via `@param`, `@return` ou `@throws` ; `saseldoc` dérive ces informations du programme.
Ils ont la même sémantique documentaire avec deux ergonomies d'écriture différentes. Saselang ne reprend pas la redondance Javadoc/PHPDoc consistant à dupliquer systématiquement la signature via `@param`, `@return`, `@throws` ou `@faults` ; `saseldoc` dérive ces informations du programme.
Une Saseldoc est autorisée uniquement lorsqu'elle est immédiatement attachée à une déclaration documentable. Elle n'est jamais un commentaire libre à l'intérieur d'un corps exécutable.
@@ -128,7 +128,7 @@ Un marqueur `@...` inconnu dans un commentaire documentaire ne doit pas faire é
La visibilité n'est pas réduite artificiellement à un ordre total lorsque les domaines `protected`, `module` et `package` ne sont pas sémantiquement comparables. L'interface de filtrage doit pouvoir exprimer les ensembles de visibilité nécessaires sans falsifier le modèle d'accès du langage.
Les signatures, types de paramètres, generics, visibilité, `Result`, `throws`, héritage et interfaces sont dérivés du modèle sémantique plutôt que redéclarés manuellement dans la Saseldoc.
Les signatures, types de paramètres, generics, visibilité, `Result`, `throws`, `faults`, héritage et interfaces sont dérivés du modèle sémantique plutôt que redéclarés manuellement dans la Saseldoc.
Les formats exacts V1 restent à finaliser avec la CLI, mais un format de sortie n'implique jamais un outil séparé.

View File

@@ -41,16 +41,17 @@ Restent à fermer :
## 48.4 Erreurs
Points désormais largement figés : hiérarchie `Error` / `ResultError` / `Exception`, `Result::Ok` / `Result::Err`, contraintes de `Result<T,E>`, absence de `unwrap` et `?` V1, `throw` / `throws`, contrats d'override/interface, sélection ordonnée des `catch`, `finally` non-escaping, causes, i18n, code de `ResultError`, stack trace d'`Exception` liée au `throw`, et sémantique LIFO/non-escaping de `defer`.
Points désormais largement figés : hiérarchie `Error` / `ResultError` / `Exception` / `Fault`, `Result::Ok` / `Result::Err`, contraintes de `Result<T,E>`, absence de `unwrap` et `?` V1, `throw` / `throws` pour les exceptions checked, `fault` / `faults` pour les faults unchecked, `catch` limité aux branches `Exception` et `Fault`, contrats d'override `throws`, causes, i18n, code de `ResultError`, stack trace diagnostique et sémantique LIFO/non-escaping de `defer`.
Restent à fermer :
- faute runtime/panic/fatal error ;
- interaction exacte cleanup/destruction/unwind pendant un `Fault` ;
- unité d'exécution exacte terminée par un `Fault` non capturé ;
- constructors faillibles et construction partielle ;
- interaction exacte cleanup/destruction pendant fault ;
- ordre exact `defer` / destructeurs automatiques avec le modèle mémoire ;
- représentation Core/runtime exacte de `I18nMessage`, `ResultErrorCode`, `StackTrace` et `StackFrame` ;
- éventuels helpers Core futurs `expectOk` / `expectErr` seulement si un besoin réel est démontré.
- politique de lint/documentation pour les `Fault` explicites non listés dans `faults` ;
- éventuels helpers Core futurs de `Result` seulement si un besoin réel est démontré.
## 48.5 Contrôle de flux
@@ -97,7 +98,10 @@ Restent à fermer :
## 48.9 Core/SDK
- frontière Core/SDK définitive ;
- collections V1 ;
- `Vector<T>` et interaction avec `Slice<T>` lors des reallocations ;
- contrats `Hashable` / égalité et implémentations HashSet/HashMap ;
- opérations avancées de bornes/ranges pour `SortedSet` / `SortedMap` ;
- propagation formelle de `const` dans `View<T>`, `Option<T>`, `Iterator<T>` et autres wrappers ;
- String encodings ;
- regex ;
- filesystem/network/process ;

View File

@@ -13,8 +13,8 @@ Les futures décisions doivent respecter les invariants suivants :
9. **Les types Core compiler-known restent peu nombreux.**
10. **Le package, le namespace, le target, la feature, la capability et l'artifact restent des dimensions distinctes.**
11. **Une collision de namespace entre packages ne doit jamais fusionner implicitement leurs types.**
12. **Les erreurs récupérables utilisent `Result`; les opérations réellement infaillibles retournent directement leur valeur.**
13. **`throws` n'existe que sur une callable retournant `Result`.**
12. **`ResultError`, `Exception` et `Fault` sont trois mécanismes distincts : valeur d'échec explicite, propagation checked et échec unchecked capturable.**
13. **`throws` ne contient que des `Exception` et reste indépendant du type de retour ; `faults` ne contient que des `Fault` et reste optionnel/non exhaustif.**
14. **L'identité d'objet est distincte de l'égalité de valeur.**
15. **Les opérateurs utilisateur passent uniquement par les contrats Core `Op...`.**
16. **Les interfaces fournissent l'héritage multiple de contrats/comportements sans introduire un second système de traits.**
@@ -34,5 +34,6 @@ Les futures décisions doivent respecter les invariants suivants :
29. **Un array safe devient observable uniquement après initialisation complète.**
30. **Une bibliothèque spécialisée peut être officielle sans appartenir au Core ou au SDK obligatoire ; son artifact normal reste une `.saselib`.**
31. **Les dépendances natives de bootstrap peuvent être remplacées progressivement par des implémentations Saselang sans imposer leur architecture historique au langage.**
32. **Lorsqu'une forme légèrement plus longue supprime un implicite ou une ambiguïté réelle, Saselang privilégie l'explicite.**
---