Clause
← Toute la documentation

Traduit de l'original anglais · 06042fa · 2026-10-09 · 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

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, marquées par leur contexte : tête, guard ou corps. L'analyse est déterministe.

Les parcours sont itératifs, avec un budget par module de 1 000 000 unités de travail. 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 : 16 384 nœuds, 16 membres d'union, 100 000 éléments de travail par traduction. L'épuisement élargit à term() avec un indicateur visible et ne restreint 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 typespec 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.

Domaine d'inférence

Décision du plan 11 step 58A (semantic/types/lattice). Un fait est un ensemble de valeurs que peut avoir une variable ou un résultat. 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()IdentitéUne fonction qui ne retourne jamais reste none()
Toutterm()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
Entiers non bornéspos_integer() (1 et au-delà), non_neg_integer() (0 et au-delà), neg_integer() (-1 et en deçà), 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 (step 58J)
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 (step 58J ; 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 ordinairesUne 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 (=> : toute clé peut manquer, step 58J)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 plus petite taille 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()

L'abaissement (lowering) consomme ces faits. L'IR générée ne convertit jamais un entier en pointeur vers le tas ; chaque résultat de service faillible n'est chargé que sur son chemin de succès, et les vérifications de forme dominent l'extraction. Politique de spécialisation : specialization.md.

--print-types

Affiche chaque module du lot (dans l'ordre des entrées ou des 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 se fait 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..2
mixed(X) ->
    case X of
        1 ->
            1;
        _ ->
            2
    end :: 1..2.

Attentes de l'inférence

tests/fixtures/inference/*.erl enregistrent 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 de types Erlang : _ pour n'importe quel terme (term(), écrit par TERM_SOURCE par souci de concision ; la syntaxe de 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 à plusieurs types de fonction les affiche dans une notation de Clause, fun((1) -> one; (_) -> other) : la syntaxe de 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 à la place de 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, ... le remplace.

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