Clause
← All dokumentation

Översatt från det engelska originalet · 1e3a39c8-dirty · 2026-10-26 · Läs på engelska

Semantisk analys

Körs efter parsning vid standardkompilering och --print-types; åtgärder som bara gäller syntax hoppar över den. Fel stoppar den berörda batchen före LLVM; diagnostik behåller makro-/include-ursprung, och senare indata diagnostiseras ändå.

Kontroller av moduler och anrop

Beteenden

Enligt OTP:s beteenden, erl_internal:add_predefined_functions och check_behaviour i erl_lint.

Importer

Enligt importreglerna i erl_lint.

Fördefinierade funktioner

Enligt OTP:s moduler och erl_internal:add_predefined_functions. Frontend lägger till module_info/0,1 i varje kompilerad modul (efter en genererad behaviour_info/1) som Erlang-funktioner som returnerar literala data (driver/predefined, semantic/module_info); Module::predefined_ räknar dessa avslutande former, som alltid exporteras och utelämnas från --print-types.

Bindningar

Varje bindning har en funktionsrelativ identitet clause[N].local[M]. Förekomster är definitioner, läsningar eller kontroller av exakt likhet, märkta med kontexten huvud/guard/kropp. Analysen är deterministisk.

Genomgångar är iterativa med en modulbudget på 1 000 000 arbetsenheter, eller 32 per uttryck när det är mer (genererade parsrar som OTP:s erl_parse behöver cirka 10 per uttryck). Lösningen av tjänster har en budget av samma storlek; den genomsöker modulens -compile- och -import-former efter ett namns autoimportstatus en gång per namn, inte en gång per anrop (var och en av erl_parse:s tusentals klausuler anropar hd/1). Uttömning eller något semantiskt fel rensar modulens bindnings- och normaliseringstabeller.

Typer och specifikationer

En privat typgraf representerar alla parsade typformer oberoende av runtime-layout: singletoner, intervall, behållare, roller för map-fält, funktionsprodukter och olösta tillämpningar. Unioner plattas ut och dedupliceras; term() är toppen och none() botten. Standardvärden: 1 048 576 noder, 256 unionsmedlemmar, 200 000 arbetsposter per genomgång av en deklarerad typ (lösning, översättning, expansion) och 8 000 000 per inferenspass över hela programmet; ett felsökningsbygge av clau (utan NDEBUG) tillåter 16 000 och 1 000 000 så att stora program fortfarande kompileras snabbt, och kan bredda mer. Uttömning breddar till term() med en synlig flagga och smalnar aldrig av en representation.

Deklarerad metadata (-type, -opaque, -nominal, -export_type, -spec, -callback, -optional_callbacks) följer OTP:s typespec-referens och det fastlåsta beteendet hos erl_lint/erl_internal/erl_types:

Inferens

Inferensen är skild från deklarerade typer och litar aldrig på specs.

Beroende fakta

semantic/types/dependent. Ett case, if eller try ... of beter sig som en fun av de variabler som dess klausuler inskränker, tillämpad på dem: dess värde är ett beroende faktum, en funktionstyp per möjlig klausul.

Inferensdomän

semantic/types/lattice. Ett faktum är en mängd värden som en variabel eller ett resultat kan ha. Fakta förenas där kontrollflöden möts (klausuler, grenar) och breddas mellan omgångarna i en rekursiv komponent; varje budget nedan breddar sunt till en större mängd och avvisar aldrig ett program. Fakta skrivs ut som Erlang-typer, kategorier med sina inbyggda namn.

