---
{
  "title": "FHIRPath expliqué : analyse de types statique et exemples",
  "description": "FHIRPath est le langage de chemin pour FHIR. Comment l'analyse de types statique détecte les erreurs à la compilation, avec des exemples de gestion de value[x] et de Reference.",
  "date": "2025-06-10",
  "author": "Olim Saidov",
  "reading-time": "15 minutes",
  "tags": [
    "Forms",
    "System Design"
  ]
}
---
FHIRPath est un puissant langage permettant de naviguer dans les ressources FHIR et d'en extraire des données, mais sa flexibilité peut mener à des expressions complexes sujettes à des erreurs d'exécution. Cet article explore l'architecture et les avantages de la mise en œuvre d'un système d'analyse de types statique pour FHIRPath. En s'intégrant étroitement aux [schémas FHIR](/articles/type-schema-a-pragmatic-approach-to-build-fhir-sdk) et en employant une représentation interne sophistiquée des types, un tel système peut activer de riches fonctionnalités d'éditeur bas-code — comme la [validation](/blog/recursive-validation-in-fhir-profiles) en temps réel, les suggestions intelligentes et une interface pilotée par les types — améliorant considérablement l'expérience développeur et la fiabilité des expressions.

## Qu'est-ce que FHIRPath?

La norme Fast Healthcare Interoperability Resources (FHIR) s'appuie massivement sur FHIRPath pour les requêtes, l'extraction de données et la définition d'invariants. À mesure que l'adoption de FHIR progresse, le besoin d'outils efficaces pour aider les développeurs et les informaticiens à rédiger des expressions FHIRPath correctes et performantes augmente également. Sans assistance, écrire du FHIRPath complexe est un processus propice aux erreurs, qui ne se manifestent souvent qu'à l'exécution.

Cet article s'adresse aux développeurs de logiciels, aux implémenteurs FHIR et aux concepteurs d'outils en informatique de la santé qui souhaitent comprendre comment les techniques d'analyse statique peuvent être appliquées à FHIRPath. Nous examinerons en profondeur les composantes fondamentales d'un système de types adapté à FHIRPath, en montrant comment il analyse les expressions, infère les types et exploite les définitions structurelles de FHIR pour fournir une base aux fonctionnalités avancées d'éditeur. Les lecteurs acquerront une compréhension de l'approche architecturale permettant de créer des éditeurs FHIRPath plus intelligents et plus fiables.

## FHIRPath et l'analyse de types statique : les bases

Avant d'entrer dans les détails, il est important de comprendre quelques concepts clés :
- **FHIRPath :** Un langage de navigation et d'extraction basé sur les chemins, conçu spécifiquement pour les données FHIR. Il permet la sélection d'éléments de données, le filtrage de collections et l'exécution de diverses opérations sur ceux-ci. Sa syntaxe rappelle XPath, mais est soigneusement adaptée au modèle de données unique de FHIR. Pour plus de détails, consultez la [spécification officielle de FHIRPath](http://hl7.org/fhirpath/N1/).
- **Analyse statique :** Le processus d'analyse du code informatique sans l'exécuter réellement. En programmation, l'analyse statique comprend généralement la vérification des types, le linting pour les problèmes de style et l'identification des bogues potentiels ou des antipatrons avant l'exécution.
- **Système de types :** Une composante fondamentale de l'analyse statique, un système de types est constitué de règles qui attribuent une propriété appelée « type » à diverses constructions d'un programme, comme les variables, les expressions, les fonctions ou les modules. Son rôle principal est d'aider à s'assurer que les opérations sont effectuées sur des types de données compatibles, prévenant ainsi de nombreuses erreurs courantes.

## Architecture d'un système de types statique pour FHIRPath

La construction d'un moteur d'analyse statique robuste pour FHIRPath implique plusieurs composantes architecturales clés, comme illustré dans l'architecture de haut niveau (voir Diagramme 1).

#### 3.1. Représentation des types : le langage interne du système

La pierre angulaire du système est une représentation interne riche de tous les types possibles qu'une expression ou ses sous-parties peuvent produire. On y parvient généralement à l'aide d'une structure d'union discriminée, permettant au système de modéliser un éventail diversifié de concepts de types :

Le système doit être capable de représenter les **types système de FHIRPath**, qui sont les types de données de base inhérents au langage, notamment Integer, Decimal, String, Boolean, Date, DateTime, Time et Quantity. Ceux-ci correspondent aux valeurs littérales utilisables directement dans les expressions (p. ex., `10`, `'hello'`, `@2023-10-26`).

