Clause
← Gesamte Dokumentation

Übersetzt aus dem englischen Original · 1e3a39c8-dirty · 2026-10-26 · Auf Englisch lesen

Semantische Analyse

Läuft nach dem Parsen bei der Standardkompilierung und bei --print-types; rein syntaktische Aktionen überspringen sie. Fehler stoppen den betroffenen Batch vor LLVM; Diagnosen behalten ihre Herkunft aus Makros und Includes, und spätere Eingaben werden trotzdem diagnostiziert.

Modul- und Aufrufprüfungen

Behaviours

Folgt den Behaviours von OTP, erl_internal:add_predefined_functions und check_behaviour von erl_lint.

Importe

Folgt den Importregeln von erl_lint.

Vordefinierte Funktionen

Folgt den Modulen von OTP und erl_internal:add_predefined_functions. Das Frontend hängt an jedes kompilierte Modul (nach einem erzeugten behaviour_info/1) module_info/0,1 als Erlang-Funktionen an, die literale Daten zurückgeben (driver/predefined, semantic/module_info); Module::predefined_ zählt diese nachgestellten Formen, die immer exportiert und aus --print-types weggelassen werden.

Bindungen

Jede Bindung hat eine funktionsrelative Identität clause[N].local[M]. Vorkommen sind Definitionen, Lesezugriffe oder Prüfungen auf exakte Gleichheit, markiert mit dem Kontext Kopf/Guard/Rumpf. Die Analyse ist deterministisch.

Durchläufe sind iterativ mit einem Modulbudget von 1.000.000 Arbeitseinheiten oder 32 pro Ausdruck, wenn das mehr ist (erzeugte Parser wie erl_parse von OTP brauchen etwa 10 pro Ausdruck). Die Dienstauflösung hat ein Budget gleicher Größe; sie durchsucht die -compile- und -import-Formen des Moduls nach dem Auto-Import-Zustand eines Namens einmal pro Name, nicht einmal pro Aufruf (jede der Tausenden Klauseln von erl_parse ruft hd/1 auf). Erschöpfung oder ein beliebiger semantischer Fehler leert die Bindungs- und Normalisierungstabellen des Moduls.

Typen und Spezifikationen

Ein privater Typgraph stellt alle geparsten Typformen unabhängig vom Runtime-Layout dar: Singletons, Bereiche, Container, Rollen von Map-Feldern, Funktionsprodukte und unaufgelöste Anwendungen. Unions werden abgeflacht und dedupliziert; term() ist oben und none() unten. Standards: 1.048.576 Knoten, 256 Union-Mitglieder, 200.000 Arbeitseinheiten pro Durchlauf über einen deklarierten Typ (Auflösung, Übersetzung, Expansion) und 8.000.000 pro Inferenzdurchlauf über das ganze Programm; ein Debug-Build von clau (ohne NDEBUG) erlaubt 16.000 und 1.000.000, damit große Programme trotzdem schnell kompilieren, und kann stärker aufweiten. Erschöpfung weitet mit sichtbarem Flag auf term() auf und engt nie eine Repräsentation ein.

Deklarierte Metadaten (-type, -opaque, -nominal, -export_type, -spec, -callback, -optional_callbacks) folgen der Typespec-Referenz von OTP und dem festgelegten Verhalten von erl_lint/erl_internal/erl_types:

Inferenz

Die Inferenz ist von deklarierten Typen getrennt und vertraut Specs nie.

Abhängige Fakten

semantic/types/dependent. Ein case, if oder try ... of verhält sich wie ein Fun der Variablen, die seine Klauseln einengen, angewendet auf diese: Sein Wert ist ein abhängiger Fakt, ein Funktionstyp pro möglicher Klausel.

Inferenzdomäne

semantic/types/lattice. Ein Fakt ist eine Menge von Werten, die eine Variable oder ein Ergebnis haben kann. Fakten werden dort vereinigt, wo Kontrollfluss zusammentrifft (Klauseln, Zweige), und zwischen den Runden einer rekursiven Komponente aufgeweitet; jedes Budget unten weitet korrekt auf eine größere Menge auf und weist nie ein Programm ab. Fakten werden als Erlang-Typen ausgegeben, Kategorien mit ihren eingebauten Namen.

