Files
saselang-bible/chapters/020-operateurs.md
2026-09-13 21:59:15 +02:00

10 KiB

20. Opérateurs

20.1 Principe — V1 REQUIS — FIGÉ

Les opérateurs utilisateur ne peuvent être fournis que via un ensemble fermé d'interfaces Core compiler-known préfixées Op.

L'utilisateur ne peut pas inventer de nouveaux symboles opérateurs.

Les contrats operator :

  • retournent directement leur valeur ;
  • ne retournent jamais Result ;
  • 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 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É

OpPositive<T>
OpNegate<T>

OpAdd<Lhs,Rhs,Out>
OpSubtract<Lhs,Rhs,Out>
OpMultiply<Lhs,Rhs,Out>
OpDivide<Lhs,Rhs,Out>
OpRemainder<Lhs,Rhs,Out>

Les opérations binaires peuvent être hétérogènes.

Exemples légitimes :

Vector3 * float64 -> Vector3
Matrix * Vector3 -> Vector3
Timestamp + Duration -> Timestamp
Timestamp - Timestamp -> Duration

L'ordre compte : A op B est distinct de B op A.

20.3 Interfaces bitwise — V1 REQUIS — FIGÉ

OpBitNot<T>
OpBitAnd<Lhs,Rhs,Out>
OpBitOr<Lhs,Rhs,Out>
OpBitXor<Lhs,Rhs,Out>
OpShiftLeft<T,Out>
OpShiftRight<T,Out>

Pour les primitives numériques, & | ^ exigent les mêmes types primitifs exacts, sans promotion implicite.

Les shifts primitifs utilisent initialement uint32 comme type de count.

Count hors largeur :

statique -> erreur compilation
dynamique -> faute runtime

>> :

unsigned -> logical zero-fill
signed   -> arithmetic sign extension

Pas de >>>.

20.4 Logique booléenne — V1 REQUIS — FIGÉ

Short-circuit :

!a
a && b
a || b

Eager :

a & b
a | b
a ^ b

&&, || et ! ne sont pas surchargeables.

&, |, ^ ont une sémantique intrinsèque eager pour bool, bitwise pour les primitives entières et peuvent utiliser les contrats OpBit... sur types utilisateur.

Pas de NAND/NOR natifs.

20.5 Égalité — V1 REQUIS — FIGÉ

OpPartialEqual<Lhs,Rhs>
OpEqual<T>
Equatable<T>

OpEqual<T> renforce :

OpPartialEqual<T,T>

et garantit contractuellement une relation d'équivalence :

réflexive
symétrique
transitive

Equatable<T> est la capacité Core destinée aux APIs génériques qui ont besoin de cette égalité totale. Elle réutilise l'implémentation opérateur canonique et n'introduit ni seconde méthode equals() ni deuxième définition de == :

interface Equatable<T> extends OpEqual<T> {
}

Ainsi, la règle générale reste inchangée : les opérateurs utilisateur sont définis uniquement via les interfaces compiler-known Op..., tandis que Equatable<T> permet d'exprimer une contrainte sémantique de haut niveau sans dupliquer l'opération.

Les float* conservent les comparaisons IEEE de la version normative adoptée. Comme NaN == NaN est faux, ils ne satisfont pas directement Equatable<float*> et relèvent de l'égalité partielle générale.

!= est toujours la négation logique de ==, jamais une implémentation indépendante.

Les classes ne reçoivent aucune comparaison champ-à-champ automatique : elles doivent choisir explicitement leur sémantique d'égalité si elles veulent supporter ==. L'identité de référence universelle reste distincte et s'obtient par Object::sameIdentity(...).

20.6 Comparaison — V1 REQUIS — FIGÉ EN PRINCIPE

Core distingue l'ordre partiel de l'ordre total :

public enum PartialOrdering {
    Less,
    Equal,
    Greater,
    Unordered
}

public enum Ordering {
    Less,
    Equal,
    Greater
}

Interfaces opérateur :

OpPartialCompare<Lhs,Rhs>
    -> PartialOrdering

OpCompare<T>
    -> Ordering

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.

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

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

Saselang doit conserver la possibilité d'égalité/comparaison hétérogène.

La forme exacte des paramètres génériques et les règles de symétrie automatique doivent encore être formalisées pour éviter les implémentations concurrentes incohérentes.

20.8 Ownership des implémentations opérateur — V1 REQUIS — FIGÉ EN PRINCIPE

Direction :

  • normalement l'implémentation appartient au type nominal de gauche ;
  • si le LHS est une primitive/Core sealed non extensible, le type nominal de droite peut fournir une forme inverse explicitement permise ;
  • un troisième package ne peut pas définir arbitrairement un opérateur entre deux types externes ;
  • une seule implémentation canonique par (operator,Lhs,Rhs).

Les détails doivent être alignés sur le resolver des packages.

20.9 Indexation — V1 REQUIS — FIGÉ