Au-delà de ceux-ci, les **types sémantiques spéciaux** sont essentiels. Un type `Null` ou `Empty` est nécessaire pour représenter les collections vides ou l'absence de valeur. Un type `Invalid` signale explicitement les erreurs de type découvertes lors de l'analyse, portant souvent un message d'erreur descriptif pour guider l'utilisateur. De plus, un type `Type` représente un type lui-même en tant que valeur, utilisé dans des opérations comme `is` ou `as`.

Une décision de conception clé dans la modélisation des types FHIRPath concerne la **connaissance des collections et de la cardinalité**. La spécification FHIRPath stipule : « L'évaluation d'une expression de chemin produit toujours une collection d'éléments, même si cette collection est vide ou ne contient qu'un seul élément. » Cette philosophie du « tout est une collection » signifie que le simple fait d'avoir un type `Collection` pour envelopper des types singuliers comme `Integer` dans `Collection` rendrait presque chaque type un type collection — diminuant l'utilité d'un tel enveloppeur pour exprimer des contraintes de cardinalité spécifiques.

On adopte plutôt une approche plus nuancée, où les types de base (comme `Integer`, `String` ou un type FHIR `Patient`) représentent intrinsèquement une *collection potentielle* de zéro à plusieurs éléments de ce type. Pour désigner explicitement une collection garantie de contenir au plus un élément, un type enveloppeur spécial, appelons-le `Single`, est introduit. Ce type `Single` devient crucial pour l'analyse statique car, comme le souligne la spécification FHIRPath, de nombreuses fonctions et opérateurs ont des attentes spécifiques quant à la cardinalité de leurs entrées ou arguments. Par exemple :
- Les opérateurs arithmétiques (p. ex., `+`, `-`) requièrent généralement que leurs opérandes soient des collections à un seul élément.
- Les fonctions comme `first()`, `last()` ou un indexeur `[...]` sont garanties de retourner une collection à un seul élément (ou une collection vide).
- D'autres fonctions peuvent exiger qu'un argument soit une valeur unique.

En utilisant `Single`, le système de types peut explicitement suivre et appliquer ces contraintes d'« élément unique ». Si un opérateur attend un `Single` mais reçoit un `Integer` (implicitement une collection de potentiellement plusieurs entiers), l'analyseur statique peut signaler cela comme une « utilisation non sécurisée », selon la terminologie de la spécification. Ce choix de conception répond directement à la préoccupation de la spécification concernant les fonctions ou opérateurs « appelés sur une sortie qui n'est pas garantie de ne contenir qu'un seul élément ». L'absence de `Single` implique une collection générale, tandis que sa présence fournit une garantie de cardinalité plus forte, cruciale pour prévenir les erreurs d'exécution.

Pour gérer la flexibilité de FHIRPath et des données FHIR, le **polymorphisme et les génériques** sont indispensables :
- Les **types choix** représentent des éléments qui peuvent être d'un parmi plusieurs types, comme `deceased[x]` de FHIR, qui peut être un booléen ou une date-heure. Le système inclut souvent des mécanismes pour normaliser ces types choix afin de simplifier les comparaisons et le traitement.
- Les **types génériques**, représentés comme des types de substitution (p. ex., `T`, `R`), sont essentiels pour typer avec précision les fonctions génériques. Un exemple classique est `someCollection.select(projection: T -> R): Collection`, où T est le type d'élément de `someCollection`. (Remarque : le résultat est une collection générale, pas nécessairement `Single`).

Pour les fonctions comme `where()` ou `select()` qui prennent des expressions comme arguments, les **types d'ordre supérieur (lambdas)** sont nécessaires. Un type `Lambda` capture le type de retour attendu de l'expression lambda et, surtout, le type de l'élément de contexte (`%this`) disponible à l'intérieur de ce lambda. Cet élément de contexte à l'intérieur d'un lambda opérant sur `Collection` serait généralement typé comme `Single`.

Enfin, le système doit comprendre en profondeur les **types structurels spécifiques à FHIR**, et ici une distinction clé apparaît :
- Les **types FHIR primitifs** (p. ex., FHIR `string`, `boolean`, `code`, `id`, `uri`, `decimal`) sont plus que de simples valeurs. Bien qu'un élément FHIR `string` possède certainement une *valeur* de type chaîne, l'élément lui-même au sein d'une ressource FHIR peut également posséder un attribut `id` et des éléments `extension`. Cette dualité signifie qu'un type FHIR primitif dans FHIRPath peut se comporter de deux façons :