FaktAusgabeVereinigungBudget und Aufweitung
Nichtsnone()Neutrales ElementEine Funktion, die nie zurückkehrt, bleibt none()
Allesterm()Absorbiert jeden Faktdynamic() und any() sind term()
Ganzzahlen42, 1 | 3 | 7Union der SingletonsMehr als 8 Singletons werden zu ihrem Bereich
Ganzzahlbereich1..10, 0..255Kleinster Bereich, der beide enthältEine Schranke, die sich zwischen Runden bewegt hat, geht zur nächsten Schwelle: eine untere auf 1, dann 0, dann unbeschränkt; eine obere auf -1, dann unbeschränkt. Auch Schranken innerhalb von Containern werden aufgeweitet, wenn der vorherige Fakt an derselben Stelle ein Tupel (dieser Größe und mit diesem Tag), eine Map (mit diesen Schlüsseln) oder eine Liste hat
Unbeschränkte Ganzzahlenpos_integer() (ab 1), non_neg_integer() (ab 0), neg_integer() (-1 und darunter), integer()Kleinstes Intervall, das beide enthält, ausgegeben nach seiner Kategorie—
Gleitkommazahlenfloat()——
Zahlennumber()Ein Bereich oder eine Kategorie von Ganzzahlen, vereinigt mit float()Singleton-Ganzzahlen mit float() bleiben 1 | float()
Atomeok, error | ok, boolean()Union der Singletons; genau false und true werden als boolean() ausgegebenMehr als 8 Singletons werden zu atom()
Bezeichnerpid(), port(), reference()——
Tupel{ok, 1}, tuple(), #point{x :: 0, y :: _}Tupel gleicher Größe, deren erste Elemente nicht zwei verschiedene Atome sind (ihr Tag), werden elementweise vereinigt; andere bleiben getrennte MitgliederMehr als 16 Elemente werden zu tuple(), sofern nicht jedes Element bekannt ist; mehr als 8 getrennte Formen werden zu tuple(). Ein Tupel aus Name und Größe eines sichtbaren Tupel-Records wird als der Record ausgegeben
Listen[], [T], [T, ...], nonempty_improper_list(H, T), [1, a], [a, b | T]Elemente werden vereinigt; [] mit einer nicht leeren Liste ergibt eine möglicherweise leere; unechte Listen vereinigen Köpfe und Enden. Listen aus zwei oder mehr bekannten Elementen behalten ihre Positionen (eine Notation von Clause, die Typsprache hat keine; Elemente mit | in Klammern ausgegeben): Positionslisten einer Länge werden Position für Position vereinigt, sonst als einfache ListenEine Liste von 0..1114111 (char()) wird als string() oder nonempty_string() ausgegeben; eine möglicherweise leere Liste von _ als list()
Maps#{}, #{a := 1}, #{1..17 => a}, map()Maps mit denselben Schlüsseln werden Wert für Wert vereinigt; Maps anderer Schlüssel werden zu einer Assoziation ihrer vereinigten Schlüssel und Werte vereinigt (=>: jeder Schlüssel kann fehlen)Mehr als 16 Schlüssel werden zu einer Assoziation vereinigt
Funsfun((term()) -> 1), fun()Funs einer Stelligkeit vereinigen ihre Ergebnisse; andere Stelligkeiten ergeben fun()—
Bitstrings<<_:16>>, <<_:3, _:_*2>>, binary()Die kürzere Größe plus jede Größendifferenz als EinheitBasis und Einheit 0/8, 8/8, 0/1, 1/1 werden als binary(), nonempty_binary(), bitstring(), nonempty_bitstring() ausgegeben

Bei -O2 mit Typspezialisierung verwendet das Lowering diese Fakten, um Prüfungen zu entfernen (Spezialisierung); Spezifikationen werden nie zu Fakten.

Inferenz-Cache

Module in den Verzeichnissen des Inferenz-Caches können eine Kompilierung mit den Ergebnissen beginnen, die eine frühere Kompilierung behalten hat: Zusammenfassungen, lokale Eingaben und Ausdrucksfakten ihrer Funktionen. infer hält sie in jedem Durchlauf fest, durchläuft nie eine Komponente, deren Mitglieder sie alle wiederverwenden, und durchläuft jede andere Funktion wie gewohnt, wobei es die behaltenen Zusammenfassungen wie jede andere liest.

--print-types

Gibt jedes Modul des Batches (in Eingabe-/Zielreihenfolge, Bibliotheksmodule danach) als Erlang-Quelltext aus (Ausgabe von Quelltext), zusammen mit dem, was die Typinferenz gefunden hat. Die Ausgabe geht auf stdout und ist für Menschen lesbar, weder Erlang noch ein Austauschformat. Warnungen bleiben auf 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

Inferenzerwartungen

tests/fixtures/inference/*.erl halten fest, was die Inferenz für jede Funktion finden sollte und was sie heute findet. Jedes Modul wird zum CTest inference_<module> (tests/compiler/inference/expectations.py):

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

Ausgabe von Typen

semantic::types::type_source(graph, type) (semantic/types/printing) stellt einen Typ des Typgraphen in Erlang-Typsyntax dar: _ für einen beliebigen Term (term(), der Kürze halber von TERM_SOURCE geschrieben; die Typsyntax liest _ als any()), none(), Atome und Ganzzahlen, 1..5, {ok, T}, tuple(), [T], [T, ...], #{K => V, K := V}, #r{f :: T}, <<_:B, _:_*U>>, fun((A) -> R), A | B. Ein Fun mit mehreren Funktionstypen gibt diese in einer Notation von Clause aus, fun((1) -> one; (_) -> other): Die Erlang-Typsyntax hat keinen überladenen Fun-Typ, und eine Union von Fun-Typen bedeutet etwas anderes. Die Ganzzahlen einer Union werden in Wertreihenfolge an der Stelle ihrer ersten Ganzzahl ausgegeben, aufeinanderfolgende als Bereich (1 | 2 | 3 | 5 wird als 1..3 | 5 ausgegeben; der Fakt behält die Singletons). Vordefinierte erlang-Typen verlieren ihr Modul; Referenzen auf deklarierte Typen bleiben benannt. Ein Knotenbudget begrenzt den Text; jenseits davon und unterhalb von 32 Verschachtelungsebenen steht ... an seiner Stelle.

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