diff --git a/doc/handwritten/for-maintainers/README.fr.md b/doc/handwritten/for-maintainers/README.fr.md index eec1cec..062a49f 100644 --- a/doc/handwritten/for-maintainers/README.fr.md +++ b/doc/handwritten/for-maintainers/README.fr.md @@ -4,7 +4,7 @@ > Documentation pour les **mainteneurs et opérateurs** de FirstClassErrors — > comment le projet est construit, publié et maintenu en bonne santé. Elle ne -> fait **pas** partie de la documentation utilisateur de la librairie, sous +> fait **pas** partie de la documentation utilisateur de la bibliothèque, sous > [`doc/`](../for-users/). La version anglaise est canonique ; les pages françaises sont > tenues à jour en parallèle. diff --git a/doc/handwritten/for-maintainers/ReleaseDryRun.fr.md b/doc/handwritten/for-maintainers/ReleaseDryRun.fr.md index 9e983d6..47916e8 100644 --- a/doc/handwritten/for-maintainers/ReleaseDryRun.fr.md +++ b/doc/handwritten/for-maintainers/ReleaseDryRun.fr.md @@ -7,7 +7,7 @@ référence des workflows pour [`release`](workflows/release.fr.md) et [`release-dryrun`](workflows/release-dryrun.fr.md). > Documentation technique / mainteneur. Elle ne fait **pas** partie de la -> documentation utilisateur de la librairie, sous `doc/`. +> documentation utilisateur de la bibliothèque, sous `doc/`. ## De quoi s'agit-il diff --git a/doc/handwritten/for-maintainers/adr/0001-lock-the-analyzer-roslyn-floor.fr.md b/doc/handwritten/for-maintainers/adr/0001-lock-the-analyzer-roslyn-floor.fr.md index 85dfdbf..715d561 100644 --- a/doc/handwritten/for-maintainers/adr/0001-lock-the-analyzer-roslyn-floor.fr.md +++ b/doc/handwritten/for-maintainers/adr/0001-lock-the-analyzer-roslyn-floor.fr.md @@ -77,7 +77,7 @@ une brèche que les autres ne peuvent fermer : de l'assembly et échoue, rapidement et in-process, si un quelconque assembly `Microsoft.CodeAnalysis*` référencé le dépasse — attrapant la dérive de *référence* ; -* **le job CI de floor-check** empaquette la librairie et reconstruit +* **le job CI de floor-check** empaquette la bibliothèque et reconstruit l'exemple contre l'analyzer empaqueté sous le SDK du floor, prouvant que l'**artefact livré** à la fois se **charge** et est **packagé** correctement sur le **compilateur pris en charge le plus ancien** — les deux brèches que le test diff --git a/doc/handwritten/for-maintainers/adr/0002-floor-the-tooling-runtime.fr.md b/doc/handwritten/for-maintainers/adr/0002-floor-the-tooling-runtime.fr.md index 0409c95..9ab27fd 100644 --- a/doc/handwritten/for-maintainers/adr/0002-floor-the-tooling-runtime.fr.md +++ b/doc/handwritten/for-maintainers/adr/0002-floor-the-tooling-runtime.fr.md @@ -10,8 +10,8 @@ FirstClassErrors livre deux types d'artefacts très différents : -* la **librairie** (`FirstClassErrors`, `FirstClassErrors.Testing`) cible - **`netstandard2.0`**. Une librairie netstandard est consommée par +* la **bibliothèque** (`FirstClassErrors`, `FirstClassErrors.Testing`) cible + **`netstandard2.0`**. Une bibliothèque netstandard est consommée par *n'importe quel* runtime qui implémente le standard — .NET Framework 4.6.1+, .NET Core 2.0+, .NET 5–10+, Mono/Unity — de sorte que la question « fonctionne presque partout » est déjà résolue, une seule fois, par le TFM, @@ -26,9 +26,9 @@ FirstClassErrors livre deux types d'artefacts très différents : Le TFM de l'outillage décide donc *quels consommateurs peuvent exécuter `fce` tout court*. Il était à `net10.0`, ce qui signifiait qu'un atelier dont le -runtime installé le plus récent est .NET 8 pouvait référencer la librairie +runtime installé le plus récent est .NET 8 pouvait référencer la bibliothèque mais **ne pouvait pas exécuter le générateur de documentation** — alors même que -la librairie qu'il documente est `netstandard2.0`. +la bibliothèque qu'il documente est `netstandard2.0`. Il existe une seconde contrainte, plus subtile. Le worker charge l'assembly **cible** via `Assembly.LoadFrom` (voir @@ -61,7 +61,7 @@ avec le roll-forward, et non une matrice de cibles. ## Justification Le floor réduit l'histoire du support à une seule phrase : *FirstClassErrors -supporte .NET 8 et au-delà pour son outillage et son analyzer ; la librairie +supporte .NET 8 et au-delà pour son outillage et son analyzer ; la bibliothèque elle-même est `netstandard2.0` et descend jusqu'à .NET Framework 4.6.1.* Le floor de l'outillage et le floor de l'analyzer (ADR-0001) énoncent le **même** minimum, de sorte que le produit énonce **un seul** chiffre de support. @@ -72,7 +72,7 @@ Le roll-forward couvre tous les runtimes plus récents, ajusté par processus : |---|---|---| | `FirstClassErrors.Cli` (`fce`) | `Major` | Le front-end a seulement besoin de *s'exécuter*. `Major` monte le build net8 à la majeure suivante lorsque .NET 8 est absent, de sorte qu'une machine qui n'a que .NET 10 l'exécute (monte 8→10). Sans lui, la politique `Minor` par défaut ne franchit jamais une majeure et `fce` échouerait à démarrer sur la machine courante « .NET plus récent, pas de .NET 8 ». | | `FirstClassErrors.GenDoc.Worker` | `LatestMajor` | Le worker doit **surclasser la cible qu'il charge**. `Major` ne monte que lorsque la majeure demandée est *absente*, de sorte que sur une machine qui embarque **à la fois** .NET 8 et .NET 10, un worker net8 se lierait à 8 et échouerait à charger une cible net10. `LatestMajor` se lie toujours à la **plus haute** majeure **installée**, de sorte que le worker peut documenter une cible compilée pour n'importe quel runtime présent. | -| `FirstClassErrors.GenDoc` | — | Chargé in-process par `fce` ; le runtime est choisi par le runtimeconfig de la CLI, de sorte qu'une librairie ne fixe aucune politique. | +| `FirstClassErrors.GenDoc` | — | Chargé in-process par `fce` ; le runtime est choisi par le runtimeconfig de la CLI, de sorte qu'une bibliothèque ne fixe aucune politique. | `latest` reste sur les trois projets afin que le floor net8 ne borne que la **surface BCL et le runtime cible**, et non le C# que la @@ -121,7 +121,7 @@ roll-forward. Rejeté parce que le TFM est un minimum strict pour une application dépendante du framework : un atelier dont le runtime installé le plus récent est .NET 8 -pourrait référencer la librairie `netstandard2.0` mais ne pourrait pas +pourrait référencer la bibliothèque `netstandard2.0` mais ne pourrait pas exécuter le générateur de documentation qui la documente. ### Multi-cibler l'outillage (`net8.0;net10.0`) @@ -168,7 +168,7 @@ simple et a la même portée. * `fce` ne peut pas s'exécuter sur une machine dont le runtime le plus récent est antérieur à .NET 8 (par exemple un .NET 6/7 en EOL, ou uniquement .NET Framework). Accepté : ces consommateurs **utilisent** toujours la - librairie `netstandard2.0` dans leur application ; exécuter un *outil* de + bibliothèque `netstandard2.0` dans leur application ; exécuter un *outil* de dev/CI sur un runtime actuellement supporté est un prérequis raisonnable (un SDK .NET moderne est déjà présent partout où l'on compile du .NET moderne). diff --git a/doc/handwritten/for-maintainers/adr/0003-unify-outcome-value-mapping-under-then.fr.md b/doc/handwritten/for-maintainers/adr/0003-unify-outcome-value-mapping-under-then.fr.md index 551c38c..50407a4 100644 --- a/doc/handwritten/for-maintainers/adr/0003-unify-outcome-value-mapping-under-then.fr.md +++ b/doc/handwritten/for-maintainers/adr/0003-unify-outcome-value-mapping-under-then.fr.md @@ -8,12 +8,12 @@ ## Contexte -* `Outcome` et `Outcome` sont le type de résultat succès-ou-échec de la librairie ; ils se composent au travers d'une petite surface fluide. +* `Outcome` et `Outcome` sont le type de résultat succès-ou-échec de la bibliothèque ; ils se composent au travers d'une petite surface fluide. * Deux opérations de composition existaient sous des noms distincts : `Then` enchaînait avec une fonction pouvant elle-même échouer (elle retourne un `Outcome`), et `To` transformait une valeur en cas de succès avec une fonction ne pouvant pas échouer (elle retourne une valeur simple). Ce sont les opérations *bind* et *map* de la programmation fonctionnelle. -* L'objectif affiché de la librairie est une API nommée pour l'**intention**, non pour la mécanique de la programmation fonctionnelle ; la FAQ défend ce choix précis lorsqu'elle oppose `Outcome` à un `Result` générique. +* L'objectif affiché de la bibliothèque est une API nommée pour l'**intention**, non pour la mécanique de la programmation fonctionnelle ; la FAQ défend ce choix précis lorsqu'elle oppose `Outcome` à un `Result` générique. * Le fait qu'une étape de composition puisse échouer est déjà exprimé par le type de retour de la fonction que l'appelant fournit : une fonction retournant un `Outcome` peut échouer ; une fonction retournant une valeur ne le peut pas. * C# interdit la surcharge sur le seul type de retour, et sa résolution de surcharge sélectionne le type de paramètre le plus spécifique lorsque plusieurs candidats s'appliquent. La distinction map/bind est donc imposée par le système de types à chaque site d'appel, indépendamment du nom de la méthode. -* La librairie est en pré-version : non publiée sur NuGet, sans consommateurs externes, de sorte qu'un renommage cassant n'entraîne aujourd'hui aucun coût de migration en aval. +* La bibliothèque est en pré-version : non publiée sur NuGet, sans consommateurs externes, de sorte qu'un renommage cassant n'entraîne aujourd'hui aucun coût de migration en aval. ## Décision @@ -32,7 +32,7 @@ L'opération de transformation de valeur sur `Outcome` est exposée sous forme d Envisagée parce qu'elle reflète la nomenclature établie `map`/`bind` familière aux praticiens de la programmation fonctionnelle et garde chaque opération sans ambiguïté par elle-même. -Rejetée parce qu'elle expose précisément la mécanique que la librairie masque délibérément, et la distinction échec/sans-échec qu'elle encode est déjà portée par le type de retour de la fonction fournie — le second nom ajoute du vocabulaire sans ajouter d'information. +Rejetée parce qu'elle expose précisément la mécanique que la bibliothèque masque délibérément, et la distinction échec/sans-échec qu'elle encode est déjà portée par le type de retour de la fonction fournie — le second nom ajoute du vocabulaire sans ajouter d'information. ### Conserver `To` comme alias déprécié de `Then` diff --git a/doc/handwritten/for-maintainers/adr/0005-reserve-the-plain-factory-name-for-the-outcome-returning-variant.fr.md b/doc/handwritten/for-maintainers/adr/0005-reserve-the-plain-factory-name-for-the-outcome-returning-variant.fr.md index 4e6e2a2..ec82196 100644 --- a/doc/handwritten/for-maintainers/adr/0005-reserve-the-plain-factory-name-for-the-outcome-returning-variant.fr.md +++ b/doc/handwritten/for-maintainers/adr/0005-reserve-the-plain-factory-name-for-the-outcome-returning-variant.fr.md @@ -14,7 +14,7 @@ une erreur est une valeur qu'un appelant retourne et inspecte La levée d'exception reste prise en charge comme commodité optionnelle — le README la présente comme un choix (« vous choisissez comment elle circule ») — mais retourner un `Outcome` est le chemin par défaut et de première classe de la -librairie. +bibliothèque. Les opérations de type fabrique se présentent donc couramment en deux variantes sur les mêmes entrées : l'une retourne `Outcome`, l'autre lève une exception en @@ -30,7 +30,7 @@ fournit sa valeur via un paramètre `out`, et se lit en ligne dans un `if` présument tous cette forme exacte à la vue d'un préfixe `Try`. Deux faits en découlent : -* **Forme.** Un `TryXxx` dans cette librairie retourne `Outcome` — il +* **Forme.** Un `TryXxx` dans cette bibliothèque retourne `Outcome` — il emprunte le nom sans la forme `bool`+`out`, si bien qu'il ne peut pas être utilisé en ligne dans un `if` et rompt discrètement l'attente que le nom établit. @@ -38,10 +38,10 @@ découlent : nom *marqué* est celui qui est sûr (`TryParse`). Le code actuel suit cette disposition (`FromKelvin` lève, `TryFromKelvin` est sûr) — plaçant le nom simple, non marqué, sur le chemin qui lève, c'est-à-dire le chemin que cette - librairie traite comme l'exception plutôt que comme le défaut. + bibliothèque traite comme l'exception plutôt que comme le défaut. `Outcome` expose déjà `GetResultOrThrow()`, si bien que « lever en cas -d'échec » dispose d'un vocabulaire établi dans la librairie. +d'échec » dispose d'un vocabulaire établi dans la bibliothèque. ## Décision @@ -51,15 +51,15 @@ retournant `Outcome`) ; la variante qui lève est marquée d'un suffixe `OrTh ## Justification -* **Un seul principe, appliqué au défaut de cette librairie.** La convention qui +* **Un seul principe, appliqué au défaut de cette bibliothèque.** La convention qui mérite d'être conservée de la BCL n'est pas le mot `Try` mais la règle sous-jacente : *la variante qui s'écarte du défaut dominant porte le marqueur.* Le défaut de la BCL est de lever, donc sa variante sûre est marquée (`Try`) ; le - défaut de cette librairie est de retourner un `Outcome`, donc sa variante qui + défaut de cette bibliothèque est de retourner un `Outcome`, donc sa variante qui lève est l'écart et prend le marqueur (`OrThrow`). Même règle, référence opposée — ce qui est précisément la raison pour laquelle réutiliser `Try` ici serait incohérent, et non un simple conflit de noms. -* **L'appel risqué est celui qui est visible.** Toute la thèse de la librairie +* **L'appel risqué est celui qui est visible.** Toute la thèse de la bibliothèque est qu'une exception est le mode d'échec qui se cache au site d'appel. `OrThrow` la remet dans le nom : `FromKelvinOrThrow(k)` annonce qu'elle peut lever ; `FromKelvin(k)` annonce qu'elle retourne une valeur à inspecter. Le nom simple et @@ -82,7 +82,7 @@ C'est la disposition de la BCL : le nom simple lève, le nom marqué retourne un `XxxOrError`, `AttemptXxx`, `XxxSafe` — la disposition partage un même défaut : elle laisse l'appel qui *lève* non marqué, de sorte que le seul appel qui peut échouer de manière invisible porte le nom le plus court et le plus semblable à un -défaut. Pour une librairie dont la raison d'être est de rendre explicite le +défaut. Pour une bibliothèque dont la raison d'être est de rendre explicite le chemin d'échec, c'est à l'envers. `Try` entre de plus en conflit avec la forme de la BCL ; les autres évitent le conflit mais pas le défaut sous-jacent. @@ -100,7 +100,7 @@ introduire un synonyme fragmenterait le vocabulaire. d'appel : un `FromKelvin` simple retourne un `Outcome` à inspecter, tandis que `FromKelvinOrThrow` annonce d'emblée qu'il peut lever. * Le nom simple, celui vers lequel on se tourne le plus, appartient désormais au - chemin de première classe de la librairie, si bien que la variante vers + chemin de première classe de la bibliothèque, si bien que la variante vers laquelle un appelant se dirige par défaut est celle qui est inspectable. * Aucune méthode n'annonce la forme `Try`/`out` de la BCL qu'elle n'implémente pas. @@ -115,7 +115,7 @@ introduire un synonyme fragmenterait le vocabulaire. * Les exemples de documentation qui utilisent le préfixe `Try` (`GettingStarted`, `UsagePatterns`, en EN comme en FR) doivent être reformulés avec les nouveaux noms. -* Il s'agit d'un renommage cassant de membres publics. La librairie est en +* Il s'agit d'un renommage cassant de membres publics. La bibliothèque est en pré-version et non publiée sur NuGet, sans consommateurs externes, si bien qu'elle n'entraîne aujourd'hui aucun coût de migration en aval. diff --git a/doc/handwritten/for-maintainers/adr/0006-supply-arbitrary-test-values-from-a-seedable-source.fr.md b/doc/handwritten/for-maintainers/adr/0006-supply-arbitrary-test-values-from-a-seedable-source.fr.md index f8adfc7..234c44f 100644 --- a/doc/handwritten/for-maintainers/adr/0006-supply-arbitrary-test-values-from-a-seedable-source.fr.md +++ b/doc/handwritten/for-maintainers/adr/0006-supply-arbitrary-test-values-from-a-seedable-source.fr.md @@ -30,7 +30,7 @@ manière dont ces entrées sont fournies aujourd'hui. à cette unique constante). Une valeur qui varie d'une exécution à l'autre fait apparaître un tel couplage. -Les librairies établies résolvent cela avec un dispositif de « valeur +Les bibliothèques établies résolvent cela avec un dispositif de « valeur anonyme / arbitraire » (AutoFixture, Bogus, l'utilitaire `Any` de GOOS, les générateurs FsCheck). En adopter un ici doit satisfaire deux contraintes strictes de ce package : @@ -120,7 +120,7 @@ avant. ### Ajouter des fabriques de test aux value objects eux-mêmes Placer « fabrique-en une arbitraire » sur `ErrorCode`, `Error`, et consorts dans -la librairie cœur. Rejeté parce que cela mêle une préoccupation de test à des +la bibliothèque cœur. Rejeté parce que cela mêle une préoccupation de test à des types de production et livrerait une surface réservée aux tests dans le package principal. diff --git a/doc/handwritten/for-maintainers/adr/0007-name-the-binder-terminals-new-and-create.fr.md b/doc/handwritten/for-maintainers/adr/0007-name-the-binder-terminals-new-and-create.fr.md index 715db6d..6ed75d0 100644 --- a/doc/handwritten/for-maintainers/adr/0007-name-the-binder-terminals-new-and-create.fr.md +++ b/doc/handwritten/for-maintainers/adr/0007-name-the-binder-terminals-new-and-create.fr.md @@ -28,14 +28,14 @@ consommateur dont la fabrique retourne `Outcome` à imbriquer le résultat sous la forme `Outcome>`, et ne laisse aucun endroit où exécuter une règle inter-champs. -* La librairie réserve déjà le nom de fabrique simple à la variante retournant un +* La bibliothèque réserve déjà le nom de fabrique simple à la variante retournant un `Outcome` (ADR-0005) ; dans tout le code, les fabriques `Parse` / `Create` des value objects retournent `Outcome`, si bien que « une fabrique au nom simple restitue un `Outcome` » constitue un vocabulaire établi. * L'assembleur reçoit un `BindingScope`, un `readonly ref struct` ; un ref struct ne peut pas être un argument de type générique, si bien qu'un assembleur ne peut pas être un `Func<>` et doit être un type delegate nommé. -* La librairie est en pré-version, non publiée sur NuGet et sans consommateurs +* La bibliothèque est en pré-version, non publiée sur NuGet et sans consommateurs externes, si bien qu'un choix de nommage sur cette nouvelle API n'entraîne aucun coût de migration en aval. diff --git a/doc/handwritten/for-maintainers/adr/0008-bind-nullable-value-type-properties-through-a-struct-constrained-overload.fr.md b/doc/handwritten/for-maintainers/adr/0008-bind-nullable-value-type-properties-through-a-struct-constrained-overload.fr.md new file mode 100644 index 0000000..e65b7ce --- /dev/null +++ b/doc/handwritten/for-maintainers/adr/0008-bind-nullable-value-type-properties-through-a-struct-constrained-overload.fr.md @@ -0,0 +1,140 @@ +# ADR-0008 | Lier les propriétés de type valeur nullable via une surcharge contrainte à struct + +🌍 🇬🇧 [English](0008-bind-nullable-value-type-properties-through-a-struct-constrained-overload.md) · 🇫🇷 Français (ce fichier) + +**Statut :** Proposé +**Date :** 2026-07-16 +**Décideurs :** Reefact + +## Contexte + +* Le request binder sélectionne chaque propriété du DTO avec `SimpleProperty(r => r.X)` ou + `ListOfSimpleProperties(r => r.X)`, puis un convertisseur la lie — une fabrique de value + object `Func>`, typiquement un groupe de méthodes tel que + `EmailAddress.Parse`. +* Le sélecteur d'origine est générique sur `TArgument`, avec un paramètre + `Expression>` non contraint. +* Lorsque la propriété du DTO est un type valeur nullable (`int?`), le sélecteur non + contraint infère `TArgument = Nullable`, car pour un paramètre de type non contraint + le `?` est une annotation sans effet sur les types valeur. L'étape de conversion attend + alors `Func, Outcome>`, si bien qu'un groupe de méthodes sur le type + sous-jacent (`int -> Outcome`) ne correspond pas et l'appel échoue à compiler avec + **CS0411**. +* Une propriété de type valeur non nullable est déjà rejetée au moment du binding (constat + de revue n°4, livré dans #141) : une telle propriété doit être déclarée nullable pour + qu'un argument manquant se distingue d'une valeur par défaut légitimement envoyée. +* C# ne traite pas deux méthodes qui ne diffèrent que par une contrainte `class` versus + `struct` comme des signatures distinctes — une contrainte ne fait pas partie de la + signature (CS0111). +* Sous une contrainte `where TArgument : struct`, `TArgument?` désigne le type construit + `Nullable`. C'est un type de paramètre différent du `TArgument` nu du sélecteur + non contraint et — étant un type construit — il est structurellement plus spécifique pour + la résolution de surcharge. +* Un élément de liste `Nullable` peut indépendamment être `null` ; un + convertisseur sur le type sous-jacent non nullable ne peut pas représenter cet élément, et + le convertisseur de liste de référence déréférence les éléments sans déballer un + `Nullable`. +* La bibliothèque est en pré-version, non publiée sur NuGet et sans consommateurs externes. Des + surcharges additives figées avant le gel de la v1 ne peuvent pas déplacer l'inférence sur + des sites d'appel de consommateurs qui n'existent pas encore ; les mêmes surcharges + ajoutées après que des consommateurs ont écrit des bindings de type valeur pourraient + changer la résolution sur ces sites. + +## Décision + +Le request binder lie une propriété de DTO de type valeur nullable au travers d'une surcharge +de sélecteur dédiée `where TArgument : struct` dont le sélecteur porte `Nullable` +et dont le convertisseur opère sur le type sous-jacent non nullable. + +## Justification + +* Le paramètre `Nullable` de la surcharge contrainte à `struct` est un type + véritablement différent — et structurellement plus spécifique — que le `TArgument` nu de la + surcharge non contrainte, si bien que les deux coexistent sans CS0111 et que la surcharge + de type valeur l'emporte pour une propriété de type valeur nullable, tandis que les + propriétés de référence et de type `string` continuent de se résoudre vers la surcharge non + contrainte. L'échec CS0411 est supprimé au niveau du sélecteur plutôt que reporté sur le + consommateur sous la forme d'une lambda d'adaptation. +* Faire remonter le type sous-jacent non nullable (`int`, et non `int?`) permet à une fabrique + de value object de lier sous forme de groupe de méthodes exactement comme elle le fait pour + une propriété de référence, gardant une même ergonomie fluide pour les deux sortes de + propriétés : la propriété reste déclarée nullable pour que l'absence demeure observable, et + le type sous-jacent est ce sur quoi le convertisseur opère concrètement. +* Un convertisseur de liste dédié est requis plutôt qu'une réutilisation, parce qu'un élément + `Nullable` a besoin de sa propre gestion du `null` — un élément `null` est un argument + manquant enregistré sous son chemin indexé — et d'un déballage avant conversion, ni l'un ni + l'autre n'étant réalisés par le convertisseur de référence. +* Décider avant le gel de la v1 fige la forme de l'API tant qu'elle est encore libre : les + surcharges sont additives maintenant, sans aucun site d'appel à perturber, alors que les + différer au-delà du gel ferait de la même addition un changement cassant à la source pour + les bindings de type valeur des consommateurs. + +## Alternatives envisagées + +### Exiger des consommateurs qu'ils passent une lambda d'adaptation + +Envisagée parce qu'elle ne nécessite aucune nouvelle API : `AsRequired(v => PositiveInt.From(v))` +lie là où le groupe de méthodes nu ne le fait pas. + +Rejetée parce qu'elle dégrade silencieusement l'ergonomie du groupe de méthodes pour le cas +courant du type valeur nullable, fait surgir une erreur de compilation CS0411 sans cause +évidente, et duplique à chaque site d'appel le déballage que le binder peut effectuer une +seule fois. + +### Un sélecteur unique unifiant types référence et types valeur + +Envisagée parce qu'une seule surcharge est la surface la plus réduite. + +Rejetée parce que C# ne peut pas l'exprimer : un `TArgument?` non contraint ne peut pas à la +fois inférer le type sous-jacent pour une propriété de type valeur et rester une référence +pour une propriété de type référence, et une contrainte ne peut pas varier au sein d'une même +méthode. + +### Réutiliser le convertisseur de liste de référence pour les listes de types valeur + +Envisagée parce que la logique de binding est par ailleurs identique. + +Rejetée parce qu'un élément `Nullable` et un élément de référence nécessitent une gestion +du `null` différente, et que le chemin de type valeur doit déballer chaque élément présent +avant conversion ; fondre les deux dans un seul convertisseur masquerait qu'un élément `null` +est un argument manquant enregistré. + +## Conséquences + +### Positives + +* Une propriété de DTO de type valeur nullable (`int?`, `bool?`, ...) et les listes de + celles-ci se lient via un groupe de méthodes sur le type sous-jacent, avec la même ergonomie + que les propriétés de référence. +* Le correctif est additif et figé avant le gel de la v1, si bien qu'il ne devient jamais un + changement cassant à la source pour le binding de type valeur existant d'un consommateur. +* Les propriétés de référence et de type `string` ne sont pas affectées : elles continuent de + se résoudre vers la surcharge d'origine. + +### Négatives + +* Deux surcharges de sélecteur publiques supplémentaires et un type de convertisseur public de + plus à documenter et à maintenir, doublant la surface de sélecteur de `SimpleProperty` et + `ListOfSimpleProperties`. +* Le mécanisme — une contrainte `struct` transformant `TArgument?` en un `Nullable` + plus spécifique — est subtil ; un mainteneur peu familier de la règle de résolution de + surcharge peut ne pas voir immédiatement pourquoi les deux sélecteurs coexistent. + +### Risques + +* Une future troisième forme de sélecteur pourrait interagir avec les deux surcharges de + manière à réintroduire de l'ambiguïté ; atténué par cette ADR et par des tests de régression + qui épinglent la résolution des types référence et `string` sur la surcharge d'origine. + +### Actions de suivi + +* Garder le guide RequestBinder (EN et français) synchronisé avec l'ergonomie de binding de + type valeur. + +## Références + +* ADR-0007 — nommer les terminaux du binder New et Create, la décision d'API publique sœur sur + le même binder. +* Pull requests #126 et #141 — la fonctionnalité de request binder et le garde-fou de type + valeur non nullable que cette décision complète. +* Issue #144 — le constat CS0411 que cette décision résout. diff --git a/doc/handwritten/for-maintainers/adr/0008-bind-nullable-value-type-properties-through-a-struct-constrained-overload.md b/doc/handwritten/for-maintainers/adr/0008-bind-nullable-value-type-properties-through-a-struct-constrained-overload.md index 822df14..c911ce5 100644 --- a/doc/handwritten/for-maintainers/adr/0008-bind-nullable-value-type-properties-through-a-struct-constrained-overload.md +++ b/doc/handwritten/for-maintainers/adr/0008-bind-nullable-value-type-properties-through-a-struct-constrained-overload.md @@ -1,5 +1,7 @@ # ADR-0008 | Bind nullable value-type properties through a struct-constrained overload +🌍 🇬🇧 English (this file) · 🇫🇷 [Français](0008-bind-nullable-value-type-properties-through-a-struct-constrained-overload.fr.md) + **Status:** Proposed **Date:** 2026-07-16 **Decision Makers:** Reefact diff --git a/doc/handwritten/for-maintainers/workflows/README.fr.md b/doc/handwritten/for-maintainers/workflows/README.fr.md index b98192e..c52073f 100644 --- a/doc/handwritten/for-maintainers/workflows/README.fr.md +++ b/doc/handwritten/for-maintainers/workflows/README.fr.md @@ -6,7 +6,7 @@ > Documentation mainteneur. Elle décrit les workflows GitHub Actions qui > construisent, vérifient et publient FirstClassErrors. Elle ne fait **pas** -> partie de la documentation utilisateur de la librairie, sous `doc/`. +> partie de la documentation utilisateur de la bibliothèque, sous `doc/`. ## De quoi il s'agit diff --git a/doc/handwritten/for-maintainers/workflows/analyzers.fr.md b/doc/handwritten/for-maintainers/workflows/analyzers.fr.md index e31138e..571e814 100644 --- a/doc/handwritten/for-maintainers/workflows/analyzers.fr.md +++ b/doc/handwritten/for-maintainers/workflows/analyzers.fr.md @@ -9,7 +9,7 @@ ## À quoi il sert -La librairie livre des analyzers Roslyn (`FCExxx`) **embarqués dans le package +La bibliothèque livre des analyzers Roslyn (`FCExxx`) **embarqués dans le package NuGet**. Ce workflow prouve deux choses que la CI ordinaire ne prouve pas : 1. **Dogfood** — les analyzers tournent réellement et ne signalent rien @@ -59,7 +59,7 @@ l'éditer, lisez les commentaires du YAML — ils font foi. Les pièges : - **Le pack tourne sous le SDK de release, la consommation sous le SDK floor.** Packer sous le SDK floor testerait un analyzer que personne ne livre et - épinglerait la librairie à C# 12. Ce découpage est tout l'intérêt. + épinglerait la bibliothèque à C# 12. Ce découpage est tout l'intérêt. - **`FLOORCHECK_VERSION` porte un suffixe `run_number.run_attempt`** pour que chaque run produise une version que NuGet n'a jamais mise en cache, forçant l'étape de consommation à restaurer le `.nupkg` fraîchement packé *de ce run*