- **En tant que valeur :** Il peut être utilisé directement dans des opérations attendant son type de valeur sous-jacent (p. ex., un FHIR `decimal` dans une opération arithmétique).
- **En tant qu'objet traversable :** On peut naviguer depuis lui pour accéder à son id ou à ses `extensions` (p. ex., `Patient.birthDate.extension.where(url='...')`). Pour modéliser cela, le système de types interne définit souvent ces types FHIR primitifs (p. ex., `PrimitiveStringType`, `PrimitiveDecimalType`) et établit une hiérarchie de types où ils sont des sous-types des types système FHIRPath correspondants (p. ex., `PrimitiveStringType` est un sous-type de `StringType`). Cela permet à une instance de `PrimitiveStringType` d'être utilisée de façon transparente là où un `StringType` est attendu (p. ex., comme opérande pour la concaténation de chaînes). Simultanément, parce qu'il s'agit d'un type complexe distinct (bien que lié) au sens du schéma FHIR, le système de types peut également savoir qu'il possède potentiellement des champs comme id et `extension`, permettant le chaînage par point pour ces constructions primitives FHIR spécifiques.

- Les **types FHIR complexes** offrent un moyen de représenter des ressources FHIR entières (p. ex., `Patient`) ou des types de données complexes non primitifs (comme `HumanName`). Ces types représentent également implicitement des collections de ces structures, sauf s'ils sont enveloppés dans `Single`. Par exemple, `Patient.name` se résoudrait en un type représentant une collection de `HumanName`, pas `Single`, car un Patient peut avoir plusieurs noms.