FaktumUtskriftFöreningBudget och breddning
Ingentingnone()IdentitetEn funktion som aldrig returnerar förblir none()
Vad som helstterm()Absorberar varje faktumdynamic() och any() är term()
Heltal42, 1 | 3 | 7Union av singletonerFler än 8 singletoner blir sitt intervall
Heltalsintervall1..10, 0..255Minsta intervall som rymmer bådaEn gräns som flyttats mellan omgångar går till nästa tröskel: en nedre till 1, sedan 0, sedan obegränsad; en övre till -1, sedan obegränsad. Gränser inuti behållare breddas också, där det föregående faktumet har en tupel (av den storleken och med den taggen), map (med de nycklarna) eller lista på samma plats
Obegränsade heltalpos_integer() (1 och uppåt), non_neg_integer() (0 och uppåt), neg_integer() (-1 och nedåt), integer()Minsta intervall som rymmer båda, utskrivet med sin kategori—
Flyttalfloat()——
Talnumber()Ett intervall eller en kategori av heltal förenat med float()Singletonheltal med float() förblir 1 | float()
Atomerok, error | ok, boolean()Union av singletoner; exakt false och true skrivs ut som boolean()Fler än 8 singletoner blir atom()
Identifierarepid(), port(), reference()——
Tupler{ok, 1}, tuple(), #point{x :: 0, y :: _}Tupler av samma storlek vars första element inte är två olika atomer (deras tagg) förenas element för element; övriga förblir separata medlemmarFler än 16 element blir tuple() om inte varje element är känt; fler än 8 separata former blir tuple(). En tupel med en synlig tupel-records namn och storlek skrivs ut som recorden
Listor[], [T], [T, ...], nonempty_improper_list(H, T), [1, a], [a, b | T]Elementen förenas; [] med en icke-tom lista ger en möjligen tom lista; oäkta listor förenar huvuden och svansar. Listor med två eller fler kända element behåller sina positioner (en Clause-notation, typspråket saknar en sådan, element skrivs ut med | inom parentes): positionella listor av samma längd förenas position för position, annars förenas de som vanliga listorEn lista av 0..1114111 (char()) skrivs ut som string() eller nonempty_string(); en möjligen tom lista av _ skrivs ut som list()
Maps#{}, #{a := 1}, #{1..17 => a}, map()Maps med samma nycklar förenas värde för värde; maps med andra nycklar förenas till en association av sina förenade nycklar och värden (=>: vilken nyckel som helst kan saknas)Fler än 16 nycklar förenas till en association
Funsfun((term()) -> 1), fun()Funs med samma aritet förenar sina resultat; andra ariteter ger fun()—
Bitstrings<<_:16>>, <<_:3, _:_*2>>, binary()Den kortare storleken plus varje skillnad mellan storlekar som enhetBas och enhet 0/8, 8/8, 0/1, 1/1 skrivs ut som binary(), nonempty_binary(), bitstring(), nonempty_bitstring()

Vid -O2 med typspecialisering använder sänkningen (lowering) dessa fakta för att ta bort kontroller (specialisering); specifikationer blir aldrig fakta.

Inferenscache

Moduler i katalogerna för inferenscachen kan börja en kompilering med de resultat som en tidigare kompilering behöll: sammanfattningar, lokala indata och uttrycksfakta för deras funktioner. infer håller dem fasta i varje pass, går aldrig igenom en komponent vars medlemmar alla återanvänder dem, och går igenom varje annan funktion som vanligt, där de behållna sammanfattningarna läses som vilka andra som helst.

--print-types

Skriver ut varje modul i batchen (i indata-/målordning, biblioteksmoduler efter dem) som Erlang-källkod (utskrift av källkod) med det som typinferensen hittade. Utdata går till stdout och är läsbara för människor, inte Erlang och inte ett utbytesformat. Varningar stannar på 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

Förväntningar på inferensen

tests/fixtures/inference/*.erl registrerar vad inferensen bör hitta för varje funktion, och vad den hittar i dag. Varje modul blir CTest-testet inference_<module> (tests/compiler/inference/expectations.py):

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

Utskrift av typer

semantic::types::type_source(graph, type) (semantic/types/printing) återger en typ ur typgrafen i Erlangs typsyntax: _ för godtycklig term (term(), skrivet av TERM_SOURCE för korthetens skull; typsyntaxen läser _ som any()), none(), atomer och heltal, 1..5, {ok, T}, tuple(), [T], [T, ...], #{K => V, K := V}, #r{f :: T}, <<_:B, _:_*U>>, fun((A) -> R), A | B. En fun med flera funktionstyper skriver ut dem i en Clause-notation, fun((1) -> one; (_) -> other): Erlangs typsyntax har ingen överlagrad fun-typ, och en union av fun-typer betyder något annat. En unions heltal skrivs ut i värdeordning där dess första heltal står, på varandra följande som ett intervall (1 | 2 | 3 | 5 skrivs ut som 1..3 | 5; faktumet behåller singletonerna). Fördefinierade erlang-typer tappar sin modul; referenser till deklarerade typer förblir namngivna. En nodbudget begränsar texten; utöver den, och djupare än 32 nivåers nästling, står ... i stället.

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