Clause
← Toute la documentation

Traduit de l'original anglais · 1e3a39c8-dirty · 2026-10-26 · Lire en anglais

Analyse sémantique

S'exécute après l'analyse syntaxique lors de la compilation par défaut et avec --print-types ; les actions purement syntaxiques l'ignorent. Les erreurs arrêtent le lot concerné avant LLVM ; les diagnostics conservent l'origine des macros et des inclusions, et les entrées suivantes sont tout de même diagnostiquées.

Vérifications des modules et des appels

Behaviours

D'après les behaviours d'OTP, erl_internal:add_predefined_functions et le check_behaviour d'erl_lint.

Imports

D'après les règles d'import d'erl_lint.

Fonctions prédéfinies

D'après les modules d'OTP et erl_internal:add_predefined_functions. Le frontend ajoute module_info/0,1 à chaque module compilé (après une behaviour_info/1 générée) sous forme de fonctions Erlang renvoyant des données littérales (driver/predefined, semantic/module_info) ; Module::predefined_ compte ces formes finales, qui sont toujours exportées et omises de --print-types.

Liaisons

Chaque liaison a une identité relative à la fonction clause[N].local[M]. Les occurrences sont des définitions, des lectures ou des vérifications d'égalité exacte, étiquetées avec le contexte tête/guard/corps. L'analyse est déterministe.

Les parcours sont itératifs, avec un budget par module de 1 000 000 unités de travail, ou 32 par expression si c'est davantage (les parseurs générés comme l'erl_parse d'OTP ont besoin d'environ 10 par expression). La résolution des services a un budget de même taille ; elle parcourt les formes -compile et -import du module pour l'état d'auto-import d'un nom une fois par nom, et non une fois par appel (chacune des milliers de clauses d'erl_parse appelle hd/1). L'épuisement ou toute erreur sémantique vide les tables de liaisons et de normalisation du module.

Types et spécifications

Un graphe de types privé représente toutes les formes de types analysées, indépendamment de la disposition au runtime : singletons, plages, conteneurs, rôles des champs de map, produits de fonctions et applications non résolues. Les unions sont aplaties et dédupliquées ; term() est le sommet et none() le fond. Valeurs par défaut : 1 048 576 nœuds, 256 membres d'union, 200 000 éléments de travail par parcours d'un type déclaré (résolution, traduction, expansion) et 8 000 000 par passe d'inférence sur tout le programme ; une compilation de débogage de clau (sans NDEBUG) autorise 16 000 et 1 000 000 afin que les grands programmes compilent toujours rapidement, et peut élargir davantage. L'épuisement élargit à term() avec un indicateur visible et ne rétrécit jamais une représentation.

Les métadonnées déclarées (-type, -opaque, -nominal, -export_type, -spec, -callback, -optional_callbacks) suivent la référence des typespecs d'OTP et le comportement épinglé d'erl_lint/erl_internal/erl_types :

Inférence

L'inférence est séparée des types déclarés et ne fait jamais confiance aux specs.

Faits dépendants

semantic/types/dependent. Un case, un if ou un try ... of se comporte comme une fun des variables que ses clauses restreignent, appliquée à celles-ci : sa valeur est un fait dépendant, un type de fonction par clause possible.

Domaine d'inférence

semantic/types/lattice. Un fait est un ensemble de valeurs qu'une variable ou un résultat peut avoir. Les faits se joignent là où le flot de contrôle se rejoint (clauses, branches) et s'élargissent entre les tours d'une composante récursive ; chaque budget ci-dessous élargit de façon sûre vers un ensemble plus grand, sans jamais rejeter un programme. Les faits s'affichent comme des types Erlang, les catégories par leurs noms intégrés.

