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
- Moduldeklaration krävs och måste vara unik; funktionsariteter 0..255; inga dubblerade definitioner; varje export finns (en upprepad export ger en varning, som i OTP). Citerade/Unicode-namn behåller sin exakta identitet.
- Varje funktion kontrolleras, även oanvänd och onåbar kod.
- Anrop löses inom batchen via modul/namn/aritet. Fjärranrop (även självkvalificerade) kräver exporter. Saknade/privata anropade funktioner och dubblerade moduler är fel.
fun F/Amåste namnge en funktion i modulen (function F/A undefined);fun M:F/Aoch anrop av värden (F(Args)) löses inte vid kompilering (funs). Varje distinkt fun-värde får en post iModule::funsefter bindningsanalysen.- Själv-, ömsesidig och modulöverskridande rekursion godtas. Anropsgrafen delas upp i starkt sammanhängande komponenter (iterativ Tarjan) i ordningen anropad före anropare; en komponent är rekursiv när den har flera medlemmar eller när en medlem anropar sig själv.
Beteenden
Enligt OTP:s
beteenden,
erl_internal:add_predefined_functions och check_behaviour i erl_lint.
- En modul som deklarerar
-callbackfår OTP:s fördefinierade, exporteradebehaviour_info/1:callbacksger de deklarerade callbacks i källkodsordning,optional_callbacksde i välformade-optional_callbacks-listor (callbacks kvalificerade med en modul utelämnas). Frontend lägger till den som en funktion på-module-raden (driver/predefined). Som i OTP kan modulens egen källkod inte namnge den: en-export, ett lokalt anrop,fun behaviour_info/1eller en-specav den är en odefinierad funktion; fjärranrop (även självkvalificerade) når den.-callbackbredvid en handskrivenbehaviour_info/1är ett fel (cannot define callback attibute for F/A when behaviour_info is defined, OTP:s stavning). -behaviour(M)och-behavior(M)får upprepas.Mlöses i batchen; ett namn med en biblioteksmodul (library.md) lägger till den modulen som ett literalt anrop gör. Diagnostik använder OTP:s text vid attributnamnet:undefined callback function F/A (behaviour 'M')för varje obligatorisk, icke-valfri callback som modulen inte exporterar (sorterat);conflicting behaviours -- callback F/A required by both 'M2' and 'M1' (line L, column C)när ett tidigare beteende också kräver den (en valfri callback räknas när den exporteras);behaviour M undefinednärMinte finns i batchen eller saknarbehaviour_info/1(ett OTP-beteende som Clauses bibliotek ännu inte tillhandahåller, till exempelapplication), plus OTP:s fel för modulnamn som inte kan vara moduler (the module name must not be empty,... must be an atom, ...).
- Varningar får inte kompileringen att misslyckas.
-compile-alternativennowarn_behaviours(hela kontrollen, fel för modulnamn inkluderade),nowarn_undefined_behaviour_func,nowarn_undefined_behaviourochnowarn_conflicting_behavioursstänger av dem. - Utvärderas inte: en handskriven exporterad
behaviour_info/1i en beteendemodul, så dess användares callbacks kontrolleras inte, och OTP:s varningar för felaktigt definierade och föråldrade callbacks förekommer aldrig (skillnader). - Belägg:
tests/fixtures/lint(diagnostik genererad av OTP, CTestlint_diagnostics),executables_behaviours, CLI-fall isemantic.
Importer
Enligt importreglerna i erl_lint.
-import(Module, [F/A, ...])gör ett lokalt anropF(...)till fjärranropetModule:F(...): modulen måste finnas i batchen (en biblioteksmodul ansluter som vid ett literalt anrop) och exportera funktionen, eller vara en inbyggd modul (-import(io, [format/2]),-import(erlang, [display/1])). En import har företräde framför en automatiskt importerad BIF med samma namn.- Fel med OTP:s text:
function F/A already imported from M(hela attributet ignoreras då),defining imported function F/A,creating a fun from imported name F/A is not allowed. En importerad funktion i en guard är ett otillåtet guard-anrop (utom guard-BIF:ar i erlang). - Varningen
import directive overrides auto-imported BIF F/A -- ...om inteno_auto_importnamnger den ellernowarn_bif_clashanges; Clause känner till de automatiskt importerade BIF:ar som det implementerar. - Belägg:
executables_imports, lint-fallenimports_conflicts,imports_fun,imports_bif, CLI-fall i semantic.
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.
module_info()ger[{module,_},{exports,_},{attributes,_},{compile,_},{md5,_}];module_info(Key)besvarar ocksåfunctions,nifs([]) ochnative(false), och gerbadargför varje annan nyckel.exports: de exporterade funktionerna i definitionsordning (escripts lägger tillmain/1), sedanbehaviour_info/1när den genereras,module_info/0,module_info/1.functions: varje funktion i definitionsordning, sedan samma fördefinierade.attributes: varje attribut utommodule, exporter och importer, type-/spec-/callback-former, records,export_type,optional_callbacks,export_record,compile,file, dokumentation ochfeature, i källkodsordning; ett värde som inte är en lista omsluts av en (-tags(a)är{tags,[a]}). Utan-vsnkommer{vsn,[N]}först, därNärmd5läst som ett teckenlöst big-endian-heltal, så som OTP härleder det.md5: MD5 av modulens utskrivna källkod (print_source: makron expanderade, inkluderingar infogade, kommentarer och layout borttagna).compile:[{version,ClauseVersion},{options,[]},{source,AbsolutePath}].- Till skillnad från
behaviour_info/1ser modulens egen källkod dem: lokala anrop ochfun module_info/1fungerar. Att definiera någon av dem är ett fel (function module_info/0 already defined); att exportera en, som varje upprepad export, ger endast en varning (function module_info/0 already exported). - Attribut är data; endast
on_load,nifsoch-compile-alternativ som inte är neutrala avvisas som beteendeändrande. - De genererade funktionerna har ingen källkodsrad (OTP ger dem ingen): inga
felsökningsplatser, källkommentarer i IR eller poster i
--print-types.module_info/0innehåller samma literaler sommodule_info/1, så den gör inga anrop. - Belägg:
executables_module_info(OTP:s stdout; härledda värden efter form), fallenmodule_info_defined,exports_repeateditests/fixtures/lint.
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.
- Varje klausul börjar från en tom miljö. Huvudkandidater samlar preliminära definitioner; guards läser dem; kroppen ser dem endast vid framgång.
_binder ingenting;_Nameär en vanlig variabel. Ett upprepat namn är ett villkor om exakt likhet.- Matchningar i kroppen besöker högerledet före vänsterledet; kedjor går från
höger till vänster. Ett mönster inom parentes
P1 = P2är ett alias, inte en sekvens. - Syskonuttryck läser samma inkommande namn; deras definitioner exporteras efter
hela uttrycket.
{X = 1, X}är en obunden läsning;{X = 4, X = 3}är tillåtet och misslyckas vid körning. - Definitioner i högerledet av
andalso/orelseeller inuticatch Exprär osäkra efteråt (OTP:svtunsafe); namn som bundits före encatchförblir användbara. Varje namn som binds inuti etttryär osäkert efteråt;of-klausuler ser kroppens namn, catch-klausuler ser dem som osäkra, och after-kroppen ser varje namn som bundits tidigare i try som osäkert. En catch-klausuls stackvariabel måste vara ny (OTP:sstacktrace_bound) och dess guard får inte läsa den (stacktrace_guard). Enmaybeexporterar ingenting: varje?=binder för de följande uttrycken i kroppen,else-klausuler ser kroppens namn som osäkra och varje namn som binds inuti är osäkert efteråt. Guards publicerar aldrig bindningar; matchningar i guards är fel även när de är onåbara. - Map-nycklar läser endast inkommande bindningar; binärstorlekar läser också tidigare segment i samma binary (se mönster).
- Det testade uttrycket (scrutinee) i ett
casebinder i den omgivande räckvidden. Varje klausul börjar från den räckvidden; dess mönsterdefinitioner är preliminära fram till guarden, som bara läser. Namn som binds av varje klausul exporteras med en identitet (senare klausuler återanvänder den identitet som en tidigare klausul gav namnet); namn som binds av endast vissa klausuler, eller är osäkra i någon, är osäkra efteråt. Exporter förenas konservativt som i OTP:serl_lint(icrt_export); OTP:s varning när ett senare mönster matchar ett exporterat namn emitteras inte.if-klausuler följer samma regler med en guard och inget mönster. begin/endär en sekvens i den omgivande räckvidden.- Obundna, osäkra och jokerteckenläsningar är fel med position. Meddelandena
behåller kompilatorns formulering (
unbound variable X,unsafe variable X); bindningskorpusen kontrollerar var och en mot OTP:s klassunbound_var/unsafe_var. - En comprehension evaluerar varje kvalificerare i ordning i den räckvidd som de föregående lämnar. Generatormönster binder nya namn som skuggar yttre (läsningar inuti mönstret föredrar namn som det redan har bundit); en zip-grupp binder alla sina mönster tillsammans. Mallar läser som syskon. Räckvidden efter comprehensionen är den som gällde före den: dess namn är obundna där.
- Varje klausul i en anonym fun börjar från räckvidden vid funen: huvudnamn är
nya och skuggar yttre, guards läser dem, och ingenting som binds inuti syns
efter funen. Varje yttre definition som läses inuti registreras som en fångst
(
Function::captures, definitionsordning). En namngiven funs namn är ytterligare en definition som varje klausul börjar med (Function::fun_names); den fångas aldrig. Variablerna ifun M:F/Aär läsningar av fun-uttrycket (Function::fun_operands).
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:
- Lokala alias får skugga inbyggda typnamn. Fjärrtyper i batchen måste vara
exporterade. Saknade typer, dubbletter, felformad metadata, typvariabler som
förekommer en gång, ogiltiga gränser och specs för saknade funktioner är fel.
Otillgängliga externa typer ger en varning och blir
term(). - Varje alias eller överlagring har sin egen variabelräckvidd; upprepade formella parametrar följer OTP:s substitution med det sista argumentet.
- Rekursiva alias förblir ändliga namngivna referenser; opaka kroppar expanderas endast i sin modul; nominella namn behålls överallt, och endast specifikationskontrollen läser en nominell typs definition, inuti dess modul.
- Konstantevaluering är exakt, begränsad till 10 000 decimala siffror per värde.
Inferens
Inferensen är skild från deklarerade typer och litar aldrig på specs.
-
Exporterade indata, och indata till funktioner som
fun F/Anamnger, är godtyckliga termer. En funktion som endast nås genom direkta anrop inom batchen (varken exporterad eller namngiven av en fun) tar föreningen (join) av argumentfakta från sina anropsplatser som indata (semantic/types/inference_inputs), matchade av varje klausuls huvudmönster; rekursiva anrop räknas också. Indata börjar pånone()(en funktion som ingenting anropar körs aldrig och härleds tillnone()) och växer under passen över batchen. Varje indata klättrar i en trappa efter hur ofta den ändrats: de första 4 ändringarna förenar, de nästa 2 breddar som rekursiva resultat, de nästa 2 ersätter dessutom behållare inuti en behållare medterm(), och ytterligare en ändring gerterm()(rapporterat som breddat). Indata som inte har stabiliserats efter 63 pass blirterm()(rapporterade som breddade). En rekursiv komponent är kostsam när en lösning förbrukar mer än 2^17 gitterföreningar (nästlade räknas) och mer än 1/16 av det största antalet föreningar som ett pass hittills förbrukat; när medlemmarnas indata ändras igen blir de genastterm()(rapporterade som breddade), så att senare lösningar inte längre följer anroparnas skiftande indata. -
Pass efter det första går endast igenom komponenter för vilka något de läser har ändrats (
semantic/types/inference_incremental): en komponent behåller sina sammanfattningar och uttrycksfakta så länge medlemmarnas indata, varje sammanfattning den läst (anrop,fun F/A,apply) och indata till anropade funktioner som den evaluerat igen är oförändrade, och budgetarna för pass och omanalys skulle ge samma svar (omanalysbudgeten är lika förbrukad som vid början av dess senaste genomgång, eller skulle återigen bevilja eller neka allt); dess arbete räknas fortfarande mot passets budget. Resultaten är desamma som om varje komponent gås igenom. Det sista passet medterm()som indata går igenom alla. Efter ett pass förenas endast indata till funktioner som gåtts igenom i det eller anropats av en funktion som gåtts igenom i det på nytt från sina anrop; övriga funktioners argumentfakta och indata är som vid den senaste föreningen. -
Literaler har exakta fakta: heltal av alla storlekar och tecken är singletoner, atomer singletonatomer, flyttal
float(), strängar icke-tomma listor av sina tecken ([]för""), och-/+av ett literalt tal behåller dess faktum. Tupler behåller sina elements fakta; maps byggda med konstanta nycklar (ett faktum med ett enda värde: ett heltal, en atom,[], eller en tupel eller map av sådana) behåller varje nyckels värdefaktum, där en nyckel som anges två gånger behåller sitt sista värde, och varje annan nyckel germap(). En bitstring-konstruktion räknar sin storlek: literala, storleksatta och UTF-kodade literala tecken exakt, ett UTF-segment av ett annat värde efter sin kodning (UTF-8: 8 till 32 bitar i byte), och ettbinary/bitstring-segment efter sitt värdes faktum eller en godtycklig multipel av sin enhet (<<1, Rest/binary>>ärnonempty_binary()). En operand som aldrig producerar ett värde (none()) gör konstruktionen tillnone(). -
Operatorer och inbyggda funktioner (
semantic/types/inference_operators) beräknar sitt resultat från sina operanders fakta. Heltalssingletoner viks exakt upp till 4 096 bitar per operand och resultat (bignums inkluderade; ett större blirinteger()); andra heltal använder intervallaritmetik för+,-,*av icke-negativa intervall,bandmed en icke-negativ operand (X band 15är0..15),remmed en begränsad divisor (X rem 10är-9..9),bnot, unärt-ochabs/1; en flyttalsoperand gerfloat(), en okändnumber(), och/är alltidfloat(). Jämförelser viks när operandernas värden bara kan jämföras på ett sätt (singletonatomer eller -heltal, disjunkta heltalsintervall, tal mot icke-tal) och är annarsboolean();and/or/xor/notochandalso/orelsekombinerar de sanningsvärden som deras operander kan ha (den högra operanden tillandalsoär resultatet när den vänstra ärtrue). En tabell ger varje inbyggd bryggfunktion dess resultat:pid()förself/0ochspawn/1,3,reference(),port(),non_neg_integer()för storlekar,string(),nonempty_string(),binary(),list(),tuple(),atom(),boolean()för typtester,true/okför inbyggda funktioner med sidoeffekter, meddelandet för!ochsend/2;trunc/1och liknande behåller heltalsoperander; okända inbyggda funktioner ärterm(). En operation som alltid kastar (1 + a,1 div 0,error/1,exit/1,throw/1,halt/0,1) ärnone(), ocherlang:raise/3returnerar endastbadarg. Vikning ändrar aldrig den genererade koden: overflow och fel förblir utfall vid körning. -
En kropp vars uttryck aldrig slutförs (
none()) ärnone(); detsamma gäller ett anrop med ett sådant argument. Kastande vägar bidrar inte till en förening, så en funktion som alltid kastar härleds tillnone(). -
Identitets-/projektionsfunktioner behåller exakta argumentrelationer, som propageras genom nästlade lokala anrop och fjärranrop med nya variabler per anrop.
-
Behållare (
semantic/types/inference_containers): en lista[E1, ..., En | T]förenar sina element framför svansens faktum (en äkta lista när svansen är det,nonempty_improper_list(H, T)när den inte är någon lista,nonempty_maybe_improper_list()när den är okänd eller kan vara oäkta: en cell är aldrig tom, även i ett mönster som[H | _]);++,--,hd/1,tl/1,element/2,setelement/3,tuple_to_list/1ochmap_get/2läser och bygger om elementfakta medlem för medlem i en union, där en medlem som skulle kasta inte bidrar med något. En map-uppdatering behåller exakta nycklar (:=av en saknad nyckel tar bort den medlemmen) och germap()för en okänd map. Tupel-records är tupler: konstruktion fyller i standardvärden (undefinedom inget finns), åtkomst och uppdatering läser och sätter fältet i de matchande tuplerna, och#r.fär fältets index. En list comprehension är en möjligen tom lista av mallarnas fakta, en binary comprehension ett godtyckligt antal kopior av mallens storlek, en map comprehensionmap(). -
Funs (
semantic/types/inference_funs):fun F/Aär en fun av sin aritet som returnerar funktionens härledda resultat (en inbyggd funktions, eller med variabel aritetfun()),fun M:F/Aen som returnerarterm(), och en anonym fun en som returnerar sina klausulers förenade resultat, fångade värden inkluderade. Ett anrop av ett värde förenar resultaten av de funs av den ariteten som dess argument väljer (term()för en okänd fun,none()när ingen medlem kan anropas så). En anonym fun som binds i sin helhet till en variabel och anropas via den evalueras igen för det anropet med sina mönster matchande argumentens fakta, högst 4 sådana anrop djupt, och dess första evaluerings fakta återställs efteråt (Double = fun(Y) -> Y * 2 end, Double(3)är 6).apply(F, Args)(ävenerlang:apply/2) är ett anrop avFmed de argument somArgsinnehåller: elementen i en literal äkta lista, annars de i dess faktum ([], en lista med kända positioner, eller så många kopior av en äkta listas element somF:s enda aritet tar); en bunden anonym fun evalueras igen som ovan (apply(fun kind/1, [1])väljerkind(1)).apply(M, F, Args)varsMochFär kända atomer som namnger en exporterad funktion i batchen läser den funktionens funktionstyper somfun F/A. Argument av okänd längd, andra mål ochM:F(Args)med variabla namn förblirterm(). Eftersomfun F/Aochapply/3kan namnge en funktion som härleds senare, upprepas passen över batchen också tills varje sådan läsning har sett sin funktions slutliga resultat; annars ger ett sista pass dem resultatetterm(). -
Tilldelningar av hela värden i kroppen och alias kopierar högerledets faktum (
Y = 42, Z = Y, id(Z)härleds till 42); mönster för tupler, listor, maps och tupel-records ger sina variabler fakta för de delar de matchar, i matchningar i kroppen,case-klausuler (från det testade uttrycket), etttry:sof-klausuler (från kroppens värde) och generatorer (från indatans element eller map-nycklar och värden). Obevisade värden förblirterm()utan relationer. -
Inskränkning (narrowing;
semantic/types/inference_narrowing,inference_scopes,meet): inuti en funktions-,case-,receive- eller fun-klausul möter (meet) varje mönster det värde det matchar (en literal, tupel, lista, tupel-record, map eller bitstring-form; en bunden variabels faktum), en variabel som är det testade uttrycket i ettcaseinskränks med det, och guarden inskränker de variabler den testar. Typtester (is_atom/1tillatom(),is_boolean/1,is_integer/1,is_float/1,is_number/1,is_binary/1,is_bitstring/1,is_list/1tillmaybe_improper_list(),is_tuple/1,is_map/1,is_function/1,2,is_pid/1,is_port/1,is_reference/1,is_record/2,3till recordens tupel,is_map_key/2dess map tillmap(), och de gamla guard-namnen) möter sitt arguments faktum; jämförelser med heltalskonstanter (<,=<,>,>=,==,=:=, på vilken sida som helst, vikta konstanter inkluderade) inskränker ett värde som redan bevisats vara ett heltal till ett intervall, där gränserna i ett test ackumuleras (10 >= X, X >= 0är0..10); två bevisade heltal som jämförs inskränker varandra genom sina gränser (X > YmedYi0..5görXminst 1), och/=,=/=med en konstant flyttar en intervallgräns som är lika med den inåt. Ett värde som kan vara ett flyttal eller en annan term inskränks inte. En konjunktion tillämpar varje test i tur och ordning, en disjunktion förenar vad varje alternativ bevisar,notoch falska tester bevisar ingenting. En klausul efter en vars mönster alla är enkla variabler och vars hela guard var ett enda typtest ser det värdet utan den testade kategorin; efter en enda jämförelse med en heltalskonstant ser samma värde (en enkel variabel i den här klausulen) som bevisats vara ett heltal jämförelsen som falsk (f(N) when N >= 0 -> ...; f(N) when is_integer(N) -> ...: den andra klausulen serneg_integer()). Den högra operanden tillandalso,true-klausulen icase Test ofoch det som följer efter ett filter i en comprehension ser testet som sant. Ett tomt möte gör klausulen omöjlig: den bidrar inte till resultatet. Inskränkta fakta gäller endast inuti sin klausul eller operand;catch, den högra operanden tillorelseoch varje klausul återställer fakta från före dem. Efter ettcase,if,receive,tryellermaybeär en variabels faktum föreningen av dess fakta i slutet av varje klausul som slutförs, såcase X of forever -> ...; N when is_integer(N), N >= 0 -> ... endlämnarXsomnon_neg_integer() | forever. -
tryochmaybe: etttryär föreningen av dessof-klausulers värden (dess kropps värde utanof) och dess catch-klausulers värden;after-kroppen bidrar inte med något.of-klausuler börjar från fakta i slutet av kroppen och matchar dess värde somcase-klausuler (en omöjlig klausul bidrar inte); catch-klausuler ochafter-kroppen börjar från fakta företry, där ett klassmönster matcharerror | exit | throw. Enmaybeär föreningen av dess kropps värde, desselse-klausulers värden och, utanelse, de värden som dess?=-matchningar kan misslyckas på: det matchade värdets faktum utan mönstrets form när mönstret matchar hela dess form (nya variabler som används en gång, literala atomer och heltal,[], tupler av dem), annars hela faktumet. Varje?=-mönster möter sitt värde och publicerar sina variabler för resten av kroppen; ett?=som aldrig kan matcha stoppar kroppen.else-klausuler börjar från fakta föremaybeoch matchar de förenade misslyckade värdena somcase-klausuler. Fakta efter etttryförenar dem i slutet av dess kropp (utanof) och i varje slutförd klausul; efter enmaybedem i slutet av dess kropp, i varje slutfördelse-klausul och, utanelse, dem före den. -
Användningar (
semantic/types/inference_uses): en operation som kastar om inte en operand har en viss typ bevisar den typen för variabeln den läste, efter att operationen returnerat: aritmetik och unärt-/+ettnumber(),div,rem, bitoperatorer ochbnotettinteger(),and/or/xor,notoch den vänstra operanden tillandalso/orelseettboolean(),++och--enlist(), ett anropat värde en fun av den ariteten, ett variabelt modul- eller funktionsnamn enatom(), en map-uppdateringmap(), en åtkomst eller uppdatering av en tupel-record recordens tupel, ett binärsegment dess typ (<<X:8>>:integer()) och dess storlek ettnon_neg_integer(), och en rad per argumentkontroll i en inbyggd bryggfunktion (hd/1,tl/1nonempty_maybe_improper_list(),length/1list(),element/2pos_integer()ochtuple(),map_get/2ochis_map_key/2map(),atom_to_list/1atom(), ...). Operationen behåller sin kontroll vid körning. Namn som är bundna till samma värde (Y = X, ett variabeltcase-mönster på ett testat uttryck som är en variabel) inskränks tillsammans. -
Ingångs- och framgångsdomäner: varje arguments ingångsdomän är föreningen över de möjliga funktionsklausulerna av dess faktum efter huvud och guard (en enkel variabels inskränkta faktum, annars mönstrets); dess framgångsdomän föreningen över de klausuler som slutförs av dess faktum vid deras normala retur.
--print-typesvisar framgångsdomänen som indata (bounded(1..10) -> 1..10,inc(X) -> X + 1sominc(number()) -> number()), eller ingångsdomänen för en funktion som aldrig returnerar. Ett anrop som returnerar inskränker sina variabla argument till den anropades domän, utom inom en rekursiv komponent som fortfarande löses. -
Klausulresultat förenas konservativt: en projektion överlever endast om varje klausul returnerar samma argument. Ett
caseellerifförenar sina klausulresultat på samma sätt. -
Funktionstyper (
semantic/types/function_types): utöver unionssammanfattningen ovan behåller en funktion en funktionstyp per möjlig klausul, likt överlagringarna i en-spec: argumentens fakta efter huvud och guard (en enkel variabels inskränkta faktum, annars mönstrets) och klausulens resultat (none()för en klausul som alltid kastar; omöjliga klausuler bidrar inte). En klausul vars resultat är ett beroende faktum av dess argument (ettcaseellerifsom avslutar den, eller en variabel bunden till ett sådant, nästlat på vilket djup som helst) delas upp i en funktionstyp per klausul i det faktumet, där dess parametrars fakta möter de argument de namnger. Funktionstyper med lika indata slås ihop (deras resultat förenas); över 32 slås de sista ihop till en, med indata och resultat förenade. Rekursiva komponenter itererar dem tillsammans med resultaten, där varje omgång förenar (och sedan breddar) varje typs resultat; en komponent som inte konvergerar behåller endast sina unionssammanfattningar. En anonym funs faktum behåller en funktionstyp per möjlig klausul (dess mönster och guard över godtyckligt argument),fun F/Afunktionstyperna förF/A; funs som förenas behåller sina funktionstyper endast när de är lika, annars förenas de som en fun av sina förenade resultat med godtyckliga indata (ett möte med en fun med godtyckliga indata, som användningenF(A)gör, behåller dem). Specialisering, domäner och specifikationskontroller läser unionssammanfattningen. -
Anrop väljer funktionstyper: ett anrop av en funktion med funktionstyper läser, i ordning, varje typ vars indata varje argumentfaktum möter, och stannar efter en exakt typ (mönster av nya variabler som används en gång, literala atomer och heltal,
[]och tupler av dem; ingen guard eller endasttrueoch typtester av enkla argumentvariabler; för en gren, ett argument som det testade uttrycket icase) vars indata rymmer argumenten: ingen senare klausul kan nås. Dess resultat är föreningen av de valda typernas resultat, där ett resultat som är lika med ett argument är det argumentets faktum inom typens resultat; om ingen väljs blir anropetnone()(det kan bara kastafunction_clause). Okända argument väljer varje typ, unionen; en funktion utan funktionstyper (budget, ej konvergerad komponent) använder sin unionssammanfattning. Efter att ett anrop returnerat inskränks dess variabla argument till framgångsdomänen och till de valda typernas förenade indata. Anrop av fun-värden väljer fun-faktumets funktionstyper på samma sätt, och en anonym fun som evalueras igen för ett anrop (se Funs) går in i, testar guards för och lämnar sina klausuler som ettcase, så en klausul som dess argument inte kan matcha bidrar inte (F = fun(1) -> one; (_) -> other end, F(2)ärother). Rekursiva komponenter konvergerar för resultat och funktionstyper. -
Anrop evaluerar den anropade funktionen igen: när ett anrops argumentfakta ligger inom den anropades indata och är snävare i något, evalueras den anropades kropp igen med dem som indata, som en bunden anonym fun, och anropets resultat är de valda funktionstypernas resultat mött med den evalueringens (
two_callers() -> {add_one(10), add_one(20)}är{11, 21}); anropets argument inskränks också till den evalueringens framgångsdomän. Den anropades sammanfattning, funktionstyper och registrerade uttrycksfakta ändras aldrig (specialisering läser endast dessa). Budgetar: 4 nästlade evalueringar, anropade funktioner med högst 256 uttryck, 4 096 arbetsenheter per anrop ur en pool på 262 144 per pass över batchen (skild från batchens egen budget); rekursiva komponenter evalueras aldrig igen. Utöver en budget behåller anropet de valda funktionstypernas resultat. -
En
receiveär föreningen av dess klausuler och dessafter-kropp, som en tidsgränsinfinityaldrig kör. -
En rekursiv komponent börjar varje medlems resultat på
none()i sin första lösning och i senare pass från sammanfattningarna som dess senaste lösning slutade med: där ersätter den första genomgången av varje medlem dess sammanfattning med vad genomgången hittade (den tidigare sammanfattningen tjänar bara som antagande för anrop före det), så att inaktuella funktionstyper aldrig blir kvar. Den härleder medlemmar på nytt tills inget resultat ändras; varje omgång förenar det nya resultatet med det föregående (breddar det efter de första 8 omgångarna, inferensdomän), och ett väntande rekursivt anrop bidrar inte till en förening. En funktion som aldrig kan returnera förblirnone(). En komponent som inte har konvergerat efter 8 omgångar plus 4 per medlem, eller vars omgångar har förbrukat mer än en åttondel av passets budget, breddar varje medlem tillterm()(rapporterat som breddat, som vid budgetuttömning) och en sista omgång beräknar uttrycksfakta på nytt; tidigare omgångars uttrycksfakta kastas, så endast fakta från de slutliga antagandena återstår. En sådan komponent kommer man ihåg: senare pass ger dess medlemmarterm()direkt och kör endast den sista omgången, i stället för att iterera till taket igen. Det pass som breddar varje indata tillterm()glömmer de ihågkomna komponenterna, så att var och en får ytterligare en lösning med dessa indata. -
Omgångar krymper aldrig en medlems sammanfattning: dess funktionstyper förenas med föregående omgångs typ för typ (resultat förenas, ändrade indata förenas och typen är inte längre exakt), eller, när antalet typer ändrats, slås båda omgångarnas typer ihop. Som ett skyddsnät behåller varje medlem sina 4 senaste sammanfattningar, och en som återvänder till någon av dem blir
term()utan funktionstyper under resten av lösningen, vilket låter de andra medlemmarna konvergera. -
En omgång går endast igenom medlemmar som läser en sammanfattning som ändrats sedan deras senaste genomgång: varje genomgång noterar de sammanfattningar den läser (anrop, anropsresultat,
fun F/A), en ändrad sammanfattning markerar sina läsare, och en medlem som inte markerats behåller sina uttrycksfakta. Den första omgången går igenom varje medlem. En medlem behåller sin senaste genomgång mellan lösningar: en lösning återanvänder den genomgångens resultat, med dess uttrycksfakta och arbete, så länge medlemmarna den läst har samma resultat och funktionstyper, och allt annat den läst är oförändrat och skulle ge samma svar, som för en behållen komponent i inkrementella pass. Resultaten är desamma som om den gås igenom. -
En delad arbetsbudget begränsar inferensen; uttömning förlorar precision och faller tillbaka på generisk kod, avvisar aldrig ett program.
-
Specifikationer som motsäger inferensen diagnostiseras (
semantic/types/contracts), men blir aldrig fakta, så programmet beter sig som dess kod säger i vilket fall som helst. En deklarerad typ blir de fakta den rymmer (eller fler): inbyggda typer efter namn (byte()är0..255,timeout()ärnon_neg_integer() | infinity,iodata()ochiolist()listor av godtycklig form), alias efter sin definition, opaka och nominella typer efter sin definition inuti sin modul och som godtycklig term utanför den, fjärrtyper via batchen, typvariabler via sinawhen-gränser (obegränsad: godtycklig term), maps och records efter sin kategori. Endast ett disjunkt par är en motsägelse, eftersom härledda fakta kan rymma fler värden än koden producerar:- en ingångsdomän för ett argument som inte delar något värde med specen är ett fel;
- ett härlett resultat som inte delar något (om det inte är okänt eller
none(), som passar vilket resultat som helst), eller ett anrop vars argumentfakta varje överlagring avvisar, är en varning (anropet kan ligga på en väg som aldrig körs); none()/no_return()godtar endast en funktion som aldrig returnerar.
Diagnostiken namnger funktionen, den deklarerade typen som den skrevs och den härledda.
-callback-specifikationer kontrolleras inte. OTP:s kompilator kontrollerar inte specifikationer (skillnader).
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.
- Parametrar: de variabler bundna före konstruktionen som dess testade uttryck
(det sista uttrycket i ett
try:s kropp) och guards läser, i källkodsordning (variabler som dess klausulmönster binder är nya i varje klausul, inte parametrar); högst 4, en senares inskränkning glöms bort. Varje möjlig klausul behåller parametrarnas fakta efter sitt mönster och sin guard samt sitt värde (none()för en klausul som aldrig slutförs); den är exakt, som en funktionstyp vid val av anrop, när ettcaseellertrymatchar en parameter eller en tupel av parametrar med ett exakt mönster och en exakt guard, eller ettifhar en exakt guard. - Ett testat uttryck som är en tupel av variabler inskränker varje variabel till
dess element i det matchade värdet, och en variabel i ett tupelmönster binds
till samma värde som det testade uttryckets element på dess position
(
case {X, Y} of {A, b} when is_integer(A) -> ...inskränkerXochY). - Ett
try ... ofberor på sinaof-klausuler; dess catch-klausuler kan följa på vilket värde som helst av dess kropp, så deras förenade värden förenas in i varje klausuls värde (try X of 1 -> one; _ -> other catch _:_ -> error endär$try_of_operator(X :: 1) -> error | one; (X :: _) -> error | other). receiveochmaybe ... elseförblir föreningen av sina klausuler: deras klausuler matchar ett meddelande eller ett misslyckat?=-värde som ingenting utanför dem kan namnge, så ingen läsning kunde välja bland dem.- En klausul vars värde i sig är beroende (ett nästlat
caseellerif, eller en läsning av en beroende variabel) bidrar med en funktionstyp per klausul i den över unionen av båda parameterlistorna, med indata mötta. Typer som aldrig kan nås (en indatanone(), eller inom en tidigare exakt typs indata) tas bort, liksom parametrar vars fakta är desamma i varje typ; lika indata slås ihop och över 32 typer slås de sista ihop, som för funktionstyper. En konstruktion vars återstående typer alla ger samma värde, eller som inte inskränker någon parameter, är sin vanliga förening. - Varje annan konsument läser det raderade faktumet, föreningen av klausulernas värden. Fakta som lämnar den genomgångna funktionen (resultat, funktionstyper, fun-fakta) raderas; föreningar behåller ett beroende endast när båda sidor har samma.
- En variabel bunden till ett beroende värde behåller det. Att läsa variabeln
väljer de klausuler som dess parametrars aktuella fakta går in i (i ordningen
för val av anrop, en exakt klausul vars indata rymmer dem stoppar) och vars
värden möter variabelns eget inskränkta faktum, och läsningens faktum är
föreningen av deras värden
(
Y = case X of 1 -> one; _ -> other end, case X of 1 -> Y endläserone). - En variabel som varje klausul i ett
caseellerifbinder är föreningen av dess fakta i slutet av de klausuler som slutförs, beroende av konstruktionens parametrar som dess värde:case X of 1 -> Y = 5; _ -> Y = 6 end, Yhärleds till(1) -> 5; (_) -> 6. Samma förening gäller efterreceive,tryochmaybe, utan beroende. - Operatorer, inbyggda funktioner, konstruktioner av tupler, listor, maps och
records, samt anrop av funktioner i batchen (deras funktionstyper valda som
vid val av anrop, argumenten inte inskränkta igen) med beroende operander
evalueras en gång per kombination av operandernas klausuler, med de
klausulernas värden i stället för operandernas fakta och deras indata mötta:
läsningar av en variabel tar samma klausul, andra operander kombineras, högst
16 kombinationer (därutöver är användningen den vanliga föreningen).
R + 1medR = case X of a -> 1; b -> 2 endhärleds till(a) -> 2; (b) -> 3. - Att inskränka en beroende variabel (ett mönster, en guard, en användning)
inskränker också dess parametrar till föreningen av indata för de nådda
klausuler vars värden möter det inskränkta faktumet, och deras parametrar i
sin tur, 4 nivåer djupt: efter
R = case X of a -> 1; b -> 2 endläsercase R of 1 -> X endXsoma. - En funktionsklausuls beroende resultat delar upp den (se Funktionstyper):
parametrar som namnger ett argument, eller ett namn bundet till dess värde,
möter dess indata; övriga tas bort (en sådan typ är exakt endast om deras
indata är en godtycklig term).
nested_case(X, L)medcase X of 1 -> case L of spanish -> uno; _ -> one end; _ -> other endhärleds till(1, spanish) -> uno; (1, _) -> one; (_, _) -> other. --print-typesnoterar ett beroende värde som en funktionstyp namngiven efter sin konstruktion, med varje indata efter sin parameters namn:case X of 1 -> one; _ -> other endslutar iend. % $case_of_operator(X :: 1) -> one; (X :: _) -> other; ettifskriver ut$if_operator, etttry ... of$try_of_operator.
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.
| Faktum | Utskrift | Förening | Budget och breddning |
|---|---|---|---|
| Ingenting | none() | Identitet | En funktion som aldrig returnerar förblir none() |
| Vad som helst | term() | Absorberar varje faktum | dynamic() och any() är term() |
| Heltal | 42, 1 | 3 | 7 | Union av singletoner | Fler än 8 singletoner blir sitt intervall |
| Heltalsintervall | 1..10, 0..255 | Minsta intervall som rymmer båda | En 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 heltal | pos_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 | — |
| Flyttal | float() | — | — |
| Tal | number() | Ett intervall eller en kategori av heltal förenat med float() | Singletonheltal med float() förblir 1 | float() |
| Atomer | ok, error | ok, boolean() | Union av singletoner; exakt false och true skrivs ut som boolean() | Fler än 8 singletoner blir atom() |
| Identifierare | pid(), 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 medlemmar | Fler ä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 listor | En 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 |
| Funs | fun((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 enhet | Bas och enhet 0/8, 8/8, 0/1, 1/1 skrivs ut som binary(), nonempty_binary(), bitstring(), nonempty_bitstring() |
- En union behåller en medlem per förenad form, i Erlangs termordning för deras
värden: tal, atomer,
reference(), funs,port(),pid(), tupler, maps,[], listor, bitstrings, sedan deklarerade namngivna typer; fler än 8 medlemmar blirterm(). - Behållare nästlas högst 4 nivåer; ett faktum längre in blir
term(). - En union tar bort en exakt tupelmedlem som en annan exakt tupel av samma storlek rymmer (av två som rymmer varandra stannar den tidigare), så en mängd värden har en union och rekursioner över träd stabiliseras.
- Rekursiva komponenter förenar i 8 omgångar (cykler av upp till 8 funktioner konvergerar exakt) och breddar sedan (inferens).
- Inskränkning (
Lattice::meet) är mötet av fakta, de värden som båda rymmer (eller fler, mennone()endast när de inte delar något). Ett tomt möte innebär att vägen inte kan köras. - Fakta är noder i en graf som aldrig ändras, så grafen behåller svaret från
varje förening av två fakta, möte och inklusionstest (
within) under resten av batchen: nästan alla upprepas. Ett behållet svar räknar fortfarande de föreningar det utförde för kostnadsstyrd uppgivning, så att resultaten inte beror på det; ett svar som beräknats medan nodbudgeten breddade ett faktum behålls inte.
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.
- Vad en moduls fakta beror på. Exporterade funktioner och funktioner som en
fun F/Anamnger börjar frånterm(); varje annan funktions indata kommer från dess anropare, som finns i samma modul; ett faktum namnger en funktion i en annan modul endast via en atom: ett fjärranrop,fun M:F/A, ellerapply/2,3på ett atomfaktum (och läser en annan moduls record-layout endast via ett native record som den kvalificerar eller importerar), och varje atomfaktum kommer från en atom som modulen stavar eller från ett faktum i en modul som den når. Alltså beror en moduls fakta endast på dess egen kod och på koden i de batchmoduler som dess atomer namnger, transitivt. Nyckeln för dess behållna resultat täcker exakt det: identiteten förclau, modulens fingeravtryck (dess källkod, varje fil den inkluderar, förbehandlingsalternativ och ett svar från en parse transform), fördefinierade former, escript- och biblioteksstatus, och detsamma för varje batchmodul som dess atomer når. En modul som nås på det sättet och som saknar fingeravtryck gör modulen omöjlig att cacha. - Livskraft. Exporters livskraft tar endast bort funktioner, och en
borttagen anropare inskränker endast indata till de funktioner den anropade:
fakta som beräknats med fler levande funktioner gäller fortfarande. Behållna
resultat återanvänds när de rymmer varje funktion som är levande nu
(
results do not fitannars); funktioner som utelämnats får ingenting. - Blandning. Behållna och färska resultat kan mötas i en batch: varje moduls fakta gäller för varje körning av programmet, vad en annan moduls fakta än säger, så en färsk modul som läser en behållen sammanfattning (eller tvärtom) förblir sund. En rekursiv komponent med båda sorternas medlemmar gås igenom i sin helhet.
- Precision. Återanvända resultat är de från den kompilering som behöll dem. En färsk moduls resultat kan fortfarande skilja sig från en kompilering utan cachen: passets budget betalar inte längre för återanvända moduler, och grafidentiteter, efter vilka några unionsmedlemmar ordnas, numreras annorlunda.
--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
- En
%% module-rad namnger modulen, dess källa, projektmålet och huruvida deklarerade och härledda typer slutfördes eller breddades av en gräns. - Deklarationer (
-type,-spec,-callback, records) visas som de skrevs. - Ovanför varje funktion anger
%% declared: f(Inputs) -> Resultvarje överlagring i dess-specsom den lösts (med sinawhen-villkor), och%% inferred: f(Inputs) -> Resultdet som inferensen hittade, så att de två kan jämföras. Indata är varje arguments framgångsdomän (frånterm()för exporterade funktioner och de somfun F/Anamnger, från anroparnas förenade argument för andra funktioner). En funktion med flera funktionstyper skriver ut en signatur per typ, var och en med sina egna indata:f(integer()) -> integer(); (atom()) -> string()(semantic::types::function_source). En-specstannar hos funktionen direkt efter den, åtskild från andra former med en tom rad. - Varje rads yttersta uttryck (ett uttryck i kroppen, eller det testade
uttrycket i ett
case) vars faktum säger mer änterm()noteras med en avslutande kommentar% Typeefter radens skiljetecken (en känd typ döljer en argumentrelation, som bara visas för ett värde som inte är känt som något annat): högst en notering per rad, ingen för uttrycken som är nästlade i det, så att utdata förblir giltig Erlang. Ett uttryck i kroppen över flera rader har sin notering på sista raden (end, % ...). Literala termer (literaler, och tupler, listor, konstruerade maps och bitstrings av literaler) noteras inte; en matchning visar sitt värde. Ett beroende värde skrivs ut som en funktionstyp för sin konstruktion (beroende fakta). - Ett värde som inferensen bevisat vara lika med ett av funktionens argument,
och som inte är känt som något mer, skrivs ut som det argumentets namn, en
typvariabel: den variabel som den första klausulen som binder hela argumentet
ger det, annars
_argumentN(1-baserat, även när ett tidigare argument tog namnet). Argumentets indata visar samma namn när det är en godtycklig term:second(_, Y) -> Y,keep(Acc, number()) -> Acc. En variabel visar endast sin typ, dess namn säger redan vilket argument den är.
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.
expect:är den signatur som--print-typesbör skriva ut på sin%% inferred:-rad;today:, som finns medan inferensen inte räcker till, är den som skrivs ut nu.- Kontrollen jämför utdata med
todaynär en sådan finns, annars medexpect; varje funktion i modulen behöver enexpect-rad. Entoday-rad som inferensen har kommit ikapp får kontrollen att misslyckas tills den tas bort. expectations.py <clau> <fixture> --recordskriver omtoday-raderna från aktuell utdata (en åtgärd för underhållare: granska diffen).values.erltäcker literala och konstruerade värden, resultat av operatorer och inbyggda funktioner, behållare och deras delar, funs och anrop av dem, lokala indata från anropare, heltalsföreningar och intervall, argumentrelationer och värden fråntry/maybe;narrowing.erltyptester, guards, mönster, jämförelseintervall, disjunktioner och inskränkningar som inte får läcka;base_types.erlen funktion per bastyp och inbyggd typ i typspråket, vars-specmåste kunna lösas och vars kropp producerar ett sådant värde;clauses.erlfunktionstyper, val vid anrop och evaluering per anrop inom och utöver sina budgetar;dependent.erlberoende fakta;trees.erlochparsers.erlrekursioner över träd och former från genererade parsrar;widening.erlräknare inuti behållare som breddas och stabiliseras.tests/fixtures/inference/contracts/innehåller specifikationskontrollerna (CTestinference_contracts).tests/fixtures/inference/budgets/walkers.erlär ett kompilatorpass i Erlangs vanliga form (ett stort tillståndsrecord som förs genom ömsesidigt rekursiva genomgångsfunktioner vars resultat är unioner av många records). CTestinference_walkers(tests/compiler/inference/walkers.py) håller dess pass, arbetssteg, gitterföreningar och genomgångsgruppens lösningar, omgångar och medlemsgenomgångar inom budgetar ungefär en fjärdedel över dagens antal, och kör programmet.
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
Clause