Ce système de types complet, avec son choix délibéré de `Single` pour les garanties de cardinalité et une hiérarchie de types pour gérer la nature duale des primitives FHIR, est soutenu par un ensemble de fonctions utilitaires. Ces fonctions facilitent la construction de types, la comparaison robuste (y compris l'égalité profonde pour les types imbriqués complexes), la vérification des sous-types et, de façon critique, la correspondance de motifs. La correspondance de motifs permet au système de vérifier si un type calculé réel est conforme à un motif de type attendu (qui peut inclure `Single`, des types FHIR primitifs ou des génériques) et, en cas de succès, d'inférer les types concrets pour ces paramètres génériques.

Cet ajout enrichit considérablement l'explication du système de types en abordant directement la manière dont il gère les nuances spécifiques au modèle de données de FHIR, en particulier pour les primitives. Il renforce également l'utilité de la hiérarchie de types.

#### 3.2. Exploitation des schémas FHIR : ancrer les types dans la structure des données

L'analyse statique de FHIRPath est considérablement renforcée par une connaissance directe et intime des modèles de données FHIR eux-mêmes. Cette intégration permet au système de types de refléter fidèlement la structure et les contraintes des données navigées.

Ce processus commence par l'**ingestion des schémas**. Le système consomme les [StructureDefinitions FHIR](/articles/upload-structuredefinition), qui sont les spécifications formelles de chaque ressource FHIR, type de données (y compris les primitives comme `string`, `boolean`, `decimal`, etc., qui ont elles-mêmes une structure dans FHIR) et élément. Ces définitions sont généralement organisées dans un **registre de schémas FHIR** interne rapidement accessible. Ce registre sert de source de vérité du système pour toutes les informations structurelles FHIR.

Ce registre en place, des mécanismes sophistiqués de **résolution de chemins** sont mis en œuvre. À partir d'un type de départ (p. ex., représentant une ressource Patient) et d'un nom de champ (p. ex., `telecom` ou `birthDate`), le système peut naviguer dans ce registre pour résoudre la définition précise de cet élément dans la structure `Patient`. Ce processus de résolution doit être capable de traverser les hiérarchies d'héritage de FHIR, en trouvant correctement les champs qui pourraient être définis dans des types de ressources de base ou des types de données communs.

La capacité fondamentale qui exploite ces connaissances de schéma est l'**inférence du type des champs**. Cette fonction cruciale détermine les types de tous les champs accessibles depuis un type FHIR complexe donné (comme un `Patient` ou un `HumanName`, ou même un type FHIR primitif comme `FHIR.string` qui peut avoir des extensions). Pour chaque élément défini dans une ressource ou un type de données dans le schéma, sa définition FHIR est traduite dans la représentation interne de types établie à la section 3.1. Cette traduction est un processus nuancé :
- **Correspondance des primitives FHIR :** Les types FHIR primitifs (p. ex., le type FHIR `string`, le type FHIR `date`) sont mis en correspondance avec leurs représentations internes `PrimitiveFHIRType` correspondantes (p. ex., `PrimitiveStringType`, `PrimitiveDateType`). Comme mentionné, ces types internes sont conçus pour être des sous-types des types système FHIRPath plus larges, leur permettant d'être utilisés comme valeurs simples tout en étant reconnus comme des objets pouvant avoir un champ `id` ou `extension`.
- **Gestion des choix FHIR :** Les éléments représentant un choix de types dans FHIR (p. ex., `value[x]` ou `deceased[x]`) sont traduits dans le `ChoiceType` interne, listant avec précision toutes les représentations internes de types possibles pour ce choix.
- **Respect de la cardinalité et de la convention `Single` :** La cardinalité définie dans le schéma FHIR (`0..1`, `0..*`, `1..1`, `1..*`) influence directement le type interne résultant :

- Les éléments avec une cardinalité maximale de 1 (p. ex., `0..1`, `1..1` comme `Patient.birthDate`) sont typés comme `Single`, où T est le type de l'élément. Cela signale explicitement que le chemin produira au plus un élément.
- Les éléments avec une cardinalité maximale supérieure à 1 (p. ex., `0..*`, `1..*` comme `Patient.identifier` ou `Patient.name`) sont typés comme `T` (p. ex., `PrimitiveIdentifierType` ou `HumanNameType`), qui, par convention (comme discuté en 3.1), représente une collection de potentiellement zéro à plusieurs éléments de type `T`.

- **Référencement d'autres types complexes :** Pour les éléments qui sont eux-mêmes des types de données FHIR complexes (p. ex., une `Address` dans un `Patient`), la traduction crée une référence à la représentation interne du type de ce type complexe (p. ex., `AddressType`).
- **Nature récursive :** Ce processus de traduction est par nature récursif. Lors de la traduction d'un `Patient`, des éléments comme name (qui est de type `HumanName`) déclencheront une traduction similaire pour le type `HumanName` s'il n'a pas encore été traité.

Pour optimiser les performances, notamment lors du traitement de schémas FHIR volumineux et interconnectés, des stratégies de mise en cache sont souvent employées. Une fois qu'un élément de schéma FHIR ou un type de données a été traduit dans sa représentation interne de types, ce résultat peut être mis en cache pour éviter un traitement redondant si le même composant de schéma est rencontré à nouveau.

En traduisant méticuleusement les StructureDefinitions FHIR dans son langage de types interne, en respectant les fonctionnalités spécifiques de FHIR comme les choix et la nature nuancée de ses types primitifs, et en appliquant la convention `Single` basée sur la cardinalité du schéma, le moteur d'analyse statique acquiert une compréhension précise et exploitable des données sur lesquelles il raisonne. Cela constitue le socle des recherches de champs précises, de la vérification des types et des suggestions intelligentes.

#### 3.3. Le moteur d'inférence : déduire les types des expressions

Le moteur d'inférence est le cœur analytique du système, chargé de déterminer le type de toute expression FHIRPath donnée. Il opère généralement sur une représentation tokenisée ou un arbre de syntaxe abstrait (ASA) de l'expression. Un flux conceptuel pour ce processus est présenté dans le Diagramme 2. Son objectif est de déduire le type le plus spécifique possible pour l'ensemble de l'expression et ses sous-parties, en exploitant les représentations internes de types et les connaissances des schémas.

Une première étape cruciale est la gestion de la **priorité des opérateurs**. Pour évaluer correctement des expressions comme `a + b * c`, une simple liste plate de jetons est insuffisante. Les jetons sont généralement analysés dans un arbre de priorité des opérateurs ou traités dans un ordre qui respecte ces règles, assurant que les opérations sont regroupées et évaluées correctement.

Le moteur emploie ensuite la **traversée récursive** pour déterminer le type d'une expression. Ce processus commence avec le type du contexte initial (p. ex., `%resource`) et propage les informations de type à travers l'expression :
- **Littéraux :** Lorsqu'une valeur littérale est rencontrée (p. ex., `10`, `'hello'`, `@2023-10-26T12:00:00Z`, `true`), elle reçoit généralement un type `Single` correspondant à son type système FHIRPath (p. ex., 10 devient `Single`, `'hello'` devient `Single`). Cela reflète le fait qu'un littéral, lorsqu'évalué, produit une collection contenant exactement un élément.
- **Variables (`%variableName`) :** Le type d'une variable est recherché dans une table de symboles (ou environnement) qui stocke les types de toutes les variables locales et externes définies (p. ex., `%resource`, `%context`, ou des liaisons définies par l'utilisateur). Le type récupéré dans cette table respectera déjà les conventions du système (p. ex., `%resource` pourrait être `Single`).
- **Chaînage (notation par point) - `X.Y` :** Il s'agit d'une opération FHIRPath fondamentale. L'inférence de type procède comme suit :