FaitAffichageJonctionBudget et élargissement
Riennone()Élément neutreUne fonction qui ne retourne jamais reste none()
N'importe quoiterm()Absorbe tout faitdynamic() et any() sont term()
Entiers42, 1 | 3 | 7Union de singletonsPlus de 8 singletons deviennent leur plage
Plage d'entiers1..10, 0..255Plus petite plage contenant les deuxUne borne qui a bougé entre deux tours passe au seuil suivant : une borne inférieure à 1, puis 0, puis non bornée ; une borne supérieure à -1, puis non bornée. Les bornes dans les conteneurs s'élargissent aussi lorsque le fait précédent contient au même endroit un tuple (de cette taille et de cette étiquette), une map (avec ces clés) ou une liste
Entiers non bornéspos_integer() (1 et plus), non_neg_integer() (0 et plus), neg_integer() (-1 et moins), integer()Plus petit intervalle contenant les deux, affiché par sa catégorie—
Flottantsfloat()——
Nombresnumber()Une plage ou une catégorie d'entiers jointe avec float()Des entiers singletons avec float() restent 1 | float()
Atomesok, error | ok, boolean()Union de singletons ; exactement false et true s'affichent boolean()Plus de 8 singletons deviennent atom()
Identifiantspid(), port(), reference()——
Tuples{ok, 1}, tuple(), #point{x :: 0, y :: _}Les tuples de même taille dont les premiers éléments ne sont pas deux atomes différents (leur étiquette) se joignent élément par élément ; les autres restent des membres séparésPlus de 16 éléments deviennent tuple() sauf si chaque élément est connu ; plus de 8 formes séparées deviennent tuple(). Un tuple ayant le nom et la taille d'un record sous forme de tuple visible s'affiche comme le record
Listes[], [T], [T, ...], nonempty_improper_list(H, T), [1, a], [a, b | T]Les éléments se joignent ; [] avec une liste non vide donne une liste éventuellement vide ; les listes impropres joignent têtes et queues. Les listes de deux éléments connus ou plus conservent leurs positions (une notation de Clause, le langage de types n'en a pas, éléments affichés avec | entre parenthèses) : les listes positionnelles d'une même longueur se joignent position par position, sinon elles se joignent comme des listes simplesUne liste de 0..1114111 (char()) s'affiche string() ou nonempty_string() ; une liste éventuellement vide de _ s'affiche list()
Maps#{}, #{a := 1}, #{1..17 => a}, map()Les maps ayant les mêmes clés se joignent valeur par valeur ; les maps d'autres clés se joignent en une seule association de leurs clés et valeurs jointes (=> : n'importe quelle clé peut manquer)Plus de 16 clés se joignent en une seule association
Funsfun((term()) -> 1), fun()Les funs d'une même arité joignent leurs résultats ; d'autres arités donnent fun()—
Bitstrings<<_:16>>, <<_:3, _:_*2>>, binary()La taille la plus courte plus chaque différence de tailles comme unitéBase et unité 0/8, 8/8, 0/1, 1/1 s'affichent binary(), nonempty_binary(), bitstring(), nonempty_bitstring()

En -O2 avec la spécialisation par type, l'abaissement utilise ces faits pour supprimer des vérifications (spécialisation) ; les spécifications ne deviennent jamais des faits.

Cache d'inférence

Les modules des répertoires du cache d'inférence peuvent commencer une compilation avec les résultats qu'une compilation antérieure a conservés : résumés, entrées locales et faits d'expressions de leurs fonctions. infer les garde fixes à chaque passe, ne parcourt jamais une composante dont tous les membres les réutilisent, et parcourt chaque autre fonction comme d'habitude, en lisant les résumés conservés comme n'importe quels autres.

--print-types

Affiche chaque module du lot (ordre des entrées/cibles, les modules de bibliothèque après eux) sous forme de source Erlang (affichage du source) avec ce que l'inférence de types a trouvé. La sortie va sur stdout et est lisible par un humain ; ce n'est ni de l'Erlang ni un format d'échange. Les avertissements restent sur stderr.

%% module "branches" source="branches.erl" target="" declared=complete inferred=complete
-module(branches).
-export([mixed/1]).

%% inferred: mixed(1) -> 1; (_) -> 2
mixed(X) ->
    case X of
        1 ->
            1;
        _ ->
            2
    end. % $case_of_operator(X :: 1) -> 1; (X :: _) -> 2

Attentes de l'inférence

tests/fixtures/inference/*.erl consignent ce que l'inférence devrait trouver pour chaque fonction, et ce qu'elle trouve aujourd'hui. Chaque module devient le test CTest inference_<module> (tests/compiler/inference/expectations.py) :

%% expect: sum() -> 3
%% today: sum() -> _
sum() -> 1 + 2.

Affichage des types

semantic::types::type_source(graph, type) (semantic/types/printing) rend un type du graphe de types dans la syntaxe des types Erlang : _ pour un terme quelconque (term(), écrit par TERM_SOURCE par concision ; la syntaxe des types lit _ comme any()), none(), atomes et entiers, 1..5, {ok, T}, tuple(), [T], [T, ...], #{K => V, K := V}, #r{f :: T}, <<_:B, _:_*U>>, fun((A) -> R), A | B. Une fun de plusieurs types de fonction les affiche dans une notation de Clause, fun((1) -> one; (_) -> other) : la syntaxe des types Erlang n'a pas de type de fun surchargé, et une union de types de fun signifie autre chose. Les entiers d'une union s'affichent dans l'ordre des valeurs à l'endroit où se trouve son premier entier, les entiers consécutifs sous forme de plage (1 | 2 | 3 | 5 s'affiche 1..3 | 5 ; le fait conserve les singletons). Les types prédéfinis d'erlang perdent leur module ; les références aux types déclarés restent nommées. Un budget de nœuds borne le texte ; au-delà, et en dessous de 32 niveaux d'imbrication, ... les remplace.

clau --print-types answer.erl client.erl
clau --print-types --project project.toml --target demo --verbose