OpIndex<Index,Read>
OpIndexMut<Index,Write>

Les deux contrats sont indépendants.

Syntaxe :

container[index]
container[index] = value

Les séquences standard utilisent initialement uint64 comme index.

D'autres index numériques pourront être supportés ultérieurement sans modifier la largeur de uint64.

Un type utilisateur peut utiliser n'importe quel type d'index pertinent.

20.10 Map — V1 REQUIS — FIGÉ EN PRINCIPE

Le contrat des maps est défini au chapitre 22.

L'indexation est stricte :

map[key] -> V

La clé doit exister ; une absence dynamique produit KeyNotFoundFault.

L'accès conditionnel est explicite et distinct :

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É

= n'est pas une expression.

Interdits :

if (a = b) { ... }
x = (a = b);
a = b = c;

20.12 Affectations composées — V1 REQUIS — FIGÉ

+= -= *= /= %=
&= |= ^=
<<= >>=

Elles n'ont aucun Op... propre et ne sont donc pas surchargeables directement.

Elles sont composées de :

lecture
+ opérateur fondamental surchargeable
+ réaffectation

Le résultat de l'opération doit être assignable exactement à la destination.

Aucune conversion implicite ne doit être inventée pour rendre l'affectation composée valide.

20.13 is — V1 REQUIS — FIGÉ

is teste l'appartenance d'une valeur à un type polymorphe ou à l'un de ses sous-types runtime.

value is Type

Pour une instance runtime de Labrador avec Labrador extends Dog et Dog extends Animal :

value is Animal      // true
value is Dog         // true
value is Labrador    // true

is est utilisable sur les classes et les interfaces lorsqu'un polymorphisme runtime existe. Il est interdit sur les types sans polymorphisme runtime tels que les primitives numériques, char, les structs, les enums, les tuples ou les unions.

is n'est pas surchargeable.

Dans une branche dont la condition positive prouve value is Type, le compilateur raffine value vers Type dans le scope correspondant. Le raffinement cesse avec ce scope.

Saselang n'introduit pas is not : la négation générale s'écrit explicitement :

!(value is Type)

20.14 instanceof — V1 REQUIS — FIGÉ

instanceof teste le type de classe runtime exact.

value instanceof ConcreteClass

Pour une instance runtime exacte de Labrador :

value instanceof Animal      // false
value instanceof Dog         // false
value instanceof Labrador    // true

instanceof est utilisable uniquement avec une classe concrète non abstraite. Il est interdit avec une interface, une classe abstraite et tout type sans identité de classe runtime.

instanceof n'est pas surchargeable.

Une branche positive raffine la valeur vers la classe concrète testée et établit en plus que son type runtime est exactement cette classe.

Saselang n'introduit pas not instanceof :

!(value instanceof ConcreteClass)

is et instanceof coexistent parce qu'ils ont deux sémantiques différentes : appartenance à une hiérarchie pour is, identité exacte de classe runtime pour instanceof.

20.15 bitcast<T> — V1 REQUIS — FIGÉ EN PRINCIPE

bitcast<T>(value)

est un intrinsic compiler-known, pas un opérateur.

Il préserve exactement les bits et exige des tailles compatibles.

Il ne réalise aucune conversion numérique.

Certains bitcasts sont sûrs lorsque tout motif de bits destination est valide ; d'autres nécessiteront unsafe.

Le tableau exact doit être lié au modèle mémoire.

20.16 Appel () — V1 REQUIS — FIGÉ EN PRINCIPE

() est une construction d'appel du langage, pas un opérateur utilisateur.

Il n'est pas prévu de fournir un OpCall général.

Les fonctions, closures/lambdas futures seront appelables par définition de leur type, pas en surchargeant arbitrairement () sur n'importe quel objet.

20.17 Précédence — V1 REQUIS — FIGÉ

Du plus fort au plus faible :

1. qualification / membre       . ::
2. appel / indexation           () []
3. préfixes                     +x -x !x ~x
4. multiplicatifs               * / %
5. additifs                     + -
6. shifts                       << >>
7. relation / type              < <= > >= is instanceof
8. égalité                      == !=
9. bitwise AND                  &
10. bitwise XOR                 ^
11. bitwise OR                  |
12. logique short-circuit AND   &&
13. logique short-circuit OR    ||

bitcast<T>(...) suit la syntaxe d'appel et n'a pas sa propre précédence.

= et les affectations composées ne figurent pas dans cette hiérarchie puisqu'elles ne produisent pas de valeur.

20.18 Associativité — V1 REQUIS — FIGÉ

Les opérateurs binaires arithmétiques/bitwise/logiques sont associatifs syntaxiquement à gauche.

Les préfixes s'imbriquent depuis la droite.

Les comparaisons sont non associatives :

a < b < c      -> interdit
a == b == c    -> interdit

Écrire explicitement :

a < b && b < c