- Le type de la partie précédente `X` est déterminé récursivement. Appelons-le `TypeX`. Par défaut, `TypeX` représente une collection d'éléments.
- Si Y est un **nom de champ** :
Le type d'élément de `TypeX` est extrait (p. ex., si `TypeX` est `PatientType` (implicitement une collection de Patients) ou `Single`, le type d'élément est `PatientType`).
- La composante d'intégration des schémas FHIR (décrite en 3.2) est interrogée pour trouver le type du champ `Y` tel que défini sur ce type d'élément. Cela retournera un type interne (p. ex., `HumanNameType` ou `Single`) qui intègre déjà les informations de cardinalité du schéma (utilisant `Single` si la cardinalité maximale est 1, ou le type de base `T` si elle peut être multiple). Ce devient le nouveau type courant.

- Si Y est un **appel de fonction** (p. ex., `X.functionName(arg1, arg2)`) :
La signature de `functionName` est récupérée dans le livre de règles (décrit en 3.4).
- `TypeX` (la collection d'entrée de la fonction) est comparé au type d'entrée attendu de la fonction (p. ex., si la fonction attend `Collection`, `TypeX` est comparé à celui-ci, inférant potentiellement `A`).
- Les arguments (`arg1`, `arg2`, etc.) sont eux-mêmes vérifiés récursivement quant aux types. Le contexte pour ces arguments, en particulier pour les lambdas, est souvent influencé par la signature de la fonction. Par exemple, dans `X.where(criteriaLambda)`, si `X` est une collection de `TypeItem`, alors `%this` à l'intérieur de `criteriaLambda` sera `Single`.
- Les types réels des arguments sont ensuite comparés aux types d'arguments attendus de la signature de la fonction, affinant davantage les liaisons génériques.
- Enfin, la logique de type de retour déclarée de la fonction (qui peut utiliser dynamiquement les liaisons génériques inférées) est invoquée pour calculer le type résultant de l'appel de fonction.

- Si Y est un **indexeur** (p. ex., `X[indexExpression]`) :
L'indexExpression est vérifiée quant au type (elle est généralement attendue comme `Single`).
- Le résultat d'un indexeur est toujours un élément unique (ou une collection vide si l'index est hors limites). Par conséquent, si `TypeX` représente une collection d'éléments de type `TypeItem`, le résultat de `X[...]` est `Single`.

- **Opérateurs (p. ex., `A + B`, `A = B`, `A and B`) :**

- Les types des opérandes gauche (`TypeA`) et droit (`TypeB`) sont déterminés récursivement.
- La signature de l'opérateur est récupérée dans le livre de règles. La signature spécifiera des attentes, souvent que les opérandes doivent être `Single` (p. ex., les opérateurs arithmétiques).
- `TypeA` et `TypeB` sont comparés à ces types d'opérandes attendus. Si un opérande est, par exemple, IntegerType (implicitement `Collection`) mais que `Single` est requis, le système signale cela comme une « utilisation potentiellement non sécurisée », sauf si le contexte garantit que `TypeA` est bien un élément unique.
- Si la correspondance est réussie (et que les paramètres génériques sont inférés), la logique de type de retour de l'opérateur détermine le résultat. Le résultat de la plupart des opérateurs est également un `Single` (p. ex., `1 + 2 produit Single`).

Tout au long de ce processus, la **propagation du contexte** est essentielle. Le type du contexte initial (p. ex., `%resource`) sert de point de départ. Pour les lambdas, le type de contexte (le type de `%this`) est déterminé dynamiquement par la fonction qui utilise le lambda, en fonction du type de la collection sur laquelle il opère.

La **propagation des erreurs** est également intégrante. Si une incompatibilité de types se produit (p. ex., tentative d'accès à un champ inexistant, passage d'un argument du mauvais type à une fonction, ou utilisation d'un opérateur avec des types d'opérandes ou des cardinalités incompatibles), un type `Invalid` est généré. Ce type `Invalid` est ensuite propagé vers le haut dans l'arbre d'expression, interrompant effectivement toute analyse significative ultérieure pour cette branche particulière et permettant au système de rapporter l'erreur avec précision au point le plus pertinent.

En appliquant récursivement ces règles, le moteur d'inférence peut construire une compréhension complète des types impliqués dans chaque partie d'une expression FHIRPath, permettant les fonctionnalités avancées d'éditeur discutées plus loin.

#### 3.4. Le livre de règles : signatures des fonctions et opérateurs

Une composante critique et déclarative du système de types statique est un référentiel de métadonnées — en fait un « livre de règles » — qui définit le comportement et les attentes de types de toutes les fonctions et opérateurs FHIRPath connus. Ce livre de règles est la référence à partir de laquelle le moteur d'inférence de types valide les opérations et détermine leurs types résultants. Chaque entrée de ce référentiel constitue une signature, spécifiant méticuleusement :
- **Nom :** L'identifiant unique de la fonction (p. ex., `where`, `select`, `first`) ou de l'opérateur (p. ex., `+`, `=`, `and`).
- **Types d'entrée/opérande attendus :**
Pour les **fonctions**, cela définit le type de la collection d'entrée sur laquelle elles opèrent (l'expression à gauche du point). Ce type d'entrée attendu peut, et souvent utilise, des espaces réservés de types génériques (comme `T` dans `collection.first()`).
- Pour les **opérateurs**, cela définit les types attendus des opérandes gauche et droit. De façon cruciale, de nombreux opérateurs, particulièrement les opérateurs arithmétiques et de comparaison, requièrent que leurs opérandes soient des collections à un seul élément. Leurs signatures indiqueront explicitement cette attente à l'aide de l'enveloppeur `Single` (p. ex., `Single + Single`).

- **Définitions des arguments (pour les fonctions) :** Pour chaque argument qu'une fonction accepte, la signature précise :
**Nom :** Un nom descriptif pour l'argument (principalement pour la documentation et potentiellement pour les arguments nommés si pris en charge).
- **Type attendu :** Le type auquel l'argument doit se conformer. Il peut s'agir d'un type concret, d'un type générique lié à l'entrée de la fonction ou à d'autres arguments, ou d'un `LambdaType`. Pour les lambdas, la signature définit également le type de retour attendu du lambda et le type de l'élément de contexte (`%this`) disponible à l'intérieur (p. ex., pour `collection.where(criteria: Lambda>`), le lambda criteria est attendu retourner `Boolean` et son contexte `%this` est `Single`).
- **Caractère optionnel :** Si l'argument est optionnel ou requis.

- **Logique de type de retour :** C'est sans doute l'aspect le plus sophistiqué des signatures. Plutôt que d'être toujours un type statique prédéfini, le type de retour est souvent spécifié comme une **fonction computationnelle** exécutée par le moteur d'inférence de types. Cette fonction reçoit :
Les types réels et résolus de la collection d'entrée (pour les fonctions) ou des opérandes (pour les opérateurs).
- Les types réels et résolus de tous les arguments passés à une fonction.
- Toutes les liaisons de types génériques inférées en comparant les types réels d'entrée/opérande/argument aux motifs de la signature (p. ex., si T a été inféré comme `PatientType`). Sur la base de ces entrées concrètes, cette logique calcule le type de sortie précis de la fonction ou de l'opérateur. Par exemple :
- La fonction `first()`, appliquée à une entrée de type `Collection` (où `T` est le type d'élément), aura une logique de type de retour qui produit `Single`.
- L'opérateur `+`, étant donné des opérandes `Single` et `Single`, produira `Single` grâce aux règles de promotion de types intégrées dans sa logique de type de retour.
- Pour une fonction générique comme `collection.select(projection: Lambda>): Collection`, la logique de type de retour utilise le type inféré `B` (à partir du type de retour du lambda) pour construire le résultat `Collection`.

Cette approche pilotée par les métadonnées rend le système de types hautement extensible. De nouvelles fonctions ou de nouveaux opérateurs peuvent être ajoutés en définissant simplement leurs signatures dans ce livre de règles sans modifier le moteur d'inférence central. La précision de ces signatures, notamment l'utilisation de `Single` pour les contraintes de cardinalité et le calcul dynamique des types de retour basé sur les génériques, est fondamentale pour l'exactitude et l'utilité de l'ensemble du processus d'analyse statique. Elle permet au système de détecter des erreurs subtiles liées aux tailles de collection et de fournir des informations de types très spécifiques pour des opérations complexes et génériques.

#### 3.5. Alimenter des éditeurs intelligents : les bénéfices de l'analyse statique

Un système d'analyse de types statique bien mis en œuvre, tel que décrit, constitue l'épine dorsale de la création d'éditeurs FHIRPath ou d'outils hautement intelligents et conviviaux. Lorsqu'une expression change, un système de gestion d'état réactif (souvent employé dans les cadres d'interface utilisateur modernes) peut déclencher une nouvelle analyse. Ce système stockerait généralement le programme actuel (l'expression principale et toutes les variables ou liaisons nommées définies par l'utilisateur) et suivrait les dépendances entre ces liaisons nommées. Si une liaison est modifiée, son type est réinféré, et par la suite, les types de toutes les liaisons dépendantes sont également réévalués et mis à jour. Cela assure que les informations de types dans l'ensemble du programme restent cohérentes et à jour.

Cette analyse continue en arrière-plan active un ensemble de fonctionnalités puissantes :
- **Validation en temps réel :** Pendant que l'utilisateur tape, les expressions sont continuellement analysées. Les erreurs de types — comme la tentative d'additionner un String à un Integer, ou de référencer un champ qui n'existe pas sur un type de ressource donné — peuvent être soulignées immédiatement dans l'éditeur.
*Exemple :* Si un utilisateur tape `Patient.birthDate + 'text'`, le système identifie `birthDate` comme ayant un type Date et `'text'` comme `String`. En consultant le livre de règles des opérateurs, il ne trouve aucune signature pour `Date + String` utilisant l'opérateur `+`. Cela entraîne l'inférence d'un type `Invalid`, et l'éditeur peut alors afficher un message d'erreur à l'utilisateur.

- **Suggestions intelligentes et autocomplétion :** C'est l'un des avantages les plus significatifs.
Lorsqu'un utilisateur tape un point après un identifiant, le système connaît déjà le type de l'expression précédente. Si ce type correspond à un type FHIR complexe, le système peut consulter le registre de schémas FHIR intégré pour lister tous les champs valides disponibles sur ce type. Il peut également consulter les livres de règles des fonctions et des opérateurs pour suggérer des fonctions ou opérateurs applicables au type courant.
- Ces suggestions peuvent être filtrées intelligemment, en priorisant ou en affichant exclusivement les options compatibles avec le contexte actuel.
- *Exemple :* Après qu'un utilisateur tape `Patient.name.`, le système, sachant que `Patient.name` se résout en une collection de `HumanName`, suggérerait des champs comme given, family, text (à partir de la définition `HumanName`) et des fonctions basées sur les collections comme `first()`, `where()`, `select()`, etc.

- **Éléments d'interface pilotés par les types :** L'interface utilisateur de l'éditeur peut être enrichie par ces informations de types.
Les menus déroulants pour sélectionner des types FHIR (p. ex., lors de l'utilisation de l'opérateur as) peuvent être dynamiquement peuplés avec des types valides issus directement du schéma FHIR.
- Des repères visuels, comme le codage par couleur ou des icônes, peuvent être appliqués aux jetons ou aux variables dans l'expression pour indiquer leur type inféré, améliorant la lisibilité.

- **Refactorisation plus sécurisée :** Lorsqu'un utilisateur renomme une variable ou une liaison nommée, le système peut aider à identifier toutes ses références. Le système de types peut ensuite revalider ces expressions mises à jour pour assurer la continuité de la correction des types.

#### 3.6. Pièges courants et scénarios avancés dans l'analyse de types FHIRPath

Malgré ses avantages, un système de types statique introduit plusieurs défis et scénarios avancés :
- **Performance dans les environnements interactifs :** Le processus d'inférence de types profondément récursif — notamment lors de l'analyse d'expressions complexes contre de grands schémas FHIR et de la résolution de types génériques complexes — peut être coûteux en calcul. Dans un éditeur interactif, où l'analyse doit être quasi temps réel pour fournir un retour immédiat, la performance est critique. Des stratégies de mise en cache efficaces (p. ex., pour les résultats des recherches dans les schémas FHIR ou pour les types de sous-expressions fréquemment rencontrées) et d'autres optimisations algorithmiques dans le moteur d'inférence sont essentielles pour maintenir une expérience utilisateur réactive. Si elles ne sont pas gérées, une vérification lente des types peut entraîner une interface lente, frustrant les utilisateurs.

- **Clarté des retours utilisateur et des messages d'erreur :** Un objectif principal de l'analyse statique est de prévenir les erreurs, mais la valeur de cette prévention dépend de la clarté des retours fournis. Les messages d'erreur générés par le système de types doivent être clairs, concis et exploitables pour les utilisateurs qui ne sont peut-être pas experts en théorie des types ou dans les éléments internes de l'analyseur. Traduire une condition `InvalidType` détectée en interne (peut-être avec une raison technique) en un message convivial — comme « Erreur : l'opérateur "+" ne peut pas être utilisé pour additionner une Date et un String ici » — est une partie cruciale de la création d'outils efficaces. Des messages d'erreur cryptiques ou peu utiles peuvent faire de la fonctionnalité d'analyse statique une gêne plutôt qu'une aide.

- **Complexité d'ingénierie du système de types central :** Concevoir et implémenter un système de types statique qui modélise avec précision l'étendue de FHIRPath — englobant diverses représentations internes de types (comme `Single`, les types choix, les lambdas et les primitives spécifiques à FHIR), des règles d'inférence sophistiquées, une intégration profonde avec les schémas FHIR, et une gestion robuste des génériques et du polymorphisme — est une tâche d'ingénierie intrinsèquement complexe. Cela requiert un effort de développement significatif, des tests approfondis sur un large éventail d'expressions FHIRPath et de structures FHIR, et une maintenance continue à mesure que les spécifications FHIR et FHIRPath évoluent. Sous-estimer cette complexité fondamentale peut mener à un système de types incomplet ou bogué.

- **Gestion des dépendances de types non linéaires dans les fonctions avancées :** Certaines fonctions FHIRPath avancées introduisent des complexités qui mettent au défi les stratégies simples d'évaluation de types linéaires. Un exemple clé est la fonction `aggregate(aggregatorLambda, initValue)`. Dans `aggregatorLambda`, une variable spéciale `$total` est disponible, et son type dépend de façon critique à la fois du type de `initValue` (le second argument) et du type de retour de `aggregatorLambda` lui-même. Une évaluation naïve des arguments de gauche à droite serait en difficulté ici : lors de la vérification de type de `aggregatorLambda`, le type de `$total` (dérivé de `initValue`) pourrait ne pas encore être connu si `initValue` n'a pas encore été traité. Cela nécessite des approches d'inférence plus sophistiquées, comme l'analyse multi-passes ou la résolution de types basée sur des contraintes, pour déterminer correctement les types dans des scénarios présentant de telles dépendances inter-arguments. Ne pas tenir compte de ces scénarios avancés peut entraîner une inférence de types incorrecte ou une incapacité à vérifier les types d'expressions valides.

Malgré ces défis, les avantages substantiels de l'application de l'analyse statique à FHIRPath — particulièrement en termes de détection précoce des erreurs, de guidance intelligente des développeurs et d'activation d'outils plus riches et plus interactifs — rendent l'entreprise hautement justifiée pour améliorer la qualité et l'efficacité du développement basé sur FHIR.

## Visualisation du système

Pour mieux comprendre le flux d'information, considérez les diagrammes suivants :

**Diagramme 1 : Architecture de haut niveau d'un éditeur FHIRPath à typage statique**

Ce diagramme illustre les composantes principales et leurs interactions, de la saisie de l'utilisateur aux fonctionnalités activées par l'analyse de types.
![](image-1.avif)
**Diagramme 2 : Flux conceptuel d'inférence de types pour `Patient.name.first()`**

Ce diagramme montre un exemple simplifié, étape par étape, de la façon dont le type pourrait être inféré pour une expression FHIRPath courante.
![](image-2.avif)
## Conclusion : donner plus de pouvoir aux utilisateurs de FHIRPath

La mise en œuvre d'un système d'analyse de types statique pour FHIRPath est un effort d'ingénierie considérable, mais les récompenses sont substantielles pour quiconque travaille avec des données FHIR. En créant une représentation interne de types détaillée et précise, en s'intégrant profondément aux définitions structurelles fournies par les schémas FHIR, et en employant un moteur d'inférence robuste, il devient possible de construire des outils de développement qui font plus qu'accepter du texte — ils comprennent et assistent activement. Ces outils peuvent détecter les erreurs avant qu'elles ne se manifestent à l'exécution, guider les utilisateurs vers la construction d'expressions FHIRPath correctes et significatives grâce à des suggestions intelligentes, et rendre la puissance de FHIRPath plus accessible et fiable. Cette élévation de l'expérience développeur est cruciale alors que FHIR et FHIRPath continuent de jouer un rôle central dans l'interopérabilité en santé.

### Lectures complémentaires et ressources
- [**Spécification officielle de FHIRPath**](http://hl7.org/fhirpath/N1/) : La source définitive pour la syntaxe et la sémantique de FHIRPath.
- [**Ressource StructureDefinition FHIR**](https://www.hl7.org/fhir/structuredefinition.html) : Décrit comment les ressources FHIR et les types de données sont définis.

### Un appel à la collaboration et à l'exploration

Le développement d'outils FHIRPath avancés est un effort continu et collaboratif au sein de la communauté FHIR. Les principes et l'architecture abordés dans cet article ne sont pas seulement théoriques; ils se reflètent dans des implémentations pratiques.

Pour ceux qui souhaitent explorer un exemple concret d'un tel système, ou contribuer à son évolution — l'éditeur FHIRPath qui incorpore bon nombre de ces techniques d'analyse statique est **libre de droits et disponible sur GitHub.**

Nous vous encourageons à :
- **Explorer le code :** Voyez comment ces concepts sont traduits en une solution fonctionnelle.
- **Fournir des retours :** Partagez vos expériences, suggérez des améliorations ou signalez des problèmes.
- **Contribuer :** Si vous avez des idées ou des correctifs, les contributions sont les bienvenues pour aider à améliorer l'outil.

En partageant connaissances, outils et expériences, nous pouvons collectivement rendre FHIRPath plus accessible, fiable et puissant pour tous dans l'écosystème de l'informatique de la santé.

Voir aussi : [Formulaires plus intelligents avec le filtrage dynamique des ValueSet](/blog/smarter-forms-with-dynamic-valueset-filtering).