12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970797179727973797479757976797779787979798079817982798379847985798679877988798979907991799279937994799579967997799879998000800180028003800480058006800780088009801080118012801380148015801680178018801980208021802280238024802580268027802880298030803180328033803480358036803780388039804080418042804380448045804680478048804980508051805280538054805580568057805880598060806180628063806480658066806780688069807080718072807380748075807680778078807980808081808280838084808580868087808880898090809180928093809480958096809780988099810081018102810381048105810681078108810981108111811281138114811581168117811881198120812181228123812481258126812781288129813081318132813381348135813681378138813981408141814281438144814581468147814881498150815181528153815481558156815781588159816081618162816381648165816681678168816981708171817281738174817581768177817881798180818181828183818481858186818781888189819081918192819381948195819681978198819982008201820282038204820582068207820882098210821182128213821482158216821782188219822082218222822382248225822682278228822982308231823282338234823582368237823882398240824182428243824482458246824782488249825082518252825382548255825682578258825982608261826282638264826582668267826882698270827182728273827482758276827782788279828082818282828382848285828682878288828982908291829282938294829582968297829882998300830183028303830483058306830783088309831083118312831383148315831683178318831983208321832283238324832583268327832883298330833183328333833483358336833783388339834083418342834383448345834683478348834983508351835283538354835583568357835883598360836183628363836483658366836783688369837083718372837383748375837683778378837983808381838283838384838583868387838883898390839183928393839483958396839783988399840084018402840384048405840684078408840984108411841284138414841584168417841884198420842184228423842484258426842784288429843084318432843384348435843684378438843984408441844284438444844584468447844884498450845184528453845484558456845784588459846084618462846384648465846684678468846984708471847284738474847584768477847884798480848184828483848484858486848784888489849084918492849384948495849684978498849985008501850285038504850585068507850885098510851185128513851485158516851785188519852085218522852385248525852685278528852985308531853285338534853585368537853885398540854185428543854485458546854785488549855085518552855385548555855685578558855985608561856285638564856585668567856885698570857185728573857485758576857785788579858085818582858385848585858685878588858985908591859285938594859585968597859885998600860186028603860486058606860786088609861086118612861386148615861686178618861986208621862286238624862586268627862886298630863186328633863486358636863786388639864086418642864386448645864686478648864986508651865286538654865586568657865886598660866186628663866486658666866786688669867086718672867386748675867686778678867986808681868286838684868586868687868886898690869186928693869486958696869786988699870087018702870387048705870687078708870987108711871287138714871587168717871887198720872187228723872487258726872787288729873087318732873387348735873687378738873987408741874287438744874587468747874887498750875187528753875487558756875787588759876087618762876387648765876687678768876987708771877287738774877587768777877887798780878187828783878487858786878787888789879087918792879387948795879687978798879988008801880288038804880588068807880888098810881188128813881488158816881788188819882088218822882388248825882688278828882988308831883288338834883588368837883888398840884188428843884488458846884788488849885088518852885388548855885688578858885988608861886288638864886588668867886888698870887188728873887488758876887788788879888088818882888388848885888688878888888988908891889288938894889588968897889888998900890189028903890489058906890789088909891089118912891389148915891689178918891989208921892289238924892589268927892889298930893189328933893489358936893789388939894089418942894389448945894689478948894989508951895289538954895589568957895889598960896189628963896489658966896789688969897089718972897389748975897689778978897989808981898289838984898589868987898889898990899189928993899489958996899789988999900090019002900390049005900690079008900990109011901290139014901590169017901890199020902190229023902490259026902790289029903090319032903390349035903690379038903990409041904290439044904590469047904890499050905190529053905490559056905790589059906090619062906390649065906690679068906990709071907290739074907590769077907890799080908190829083908490859086908790889089909090919092909390949095909690979098909991009101910291039104910591069107910891099110911191129113911491159116911791189119912091219122912391249125912691279128912991309131913291339134913591369137913891399140914191429143914491459146914791489149915091519152915391549155915691579158915991609161916291639164916591669167916891699170917191729173917491759176917791789179918091819182918391849185918691879188918991909191919291939194919591969197919891999200920192029203920492059206920792089209921092119212921392149215921692179218921992209221922292239224922592269227922892299230923192329233923492359236923792389239924092419242924392449245924692479248924992509251925292539254925592569257925892599260926192629263926492659266926792689269927092719272927392749275927692779278927992809281928292839284928592869287928892899290929192929293929492959296929792989299930093019302930393049305930693079308930993109311931293139314931593169317931893199320932193229323932493259326932793289329933093319332933393349335933693379338933993409341934293439344934593469347934893499350935193529353935493559356935793589359936093619362936393649365936693679368936993709371937293739374937593769377937893799380938193829383938493859386938793889389939093919392939393949395939693979398939994009401940294039404940594069407940894099410941194129413941494159416941794189419942094219422942394249425942694279428942994309431943294339434943594369437943894399440944194429443944494459446944794489449945094519452945394549455945694579458945994609461946294639464946594669467946894699470947194729473947494759476947794789479948094819482948394849485948694879488948994909491949294939494949594969497949894999500950195029503950495059506950795089509951095119512951395149515951695179518951995209521952295239524952595269527952895299530953195329533953495359536953795389539954095419542954395449545954695479548954995509551955295539554955595569557955895599560956195629563956495659566956795689569957095719572957395749575957695779578957995809581958295839584958595869587958895899590959195929593959495959596959795989599960096019602960396049605960696079608960996109611961296139614961596169617961896199620962196229623962496259626962796289629963096319632963396349635963696379638963996409641964296439644964596469647964896499650965196529653965496559656965796589659966096619662966396649665966696679668966996709671967296739674967596769677967896799680968196829683968496859686968796889689969096919692969396949695969696979698969997009701970297039704970597069707970897099710971197129713971497159716971797189719972097219722972397249725972697279728972997309731973297339734973597369737973897399740974197429743974497459746974797489749975097519752975397549755975697579758975997609761976297639764976597669767976897699770977197729773977497759776977797789779978097819782978397849785978697879788978997909791979297939794979597969797979897999800980198029803980498059806980798089809981098119812981398149815981698179818981998209821982298239824982598269827982898299830983198329833983498359836983798389839984098419842984398449845984698479848984998509851985298539854985598569857985898599860986198629863986498659866986798689869987098719872987398749875987698779878987998809881988298839884988598869887988898899890989198929893989498959896989798989899990099019902990399049905990699079908990999109911991299139914991599169917991899199920992199229923992499259926992799289929993099319932993399349935993699379938993999409941994299439944994599469947994899499950995199529953995499559956995799589959996099619962996399649965996699679968996999709971997299739974997599769977997899799980998199829983998499859986998799889989999099919992999399949995999699979998999910000100011000210003100041000510006100071000810009100101001110012100131001410015100161001710018100191002010021100221002310024100251002610027100281002910030100311003210033100341003510036100371003810039100401004110042100431004410045100461004710048100491005010051100521005310054100551005610057100581005910060100611006210063100641006510066100671006810069100701007110072100731007410075100761007710078100791008010081100821008310084100851008610087100881008910090100911009210093100941009510096100971009810099101001010110102101031010410105101061010710108101091011010111101121011310114101151011610117101181011910120101211012210123101241012510126101271012810129101301013110132101331013410135101361013710138101391014010141101421014310144101451014610147101481014910150101511015210153101541015510156101571015810159101601016110162101631016410165101661016710168101691017010171101721017310174101751017610177101781017910180101811018210183101841018510186101871018810189101901019110192101931019410195101961019710198101991020010201102021020310204102051020610207102081020910210102111021210213102141021510216102171021810219102201022110222102231022410225102261022710228102291023010231102321023310234102351023610237102381023910240102411024210243102441024510246102471024810249102501025110252102531025410255102561025710258102591026010261102621026310264102651026610267102681026910270102711027210273102741027510276102771027810279102801028110282102831028410285102861028710288102891029010291102921029310294102951029610297102981029910300103011030210303103041030510306103071030810309103101031110312103131031410315103161031710318103191032010321103221032310324103251032610327103281032910330103311033210333103341033510336103371033810339103401034110342103431034410345103461034710348103491035010351103521035310354103551035610357103581035910360103611036210363103641036510366103671036810369103701037110372103731037410375103761037710378103791038010381103821038310384103851038610387103881038910390103911039210393103941039510396103971039810399104001040110402104031040410405104061040710408104091041010411104121041310414104151041610417104181041910420104211042210423104241042510426104271042810429104301043110432104331043410435104361043710438104391044010441104421044310444104451044610447104481044910450104511045210453104541045510456104571045810459104601046110462104631046410465104661046710468104691047010471104721047310474104751047610477104781047910480104811048210483104841048510486104871048810489104901049110492104931049410495104961049710498104991050010501105021050310504105051050610507105081050910510105111051210513105141051510516105171051810519105201052110522105231052410525105261052710528105291053010531105321053310534105351053610537105381053910540105411054210543105441054510546105471054810549105501055110552105531055410555105561055710558105591056010561105621056310564105651056610567105681056910570105711057210573105741057510576105771057810579105801058110582105831058410585105861058710588105891059010591105921059310594105951059610597105981059910600106011060210603106041060510606106071060810609106101061110612106131061410615106161061710618106191062010621106221062310624106251062610627106281062910630106311063210633106341063510636106371063810639106401064110642106431064410645106461064710648106491065010651106521065310654106551065610657106581065910660106611066210663106641066510666106671066810669106701067110672106731067410675106761067710678106791068010681106821068310684106851068610687106881068910690106911069210693106941069510696106971069810699107001070110702107031070410705107061070710708107091071010711107121071310714107151071610717107181071910720107211072210723107241072510726107271072810729107301073110732107331073410735107361073710738107391074010741107421074310744107451074610747107481074910750107511075210753107541075510756107571075810759107601076110762107631076410765107661076710768107691077010771107721077310774107751077610777107781077910780107811078210783107841078510786107871078810789107901079110792107931079410795107961079710798107991080010801108021080310804108051080610807108081080910810108111081210813108141081510816108171081810819108201082110822108231082410825108261082710828108291083010831108321083310834108351083610837108381083910840108411084210843108441084510846108471084810849108501085110852108531085410855108561085710858108591086010861108621086310864108651086610867108681086910870108711087210873108741087510876108771087810879108801088110882108831088410885108861088710888108891089010891108921089310894108951089610897108981089910900109011090210903109041090510906109071090810909109101091110912109131091410915109161091710918109191092010921109221092310924109251092610927109281092910930109311093210933109341093510936109371093810939109401094110942109431094410945109461094710948109491095010951109521095310954109551095610957109581095910960109611096210963109641096510966109671096810969109701097110972109731097410975109761097710978109791098010981109821098310984109851098610987109881098910990109911099210993109941099510996109971099810999110001100111002110031100411005110061100711008110091101011011110121101311014110151101611017110181101911020110211102211023110241102511026110271102811029110301103111032110331103411035110361103711038110391104011041110421104311044110451104611047110481104911050110511105211053110541105511056110571105811059110601106111062110631106411065110661106711068110691107011071110721107311074110751107611077110781107911080110811108211083110841108511086110871108811089110901109111092110931109411095110961109711098110991110011101111021110311104111051110611107111081110911110111111111211113111141111511116111171111811119111201112111122111231112411125111261112711128111291113011131111321113311134111351113611137111381113911140111411114211143111441114511146111471114811149111501115111152111531115411155111561115711158111591116011161111621116311164111651116611167111681116911170111711117211173111741117511176111771117811179111801118111182111831118411185111861118711188111891119011191111921119311194111951119611197111981119911200112011120211203112041120511206112071120811209112101121111212112131121411215112161121711218112191122011221112221122311224112251122611227112281122911230112311123211233112341123511236112371123811239112401124111242112431124411245112461124711248112491125011251112521125311254112551125611257112581125911260112611126211263112641126511266112671126811269112701127111272112731127411275112761127711278112791128011281112821128311284112851128611287112881128911290112911129211293112941129511296112971129811299113001130111302113031130411305113061130711308113091131011311113121131311314113151131611317113181131911320113211132211323113241132511326113271132811329113301133111332113331133411335113361133711338113391134011341113421134311344113451134611347113481134911350113511135211353113541135511356113571135811359113601136111362113631136411365113661136711368113691137011371113721137311374113751137611377113781137911380113811138211383113841138511386113871138811389113901139111392113931139411395113961139711398113991140011401114021140311404114051140611407114081140911410114111141211413114141141511416114171141811419114201142111422114231142411425114261142711428114291143011431114321143311434114351143611437114381143911440114411144211443114441144511446114471144811449114501145111452114531145411455114561145711458114591146011461114621146311464114651146611467114681146911470114711147211473114741147511476114771147811479114801148111482114831148411485114861148711488114891149011491114921149311494114951149611497114981149911500115011150211503115041150511506115071150811509115101151111512115131151411515115161151711518115191152011521115221152311524115251152611527115281152911530115311153211533115341153511536115371153811539115401154111542115431154411545115461154711548115491155011551115521155311554115551155611557115581155911560115611156211563115641156511566115671156811569115701157111572115731157411575115761157711578115791158011581115821158311584115851158611587115881158911590115911159211593115941159511596115971159811599116001160111602116031160411605116061160711608116091161011611116121161311614116151161611617116181161911620116211162211623116241162511626116271162811629116301163111632116331163411635116361163711638116391164011641116421164311644116451164611647116481164911650116511165211653116541165511656116571165811659116601166111662116631166411665116661166711668116691167011671116721167311674116751167611677116781167911680116811168211683116841168511686116871168811689116901169111692116931169411695116961169711698116991170011701117021170311704117051170611707117081170911710117111171211713117141171511716117171171811719117201172111722117231172411725117261172711728117291173011731openAstmoduleCond=Wax_wasm.Cond_solvermoduleNz=Wax_wasm.Types.Normalizedtypetyped_module_annotation=Ast.storagetypeoptionarray*Ast.locationopenInfer(* The typed tree as it stands during checking, before the cells are resolved to
[typed_module_annotation]: each node carries the inference cells for the
values it leaves on the stack, plus its span. [f] resolves these to storage
types for the Wasm conversion; the editor reads the cells directly (they carry
the flexible-literal / unknown distinctions [output_inferred_type] renders,
which resolution discards). *)typeinferred_module_annotation=inferred_typeCell.tarray*Ast.location(* A resolved name or label reference: the source span of a *use*, the span(s)
of the *definition(s)* it binds to, and a rendered one-line summary of what it
resolves to (the referenced type's structure, or a variable's type) for a
hover on a name that is not itself an expression. There is more than one
definition only under conditional compilation (a name declared in several
mutually exclusive branches). Accumulated during type checking into a
[reference list ref] when the caller supplies one, for the editor's
go-to-definition and hover; nil otherwise, so an ordinary compile pays
nothing. *)(* What a resolved reference summarises for a hover on a name that is not itself
an expression: a variable's type, or a referenced type's definition. Kept as
data, not a rendered string — nothing is formatted until a hover actually
asks (the editor renders the one it needs), so a check pays only a boxing per
reference. *)typehover_target=Value_typeofinferred_valtype|Type_defofsubtypetypereference={use:Ast.location;definitions:Ast.locationlist;hover:hover_targetoption;}typeresolve_sink=referencelistrefoption(* A synthesized node (an interned function type looked up for a call, a
desugared construct) carries [Ast.dummy_loc] rather than a source span; skip
those, so only genuine source references are recorded. *)letis_source(l:location)=l.loc_start.Lexing.pos_cnum>=0letsame_span(a:location)(b:location)=a.loc_start.Lexing.pos_cnum=b.loc_start.Lexing.pos_cnum&&a.loc_end.Lexing.pos_cnum=b.loc_end.Lexing.pos_cnum(* Record a punned struct-literal field's span (the field name, which is also
the variable use), so the editor can expand it on rename. *)letrecord_pun(sink:locationlistrefoption)(name_info:location)=matchsinkwith|Somerwhenis_sourcename_info->r:=name_info::!r|_->()(* A member-completion candidate for [recv.<here>] or [ns::<here>]: a struct
field, a value method, or a namespace free function, its [member_kind]
driving the editor's icon and [member_detail] a rendered type/signature —
the field's declared type or the method/function's signature. *)typemember_kind=Field|Method|Functiontypemember_candidate={member_name:string;member_kind:member_kind;member_detail:string;}(* What a member access [recv.<here>]'s completion candidates are derived from.
The typer records this lightweight descriptor (a kind and the receiver's
type), and {!member_candidates} turns it into the candidate list on demand —
so the list, which for a v128 or memory receiver is large, is built only for
the access under the cursor, not at every access in the file. *)typemember_receiver=|R_numericofinferred_type(** a value receiver: its integer / float / v128 methods *)|R_structoffieldtypeAst.annotated_array(** the struct's fields *)|R_arrayoffieldtype(** by element type: [length]/[fill]/[copy]/[init] *)|R_memoryof[`I32|`I64](** by address type *)|R_tableof[`I32|`I64]*reftype(** by address and element type *)|R_contofmember_candidatelist(** a continuation-typed receiver: the resume family and [switch],
prebuilt (their signatures need the type context) *)(* Record, at a struct field access, the (possibly partial) field's span and the
receiver it is on, for member completion. [None] outside the editor. *)letrecord_members(sink:(location*member_receiver)listrefoption)fieldreceiver=matchsinkwith|Somerwhenis_sourcefield->r:=(field,receiver)::!r|_->()(* What a value method's result type is relative to its receiver: [Same] as the
receiver, or the equal-width opposite numeric family ([i32]<->[f32],
[i64]<->[f64]), as [from_bits] / [to_bits] reinterpret. *)typemethod_result=Same|Reinterprettypevalue_method={vm_name:string;vm_binary:bool;(** takes a second operand of the receiver's type *)vm_result:method_result;}letmeth?(binary=false)?(result=Same)vm_name={vm_name;vm_binary=binary;vm_result=result}(* The value methods offered by member completion for an integer / float
receiver. A curated registry: the method dispatch (see
[type_unary_intrinsic_call] / [type_binary_intrinsic_call]) is match-based
and cannot be enumerated, so the test in test/method-consistency type-checks
each of these — arity and result type included — to keep the registry in step
with what the typer actually accepts. Vector ([v128]) and memory / table
methods (a different dispatch path) are not covered yet. *)letinteger_methods=[meth"clz";meth"ctz";meth"popcnt";meth"extend8_s";meth"extend16_s";meth~result:Reinterpret"from_bits";meth~binary:true"rotl";meth~binary:true"rotr";]letfloat_methods=[meth"abs";meth"ceil";meth"floor";meth"trunc";meth"nearest";meth"sqrt";meth~result:Reinterpret"to_bits";meth~binary:true"min";meth~binary:true"max";meth~binary:true"copysign";]letnumtype_name:Ast.valtype->string=function|I32->"i32"|I64->"i64"|F32->"f32"|F64->"f64"|V128->"v128"|Ref_->"ref"(* The member-completion candidates for [methods] on a numeric receiver
rendered as [recv_name] (a concrete [i32] or a flexible-literal family like
[int]), with a real signature ([fn() -> i32], [fn(f32) -> f32]).
[reinterp_name] is the result type of a bit-reinterpreting method
([from_bits]/[to_bits]) — the opposite family, which for a flexible receiver
is rendered by family name too. *)letmethod_candidates~recv_name~reinterp_namemethods=List.map(funm->letparams=ifm.vm_binarythenrecv_nameelse""inletresult=matchm.vm_resultwith|Same->recv_name|Reinterpret->reinterp_namein{member_name=m.vm_name;member_kind=Method;member_detail=Printf.sprintf"fn(%s) -> %s"paramsresult;})methods(* A struct field's declared type, rendered for the member-completion detail
(e.g. [i32], [mut i32], [&point]) as it reads in a type definition. [Output]
here is [Infer.Output] (open Infer), whose printers take a formatter. *)letrender_fieldtype(f:Ast.fieldtype)=String.trim(Format.asprintf"%a"Output.fieldtypef)(* A reference type rendered as it reads in source (e.g. [&func], [&?extern]),
for a table's element type in the member-completion detail. *)letrender_reftype(rt:Ast.reftype)=String.trim(Format.asprintf"%a"Output.valtype(Ast.Refrt))(* The member candidates for a struct's [fields] (each name and declared type),
for member completion. *)letstruct_candidatesfields=Array.to_listfields|>List.map(funf->letnm,typ=f.Ast.descin{member_name=nm.Ast.desc;member_kind=Field;member_detail=render_fieldtypetyp;})letrecord_reference?(hover=None)(sink:resolve_sink)usedefinitions=matchsinkwith|Somerwhenis_sourceuse->((* Drop synthesized definitions and the self-reference a name's own
declaration makes when it looks itself up (go-to-definition on a
definition has nowhere useful to go). *)matchList.filter(fund->is_sourced&¬(same_spanduse))definitionswith|[]->()|definitions->r:={use;definitions;hover}::!r)|_->()(*** Diagnostics ***)moduleError=structopenWax_utils(* Message-building combinators (see {!Wax_utils.Message}). Prose is [text],
joined with [++] (soft, wrap-point space) or [^^] (no space). An emphasized
atom — [name] an identifier, [kw] a code token, [num] a numeric literal,
[typ] an inferred type — is coloured when the theme is coloured and quoted
['…'] when it is not (so JSON/short, always uncoloured, are always quoted). *)lettext=Message.textlet(++)=Message.(++)let(^^)=Message.(^^)letnamex=Message.identx.descletkw=Message.codeletnums=Message.styledColors.Constants(* An inferred type, rendered through the shared pretty-printer so it shares
the message's theme and width. Quoted when the theme is uncoloured, to match
the other emphasized atoms. *)lettypty=Message.raw(funsp->letquote=Colors.escape_sequencesp.Styled_printer.themeColors.Type=""inifquotethenPrinter.stringsp.Styled_printer.printer"'";(* Render the whole type in the [Type] colour as one unit, rather than
syntax-highlighting its innards (parens, [&], keywords) in separate
role colours — a type in a message reads as a single concept. *)Styled_printer.with_stylespColors.Type(fun()->Infer.output_inferred_type_styledspty);ifquotethenPrinter.stringsp.Styled_printer.printer"'")(* All errors share the same envelope: severity [Error], a message, and an
optional hint. [report] captures that boilerplate so each error below is
just its message (and, where relevant, a hint). *)letreport?hint?relatedcontext~locationmessage=Diagnostic.reportcontext~location~severity:Error?hint?related~message()(* Warnings share the same envelope as [report] but with severity [Warning],
so they are printed without aborting the pass. [warning] names the warning
so its level can be configured (see {!Wax_utils.Warning}).
In error-recovery mode (type-checking a best-effort AST past syntax errors)
every warning is at best secondary and usually a cascade: a local is
"unused" only because its use was dropped at a sync boundary or auto-closed
away at EOF, a result is "unused" because the following code was skipped,
and the lints may fire on mangled recovered code. Warnings are advisory, so
suppress them wholesale here — the user fixes the syntax errors first and the
warnings surface on a clean re-check. This is the warning-severity analogue
of the [unbound_name]/[empty_stack] cascade guards. *)letwarn?warning?universal?hint?relatedcontext~locationmessage=ifnot(Wax_utils.Diagnostic.in_recoverycontext)thenDiagnostic.reportcontext~location~severity:Warning?warning?universal?hint?related~message()(* A local declared by a [let] but never read. Prefix its name with [_] to
silence the warning. *)letunused_localcontext~locationx=warn~warning:Wax_utils.Warning.Unused_local~universal:truecontext~location(text"The local variable"++namex++text"is never used.")(* A module field (a function or global) declared but never referenced,
exported, or used as the start function. Prefix its name with [_] to
silence the warning. *)letunused_fieldcontext~locationkindx=warn~warning:Wax_utils.Warning.Unused_field~universal:truecontext~location(text"The"++textkind++namex++text"is never used.")(* An imported function or global never referenced, exported, or used as the
start function. Prefix its name with [_] to silence the warning. *)letunused_importcontext~locationkindx=warn~warning:Wax_utils.Warning.Unused_import~universal:truecontext~location(text"The imported"++textkind++namex++text"is never used.")(* An operation with no effect on its result, or a constant result. *)letredundant_operationcontext~locationmessage=warn~warning:Wax_utils.Warning.Redundant_operation~universal:truecontext~locationmessage(* A cast/test whose operand can never have the target type. *)letcast_always_failscontext~location~is_test=warn~warning:Wax_utils.Warning.Cast_always_fails~universal:truecontext~location(text(ifis_testthen"This type test is always false: the value can never have this \
type."else"This cast always traps: the value can never have this type."))(* A cast/test whose operand already has the target type. *)letredundant_castcontext~location~is_test=warn~warning:Wax_utils.Warning.Redundant_operation~universal:truecontext~location(text(ifis_testthen"This type test is always true: the value already has this type."else"This cast is redundant: the value already has this type."))(* A block label declared but never branched to. Prefix its name with [_] to
silence the warning. *)letunused_labelcontext~locationx=warn~warning:Wax_utils.Warning.Unused_label~universal:truecontext~location(text"The label"++namex++text"is never used.")(* A shift whose constant count is at least the operand's bit width. Wasm
shifts mask the count modulo the width, so the result is very likely not
what was intended. *)letshift_overflowcontext~location~widthcount=warn~warning:Wax_utils.Warning.Shift_overflow~universal:truecontext~location~hint:((text"Wasm masks the count modulo"++Message.intwidth)^^text","++text"shifting by"++Message.int64(Int64.remcount(Int64.of_intwidth))++text"instead.")(text"The shift count"++Message.int64count++text"is at least the operand width ("^^Message.intwidth^^text" bits).")(* An integer division or remainder by a constant zero: it always traps. *)letdivision_by_zerocontext~location=warn~warning:Wax_utils.Warning.Constant_trap~universal:truecontext~location(text"This integer division or remainder by zero always traps.")(* A comparison whose result does not depend on its variable operand. *)lettautological_comparisoncontext~location~value=warn~warning:Wax_utils.Warning.Tautological_comparison~universal:truecontext~location((text"This comparison is always"++Message.boolvalue)^^text".")(* A branch, loop, or [select] condition that is a constant. *)letconstant_conditioncontext~location~value=warn~warning:Wax_utils.Warning.Constant_condition~universal:truecontext~location((text"This condition is always"++Message.boolvalue)^^text".")(* A side-effect-free expression whose result is computed and then dropped. *)letunused_resultcontext~location=warn~warning:Wax_utils.Warning.Unused_result~universal:truecontext~location(text"The result of this expression is discarded, and computing it has no \
effect.")(* A trapping float-to-integer conversion of a constant that lies outside the
target type's range (or is NaN/infinite): it always traps. *)letconversion_out_of_rangecontext~location=warn~warning:Wax_utils.Warning.Constant_trap~universal:truecontext~location(text"This conversion always traps: the constant is out of the target \
type's range.")(* A statement that can never be reached: it follows an unconditional branch,
[return], or [unreachable]. [related] points at the diverging instruction. *)letdead_codecontext~location~related=warn~warning:Wax_utils.Warning.Dead_code~universal:truecontext~location~related(text"This code is unreachable.")(* A trapping or effectful operation inside a branch of a [?:]. Because [?:]
compiles to a [select], which evaluates both branches, the operation runs
even when the condition selects the other branch — unlike the [?:] of most
languages, which is lazy. [select] points at the whole [?:]. *)leteager_selectcontext~location~select=warn~warning:Wax_utils.Warning.Eager_select~universal:truecontext~location~related:[{Wax_utils.Diagnostic.location=select;message=text"This '?:' evaluates both branches (it compiles to a 'select').";};]~hint:(text"Use an 'if' expression to evaluate only the chosen branch.")(text"This operation is evaluated even when the condition selects the \
other branch.")(* Two operators whose relative precedence is easy to misremember are mixed
without parentheses (see {!lint_precedence}). [location] is the outer
operator, [inner] the tighter-binding one; the [kind]s name the two
operator classes ("shift", "arithmetic", "comparison", "bitwise"). *)letprecedencecontext~location~inner~outer_kind~inner_kind=warn~warning:Wax_utils.Warning.Precedence~universal:truecontext~location~related:[{Wax_utils.Diagnostic.location=inner;message=text"This"++textinner_kind++text"operator binds tighter than the"++textouter_kind++text"operator.";};]~hint:(text"Add parentheses to make the grouping explicit.")(text"Operator precedence here is easy to misread.")letempty_stackcontext~location=(* Like [unbound_name], suppress this in error-recovery mode: a stack
underflow while type-checking a best-effort AST is usually a cascade from
a value-producing construct dropped at a sync boundary, not a real
mistake. Both callers ([pop_any]/[pop]) recover with [Error]/[()], so
nothing downstream cascades; genuine underflows in intact code still
surface on a clean re-check once the syntax errors are fixed. *)ifnot(Wax_utils.Diagnostic.in_recoverycontext)thenreportcontext~location(text"The stack is empty.")letlet_in_conditionalcontext~location=reportcontext~location(text"A let binding is not allowed inside a conditional annotation; \
declare the local before the conditional.")letnon_empty_stackcontext~locationrender=reportcontext~location(text"Some values remain on the stack:"^^Message.rawrender)(* Report the values still on the stack by pointing a caret at each of them.
[location] carries the topmost value; [related] the others. *)letleftover_valuescontext~location~related=reportcontext~location~related(text(ifrelated=[]then"This value remains on the stack."else"These values remain on the stack."))letexpected_func_typecontext~location=reportcontext~location(text"Expected function type.")letinline_function_type_mismatchcontext~location=reportcontext~location(text"The inline function type does not match the type definition.")letexpected_struct_typecontext~location=reportcontext~location(text"Expected struct type.")letexpected_array_typecontext~location=reportcontext~location(text"Expected array type.")(* An operation (a call, a field/array access, …) needs its operand's concrete
type to be compiled, but the operand's type is unknown: it was taken off the
polymorphic stack of unreachable or branch-terminated code. This is the
first error for the operand (an already-failed operand reads as the [Error]
type and stays silent), so it is reported here. *)letunknown_operand_typecontext~location=reportcontext~location(text"Cannot determine the type of this expression, which is needed to \
compile this operation.")(* A struct literal omitted its type name in a position where the expected
type does not pin an exact struct type, so the type cannot be inferred. *)letcannot_infer_struct_typecontext~location=reportcontext~location(text"Cannot infer the struct type here; add an explicit type, as in"++kw"{T| ..}"^^text".")letcannot_infer_array_typecontext~location=reportcontext~location(text"Cannot infer the array type here; add an explicit type, as in"++kw"[T| ..]"^^text".")letmethod_needs_parenthesescontext~locationmeth=reportcontext~location(kwmeth++text"is an instruction method and must be called with parentheses, as"++kw(meth^"()")^^text".")lettype_mismatchcontext~locationty'ty=reportcontext~location((text"Expecting type"++typty++text"but got type"++typty')^^text".")letnot_an_expressioncontext~locationn=(* Suppress in error-recovery mode, like [empty_stack]: an instruction with
the wrong number of values in expression position is usually a cascade
from recovery mangling the surrounding code (a dropped operand, or a
construct auto-closed at EOF). Both callers recover with [Error], so
nothing downstream cascades; a genuine arity error in intact code still
surfaces on a clean re-check. *)ifnot(Wax_utils.Diagnostic.in_recoverycontext)thenreportcontext~location(text"An expression is expected here. This instruction returns"++Message.intn++text"values.")letbinop_type_mismatchcontext~locationty1ty2=reportcontext~location(text"This operator cannot be applied to operands of types"++typty1++text"and"++typty2^^text".")letinstruction_type_mismatchcontext~locationtyty'=reportcontext~location(text"This instruction has type"++typty++text"but is expected to have type"++typty'^^text".")letvalue_count_mismatchcontext~location~expected~provided=reportcontext~location(text"This instruction provides"++Message.intprovided++text"value(s) but"++Message.intexpected++text"was/were expected.")letinvalid_method_receivercontext~locationty=reportcontext~location((text"This operation cannot be applied to a value of type"++typty)^^text".")letinvalid_management_callcontext~locationmeth=reportcontext~location((text"Invalid arguments in call to"++kwmeth)^^text".")letif_without_elsecontext~location=reportcontext~location(text"This 'if' must produce a value and so requires an 'else' branch.")letparameterized_block_expressioncontext~location=reportcontext~location(text"A block, loop or if used as an expression cannot take parameters.")letuninitialized_localcontext~locationx=reportcontext~location(text"The local variable"++namex++text"has not been initialized.")letnon_nullable_tablecontext~location=reportcontext~location(text"A table with a non-nullable element type must have an initializer.")letstart_function_signaturecontext~location=reportcontext~location(text"The start function must have no parameters and no results.")letmultiple_startcontext~location=reportcontext~location(text"A module can have at most one start function.")letmultiple_modulecontext~location=reportcontext~location(text"A module can have at most one name annotation.")letunknown_annotationcontext~locationname=reportcontext~location((text"Unknown annotation"++kwname)^^text".")letannotation_value_mismatchcontext~locationnameexpected=reportcontext~location((text"The"++textname++text"annotation expects"++textexpected)^^text".")letannotation_not_allowedcontext~locationname=reportcontext~location(text"The"++textname++text"annotation is not allowed here.")letguard_not_allowedcontext~locationname=reportcontext~location(text"A conditional guard is only allowed on an export or start \
annotation, not on"++textname^^text".")letmultiple_importcontext~location=reportcontext~location(text"An import can have at most one import-name annotation.")letfinal_supertypecontext~locationx=reportcontext~location(text"The type"++namex++text"is final and cannot be extended; declare it 'open'.")letinvalid_subtypecontext~locationx=reportcontext~location((text"This type is not a valid subtype of"++namex)^^text".")letdescriptor_outside_rec_groupcontext~location~described=reportcontext~location(text"The"++text(ifdescribedthen"described"else"descriptor")++text"type must be in the same recursion group.")letdescriptor_not_reciprocalcontext~location~described=reportcontext~location(text(ifdescribedthen"This descriptor does not describe the type it is attached to."else"The descriptor of this type does not describe it back."))letforward_use_of_describedcontext~location=reportcontext~location(text"A described type must be declared before its descriptor.")letdescriptor_not_structcontext~location~described=reportcontext~location(text"A"++text(ifdescribedthen"described"else"descriptor")++text"type must be a struct type.")lettype_without_descriptorcontext~location=reportcontext~location(text"This descriptor instruction requires a type that has a descriptor.")letdescriptor_allocation_requiredcontext~location=reportcontext~location(text"A type with a descriptor must be allocated with a descriptor: \
{descriptor(d) | …}.")letfeature_disabledcontext~locationfeature=reportcontext~location(text"This uses the"++text(Wax_utils.Feature.namefeature)++text"feature, which is not enabled; pass --feature"++text(Wax_utils.Feature.namefeature)^^text".")letunknown_featurecontext~locationname=reportcontext~location((text"Unknown feature"++kwname)^^text". Known features:"++text(String.concat", "(List.mapWax_utils.Feature.nameWax_utils.Feature.all))^^text".")letfeature_conflictcontext~locationfeature=reportcontext~location(text"This module requires the"++text(Wax_utils.Feature.namefeature)++text"feature, which is disabled on the command line; drop --feature"++text(Wax_utils.Feature.namefeature^"=off")^^text".")(* A secondary caret at [location] labelled with an inferred value type. Used
to point at each branch of an [if]/select whose branches are in
incompatible type hierarchies: there is no common supertype — and, unlike a
checked position which can name one expected type, no annotation that would
reconcile them — so we just show what each branch produces. *)lettyped_branch_labellocationty={Wax_utils.Diagnostic.location;message=typty}letselect_type_mismatchcontext~location~loc1~loc2ty1ty2=reportcontext~location~related:[typed_branch_labelloc1ty1;typed_branch_labelloc2ty2](text"The two branches of this select have no common supertype, so its \
result type cannot be inferred.")(* The exit values of a block-like construct (an [if]'s two branches, or a
[do]/[loop]/[try]'s fall-through and/or values branched to its label) do not
join to a common supertype. A caret marks each offending value. *)letblock_exit_type_mismatchcontext~location~loc1~loc2ty1ty2=reportcontext~location~related:[typed_branch_labelloc1ty1;typed_branch_labelloc2ty2](text"The values reaching this block's exit have no common supertype, so \
its result type cannot be inferred.")(* A value delivered by [br_if] stays on the stack (the fall-through) typed as
the block's result, so its type must equal the inferred result exactly, not
merely be a subtype. Here it is a strict subtype and there is no annotation
to pin the result, so the block cannot be given a result type consistent with
both. *)letbr_if_result_mismatchcontext~location~loc~resultty=reportcontext~location~related:[typed_branch_labellocty;typed_branch_labellocationresult](text"This [br_if] value stays on the stack as the block's result, so its \
type must match the inferred result exactly; add a result annotation \
to the block.")letname_already_boundcontext~locationkindx=reportcontext~location(text"A"++textkind++text"named"++namex++text"is already bound.")letdid_you_mean=function|[]->None|suggestions->Some(text"Did you mean"++Message.enumerate~conj:"or"(List.mapMessage.identsuggestions)^^text"?")letunbound_namecontext~location?(suggestions=[])kindx=(* In error-recovery mode (type-checking a best-effort AST past syntax
errors) a name is often unbound only because the construct that would
bind it was dropped at a sync boundary — our recovery drops spans rather
than leaving a placeholder node, so the binding is simply absent. Such
"not bound" reports are cascades from the syntax error, so suppress them;
the caller has already reported the syntax errors, and real type errors
in the intact regions still surface. The value still recovers as [Error]
at the use site, so nothing downstream cascades either. *)ifnot(Wax_utils.Diagnostic.in_recoverycontext)thenreport?hint:(did_you_meansuggestions)context~location(text"The"++textkind++namex++text"is not bound.")letunknown_intrinsiccontext~locationnsname=reportcontext~location(text"There is no"++kw(ns^"::"^name)++text"intrinsic.")letintrinsic_not_calledcontext~locationnsname=reportcontext~location(text"The qualified name"++kw(ns^"::"^name)++text"can only be used as a function call.")letbefore_holecontext~location=reportcontext~location(text"This expression occurs before a hole '_'.")letduplicated_fieldcontext~locationx=reportcontext~location((text"Several fields have the same name"++namex)^^text".")letsplice_without_supertypecontext~location=reportcontext~location(text"'..' requires a supertype to inherit fields from (write 'type t: \
super = { .., ... }').")letsplice_non_structcontext~locationx=reportcontext~location(text"'..' can only inherit fields from a struct supertype;"++namex++text"is not a struct.")letduplicated_parametercontext~locationx=reportcontext~location((text"Several parameters have the same name"++namex)^^text".")letconstant_expression_requiredcontext~location=reportcontext~location(text"Only constant expressions are allowed here.")letdata_run_bad_elementcontext~locationtypename=reportcontext~location(text"This value is out of range for the data run's element type"++kwtypename^^text".")letdata_v128_aritycontext~locationcount=reportcontext~location(text"This v128 lane group must have"++Message.intcount++text"lanes.")letmemory_offset_too_largecontext~locationmax_offset=reportcontext~location(text"The memory offset should be less than"++num(Printf.sprintf"0x%Lx"(Wax_utils.Uint64.to_int64max_offset))^^text".")letmemory_align_too_largecontext~locationnatural=reportcontext~location(text"The memory alignment is larger than the natural alignment"++Message.intnatural^^text".")letmemory_immediate_too_largecontext~location=reportcontext~location(text"This memory offset or alignment must fit a 64-bit unsigned integer.")letbad_memory_aligncontext~location=reportcontext~location(text"The memory alignment should be a power of two.")letatomic_alignmentcontext~locationnatural=reportcontext~location(text"The alignment of an atomic access must be its natural alignment"++Message.intnatural^^text".")letatomic_signed_loadcontext~location~cast~extend=reportcontext~location(text"An atomic load zero-extends; use"++(kwcast^^text",")++text"then"++kwextend++text"if you need the sign.")letinvalid_lane_indexcontext~locationmax_lane=reportcontext~location((text"The lane index should be less than"++Message.intmax_lane)^^text".")letlane_value_out_of_rangecontext~locationbits=reportcontext~location(text"The lane value does not fit in"++Message.intbits++text"bits.")letlabelled_argument_not_allowedcontext~location=reportcontext~location(text"Labelled arguments are only allowed for the"++(kw"offset"^^text",")++kw"align"++text"and"++kw"lane"++text"immediates of a memory access.")letunknown_argument_labelcontext~location~suggestionsx=report?hint:(did_you_meansuggestions)context~location((text"Unknown argument label"++namex)^^text".")letduplicate_argument_labelcontext~locationx=reportcontext~location(text"The argument label"++namex++text"is given several times.")letpositional_argument_after_labelcontext~location=reportcontext~location(text"A positional argument cannot follow a labelled argument.")(* The pre-labelled-arguments syntax passed the [align]/[offset] (and SIMD
[lane]) immediates positionally; give old code a targeted migration
message rather than a generic arity error. *)letpositional_memory_immediatecontext~location~example=reportcontext~location(text"The static immediates of a memory access must be labelled, e.g."++kwexample^^text".")letmissing_lane_immediatecontext~location=reportcontext~location(text"This memory access needs a"++kw"lane:"++text"immediate (e.g."++kw"lane: 0"^^text").")letlimit_too_largecontext~locationkindmax=reportcontext~location(text"The"++textkind++text"size is too large. It should be less than"++num(Printf.sprintf"0x%Lx"(Wax_utils.Uint64.to_int64max))^^text".")letlimit_mismatchcontext~locationkind=reportcontext~location(text"The"++textkind++text"maximum size should be larger than the minimal size.")letinvalid_page_sizecontext~location=reportcontext~location(text"The custom page size must be 1 or 65536.")letshared_memory_without_maxcontext~location=reportcontext~location(text"A shared memory must have a maximum size.")letduplicated_exportcontext~locationname=reportcontext~location((text"There is already an export of name"++kwname)^^text".")(* A cast to a continuation type that is not a provable no-op: continuations
carry no RTT, so no cast can ever narrow one — point at the value's
introduction, not the cast site. *)letcont_cast_not_ascriptioncontext~location=reportcontext~location~hint:(text"Give the value a declared continuation type where it is introduced \
(a parameter, local or block-result annotation).")(text"A cast to a continuation type is a compile-time ascription: the \
operand's type must already be a subtype of the target, as there is \
no runtime continuation cast.")letinvalid_cast_typecontext~location=reportcontext~location(text"Continuation types cannot be used in a cast instruction.")letstack_switching_type_mismatchcontext~location~descr=reportcontext~location((text"Type mismatch in this stack switching instruction:"++textdescr)^^text".")letreserved_type_namecontext~locationx=reportcontext~location(namex++text"is a reserved built-in type name.")letexpected_cont_typecontext~location=reportcontext~location(text"This expression should be a reference to a declared continuation \
type.")(* Mirrors the call_ref rule for an abstract function reference at a call:
the type immediate comes from the receiver's static type. Unlike a
function reference, an abstract [&cont] cannot be cast to a declared
continuation type (the proposal defines no such cast), so the fix is to
give the value its precise type at its source. *)letabstract_cont_receivercontext~location=reportcontext~location(text"The continuation type cannot be resolved from this expression. Give \
the value a declared continuation type where it is introduced (a \
parameter, local or block-result annotation): a continuation \
reference cannot be narrowed by a cast.")leton_clause_contextcontext~location=reportcontext~location(text"An"++kw"on"++text"handler clause is only allowed on a"++(kw"resume"^^text",")++kw"resume_throw"++text"or"++kw"resume_throw_ref"++text"call.")letswitch_needs_tagcontext~location=reportcontext~location(text"A"++kw"switch"++text"names its enabling tag as a labelled immediate, e.g."++(kw"c.switch(x, tag: t)"^^text"."))letresume_throw_needs_tagcontext~location=reportcontext~location(kw"resume_throw"++text"raises a tag applied to its payload, e.g."++(kw"c.resume_throw(exc(x))"^^text"."))letconstant_global_requiredcontext~location=reportcontext~location(text"Only accessing a constant global is allowed here.")letimmutablecontext~locationwhat=reportcontext~location(text"This"++textwhat++text"is immutable and cannot be assigned.")letnot_assignablecontext~locationx=reportcontext~location(namex++text"cannot be assigned.")letfield_count_mismatchcontext~location~expected~provided=reportcontext~location(text"This structure provides"++Message.intprovided++text"field(s) but"++Message.intexpected++text"was/were expected.")letmissing_fieldcontext~locationx=reportcontext~location((text"There is no field named"++namex)^^text".")letinvalid_castcontext~locationty'=reportcontext~location(text"This value of type"++typty'++text"cannot be cast to the target type.")lettag_with_resultscontext~location=reportcontext~location(text"An exception tag cannot have result values.")letcatch_target_mismatchcontext~locationprovidedexpected=reportcontext~location(text"Catching this exception provides a value of type"++typprovided++text"but the handler's branch target expects"++typexpected^^text".")letnot_defaultablecontext~location=reportcontext~location(text"This type has no default value for all its fields.")letincompatible_array_elementscontext~location=reportcontext~location(text"The source and destination array element types are incompatible.")letincompatible_element_typecontext~locationprovidedexpected=reportcontext~location(text"The element type"++typprovided++text"is not compatible with the expected element type"++typexpected^^text".")letinvalid_string_element_typecontext~location=reportcontext~location(text"A string literal can only build an [i8] or [i16] array.")letstring_not_unicodecontext~location=reportcontext~location(text"A string building an [i16] array must be a valid Unicode string.")letexpected_refcontext~location=reportcontext~location(text"A reference type is expected here.")letdispatch_duplicate_armcontext~locationx=reportcontext~location((text"This dispatch has several cases named"++namex)^^text".")end(*** Symbol tables and namespaces ***)moduleStringSet=Set.Make(String)moduleStringMap=Map.Make(String)(* The option let-operators (the [@] suffix denotes "optional"): [let*@] binds
through [Some]/short-circuits on [None] (Option.bind), [let+@] maps the
payload (Option.map), and [let>@] runs an effect only when [Some]
(Option.iter). Distinct from the stack-threading [let*]/[let*!] defined with
the typing monad further down. *)let(let*@)=Option.bindlet(let+@)of=Option.mapfolet(let>@)of=Option.iterfo(* Names are resolved relative to a "current assumption" — the conjunction of
the conditional-branch conditions enclosing the point being typed. The cell
is shared by every namespace and table of one module typing, and updated as
the passes descend into [#[if]]/[#[else]] branches. When no conditionals are
present (or when checking a single specialized configuration) it stays
[true_] and these structures behave like plain name-keyed tables. *)moduleNamespace=structtypet={cond:Cond.tref;tbl:(string,(string*location*Cond.t)list)Hashtbl.t;links:resolve_sink;(* Where [Tbl.resolve] records a use -> definition(s) reference, shared
across the namespaces of one module; [None] disables recording. *)}letmake?(links=None)cond={cond;tbl=Hashtbl.create16;links}letentriesnsx=tryHashtbl.findns.tblx.descwithNot_found->[](* A name conflicts with an earlier declaration only if their assumptions can
both hold; declarations in mutually-exclusive branches do not conflict. *)letconflictnsx=letc=!(ns.cond)inList.find_opt(fun(_,_,c')->Cond.is_satisfiable(Cond.and_cc'))(entriesnsx)letregisterdnskindx=(matchconflictnsxwith|Some(kind',_,_)->Error.name_already_boundd~location:x.infokind'x|None->());Hashtbl.replacens.tblx.desc((kind,x.info,!(ns.cond))::entriesnsx)letexistsdnsx=matchconflictnsxwith|Some(kind',_,_)->Error.name_already_boundd~location:x.infokind'x;true|None->falseendmoduleTbl=structtype'at={kind:string;namespace:Namespace.t;tbl:(string,(Cond.t*'a)list)Hashtbl.t;(* Names referenced (looked up) through this table, so a declaration that is
never referenced can be reported as unused. Populated by [resolve];
queried by [is_used]. *)used:(string,unit)Hashtbl.t;hover:'a->hover_targetoption;(* A summary of a resolved value (its type / definition), attached to
the reference [resolve] records, for editor hover on a name that is
not an expression. [fun _ -> None] leaves the reference hover-less. *)}letmake?(hover=fun_->None)namespacekind={kind;namespace;tbl=Hashtbl.create16;used=Hashtbl.create16;hover;}(* Whether a name declared in this table has been referenced. *)letis_usedenvname=Hashtbl.memenv.usednameletcurenv=!(env.namespace.cond)letentriesenvx=tryHashtbl.findenv.tblx.descwithNot_found->[]letadddenvxv=Namespace.registerdenv.namespaceenv.kindx;Hashtbl.replaceenv.tblx.desc((curenv,v)::entriesenvx)letexistsdenvx=Namespace.existsdenv.namespacex(* Replace the most recently added entry (added by [add] under the current
assumption); used by [add_type] to fix up rectype indices in place. *)letoverrideenvxv=matchentriesenvxwith|_::tl->Hashtbl.replaceenv.tblx.desc((curenv,v)::tl)|[]->Hashtbl.replaceenv.tblx.desc[(curenv,v)](* Pick the declaration whose assumption is entailed by the current one,
falling back to one merely compatible with it, then to the most recent.
A successful lookup marks the name referenced (for the unused-field lint);
[resolve] is only ever called to look up a reference, never for a
declaration (which goes through [add]). *)letresolveenvx=letr=matchentriesenvxwith|[]->None|[(_,v)]->Somev|l->(letc=curenvinletpickp=Option.mapsnd(List.find_opt(fun(c',_)->pc')l)inmatchpick(func'->Cond.logical_impliescc')with|Some_asr->r|None->(matchpick(func'->Cond.is_satisfiable(Cond.and_cc'))with|Some_asr->r|None->(matchlwith(_,v)::_->Somev|[]->None)))in(matchrwith|Somev->Hashtbl.replaceenv.usedx.desc();(* Link this use to every definition of the name (several only across
conditional branches); [resolve] handles only references, so [x.info]
is a use site. The resolved value's summary rides along for hover. *)record_reference~hover:(env.hoverv)env.namespace.linksx.info(List.map(fun(_,loc,_)->loc)(Namespace.entriesenv.namespacex))|None->());rletfinddenvx=matchresolveenvxwith|Some_asr->r|None->letsuggestions=Wax_utils.Spell_check.f(funf->Hashtbl.iter(funk_->fk)env.tbl)x.descinError.unbound_named~location:x.info~suggestionsenv.kindx;Noneletfind_optenvx=resolveenvxletiterenvf=Hashtbl.iter(funkl->List.iter(fun(_,v)->fkv)l)env.tbl(* Drop the most recently added entry (the temporary [add_type] placeholder),
keeping any declaration of the same name from another branch. *)letremoveenvx=matchentriesenvxwith|_::(_::_astl)->Hashtbl.replaceenv.tblx.desctl|_->Hashtbl.removeenv.tblx.descend(*** Types and the type context ***)typetypes=(Wax_wasm.Types.ref_index*subtype)Tbl.ttypetype_context={internal_types:Wax_wasm.Types.t;types:(Wax_wasm.Types.ref_index*subtype)Tbl.t;features:Wax_utils.Feature.set;(* The enabled optional features / proposals, and which are used. *)mutablesubtyping_info_cache:Wax_wasm.Types.subtyping_infooption;(* Memoised subtyping info for [internal_types]; invalidated by [add_type]
when a type is added (including function types minted while
type-checking, e.g. an inline [&fn(..)] cast target), so subtyping
queries always see the current type space. Read via [subtyping_info]. *)}letget_type_definitiondtypesnm=Option.mapsnd(Tbl.finddtypesnm)(* The canonical index of an already-defined type; a [Rec] would mean a group
still under construction, which the type-definition builders never look up. *)letdef_id:Wax_wasm.Types.ref_index->Wax_wasm.Types.Id.t=function|Defid->id|Rec_->assertfalse(* How a source reference appears inside a rec group being registered. *)letresolve_type_refdctxname=let+@res=Tbl.finddctx.typesnameinfstres(* The canonical index of an already-defined referenced type. *)letresolve_type_namedctxname=let+@r=resolve_type_refdctxnameindef_idr(* Record that [feature] is used and, if it is disabled, report it at
[location]. Typing continues either way (error recovery). *)letrequire_featured(ctx:type_context)~locationfeature=Wax_utils.Feature.mark_usedctx.featuresfeature;ifnot(Wax_utils.Feature.is_enabledctx.featuresfeature)thenError.feature_disabledd~locationfeatureletheaptypedctx(h:heaptype):Internal.heaptypeoption=matchhwith|Func->SomeFunc|NoFunc->SomeNoFunc|Exn->SomeExn|NoExn->SomeNoExn|Cont->SomeCont|NoCont->SomeNoCont|Extern->SomeExtern|NoExtern->SomeNoExtern|Any->SomeAny|Eq->SomeEq|I31->SomeI31|Struct->SomeStruct|Array->SomeArray|None_->SomeNone_|Typeidx->let+@ty=resolve_type_namedctxidxin(Typety:Internal.heaptype)|Exactidx->require_featuredctx~location:idx.infoWax_utils.Feature.Custom_descriptors;let+@ty=resolve_type_namedctxidxin(Exactty:Internal.heaptype)letreftypedctx{nullable;typ}=let+@typ=heaptypedctxtypin{Internal.nullable;typ}letvaltypedctxty:Internal.valtypeoption=matchtywith|I32->SomeI32|I64->SomeI64|F32->SomeF32|F64->SomeF64|V128->SomeV128|Refr->let+@ty=reftypedctxrin(Refty:Internal.valtype)(* Like [Array.map] into an option, returning [None] as soon as [f] returns
[None] on any element (so [let*!] propagates a single failure). *)letarray_map_optfarr=letexceptionShort_circuitintryletresult=Array.init(Array.lengtharr)(funi->matchfarr.(i)withSomev->v|None->raiseShort_circuit)inSomeresultwithShort_circuit->Noneletarray_mapi_optfarr=letexceptionShort_circuitintryletresult=Array.init(Array.lengtharr)(funi->matchfiarr.(i)withSomev->v|None->raiseShort_circuit)inSomeresultwithShort_circuit->None(* Report any parameter name used more than once in a signature. *)letcheck_unique_param_namesdparams=ignore(Array.fold_left(funsp->matchfstp.descwith|None->s|Somename->ifStringSet.memname.descsthenError.duplicated_parameterd~location:name.infoname;StringSet.addname.descs)StringSet.emptyparams:StringSet.t)letmuttypefdctx{mut;typ}=let+@typ=fdctxtypin{mut;typ}(* Type-definition builders producing the normalized form ({!Wax_wasm.Types.
Normalized}) that {!Wax_wasm.Types.add_rectype} takes: an in-group reference
is [Rec pos], anything else [Def id]. Separate from the [Internal]-producing
builders above, which serve the checker where every reference is defined. *)letn_heaptypedctx(h:heaptype):Nz.heaptypeoption=matchhwith|Func->SomeFunc|NoFunc->SomeNoFunc|Exn->SomeExn|NoExn->SomeNoExn|Cont->SomeCont|NoCont->SomeNoCont|Extern->SomeExtern|NoExtern->SomeNoExtern|Any->SomeAny|Eq->SomeEq|I31->SomeI31|Struct->SomeStruct|Array->SomeArray|None_->SomeNone_|Typeidx->let+@r=resolve_type_refdctxidxin(Typer:Nz.heaptype)|Exactidx->require_featuredctx~location:idx.infoWax_utils.Feature.Custom_descriptors;let+@r=resolve_type_refdctxidxin(Exactr:Nz.heaptype)letn_reftypedctx{nullable;typ}:Nz.reftypeoption=let+@typ=n_heaptypedctxtypin{Nz.nullable;typ}letn_valtypedctxty:Nz.valtypeoption=matchtywith|I32->SomeI32|I64->SomeI64|F32->SomeF32|F64->SomeF64|V128->SomeV128|Refr->let+@ty=n_reftypedctxrin(Refty:Nz.valtype)letn_functypedctx{params;results}:Nz.functypeoption=check_unique_param_namesdparams;let*@params=array_map_opt(funp->n_valtypedctx(sndp.desc))paramsinlet+@results=array_map_opt(funty->n_valtypedctxty)resultsin{Nz.params;results}letn_storagetypedctxty:Nz.storagetypeoption=matchtywith|Valuety->let+@ty=n_valtypedctxtyin(Valuety:Nz.storagetype)|Packedty->Some(Packedty)letn_fieldtypedctxty:Nz.fieldtypeoption=muttypen_storagetypedctxtyletcomptypedctx(ty:comptype):Nz.comptypeoption=matchtywith|Functy->let+@ty=n_functypedctxtyin(Functy:Nz.comptype)|Structfields->let_:StringSet.t=Array.fold_left(funsfield->letname,_=field.descinifStringSet.memname.descsthenError.duplicated_fieldd~location:name.infoname;StringSet.addname.descs)StringSet.emptyfieldsinlet+@fields=array_map_opt(funfield->n_fieldtypedctx(sndfield.desc))fieldsin(Structfields:Nz.comptype)|Arrayfield->let+@field=n_fieldtypedctxfieldin(Arrayfield:Nz.comptype)|Contidx->let+@r=resolve_type_refdctxidxin(Contr:Nz.comptype)(* A reference is to an already-defined type when it is a [Def], or a [Rec]
member strictly before [current] in the group. *)letdefined_beforecurrent:Wax_wasm.Types.ref_index->bool=function|Def_->true|Recpos->pos<currentletsubtypedctxcurrent{typ;supertype;final;descriptor;describes}:Nz.subtypeoption=let*@typ=comptypedctxtypinlet*@supertype=matchsupertypewith|None->SomeNone|Somesup->let+@r=resolve_type_refdctxsupin(* A supertype must be declared before; a self-reference or a forward
reference within the same rec group is treated as unbound, matching
the validator (rather than crashing). *)ifnot(defined_beforecurrentr)thenError.unbound_named~location:sup.info"type"sup;Somerin(* [descriptor]/[describes] may refer mutually within the rec group, so no
declared-before restriction applies. *)letresolve_opt=function|None->SomeNone|Someidx->require_featuredctx~location:idx.infoWax_utils.Feature.Custom_descriptors;let+@r=resolve_type_refdctxidxinSomerinlet*@descriptor=resolve_optdescriptorinlet+@describes=resolve_optdescribesin{Nz.typ;supertype;final;descriptor;describes}letrectypedctxty=array_mapi_opt(funielt->subtypedctxi(sndelt.desc))ty(* Replace a leading [..] splice sentinel in each struct of the rec group with
the supertype's fields. Called after the group's names are temporarily
registered (so an in-group supertype resolves), and expands members in source
order so an earlier member is already expanded when a later one inherits from
it. Returns a fresh array; the parsed module AST keeps its sentinel (for
[format] / decompilation round-trip), while the internal type and [ctx.types]
get the expanded fields. *)letexpand_splicesdctxty=letexpanded=Array.copytyinArray.iteri(funielt->letname,(sub:subtype)=elt.descinmatchsub.typwith|StructfieldswhenArray.lengthfields>0&&Ast.is_splice_fieldfields.(0)->letdelta=Array.subfields1(Array.lengthfields-1)inletparent_fields=matchsub.supertypewith|None->Error.splice_without_supertyped~location:fields.(0).info;None|Somesup->(matchTbl.find_optctx.typessupwith|Some(idx,parent)->((* An in-group member is a [Rec]; use its already-expanded
form. A self/forward reference ([j >= i]) is reported as
unbound by [subtype], so skip. *)letparent=matchidxwith|Wax_wasm.Types.Recj->ifj<ithenSome(sndexpanded.(j).desc)elseNone|Def_->Someparentinmatchparentwith|Some{typ=Structpf;_}->Somepf|Some_->Error.splice_non_structd~location:sup.infosup;None|None->None)|None->None(* unbound supertype: reported by [subtype] *))inletfields'=matchparent_fieldswith|Somepf->Array.appendpfdelta|None->deltainexpanded.(i)<-{eltwithdesc=(name,{subwithtyp=Structfields'})}|_->())ty;expanded(* The built-in type names a [type] declaration (or a [rec] member) may not
take: [T::] extends to declared types, making the [::] left-hand side one
namespace shared by the intrinsic namespaces and user types, so the
built-ins must stay unambiguous ([&i64] is the value type, [atomic::fence]
the intrinsic, …). The valtypes, the abstract heap types (the parser's
[absheaptype_tbl] set), and the [atomic] intrinsic namespace ([v128]/[i64]
are already valtypes; [cont] is a keyword). [From_wasm] renames a [$type]
that collides (see [Namespace.reserved_heap_types]). *)letreserved_type_names=["i32";"i64";"f32";"f64";"v128"(* the value types *);"any";"array";"eq";"exn";"extern";"func";"i31";"nocont";"noexn";"noextern";"nofunc";"none";"struct"(* the abstract heap types *);"atomic"(* the intrinsic namespace *);]letadd_typedctxty=Array.iteri(funielt->letname,(typ:subtype)=elt.descinifList.memname.descreserved_type_namesthenError.reserved_type_named~location:name.infoname;Tbl.adddctx.typesname(Wax_wasm.Types.Reci,typ))ty;(* Expand [..] splices before building the internal type and before the final
[ctx.types] override below, so both see the supertype's fields. *)letty=expand_splicesdctxtyinmatchrectypedctxtywith|None->(* Remove temporary names on failure *)Array.iter(funelt->Tbl.removectx.types(fstelt.desc))ty;None|Someity->(* Well-formedness of [descriptor]/[describes] clauses, which must link two
struct types within the same recursion group. In [ity] a [Rec] reference
names a member of this group; a [Def] denotes an already-defined type
outside it. *)Array.iteri(funi(sub:Nz.subtype)->letlocation=ty.(i).infoin(matchsub.descriptorwith|None->()|Some(Def_)->Error.descriptor_outside_rec_groupd~location~described:false|Some(Recpos)->(matchity.(pos).describeswith|Some(Reco)wheno=i->()|_->Error.descriptor_not_reciprocald~location~described:false));(matchsub.describeswith|None->()|Some(Def_)->Error.descriptor_outside_rec_groupd~location~described:true|Some(Recpos)->(ifpos>=ithenError.forward_use_of_describedd~location;matchity.(pos).descriptorwith|Some(Recdd)whendd=i->()|_->Error.descriptor_not_reciprocald~location~described:true));if(sub.descriptor<>None||sub.describes<>None)&&matchsub.typwithStruct_->false|_->truethenError.descriptor_not_structd~location~described:(sub.describes<>None))ity;leti'=Wax_wasm.Types.add_rectypectx.internal_typesityin(* The type space grew, so any memoised subtyping info is stale. *)ctx.subtyping_info_cache<-None;Array.iteri(funielt->letname,(typ:subtype)=elt.descinTbl.overridectx.typesname(Wax_wasm.Types.Def(Wax_wasm.Types.Id.addi'i),typ))ty;Somei'(*** The module context ***)typemodule_context={(* --- Diagnostics and whole-run configuration --- *)diagnostics:Wax_utils.Diagnostic.context;warn_unused:bool;(* Whether to report locals declared by a [let] but never read. Enabled
only when validation is requested. *)simplify:bool;(* Whether to rewrite the AST while typing: drop casts the inferred types
make redundant and tighten [&?extern]/[&?any] casts to
[&extern]/[&any]. Enabled only when converting from Wasm; for
hand-written Wax (formatting, or compiling to Wasm) casts are kept as
written. *)(* --- Module-wide type and name tables (built once, before any body) --- *)type_context:type_context;types:(Wax_wasm.Types.ref_index*subtype)Tbl.t;(* Per function: interned type index, type name, and whether a reference to it
is exact (a defined function or an exact import — custom-descriptors). *)functions:(Wax_wasm.Types.Id.t*string*bool)Tbl.t;globals:(*mutable:*)(bool*inferred_valtypeoption)Tbl.t;(* As for [locals], the type is [None] for a global whose initializer
failed to type — a poison global read as [Error] to avoid cascades. *)import_globals:(bool*inferred_valtypeoption)Tbl.t;(* The globals in scope for a table initializer: only the imported ones.
A table is typed before the module's own globals are registered, so its
initializer can reference only imports (unlike a global initializer,
which sees the globals declared before it). *)tags:functypeTbl.t;memories:(int*[`I32|`I64])Tbl.t;datas:unitTbl.t;tables:([`I32|`I64]*reftype)Tbl.t;elems:reftypeTbl.t;structs_by_fields:(string,identoption)Hashtbl.t;(* Maps a struct's canonical field-set key (see [field_set_key]) to the
unique struct type with that field set, or [None] when several share
it. Lets a struct literal whose name is omitted resolve from its fields
alone (and the name be dropped when the fields make it unambiguous).
Built once at module-context creation. *)(* --- Per-function state (reset on entry to each function) --- *)mutablelocals:(inferred_valtypeoption*location)StringMap.t;(* The local's type paired with its binding site's source span (for
go-to-definition). The type is [None] when it could not be determined
because its initializer failed to type — an error-recovery "poison"
local, read as the [Error] type so its uses don't cascade into further
errors. *)mutableinitialized_locals:StringSet.t;(* Locals known to hold a value at the current point. A non-defaultable
(non-nullable reference) local starts uninitialized and must be
assigned before it is read. The set is captured by [{ ctx with ... }]
on block entry, so an assignment inside a block does not escape it. *)read_locals:StringSet.tref;(* Names of locals read so far in the current function. A [ref] (rather
than a snapshot field) so reads inside a block propagate to the
function level. Reset per function. *)local_decls:identlistref;(* The [let]-bound locals declared in the current function, in declaration
order, so an unread one can be reported as unused. Reset per function. *)used_labels:StringSet.tref;(* Names of block labels branched to so far in the current function
(marked by [branch_target]). A [ref] so a branch nested in a block
propagates to the function level. Reset per function. *)label_decls:identlist;(* The block labels declared in the current function's body, collected up
front from the source AST (see [collect_labels]), so one never branched
to can be reported as unused. Reset per function. *)assigned_locals:StringSet.t;(* Names of locals assigned ([Set]/[Tee] targets) anywhere in the current
function, collected once on entry (see [collect_assigned_locals]). Lets
the annotation-drop on a fused [let x: T = e] tell a write-once local —
which may narrow to [e]'s subtype just like an immutable global — from
one a later assignment still needs the wider [T] for. Reset per
function. *)control_types:(labeloption*inferred_typeCell.tarray)list;(* Each enclosing control frame's label (kept as its [ident], so a branch
can be linked to the labelled construct for go-to-definition) and the
types it delivers. *)return_types:inferred_typeCell.tarray;(* --- Conditional-compilation branch assumption --- *)cond:Cond.tref;(* Current branch assumption (shared with every namespace/table above);
set while typing a conditional branch so names resolve per branch. *)cond_env:Cond.env;resolve_links:resolve_sink;(* Where use -> definition references are recorded (locals via
[resolve_variable], labels via [branch_target]; module fields via
[Tbl.resolve] through the namespaces). The same sink the namespaces
hold; [None] outside the editor. *)pun_spans:locationlistrefoption;(* The span of each punned struct-literal field (the bare-name form,
[x] standing for [x: x]), recorded at the field name. Such a span is
both a field name and a variable use, so the editor must expand it
([x] -> [x: new]) rather than replace it on rename. [None] outside the
editor. *)member_completions:(location*member_receiver)listrefoption;(* At each struct field access [recv.field], the field-name span paired
with the receiver's members (a struct's fields, or the value methods of
a numeric / array receiver), for member completion. The editor offers
those when the cursor is on the (possibly partial) field. [None]
outside the editor. *)}(* The subtyping info for the current type space, memoised on [type_context] and
rebuilt on demand after [add_type] invalidates it. Always current, so a
subtyping query on a type minted while type-checking (an inline [&fn(..)] cast
target) sees it rather than indexing past a stale snapshot. *)letsubtyping_infoctx=matchctx.type_context.subtyping_info_cachewith|Someinfo->info|None->letinfo=Wax_wasm.Types.subtyping_infoctx.type_context.internal_typesinctx.type_context.subtyping_info_cache<-Someinfo;info(*** Name resolution and subtyping ***)(* Type [f] under the assumption of a conditional branch ([positive] for
[@then], negative for [@else]), restoring the previous assumption after. *)letwith_cond_refcond_refcond_envdiagnostics~locationcondpositivef=letsaved=!cond_refinletc=Cond.of_condcond_envdiagnostics~locationcondincond_ref:=Cond.and_saved(ifpositivethencelseCond.not_c);Fun.protect~finally:(fun()->cond_ref:=saved)fletwith_condctx~locationcondpositivef=with_cond_refctx.condctx.cond_envctx.diagnostics~locationcondpositivefletlookup_func_type?locationctxname=let*@ty=Tbl.find_optctx.type_context.typesnameinmatch(sndty).typwith|Funcf->Somef|Struct_|Array_|Cont_->Error.expected_func_typectx.diagnostics~location:(Option.value~default:name.infolocation);Noneletlookup_struct_type?locationctxname=let*@ty=Tbl.find_optctx.type_context.typesnameinmatch(sndty).typwith|Structfields->Somefields|Func_|Array_|Cont_->Error.expected_struct_typectx.diagnostics~location:(Option.value~default:name.infolocation);None(* A canonical key for a set of field names, so two structs with the same fields
(in any order) get the same key. Identifiers never contain a comma. *)letfield_set_keynames=String.concat","(List.sort_uniqcomparenames)(* The unique struct type whose field-set matches the literal's [fields], or
[None] when none or several do (then the type is ambiguous and must be
named). O(#fields) given the precomputed [ctx.structs_by_fields] map. *)letinfer_struct_by_fieldsctxfields=letkey=field_set_key(List.map(fun(idx,_)->idx.desc)fields)inmatchHashtbl.find_optctx.structs_by_fieldskeywith|Some(Somename)->Somename|SomeNone|None->None(* A struct-literal field's value. A punned field ([None], written [{x}]) stands
for the like-named local/global, i.e. [Get x]; typing resolves it to that
explicit [Get], which is what is type-checked and emitted, so lowering never
sees a pun. *)letfield_value(name:ident)=function|Somei->i|None->{desc=Getname;info=name.info}letlookup_array_type?locationctxname=let*@ty=Tbl.find_optctx.type_context.typesnameinmatch(sndty).typwith|Arrayfield->Somefield|Func_|Struct_|Cont_->Error.expected_array_typectx.diagnostics~location:(Option.value~default:name.infolocation);None(* The composite type of a synthesized type (its name starting with ['<'], e.g.
[<string>] or an inline function type) — used as the [anon_comptype] of an
[inferred_valtype] so a reference to it renders by that composite type rather
than by its meaningless synthetic name. [None] for a source-named type. *)letinline_comptypectx(name:ident)=ifname.desc<>""&&name.desc.[0]='<'thenOption.map(fun(_,(sub:subtype))->sub.typ)(Tbl.find_optctx.type_context.typesname)elseNone(* The name of the function type a continuation type wraps. *)letlookup_cont_inner?locationctxname=let*@ty=Tbl.find_optctx.type_context.typesnameinmatch(sndty).typwith|Contft->Someft|Func_|Struct_|Array_->Error.expected_func_typectx.diagnostics~location:(Option.value~default:name.infolocation);Nonelettop_heap_typectx(t:heaptype):heaptypeoption=matchtwith|Any|Eq|I31|Struct|Array|None_->SomeAny|Func|NoFunc->SomeFunc|Exn|NoExn->SomeExn|Cont|NoCont->SomeCont|Extern|NoExtern->SomeExtern|Typety|Exactty->(let+@ty=Tbl.findctx.diagnosticsctx.typestyinmatch(sndty).typwith|Struct_|Array_->Any|Func_->Func|Cont_->Cont)(* Whether a heap type belongs to the continuation hierarchy, without reporting
an unbound reference (the caller's normal resolution handles that). *)letis_cont_heaptypectx(t:heaptype)=matchtwith|Cont|NoCont->true|Typety|Exactty->(matchTbl.find_optctx.typestywith|Somex->(match(sndx).typwithCont_->true|_->false)|None->false)|Any|Eq|I31|Struct|Array|None_|Func|NoFunc|Exn|NoExn|Extern|NoExtern->falseletdiff_ref_typet1t2={nullable=t1.nullable&¬t2.nullable;typ=t1.typ}letstorage_subtypectxtyty'=match(ty,ty')with|PackedI8,PackedI8|PackedI16,PackedI16->true|Valuety,Valuety'->Option.value~default:true(* Do not generate a spurious error *)(let*@ty=valtypectx.diagnosticsctx.type_contexttyinlet+@ty'=valtypectx.diagnosticsctx.type_contextty'inWax_wasm.Types.val_subtype(subtyping_infoctx)tyty')|PackedI8,PackedI16|PackedI16,PackedI8|Packed_,Value_|Value_,Packed_->falseletstorage_subtype'ctx(ty:Wax_wasm.Types.Internal.storagetype)(ty':Wax_wasm.Types.Internal.storagetype)=match(ty,ty')with|PackedI8,PackedI8|PackedI16,PackedI16->true|Valuety,Valuety'->Wax_wasm.Types.val_subtype(subtyping_infoctx)tyty'|PackedI8,PackedI16|PackedI16,PackedI8|Packed_,Value_|Value_,Packed_->falseletfield_subtypeinfo(ty:Wax_wasm.Types.Internal.fieldtype)(ty':Wax_wasm.Types.Internal.fieldtype)=ty.mut=ty'.mut&&storage_subtype'infoty.typty'.typ&&((notty.mut)||storage_subtype'infoty'.typty.typ)(* Whether [ty] is the result cell of a block whose type is being inferred. *)letis_inferringty=matchCell.gettywithCollecting_->true|_->false(* The type a value passing through a branch to [ty] takes: it continues on the
stack typed as the target's result. When the target is a block being inferred
([Collecting]) with a declared result (an annotation under test, or the context
type in expression position), that result is the right type — resolve to it, so
the pass-through is typed as the block's result rather than its own, possibly
narrower, operand (which would be unsound, see [Collecting.exacts]). With no
declared result (a fully-inferred block) the [Collecting] cell would leak as a
value, so the caller keeps the operand's own type instead. *)letrecresolve_declaredty=matchCell.gettywith|Collecting{declared=Somed;_}->resolve_declaredd|_->ty(* Whether the inferred type [ty] is a subtype of the expected type [ty'].
Not a pure relation: when the two are compatible it *unifies* their
union-find cells (so an as-yet-unconstrained literal like [Int]/[Number]
gets pinned to the concrete type it is checked against). [Unknown] or [Error]
on the left (dead code / error recovery) is a subtype of anything;
[UnknownRef] is a subtype of every reference type but of no other (so a
numeric use of it is rejected). None of the three appears on the right
because expected types always come from a real declaration, annotation or
instruction signature — hence the [assert]. *)letrecsubtype?locationctxtyty'=letity=Cell.gettyinletity'=Cell.getty'inmatch(ity,ity')with(* [ty'] is a block result being inferred. Record [ty]'s natural type — a
snapshot taken before any validation below resolves it — as a value reaching
the block's exit, to be joined later (see [block_infer_general]); pair it with
[location] when the caller has one, so a join failure can point at the exit.
When an annotation is under test ([declared]), also validate [ty] against it
per-delivery and return that result, so a [br]/catch carrying the wrong type
is reported precisely at its site rather than once, generically, at the join.
A [Collecting] cell never appears as a real value type, so the left-hand
cases below treat it like [Unknown]. *)|_,Collectingst->(matchst.declaredwith|Somed->(* An annotation is under test: the [subtype] check below may resolve
[ty], so record a snapshot of its natural type first — the keep-bool
decision compares that pre-validation type against the annotation. *)st.collected<-(location,Cell.makeity)::st.collected;subtype?locationctxtyd|None->(* No annotation under test, so nothing here resolves [ty]: record the
live cell. When the join later settles the block's result to a
concrete width, that propagates back to a flexible numeric literal
reaching the exit (the only types [join_value_types] merges) — else
the literal keeps its default width and [To_wasm] emits, e.g., an f64
const as the fall-through of an f32-typed block (invalid). *)st.collected<-(location,ty)::st.collected;true)|Collecting_,_->true|Valtypety,Valtypety'->Wax_wasm.Types.val_subtype(subtyping_infoctx)ty.internalty'.internal(* A flexible numeric literal ([Number]/[Int]/[LargeInt]/[Float]) never appears
as the expected (right-hand) type: an expected type comes from a declaration,
annotation or instruction signature — always a concrete valtype, or a
[Collecting] block result (handled above). This is the numeric counterpart of
the [Unknown]/[Error]/[UnknownRef] right-hand assertion below. *)|_,(Number|Int|LargeInt|Float)->assertfalse|Null,Null->Cell.mergetyty'ity;true|Number,Valtype{internal=I32|I64|F32|F64;_}|Int,Valtype{internal=I32|I64;_}|Float,Valtype{internal=F32|F64;_}(* LargeInt — a numeric literal too big for i32: never i32, defaults to i64; the
concrete types it accepts are i64, f32 and f64. *)|LargeInt,Valtype{internal=I64|F32|F64;_}|Null,Valtype{internal=Ref{nullable=true;_};_}->Cell.mergetyty'ity';true|(Null,Valtype{internal=I32|I64|F32|F64|V128|Ref{nullable=false;_};_;})|Valtype_,Null|Number,(Null|Valtype{internal=V128|Ref_;_})|Int,(Null|Valtype{internal=F32|F64|V128|Ref_;_})|Float,(Null|Valtype{internal=I32|I64|V128|Ref_;_})|LargeInt,(Null|Valtype_)->false|(Int8|Int16),_|_,(Int8|Int16)->false|(Unknown|Error),_->true|UnknownRef,(Valtype{internal=Ref_;_}ast)->(* The bottom reference is a subtype of every reference; pin it to the
hierarchy it is checked against, so it resolves to a concrete type in
that hierarchy rather than the default any-hierarchy [&none]. *)Cell.settyt;true|_,(Unknown|Error|UnknownRef)->assertfalse|UnknownRef,_->falseletcastctxtyty'=letity=Cell.gettyinmatch(ity,ty')with|(Number|Int),Ref{typ=I31|Extern;_}->Cell.setty(Valtypei32_valtype);true|(Number|Int),I32->Cell.setty(Valtypei32_valtype);true|(Number|Int),I64->Cell.setty(Valtypei64_valtype);true(* A still-flexible numeric literal ([Number]) folds straight to the target
float constant. A value already committed to a family — [Int] (an integer
operation such as [x & y] or [clz]) or [Float] (a float operation, or a
float literal) — is *not* accepted here for the opposite family: a plain
[int <-> float] cast needs a signedness ([as f32_s], [as i32_u]) to lower to
a [convert]/[trunc], so it falls through to the cast error, exactly as a cast
of a concrete [i32]/[f32] value does. (An integer-to-float [convert] would
carry a sign, as [signed_cast].) *)|(Number|Float),F32->Cell.setty(Valtypef32_valtype);true|(Number|Float),F64->Cell.setty(Valtypef64_valtype);true(* The literal is always i64 here since it is too big for i32. A cast to
[i32] wraps it (the low 32 bits), as produced when decompiling e.g.
[i64.extend32_s] of a constant; a cast to [i64] is the identity. *)|LargeInt,(I32|I64)->Cell.setty(Valtypei64_valtype);true(* A cast to a float folds the literal to a float constant, exactly like a
small [Number] literal above (a runtime integer-to-float [convert] would
carry a sign, as [signed_cast]). Settle the operand at the target float type
so [to_wasm] emits [f32.const]/[f64.const] rather than an unlowerable [i64]
value. *)|LargeInt,F32->Cell.setty(Valtypef32_valtype);true|LargeInt,F64->Cell.setty(Valtypef64_valtype);true(* [ref.i31] takes an [i32]; the i64-sized literal wraps to [i32] first, exactly
like [i64 as &i31] below ([to_wasm] re-emits [i32.wrap_i64] then [ref.i31]).
This is the residue of [(big as i32) as &i31] after [simplify] fuses the
inner wrap into the [i31] cast. *)|LargeInt,Ref{typ=I31;_}->Cell.setty(Valtypei64_valtype);true|LargeInt,_->false(* not v128 or another reference *)|Null,Ref{typ=ty';_}->(let>@typ=top_heap_typectxty'inletty'=Ref{nullable=true;typ}inlet>@ity'=valtypectx.diagnosticsctx.type_contextty'inCell.setty(Valtype{typ=ty';internal=ity';anon_comptype=None}));true|Valtype{internal=F32|F64;_},(F32|F64)|Valtype{internal=I32|I64;_},I32|Valtype{internal=I64;_},I64|Valtype{internal=V128;_},V128(* [i32 as &i31] is [ref.i31]; [i64 as &i31] wraps to [i32] first. *)|Valtype{internal=I32|I64;_},Ref{typ=I31;_}(* [i32 as &extern]: [ref.i31] then [extern.convert_any]. *)|Valtype{internal=I32;_},Ref{typ=Extern;_}->true|Valtype{internal=Ref_asity;_},Ref{typ=ty';_}->(letsubab=Wax_wasm.Types.val_subtype(subtyping_infoctx)abinOption.value~default:true(let*@typ=top_heap_typectxty'inlet+@ity'=valtypectx.diagnosticsctx.type_context(Ref{nullable=true;typ})insubityity')||(* [extern] <-> [any] across hierarchies ([any.convert_extern] /
[extern.convert_any]), then a [ref.cast] to the concrete target. The
[ref.cast] handles nullability, so only hierarchy membership is checked
here — test the operand against a *nullable* reference regardless of the
target's nullability (a nullable operand cast to a non-null target is a
valid convert-then-null-checking-cast). *)matchty'with|Extern->subity(Ref{nullable=true;typ=Any})|Any->subity(Ref{nullable=true;typ=Extern})|_->Option.value~default:false(let+@top=top_heap_typectxty'in(subity(Ref{nullable=true;typ=Extern})&&top=Any)||(subity(Ref{nullable=true;typ=Any})&&top=Extern)))|((Number|Int|Float|Valtype{internal=I32|F32|I64|F64;_}),(Ref{typ=(Func|NoFunc|Exn|NoExn|Cont|NoCont|Extern|NoExtern|Any|Eq|Array|Struct|Type_|Exact_|None_);_;}|V128))|Valtype{internal=F32|F64;_},(I32|I64)|Valtype{internal=I32|I64;_},(F32|F64)|Valtype{internal=I32;_},I64(* A value committed to one numeric family cast to the other with a plain
(unsigned) cast: it needs a signedness to lower to a [convert]/[trunc], so
it is rejected here (a still-flexible [Number] literal folds above). *)|Int,(F32|F64)|Float,I64|((Float|Valtype{internal=F32|F64|V128;_}),(I32|Ref{typ=I31;_}))|(Null|Valtype{internal=Ref_;_}),(I32|I64|F32|F64|V128)|Valtype{internal=V128;_},(I64|F32|F64|Ref_)|(Int8|Int16),_->false|(Unknown|Error|UnknownRef|Collecting_),_->trueletsigned_castctxtyty'=letity=Cell.gettyinmatch(ity,ty')with|(Int8|Int16),(`I32|`I64)->true|Valtype{internal=Ref_asity;_},(`I32|`I64)->(* [i31.get] extracts an [i32]; [&ref as i64_X] widens it further. *)Wax_wasm.Types.val_subtype(subtyping_infoctx)ity(Ref{nullable=true;typ=Any})|Null,`I32->Cell.setty(Valtype{typ=Ref{typ=Any;nullable=true};internal=Ref{typ=Any;nullable=true};anon_comptype=None;});true|(Number|Int),(`I64|`F32|`F64)->(* [i64.extend_i32], [f*.convert_i32]: default the integer source to i32. *)Cell.setty(Valtypei32_valtype);true|LargeInt,(`F32|`F64)->(* [f*.convert_i64]: a [LargeInt] source defaults to i64. *)Cell.setty(Valtypei64_valtype);true|LargeInt,(`I32|`I64)->(* The only numeric -> i32/i64 signed cast is a float truncation
([iNN.trunc_f*_X]) — there is no i64->i32 or i64->i64 signed *integer*
conversion — so the flexible source is a float and defaults to f64, as
the [Number, `I32] case below. Without this a [LargeInt] there was
rejected, so a decompiled [iNN.trunc_f* (f*.const <big>)] — which
renders the const as a large integer literal — failed to recompile. *)Cell.setty(Valtypef64_valtype);true|Valtype{internal=I32;_},`I64|Valtype{internal=I32|I64;_},(`F32|`F64)|Valtype{internal=F32|F64;_},(`I32|`I64)->true|Number,`I32->(* The only numeric -> i32 signed cast is a float truncation
([i32.trunc_f*_s]), so a flexible [Number] source is a float and defaults
to f64 (like the [Float] case below). ([Int] is rejected below: no
integer -> i32 signed conversion exists.) *)Cell.setty(Valtypef64_valtype);true|Int,`I32(* no integer-to-i32 signed conversion exists *)|Valtype{internal=I32;_},`I32|Valtype{internal=I64;_},(`I32|`I64)(* A signed cast to a float is an integer->float [convert]; float->float has no
signedness, so a float source (concrete or the abstract [Float]) is rejected
for a float target — only [demote]/[promote] via a plain cast. *)|(Float|Valtype{internal=F32|F64;_}),(`F32|`F64)|(Int8|Int16),(`F32|`F64)|((Null|Valtype{internal=Ref{typ=Type_|Exact_|None_|Struct|Array|I31|Eq|Any;_;};_;}),(`I64|`F32|`F64))|(Valtype{internal=(V128|Ref{typ=(Func|NoFunc|Exn|NoExn|Cont|NoCont|Extern|NoExtern);_;});_;},_)->false(* A bare float literal carries the abstract [Float]; default it to its
canonical f64 (like the concrete [F32 | F64] arms above) so a strict cast on
it — e.g. [1.5 as i64_s_strict], the [i64.trunc_f64_s] a decompiled
[f64.const] produces — type-checks instead of being rejected as float. *)|Float,(`I32|`I64)->Cell.setty(Valtypef64_valtype);true(* A polymorphic reference (the bottom [UnknownRef], e.g. [null!] in dead code)
is a reference: a signed cast to [i32]/[i64] is [i31.get] (as for a concrete
any-hierarchy reference above), but it can never convert to a float. *)|UnknownRef,(`I32|`I64)->true|UnknownRef,(`F32|`F64)->false|(Unknown|Error|Collecting_),_->true(*** The typing stack ***)typestack=|Unreachable|Empty|Consoflocation*inferred_typeCell.t*stackletrecoutput_stackppst=letmoduleSP=Wax_utils.Styled_printerinmatchstwith|Empty->()|Unreachable->Wax_utils.Printer.spacepp.SP.printer();SP.print_styledppWax_utils.Colors.Keyword"unreachable"|Cons(_,ty,st)->Wax_utils.Printer.spacepp.SP.printer();output_inferred_type_styledppty;output_stackppstletprint_stackst=Wax_utils.Printer.runFormat.err_formatter(funp->letpp=Wax_utils.Styled_printer.create~printer:p~theme:Wax_utils.Colors.no_color~trivia:(Hashtbl.create0)()inWax_utils.Printer.stringp"Stack:";output_stackppst);(st,())let_=print_stack(* The typing monad. A monadic action is a function [stack -> stack * 'a]: it
reads the operand stack, may push/pop, and returns the new stack alongside
its result. This threads the stack implicitly so the instruction cases read
top-to-bottom instead of passing [st] by hand. The operators:
- [return v] lift a value, leaving the stack unchanged;
- [let* x = e] bind: run [e], thread its stack into the continuation;
- [let*! x = e] run [e : _ option], short-circuiting a [None] (a failed
lookup) by returning an [unreachable] recovery instruction
so typing continues without cascading errors;
- [unreachable e] run [e] but mark the resulting stack [Unreachable] (the
polymorphic stack of code after a [br]/[return]/etc.).
Not to be confused with the pure-option [let*@]/[let+@]/[let>@] above. *)letunreachableest=let_,v=estin(Unreachable,v)letreturnvst=(st,v)let(let*)efst=letst,v=estinfvstlet(let*!)ef=matchewith|Somev->fv|None->return{desc=Ast.Unreachable;info=([|Cell.makeError|],(Ast.no_loc()).info);}(* Pop the top operand's type. An [Unreachable] (polymorphic) stack yields a
fresh [Unknown] and consumes nothing; [Empty] is a genuine stack underflow. *)letpop_anyctxist=matchstwith|Unreachable->(st,Cell.makeUnknown)|Cons(_,ty,r)->(r,ty)|Empty->Error.empty_stackctx.diagnostics~location:i.info;(st,Cell.makeError)letrecpop_manyctxinaccu=ifn=0thenreturnaccuelselet*ty=pop_anyctxiinpop_manyctxi(n-1)(ty::accu)(*ZZZ This is for block parameters and return values:
there should be n .. on the stack, but there are ...
(with type)
The nth argument should have type BLA but has type BLA
(unless we have a locationfrom the stack)
*)letpopctx~locationtyst=matchstwith|Unreachable->(st,())|Cons(loc,ty',r)->(matchCell.getty'with|Error->(* The top value is the poison of an already-reported error. Leave it
on the stack instead of consuming it — like the polymorphic
[Unreachable] case above — so it keeps suppressing leftover-stack
diagnostics in this scope (see [with_empty_stack]). Consuming it
would strip the poison and let a cascade surface: e.g. a rejected
instruction recovers as an [Error] value where the correct one was
void, and popping that phantom as the block's result leaves the
genuine value below it reading as a bogus leftover. *)(st,())|_->ifnot(subtypectxty'ty)thenError.type_mismatchctx.diagnostics~location:locty'ty;(r,()))|Empty->Error.empty_stackctx.diagnostics~location;(st,())letpop_argsctx~locationargs=Array.fold_right(funtyrem->let*()=reminpopctx~locationty)args(return())letpushloctyst=(Cons(loc,ty,st),())letrecpush_resultsresults=matchresultswith|[]->iffalsethenprerr_endline"PUSH";return()|(loc,ty)::rem->let*()=pushloctyinpush_resultsremtypeempty_stack_context=Expression|Block|Functionletwith_empty_stackctx~kind:_~locationf=letst,res=fEmptyin(* Decide what to report about values still on the stack. A value of type
[Error] is the poison of an already-reported error, so if any leftover
carries it the stack is unreliable and the whole diagnostic is a cascade —
suppress it. Otherwise the leftovers are genuine values and are reported: a
caret on each that has a source location, or — for values that carry only
an error-recovery placeholder location, which are still real values, just
not locatable — the construct itself. [scan] gathers the locatable values
(topmost first, after the final [List.rev]) and whether any [Error] value
is present. *)letrecscanhas_errorlocs=function|Cons(loc,cell,st)->lethas_error=has_error||matchCell.getcellwithError->true|_->falseinletlocs=ifloc.loc_start.Lexing.pos_cnum>=0thenloc::locselselocsinscanhas_errorlocsst|Empty|Unreachable->(has_error,List.revlocs)in(matchstwith|Empty|Unreachable->()|Cons_->(matchscanfalse[]stwith|true,_->()(* poison on the stack: an already-reported cascade *)|false,location::rest->(* Point a caret right at each locatable leftover value rather than at
the (potentially large) enclosing construct. *)letrelated=List.map(funlocation->{Wax_utils.Diagnostic.location;message=Wax_utils.Message.empty;})restinError.leftover_valuesctx.diagnostics~location~related|false,[]->(* Real values remain but none carries a usable location: name the
construct and list what is on the stack. *)Error.non_empty_stackctx.diagnostics~location(funpp->output_stackppst)));res(*** Instruction-checking helpers ***)letinternalize_valtypectxtyp=let+@internal=valtypectx.diagnosticsctx.type_contexttypin{typ;internal;anon_comptype=None}letinternalize?inlinectxtyp=let+@internal=valtypectx.diagnosticsctx.type_contexttypinvaltype_cell{typ;internal;anon_comptype=inline}(* Check that a source element reference type can be stored where [dst] elements
are expected (table.copy / table.init / array.init_elem): [src] must be a
subtype of [dst]. *)letcheck_elem_subtypectx~location~src~dst=match(internalize_valtypectx(Refsrc),internalize_valtypectx(Refdst))with|Somes,Somed->ifnot(Wax_wasm.Types.val_subtype(subtyping_infoctx)s.internald.internal)thenError.incompatible_element_typectx.diagnostics~location(valtype_cells)(valtype_celld)|_->()(* The inferred type of a value read from a field: a packed [i8]/[i16] field
reads back as the unpacked [Int8]/[Int16] cell, any other as its value type.
(Distinct from the [fieldtype] type converter above, which maps a source
field type to its [Internal] form.) *)letfield_read_typectx(f:fieldtype)=matchf.typwith|Valuetyp->internalizectxtyp|PackedI8->Some(Cell.makeInt8)|PackedI16->Some(Cell.makeInt16)letunpack_type(f:fieldtype)=matchf.typwithValuev->v|Packed_->I32letbranch_targetctxlabel=letrecfindllabel=matchlwith|[]->letsuggestions=Wax_utils.Spell_check.f(funf->List.iter(fun(l,_)->Option.iter(funl->fl.desc)l)ctx.control_types)label.descinError.unbound_namectx.diagnostics~location:label.info~suggestions"label"label;[||]|(Somelabel',res)::_whenlabel.desc=label'.desc->ctx.used_labels:=StringSet.addlabel.desc!(ctx.used_labels);record_referencectx.resolve_linkslabel.info[label'.info];res|_::rem->findremlabelinfindctx.control_typeslabel(* Draw "did you mean" suggestions from the namespaces an identifier may
legitimately name, which depends on how it is used:
- [Get] reads any value, so a local, a global or a function;
- [Set] assigns, so a local or a mutable global;
- [Tee] only ever targets a local. *)letget_suggestionsctxname=Wax_utils.Spell_check.f(funf->StringMap.iter(funk_->fk)ctx.locals;Tbl.iterctx.globals(funk_->fk);Tbl.iterctx.functions(funk_->fk))nameletset_suggestionsctxname=Wax_utils.Spell_check.f(funf->StringMap.iter(funk_->fk)ctx.locals;Tbl.iterctx.globals(funk(mut,_)->ifmutthenfk))nameletlocal_suggestionsctxname=Wax_utils.Spell_check.f(funf->StringMap.iter(funk_->fk)ctx.locals)name(* One-line summaries of a resolved reference, rendered the way diagnostics do,
for a hover on a name that is not itself an expression (a type reference, a
[Set]/[Tee] target, a bare global). A poison value ([None]) has no summary. *)lethover_of_valtypety=Option.map(funity->Value_typeity)tylethover_of_global((_,ty):bool*inferred_valtypeoption)=hover_of_valtypetylethover_of_type((_,st):Wax_wasm.Types.ref_index*subtype)=Some(Type_defst)(* A name in value position resolves, in order, to a local, then a global, then
a function (as a non-null reference); [Get]/[Set]/[Tee] share this ladder and
only differ in what they do with each outcome. *)typeresolved_var=|Localofinferred_valtypeoption|Globalofbool(* mutable *)*inferred_valtypeoption|Func_refofWax_wasm.Types.Id.t*string*bool|Unboundletresolve_variablectxidx=matchStringMap.find_optidx.descctx.localswith|Some(ty,def)->record_reference~hover:(hover_of_valtypety)ctx.resolve_linksidx.info[def];Localty|None->(matchTbl.find_optctx.globalsidxwith|Some(mut,ty)->Global(mut,ty)|None->(matchTbl.find_optctx.functionsidxwith|Some(ty,ty',exact)->Func_ref(ty,ty',exact)|None->Unbound))(* Whether [name] denotes a memory (resp. table) usable as a method/index
receiver — [mem.load(..)], [tab[..]], [tab.size()]. A local of the same name
shadows it: Wax resolves a bare name to a local first, and globals, functions,
memories and tables share one namespace (so only a local can collide), so the
receiver form must defer to the local just as [Get name] does. *)letmemory_receiverctxname=(not(StringMap.memname.descctx.locals))&&Tbl.find_optctx.memoriesname<>Nonelettable_receiverctxname=(not(StringMap.memname.descctx.locals))&&Tbl.find_optctx.tablesname<>None(* Likewise for a data/element segment named by [seg.drop()] (and the segment
operand of [mem.init]/[tab.init]/array segment ops): usable as such only when
not shadowed by a local. *)letsegment_receiverctxname=(not(StringMap.memname.descctx.locals))&&(Tbl.find_optctx.datasname<>None||Tbl.find_optctx.elemsname<>None)(* When [e] is an atomic narrow-load call [mem.atomic_load8/16(p)] (whose
raw-bits result a cast resolves), its access width. Used to reject an
[as iN_s] cast on it: only the zero-extending [_u] atomic loads exist. *)letatomic_narrow_load_widthctxe=matche.descwith|Call({desc=StructGet({desc=Getmemname;_},meth);_},_)whenmemory_receiverctxmemname->(matchWax_wasm.Atomics.of_method_namemeth.descwith|Some(Wax_wasm.Atomics.Load((`W8|`W16)asw))->Somew|_->None)|_->None(* Check the operands of an integer (resp. float) binary operator and return
the unified result-type cell — the two operand cells are merged on success,
so the caller takes [typ1] as the operator's result type. *)letcheck_int_bin_opctx~locationtyp1typ2=(match(Cell.gettyp1,Cell.gettyp2)with|Valtype{internal=I32;_},Valtype{internal=I32;_}|Valtype{internal=I64;_},Valtype{internal=I64;_}|(Valtype{internal=I32|I64;_}|Int),(Number|Int)->Cell.mergetyp1typ2(Cell.gettyp1)|(Number|Int),Valtype{internal=I32|I64;_}->Cell.mergetyp1typ2(Cell.gettyp2)|Number,Number->Cell.mergetyp1typ2Int(* A LargeInt operand forces i64: it pairs with i64 or another flexible integer
(never i32). *)|Valtype{internal=I64;_},LargeInt->Cell.mergetyp1typ2(Cell.gettyp1)|LargeInt,Valtype{internal=I64;_}->Cell.mergetyp1typ2(Cell.gettyp2)(* An integer-only operator pins every [LargeInt] operand to i64: it exceeds
i32, and the result is a committed integer (never a float), so the pair takes
i64 rather than the still-float-capable [LargeInt]. *)|LargeInt,(LargeInt|Number|Int)|(Number|Int),LargeInt->Cell.mergetyp1typ2(Valtypei64_valtype)(* A fully-flexible [Number] on the left pairs with a flexible [Int] (the
symmetric [Int, Number] and [Number, Number] cases are above). *)|Number,Int->Cell.mergetyp1typ2Int|_->Error.binop_type_mismatchctx.diagnostics~locationtyp1typ2);typ1letcheck_float_bin_opctx~locationtyp1typ2=(match(Cell.gettyp1,Cell.gettyp2)with|Valtype{internal=F32;_},Valtype{internal=F32;_}|Valtype{internal=F64;_},Valtype{internal=F64;_}|(Valtype{internal=F32|F64;_}|Float),(Number|Float|LargeInt)->Cell.mergetyp1typ2(Cell.gettyp1)|(Number|Float|LargeInt),Valtype{internal=F32|F64;_}->Cell.mergetyp1typ2(Cell.gettyp2)(* Two flexible operands of a float operator (the [Float, _] cases are above):
a large-int literal is taken as a float here, so anything pairs to [Float]. *)|(Number|LargeInt),(Number|Float|LargeInt)->Cell.mergetyp1typ2Float|_->Error.binop_type_mismatchctx.diagnostics~locationtyp1typ2);typ1(* Check and unify the operands of a numeric binary operator that accepts either
integers or floats (+, -, *, ==, !=); the two cells are merged to their common
type. Operands here are concrete or flexible numeric literals — the caller
handles the abstract [Unknown]/[Error] arms. Two fully-flexible [Number]s stay
[Number] (the operator could still resolve either way); any more committed
operand pins the pair to its group. Mirrors [check_int_bin_op] (int group) and
[check_float_bin_op] (float group) unioned. *)letcheck_num_concretectx~locationty1ty2=match(Cell.getty1,Cell.getty2)with|Valtype{internal=I32;_},Valtype{internal=I32;_}|Valtype{internal=I64;_},Valtype{internal=I64;_}|Valtype{internal=F32;_},Valtype{internal=F32;_}|Valtype{internal=F64;_},Valtype{internal=F64;_}->()|(Valtype{internal=I32|I64;_}|Int),(Number|Int)|(Valtype{internal=F32|F64;_}|Float),(Number|Float|LargeInt)->Cell.mergety1ty2(Cell.getty1)|(Number|Int),Valtype{internal=I32|I64;_}|(Number|Float|LargeInt),Valtype{internal=F32|F64;_}->Cell.mergety1ty2(Cell.getty2)|Valtype{internal=I64;_},LargeInt->Cell.mergety1ty2(Cell.getty1)|LargeInt,Valtype{internal=I64;_}->Cell.mergety1ty2(Cell.getty2)(* Two flexible literals (the [Float, _] and [LargeInt, Valtype] cases are
above). A [LargeInt] with a committed [Int] must be an integer — the [Int]
cannot be a float — and a [LargeInt] cannot be i32, so their sole common type
is i64; with another [LargeInt] or a fully-flexible [Number] it stays
[LargeInt] (the operator could still resolve to a float). *)|LargeInt,Int|Int,LargeInt->Cell.mergety1ty2(Valtypei64_valtype)|LargeInt,(LargeInt|Number)|Number,LargeInt->Cell.mergety1ty2LargeInt|LargeInt,Float->Cell.mergety1ty2Float|Number,Float->Cell.mergety1ty2Float|Number,Int->Cell.mergety1ty2Int|Number,Number->Cell.mergety1ty2Number|_->Error.binop_type_mismatchctx.diagnostics~locationty1ty2letfield_has_default(ty:fieldtype)=matchty.typwith|Packed_->true|Valuety->(matchtywith|I32|I64|F32|F64|V128->true|Ref{nullable;_}->nullable)letreturn_statement(i:locationinstr)(desc:(inferred_typeCell.tarray*location)instr_desc)(ty:_array)st=(st,{desc;info=((ty:_array),i.info)})letreturn_expressionidescty=return_statementidesc[|ty|]letexpression_typectxi=lettyp,location=i.infoinmatchtypwith|[|ty|]->ty|_->Error.not_an_expressionctx.diagnostics~location(Array.lengthtyp);Cell.makeErrorletcheck_subtypectx~locationty'ty=(* Pass [location] so that, when [ty] is an inferring block result, the value
is recorded with its branch site (see [Collecting]). *)ifnot(subtype~locationctxty'ty)thenError.instruction_type_mismatchctx.diagnostics~locationty'tyletcheck_subtypesctx~locationtypes'types=ifArray.lengthtypes'<>Array.lengthtypesthenError.value_count_mismatchctx.diagnostics~location~expected:(Array.lengthtypes)~provided:(Array.lengthtypes')elseArray.iter2(funty'ty->check_subtypectx~locationty'ty)types'typesletcheck_typectxity=letty'=expression_typectxiinletok=subtypectxty'tyinifnotokthenError.instruction_type_mismatchctx.diagnostics~location:(sndi.info)ty'ty(*** Lint checks on constant operands ***)(* Parse a Wax integer literal (decimal or [0x] hex, with [_] separators) to an
[int64], or [None] if it is malformed or does not fit. *)letint_literal_values=Int64.of_string_opt(String.concat""(String.split_on_char'_'s))(* Whether [e] is the integer literal equal to [n]. *)letint_literal_value_isn(e:_Ast.instr)=matche.descwithAst.Ints->int_literal_values=Somen|_->false(* Whether [e] is the integer literal zero. *)letint_literal_value_is_zeroe=int_literal_value_is0Le(* [x << n] / [x >> n] with a constant [n] at least the operand's bit width:
Wasm masks [n] modulo the width, so the shift is almost certainly not what
was meant. Only fires when the operand is a concrete i32/i64 (so the width is
known) and [n] a non-negative literal. *)letlint_shiftctxopresultrhs=matchop.descwith|Shl|Shr_->(matchrhs.descwith|Ast.Ints->(match(int_literal_values,Cell.getresult)with|Somen,Valtype{internal=(I32|I64)ast;_}whenn>=0L->letwidth=matchtwithI32->32|_->64inifn>=Int64.of_intwidththenError.shift_overflowctx.diagnostics~location:op.info~widthn|_->())|_->())|_->()(* Integer [/] or [%] by a constant zero always traps. [Div (Some _)] and
[Rem _] are the integer forms ([Div None] is float division, which does not
trap on a zero divisor). *)letlint_divisionctxoprhs=matchop.descwith|(Div(Some_)|Rem_)whenint_literal_value_is_zerorhs->Error.division_by_zeroctx.diagnostics~location:op.info|_->()(* Parse a Wax float literal (decimal or hex float with [_] separators, or the
[nan:0x…] form) to an OCaml float, or [None] if it is malformed. *)letfloat_literal_values=ifString.lengths>=3&&String.equal(String.subs03)"nan"thenSomeFloat.nanelsefloat_of_string_opt(String.concat""(String.split_on_char'_'s))(* The float value of a constant operand, looking through a leading sign. *)letrecfloat_operand_valuei=matchi.descwith|Ast.Floats->float_literal_values|UnOp({desc=Neg;_},e)->Option.mapFloat.neg(float_operand_valuee)|UnOp({desc=Pos;_},e)->float_operand_valuee|_->None(* Whether a trapping (toward-zero) float-to-integer conversion of [f] to the
given target/signage would trap: [f] is NaN or infinite, or its truncation
lies outside the target range. Bounds are the exact powers of two, so a value
is flagged only when it is definitely out of range (no false positives near a
boundary the float type cannot represent exactly). *)letfloat_conversion_trapstargetsignagef=ifnot(Float.is_finitef)thentrueelselett=Float.truncfinletpow2n=Float.ldexp1.ninmatch(target,signage)with|`I32,Signed->t<-.pow231||t>=pow231|`I32,Unsigned->t<0.||t>=pow232|`I64,Signed->t<-.pow263||t>=pow263|`I64,Unsigned->t<0.||t>=pow264(* A trapping float-to-integer conversion ([e as i32_s] and the like — the
[strict] cast forms lower to [trunc], which traps, rather than [trunc_sat])
of a constant float that is out of the target range: it always traps. *)letlint_conversionctx~locationtypoperand=matchtypwith|Signedtype{typ=(`I32|`I64)astarget;signage;strict=true}->(matchfloat_operand_valueoperandwith|Somefwhenfloat_conversion_trapstargetsignagef->Error.conversion_out_of_rangectx.diagnostics~location|_->())|_->()(* Whether two operands are the same pure read (a local or global [get]), so the
two evaluations yield the same value with no side effect. Restricted to [get]
to stay conservative — no calls, no field/array reads that could trap. *)letidentical_operands(l:_Ast.instr)(r:_Ast.instr)=match(l.desc,r.desc)with|Geta,Getb->String.equala.descb.desc|_->false(* A comparison whose result is constant regardless of its variable operand: an
unsigned comparison against zero ([a <u 0] is false, [a >=u 0] is true), or a
comparison of two identical operands ([a < a] is false, [a == a] is true).
The signed/unsigned option marks an integer comparison; [Eq]/[Ne] carry no
signage, so a self-comparison is only flagged for a concrete integer operand
(a float [a == a] is false on NaN, and reference identity is a separate
concern). *)letlint_comparisonctxoplr=letis_inte=matchCell.get(expression_typectxe)with|Valtype{internal=I32|I64;_}->true|_->falseinlettautology=matchop.descwith|Lt(SomeUnsigned)whenint_literal_value_is_zeror->Somefalse|Ge(SomeUnsigned)whenint_literal_value_is_zeror->Sometrue|Gt(SomeUnsigned)whenint_literal_value_is_zerol->Somefalse|Le(SomeUnsigned)whenint_literal_value_is_zerol->Sometrue|(Lt(Some_)|Gt(Some_))whenidentical_operandslr->Somefalse|(Le(Some_)|Ge(Some_))whenidentical_operandslr->Sometrue|Eqwhenidentical_operandslr&&is_intl->Sometrue|Newhenidentical_operandslr&&is_intl->Somefalse|_->Noneinmatchtautologywith|Somevalue->Error.tautological_comparisonctx.diagnostics~location:op.info~value|None->()(* An arithmetic operation with no effect on its result (an identity operand or
two identical operands), or whose result is a constant regardless of the
variable operand (an absorbing operand). Off by default. *)letlint_redundantctxoplr=letis0=int_literal_value_is0Linletis1=int_literal_value_is1Linletno_effect()=Error.redundant_operationctx.diagnostics~location:op.info(Wax_utils.Message.text"This operation has no effect on its result.")inletalwaysv=Error.redundant_operationctx.diagnostics~location:op.infoWax_utils.Message.((text"This operation always yields"++int64v)^^text".")inmatchop.descwith|Addwhenis0l||is0r->no_effect()(* x + 0 *)|(Sub|Shl|Shr_)whenis0r->no_effect()(* x - 0, x << 0 *)|Mulwhenis1l||is1r->no_effect()(* x * 1 *)|Div(Some_)whenis1r->no_effect()(* x / 1 *)|(Or|Xor)whenis0l||is0r->no_effect()(* x | 0, x ^ 0 *)|(And|Or)whenidentical_operandslr->no_effect()(* x & x, x | x *)|Mulwhenis0l||is0r->always0L(* x * 0 *)|Andwhenis0l||is0r->always0L(* x & 0 *)|Rem_whenis1r->always0L(* x % 1 *)|(Sub|Xor)whenidentical_operandslr->always0L(* x - x, x ^ x *)|_->()(* A branch, loop, or [select] condition that is a constant literal, so it
always takes the same path. [is_while] excludes the idiomatic infinite loop
[while <nonzero>] (only [while 0], a loop that never runs, is flagged). *)letlint_conditionctx?(is_while=false)(cond:_Ast.instr)=matchcond.descwith|Ast.Ints->(matchint_literal_valueswith|Somen->letvalue=n<>0Linifnot(is_while&&value)thenError.constant_conditionctx.diagnostics~location:cond.info~value|None->())|_->()(* Whether evaluating [e] has no side effect and cannot trap, so computing it
only to discard the result is pointless. Conservative: reads of locals/
globals and constants, pure arithmetic/logic over them, and heap allocations
(which are effect-free and non-trapping) whose operands are themselves
effect-free. Excludes calls, assignments, field/element accesses (may trap on
null / out of bounds), casts, [array.new_data]/[array.new_elem] (trap out of
bounds), and trapping arithmetic ([/]/[%]). Mirrors the Wasm validator's
purity classification (see [lint_body] in [Validation]). *)letrecis_effectless(e:_Ast.instr)=(* A field value; the punning shorthand [{x}] reads a local/global. *)letfield(_,v)=matchvwithSomee->is_effectlesse|None->trueinmatche.descwith|Get_|Int_|Float_|Char_|String_|Null|StructDefault_->true|UnOp(_,a)->is_effectlessa|BinOp({desc=Div_|Rem_;_},_,_)->false|BinOp(_,a,b)->is_effectlessa&&is_effectlessb|Select(a,b,c)->is_effectlessa&&is_effectlessb&&is_effectlessc|Test(a,_)->is_effectlessa|Struct(_,fields)->List.for_allfieldfields|StructDesc(d,fields)->is_effectlessd&&List.for_allfieldfields|StructDefaultDescd->is_effectlessd|Array(_,elt,len)->is_effectlesselt&&is_effectlesslen|ArrayDefault(_,len)->is_effectlesslen|ArrayFixed(_,elts)->List.for_allis_effectlesselts|_->false(* The concrete type an initializer would take with no annotation, matching the
resolution of the unannotated [let] case. Returns [None] for types we never
want to drop an annotation for (packed or still unconstrained). Pure: it does
not mutate [ty], so it can be read before [check_type] constrains it. *)letstandalone_valtypectxty=matchCell.gettywith|Valtypev->Somev|Int|Number->Somei32_valtype|LargeInt->Somei64_valtype|Float->Somef64_valtype|Null->internalize_valtypectx(Ref{nullable=true;typ=None_})(* The bottom reference concretizes to the non-null [&none], matching the type
[null!] produced before [UnknownRef] existed. *)|UnknownRef->internalize_valtypectx(Ref{nullable=false;typ=None_})|Int8|Int16|Unknown|Error|Collecting_->None(* Resolve the type that an omitted annotation takes from its initializer, as in
[let x = e] or [const x = e]: an as-yet-unconstrained literal is pinned to a
concrete type the way the final type erasure does (int/number -> i32,
float -> f64, null -> nullref), so the binding gets a definite type. Mutates
[ty] so later uses observe the resolved type. *)letresolve_omitted_valtypectxty=matchCell.gettywith|Valtypev->Somev|LargeInt->letv=i64_valtypeinCell.setty(Valtypev);Somev|Int|Number|Int8|Int16|Unknown|Error|Collecting_->letv=i32_valtypeinCell.setty(Valtypev);Somev|Float->letv=f64_valtypeinCell.setty(Valtypev);Somev|Null->let+@v=internalize_valtypectx(Ref{nullable=true;typ=None_})inCell.setty(Valtypev);v(* The bottom reference concretizes to the non-null [&none], matching the type
[null!] produced before [UnknownRef] existed. *)|UnknownRef->let+@v=internalize_valtypectx(Ref{nullable=false;typ=None_})inCell.setty(Valtypev);v(* The type an unannotated [let]/global binding takes from its initializer,
recording a poison ([None]) type when the initializer has no concrete one. An
[Unknown] initializer (unreachable / branch code) reports an error here: a
binding needs a determinable type to be compiled, and silently demoting it to
the [Error] type would mask that. An [Error] initializer (already reported)
stays silent. *)letbound_value_typectx~locationresult_ty=matchCell.getresult_tywith|Error->None|Unknown->Error.unknown_operand_typectx.diagnostics~location;None|_->resolve_omitted_valtypectxresult_ty(* --- Annotation dropping (the "keep-bool" machinery) -----------------------
When converting from Wasm, the typed AST is rewritten ([ctx.simplify]) to
drop type annotations that the inferred types make redundant, so the printed
Wax is not littered with annotations a reader (or a re-parse) would recover
anyway. The decision is a "keep-bool": when [check_instruction] types a value
against an expected type (the annotation), it returns whether that annotation
is load-bearing — i.e. whether omitting it would change what the value
re-infers to. The binding/construct site then drops the annotation precisely
when [simplify] is on and the keep-bool says it is not needed.
The pieces, by where the annotation lives:
- a scalar value vs. its annotation: [annotation_needed] (the leaf keep-bool,
comparing the value's standalone type to the expected one);
- a block/loop/try result type: [block_keep_bool] / [block_keep_needed],
with [context_block_typ] / [finalize_inferred] filling an omitted result
from context or dropping a redundant declared one;
- [is_null_initializer] is the one exception to the "equal type ⇒ drop" rule
(a bare [null] re-infers a floating type), and [drop_supertype] the one
relaxation (an immutable binding may drop a mere-supertype annotation).
--------------------------------------------------------------------------- *)(* Whether [i] is a (possibly cast-wrapped) [null].
This guards the dropping of a redundant type annotation on an initialized
binding ([let]/[const]) when converting from Wasm. The general rule is to
drop the annotation when the initializer's type already equals it. That is
unsound for [null]: [from_wasm] lowers [ref.null t] to [(null : &?t)] (a cast),
so the initializer's inferred type is the concrete [&?t] and the comparison
reports the annotation as redundant — but the printed bare [null] re-infers to
the *floating* null type [&?none], not [&?t], so dropping the annotation would
not round-trip. The annotation (or the cast) is what pins the type, so we must
keep it.
A cleaner fix would compare against what omitting the annotation actually
re-infers to (resolving the floating [null] under the cast to [&?none] rather
than reading the cast's concrete type); until then we keep the annotation
whenever the initializer is a [null]. *)letrecis_null_initializer(i:_instr)=matchi.descwith|Null->true|Cast(e,_)->is_null_initializere|_->falseletvaltype_equalctx(a:inferred_valtype)(b:inferred_valtype)=Wax_wasm.Types.val_subtype(subtyping_infoctx)a.internalb.internal&&Wax_wasm.Types.val_subtype(subtyping_infoctx)b.internala.internal(* Bidirectional checking helpers (see [check_instruction] below).
The keep-bool for a non-construction value: the contextual annotation is
load-bearing unless the value's own standalone-resolved type ([standalone],
captured BEFORE [check_type] mutates the cell) already equals it. This
mirrors exactly the drop test [bind_let_value]/globals applied via
[standalone_valtype], so routing those sites through [check_instruction] preserves their
behaviour — e.g. [let x: i32 = 1] still drops to [let x = 1] (a floating
number resolves to [i32]), while [let x: i64 = 1] keeps its annotation.
[drop_supertype] loosens the test for an immutable binding (a [const] global):
there the annotation is no more than a supertype of the value's own type, so
dropping it narrows the binding to that subtype — sound because nothing
reassigns it, and a narrower immutable global still satisfies every use (and
every import) expecting the wider type. The standalone value must therefore
only be a *subtype* of the annotation, not equal to it. *)letannotation_needed?(drop_supertype=false)ctx(standalone:inferred_valtypeoption)expected=match(standalone,Cell.getexpected)with|Somev,Valtypeb->ifdrop_supertypethennot(Wax_wasm.Types.val_subtype(subtyping_infoctx)v.internalb.internal)elsenot(valtype_equalctxvb)|_->true(* Whether [expected] carries a real type expectation (vs. the [Unknown]
sentinel used when [check_instruction] is entered from synthesis with no context).
[subtype] asserts on an [Unknown] right-hand side, so callers guard with
this before checking against [expected]. *)lethas_expectationexpected=matchCell.getexpectedwithUnknown|Collecting_->false|_->true(* For a block-like construct (do/loop/try/try_table) checked against [expected]:
the single cell to type its body and handlers against — its declared result,
or [expected] when the annotation was omitted (a re-parse of a dropped one). *)letcontext_result_cellctxtyp~expected=iftyp.results=[||]thenexpectedelsematcharray_map_opt(internalizectx)typ.resultswith|Some[|c|]->c|_->expected(* The [typ] to store for such a construct after its body is typed: fill an
omitted result from [expected] (so re-parse / [to_wasm] recovers it), or drop
a declared result on [simplify] when it equals the context — then re-parse
recovers the same type from the same context, so nothing is lost. *)letcontext_block_typctxtyp~expected~result_cell=iftyp.results=[||]thenmatchstandalone_valtypectxexpectedwith|Someiv->{typwithresults=[|iv.typ|]}|None->typelseifctx.simplify&&match(standalone_valtypectxexpected,standalone_valtypectxresult_cell)with|Somea,Someb->valtype_equalctxab|_->falsethen{typwithresults=[||]}elsetyp(* The exact user heap-type name [expected] pins, if any — usable to supply an
omitted struct/array type name. A supertype top ([any]/[eq]/[struct]/[array]/
…) or a floating/non-ref cell returns [None]: construction needs the exact
type, never a supertype. *)letexact_named_typeexpected=matchCell.getexpectedwith|Valtype{typ=Ref{typ=Typeident|Exactident;_};_}->Someident|_->None(* The user type name a heap type refers to ([Type]/[Exact]), or [None] for an
abstract/bottom heap type. *)letnamed_heaptype(t:heaptype)=matchtwithTypeident|Exactident->Someident|_->None(* A value type is defaultable unless it is a non-nullable reference: such a
local has no zero value and must be assigned before use. *)letis_defaultable(ty:valtype)=matchtywithRef{nullable;_}->nullable|_->trueletmark_initializedctxname=ctx.initialized_locals<-StringSet.addnamectx.initialized_locals(* Type-check one [let] binding against [result_ty] — the value it takes off the
stack — and record the local. Returns the binding to emit: an annotation that
[simplify] finds redundant (it equals what the value would infer to on its
own) is dropped, so Wax printed back from Wasm omits it. Used for both the
single-value form and each name of a multi-value [let]. *)letbind_let_valuectx~locationresult_ty(name,typ)=matchtypwith|Sometyp->(* The type the value would take on its own, captured before
[check_subtype] constrains it. *)letstandalone=standalone_valtypectxresult_tyinletdrop=Option.value~default:false(let+@ity=internalize_valtypectxtypincheck_subtypectx~locationresult_ty(valtype_cellity);Option.iter(funname->ctx.locals<-StringMap.addname.desc(Someity,name.info)ctx.locals;ctx.local_decls:=name::!(ctx.local_decls);mark_initializedctxname.desc)name;ctx.simplify&&Option.fold~none:false~some:(funv->valtype_equalctxvity)standalone)in(name,ifdropthenNoneelseSometyp)|None->Option.iter(funname->(* The local takes its initializer's type; an [Unknown]/[Error]
initializer has no determinable one, so the local is recorded as
poison ([None]) rather than defaulting to [i32], and an [Unknown]
initializer is additionally reported (see [bound_value_type]). *)letity=bound_value_typectx~locationresult_tyinctx.locals<-StringMap.addname.desc(ity,name.info)ctx.locals;ctx.local_decls:=name::!(ctx.local_decls);mark_initializedctxname.desc)name;(name,None)(* When converting from Wasm, an expression producing several values (typically
a call) is emitted as a bare statement, and the values it leaves on the stack
are peeled off by a following run of [let x = _] declarations (and [_ = _]
drops for results that are discarded). [merge_let_tuple] folds that run back
into a single multi-binding [let (..) = expr] — the exact inverse of how such
a [let] lowers, so the rewrite preserves semantics.
The run consumed is exactly [head]'s result arity, read from the typed info,
so we never absorb a [let x = _] that draws from a value sitting below
[head]. Each bound name takes one value left to right, whereas the lowering
stores the topmost value first, so the bindings are the run in reverse. Only
done while simplifying, i.e. on the Wasm-to-Wax path. *)letmerge_let_tuplectxheadrest=letis_holei=matchi.descwithHole->true|_->falseinletarity=Array.length(fsthead.info)inletrectakenaccl=ifn=0thenSome(List.revacc,l)elsematchlwith(* A named binding [let x = _] or an anonymous drop [_ = _] (both a
single-binding [Let] over a hole): peel one value each. *)|{desc=Let([b],Somev);_}::rwhenis_holev->take(n-1)(b::acc)r|_->Noneinif(notctx.simplify)||arity<2thenhead::restelsematchtakearity[]restwith|Some(bindings,rest')whenList.exists(fun(name,_)->Option.is_somename)bindings->letinfo=([||],sndhead.info)in{desc=Let(List.revbindings,Somehead);info}::rest'|_->head::rest(* Check a list of typed operands against an array of expected types. *)letcheck_operandsctx~locationlexpected=ifArray.lengthexpected=List.lengthlthenList.iter2(funity->check_typectxity)l(Array.to_listexpected)else(* With the type immediates inferred from the receiver, a wrong operand
count is no longer caught as an immediate/operand mismatch; report it
as an arity error. *)Error.value_count_mismatchctx.diagnostics~location~expected:(Array.lengthexpected)~provided:(List.lengthl)(* A missing else branch behaves like an empty one: it leaves the block
parameters on the stack, so it is valid only when those already match the
results (in particular, an if that produces a value needs an explicit else). *)letmissing_else_okctxparamsresults=Array.lengthparams=Array.lengthresults&&Array.for_all2(funpr->subtypectxpr)paramsresults(* The function type wrapped by a continuation type, given its (canonical) heap
type. Mirrors [Validation.cont_functype_of_heaptype]. *)letcont_functypectx(h:Internal.heaptype):Internal.functypeoption=matchhwith|Typety|Exactty->(match(Wax_wasm.Types.get_subtype(subtyping_infoctx)ty).typwith|Contft->(match(Wax_wasm.Types.get_subtype(subtyping_infoctx)ft).typwith|Funcf->Somef|Struct_|Array_|Cont_->None)|Func_|Struct_|Array_->None)|_->None(* [ft] matches [ft'] when their arities agree and [ft']'s parameters /
[ft]'s results are respectively subtypes. Mirrors [Validation.functype_matches]. *)letfunctype_matchesinfo(ft:Internal.functype)(ft':Internal.functype)=Array.lengthft.params=Array.lengthft'.params&&Array.lengthft.results=Array.lengthft'.results&&Array.for_allFun.id(Array.mapi(funip->Wax_wasm.Types.val_subtypeinfoft'.params.(i)p)ft.params)&&Array.for_allFun.id(Array.mapi(funir->Wax_wasm.Types.val_subtypeinforft'.results.(i))ft.results)(* A source function type with its parameter and result types resolved to their
canonical (Binary) form, for structural comparison with [functype_matches]. *)letinternal_functypectx(ft:functype):Internal.functypeoption=let*@params=array_map_opt(funp->let+@iv=internalize_valtypectx(sndp.desc)iniv.internal)ft.paramsinlet+@results=array_map_opt(funt->let+@iv=internalize_valtypectxtiniv.internal)ft.resultsin({params;results}:Internal.functype)(* Validate a [resume]/[resume_throw] handler table. [result_types] is the
result type of the resumed continuation. Mirrors
[Validation.check_resume_table]. *)letcheck_resume_handlersctx~result_typeshandlers=letinfo=subtyping_infoctxin(* A block whose result is being inferred presents its label as a [Collecting]
cell. The handler reads the label's type to validate the contract, which the
join cannot re-derive, so resolve to the declared annotation under test and
mark it needed (kept). *)letrecinternal_of_inferredty=matchCell.gettywith|Valtype{internal;_}->Someinternal|Collecting{declared=Somed;_}->internal_of_inferredd|_->Noneinletto_internalarr=array_map_opt(funtyp->let+@iv=internalize_valtypectxtypiniv.internal)arrinList.iter(funhandler->matchhandlerwith|OnLabel(tag,label)->(matchTbl.findctx.diagnosticsctx.tagstagwith|None->ignore(branch_targetctxlabel)|Some{params=ts3;results=ts4}->letts'=branch_targetctxlabelinletmismatch()=Error.stack_switching_type_mismatchctx.diagnostics~location:label.info~descr:"this handler must take the tag's parameters followed by a \
continuation of the remaining result type"in(* The handler label receives the tag's parameters followed by a
continuation of type [cont (ts4 -> result_types)]. *)letn=Array.lengthts'inifn<>Array.lengthts3+1thenmismatch()elsebegin(* The continuation slot may be a block result still being
inferred (the Wasm->Wax [simplify] pass), presented as a
[Collecting] cell. Reading it to validate the contract is a use
the join cannot re-derive, so mark its annotation needed (kept);
[internal_of_inferred] resolves the cell to that declared type
below. Only this last slot can be under inference: a block being
inferred has a single result, so a handler with tag parameters
(n > 1) is never inferred and its slots are concrete. A cell
inferring with no declared annotation resolves to [None] below
and so fails the contract check, as it must. *)(matchCell.getts'.(n-1)with|Collectingcs->cs.needed<-true|_->());Array.iteri(funip->let_,t=p.descinmatch(internalize_valtypectxt,internal_of_inferredts'.(i))with|Someit,Someit'->ifnot(Wax_wasm.Types.val_subtypeinfoit.internalit')thenmismatch()|_->())ts3;matchinternal_of_inferredts'.(n-1)with|Some(Ref{typ=ht;_})->(matchcont_functypectxhtwith|Someft'->(match(to_internalts4,to_internalresult_types)with|Someparams,Someresults->ifnot(functype_matchesinfo{params;results}ft')thenmismatch()|_->())|None->mismatch())|_->mismatch()end)|OnSwitchtag->(matchTbl.findctx.diagnosticsctx.tagstagwith|None->()|Some{params=ts3;results=ts4}->(letmismatchdescr=Error.stack_switching_type_mismatchctx.diagnostics~location:tag.info~descrin(* A switch handler tag has type [] -> [t*]. The reified current
continuation ([cont [t2*] -> [t*]]) runs to this [resume]
boundary, whose results are [result_types], so [t*] must
*equal* those results (equivalence, not merely subtyping): a
subtype would let a continuation whose completion produces the
boundary results be observed by a peer at the narrower tag type.
Mirrors [Validation]'s [result_equivalent] check; the older
written subtyping rule is unsound. *)ifArray.lengthts3<>0thenmismatch"the tag of a 'switch' handler must take no parameters"elsematch(to_internalts4,to_internalresult_types)with|Sometr,Somecr->ifArray.lengthtr<>Array.lengthcr||not(Array.for_all2(funab->Wax_wasm.Types.val_subtypeinfoab&&Wax_wasm.Types.val_subtypeinfoba)trcr)thenmismatch"the results of a 'switch' handler's tag must match \
the resumed continuation's results"|_->())))handlersletreccount_holesi=matchi.descwith|Hole->1|BinOp(_,l,r)|Array(_,l,r)|ArraySegment(_,_,l,r)|ArrayGet(l,r)->count_holesl+count_holesr|ArraySet(t,i,v)->count_holest+count_holesi+count_holesv|Call(f,args)|TailCall(f,args)->count_holesf+List.fold_left(funacci->acc+count_holesi)0args|If{cond=i;_}|Let(_,Somei)|Set(_,_,i)|Tee(_,i)|Labelled(_,i)|UnOp(_,i)|Cast(i,_)|Test(i,_)|NonNulli|Br(_,Somei)|Br_if(_,i)|Hinted(_,i)|On(i,_)|Br_table(_,i)|Br_on_null(_,i)|Br_on_non_null(_,i)|Br_on_cast(_,_,i)|Br_on_cast_fail(_,_,i)|ArrayDefault(_,i)|ThrowRefi|ContNew(_,i)|Return(Somei)|StructDefaultDesci|GetDescriptori|StructGet(i,_)->count_holesi|CastDesc(i1,_,i2)|Br_on_cast_desc_eq(_,_,i1,i2)|Br_on_cast_desc_eq_fail(_,_,i1,i2)|StructSet(i1,_,i2)->count_holesi1+count_holesi2(* A punned field ([None]) is a [Get], which contains no holes. *)|Struct(_,l)->List.fold_left(funacc(_,i)->acc+Option.fold~none:0~some:count_holesi)0l|StructDesc(d,l)->count_holesd+List.fold_left(funacc(_,i)->acc+Option.fold~none:0~some:count_holesi)0l|Sequencel|ArrayFixed(_,l)|ContBind(_,_,l)|Suspend(_,l)|Resume(_,_,l)|ResumeThrow(_,_,_,l)|ResumeThrowRef(_,_,l)|Switch(_,_,l)|Throw(_,l)->List.fold_left(funacci->acc+count_holesi)0l|Select(c,t,e)->count_holesc+count_holest+count_holese(* [dispatch]/[match], [while] and [do]-[while] are block-like: their
operands/scrutinee and bodies are checked inside the blocks they desugar
to, so no hole at this level draws from the stack. *)|Block_|Loop_|While_|TryTable_|Try_|TryCatch_|If_annotation_|Dispatch_|Match_|StructDefault_|Char_|String_|Int_|Float_|Get_|Path_|Null|Unreachable|Nop|Let(_,None)|Br(_,None)|ReturnNone->0(* Accumulate into [acc] the local names assigned ([Set]/[Tee] targets) anywhere
in [i], recursing through every sub-instruction. Mirrors the case coverage of
{!Sink_let.occurs}: only [Set]/[Tee] write a local, every other case just
recurses. A drop ([_ = e], an anonymous [Let]) names no local, so it just
recurses via the [Let] case. Wasm-derived locals are uniquely named within a
function, so the
resulting by-name set is exact; a stray name collision could only keep an
annotation, never wrongly drop one. *)letreccollect_assigned_localsacci=letin_listaccl=List.fold_leftcollect_assigned_localsacclinletin_optacco=matchowithSomei->collect_assigned_localsacci|None->accinmatchi.descwith|Set(id,_,e)|Tee(id,e)->collect_assigned_locals(StringSet.addid.descacc)e|Block{block;_}|Loop{block;_}|TryTable{block;_}->in_listaccblock.desc|While{cond;step;block;_}->letacc=collect_assigned_localsacccondinletacc=Option.fold~none:acc~some:(collect_assigned_localsacc)stepinin_listaccblock.desc|If{cond;if_block;else_block;_}->letacc=in_list(collect_assigned_localsacccond)if_block.descinOption.fold~none:acc~some:(funb->in_listaccb.desc)else_block|Try{block;catches;catch_all;_}->letacc=in_listaccblock.descinletacc=List.fold_left(funacc(_,b)->in_listaccb.desc)acccatchesinOption.fold~none:acc~some:(funb->in_listaccb.desc)catch_all|TryCatch{block;arms;_}->letacc=in_listaccblock.descinList.fold_left(funacca->in_listacca.arm_body.desc)accarms|Call(t,args)|TailCall(t,args)->in_list(collect_assigned_localsacct)args|Cast(e,_)|Test(e,_)|NonNulle|StructGet(e,_)|GetDescriptore|StructDefaultDesce|UnOp(_,e)|Br_if(_,e)|Hinted(_,e)|On(e,_)|Labelled(_,e)|Br_table(_,e)|Br_on_null(_,e)|Br_on_non_null(_,e)|Br_on_cast(_,_,e)|Br_on_cast_fail(_,_,e)|ThrowRefe|ArrayDefault(_,e)|ContNew(_,e)->collect_assigned_localsacce(* A punned field ([None]) is a [Get] and assigns nothing. *)|Struct(_,fields)->List.fold_left(funacc(_,e)->Option.fold~none:acc~some:(collect_assigned_localsacc)e)accfields|StructDesc(d,fields)->List.fold_left(funacc(_,e)->Option.fold~none:acc~some:(collect_assigned_localsacc)e)(collect_assigned_localsaccd)fields|CastDesc(e1,_,e2)|Br_on_cast_desc_eq(_,_,e1,e2)|Br_on_cast_desc_eq_fail(_,_,e1,e2)|StructSet(e1,_,e2)|Array(_,e1,e2)|ArraySegment(_,_,e1,e2)|ArrayGet(e1,e2)|BinOp(_,e1,e2)->collect_assigned_locals(collect_assigned_localsacce1)e2|ArraySet(e1,e2,e3)|Select(e1,e2,e3)->collect_assigned_locals(collect_assigned_locals(collect_assigned_localsacce1)e2)e3|ArrayFixed(_,l)|ContBind(_,_,l)|Suspend(_,l)|Resume(_,_,l)|ResumeThrow(_,_,_,l)|ResumeThrowRef(_,_,l)|Switch(_,_,l)|Throw(_,l)|Sequencel->in_listaccl|Dispatch{index;arms;_}->List.fold_left(funacc(_,b)->in_listaccb.desc)(collect_assigned_localsaccindex)arms|Match{scrutinee;arms;default}->letacc=collect_assigned_localsaccscrutineeinletacc=List.fold_left(funacc(_,b)->in_listaccb.desc)accarmsinin_listaccdefault.desc|Let(_,body)->in_optaccbody|Br(_,o)|Returno->in_optacco|If_annotation{then_body;else_body;_}->letacc=in_listaccthen_body.descinOption.fold~none:acc~some:(funb->in_listaccb.desc)else_body|Get_|Path_|Unreachable|Nop|Hole|Null|Char_|String_|Int_|Float_|StructDefault_->acc(* Accumulate into [acc] the block labels declared anywhere in [i], from the
source AST (before any lowering, so synthesized labels from [while]/[dispatch]/
[match] desugaring are never collected). Every case recurses; the labelled
constructs also contribute their own label. The [dispatch]/[match] arm labels
are branch targets, not declarations, so they are not collected. Mirrors the
case coverage of {!collect_assigned_locals}. *)letreccollect_labelsacc(i:_Ast.instr)=letin_listaccl=List.fold_leftcollect_labelsacclinletin_optacco=matchowithSomei->collect_labelsacci|None->accinletaddacclabel=matchlabelwithSomel->l::acc|None->accinmatchi.descwith|Block{label;block;_}|Loop{label;block;_}|TryTable{label;block;_}->in_list(addacclabel)block.desc|While{label;cond;step;block;_}->letacc=collect_labels(addacclabel)condinletacc=Option.fold~none:acc~some:(collect_labelsacc)stepinin_listaccblock.desc|If{label;cond;if_block;else_block;_}->letacc=in_list(collect_labels(addacclabel)cond)if_block.descinOption.fold~none:acc~some:(funb->in_listaccb.desc)else_block|Try{label;block;catches;catch_all;_}->letacc=in_list(addacclabel)block.descinletacc=List.fold_left(funacc(_,b)->in_listaccb.desc)acccatchesinOption.fold~none:acc~some:(funb->in_listaccb.desc)catch_all|TryCatch{label;block;arms;_}->letacc=in_list(addacclabel)block.descinList.fold_left(funacca->in_listacca.arm_body.desc)accarms|Call(t,args)|TailCall(t,args)->in_list(collect_labelsacct)args|Set(_,_,e)|Tee(_,e)|Labelled(_,e)|Cast(e,_)|Test(e,_)|NonNulle|StructGet(e,_)|GetDescriptore|StructDefaultDesce|UnOp(_,e)|Br_if(_,e)|Hinted(_,e)|On(e,_)|Br_table(_,e)|Br_on_null(_,e)|Br_on_non_null(_,e)|Br_on_cast(_,_,e)|Br_on_cast_fail(_,_,e)|ThrowRefe|ArrayDefault(_,e)|ContNew(_,e)->collect_labelsacce|Struct(_,fields)->List.fold_left(funacc(_,e)->in_optacce)accfields|StructDesc(d,fields)->List.fold_left(funacc(_,e)->in_optacce)(collect_labelsaccd)fields|CastDesc(e1,_,e2)|Br_on_cast_desc_eq(_,_,e1,e2)|Br_on_cast_desc_eq_fail(_,_,e1,e2)|StructSet(e1,_,e2)|Array(_,e1,e2)|ArraySegment(_,_,e1,e2)|ArrayGet(e1,e2)|BinOp(_,e1,e2)->collect_labels(collect_labelsacce1)e2|ArraySet(e1,e2,e3)|Select(e1,e2,e3)->collect_labels(collect_labels(collect_labelsacce1)e2)e3|ArrayFixed(_,l)|ContBind(_,_,l)|Suspend(_,l)|Resume(_,_,l)|ResumeThrow(_,_,_,l)|ResumeThrowRef(_,_,l)|Switch(_,_,l)|Throw(_,l)|Sequencel->in_listaccl|Dispatch{index;arms;_}->List.fold_left(funacc(_,b)->in_listaccb.desc)(collect_labelsaccindex)arms|Match{scrutinee;arms;default}->letacc=collect_labelsaccscrutineeinletacc=List.fold_left(funacc(_,b)->in_listaccb.desc)accarmsinin_listaccdefault.desc|Let(_,body)->in_optaccbody|Br(_,o)|Returno->in_optacco|If_annotation{then_body;else_body;_}->letacc=in_listaccthen_body.descinOption.fold~none:acc~some:(funb->in_listaccb.desc)else_body|Get_|Path_|Unreachable|Nop|Hole|Null|Char_|String_|Int_|Float_|StructDefault_->acc(* The location of a trapping or effectful operation reached on the eagerly-
evaluated spine of a [?:] branch [e], or [None] if the branch only reads
locals/globals and computes pure arithmetic. Descends through pure operators
(into the operands that are always evaluated) but stops at any nested control
construct (an inner [if], [?:], block, loop, …): the sub-expressions guarded
by it are not evaluated unconditionally, and a nested [?:] is linted in its
own right. The hazard set matches the Wasm validator's ([lint_eager_select]
in [Validation]): integer division/remainder, field and element accesses,
[!], the descriptor cast, [array.new_data]/[array.new_elem], [unreachable],
calls, assignments, throws, and stack-switching — but not plain casts (a
[ref.cast] is diagnosed by [cast-always-fails] instead). *)letrecfind_eager_hazard(e:_Ast.instr)=let(<|>)of=matchowithSome_->o|None->f()inletdescendl=List.fold_left(funacce->acc<|>fun()->find_eager_hazarde)Nonelinmatche.descwith(* Trapping or effectful operations: report the operation itself. *)|ArrayGet_|ArraySet_|StructGet_|StructSet_|GetDescriptor_|NonNull_|CastDesc_|ArraySegment_|Unreachable|Call_|TailCall_|Set_|Tee_|Throw_|ThrowRef_|ContNew_|ContBind_|Suspend_|Resume_|ResumeThrow_|ResumeThrowRef_|Switch_->Somee.info|BinOp({desc=Div(Some_)|Rem_;_},_,_)->Somee.info(* Pure operators: descend into their eagerly-evaluated operands. *)|BinOp(_,a,b)->descend[a;b]|UnOp(_,a)|Cast(a,_)|Test(a,_)|Labelled(_,a)|ArrayDefault(_,a)|StructDefaultDesca->find_eager_hazarda|Array(_,a,b)->descend[a;b]|ArrayFixed(_,l)|Sequencel->descendl|Struct(_,fields)->descend(List.filter_map(fun(_,v)->v)fields)|StructDesc(d,fields)->find_eager_hazardd<|>fun()->descend(List.filter_map(fun(_,v)->v)fields)|Let(_,init)->(matchinitwithSomee->find_eager_hazarde|None->None)(* Constants, reads, and allocations of default values never trap; nested
control constructs guard their sub-expressions, so stop there. *)|Get_|Path_|Int_|Float_|Char_|String_|Null|Nop|Hole|StructDefault_|Block_|Loop_|While_|If_|TryTable_|Try_|TryCatch_|Br_|Br_if_|Br_table_|Dispatch_|Match_|Br_on_null_|Br_on_non_null_|Br_on_cast_|Br_on_cast_fail_|Br_on_cast_desc_eq_|Br_on_cast_desc_eq_fail_|Hinted_|On_|Return_|Select_|If_annotation_->None(* Report an eager-evaluation hazard in the [?:] branch [arm]; [select] is the
location of the whole [?:] (for the secondary caret). *)letlint_eager_selectctx~selectarm=matchfind_eager_hazardarmwith|Somelocation->Error.eager_selectctx.diagnostics~location~select|None->()(* Human-readable name of a binary operator's precedence class, for the
[precedence] lint's message. The classification and the confusing-mix table
are shared with the Wax printer — see {!Ast_utils.binop_kind} and
{!Ast_utils.confusing_precedence}. *)letbinop_kind_name=function|`Shift->"shift"|`Arith->"arithmetic"|`Bitwise->"bitwise"|`Comparison->"comparison"(* Whether the operand [child] of a binary operator was written parenthesized.
Parentheses are erased by the grammar ([1 << (n - 1)] and [1 << n - 1] parse
to the same tree), so this is decided from the source text: a parenthesized
operand is immediately preceded by [(] (a right operand) or followed by [)]
(a left operand), skipping whitespace. With no source available, assume it is
parenthesized (so the lint stays silent rather than risk a false positive). *)letoperand_parenthesizedctx~side(child:_Ast.instr)=matchWax_utils.Diagnostic.sourcectx.diagnosticswith|None->true|Somesrc->(letis_space=function' '|'\t'|'\n'|'\r'->true|_->falseinletn=String.lengthsrcinmatchsidewith|`Right->letrecbacki=ifi<0thenfalseelseifis_spacesrc.[i]thenback(i-1)elsesrc.[i]='('inback(child.info.loc_start.pos_cnum-1)|`Left->letrecfwdi=ifi>=nthenfalseelseifis_spacesrc.[i]thenfwd(i+1)elsesrc.[i]=')'infwdchild.info.loc_end.pos_cnum)(* The [precedence] lint: flag a binary operator [op] one of whose operands is
itself a binary operator of a confusingly-related class (see
{!Ast_utils.confusing_precedence}), written without disambiguating
parentheses. The Wax printer parenthesises exactly these mixes (see
[Output]), so re-printed / decompiled Wax stays quiet under the lint. *)letlint_precedencectx(op:(binop,location)annotated)e1e2=letouter=Ast_utils.binop_kindop.descinList.iter(fun(child,side)->matchchild.descwith|BinOp(inner_op,_,_)whenAst_utils.confusing_precedenceouter(Ast_utils.binop_kindinner_op.desc)&¬(operand_parenthesizedctx~sidechild)->Error.precedencectx.diagnostics~location:op.info~inner:inner_op.info~outer_kind:(binop_kind_nameouter)~inner_kind:(binop_kind_name(Ast_utils.binop_kindinner_op.desc))|_->())[(e1,`Left);(e2,`Right)](* Walk the source AST (before any lowering, so [while] keeps its own condition
rather than the [if] it desugars to) and report the purely-syntactic lints: a
constant branch/loop/select condition, and a drop ([_ = e]) of a
side-effect-free expression. Runs once over the source rather than in the type
checker's expression handling. Mirrors the case coverage of
{!collect_labels}. *)letreclint_sourcectx(i:_Ast.instr)=letlistl=List.iter(lint_sourcectx)linletopto=Option.iter(lint_sourcectx)oinmatchi.descwith|If{cond;if_block;else_block;_}->lint_conditionctxcond;lint_sourcectxcond;listif_block.desc;Option.iter(funb->listb.desc)else_block|While{cond;step;block;_}->lint_conditionctx~is_while:truecond;lint_sourcectxcond;optstep;listblock.desc|Select(c,t,e)->lint_conditionctxc;lint_eager_selectctx~select:i.infot;lint_eager_selectctx~select:i.infoe;lint_sourcectxc;lint_sourcectxt;lint_sourcectxe|Br_if(_,c)->lint_conditionctxc;lint_sourcectxc|Block{block;_}|Loop{block;_}|TryTable{block;_}->listblock.desc|Try{block;catches;catch_all;_}->listblock.desc;List.iter(fun(_,b)->listb.desc)catches;Option.iter(funb->listb.desc)catch_all|TryCatch{block;arms;_}->listblock.desc;List.iter(funa->lista.arm_body.desc)arms|Call(t,args)|TailCall(t,args)->lint_sourcectxt;listargs|Set(id,op,e)->(* A plain self-assignment [x = x] has no effect. A compound assignment
[x op= x] is not redundant (e.g. [x += x] doubles it). The pointless-
drop check lives in the [Let] case, since a drop [_ = e] is an anonymous
binding. *)(match(op,e.desc)with|None,Getid'whenString.equalid.descid'.desc->Error.redundant_operationctx.diagnostics~location:i.info(Wax_utils.Message.text"This assignment writes the variable back to itself.")|_->());lint_sourcectxe|Tee(_,e)|Labelled(_,e)|Cast(e,_)|Test(e,_)|NonNulle|StructGet(e,_)|GetDescriptore|StructDefaultDesce|UnOp(_,e)|Hinted(_,e)|On(e,_)|Br_table(_,e)|Br_on_null(_,e)|Br_on_non_null(_,e)|Br_on_cast(_,_,e)|Br_on_cast_fail(_,_,e)|ThrowRefe|ArrayDefault(_,e)|ContNew(_,e)->lint_sourcectxe|Struct(_,fields)->List.iter(fun(_,e)->Option.iter(lint_sourcectx)e)fields|StructDesc(d,fields)->lint_sourcectxd;List.iter(fun(_,e)->Option.iter(lint_sourcectx)e)fields|BinOp(op,e1,e2)->lint_precedencectxope1e2;lint_sourcectxe1;lint_sourcectxe2|CastDesc(e1,_,e2)|Br_on_cast_desc_eq(_,_,e1,e2)|Br_on_cast_desc_eq_fail(_,_,e1,e2)|StructSet(e1,_,e2)|Array(_,e1,e2)|ArraySegment(_,_,e1,e2)|ArrayGet(e1,e2)->lint_sourcectxe1;lint_sourcectxe2|ArraySet(e1,e2,e3)->lint_sourcectxe1;lint_sourcectxe2;lint_sourcectxe3|ArrayFixed(_,l)|ContBind(_,_,l)|Suspend(_,l)|Resume(_,_,l)|ResumeThrow(_,_,_,l)|ResumeThrowRef(_,_,l)|Switch(_,_,l)|Throw(_,l)|Sequencel->listl|Dispatch{index;arms;_}->lint_sourcectxindex;List.iter(fun(_,b)->listb.desc)arms|Match{scrutinee;arms;default}->lint_sourcectxscrutinee;List.iter(fun(_,b)->listb.desc)arms;listdefault.desc|Let(bindings,body)->(* A drop [_ = e] is a single anonymous binding; if [e] is effect-free,
computing it only to discard the result is pointless. *)(match(bindings,body)with|[(None,_)],Someewhenis_effectlesse->Error.unused_resultctx.diagnostics~location:e.info|_->());optbody|Br(_,o)|Returno->opto|If_annotation{then_body;else_body;_}->listthen_body.desc;Option.iter(funb->listb.desc)else_body|Get_|Path_|Unreachable|Nop|Hole|Null|Char_|String_|Int_|Float_|StructDefault_->()(* If [meth] names an intrinsic written as a method on a value receiver — a SIMD
lane/vector op [v.add_i32x4(b)], or a scalar [x.copysign(y)], [x.min(y)],
[x.rotl(y)] — the number of leading constant lane immediates in its argument
list (always 0 for the scalar ops), else [None]. Such a call evaluates the
receiver before its operands, unlike a generic call whose callee is evaluated
last; the leading SIMD lane immediates are static and never reach the stack.
The set of names matches the call dispatch (see [type_simd_vector_op_call],
[type_binary_intrinsic_call]). This is decided by name alone — but the same
names are only receiver-first when the receiver is actually a value, see
[receiver_is_value]. *)letintrinsic_method_immsmeth=matchWax_wasm.Simd.classifymethwith|Some{free=false;imm;_}->(matchimmwithNo_imm->Some0|Lane_->Some1|Shuffle->Some16)|_->(matchmethwith|"rotl"|"rotr"|"copysign"|"min"|"max"->Some0|_->None)(* Whether [obj], the receiver of an [obj.meth(args)] call, is a value rather
than a reference. Only a value receiver makes [meth] an intrinsic evaluated
receiver-first: when [obj] is a reference, [obj.meth] may instead load a
function-pointer field, so the call is an indirect call whose arguments are
evaluated first (then the loaded callee). Treating that as receiver-first
could hide a value occurring before a hole, so the reorder is gated on this. *)letreceiver_is_valuectxobj=matchCell.get(expression_typectxobj)with|Null|Valtype{internal=Ref_;_}->false|_->true(* Whether the receiver of an [obj.meth(..)] call is a concrete array — the case
that makes a [fill]/[copy]/[init] method an array operation (evaluated
receiver-first), as opposed to a struct-field/indirect call or a static
memory/table form (both args-first / static-receiver). Reads the receiver's
type cell directly: a memory/table receiver carries no value type ([||]), for
which [expression_type] would spuriously report "not an expression". *)letreceiver_is_arrayctxrecv=matchfstrecv.Ast.infowith|[|cell|]->(matchCell.getcellwith|Valtype{typ=Ref{typ=Typety|Exactty;_};_}->(matchTbl.find_optctx.type_context.typestywith|Somet->(match(sndt).typwithArray_->true|_->false)|None->false)|_->false)|_->false(* Whether the receiver of a scalar-intrinsic-method call [recv.min(..)] names a
reference (e.g. a struct) rather than a numeric value. Decided purely, by
looking the name up in the locals / globals — no typing, so the dispatch can
gate on it without recording a spurious use. A reference receiver means
[recv.min] loads a function-pointer field (an indirect call), not the scalar
[min] intrinsic; only a numeric receiver reaches [type_binary_intrinsic_call].
A non-name receiver (a literal, a nested expression) is not a reference. *)letreceiver_is_refctxrecv=letis_ref=function|Some({typ=Ref_;_}:inferred_valtype)->true|_->falseinmatchrecv.Ast.descwith|Getname->(matchStringMap.find_optname.descctx.localswith|Some(ity,_)->is_refity|None->(matchTbl.entriesctx.globalsnamewith|(_,(_,ity))::_->is_refity|[]->false))|_->false(* Whether the receiver of an array-op method call ([a.fill(..)]) names a value
whose type is a reference to an array type. Pure, like {!receiver_is_ref}: it
reads the name's type from the locals / globals and the referenced type's
definition from the type table, recording nothing. Gates the recovery of a
wrong-arity array op (an [a.fill()] being typed) so a struct with a field
named [fill]/[copy]/[init] is left to the indirect-call path instead. *)letreceiver_is_array_refctxrecv=letref_name=function|Some({typ=Ref{typ=Typen|Exactn;_};_}:inferred_valtype)->Somen|_->Noneinletarrname=matchrecv.Ast.descwith|Getname->(matchStringMap.find_optname.descctx.localswith|Some(ity,_)->ref_nameity|None->(matchTbl.entriesctx.globalsnamewith|(_,(_,ity))::_->ref_nameity|[]->None))|_->Noneinmatcharrnamewith|None->false|Somen->(matchTbl.entriesctx.type_context.typesnwith|(_,(_,sub))::_->(matchsub.typwithArray_->true|_->false)|[]->false)(* A cast is transparent to the hole-order check exactly when [to_wasm] lowers it
to no instruction (so it occupies its operand's position and produces nothing):
an operand with no value type — unreachable / failed code, where the cast emits
nothing — or a numeric-scalar identity, the only [Nop] case in [to_wasm]'s cast
lowering. Everything else IS emitted: a numeric conversion ([_ as f32] on an
[f64] hole is [f32.demote_f64]) or a reference cast (always a [ref.cast], even
an up-cast), and operates on the stack top, so it must be ordered like any
other value-producing expression. *)letcast_is_transparentctx~cast~operand=matchCell.get(expression_typectxoperand)with|Unknown|Error->true|Valtype{internal=(I32|I64|F32|F64)assrc;_}->(matchCell.get(expression_typectxcast)with|Valtype{internal=dst;_}->src=dst|_->false)|_->falseletreccheck_hole_order_recctxin=matchi.descwith|Hole->n-1|Getnamewhenmemory_receiverctxname||table_receiverctxname||segment_receiverctxname->(* A memory/table name (a method/index receiver [mem.load(..)], [tab[..]],
or a cross-mem/table [copy] source) or a data/element segment name (a
[seg.drop()] receiver or an [init] operand) is a static immediate, not a
stack value, so it never counts as occurring before a hole. *)n|_whenn<=0->n|Cast(inner,_)whencast_is_transparentctx~cast:i~operand:inner->(* A nop cast (see [cast_is_transparent]) is transparent: recurse into the
operand, which is itself flagged if it is a value occurring before a
hole, without counting the cast as such — so [(_ as T)] with a hole
already of type [T] is fine even when later holes remain. A non-nop
cast falls through to the normal handling below. *)check_hole_order_recctxinnern|_->letn=matchi.descwith|Block_|Loop_|While_|TryTable_|Try_|TryCatch_|If_annotation_|Dispatch_|Match_|StructDefault_|Char_|String_|Int_|Float_|Get_|Path_|Null|Unreachable|Nop|Let(_,None)|Br(_,None)|ReturnNone->n(* A table reference [tab[..]] has a static receiver (the table name),
not an evaluated operand, so it does not count as occurring before a
hole; only the index/value do. *)|ArrayGet({desc=Gettab;_},r)whentable_receiverctxtab->check_hole_order_recctxrn|ArraySet({desc=Gettab;_},idx,v)whentable_receiverctxtab->n|>check_hole_order_recctxidx|>check_hole_order_recctxv|BinOp(_,l,r)|Array(_,l,r)|ArraySegment(_,_,l,r)|ArrayGet(l,r)->n|>check_hole_order_recctxl|>check_hole_order_recctxr|ArraySet(t,i,v)->n|>check_hole_order_recctxt|>check_hole_order_recctxi|>check_hole_order_recctxv|Call({desc=StructGet(obj,meth);_},args)whenintrinsic_method_immsmeth.desc<>None&&receiver_is_valuectxobj->(* An intrinsic method on a value receiver, [recv.op(imms.., ops..)].
[to_wasm] evaluates the receiver first, then the non-immediate
stack operands; any leading SIMD lane immediates ([Lane]/[Shuffle])
are static and never reach the operand stack. Mirror that order so
a static lane index — or an operand of a receiver-first scalar op
like [copysign] — is not mistaken for a value before a hole. A
reference receiver is excluded ([receiver_is_value]): it could be a
function-pointer field, i.e. an args-first indirect call. *)letnimm=Option.get(intrinsic_method_immsmeth.desc)inletoperands=List.filteri(funk_->k>=nimm)argsinn|>check_hole_order_recctxobj|>check_hole_order_in_listctxoperands|Call({desc=StructGet(recv,meth);_},args)when(matchmeth.descwith|"fill"|"copy"|"init"->true|_->false)&&receiver_is_arrayctxrecv->(* [arr.fill/copy/init] on an array receiver is a receiver-first array
operation, like the intrinsics above: [to_wasm] and the type
checker both evaluate the array receiver before the operands, so
mirror that order. The same method name on a non-array receiver is
a struct-field/indirect call or a static memory/table form, all of
which the general case below handles (args-first, with a static
[Get name] receiver not counted). The arity is not re-checked —
typing has already validated it. *)n|>check_hole_order_recctxrecv|>check_hole_order_in_listctxargs|Call(f,args)|TailCall(f,args)->n|>check_hole_order_in_listctxargs|>check_hole_order_recctxf|If{cond=i;_}|Let(_,Somei)|Set(_,_,i)|Tee(_,i)|Labelled(_,i)|UnOp(_,i)|Cast(i,_)|Test(i,_)|NonNulli|Br(_,Somei)|Br_if(_,i)|Hinted(_,i)|On(i,_)|Br_table(_,i)|Br_on_null(_,i)|Br_on_non_null(_,i)|Br_on_cast(_,_,i)|Br_on_cast_fail(_,_,i)|ArrayDefault(_,i)|ThrowRefi|ContNew(_,i)|Return(Somei)|StructDefaultDesci|GetDescriptori|StructGet(i,_)->check_hole_order_recctxin|CastDesc(i1,_,i2)|Br_on_cast_desc_eq(_,_,i1,i2)|Br_on_cast_desc_eq_fail(_,_,i1,i2)|StructSet(i1,_,i2)->n|>check_hole_order_recctxi1|>check_hole_order_recctxi2|Sequencel|ArrayFixed(_,l)|ContBind(_,_,l)|Suspend(_,l)|Resume(_,_,l)|ResumeThrow(_,_,_,l)|ResumeThrowRef(_,_,l)|Switch(_,_,l)|Throw(_,l)->check_hole_order_in_listctxln|Struct(_,l)->letfields=matchCell.get(expression_typectxi)with|Valtype{typ=Ref{typ=Typet|Exactt;_};_}->(matchlookup_struct_typectxtwith|Somefields->letfield_map=List.fold_left(funacc(name,instr)->StringMap.addname.descinstracc)StringMap.emptylin(* Reorder fields according to definition. Pinned fields
([None]) are [Get]s with no hole, so drop them. *)Array.map(funfield->StringMap.find(fstfield.desc).descfield_map)fields|>Array.to_list|>List.filter_mapFun.id|None->List.filter_mapsndl)|_->List.filter_mapsndlincheck_hole_order_in_listctxfieldsn|StructDesc(d,l)->(* As [Struct], with the descriptor operand evaluated last (after the
field values). *)letfields=matchCell.get(expression_typectxi)with|Valtype{typ=Ref{typ=Typet|Exactt;_};_}->(matchlookup_struct_typectxtwith|Somefields->letfield_map=List.fold_left(funacc(name,instr)->StringMap.addname.descinstracc)StringMap.emptylinArray.map(funfield->StringMap.find(fstfield.desc).descfield_map)fields|>Array.to_list|>List.filter_mapFun.id|None->List.filter_mapsndl)|_->List.filter_mapsndlincheck_hole_order_in_listctx(fields@[d])n|Select(c,t,e)->n|>check_hole_order_recctxt|>check_hole_order_recctxe|>check_hole_order_recctxc|Hole->assertfalseinifn=0then0else(Error.before_holectx.diagnostics~location:(sndi.info);raiseExit)andcheck_hole_order_in_listctxln=List.fold_left(funni->check_hole_order_recctxin)nlletcheck_hole_orderctxln=trylet_:int=check_hole_order_recctxlnintruewithExit->falseletpop_parameterst=matchstwith[]->assertfalse|x::r->(r,x)let_print_arg_stackfl=Format.pp_print_list~pp_sep:(funf()->Format.fprintff"@ ")output_inferred_typefl(* Peel the condition / reference operand off the last slot of a branch
instruction's operand types, returning it together with the remaining branch
parameters. The operand is an arbitrary expression, which may type to no
value at all (e.g. a call to a function with no results); rather than assert,
report the missing operand and recover with an unknown value. *)letsplit_on_last_typectx~locationi=leta=fsti.infoinletlen=Array.lengthainiflen=0then(Error.value_count_mismatchctx.diagnostics~location~expected:1~provided:0;(Cell.makeError,[||]))else(a.(len-1),Array.suba0(len-1))letimmediate_supertypes:Ast.heaptype=match(s.supertype,s.typ)with|Somet,_->Typet|None,Struct_->Struct|None,Array_->Array|None,Func_->Func|None,Cont_->Cont(* The top of [h]'s subtyping hierarchy ([Any]/[Func]/[Extern]/[Exn]/[Cont]),
without reporting an unbound type ([None] then). *)letheaptype_topctx(h:Ast.heaptype):Ast.heaptypeoption=matchhwith|Any|Eq|I31|Struct|Array|None_->SomeAny|Func|NoFunc->SomeFunc|Extern|NoExtern->SomeExtern|Exn|NoExn->SomeExn|Cont|NoCont->SomeCont|Typeid|Exactid->(matchTbl.find_optctx.type_context.typesidwith|Some(_,s)->(matchs.typwith|Struct_|Array_->SomeAny|Func_->SomeFunc|Cont_->SomeCont)|None->None)(* A bottom reference of a hierarchy. *)letis_bottom_heaptype=function|None_|NoFunc|NoExtern|NoExn|NoCont->true|_->false(* Lint a reference cast ([is_test = false]) or test ([is_test = true]) given the
operand's inferred type and the interned target type. Under single-inheritance
subtyping two heap types share a value only when one is a subtype of the
other, so unrelated types make the cast always trap / the test always false
(unless a shared [null] slips through); an operand that already has the target
type makes it redundant. A bottom-reference operand is skipped: its cast is
load-bearing (dropping it loses the type the value stands in for). *)letlint_ref_castctx~location~is_testop_naturaltarget_natural=letinfo=subtyping_infoctxinmatch(op_natural,target_natural)with|(Valtype{typ=Ref{typ=op_src;_};internal=Refop;_},Valtype{internal=Reftgt;_})whennot(is_bottom_heaptypeop_src)->letrelated=Wax_wasm.Types.heap_subtypeinfoop.typtgt.typ||Wax_wasm.Types.heap_subtypeinfotgt.typop.typinif(notrelated)&¬(op.nullable&&tgt.nullable)thenError.cast_always_failsctx.diagnostics~location~is_testelseifWax_wasm.Types.ref_subtypeinfooptgtthenError.redundant_castctx.diagnostics~location~is_test|_->()(* The type lookups below never fail *)letrecheap_lubctx(h1:Ast.heaptype)(h2:Ast.heaptype)=match(h1,h2)with(* A bottom reference is below everything in its hierarchy, so its lub with any
type of that same hierarchy is that other type. Handle this before walking a
concrete [Type] up to its supertype, which would otherwise discard the
bottom and over-generalise (e.g. [lub(none, $t)] giving [struct] not [$t]). *)|b,hwhenis_bottom_heaptypeb&&heaptype_topctxb=heaptype_topctxh->Someh|h,bwhenis_bottom_heaptypeb&&heaptype_topctxb=heaptype_topctxh->Someh(* [exact] survives a lub only when both sides are the same exact type; any
generalization drops exactness (an [exact a]/[exact b] pair joins at their
common non-exact supertype). *)|Exactid1,Exactid2->let*@i1,_=Tbl.find_optctx.type_context.typesid1inlet*@i2,_=Tbl.find_optctx.type_context.typesid2inifi1=i2thenSome(Exactid1)elseheap_lubctx(Typeid1)(Typeid2)|Exactid1,h->heap_lubctx(Typeid1)h|h,Exactid2->heap_lubctxh(Typeid2)|Typeid1,Typeid2->let*@i1,s1=Tbl.find_optctx.type_context.typesid1inlet*@i2,s2=Tbl.find_optctx.type_context.typesid2inifi1>i2thenheap_lubctx(immediate_supertypes1)h2elseifi2>i1thenheap_lubctxh1(immediate_supertypes2)elseSomeh1|Typeid1,_->let*@_,s1=Tbl.find_optctx.type_context.typesid1inheap_lubctx(immediate_supertypes1)h2|_,Typeid2->let*@_,s2=Tbl.find_optctx.type_context.typesid2inheap_lubctxh1(immediate_supertypes2)(* Abstract hierarchy *)|None_,None_->SomeNone_|(None_|I31),I31|I31,None_->SomeI31|(None_|Struct),Struct|Struct,None_->SomeStruct|(None_|Array),Array|Array,None_->SomeArray|(None_|I31|Struct|Array|Eq),Eq|Eq,(None_|I31|Struct|Array)|(Struct|Array),I31|I31,(Struct|Array)|Struct,Array|Array,Struct->SomeEq|(None_|I31|Struct|Array|Eq|Any),Any|Any,(None_|Eq|I31|Struct|Array)->SomeAny|NoFunc,NoFunc->SomeNoFunc|(NoFunc|Func),Func|Func,NoFunc->SomeFunc|NoExtern,NoExtern->SomeNoExtern|(NoExtern|Extern),Extern|Extern,NoExtern->SomeExtern|NoExn,NoExn->SomeNoExn|(NoExn|Exn),Exn|Exn,NoExn->SomeExn|NoCont,NoCont->SomeNoCont|(NoCont|Cont),Cont|Cont,NoCont->SomeCont|((None_|Eq|I31|Struct|Array|Any),(NoExtern|Extern|NoExn|Exn|NoFunc|Func))|((NoExtern|Extern|NoExn|Exn|NoFunc|Func),(None_|Eq|I31|Struct|Array|Any))|(NoFunc|Func),(NoExtern|Extern|NoExn|Exn)|(NoExtern|Extern|NoExn|Exn),(NoFunc|Func)|(NoExtern|Extern),(NoExn|Exn)|(NoExn|Exn),(NoExtern|Extern)(* Continuation types form their own hierarchy, incompatible with all
others (and have no Wax surface syntax). *)|(Cont|NoCont),_|_,(Cont|NoCont)->Noneletval_lubctxv1v2=match(v1,v2)with|Refr1,Refr2->let+@lub=heap_lubctxr1.typr2.typinletnullable=r1.nullable||r2.nullableinRef{nullable;typ=lub}|_->ifv1=v2thenSomev1elseNone(* The least upper bound of two value type cells, or [None] when they have no
common type. Mirrors the [Select] (?:) reconciliation: it pins an
as-yet-unconstrained literal/[null] to the other side and lubs two reference
types via [val_lub]. Used to combine the values reaching a block's exit (the
branches of an [if], etc.) when inferring the block's result type. *)letjoin_value_typesctxty1ty2=match(Cell.getty1,Cell.getty2)with(* [Unknown]/[Error] are the universal bottom: the other side wins (the lub of
[Unknown] and [UnknownRef] is the more informative [UnknownRef]). [UnknownRef]
(a bottom reference) joins with any other reference — concrete, [Null] or
another [UnknownRef] — which, being a supertype, wins; a bottom reference
paired with a non-reference (e.g. [i32]) has no common type and falls
through to a mismatch. *)(* An [Unknown] value reaching a block's exit (a hole on the polymorphic stack of
dead code) genuinely takes the block's result type: pin it (merge), so a
branch that also passes it through — a [br_if]/[br_on_null] whose value is then
cast — sees the resolved width rather than staying [Unknown], which would make
[To_wasm] drop the cast. [Error] (already reported) stays the untouched bottom. *)|_,Unknown->Cell.mergety1ty2(Cell.getty1);Somety1|Unknown,_->Cell.mergety1ty2(Cell.getty2);Somety2|_,Error->Somety1|Error,_->Somety2|UnknownRef,UnknownRef->(* Merge the two bottom references so pinning one (later, against a concrete
type) pins the other too. *)Cell.mergety1ty2UnknownRef;Somety1|(Valtype{internal=Ref_;_}|Null),UnknownRef->Cell.setty2(Cell.getty1);Somety1|UnknownRef,(Valtype{internal=Ref_;_}|Null)->Cell.setty1(Cell.getty2);Somety2|Null,Null->(* Unify the two nulls so pinning one (later, to a reference type) pins the
other too. *)Cell.mergety1ty2Null;Somety1|Valtype{internal=I32;_},Valtype{internal=I32;_}|Valtype{internal=I64;_},Valtype{internal=I64;_}|Valtype{internal=F32;_},Valtype{internal=F32;_}|Valtype{internal=F64;_},Valtype{internal=F64;_}->Somety2|(Int|Number),(Int|Valtype{internal=I32|I64;_})|(Float|Number),(Float|Valtype{internal=F32|F64;_})|Number,Number->Cell.mergety1ty2(Cell.getty2);Somety2|((Valtype{internal=I32;_}|Valtype{internal=I64;_}),(Int|Number))|((Valtype{internal=F32;_}|Valtype{internal=F64;_}),(Float|Number))|(Int|Float),Number->Cell.mergety1ty2(Cell.getty1);Somety1(* A [LargeInt] (literal too big for i32) defaults to i64 and is also
convertible to a float. It joins with another [LargeInt] or a fully-flexible
[Number] staying [LargeInt] (i64/f32/f64), or with a concrete i64/f32/f64 or a
flexible float taking that type, but never with i32. A committed [Int], being
integer-only, has i64 as its sole common type with a [LargeInt] — pin it
there, so the join cannot later be coerced to a float the [Int] cannot be.
Mirrors [check_int_bin_op]/[check_num_concrete]. *)|LargeInt,Int|Int,LargeInt->Cell.mergety1ty2(Valtypei64_valtype);Somety1|LargeInt,(LargeInt|Number)->Cell.mergety1ty2LargeInt;Somety1|Number,LargeInt->Cell.mergety1ty2LargeInt;Somety2|LargeInt,(Float|Valtype{internal=I64|F32|F64;_})->Cell.mergety1ty2(Cell.getty2);Somety2|(Float|Valtype{internal=I64|F32|F64;_}),LargeInt->Cell.mergety1ty2(Cell.getty1);Somety1|Valtype{typ=typ1;_},Valtype{typ=typ2;_}->(matchval_lubctxtyp1typ2with|Somety->internalizectxty|None->None)|Valtype{typ=Ref{typ;_};_},Null->(matchinternalizectx(Ref{typ;nullable=true})with|Somety->Cell.setty2(Cell.getty);Somety|None->None)|Null,Valtype{typ=Ref{typ;_};_}->(matchinternalizectx(Ref{typ;nullable=true})with|Somety->Cell.setty1(Cell.getty);Somety|None->None)|_->Noneletaddress_valtype(at:[`I32|`I64]):inferred_valtype=matchatwith`I32->i32_valtype|`I64->i64_valtypeletaddress_cellat=valtype_cell(address_valtypeat)(* Expected operand/result type of a SIMD intrinsic, as a fresh type cell. *)letsimd_valtype:Simd.ty->inferred_valtype=function|TV128->{typ=V128;internal=V128;anon_comptype=None}|TI32->i32_valtype|TI64->i64_valtype|TF32->f32_valtype|TF64->f64_valtypeletsimd_cellt=valtype_cell(simd_valtypet)letsimd_ty_namet=numtype_name(simd_valtypet).typ(* The member-completion candidate for a SIMD method [name] (e.g. [add_i32x4]),
its signature read straight from the registry the typer dispatches through
([Simd.classify]): the leading constant lane immediates, then the
non-receiver stack operands, then the result. *)letsimd_method_candidatename=letdetail=matchSimd.classifynamewith|Some{operands=_receiver::rest;result;imm;_}->letimm_params=matchimmwith|Simd.No_imm->[]|Lane_->["lane index"]|Shuffle->["16 lane indices"]inletparams=imm_params@List.mapsimd_ty_namerestinletresult=matchresultwithSomet->simd_ty_namet|None->"()"inPrintf.sprintf"fn(%s) -> %s"(String.concat", "params)result|_->""in{member_name=name;member_kind=Method;member_detail=detail}(* The value methods offered by member completion for a [v128] receiver — the
vector ops [v.add_i32x4(w)], enumerated from the SIMD registry (so, unlike
the scalar registries above, no drift is possible: the same table classifies
the call). *)letsimd_v128_methods()=List.mapsimd_method_candidate(Simd.method_namesSimd.TV128)(* The value-method candidates member completion offers for a numeric receiver
of inferred type [t], or [None] if it has none. Beyond the concrete numeric
valtypes ([i32] … [f64], [v128]), a receiver can still be a flexible literal
type: an [int] takes its integer methods only, a [number] or [large number]
both families (either narrowing is still open), a [float] its float methods
only. A packed [i8]/[i16] read must be cast before any method, so gets none.
The [from_bits]/[to_bits] reinterpretation flips the family, rendered by
family name for a flexible receiver since the width is uncommitted. *)letnumeric_receiver_candidates(t:inferred_type):member_candidatelistoption=letints~recv_name~reinterp=method_candidates~recv_name~reinterp_name:reinterpinteger_methodsinletfloats~recv_name~reinterp=method_candidates~recv_name~reinterp_name:reinterpfloat_methodsinmatchtwith|Valtype{typ=I32;_}->Some(ints~recv_name:"i32"~reinterp:"f32")|Valtype{typ=I64;_}->Some(ints~recv_name:"i64"~reinterp:"f64")|Valtype{typ=F32;_}->Some(floats~recv_name:"f32"~reinterp:"i32")|Valtype{typ=F64;_}->Some(floats~recv_name:"f64"~reinterp:"i64")|Valtype{typ=V128;_}->Some(simd_v128_methods())|Int->Some(ints~recv_name:"int"~reinterp:"float")|Number->Some(ints~recv_name:"number"~reinterp:"float"@floats~recv_name:"number"~reinterp:"int")|LargeInt->Some(ints~recv_name:"large number"~reinterp:"float"@floats~recv_name:"large number"~reinterp:"int")|Float->Some(floats~recv_name:"float"~reinterp:"int")|_->None(* Whether a value receiver of type [t] has value methods, as an [R_numeric]
descriptor — the cheap classification the recorder uses to decide whether to
record, without building the (possibly large) candidate list. Its domain must
match [numeric_receiver_candidates] returning [Some]. *)letnumeric_receiver_kind(t:inferred_type):member_receiveroption=matchtwith|Valtype{typ=I32|I64|F32|F64|V128;_}|Int|Number|LargeInt|Float->Some(R_numerict)|_->Noneletaddress_type_name:[`I32|`I64]->string=function|`I32->"i32"|`I64->"i64"(* [fn(<params>) -> <result>], with an empty result rendered [()] and several
as a tuple. *)letrender_signatureparamsresult=letresult=matchresultwith|[]->"()"|[r]->r|rs->"("^String.concat", "rs^")"inPrintf.sprintf"fn(%s) -> %s"(String.concat", "params)result(* The methods member completion offers on a continuation-typed receiver — the
resume family and [switch] — with [params]/[results] the rendered parameter
and result types of the continuation's function type and [switch_results]
the rendered results of a [switch] (the last parameter's own continuation
parameters, when it has one). Unlike the other receivers, the candidate
list is built at record time (the signatures need the type context) and
carried by {!R_cont}; the editor's signature help rebuilds it from the
declarations. *)letcont_method_candidates~params~results~switch_results=letmmember_namemember_detail={member_name;member_kind=Method;member_detail}inletleading=List.filteri(funi_->i<List.lengthparams-1)paramsin[m"resume"(render_signatureparamsresults);m"resume_throw"(render_signature["tag(payload)"]results);m"resume_throw_ref"(render_signature["&?exn"]results);m"switch"(render_signature(leading@["tag: tag"])switch_results);](* Build the {!R_cont} descriptor of a receiver of declared continuation type
[ct], rendering the method signatures from the type context. *)letcont_receiverctxct=letrender(t:Ast.valtype)=String.trim(Format.asprintf"%a"Output.valtypet)inletsign=let*@inner=lookup_cont_innerctxctinlookup_func_typectxinnerinletparams,results=matchsignwith|Somesg->(Array.to_list(Array.map(funp->render(sndp.Ast.desc))sg.params),Array.to_list(Array.maprendersg.results))|None->([],[])inletswitch_results=matchlet*@sg=signinletn=Array.lengthsg.paramsinifn=0thenNoneelsematchsndsg.params.(n-1).Ast.descwith|Ast.Ref{typ=Typect2|Exactct2;_}->let*@inner2=lookup_cont_innerctxct2inlet*@sg2=lookup_func_typectxinner2inSome(Array.to_list(Array.map(funp->render(sndp.Ast.desc))sg2.params))|_->Nonewith|Somers->rs|None->[]inR_cont(cont_method_candidates~params~results~switch_results)(* The atomic memory accesses ([mem.atomic_load32(addr)],
[mem.atomic_rmw_add8(addr, v)], …), enumerated from the
{!Wax_wasm.Atomics.families} the typer dispatches on; the address takes
[addr_name]. The name carries the access width only: a narrow load returns
the raw-bits [i8]/[i16] (resolved by a surrounding [as iN_u] cast) and a
narrow store/RMW value picks the i32/i64 family by its type (rendered
[int]); the 64-bit accesses are necessarily [i64]. *)letatomic_method_candidates~addr_name=letvalue:Wax_wasm.Atomics.width->string=function|`W8|`W16|`W32->"int"|`W64->"i64"inletload_result:Wax_wasm.Atomics.width->string=function|`W8->"i8"|`W16->"i16"|`W32->"i32"|`W64->"i64"inList.map(funf->letoperands,results=match(f:Wax_wasm.Atomics.family)with|Loadw->([],[load_resultw])|Storew->([valuew],[])|Rmw(Wax_wasm.Ast.AtomicCmpxchg,w)->([valuew;valuew],[valuew])|Rmw(_,w)->([valuew],[valuew])|Wait`I32->(["i32";"i64"],["i32"])|Wait`I64->(["i64";"i64"],["i32"])|Notify->(["i32"],["i32"])in{member_name=Wax_wasm.Atomics.method_namef;member_kind=Method;member_detail=render_signature((addr_name::operands)@["offset?: int"])results;})Wax_wasm.Atomics.families(* The SIMD memory accesses ([mem.loadv128(addr)],
[mem.load8_lane(addr, v, lane)], …), enumerated from
{!Wax_wasm.Simd.mem_method_names}; the first operand is the address. *)letsimd_mem_method_candidates~addr_name=List.map(funname->letmi:Simd.mem_intrinsic=Option.get(Simd.mem_methodname)inletrest=matchmi.m_operandswith|_addr::r->List.mapsimd_ty_namer|[]->[]inletparams=(addr_name::rest)@(ifmi.m_lanethen["lane: int"]else[])@["offset?: int";"align?: int"]in{member_name=name;member_kind=Method;member_detail=render_signatureparams(matchmi.m_resultwithSomet->[simd_ty_namet]|None->[]);})Simd.mem_method_names(* The value methods member completion offers on a memory receiver
[mem.load8(addr)], with [addr_name] the memory's address type: the scalar
loads/stores (with their optional labelled [offset]/[align] immediates),
the size/grow/fill/copy/init management ops, and the atomic and SIMD memory
accesses. *)letmemory_method_candidates~addr_name=letmmember_namemember_detail={member_name;member_kind=Method;member_detail}inletloadnamer=mname(Printf.sprintf"fn(%s, offset?: int, align?: int) -> %s"addr_namer)inletstorenamev=mname(Printf.sprintf"fn(%s, %s, offset?: int, align?: int) -> ()"addr_namev)in[load"load8""i32";load"load16""i32";load"load32""i32";load"load64""i64";load"loadf32""f32";load"loadf64""f64";store"store8""i32";store"store16""i32";store"store32""i32";store"store64""i64";store"storef32""f32";store"storef64""f64";m"size"(Printf.sprintf"fn() -> %s"addr_name);m"grow"(Printf.sprintf"fn(%s) -> %s"addr_nameaddr_name);m"fill"(Printf.sprintf"fn(%s, i32, %s) -> ()"addr_nameaddr_name);m"copy"(Printf.sprintf"fn(%s, %s, %s) -> ()"addr_nameaddr_nameaddr_name);m"init"(Printf.sprintf"fn(data, %s, i32, i32) -> ()"addr_name);]@atomic_method_candidates~addr_name@simd_mem_method_candidates~addr_name(* The value methods member completion offers on a table receiver [tab.size()],
with [addr_name] the table's address type and [elem_name] its element type:
the size/grow/fill/copy/init management ops. Element access is [tab[i]], not
a method. *)lettable_method_candidates~addr_name~elem_name=letmmember_namemember_detail={member_name;member_kind=Method;member_detail}in[m"size"(Printf.sprintf"fn() -> %s"addr_name);m"grow"(Printf.sprintf"fn(%s, %s) -> %s"elem_nameaddr_nameaddr_name);m"fill"(Printf.sprintf"fn(%s, %s, %s) -> ()"addr_nameelem_nameaddr_name);m"copy"(Printf.sprintf"fn(%s, %s, %s) -> ()"addr_nameaddr_nameaddr_name);m"init"(Printf.sprintf"fn(elem, %s, i32, i32) -> ()"addr_name);](* The methods member completion offers on an array receiver [a.length()] with
element [elem]: [length], and the [fill]/[copy]/[init] bulk operations (the
last from a data / element segment). Indices and counts are [i32]; [fill]'s
value and [copy]'s source array are the element type. *)letarray_method_candidateselem=letmmember_namemember_detail={member_name;member_kind=Method;member_detail}inletvalue=render_fieldtype{elemwithAst.mut=false}inletarr="&["^render_fieldtypeelem^"]"in[m"length""fn() -> i32";m"fill"(Printf.sprintf"fn(i32, %s, i32) -> ()"value);m"copy"(Printf.sprintf"fn(i32, %s, i32, i32) -> ()"arr);m"init"(Printf.sprintf"fn(seg, i32, i32, i32) -> ()");](* The member-completion candidates a recorded {!member_receiver} stands for,
derived on demand (the editor forces only the one under the cursor). *)letmember_candidates:member_receiver->member_candidatelist=function|R_numerict->Option.value~default:[](numeric_receiver_candidatest)|R_structfields->struct_candidatesfields|R_arrayelem->array_method_candidateselem|R_memoryat->memory_method_candidates~addr_name:(address_type_nameat)|R_table(at,rt)->table_method_candidates~addr_name:(address_type_nameat)~elem_name:(render_reftypert)|R_contl->l(* Free-function members offered after [v128::] — [bitselect] and the per-shape
const constructors — with signatures from the SIMD registry. *)letsimd_free_members()=List.map(funname->letfull=Simd.free_fullnameinletdetail=matchSimd.const_shape_of_namefullwith|Someshape->Printf.sprintf"fn(%d lanes) -> v128"(Simd.const_arityshape)|None->(matchSimd.classifyfullwith|Some{operands;result;_}->Printf.sprintf"fn(%s) -> %s"(String.concat", "(List.mapsimd_ty_nameoperands))(matchresultwithSomet->simd_ty_namet|None->"()")|None->"")in{member_name=name;member_kind=Function;member_detail=detail})Simd.free_member_names(* The free functions offered by completion after an intrinsic namespace path
[ns::]: [v128::] holds the SIMD const constructors and [bitselect], [i64::]
the wide-arithmetic ops, [atomic::] the memory fence. Mirrors the dispatch in
[type_path_intrinsic_call] / [type_wide_arith_call] (test/method-consistency
type-checks each offered call). Empty for an unknown namespace. *)letnamespace_membersns:member_candidatelist=letfnmember_namemember_detail={member_name;member_kind=Function;member_detail}inletwide="fn(i64, i64, i64, i64) -> (i64, i64)"inletmul="fn(i64, i64) -> (i64, i64)"inmatchnswith|"v128"->simd_free_members()|"i64"->[fn"add128"wide;fn"sub128"wide;fn"mul_wide_s"mul;fn"mul_wide_u"mul;]|"atomic"->[fn"fence""fn() -> ()"]|_->[](* The intrinsic namespace names ([v128], [i64], [atomic]), for completion of
the [ns] before [::]. Exactly the namespaces {!namespace_members} answers. *)letintrinsic_namespaces=["v128";"i64";"atomic"](* Memory access method names. The value width is in the name; signedness and the
i32/i64 result come from a surrounding [as iN_s/u] cast (see [to_wasm]). *)letmem_load_resultmeth:inferred_typeoption=matchmethwith|"load8"->SomeInt8|"load16"->SomeInt16|"load32"->Some(Valtypei32_valtype)|"load64"->Some(Valtypei64_valtype)|"loadf32"->Some(Valtypef32_valtype)|"loadf64"->Some(Valtypef64_valtype)|_->Noneletmem_store_methodmeth=matchmethwith|"store8"|"store16"|"store32"|"store64"|"storef32"|"storef64"->true|_->falseletis_mem_methodmeth=mem_load_resultmeth<>None||mem_store_methodmeth(* Natural alignment (in bytes) of a scalar memory access. *)letmem_natural_alignmeth=matchmethwith|"load8"|"store8"->1|"load16"|"store16"->2|"load32"|"store32"|"loadf32"|"storef32"->4|"load64"|"store64"|"loadf64"|"storef64"->8|_->1(* The unsigned 64-bit value of an integer literal, or [None] if it is not an
integer literal or does not fit u64. Parsed quietly (a plain [of_string] would
print "Unsigned int overflow" before raising on an out-of-range value). *)letint_literala=matcha.Ast.descwith|Ast.Ints->(ifString.starts_with~prefix:"0x"sthenInt64.of_string_optselseInt64.of_string_opt("0u"^s))|>Option.mapWax_utils.Uint64.of_int64|_->Noneletmax_offset_i32_exclusive=Wax_utils.Uint64.of_string"0x1_0000_0000"(* 2^32 *)letmax_align=Wax_utils.Uint64.of_int16(* Validate the trailing [align]/[offset] literals of a memory access against
the access's natural alignment (in bytes) and the address type. Mirrors
[Validation.check_memarg]. [align] and [offset] are the corresponding
argument expressions, when present. *)letcheck_memargctx~address_type~natural~align~offset=(let>@offset=offsetinmatchint_literaloffsetwith|None->(* The literal does not fit u64, so it cannot be a memory offset. *)Error.memory_immediate_too_largectx.diagnostics~location:(sndoffset.info)|Someo->ifaddress_type=`I32&&Wax_utils.Uint64.compareomax_offset_i32_exclusive>=0thenError.memory_offset_too_largectx.diagnostics~location:(sndoffset.info)max_offset_i32_exclusive);let>@align=aligninmatchint_literalalignwith|None->Error.memory_immediate_too_largectx.diagnostics~location:(sndalign.info)|Somea->(ifWax_utils.Uint64.compareamax_align>0||Wax_utils.Uint64.to_inta>naturalthenError.memory_align_too_largectx.diagnostics~location:(sndalign.info)naturalelsematchWax_utils.Uint64.to_intawith|1|2|4|8|16->()|_->Error.bad_memory_alignctx.diagnostics~location:(sndalign.info))(* Split a memory-access call's (typed) argument list into the positional
stack operands and the labelled immediates. A positional argument after a
labelled one is reported and kept positional, for recovery. *)letsplit_labelled_argsctxargs=letrecsplitpositionallabelled=function|[]->(List.revpositional,List.revlabelled)|a::rest->(matcha.Ast.descwith|Ast.Labelled(l,e)->splitpositional((l,e)::labelled)rest|_->iflabelled<>[]thenError.positional_argument_after_labelctx.diagnostics~location:(snda.Ast.info);split(a::positional)labelledrest)insplit[][]args(* Check the labelled immediates of a memory access against the label names
[allowed] for it — an unknown or duplicate label is reported, and the
payload of an accepted label must be an integer literal — and return a
by-name lookup of the payloads. *)lettake_labelsctx~allowedlabelled=lettake(seen,acc)((l:Ast.ident),e)=ifnot(List.meml.descallowed)then(Error.unknown_argument_labelctx.diagnostics~location:l.info~suggestions:(Wax_utils.Spell_check.f(funf->List.iterfallowed)l.desc)l;(seen,acc))elseifStringSet.meml.descseenthen(Error.duplicate_argument_labelctx.diagnostics~location:l.infol;(seen,acc))elsematche.Ast.descwith|Ast.Int_->(StringSet.addl.descseen,(l.desc,e)::acc)|_->(* Report it and drop the pair, so [check_memarg] (which would also
fail to read it as a literal) does not report it again. *)Error.constant_expression_requiredctx.diagnostics~location:(snde.Ast.info);(StringSet.addl.descseen,acc)inlet_,acc=List.fold_lefttake(StringSet.empty,[])labelledinfunname->List.assoc_optnameacc(* The [lane]/[align]/[offset] immediates of a memory access with [nstack]
stack operands, from the label lookup [find]. Extra positional arguments
are the pre-labelled-arguments syntax when they are integer literals — the
targeted migration error is reported and they still fill the immediates in
the old positional order ([lane,] align, offset), so old code gets exactly
one error and no cascade — and an ordinary arity error otherwise. *)letmem_immediatesctx~location~example~nstack~has_lanefindpositional=letnargs=List.lengthpositionalinletextra=List.filteri(funk_->k>=nstack)positionalin(ifnargs<nstackthenError.value_count_mismatchctx.diagnostics~location~expected:nstack~provided:nargselsematchextrawith|[]->()|a::_->letnimms=ifhas_lanethen3else2inifList.lengthextra<=nimms&&List.for_all(funa->matcha.Ast.descwithAst.Int_->true|_->false)extrathenError.positional_memory_immediatectx.diagnostics~location:(snda.Ast.info)~exampleelseError.value_count_mismatchctx.diagnostics~location~expected:nstack~provided:nargs);letpicknamek=matchfindnamewithSomee->Somee|None->List.nth_optextrakinifhas_lanethen(pick"lane"0,pick"align"1,pick"offset"2)else(None,pick"align"0,pick"offset"1)(* [min(2^bits - 1, 2^(bits - p))]; mirrors [Validation.max_memory_size]. *)letmax_memory_sizeaddress_typepage_size_log2=letp=matchpage_size_log2withNone->16|Somep->pinletbits,index_max=matchaddress_typewith|`I32->(32,Wax_utils.Uint64.of_string"0xffff_ffff")|`I64->(64,Wax_utils.Uint64.of_string"0xffff_ffff_ffff_ffff")inlete=bits-pinletby_page=ife>=64thenindex_maxelseife<=0thenWax_utils.Uint64.zeroelseWax_utils.Uint64.of_int64(Int64.shift_left1Le)inifWax_utils.Uint64.compareindex_maxby_page<=0thenindex_maxelseby_pageletmax_table_sizeaddress_type_page_size_log2=matchaddress_typewith|`I32->Wax_utils.Uint64.of_string"0xffff_ffff"|`I64->Wax_utils.Uint64.of_string"0xffff_ffff_ffff_ffff"(* Validate a memory/table size limit and page size. Mirrors [Validation.limits]. *)letcheck_limitsctx~locationkind~sharedaddress_typepage_size_log2limitsmax_fn=(matchpage_size_log2with|None|Some(0|16)->()|Some_->Error.invalid_page_sizectx.diagnostics~location);ifshared&&matchlimitswithSome(_,Some_)->false|_->truethenError.shared_memory_without_maxctx.diagnostics~location;matchlimitswith|None->()|Some(mi,ma)->(letmax=max_fnaddress_typepage_size_log2inmatchmawith|None->ifWax_utils.Uint64.comparemimax>0thenError.limit_too_largectx.diagnostics~locationkindmax|Somema->ifWax_utils.Uint64.comparemima>0thenError.limit_mismatchctx.diagnostics~locationkind;ifWax_utils.Uint64.comparemamax>0thenError.limit_too_largectx.diagnostics~locationkindmax)(* Management methods shared by memories and tables, dispatched by the receiver
(a memory or table name). *)letis_mgmt_methodm=matchmwith"size"|"grow"|"fill"|"copy"|"init"->true|_->false(* No-argument instruction methods written as a call on a value, [x.sqrt()]:
the integer and float unary operators, the [to_bits]/[from_bits] reinterpret
casts, and [arr.length()]. They are parsed as [Call (StructGet …, [])] and
kept in that form so they print back with their parentheses. *)letis_unary_methodm=matchmwith|"clz"|"ctz"|"popcnt"|"extend8_s"|"extend16_s"|"abs"|"ceil"|"floor"|"trunc"|"nearest"|"sqrt"|"to_bits"|"from_bits"|"length"->true|_->false(* Register (once) a type definition for an anonymous function signature and
return the synthetic name standing for it — used when a cast or [call_ref]
needs a named [func] type but the source wrote the signature inline. The name
is a deterministic mangling of the signature, so identical signatures map to
the same definition; [to_wasm] materialises it through the [<..>]
synthetic-type path. *)letanon_function_typectx(sign:functype)=letbuf=Buffer.create32inletrecvt(t:valtype)=matchtwith|I32->Buffer.add_charbuf'i'|I64->Buffer.add_charbuf'I'|F32->Buffer.add_charbuf'f'|F64->Buffer.add_charbuf'F'|V128->Buffer.add_charbuf'v'|Ref{nullable;typ}->Buffer.add_charbuf'&';ifnullablethenBuffer.add_charbuf'?';httypandht(h:heaptype)=Buffer.add_stringbuf(matchhwith|Func->"func"|NoFunc->"nofunc"|Exn->"exn"|NoExn->"noexn"|Cont->"cont"|NoCont->"nocont"|Extern->"extern"|NoExtern->"noextern"|Any->"any"|Eq->"eq"|I31->"i31"|Struct->"struct"|Array->"array"|None_->"none"|Typeid->"$"^id.desc|Exactid->"!$"^id.desc)inBuffer.add_stringbuf"<fn:";Array.iter(funp->vt(sndp.desc);Buffer.add_charbuf';')sign.params;Buffer.add_stringbuf"->";Array.iter(funt->vtt;Buffer.add_charbuf';')sign.results;Buffer.add_charbuf'>';letname=Ast.no_loc(Buffer.contentsbuf)in(* A pure existence check: [Tbl.exists] would also *report* a spurious
"already bound" error on the second cast with the same signature. *)ifTbl.find_optctx.type_context.typesname=Nonethenignore(add_typectx.diagnosticsctx.type_context[|Ast.no_loc(name,{supertype=None;typ=Funcsign;final=true;descriptor=None;describes=None;});|]:Wax_wasm.Types.Id.toption);name(* Peel a type-checked [dispatch] lowering (see [Ast_utils.lower_dispatch]) back
apart: descend [k] case blocks, collecting each case body, and return the
[br_table] index together with the bodies in arm order. Deterministic — the
lowering we just type-checked guarantees the shape. *)letextract_dispatchwrapperk=letbody_ofw=matchw.descwithAst.Block{block;_}->block.desc|_->assertfalseinletrecpeelblockn=ifn=0thenmatchblockwith|[{desc=Ast.Br_table(_,idx);_}]->(idx,[])|_->assertfalseelsematchblockwith|head::tail->letidx,bodies=peel(body_ofhead)(n-1)in(idx,tail::bodies)|[]->assertfalseinpeel(body_ofwrapper)k(* Rebuild a typed [dispatch] from the type-checked lowering [typed_list] (the
outermost case block followed by its trailing body) and the original [arms]
(for the labels). Arms are in fall-through order, the reverse of the block
nesting (see [Ast_utils.lower_dispatch]), so we peel against the reversed arm
list — outermost first — and reverse the result back. Returns the typed index
and arms. *)letrebuild_dispatchtyped_listarms=match(List.revarms,typed_list)with|[],[{desc=Ast.Br_table(_,idx);_}]->(idx,[])|(outer_label,outer_orig)::rest_arms,outer::outer_body->letidx,rest_bodies=extract_dispatchouter(List.lengthrest_arms)in(idx,List.rev((outer_label,{outer_origwithdesc=outer_body})::List.map2(fun(l,orig)b->(l,{origwithdesc=b}))rest_armsrest_bodies))|_->assertfalse(* Peel a type-checked [while] lowering (see [Ast_utils.lower_while]) back to the
typed condition, continue-expression and body, dropping the synthesised loop,
[if] and back-edge. Deterministic — the lowering we just type-checked
guarantees the shape. [stepped]/[labelled] pick which of the three shapes
[lower_while] produced. *)letrebuild_while~stepped~labelledtyped_list=matchtyped_listwith|[{desc=Ast.Loop{block={desc=[{desc=If{cond;if_block;_};_}];_};_};_;};]->(match(stepped,labelled)with(* Labelled step: [ block { body } ; step ; br ] *)|true,true->(matchif_block.descwith|[{desc=Ast.Block{block={desc=body;_};_};_};step;{desc=Br_;_};]->(cond,Somestep,body)|_->assertfalse)(* Unlabelled step: [ body… ; step ; br ]; else just [ body… ; br ]. *)|_->(matchList.revif_block.descwith|{desc=Ast.Br_;_}::step::rev_bodywhenstepped->(cond,Somestep,List.revrev_body)|{desc=Ast.Br_;_}::rev_body->(cond,None,List.revrev_body)|_->assertfalse))|_->assertfalse(* Peel a type-checked [match] lowering (see [Ast_utils.lower_match]) apart. The
lowering nests one block per arm inside an outer void [escape] block, each
wrapping the previous block (its result consumed for the previous arm) then
that arm's body; the innermost block holds the threaded test chain and the
[escape] branch, and the [default] follows the [escape] block as trailing
code. Descending from the [escape] block consumes the arms in reverse source
order. Returns the typed arm bodies (paired with the original patterns) and
the typed default. *)letrebuild_matchtyped_listarms=matcharmswith|[]->([],typed_list)|_->letblock_bodyblk=matchblk.descwith|Ast.Block{block;_}->block.desc|_->assertfalsein(* Strip a wrapper block's leading consume of its inner block, returning
that inner block and the arm body following it. *)letunwrappatstmts=match(pat,stmts)with|(Ast.MatchCast(Some_,_),{desc=Ast.Let(_,Someinner);_}::body)->(inner,body)|(Ast.MatchCast(None,_),{desc=Ast.Let([(None,_)],Someinner);_}::body)->(inner,body)|Ast.MatchNull,inner::body->(inner,body)|_->assertfalseinletescape,default=matchtyped_listwithx::r->(x,r)|[]->assertfalseinletrecpeelblk=function|[]->[](* [blk] is the innermost block (test chain + escape). *)|(pat,orig)::rest_rev->letinner,arm_body=unwrappat(block_bodyblk)in(pat,{origwithdesc=arm_body})::peelinnerrest_revinletarms_rev=peelescape(List.revarms)in(List.revarms_rev,default)(* Synthesise the block labels for a [match] lowering: one per arm, then the
outer [escape] label ([n+1] in all). The [<…>] form is outside the source
identifier grammar, so it cannot capture a user branch. *)letmatch_labelsinfoarms=List.init(List.lengtharms+1)(funk->{desc=Printf.sprintf"<match%d>"k;info})(* The scrutinee's external reference type, used as the arm blocks' result type
(a failed test forwards the scrutinee there). [None] if it is not a single
reference value. *)letmatch_scrut_reftypectxscrut'=matchstandalone_valtypectx(expression_typectxscrut')with|Some{typ=Ref_astyp;_}->Sometyp|_->None(* Classify how a block's trailing instruction produces the block's value, as
[(needs_context, self_resolving)]. [needs_context] is a construction whose
type the surrounding context must pin (an ambiguous/named/default struct, an
array, a string, a [null] cast, or a [?:] with such a branch): it is checked
against the result, so a surrounding result annotation is load-bearing.
[self_resolving] resolves its own type (a nested block with no parameters, or
a struct named unambiguously by its fields). Anything else — a plain
statement, a parameterized block, a scalar [?:] — sets neither; a block then
types it on the statement path rather than against its result. *)letrecclassify_trailingctxdesc=matchdescwith|Struct(_,fields)|StructDesc(_,fields)->(matchinfer_struct_by_fieldsctxfieldswith|Some_->(false,true)|None->(true,false))|StructDefault_|StructDefaultDesc_|Array_|ArrayDefault_|ArrayFixed_|ArraySegment_|String_->(true,false)|If{typ;_}|Block{typ;_}|Loop{typ;_}|TryTable{typ;_}|Try{typ;_}|TryCatch{typ;_}->ifArray.lengthtyp.params=0then(false,true)else(false,false)|Cast(e,_)->(is_null_initializere,false)|Select(_,a,b)->(* Needs the context iff a branch does; a select is not itself a
self-resolving nested block. *)(fst(classify_trailingctxa.desc)||fst(classify_trailingctxb.desc),false)(* A branch hint is advisory: classify the wrapped branch itself. *)|Hinted(_,i)->classify_trailingctxi.desc|_->(false,false)(*** The instruction type-checker ***)(* Set to [true] to trace each instruction as it is type-checked. *)letdebug=false(* Deliver the values below a [br_if]/[br_on_null] operand to the branch target
and return their fall-through result types. Shared by both branches (their only
difference is what each appends to the result afterward: [br_on_null] adds the
non-null reference). [types] are the delivered values' types and [params] the
target's parameter types, both at [loc].
When the target is a block result being inferred, each delivered value is an
[exact] exit: its natural type — snapshotted here, before the delivery below
pins it — must equal the block's result, not merely be a subtype. A flexible
numeric literal among them can be pinned to a non-default width by a downstream
op on re-parse, so the annotation is kept ([cs.needed]). On the fall-through
the values stay typed as the target's result ([resolve_declared]); when the
target has no declared result that cell would leak, so fall back to the
operands' own [types]. *)letdeliver_to_branch_targetctx~loc~types~params=ifArray.lengthtypes=Array.lengthparamsthenArray.iter2(funtyparam->matchCell.getparamwith|Collectingcs->(cs.exacts<-(Someloc,Cell.make(Cell.getty))::cs.exacts;matchCell.gettywith|Number|Int|LargeInt|Float->cs.needed<-true|_->())|_->())typesparams;check_subtypesctx~location:loctypesparams;(* Guard the arity as the snapshot loop does: on a mismatch [check_subtypes]
has reported the arity error, so [map2] would only crash on the unequal
lengths — fall back to the target's params. *)ifArray.existsis_inferringparams&&Array.lengthtypes=Array.lengthparamsthenArray.map2(funtyparam->matchCell.getparamwith|Collecting{declared=None;_}->ty|_->resolve_declaredparam)typesparamselseparamsletrecinstructionctxi:'alist->'alist*(_,_array*_)annotated=ifdebugthenFormat.eprintf"%a@."Output.instri;matchi.descwith|Block_|Dispatch_|Match_|Loop_|While_|If_|If_annotation_|TryTable_|Try_|TryCatch_->type_block_constructctxi|(Unreachable|Nop)asdesc->(* [unreachable] and [nop] are statements that yield no value; they are
only meaningful in statement (top-level) position, where
[toplevel_instruction] handles them. Reaching here means one was used
where a value is expected, so report it and recover with an unknown
value. *)Error.not_an_expressionctx.diagnostics~location:i.info0;return_expressionidesc(Cell.makeError)|Hole->let*ty=pop_parameterinreturn_expressioniHolety|Null->return_expressioniNull(Cell.makeNull)|Get_|Set_|Tee_->type_variable_accessctxi|Path(ns,name)->Error.intrinsic_not_calledctx.diagnostics~location:i.infons.descname.desc;return_expressioni(Path(ns,name))(Cell.makeError)|Call_->call_instructionctxi|TailCall(i',l)->((* Type it exactly as the corresponding call — reusing the whole intrinsic
dispatch, so [become mem.grow(n)] etc. are accepted like [mem.grow(n)]
— then re-tag it as a tail call and require the callee's results to be
subtypes of this function's declared results. *)let*typed=call_instructionctx{iwithdesc=Call(i',l)}inmatchtyped.descwith|Call(i'',l')->check_subtypesctx~location:i.info(fsttyped.info)ctx.return_types;return_statementi(TailCall(i'',l'))[||]|_->(* The target types to a non-[Call] only when typing it already
failed: a [let*!] on a [None] lookup yields an [Unreachable] node
typed [Error] (with the error already reported). A call that
type-checks is always a [Call] — an ill-formed or indirect callee
too, via [type_indirect_call] — so there is no tail call to form
here; propagate the failed result rather than re-reporting or
[assert false]. *)returntyped)|Labelled(_,e)->(* A labelled argument is only meaningful as a direct argument of a
memory-access call, whose typers consume the labels before typing the
rest; reaching here means it appeared anywhere else (a plain call, a
non-memory method, …). Recover by typing the payload in place. *)Error.labelled_argument_not_allowedctx.diagnostics~location:i.info;instructionctxe|Char_asdesc->return_expressionidesci32_cell|Intsasdesc->(* Pick the lattice type from the magnitude (the sign is a separate [Neg],
so this is unsigned): a value over the 32-bit range cannot be i32, so it
is [LargeInt] (which defaults to i64) rather than the i32-defaulting
[Number]; one that does not even fit u64 cannot be any integer type, so
it is [Float] (representable only by f32/f64) — using it as an integer is
then a clean type error rather than an [Int64.of_string] crash in the
encoder. *)letlattice=matchifString.starts_with~prefix:"0x"sthenInt64.of_string_optselseInt64.of_string_opt("0u"^s)with|None->Float|SomevwhenInt64.unsigned_comparev0xFFFFFFFFL>0->LargeInt|Some_->Numberinreturn_expressionidesc(Cell.makelattice)|Float_asdesc->return_expressionidesc(Cell.makeFloat)|Cast_|CastDesc_|Test_->type_castctxi|Struct_|StructDefault_|StructDesc_|StructDefaultDesc_|Array_|ArrayDefault_|ArrayFixed_|ArraySegment_|String_->let*i',_=check_instructionctx(Cell.makeUnknown)iinreturni'|StructGet_|GetDescriptor_|StructSet_|ArrayGet_|ArraySet_->type_aggregate_accessctxi|BinOp_|UnOp_->type_arithctxi|Let_->type_letctxi|Br_|Br_if_|Br_table_|Br_on_null_|Br_on_non_null_|Br_on_cast_|Br_on_cast_fail_|Br_on_cast_desc_eq_|Br_on_cast_desc_eq_fail_|Hinted_->type_branchctxi|Throw_|ThrowRef_->type_exceptionctxi|ContNew_|ContBind_|Suspend_|Resume_|ResumeThrow_|ResumeThrowRef_|Switch_|On_->type_stack_switchingctxi|NonNulli'->(let*i'=instructionctxi'inmatchCell.get(expression_typectxi')with|Valtype{typ=Ref{nullable=_;typ;_};internal=Ref{nullable=_;typ=ityp;_};anon_comptype;}->return_expressioni(NonNulli')(Cell.make(Valtype{typ=Ref{nullable=false;typ};internal=Ref{nullable=false;typ=ityp};anon_comptype;}))|Unknown|UnknownRef|Null->(* A reference recovered from a polymorphic value — dead/branch code, a
value already known only as a reference, or a bare [null]: the
non-null bottom reference [UnknownRef], a subtype of every reference
type (so it satisfies any consumer). [ref.as_non_null] of a null is
valid Wasm (it just always traps), so a bare [null] is accepted here
too — like [br_on_null] and [ref.is_null] on a bottom reference. *)return_expressioni(NonNulli')(Cell.makeUnknownRef)|Error->return_expressioni(NonNulli')(expression_typectxi')|_->Error.expected_refctx.diagnostics~location:(sndi'.info);return_expressioni(NonNulli')(Cell.makeError))|Returni'->let*i'=matchi'with|Somei'->let*i'=check_againstctxctx.return_typesi'inreturn(Somei')|None->ifctx.return_types<>[||]thenError.value_count_mismatchctx.diagnostics~location:i.info~expected:(Array.lengthctx.return_types)~provided:0;returnNoneinreturn_statementi(Returni')[||]|Sequencel->let*l'=instructionsctxlinreturn_statementi(Sequencel')(Array.map(expression_typectx)(Array.of_listl'))|Select(i1,i2,i3)->let*i2'=instructionctxi2inlet*i3'=instructionctxi3inlet*i1'=instructionctxi1incheck_typectxi1'i32_cell;let*!ty=letty1=expression_typectxi2'inletty2=expression_typectxi3'in(* A select's two branch values join exactly as the values reaching a
block's exit do; reuse [join_value_types] and, on no common type,
report it against the select. *)matchjoin_value_typesctxty1ty2with|Some_asr->r|None->Error.select_type_mismatchctx.diagnostics~location:i.info~loc1:i2.info~loc2:i3.infoty1ty2;Noneinreturn_expressioni(Select(i1',i2',i3'))tyanddescriptor_targetctx~location~nullabled=(* The custom-descriptors casts/branches write only the descriptor operand [d];
the target reference type is recovered from it. [d : (ref null? (exact_1 Y))]
with [Y describes X], so the target is [(ref nullable (exact_1 X))]: the
described type [X] and the exactness [exact_1] both come from [d], and only
the result nullability is written (the leading [?]). Returns the typed
operand and the recovered target reftype ([None] once an error is reported —
[d] is not a reference to a descriptor type). *)let*d'=instructionctxdinlettarget=matchCell.get(expression_typectxd')with|Valtype{typ=Ref{typ=(Typey|Exacty)asyt;_};_}->(matchTbl.find_optctx.type_context.typesywith|Some(_,{describes=Somex;_})->letexact=matchytwithExact_->true|_->falseinSome{nullable;typ=(ifexactthenExactxelseTypex)}|_->None)|_->Nonein(matchtargetwith|Some_->()|None->Error.type_without_descriptorctx.diagnostics~location);return(d',target)andtype_branchctxi=(* The branch instructions: [br], [br_if], [br_table] and the [br_on_*]
family, each checking its operand(s) against the target label's
parameter types. *)matchi.descwith|Br(label,i')->(* Sequence of instructions *)letparams=branch_targetctxlabelinlet*i'=matchi'with|Somei'->let*i'=check_againstctxparamsi'inreturn(Somei')|None->ifparams<>[||]thenError.value_count_mismatchctx.diagnostics~location:i.info~expected:(Array.lengthparams)~provided:0;returnNoneinreturn_statementi(Br(label,i'))[||]|Br_if(label,i')->let*i'=instructionctxi'inletloc=sndi'.infoinletty,types=split_on_last_typectx~location:loci'incheck_subtypectx~location:loctyi32_cell;letparams=branch_targetctxlabelinletresult=deliver_to_branch_targetctx~loc~types~paramsinreturn_statementi(Br_if(label,i'))result(* Branch-hinting proposal: the hint is advisory; type the wrapped branch and
carry its result through unchanged. *)|Hinted(h,inner)->let*inner=instructionctxinnerinreturn_statementi(Hinted(h,inner))(fstinner.info)|Br_table(labels,i')->let*i'=instructionctxi'inletloc=sndi'.infoinletty,types=split_on_last_typectx~location:loci'incheck_subtypectx~location:loctyi32_cell;letlen=Array.length(branch_targetctx(List.hdlabels))inList.iter(funlabel->letparams=branch_targetctxlabelinifArray.lengthparams<>lenthenError.value_count_mismatchctx.diagnostics~location:i.info~expected:len~provided:(Array.lengthparams);check_subtypesctx~location:loctypesparams)labels;return_statementi(Br_table(labels,i'))[||]|Br_on_null(idx,i')->let*i'=instructionctxi'inlettyp,types=split_on_last_typectx~location:(sndi'.info)i'inlettyp=Cell.gettypinlettyp'=matchtypwith|Valtype{typ=Ref{nullable=_;typ;_};internal=Ref{nullable=_;typ=ityp;_};anon_comptype;}->Cell.make(Valtype{typ=Ref{nullable=false;typ};internal=Ref{nullable=false;typ=ityp};anon_comptype;})|Unknown|UnknownRef|Null->(* A reference recovered from a polymorphic value, or a bare [null]
(always null, so the branch is always taken): the non-null
fall-through value is the bottom reference type [UnknownRef].
Unlike [null!], this is a well-defined branch, not a contradiction
— so a bare null is accepted here. *)Cell.makeUnknownRef|Error->Cell.makeError|_->Error.expected_refctx.diagnostics~location:(sndi'.info);Cell.makeErrorinletloc=sndi'.infoinletparams=branch_targetctxidxin(* Like [br_if]: the values below the reference are delivered to the target
and continue on the non-null fall-through, and the recovered non-null
reference is appended. *)letresult=deliver_to_branch_targetctx~loc~types~paramsinreturn_statementi(Br_on_null(idx,i'))(Array.appendresult[|typ'|])|Br_on_non_null(idx,i')->let*i'=instructionctxi'inletparams=branch_targetctxidxinlettyp,types=split_on_last_typectx~location:(sndi'.info)i'inlettyp=Cell.gettypin(matchtypwith|Unknown|Error|UnknownRef->()|Valtype{typ=Ref{nullable=_;typ;_};internal=Ref{nullable=_;typ=ityp;_};anon_comptype;}->check_subtypesctx~location:(sndi'.info)(Array.appendtypes[|Cell.make(Valtype{typ=Ref{nullable=false;typ};internal=Ref{nullable=false;typ=ityp};anon_comptype;});|])params|Null->(* A bare [null] is always null, so the branch is never taken; the
popped value's non-null form is the [any]-hierarchy bottom [&none].
(A non-[any] null keeps its [as &?H] annotation in [type_cast], so
only [any]-hierarchy bare nulls reach here.) *)check_subtypesctx~location:(sndi'.info)(Array.appendtypes[|Cell.make(Valtype{typ=Ref{nullable=false;typ=None_};internal=Ref{nullable=false;typ=None_};anon_comptype=None;});|])params|_->Error.expected_refctx.diagnostics~location:(sndi'.info));return_statementi(Br_on_non_null(idx,i'))(* The branch delivers [types ++ [ref]] to the target and the fall-through
keeps all but that trailing ref. A target with no params is malformed
(already reported by [check_subtypes] above); [max 0] avoids
[Array.sub _ 0 (-1)] and leaves an empty fall-through. *)(Array.subparams0(max0(Array.lengthparams-1)))|Br_on_cast(label,ty,i')->let*i'=instructionctxi'inifis_cont_heaptypectxty.typthenError.invalid_cast_typectx.diagnostics~location:i.info;lettyp',types=split_on_last_typectx~location:(sndi'.info)i'inletparams=branch_targetctxlabelin(let>@ityp=reftypectx.diagnosticsctx.type_contexttyinlettyp=Cell.make(Valtype{typ=Refty;internal=Refityp;anon_comptype=None})incheck_subtypesctx~location:(sndi'.info)(Array.appendtypes[|typ|])params);(* On success the branch carries the cast target [ty] (via [params]); the
fall-through keeps the value at its residual type [typ2] ([ty'] minus
[ty]). [typ1] re-types the operand as the lub of its type and [ty]. *)let*!typ1,typ2=matchCell.gettyp'with|Valtype{typ=Refty';_}->(* The fall-through residual must be [diff(source, ty)] for the source
[to_wasm] emits — [lub(ty, operand)] — not the operand's own [ty'];
see the matching note in [Br_on_cast_fail]. A no-op when [ty <: ty']
(a plain cast), where the lub is [ty']. A failed [val_lub] means
[ty] and the operand are in different hierarchies — an invalid
cast; report it and recover with the cast target. *)letty1=matchval_lubctx(Refty)(Refty')with|Somet->t|None->Error.invalid_castctx.diagnostics~location:(sndi'.info)typ';Reftyinlet*@typ1=internalizectxty1inlet+@typ2=internalizectx(matchty1with|Reflub->Ref(diff_ref_typelubty)|_->Ref(diff_ref_typety'ty))in(typ1,typ2)(* A polymorphic operand (unreachable / branch code): [to_wasm] recovers the
source type as the cast target [ty], so the fall-through is [ty \ ty]
(as the [Valtype] case computes with the operand's own type) — a concrete
reference matching the emitted instruction. Not [Unknown]: the residual is
always a reference, and not the bottom [UnknownRef] either, or a chained
[br_on_cast] would recover a source that mismatches this one. *)|Unknown|UnknownRef->let+@typ2=internalizectx(Ref(diff_ref_typetyty))in(typ',typ2)|Error->Some(typ',Cell.makeError)|Null->(* A bare [null] operand carries no type wider than the cast target,
so [to_wasm] reconstructs the source as [ty] made nullable and
emits [br_on_cast (ref null H) ty]; the residual must be
[diff(source, ty)] to match what wasm validation derives from
those immediates. A null always matches a nullable [ty] and falls
through, so the residual is unreachable at runtime, but wasm types
it from the immediates, not the operand's nullness — typing it as
the [(ref none)] bottom instead would accept programs whose
emitted wasm the validator rejects. *)letsource={tywithnullable=true}inlet*@typ1=internalizectx(Refsource)inlet+@typ2=internalizectx(Ref(diff_ref_typesourcety))in(typ1,typ2)|_->Error.expected_refctx.diagnostics~location:(sndi'.info);Noneinreturn_statementi(Br_on_cast(label,ty,{i'withinfo=(Array.appendtypes[|typ1|],sndi'.info)}))(Array.append(Array.subparams0(max0(Array.lengthparams-1)))[|typ2|])|Br_on_cast_fail(label,ty,i')->let*i'=instructionctxi'inifis_cont_heaptypectxty.typthenError.invalid_cast_typectx.diagnostics~location:i.info;lettyp',types=split_on_last_typectx~location:(sndi'.info)i'inlet*!ityp=reftypectx.diagnosticsctx.type_contexttyin(* [br_on_cast_fail] branches when the cast fails, carrying the residual
type [typ2] ([ty'] minus [ty]) to the label; the fall-through (cast
succeeded) carries the cast target [ty]. [typ1] re-types the operand as
the lub of its type and [ty]. *)let*!typ1,typ2=matchCell.gettyp'with|Valtype{typ=Refty';_}->(* [to_wasm] emits the source as [lub(ty, operand)] — widened so the
target [ty] is a subtype of it — and wasm then derives the branch
residual as [diff(source, ty)]. Type the residual from that same
[lub] source, not the operand's own [ty']: otherwise, when the
operand and target are unrelated (a chained cast whose source
widens to their common supertype), the residual the typer feeds
the label's join is narrower than the one the emitted instruction
delivers, and the block infers too narrow to accept it. When
[ty <: ty'] (a plain cast) the lub is [ty'] and this is unchanged.
A failed [val_lub] means different hierarchies — an invalid cast;
report it and recover with the cast target. *)letty1=matchval_lubctx(Refty)(Refty')with|Somet->t|None->Error.invalid_castctx.diagnostics~location:(sndi'.info)typ';Reftyinlet*@typ1=internalizectxty1inlet+@typ2=internalizectx(matchty1with|Reflub->Ref(diff_ref_typelubty)|_->Ref(diff_ref_typety'ty))in(typ1,typ2)(* A polymorphic operand: as for [br_on_cast] above, [to_wasm] recovers the
source as the cast target [ty], so the residual sent to the branch is
[ty \ ty] — a concrete reference matching the emitted instruction, not
[Unknown] (the residual is always a reference) nor the bottom [UnknownRef]
(a chained cast would then recover a mismatching source). *)|Unknown|UnknownRef->let+@typ2=internalizectx(Ref(diff_ref_typetyty))in(typ',typ2)|Error->Some(typ',Cell.makeError)|Null->(* A bare [null] operand, as in [br_on_cast] above: [to_wasm] emits
the source as [ty] made nullable, so the residual sent to the
branch is [diff(source, ty)] — mirroring wasm validation rather
than the narrower [(ref none)] bottom, which would let programs
through whose emitted wasm the validator rejects. *)letsource={tywithnullable=true}inlet*@typ1=internalizectx(Refsource)inlet+@typ2=internalizectx(Ref(diff_ref_typesourcety))in(typ1,typ2)|_->Error.expected_refctx.diagnostics~location:(sndi'.info);Noneinletparams=branch_targetctxlabelincheck_subtypesctx~location:(sndi'.info)(Array.appendtypes[|typ2|])params;lettyp=Cell.make(Valtype{typ=Refty;internal=Refityp;anon_comptype=None})inreturn_statementi(Br_on_cast_fail(label,ty,{i'withinfo=(Array.appendtypes[|typ1|],sndi'.info)}))(Array.append(Array.subparams0(max0(Array.lengthparams-1)))[|typ|])|Br_on_cast_desc_eq(label,nullable,i',d)->(* As [br_on_cast]; the target [ty] is recovered from the descriptor
operand [d] ([d : (ref null? (exact_1 Y))], [Y describes X] ⇒ target
[(ref nullable (exact_1 X))]). *)let*d,target=descriptor_targetctx~location:i.info~nullabledinlet*i'=instructionctxi'inlet*!ty=targetinifis_cont_heaptypectxty.typthenError.invalid_cast_typectx.diagnostics~location:i.info;lettyp',types=split_on_last_typectx~location:(sndi'.info)i'inletparams=branch_targetctxlabelin(let>@ityp=reftypectx.diagnosticsctx.type_contexttyinlettyp=Cell.make(Valtype{typ=Refty;internal=Refityp;anon_comptype=None})incheck_subtypesctx~location:(sndi'.info)(Array.appendtypes[|typ|])params);let*!typ1,typ2=matchCell.gettyp'with|Valtype{typ=Refty';_}->(* The operand keeps its own type [typ'] (the descriptor already fixes
the target's exactness); [ty] and the operand must share a
supertype — a failed [val_lub] means different hierarchies. *)ifOption.is_none(val_lubctx(Refty)(Refty'))thenError.invalid_castctx.diagnostics~location:(sndi'.info)typ';let+@typ2=internalizectx(Ref(diff_ref_typety'ty))in(typ',typ2)|Unknown|UnknownRef->let+@typ2=internalizectx(Ref(diff_ref_typetyty))in(typ',typ2)|Error->Some(typ',Cell.makeError)|_->Error.expected_refctx.diagnostics~location:(sndi'.info);Noneinreturn_statementi(Br_on_cast_desc_eq(label,nullable,{i'withinfo=(Array.appendtypes[|typ1|],sndi'.info)},d))(Array.append(Array.subparams0(max0(Array.lengthparams-1)))[|typ2|])|Br_on_cast_desc_eq_fail(label,nullable,i',d)->let*d,target=descriptor_targetctx~location:i.info~nullabledinlet*i'=instructionctxi'inlet*!ty=targetinifis_cont_heaptypectxty.typthenError.invalid_cast_typectx.diagnostics~location:i.info;lettyp',types=split_on_last_typectx~location:(sndi'.info)i'inlet*!ityp=reftypectx.diagnosticsctx.type_contexttyinlet*!typ1,typ2=matchCell.gettyp'with|Valtype{typ=Refty';_}->(* See [Br_on_cast_desc_eq]. *)ifOption.is_none(val_lubctx(Refty)(Refty'))thenError.invalid_castctx.diagnostics~location:(sndi'.info)typ';let+@typ2=internalizectx(Ref(diff_ref_typety'ty))in(typ',typ2)|Unknown|UnknownRef->let+@typ2=internalizectx(Ref(diff_ref_typetyty))in(typ',typ2)|Error->Some(typ',Cell.makeError)|_->Error.expected_refctx.diagnostics~location:(sndi'.info);Noneinletparams=branch_targetctxlabelincheck_subtypesctx~location:(sndi'.info)(Array.appendtypes[|typ2|])params;lettyp=Cell.make(Valtype{typ=Refty;internal=Refityp;anon_comptype=None})inreturn_statementi(Br_on_cast_desc_eq_fail(label,nullable,{i'withinfo=(Array.appendtypes[|typ1|],sndi'.info)},d))(Array.append(Array.subparams0(max0(Array.lengthparams-1)))[|typ|])|_->assertfalse(* only invoked on a branch instruction *)andtype_stack_switchingctxi=(* The typed-continuation / stack-switching instructions: cont.new, cont.bind,
suspend, resume(.throw), and switch. Two surfaces reach here: the parsed
method / constructor forms ([c.resume(x) on […]], [k::new(f)]) arrive as
[Call]/[On] nodes routed from [call_instruction] / the dispatch and are
resolved into the dedicated nodes below (their type immediates inferred
from the receiver, on the call_ref model); the dedicated nodes themselves
arrive when re-typing a decompiled module. The [finish_*] helpers hold the
shared checks. *)matchi.descwith|ContNew(ct,f)->let*f'=instructionctxfinfinish_cont_newctxictf'|ContBind(src,dst,l)->let*l'=instructionsctxlinfinish_cont_bindctxisrcdstl'|On(inner,handlers)->type_on_clausectxiinnerhandlers|_->type_stack_switching_opsctxiandfinish_cont_newctxictf'=(let>@ft=lookup_cont_innerctxctinlet>@fref=internalizectx(Ref{nullable=true;typ=Typeft})incheck_typectxf'fref);(* [cont.new] allocates a fresh continuation of exactly [ct], so its result
is an exact reference. As for [struct.new]/[array.new], we type it exact
only under custom-descriptors (exact reference types are part of that
proposal); the Wasm validator always tracks it exact internally. *)letwant_exact=Wax_utils.Feature.is_enabledctx.type_context.featuresWax_utils.Feature.Custom_descriptorsinlet*!cref=internalizectx(Ref{nullable=false;typ=(ifwant_exactthenExactctelseTypect)})inreturn_expressioni(ContNew(ct,f'))crefandfinish_cont_bindctxisrcdstl'=let*!src_inner=lookup_cont_innerctxsrcinlet*!src_sig=lookup_func_typectxsrc_innerinlet*!dst_inner=lookup_cont_innerctxdstinlet*!dst_sig=lookup_func_typectxdst_innerinletnp=Array.lengthsrc_sig.params-Array.lengthdst_sig.paramsin(* The destination continuation must be [src] with its leading [np]
parameters bound away: the unbound tail and the results must match.
Mirrors [Validation]'s [ContBind] check. *)(ifnp<0thenError.stack_switching_type_mismatchctx.diagnostics~location:i.info~descr:"the resulting continuation takes more parameters than the original \
one"elselet>@src_ft=internal_functypectxsrc_siginlet>@dst_ft=internal_functypectxdst_siginletts12=Array.subsrc_ft.paramsnp(Array.lengthdst_ft.params)inifnot(functype_matches(subtyping_infoctx){params=ts12;results=src_ft.results}dst_ft)thenError.stack_switching_type_mismatchctx.diagnostics~location:i.info~descr:"the bound parameters and results do not match between the two \
continuation types");(letn=max0npinlet>@bound=array_map_opt(funp->internalizectx(sndp.desc))(Array.subsrc_sig.params0n)inlet>@srcref=internalizectx(Ref{nullable=true;typ=Typesrc})incheck_operandsctx~location:i.infol'(Array.appendbound[|srcref|]));(* Like [cont.new], [cont.bind] yields a fresh continuation of exactly
[dst], so an exact reference (gated on custom-descriptors as above). *)letwant_exact=Wax_utils.Feature.is_enabledctx.type_context.featuresWax_utils.Feature.Custom_descriptorsinlet*!dstref=internalizectx(Ref{nullable=false;typ=(ifwant_exactthenExactdstelseTypedst);})inreturn_expressioni(ContBind(src,dst,l'))dstrefandtype_stack_switching_opsctxi=matchi.descwith|Suspend(tag,l)->let*l'=instructionsctxlinlet*!{params;results}=Tbl.findctx.diagnosticsctx.tagstagin(let>@ptypes=array_map_opt(funp->internalizectx(sndp.desc))paramsincheck_operandsctx~location:i.infol'ptypes);let*!rtypes=array_map_opt(internalizectx)resultsinreturn_statementi(Suspend(tag,l'))rtypes|Resume(ct,handlers,l)->let*l'=instructionsctxlinfinish_resumectxicthandlersl'|ResumeThrow(ct,tag,handlers,l)->let*l'=instructionsctxlinfinish_resume_throwctxicttaghandlersl'|ResumeThrowRef(ct,handlers,l)->let*l'=instructionsctxlinfinish_resume_throw_refctxicthandlersl'|Switch(ct,tag,l)->let*l'=instructionsctxlinfinish_switchctxicttagl'|_->assertfalse(* only invoked on a stack-switching instruction *)andfinish_resumectxicthandlersl'=let*!inner=lookup_cont_innerctxctinlet*!sg=lookup_func_typectxinnerin(let>@ptypes=array_map_opt(funp->internalizectx(sndp.desc))sg.paramsinlet>@cref=internalizectx(Ref{nullable=true;typ=Typect})incheck_operandsctx~location:i.infol'(Array.appendptypes[|cref|]));check_resume_handlersctx~result_types:sg.resultshandlers;let*!rtypes=array_map_opt(internalizectx)sg.resultsinreturn_statementi(Resume(ct,handlers,l'))rtypesandfinish_resume_throwctxicttaghandlersl'=let*!inner=lookup_cont_innerctxctinlet*!sg=lookup_func_typectxinnerinlet*!{params=tparams;_}=Tbl.findctx.diagnosticsctx.tagstagin(let>@ptypes=array_map_opt(funp->internalizectx(sndp.desc))tparamsinlet>@cref=internalizectx(Ref{nullable=true;typ=Typect})incheck_operandsctx~location:i.infol'(Array.appendptypes[|cref|]));check_resume_handlersctx~result_types:sg.resultshandlers;let*!rtypes=array_map_opt(internalizectx)sg.resultsinreturn_statementi(ResumeThrow(ct,tag,handlers,l'))rtypesandfinish_resume_throw_refctxicthandlersl'=let*!inner=lookup_cont_innerctxctinlet*!sg=lookup_func_typectxinnerin(let>@exnref=internalizectx(Ref{nullable=true;typ=Exn})inlet>@cref=internalizectx(Ref{nullable=true;typ=Typect})incheck_operandsctx~location:i.infol'[|exnref;cref|]);check_resume_handlersctx~result_types:sg.resultshandlers;let*!rtypes=array_map_opt(internalizectx)sg.resultsinreturn_statementi(ResumeThrowRef(ct,handlers,l'))rtypesandfinish_switchctxicttagl'=let*!inner=lookup_cont_innerctxctinlet*!sg=lookup_func_typectxinnerinlettag_sig=Tbl.findctx.diagnosticsctx.tagstaginletnp=Array.lengthsg.paramsin(ifnp>=1thenlet>@lead=array_map_opt(funp->internalizectx(sndp.desc))(Array.subsg.params0(np-1))inlet>@cref=internalizectx(Ref{nullable=true;typ=Typect})incheck_operandsctx~location:i.infol'(Array.appendlead[|cref|]));(* The last parameter of [ct]'s function type must itself be a
continuation type; the result is that inner continuation's parameter
types. *)letinner_sg=matchifnp=0thenNoneelseSome(sndsg.params.(np-1).desc)with|Some(Ref{typ=Typect2|Exactct2;_})->let*@inner2=lookup_cont_innerctxct2inlookup_func_typectxinner2|_->Nonein(* The 'switch' tag must take no parameters and its results must match
both continuation types. Mirrors [Validation]'s [Switch] check. *)letto_internalarr=array_map_opt(funtyp->let+@iv=internalize_valtypectxtypiniv.internal)arrinletresult_subtypeab=match(to_internala,to_internalb)with|Somea,Someb->Array.lengtha=Array.lengthb&&Array.for_allFun.id(Array.mapi(funit->Wax_wasm.Types.val_subtype(subtyping_infoctx)tb.(i))a)|_->truein(matchinner_sgwith|None->Error.stack_switching_type_mismatchctx.diagnostics~location:i.info~descr:"the continuation's last parameter must itself be a continuation type"|Someinner_sg->(matchtag_sigwith|None->()|Some{params=tparams;results=tresults}->ifArray.lengthtparams<>0||(not(result_subtypesg.resultstresults))||not(result_subtypetresultsinner_sg.results)thenError.stack_switching_type_mismatchctx.diagnostics~location:i.info~descr:"the 'switch' tag must take no parameters and its results must \
match the two continuation types"));letresult_params=matchinner_sgwithSomes2->s2.params|None->[||]inlet*!rtypes=array_map_opt(funp->internalizectx(sndp.desc))result_paramsinreturn_statementi(Switch(ct,tag,l'))rtypes(* The declared continuation type of a stack-switching receiver (or of
[bind]'s continuation operand): the type immediate, inferred from the
operand's static type on the call_ref model. An abstract [&cont] cannot
supply it and must be cast to a declared type first, as an abstract
function reference must be at a call. [None] after reporting. *)andcont_operand_typectxe'=matchCell.get(expression_typectxe')with|Valtype{typ=Ref{typ=Typect|Exactct;_};_}->(matchTbl.find_optctx.type_context.typesctwith|Some(_,{typ=Cont_;_})->Somect|_->Error.expected_cont_typectx.diagnostics~location:(snde'.info);None)|Valtype{typ=Ref{typ=Cont|NoCont;_};_}->Error.abstract_cont_receiverctx.diagnostics~location:(snde'.info);None|Error->None(* the operand already failed to type; recover silently *)|Unknown|UnknownRef->Error.unknown_operand_typectx.diagnostics~location:(snde'.info);None|_->Error.expected_cont_typectx.diagnostics~location:(snde'.info);None(* A stack-switching method call [c.resume(x)], [c.resume_throw(exc(p))],
[c.resume_throw_ref(e)], [c.switch(x, tag: t)], with [handlers] from a
wrapping [on] clause. The receiver compiles last (Wasm stack order, as for
call_ref), so the arguments are typed first and the receiver appended. *)andtype_cont_method_callctxi~handlersrecvmethargs=(* [switch]'s enabling tag is a required labelled immediate, extracted before
the arguments are typed (it names a tag, not a value); [resume_throw]'s
tag is invoked with its payload, as in [throw exc(p)] — the callee is
resolved in the tag namespace, so a function of the same name does not
conflict. *)lettag,args=matchmeth.descwith|"switch"->(lettags,rest=List.partition_map(funa->matcha.Ast.descwith|Ast.Labelled({desc="tag";_},{desc=Gett;_})->Either.Leftt|_->Either.Righta)argsinmatchtagswith|[t]->(Somet,rest)|t::dup::_->Error.duplicate_argument_labelctx.diagnostics~location:dup.info{dupwithdesc="tag"};(Somet,rest)|[]->Error.switch_needs_tagctx.diagnostics~location:i.info;(None,rest))|"resume_throw"->(matchargswith|[{desc=Call({desc=Gett;_},payload);_}]->(Somet,payload)|_->Error.resume_throw_needs_tagctx.diagnostics~location:i.info;(None,args))|_->(None,args)inlet*args'=instructionsctxargsinlet*recv'=instructionctxrecvinletl'=args'@[recv']inlet*!ct=cont_operand_typectxrecv'inmatchmeth.descwith|"resume"->finish_resumectxicthandlersl'|"resume_throw"->let*!tag=taginfinish_resume_throwctxicttaghandlersl'|"resume_throw_ref"->finish_resume_throw_refctxicthandlersl'|_->let*!tag=taginfinish_switchctxicttagl'(* The postfix handler clause [e on [t -> 'l, …]]: fold the handlers into the
resume-family call it wraps; any other wrapped expression is an error (the
grammar attaches the clause to any expression). *)andtype_on_clausectxiinnerhandlers=matchinner.descwith|Call({desc=StructGet(recv,({desc="resume"|"resume_throw"|"resume_throw_ref";_}asmeth));_;},args)->type_cont_method_callctxi~handlersrecvmethargs|_->Error.on_clause_contextctx.diagnostics~location:i.info;(* Recover by typing the wrapped expression and carrying its result. *)let*inner'=instructionctxinnerinreturn_statementi(On(inner',handlers))(fstinner'.info)(* The [T::new] / [T::bind] constructors of a declared continuation type: the
[T::] namespace constructs a [&T]. [bind]'s source type — the type
immediate — is inferred from its continuation operand (the last argument),
as the method receivers' types are. *)andtype_cont_construct_callctxifuncnsnameargs=let*args'=instructionsctxargsinletrecover()=return_statementi(Call({desc=Path(ns,name);info=([||],func.info)},args'))[|Cell.makeError|]inmatchname.descwith|"new"->(matchargs'with|[f']->finish_cont_newctxinsf'|_->Error.value_count_mismatchctx.diagnostics~location:i.info~expected:1~provided:(List.lengthargs');recover())|"bind"->(matchList.revargs'with|c'::_->let*!src=cont_operand_typectxc'infinish_cont_bindctxisrcnsargs'|[]->Error.value_count_mismatchctx.diagnostics~location:i.info~expected:1~provided:0;recover())|_->Error.unknown_intrinsicctx.diagnostics~location:i.infons.descname.desc;recover()andtype_arithctxi=(* Arithmetic, comparison and conversion operators in binary ([a + b]) and
unary ([-a], [a as i64]) form. *)matchi.descwith|BinOp(op,i1,i2)->let*i1'=instructionctxi1inlet*i2'=instructionctxi2inletty=letty1=expression_typectxi1'inletty2=expression_typectxi2'inletmismatch()=(* Point at the operator itself, not the whole expression. *)Error.binop_type_mismatchctx.diagnostics~location:op.infoty1ty2in(* Split on how many operands are still abstract ([Unknown]/[Error]).
Both abstract: unify the two cells to the operator's default type so a
result is still produced. One abstract: unify it onto the known
operand's type and validate that. Both concrete (the arms below):
just validate. The abstract arms unify operand cells in place. *)match(Cell.getty1,Cell.getty2)with|(Unknown|Error),(Unknown|Error)->(matchop.descwith|Add|Sub|Mul->Cell.mergety1ty2Number;ty1|Div(Some_)|Rem_|And|Or|Xor|Shl|Shr_->Cell.mergety1ty2Int;ty1|Lt(Some_)|Gt(Some_)|Le(Some_)|Ge(Some_)|Eq|Ne->Cell.mergety1ty2(Valtypei32_valtype);i32_cell|DivNone->Cell.mergety1ty2Float;ty1|LtNone|GtNone|LeNone|GeNone->Cell.mergety1ty2(Valtypef32_valtype);i32_cell)|typ,(Unknown|Error)|(Unknown|Error),typ->(Cell.mergety1ty2typ;matchop.descwith|Eq|Ne->(* [==]/[!=] on references are both [ref.eq] (the latter negated
in lowering), so they take the same operands: [eqref] or a
number. *)(matchtypwith|Valtype{internal=Ref_asty;_}->ifnot(Wax_wasm.Types.val_subtype(subtyping_infoctx)ty(Ref{nullable=true;typ=Eq}))thenmismatch()|Null->Cell.setty1(Valtype{typ=Ref{nullable=true;typ=Eq};internal=Ref{nullable=true;typ=Eq};anon_comptype=None;})|Valtype{internal=I32;_}|Valtype{internal=I64;_}|Valtype{internal=F32;_}|Valtype{internal=F64;_}|Number|Int|LargeInt|Float->()(* The bottom reference is [eqref], so [ref.eq] accepts it. *)|UnknownRef->()|_->mismatch());i32_cell|Add|Sub|Mul->(matchtypwith|Valtype{internal=I32;_}|Valtype{internal=I64;_}|Valtype{internal=F32;_}|Valtype{internal=F64;_}|Number|Int|LargeInt|Float->()|_->mismatch());ty1|Div(Some_)|Rem_|And|Or|Xor|Shl|Shr_->check_int_bin_opctx~location:op.infoty1ty2|DivNone->check_float_bin_opctx~location:op.infoty1ty2|Lt(Some_)|Gt(Some_)|Le(Some_)|Ge(Some_)->(matchtypwith|Valtype{internal=I32;_}|Valtype{internal=I64;_}|Int->()|Number->Cell.setty1Int(* A signed integer comparison forces a [LargeInt] operand to i64
(it cannot be i32, and this is an integer op), pinning it rather
than leaving it float-capable. *)|LargeInt->Cell.setty1(Valtypei64_valtype)|_->mismatch());i32_cell|LtNone|GtNone|LeNone|GeNone->(matchtypwith|Valtype{internal=F32;_}|Valtype{internal=F64;_}|Float->()(* A float comparison takes a [LargeInt] operand as a float (it is
a numeric literal, so float-capable), like a [Number]. *)|Number|LargeInt->Cell.setty1Float|_->mismatch());i32_cell)|_->(matchop.descwith|Eq|Ne->(match(Cell.getty1,Cell.getty2)with|(Valtype{internal=Ref_asty1;_},Valtype{internal=Ref_asty2;_})->ifnot(Wax_wasm.Types.val_subtype(subtyping_infoctx)ty1(Ref{nullable=true;typ=Eq})&&Wax_wasm.Types.val_subtype(subtyping_infoctx)ty2(Ref{nullable=true;typ=Eq}))thenmismatch()|Valtype{internal=Ref_astyp1;_},Null->ifnot(Wax_wasm.Types.val_subtype(subtyping_infoctx)typ1(Ref{nullable=true;typ=Eq}))thenmismatch();Cell.mergety1ty2(Cell.getty2)|Null,Valtype{internal=Ref_astyp2;_}->ifnot(Wax_wasm.Types.val_subtype(subtyping_infoctx)typ2(Ref{nullable=true;typ=Eq}))thenmismatch();Cell.mergety1ty2(Cell.getty2)(* [ref.eq] needs both operands [eqref]; the bottom reference
[UnknownRef] always is, so only a concrete side is checked. *)|Valtype{internal=Ref_asty;_},UnknownRef|UnknownRef,Valtype{internal=Ref_asty;_}->ifnot(Wax_wasm.Types.val_subtype(subtyping_infoctx)ty(Ref{nullable=true;typ=Eq}))thenmismatch()|UnknownRef,(UnknownRef|Null)|Null,UnknownRef->()(* Any non-reference operands are the ordinary numeric comparison.
[check_num_concrete] reports the same mismatch otherwise. *)|_->check_num_concretectx~location:op.infoty1ty2);i32_cell|Add|Sub|Mul->check_num_concretectx~location:op.infoty1ty2;ty1|Div(Some_)|Rem_|And|Or|Xor|Shl|Shr_->check_int_bin_opctx~location:op.infoty1ty2|DivNone->check_float_bin_opctx~location:op.infoty1ty2|Lt(Some_)|Gt(Some_)|Le(Some_)|Ge(Some_)->ignore(check_int_bin_opctx~location:op.infoty1ty2);i32_cell|LtNone|GtNone|LeNone|GeNone->ignore(check_float_bin_opctx~location:op.infoty1ty2);i32_cell)inifctx.warn_unusedthenbeginlint_shiftctxoptyi2';lint_divisionctxopi2';lint_comparisonctxopi1'i2';lint_redundantctxopi1'i2'end;return_expressioni(BinOp(op,i1',i2'))ty|UnOp(op,i')->let*i'=instructionctxi'inlettyp=expression_typectxi'inletty=matchCell.gettypwith|Unknown|Error->(matchop.descwithNot->i32_cell|Neg|Pos->Cell.makeNumber)|_->(matchop.descwith|Not->(matchCell.gettypwith(* [!] is [i32.eqz] on an integer and [ref.is_null] on a
reference; [UnknownRef] is a (bottom) reference, so it takes
the [ref.is_null] reading like any other ref. *)|Valtype{internal=I32|I64|Ref_;_}|Null|Int|UnknownRef->()|Number->Cell.settypInt(* [!] on a [LargeInt] is [i64.eqz]; pin it to i64 so it cannot be
left float-capable (there is no float [eqz]). *)|LargeInt->Cell.settyp(Valtypei64_valtype)|_->Error.instruction_type_mismatchctx.diagnostics~location:op.infotyp(Cell.makeInt));i32_cell|Neg|Pos->(matchCell.gettypwith|Valtype{internal=I32|I64|F32|F64;_}|Int|LargeInt|Float|Number->()|_->Error.instruction_type_mismatchctx.diagnostics~location:op.infotyp(Cell.makeNumber));typ)inreturn_expressioni(UnOp(op,i'))ty|_->assertfalse(* only invoked on BinOp/UnOp *)andtype_castctxi=(* Type casts ([e as t]) and type tests ([e is t]). *)matchi.descwith|Cast(i',typ)->let*i'=instructionctxi'inifctx.warn_unusedthenlint_conversionctx~location:i.infotypi';(* When converting from Wasm, fuse two casts whose inserted intermediate
type is superfluous (only when [ctx.simplify]); [to_wasm] re-expands
each single cast to the same instructions:
- [(e as i32_X) as i64_X] -> [e as i64_X]: a narrow [i32]-producing
read widened to [i64]. [e] is a packed [Int8]/[Int16] read (the
[i32] is [i64.extend_i32_X]) or a reference (the [i32] is [i31.get],
the [i64] [i64.extend_i32_X]).
- [(e as &i31) as i32_X] -> [e as i32_X]: a [ref.cast] feeding
[i31.get]. A reference already typed [&i31]/[&?i31] never reaches
here (its [&i31] cast is dropped as redundant first); an [i31] built
from an [i32] ([ref.i31]) is excluded by the [is_*_ref] guard.
- [(e as i32) as &i31] -> [e as &i31]: an [i64] wrapped to [i32]
before [ref.i31] (which takes an [i32]).
- [(e as &any) as &T] -> [e as &T]: an [extern] converted to the
[any] hierarchy ([any.convert_extern]) before a [ref.cast] to a
concrete [any]-hierarchy type [T].
- [(e as &i31) as &extern] -> [e as &extern]: an [i32] boxed as an
[i31] ([ref.i31]) before [extern.convert_any]. *)letis_packed_reade=matchCell.get(expression_typectxe)with|Int8|Int16->true|_->falseinletis_refe=matchCell.get(expression_typectxe)with|Valtype{internal=Ref_;_}->true|_->falsein(* A non-[i31] reference in the [any] hierarchy — the operand of a plain
[ref.cast] to [&i31]. [extern]/[noextern] are excluded: [e as &i31] for an
[extern] is not a [ref.cast] but a cross-hierarchy convert then cast
([any.convert_extern]; [ref.cast]), so fusing it with a trailing [i31.get]
into [e as i32] would leave an untranslatable [&extern as i32]. *)letis_non_i31_refe=matchCell.get(expression_typectxe)with|Valtype{internal=Ref{typ=I31|Extern|NoExtern;_};_}->false|Valtype{internal=Ref_;_}->true|_->falseinletis_i64e=matchCell.get(expression_typectxe)with|Valtype{internal=I64;_}->true|_->falseinletis_i32e=matchCell.get(expression_typectxe)with|Valtype{internal=I32;_}->true|_->falseinletis_externe=matchCell.get(expression_typectxe)with|Valtype{internal=Ref{typ=Extern|NoExtern;_};_}->true|_->falseinleti',typ=match(typ,i'.desc)with|(Signedtype{typ=`I64;signage=s2;strict=false},Cast(e,Signedtype{typ=`I32;signage=s1;strict=false}))whenctx.simplify&&s1=s2&&(is_packed_reade||is_refe)->(e,typ)|(Signedtype{typ=`I32;_},Cast(e,Valtype(Ref{typ=I31;nullable=false})))whenctx.simplify&&is_non_i31_refe->(e,typ)|Valtype(Ref{typ=I31;nullable=false}),Cast(e,ValtypeI32)whenctx.simplify&&is_i64e->(e,typ)|(Valtype(Ref({typ=Extern;_}asr)),Cast(e,Valtype(Ref{typ=I31;nullable=false})))whenctx.simplify&&is_i32e->(* [ref.i31] is non-null and [extern.convert_any] preserves that,
so the fused [i32 as &extern] is non-null. *)(e,Valtype(Ref{rwithnullable=false}))|(Valtype(Ref{typ=Any|Eq|I31|Struct|Array|None_|Type_;_}),Cast(e,Valtype(Ref{typ=Any;_})))whenctx.simplify&&is_externe->(e,typ)|_->(i',typ)inletty'=expression_typectxi'in(* Snapshot the inner type *before* [cast]/[signed_cast] below concretize it
to the cast target: this is the type the inner expression would settle on
if the cast were removed (see [load_bearing_literal]). *)letty'_natural=Cell.getty'in(* [extern.convert_any]/[any.convert_extern] preserve non-nullness, so a
cast to [&?extern]/[&?any] of a non-nullable argument actually yields
[&extern]/[&any]; refine the target accordingly. Like the
redundant-cast removal below, this only applies when converting from
Wasm ([ctx.simplify]); otherwise the cast is kept as written. *)letarg_non_nullable=matchCell.getty'with|Valtype{typ=Ref{nullable=false;_};_}->true|_->falseinlettyp=matchtypwith|Valtype(Ref({typ=Extern|Any;nullable=true}asr))whenctx.simplify&&arg_non_nullable->Ast.Valtype(Ref{rwithnullable=false})|_->typin(* The cast target as a valtype, resolving an inline function type
[&fn(..)] to a minted anonymous function type. The AST node keeps the
original [typ] (so an inline function-type cast prints and lowers
faithfully); only [ty]/validation use the resolved type. *)lettarget_valtype=matchtypwith|Valtypet->Somet|Functype{nullable;sign}->Some(Ref{nullable;typ=Type(anon_function_typectxsign)})|Signedtype_->Nonein(* A continuation carries no RTT, so there is no [ref.cast] into a
continuation type: [as &k] with a continuation target is a
compile-time ascription, accepted (below) exactly when it lowers to
no instruction. *)letcont_target=matchtarget_valtypewith|Some(Ref{typ;_})->is_cont_heaptypectxtyp|_->falsein(* An inline function-type cast target [&fn(..)] is lowered through a
synthesized type (see [anon_function_type]); carry its signature so the
result renders as [&fn(..)] rather than that synthetic name. *)letinline:comptypeoption=matchtypwithFunctype{sign;_}->Some(Funcsign)|_->Noneinlet*!ty=internalize?inlinectx(matchtarget_valtypewith|Somet->t|None->(matchtypwith|Signedtype{typ=`I32;_}->I32|Signedtype{typ=`I64;_}->I64|Signedtype{typ=`F32;_}->F32|Signedtype{typ=`F64;_}->F64|Valtype_|Functype_->assertfalse))inlet()=matchtarget_valtypewith|Some_whencont_target->(* Accepted exactly when it is a provable no-op — the cases
[subtype] validates: the operand's static type is already a
subtype of the target (identity or upcast, letting a [resume]
go through a supertype signature), a [null] literal with a
nullable target ([ref.null], no cast), or a stack-polymorphic
operand (dead code, or an unconstrained inference cell the
ascription pins). NOT the general [cast] castability check
below, which admits runtime downcasts. *)ifnot(subtypectxty'ty)thenError.cont_cast_not_ascriptionctx.diagnostics~location:i.info|Somet->ifnot(castctxty't)thenError.invalid_castctx.diagnostics~location:(sndi'.info)ty'|None->(matchtypwith|Signedtype{typ=target;signage;_}->((* An atomic narrow load has no sign-extending form (only the
zero-extending [_u] instructions exist), so reject [as iN_s]
on one outright — with the [_u]-then-extend spelling to use —
rather than quietly compiling a load + sign-extend pair. *)match(signage,target,atomic_narrow_load_widthctxi')with|Signed,((`I32|`I64)ast),Somew->Error.atomic_signed_loadctx.diagnostics~location:i.info~cast:("as "^(matchtwith`I32->"i32"|`I64->"i64")^"_u")~extend:(matchwwith|`W8->".extend8_s()"|`W16->".extend16_s()")|_->ifnot(signed_castctxty'target)thenError.invalid_castctx.diagnostics~location:(sndi'.info)ty')|Valtype_|Functype_->assertfalse)in(* Lint the cast against its operand's natural type (snapshotted before
[cast] above concretised it to the target). Skipped for from-Wasm input
([simplify]), whose casts are compiler-inserted and whose redundant ones
are dropped below — and for a continuation target, whose "redundant"
upcast is the intended use (a compile-time ascription). *)ifctx.warn_unused&&(notctx.simplify)&¬cont_targetthenlint_ref_castctx~location:i.info~is_test:falsety'_natural(Cell.getty);(* A cast is load-bearing when its target differs from the type the inner
expression would settle on if the cast were removed — its natural
default, read from [ty'_natural] (the inner type *before* [cast] above
concretized it to the target). A still-abstract numeric value re-parses
at its default width (int -> i32, an out-of-i32-range int -> i64,
float -> f64), so a cast to any other width must be kept or the value
changes on the round-trip. This keeps e.g. [(nan as f32).to_bits()] /
[(5 as i64).from_bits()] from losing the operand's type. Only an abstract
numeric inner has such a default; a concrete inner (numeric or reference)
is already pinned, so [subtype] below is the right redundancy test. *)letnatural_typ=matchty'_naturalwith|Number|Int|Int8|Int16->SomeI32|LargeInt->SomeI64|Float->SomeF64|Null|UnknownRef|Valtype_|Unknown|Error|Collecting_->Noneinletload_bearing_literal=match(natural_typ,Cell.getty)with|Somed,Valtype{typ;_}->d<>typ|_->falsein(* A cast of a bare [null] to a non-[any]-hierarchy reference is also load
bearing: dropping it leaves a bare [null], whose non-null / branch
consumers ([null!], [br_on_*]) fall back to the [any]-hierarchy bottom
[&none] — not a subtype of a func/extern/exn/cont type — so the
reconstructed module no longer type-checks. (An [any]-hierarchy null is
safe to drop: [&none] satisfies every [any]-hierarchy consumer.) *)letload_bearing_null=match(ty'_natural,Cell.getty)with|Null,Valtype{typ=Ref{typ=ht;_};_}->top_heap_typectxht<>SomeAny|_->falsein(* Likewise a cast of a bottom reference (the residual of a polymorphic
[br_on_cast] in dead code, or [ref.null nofunc]) to a type the bottom
cannot stand in for. The bottom heap type carries no usable type, so
dropping the cast leaves a value that no longer names one: a [(ref
nofunc)] feeding [call_ref] has no function type to resolve (any
non-[any]-hierarchy target), and — even in the [any] hierarchy — a
bottom [&none] feeding a struct/array field access ([s.f], [a[i]])
names no concrete type for the field to resolve against (Wasm's
[struct.get] carries the type index; Wax's [.f] recovers it from the
receiver). An *abstract* [any]-hierarchy target ([any]/[eq]/[struct]/…)
is still safe: [&none] satisfies those consumers. *)letload_bearing_bottom_ref=match(ty'_natural,Cell.getty)with|(Valtype{typ=Ref{typ=bot;_};_},Valtype{typ=Ref{typ=ht;_};_})whenis_bottom_heaptypebot&¬(is_bottom_heaptypeht)->(top_heap_typectxht<>SomeAny||matchhtwithType_->true|_->false)|_->falsein(* A continuation-target ascription is load-bearing unless it names the
operand's own type: [From_wasm] wraps every resume/switch/bind
continuation operand in one to pin the instruction's type immediate,
and dropping a strict upcast would re-infer the operand's own
(narrower) type and change the immediate on the round trip. *)letload_bearing_cont=cont_target&&match(ty'_natural,Cell.getty)with|(Valtype{typ=Ref{typ=Typea|Exacta;_};_},Valtype{typ=Ref{typ=Typeb|Exactb;_};_})->a.desc<>b.desc|_->truein(* Drop a cast the inferred types already make redundant. This is only
desirable when converting from Wasm ([ctx.simplify]): there casts are
inserted to pin types and precise inference makes some unnecessary. For
hand-written Wax (formatting, or compiling to Wasm) we keep casts as
written.
ZZZ Handle select instruction better *)letunnecessary_cast=ctx.simplify&&(notload_bearing_literal)&&(notload_bearing_null)&&(notload_bearing_bottom_ref)&&(notload_bearing_cont)&&(not(is_unknown_or_errorty'))&&subtypectxty'tyinifunnecessary_castthenreturn{i'withinfo=([|ty|],sndi'.info)}elsereturn_expressioni(Cast(i',typ))ty|CastDesc(value,nullable,d)->(* [value as [?]descriptor(d)]: a descriptor-equality cast. The target type
is recovered from [d] ([d : (ref null? (exact_1 Y))], [Y describes X] ⇒
target [(ref nullable (exact_1 X))]). *)let*value'=instructionctxvalueinlet*d',target=descriptor_targetctx~location:i.info~nullabledinlet*!t=targetinlet*!ty=internalizectx(Reft)inletty'=expression_typectxvalue'inifnot(castctxty'(Reft))thenError.invalid_castctx.diagnostics~location:(sndvalue'.info)ty';return_expressioni(CastDesc(value',nullable,d'))ty|Test(i,ty)->let*i'=instructionctxiinifis_cont_heaptypectxty.typthenError.invalid_cast_typectx.diagnostics~location:i.info;(* The operand's natural type, before [check_type] below concretises it. *)letop_natural=Cell.get(expression_typectxi')in(let>@typ=top_heap_typectxty.typinlet>@typ=internalizectx(Ref{nullable=true;typ})incheck_typectxi'typ);(ifctx.warn_unused&¬ctx.simplifythenlet>@target=internalizectx(Refty)inlint_ref_castctx~location:i.info~is_test:trueop_natural(Cell.gettarget));return_expressioni(Test(i',ty))i32_cell(* Construction literals carry an optional type name that can be inferred from
an expected type. Their typing lives in [check_instruction]; in synthesis position
there is no expectation, so [check_instruction] against the [Unknown] sentinel keeps a
present name and reports [cannot_infer_*] when one is omitted. *)|_->assertfalse(* only invoked on Cast/Test *)andtype_aggregate_accessctxi=(* Field and element access: struct field reads/writes ([s.f], [s.f = v]) and
array or table indexing ([a[i]], [a[i] = v]). *)matchi.descwith|StructGet(i',field)->let*i'=instructionctxi'inlet*!ty=letty=expression_typectxi'in(* The receiver this access is on, for member completion: a memory /
table name (that object's methods), else a numeric value (its
methods). A reference receiver's struct fields / array [length] are
recorded in the arms below. Only the receiver kind and type are
recorded; the editor derives the candidate list on demand. *)(ifctx.member_completions<>Nonethenmatchi'.descwith|Getnamewhenmemory_receiverctxname->let_,at=Option.get(Tbl.find_optctx.memoriesname)inrecord_membersctx.member_completionsfield.info(R_memoryat)|Getnamewhentable_receiverctxname->letat,rt=Option.get(Tbl.find_optctx.tablesname)inrecord_membersctx.member_completionsfield.info(R_table(at,rt))|_->(matchnumeric_receiver_kind(Cell.getty)with|Somer->record_membersctx.member_completionsfield.infor|None->()));match(Cell.getty,field.desc)with|Valtype{typ=Ref{typ=Typety|Exactty;_};_},_->(let*@_,def=Tbl.find_optctx.type_context.typestyinmatchdef.typwith|Structfields->(record_membersctx.member_completionsfield.info(R_structfields);matchArray.find_map(funf->letnm,typ=f.descinifnm.desc=field.descthenSometypelseNone)fieldswith|Sometyp->field_read_typectxtyp|None->Error.missing_fieldctx.diagnostics~location:field.infofield;None)|Func_|Array_|Cont_->(matchdef.typwith|Arrayelem->record_membersctx.member_completionsfield.info(R_arrayelem)|Cont_->ifctx.member_completions<>Nonethenrecord_membersctx.member_completionsfield.info(cont_receiverctxty)|_->());ifis_unary_methodfield.descthenError.method_needs_parenthesesctx.diagnostics~location:field.infofield.descelseError.expected_struct_typectx.diagnostics~location:(sndi'.info);None)(* Leave a receiver that already failed to type alone (its error is
reported elsewhere): keep the access with an error result type rather
than giving up, which would drop a hole receiver and desync hole
counting. *)|Error,_->Some(Cell.makeError)(* The receiver's type is unknown (unreachable / branch code) or only a
reference (its struct type cannot be resolved), so the field cannot
be read. *)|(Unknown|UnknownRef),_->Error.unknown_operand_typectx.diagnostics~location:(sndi'.info);Some(Cell.makeError)(* A name that is an instruction method was likely meant as the
parenthesised call [x.sqrt()]; any other field access on a non-struct
type has no fields to find. *)|_whenis_unary_methodfield.desc->Error.method_needs_parenthesesctx.diagnostics~location:field.infofield.desc;None|_->Error.expected_struct_typectx.diagnostics~location:(sndi'.info);Noneinreturn_expressioni(StructGet(i',field))ty|GetDescriptori'->let*i'=instructionctxi'inlet*!ty=matchCell.get(expression_typectxi')with|Valtype{typ=Ref{typ=(Typety|Exactty)asht;_};_}->(letexact=matchhtwithExact_->true|_->falseinlet*@_,def=Tbl.find_optctx.type_context.typestyinmatchdef.descriptorwith|None->Error.type_without_descriptorctx.diagnostics~location:(sndi'.info);None|Somedescname->internalizectx(Ref{nullable=false;typ=(ifexactthenExactdescnameelseTypedescname);}))|Error->Some(Cell.makeError)|Unknown|UnknownRef->Error.unknown_operand_typectx.diagnostics~location:(sndi'.info);Some(Cell.makeError)|_->Error.expected_struct_typectx.diagnostics~location:(sndi'.info);Noneinreturn_expressioni(GetDescriptori')ty|StructSet(i1,field,i2)->let*i1'=instructionctxi1in(* Resolve the field's declared type (pure, reporting any field error)
before typing the value, so the value can be checked against it and a
struct/array literal can drop its name. The value is then typed on
every path, so its holes are always consumed. *)letexpected=matchCell.get(expression_typectxi1')with|Valtype{typ=Ref{typ=Typety|Exactty;_};_}->(matchlookup_struct_typectxtywith|None->None|Somefields->(record_membersctx.member_completionsfield.info(R_structfields);matchArray.find_map(funf->letnm,ftyp=f.descinifnm.desc=field.descthenSomeftypelseNone)fieldswith|None->Error.missing_fieldctx.diagnostics~location:field.infofield;None|Someftyp->ifnotftyp.mutthenError.immutablectx.diagnostics~location:field.info"field";internalizectx(unpack_typeftyp)))|Error->(* Receiver already failed to type; recover without a spurious
"expected struct type". *)None|Unknown|UnknownRef->(* The receiver's type is unknown (unreachable / branch code) or
only a reference (its struct type cannot be resolved), so the
field cannot be written. *)Error.unknown_operand_typectx.diagnostics~location:i1.info;None|_->Error.expected_struct_typectx.diagnostics~location:i1.info;Noneinlet*i2'=matchexpectedwith|Somecell->let*i2',_=check_instructionctxcelli2inreturni2'|None->instructionctxi2inreturn_statementi(StructSet(i1',field,i2'))[||](* [tab[i]] on a table name is [table.get]; the receiver is not a value. *)|ArrayGet(({desc=Gettabname;_}asrecv),i2)whentable_receiverctxtabname->letat,rt=Option.get(Tbl.find_optctx.tablestabname)inlet*i2'=instructionctxi2incheck_typectxi2'(address_cellat);let*!typ=internalizectx(Refrt)inreturn_expressioni(ArrayGet({desc=Gettabname;info=([||],recv.info)},i2'))typ|ArrayGet(i1,i2)->(let*i1'=instructionctxi1inlet*i2'=instructionctxi2incheck_typectxi2'i32_cell;matchCell.get(expression_typectxi1')with|Valtype{typ=Ref{typ=Typety|Exactty;_};_}->let*!typ=lookup_array_type~location:i1.infoctxtyinlet*!ty=field_read_typectxtypinreturn_expressioni(ArrayGet(i1',i2'))ty|Error->(* Receiver already failed to type; recover silently. *)return_expressioni(ArrayGet(i1',i2'))(Cell.makeError)|Unknown|UnknownRef->(* The receiver's type is unknown (unreachable / branch code) or only
a reference (its array type cannot be resolved), so the element
cannot be read. *)Error.unknown_operand_typectx.diagnostics~location:i1.info;return_expressioni(ArrayGet(i1',i2'))(Cell.makeError)|_->Error.expected_array_typectx.diagnostics~location:i1.info;return_expressioni(ArrayGet(i1',i2'))(Cell.makeError))(* [tab[i] = v] on a table name is [table.set]; the receiver is not a value. *)|ArraySet(({desc=Gettabname;_}asrecv),i2,i3)whentable_receiverctxtabname->letat,rt=Option.get(Tbl.find_optctx.tablestabname)inlet*i2'=instructionctxi2incheck_typectxi2'(address_cellat);(* Check the stored value against the table's element type, so a
struct/array literal can drop its name. *)let*i3'=matchinternalizectx(Refrt)with|Somecell->let*i3',_=check_instructionctxcelli3inreturni3'|None->instructionctxi3inreturn_statementi(ArraySet({desc=Gettabname;info=([||],recv.info)},i2',i3'))[||]|ArraySet(i1,i2,i3)->(let*i1'=instructionctxi1inlet*i2'=instructionctxi2incheck_typectxi2'i32_cell;matchCell.get(expression_typectxi1')with|Valtype{typ=Ref{typ=Typety|Exactty;_};_}->(* Resolve the element type (pure) before typing the value, so a
struct/array literal value can drop its name. *)letexpected=matchlookup_array_type~location:i1.infoctxtywith|None->None|Sometyp->ifnottyp.mutthenError.immutablectx.diagnostics~location:i1.info"array";internalizectx(unpack_typetyp)inlet*i3'=matchexpectedwith|Somecell->let*i3',_=check_instructionctxcelli3inreturni3'|None->instructionctxi3inreturn_statementi(ArraySet(i1',i2',i3'))[||]|Error->(* Receiver already failed to type; recover silently (still type the
value so its holes are consumed). *)let*i3'=instructionctxi3inreturn_statementi(ArraySet(i1',i2',i3'))[||]|Unknown|UnknownRef->(* The receiver's type is unknown (unreachable / branch code) or only
a reference (its array type cannot be resolved), so the element
cannot be written. Still type the value so its holes are
consumed. *)let*i3'=instructionctxi3inError.unknown_operand_typectx.diagnostics~location:i1.info;return_statementi(ArraySet(i1',i2',i3'))[||]|_->let*i3'=instructionctxi3inError.expected_array_typectx.diagnostics~location:i1.info;return_statementi(ArraySet(i1',i2',i3'))[||])|_->assertfalse(* only invoked on a struct/array access *)andtype_variable_accessctxi=(* Reading and assigning a local or global: [x] ([Get]), [x = v] ([Set]) and
the tee form [Tee] that also leaves the value on the stack. *)matchi.descwith|Getidxasdesc->letty=matchresolve_variablectxidxwith|Localty->ctx.read_locals:=StringSet.addidx.desc!(ctx.read_locals);ifnot(StringSet.memidx.descctx.initialized_locals)thenError.uninitialized_localctx.diagnostics~location:idx.infoidx;(* A poison local ([None]) reads as [Error] so its uses don't
cascade. *)Cell.make(matchtywithSomeity->Valtypeity|None->Error)|Global(_,ty)->Cell.make(matchtywithSomeity->Valtypeity|None->Error)|Func_ref(ty,ty',exact)->letname=Ast.no_locty'inCell.make(Valtype{typ=Ref{nullable=false;typ=(ifexactthenExactnameelseTypename);};internal=Ref{nullable=false;typ=(ifexactthenExacttyelseTypety);};anon_comptype=inline_comptypectxname;})|Unbound->Error.unbound_namectx.diagnostics~location:idx.info~suggestions:(get_suggestionsctxidx.desc)"variable"idx;Cell.makeErrorinreturn_expressionidescty|Set(idx,op,i')->(* Resolve the target first (a pure lookup) so the value can be checked
against its type, letting a struct/array literal drop its name. The
local is marked initialized only after the value is typed, so an
assignment reading the same local (e.g. [x = x + 1]) still sees its
pre-assignment state. *)letresolved=resolve_variablectxidxin(* A compound assignment [x op= e] is type-checked as [x = x op e]: reading
[x] requires it to be initialized already, and the operator is validated
against its type by the ordinary [BinOp] path. The compound form is kept
in the typed AST (so it round-trips and lowers back to a get/op/set); the
typed right-hand side is the [BinOp]'s second operand. *)letto_check=matchopwith|None->i'|Someop->{i'withdesc=BinOp(op,{idxwithdesc=Getidx},i')}inlet*checked=matchresolvedwith|Local(Someity)|Global(_,Someity)->let*c,_=check_instructionctx(valtype_cellity)to_checkinreturnc|LocalNone|Global(_,None)|Func_ref_|Unbound->instructionctxto_checkinletvalue=match(op,checked.desc)with|None,_->checked(* A numeric [BinOp] is never wrapped by [check_instruction], so its typed
right operand is recoverable directly. *)|Some_,BinOp(_,_,rhs)->rhs|Some_,_->assertfalsein(matchresolvedwith|Local_->mark_initializedctxidx.desc|Global(mut,_)->ifnotmutthenError.immutablectx.diagnostics~location:idx.info"global"|Func_ref_->Error.not_assignablectx.diagnostics~location:idx.infoidx|Unbound->Error.unbound_namectx.diagnostics~location:idx.info~suggestions:(set_suggestionsctxidx.desc)"variable"idx);return_statementi(Set(idx,op,value))[||]|Tee(idx,i')->((* Only a local is assignable. Resolve it first so the value can be
checked against the local's type (letting a struct/array literal drop
its name); anything else is an error, after which we recover with the
operand's own type rather than [Unknown], which [check_type] cannot
match against. *)matchresolve_variablectxidxwith|Local(Someity)->lettyp=valtype_cellityinlet*i',_=check_instructionctxtypi'inmark_initializedctxidx.desc;return_expressioni(Tee(idx,i'))typ|LocalNone->(* Poison local: recover with the operand's own type, no check. *)let*i'=instructionctxi'inmark_initializedctxidx.desc;return_expressioni(Tee(idx,i'))(expression_typectxi')|Global_|Func_ref_->let*i'=instructionctxi'inError.not_assignablectx.diagnostics~location:idx.infoidx;return_expressioni(Tee(idx,i'))(expression_typectxi')|Unbound->let*i'=instructionctxi'inError.unbound_namectx.diagnostics~location:idx.info~suggestions:(local_suggestionsctxidx.desc)"variable"idx;return_expressioni(Tee(idx,i'))(expression_typectxi'))|_->assertfalse(* only invoked on Get/Set/Tee *)andtype_letctxi=(* Let bindings: a single annotated binding, a multi-value binding, and a bare
declaration ([let x: t;]). *)matchi.descwith|Let([(name_opt,Someannot)],Somei')->((* Bidirectional single annotated binding: type the initializer in
checking mode against the annotation, so an omitted struct/array name
is inferred from it; the keep-bool then says whether the annotation is
load-bearing. Dropping a present annotation stays gated on [simplify]
(Wasm->Wax), so hand-written Wax is never rewritten. A binding no later
assignment writes is effectively immutable, so — like a [const] global
— it also drops an annotation that is a mere supertype of the
initializer's type ([drop_supertype]), narrowing to that subtype. *)matchinternalize_valtypectxannotwith|None->let*i'=instructionctxi'inreturn_statementi(Let([(name_opt,Someannot)],Somei'))[||]|Someity->letdrop_supertype=matchname_optwith|Somename->not(StringSet.memname.descctx.assigned_locals)|None->trueinlet*i',needed=check_instruction~drop_supertypectx(valtype_cellity)i'inOption.iter(funname->ctx.locals<-StringMap.addname.desc(Someity,name.info)ctx.locals;ctx.local_decls:=name::!(ctx.local_decls);mark_initializedctxname.desc)name_opt;letdrop=ctx.simplify&¬neededinreturn_statementi(Let([(name_opt,ifdropthenNoneelseSomeannot)],Somei'))[||])|Let(bindings,Somei')->let*i'=instructionctxi'inletbindings=matchbindingswith|[binding]->(* Single binding: the initializer must be a one-value expression;
[expression_type] reports it if it is not. *)[bind_let_valuectx~location:(sndi'.info)(expression_typectxi')binding;]|_->(* Each name takes one value off a multi-value initializer, left to
right (the names match the values in order). *)letresult_types=fsti'.infoinletn=List.lengthbindingsinifArray.lengthresult_types<>nthenError.value_count_mismatchctx.diagnostics~location:i.info~expected:n~provided:(Array.lengthresult_types);List.mapi(funidxbinding->letresult_ty=ifidx<Array.lengthresult_typesthenresult_types.(idx)elseCell.makeErrorinbind_let_valuectx~location:i.inforesult_tybinding)bindingsinreturn_statementi(Let(bindings,Somei'))[||]|Let(bindings,None)->(* No initializer: each annotated name declares a local at its zero
value; an unannotated name has no type to take and is left out. *)List.iter(fun(name,typ)->match(name,typ)with|Somename,Sometyp->let>@ity=internalize_valtypectxtypinctx.locals<-StringMap.addname.desc(Someity,name.info)ctx.locals;ctx.local_decls:=name::!(ctx.local_decls);(* A defaultable local holds its zero value; a non-defaultable one
stays uninitialized until assigned. *)ifis_defaultabletypthenmark_initializedctxname.desc|_->())bindings;return_statementi(Let(bindings,None))[||]|_->assertfalse(* only invoked on Let *)andtype_exceptionctxi=(* Raising exceptions: [throw tag(..)] ([Throw]) and re-raising a caught
exnref ([ThrowRef]). *)matchi.descwith|Throw(tag,l)->let*l'=instructionsctxlin(let>@{params;results}=Tbl.findctx.diagnosticsctx.tagstaginifresults<>[||]thenError.tag_with_resultsctx.diagnostics~location:tag.info;let>@types=array_map_opt(funp->internalizectx(sndp.desc))paramsin(* An argument may itself produce several values (a multi-result call),
so check the flattened values against the tag's parameters, each at
its own argument's location. *)letprovided=List.concat_map(funa->List.map(funty->(ty,snda.info))(Array.to_list(fsta.info)))l'inifList.lengthprovided<>Array.lengthtypesthenError.value_count_mismatchctx.diagnostics~location:i.info~expected:(Array.lengthtypes)~provided:(List.lengthprovided)elseList.iteri(funk(ty',location)->check_subtypectx~locationty'types.(k))provided);return_statementi(Throw(tag,l'))[||]|ThrowRefi'->let*i'=instructionctxi'in(let>@typ=internalizectx(Ref{nullable=true;typ=Exn})incheck_typectxi'typ);return_statementi(ThrowRefi')[||]|_->assertfalse(* only invoked on Throw/ThrowRef *)andtype_block_constructctxi=(* The block-like control constructs (block, loop, while, if, dispatch, match,
try, try_table), which type their bodies and results through the
block-inference helpers. *)matchi.descwith|Block{label;typ;block={desc=instrs;_}asblkloc}->((* An expression-position block draws nothing from a stack, so a parameter
type has no source; report it, then recover by supplying the declared
parameters anyway so the body does not underflow into spurious "stack
empty" errors. (With no parameters this is the empty stack, unchanged.) *)ifArray.lengthtyp.params>0thenError.parameterized_block_expressionctx.diagnostics~location:i.info;(* The block's value is consumed here, so it is value-producing: infer (and
on [simplify] drop) its result type, admitting branches to its own
label (unlike [if]). An omitted annotation is therefore always a dropped
single result, never a void block. *)matchblock_inferencectxilabeltyp~instrs:blklocwith|Some(desc,results)->return_statementidescresults|None->let*!params=array_map_opt(funp->internalizectx(sndp.desc))typ.paramsinlet*!results=array_map_opt(internalizectx)typ.resultsinletinstrs'=blockctxi.infolabelparamsresultsresultsinstrsinreturn_statementi(Block{label;typ;block={blklocwithdesc=instrs'}})results)|Dispatch{index;cases;default;arms}->(* The case (arm) labels become distinct block labels in the lowering and
key the arm bodies, so they must be distinct. *)letreccheck_dupsseen=function|[]->()|(l,_)::r->ifList.exists(funs->s=l.desc)seenthenError.dispatch_duplicate_armctx.diagnostics~location:l.infol;check_dups(l.desc::seen)rincheck_dups[]arms;(* Type-check against the equivalent blocks (see [Ast_utils.lower_dispatch])
as a void block body — the outermost case block followed by the first
arm's trailing body. This validates the index is an [i32], every
[br_table] target resolves to a 0-ary label, and each case body is
well-typed. Then rebuild a typed [Dispatch], preserving the high-level
form for the formatter and for the identical re-lowering in [To_wasm]. *)letlowered=Ast_utils.lower_dispatch~block_info:i.info~index~cases~default~armsin(* In expression position the dispatch is checked in isolation (a void
block body); a divergence in the trailing case body is propagated only
in statement position — see [toplevel_instruction]. *)lettyped=blockctxi.infoNone[||][||][||]loweredinletindex',arms'=rebuild_dispatchtypedarmsinreturn_statementi(Dispatch{index=index';cases;default;arms=arms'})[||]|Match{scrutinee;arms;default}->(* Type-check against the nested type-test ladder (see
[Ast_utils.lower_match]): the scrutinee is threaded once through a
[br_on_cast]/[br_on_null] chain whose tests branch out to the arm
blocks. The arm bodies must diverge (a block's result is supplied only
on the matching-branch path); the lowered block check enforces this.
Rebuild a typed [Match] for the formatter and the identical re-lowering
in [To_wasm]. *)let*scrut'=instructionctxscrutineein(* The chain's casts require a reference scrutinee; flag a non-reference
here (the failed cast in the lowered form reports at the same spot). *)(matchmatch_scrut_reftypectxscrut'with|Some_->()|None->Error.expected_refctx.diagnostics~location:(sndscrut'.info));letlabels=match_labelsi.infoarmsinletlowered=Ast_utils.lower_match~block_info:i.info~labels~scrutinee~arms~defaultinlettyped=blockctxi.infoNone[||][||][||]loweredinletarms',default'=rebuild_matchtypedarmsinreturn_statementi(Match{scrutinee=scrut';arms=arms';default={defaultwithdesc=default'};})[||]|Loop{label;typ;block={desc=instrs;_}asblkloc}->(ifArray.lengthtyp.params>0thenError.parameterized_block_expressionctx.diagnostics~location:i.info;matchloop_inferencectxilabeltyp~instrs:blklocwith|Some(desc,results)->return_statementidescresults|None->let*!params=array_map_opt(funp->internalizectx(sndp.desc))typ.paramsinlet*!results=array_map_opt(internalizectx)typ.resultsinletinstrs'=blockctxi.infolabelparamsresultsparamsinstrsinreturn_statementi(Loop{label;typ;block={blklocwithdesc=instrs'}})results)|While{label;cond;step;block={desc=instrs;_}asblkloc}->(* Type-check the equivalent loop (see [Ast_utils.lower_while]): this
validates that [cond] is an [i32], the continue-expression and body are
well-typed, and — for a labelled step — that a [br] to the loop label
(continue) runs the step. Then rebuild a typed [While], keeping the
high-level form for the formatter and for the identical re-lowering in
[To_wasm]. *)letlowered=Ast_utils.lower_while~block_info:i.info~fresh_loop:(Ast.no_locAst_utils.synthetic_loop_label)~label~cond~step~block:instrsinlettyped=blockctxi.infoNone[||][||][||]loweredinletcond',step',instrs'=rebuild_while~stepped:(step<>None)~labelled:(label<>None)typedinreturn_statementi(While{label;cond=cond';step=step';block={blklocwithdesc=instrs'};})[||]|If{label;typ;cond;if_block;else_block}->(let*cond'=instructionctxcondincheck_typectxcond'i32_cell;ifArray.lengthtyp.params>0thenError.parameterized_block_expressionctx.diagnostics~location:i.info;matchif_inferencectxilabeltyp~cond:cond'~if_block~else_blockwith|Some(desc,results)->return_statementidescresults|None->let*!params=array_map_opt(funp->internalizectx(sndp.desc))typ.paramsinlet*!results=array_map_opt(internalizectx)typ.resultsinletif_block'={if_blockwithdesc=blockctxi.infolabelparamsresultsresultsif_block.desc;}inletelse_block'=matchelse_blockwith|Someb->Some{bwithdesc=blockctxi.infolabelparamsresultsresultsb.desc;}|None->ifnot(missing_else_okctxparamsresults)thenError.if_without_elsectx.diagnostics~location:i.info;Noneinreturn_statementi(If{label;typ;cond=cond';if_block=if_block';else_block=else_block';})results)|If_annotation{cond;then_body;else_body}->(* Type each branch as an isolated block, under the branch's assumption so
names resolve per branch (a name may be declared only in, or with a
different type in, the matching configuration). *)letthen_body'={then_bodywithdesc=with_condctx~location:i.infocondtrue(fun()->blockctxi.infoNone[||][||][||]then_body.desc);}inletelse_body'=Option.map(funb->{bwithdesc=with_condctx~location:i.infocondfalse(fun()->blockctxi.infoNone[||][||][||]b.desc);})else_bodyinreturn_statementi(If_annotation{cond;then_body=then_body';else_body=else_body'})[||]|TryTable{label;typ;block={desc=body;_}asblkloc;catches}->(ifArray.lengthtyp.params>0thenError.parameterized_block_expressionctx.diagnostics~location:i.info;matchtrytable_inferencectxilabeltyp~body:blkloc~catcheswith|Some(desc,results)->return_statementidescresults|None->let*!params=array_map_opt(funp->internalizectx(sndp.desc))typ.paramsinlet*!results=array_map_opt(internalizectx)typ.resultsinletbody'=blockctxi.infolabelparamsresultsresultsbodyincheck_trytable_catchesctxcatches;return_statementi(TryTable{label;typ;block={blklocwithdesc=body'};catches})results)|TryCatch{label;typ;block={desc=body;_}asblkloc;arms}->((* The structured try (see [Ast_utils.lower_trycatch] for the lowering):
the body's normal completion escapes past all arms (one implicit
branch to the join, carrying the try's value); the arms are honest
trailing code in clause order — arm [k] enters on its tag's payload
(plus the [&exn] for a [&] arm) and its completion must be arm
[k+1]'s entry, the last arm's the try's result. The label is the
join, a block-like exit carrying the result. *)ifArray.lengthtyp.params>0thenError.parameterized_block_expressionctx.diagnostics~location:i.info;matchtrycatch_inferencectxilabeltyp~body:blkloc~armswith|Some(desc,results)->return_statementidescresults|None->let*!results=array_map_opt(internalizectx)typ.resultsinletbody'=blockctxi.infolabel[||]resultsresultsbodyinletarms'=type_trycatch_armsctxilabel~resultsarmsinreturn_statementi(TryCatch{label;typ;block={blklocwithdesc=body'};arms=arms';})results)|Try{label;typ;block={desc=body;_}asblkloc;catches;catch_all}->(assert(typ.params=[||]);matchtry_inferencectxilabeltyp~body:blkloc~catches~catch_allwith|Some(desc,results)->return_statementidescresults|None->let*!results=array_map_opt(internalizectx)typ.resultsinletbody'=blockctxi.infolabel[||]resultsresultsbodyinletcatches,catch_all=type_try_catchesctxilabel~resultscatchescatch_allinreturn_statementi(Try{label;typ;block={blklocwithdesc=body'};catches;catch_all;})results)|_->assertfalse(* only invoked on a block-like construct *)andtype_mem_method_callctxifuncrecvmemnamemethargs=let_,address_type=Option.get(Tbl.find_optctx.memoriesmemname)inletaddr_vt=address_celladdress_typeinletis_store=mem_store_methodmeth.descinletnstack=ifis_storethen2else1inlet*args'=mem_call_argumentsctxargsinletpositional,labelled=split_labelled_argsctxargs'inletfind=take_labelsctx~allowed:["offset";"align"]labelledinletexample=memname.desc^"."^meth.desc^"(..., offset: 16, align: 1)"inlet_,align,offset=mem_immediatesctx~location:i.info~example~nstack~has_lane:falsefindpositionalin(matchpositionalwith|addr'::rest->(check_typectxaddr'addr_vt;ifis_storethenmatchrestwith|value'::_->(letvty=expression_typectxvalue'inmatchmeth.descwith|"store64"->check_typectxvalue'i64_cell|"storef32"->check_typectxvalue'f32_cell|"storef64"->check_typectxvalue'f64_cell|_->(matchCell.getvtywith|Valtype{internal=I32|I64;_}|Int|Number|LargeInt|Unknown|Error->(* A narrowing store ([store8]/[store16]/[store32]) wraps, so
it also accepts an i64-wide value, including a [LargeInt]
literal too big for i32. *)()|_->Error.instruction_type_mismatchctx.diagnostics~location:(sndvalue'.info)vty(Cell.makeInt)))|[]->())|[]->());check_memargctx~address_type~natural:(mem_natural_alignmeth.desc)~align~offset;letresult=ifis_storethen[||]elsematchmem_load_resultmeth.descwith|Somet->[|Cell.maket|]|None->[||]inreturn_statementi(Call({desc=StructGet({desc=Getmemname;info=([||],recv.info)},meth);info=([||],func.info);},args'))resultandtype_atomic_method_callctxifuncrecvmemnamemethfamilyargs=letmoduleA=Wax_wasm.Atomicsinlet_,address_type=Option.get(Tbl.find_optctx.memoriesmemname)in(* The address, then the value operands; then optional labelled immediates. *)letn_values=matchfamilywith|A.Load_->0|A.Store_|A.Notify->1|A.Rmw(Wax_wasm.Ast.AtomicCmpxchg,_)|A.Wait_->2|A.Rmw_->1inletnstack=1+n_valuesinlet*args'=mem_call_argumentsctxargsinletpositional,labelled=split_labelled_argsctxargs'inletfind=take_labelsctx~allowed:["offset";"align"]labelledinletexample=memname.desc^"."^meth.desc^"(..., offset: 16)"inlet_,align,offset=mem_immediatesctx~location:i.info~example~nstack~has_lane:falsefindpositionalinletrest=matchpositionalwith|addr'::rest->check_typectxaddr'(address_celladdress_type);rest|[]->[]in(* The value operand of a narrow (8/16/32-bit) store or RMW picks the i32/i64
family by its type, so it accepts either — pinned to the integer group,
with a still-flexible literal defaulting to i32 as usual; the merged cell
is the RMW's result (the returned old value). A 64-bit access is
necessarily i64. [Unknown]/[Error] (dead code / recovery) pass through. *)letcheck_valuev=letvty=expression_typectxvinmatchCell.getvtywith|Unknown|Error->vty|_->check_int_bin_opctx~location:(sndv.info)vty(Cell.makeInt)inletresult=matchfamilywith|A.Load`W8->[|Cell.makeInt8|]|A.Load`W16->[|Cell.makeInt16|]|A.Load`W32->[|i32_cell|]|A.Load`W64->[|i64_cell|]|A.Store`W64->List.iter(funv->check_typectxvi64_cell)rest;[||]|A.Store_->List.iter(funv->ignore(check_valuev))rest;[||]|A.Rmw(op,w)->(matchrestwith|[]->[|Cell.makeError|]|v::more->(matchwwith|`W64->List.iter(funv->check_typectxvi64_cell)rest;[|i64_cell|]|_->letvty=check_valuevin(match(op,more)with|Wax_wasm.Ast.AtomicCmpxchg,r::_->((* The expected and replacement values must agree on the
family; merge their cells (as a binary operator does). *)letrty=expression_typectxrinmatch(Cell.getvty,Cell.getrty)with|(Unknown|Error),_|_,(Unknown|Error)->()|_->ignore(check_int_bin_opctx~location:(sndr.info)vtyrty))|_->());[|vty|]))|A.Waitt->(matchrestwith|e::more->check_typectxe(matchtwith`I32->i32_cell|`I64->i64_cell);List.iter(funv->check_typectxvi64_cell)more|[]->());[|i32_cell|]|A.Notify->List.iter(funv->check_typectxvi32_cell)rest;[|i32_cell|]inletnatural=A.family_bytesfamilyin(* Only the offset immediate is range-checked here; an atomic access requires
exactly its natural alignment (the access width from the name, independent
of the i32/i64 family), not merely at most, so check that below. *)check_memargctx~address_type~natural~align:None~offset;(matchalignwith|Somea->(matchint_literalawith|SomevwhenWax_utils.Uint64.comparev(Wax_utils.Uint64.of_intnatural)=0->()|_->Error.atomic_alignmentctx.diagnostics~location:(snda.info)natural)|None->());return_statementi(Call({desc=StructGet({desc=Getmemname;info=([||],recv.info)},meth);info=([||],func.info);},args'))resultandtype_simd_mem_method_callctxifuncrecvmemnamemethargs=letmop=Option.get(Simd.mem_methodmeth.desc)inlet_,address_type=Option.get(Tbl.find_optctx.memoriesmemname)inletaddr_vt=address_celladdress_typeinletnstack=List.lengthmop.m_operandsinlet*args'=mem_call_argumentsctxargsinletpositional,labelled=split_labelled_argsctxargs'inletallowed=ifmop.m_lanethen["lane";"offset";"align"]else["offset";"align"]inletfind=take_labelsctx~allowedlabelledinletexample=memname.desc^"."^meth.desc^ifmop.m_lanethen"(..., lane: 0, offset: 16)"else"(..., offset: 16)"inletlane,align,offset=mem_immediatesctx~location:i.info~example~nstack~has_lane:mop.m_lanefindpositionalinList.iteri(funka->ifk=0thencheck_typectxaaddr_vtelseifk<nstackthencheck_typectxa(simd_cell(List.nthmop.m_operandsk)))positional;(ifmop.m_lanethenmatchlanewith|None->(* Only when the stack operands are exactly accounted for and no
(possibly ill-formed, already reported) [lane:] was written: too
few or extra positional arguments were reported just above, a
non-constant lane payload by [take_labels]. *)ifList.lengthpositional=nstack&¬(List.exists(fun((l:Ast.ident),_)->l.desc="lane")labelled)thenError.missing_lane_immediatectx.diagnostics~location:i.info|Somelane->(letmax_lane=16/mop.m_nat_alignin(* Compare unsigned, and reject an [Ast.Int] too large even for [u64]
([int_literal] = [None]): otherwise it slips past this check and
crashes [to_wasm]'s [int_of_string] (as for the SIMD lane index in
[type_simd_method_call]). A non-constant lane is reported by
[take_labels]. *)matchlane.descwith|Ast.Int_->(matchint_literallanewith|SomelwhenWax_utils.Uint64.comparel(Wax_utils.Uint64.of_intmax_lane)<0->()|_->Error.invalid_lane_indexctx.diagnostics~location:(sndlane.info)max_lane)|_->()));check_memargctx~address_type~natural:mop.m_nat_align~align~offset;letresult=matchmop.m_resultwithSomet->[|simd_cellt|]|None->[||]inreturn_statementi(Call({desc=StructGet({desc=Getmemname;info=([||],recv.info)},meth);info=([||],func.info);},args'))resultandtype_mem_mgmt_callctxifuncrecvnamemethargs=let_,at=Option.get(Tbl.find_optctx.memoriesname)inletaddr()=address_cellatinleti32()=i32_cellinletrecv'={desc=Getname;info=([||],recv.info)}inletmkargs'=Ast.Call({desc=StructGet(recv',meth);info=([||],func.info)},args')inletbad()=Error.invalid_management_callctx.diagnostics~location:i.infometh.desc;(* The (method, args) form matched nothing, so the result type is unknown;
recover with an [Error] value rather than [||] — some of these methods
([size], [grow]) produce a value, and claiming none would cascade into a
spurious value-count error where the call is used as an expression. *)return_statementi(mk[])[|Cell.makeError|]inmatch(meth.desc,args)with|"size",[]->return_expressioni(mk[])(addr())|"grow",[d]->let*d'=instructionctxdincheck_typectxd'(addr());return_expressioni(mk[d'])(addr())|"fill",[d;v;n]->let*d'=instructionctxdinlet*v'=instructionctxvinlet*n'=instructionctxnincheck_typectxd'(addr());check_typectxv'(i32());check_typectxn'(addr());return_statementi(mk[d';v';n'])[||]|"copy",[d;s;n]->let*d'=instructionctxdinlet*s'=instructionctxsinlet*n'=instructionctxnincheck_typectxd'(addr());check_typectxs'(addr());check_typectxn'(addr());return_statementi(mk[d';s';n'])[||]|"copy",{desc=Getsrc;info=sinfo}::([_;_;_]asrest)whenmemory_receiverctxsrc->letsrc_at=matchTbl.find_optctx.memoriessrcwithSome(_,a)->a|None->atinletaddr_ofa=address_cellain(* The length [n] indexes both the source and destination, so it is typed
at the narrower of the two address types ([I32] if either is 32-bit). *)letmin_at=match(at,src_at)with`I32,_|_,`I32->`I32|`I64,`I64->`I64inletsrc'={desc=Getsrc;info=([||],sinfo)}inlet*rest'=instructionsctxrestin(matchrest'with|[d';s';n']->check_typectxd'(addr_ofat);check_typectxs'(addr_ofsrc_at);check_typectxn'(addr_ofmin_at)|_->());return_statementi(mk(src'::rest'))[||]|"init",{desc=Getseg;info=sinfo}::([_;_;_]asrest)->ignore(Tbl.findctx.diagnosticsctx.datasseg:unitoption);letseg'={desc=Getseg;info=([||],sinfo)}inlet*rest'=instructionsctxrestin(matchrest'with|[d';s';n']->check_typectxd'(addr());check_typectxs'(i32());check_typectxn'(i32())|_->());return_statementi(mk(seg'::rest'))[||]|_->bad()andtype_table_mgmt_callctxifuncrecvnamemethargs=letat,rt=Option.get(Tbl.find_optctx.tablesname)inletaddr()=address_cellatinleti32()=i32_cellinletcheck_elte=let>@t=internalizectx(Refrt)incheck_typectxetinletrecv'={desc=Getname;info=([||],recv.info)}inletmkargs'=Ast.Call({desc=StructGet(recv',meth);info=([||],func.info)},args')inletbad()=Error.invalid_management_callctx.diagnostics~location:i.infometh.desc;(* The (method, args) form matched nothing, so the result type is unknown;
recover with an [Error] value rather than [||] — some of these methods
([size], [grow]) produce a value, and claiming none would cascade into a
spurious value-count error where the call is used as an expression. *)return_statementi(mk[])[|Cell.makeError|]inmatch(meth.desc,args)with|"size",[]->return_expressioni(mk[])(addr())|"grow",[v;n]->let*v'=instructionctxvinlet*n'=instructionctxnincheck_eltv';check_typectxn'(addr());return_expressioni(mk[v';n'])(addr())|"fill",[d;v;n]->let*d'=instructionctxdinlet*v'=instructionctxvinlet*n'=instructionctxnincheck_typectxd'(addr());check_eltv';check_typectxn'(addr());return_statementi(mk[d';v';n'])[||]|"copy",[d;s;n]->let*d'=instructionctxdinlet*s'=instructionctxsinlet*n'=instructionctxnincheck_typectxd'(addr());check_typectxs'(addr());check_typectxn'(addr());return_statementi(mk[d';s';n'])[||]|"copy",{desc=Getsrc;info=sinfo}::([_;_;_]asrest)whentable_receiverctxsrc->letsrc_at=matchTbl.find_optctx.tablessrcwith|Some(a,src_rt)->check_elem_subtypectx~location:i.info~src:src_rt~dst:rt;a|None->atinletaddr_ofa=address_cellain(* The length [n] indexes both the source and destination, so it is typed
at the narrower of the two address types ([I32] if either is 32-bit). *)letmin_at=match(at,src_at)with`I32,_|_,`I32->`I32|`I64,`I64->`I64inletsrc'={desc=Getsrc;info=([||],sinfo)}inlet*rest'=instructionsctxrestin(matchrest'with|[d';s';n']->check_typectxd'(addr_ofat);check_typectxs'(addr_ofsrc_at);check_typectxn'(addr_ofmin_at)|_->());return_statementi(mk(src'::rest'))[||]|"init",{desc=Getseg;info=sinfo}::([_;_;_]asrest)->(let>@src_rt=Tbl.findctx.diagnosticsctx.elemssegincheck_elem_subtypectx~location:i.info~src:src_rt~dst:rt);letseg'={desc=Getseg;info=([||],sinfo)}inlet*rest'=instructionsctxrestin(matchrest'with|[d';s';n']->check_typectxd'(addr());check_typectxs'(i32());check_typectxn'(i32())|_->());return_statementi(mk(seg'::rest'))[||]|_->bad()andtype_array_fill_callctxifuncamethjvn=let*a'=instructionctxainlet*j'=instructionctxjinlet*v'=instructionctxvinlet*n'=instructionctxnincheck_typectxn'i32_cell;check_typectxj'i32_cell;(matchCell.get(expression_typectxa')with|Valtype{typ=Ref{typ=Typety|Exactty;_};_}->let>@typ=lookup_array_type~location:a.infoctxtyinifnottyp.mutthenError.immutablectx.diagnostics~location:a.info"array";let>@ty=internalizectx(unpack_typetyp)inletty'=expression_typectxv'inifnot(subtypectxty'ty)thenError.instruction_type_mismatchctx.diagnostics~location:(sndv'.info)ty'ty|Error->(* receiver already failed to type; recover silently *)()|Unknown|UnknownRef->(* The receiver's type is unknown (unreachable / branch code) or only a
reference (its array type cannot be resolved), so the operation cannot
be compiled. *)Error.unknown_operand_typectx.diagnostics~location:a.info|_->Error.expected_array_typectx.diagnostics~location:a.info);return_statementi(Call({desc=StructGet(a',meth);info=([||],func.info)},[j';v';n']))[||]andtype_array_copy_callctxifunca1methi1a2i2n=let*a1'=instructionctxa1inlet*i1'=instructionctxi1inlet*a2'=instructionctxa2inlet*i2'=instructionctxi2inlet*n'=instructionctxnincheck_typectxn'i32_cell;check_typectxi2'i32_cell;letty'=expression_typectxa2'incheck_typectxi1'i32_cell;letty=expression_typectxa1'in(match(Cell.getty,Cell.getty')with(* Either array already failed to type; recover silently. *)|Error,_|_,Error->()(* An array's type is unknown (unreachable / branch code): its element type
cannot be resolved, so the copy cannot be compiled. Point at the offending
array. *)|(Unknown|UnknownRef),_->Error.unknown_operand_typectx.diagnostics~location:a1.info|_,(Unknown|UnknownRef)->Error.unknown_operand_typectx.diagnostics~location:a2.info|(Valtype{typ=Ref{typ=Typety|Exactty;_};_},Valtype{typ=Ref{typ=Typety'|Exactty';_};_})->let>@typ=lookup_array_type~location:a1.infoctxtyinlet>@typ'=lookup_array_type~location:a2.infoctxty'inifnottyp.mutthenError.immutablectx.diagnostics~location:a1.info"array";ifnot(storage_subtypectxtyp'.typtyp.typ)thenError.incompatible_array_elementsctx.diagnostics~location:a2.info|_->Error.expected_array_typectx.diagnostics~location:a1.info);return_statementi(Call({desc=StructGet(a1',meth);info=([||],func.info)},[i1';a2';i2';n']))[||]andtype_array_init_callctxifuncametharg1rest=let*a'=instructionctxainmatcharg1.descwith|Getseg->letsinfo=arg1.infoinlet*rest'=instructionsctxrestinleti32=i32_cellin(matchrest'with|[d';s';n']->check_typectxd'i32;check_typectxs'i32;check_typectxn'i32|_->());(matchCell.get(expression_typectxa')with|Valtype{typ=Ref{typ=Typety|Exactty;_};_}->(let>@field=lookup_array_type~location:a.infoctxtyinifnotfield.mutthenError.immutablectx.diagnostics~location:a.info"array";matchfield.typwith|Value(Refdst)->let>@src=Tbl.findctx.diagnosticsctx.elemssegincheck_elem_subtypectx~location:a.info~src~dst|_->ignore(Tbl.findctx.diagnosticsctx.datasseg:unitoption))|Error->(* receiver already failed to type; recover silently *)()|Unknown|UnknownRef->(* The receiver's type is unknown (unreachable / branch code) or only
a reference (its array type cannot be resolved), so the operation
cannot be compiled. *)Error.unknown_operand_typectx.diagnostics~location:a.info|_->Error.expected_array_typectx.diagnostics~location:a.info);letseg'={desc=Getseg;info=([||],sinfo)}inreturn_statementi(Call({desc=StructGet(a',meth);info=([||],func.info)},seg'::rest'))[||]|_->(* [array.init_data]/[array.init_elem] name a data or element segment as
their first argument; the lowering requires that name, so anything else
(a [null], a computed value) cannot be compiled. Type the arguments for
recovery, then reject. *)let*args'=instructionsctx(arg1::rest)inError.invalid_management_callctx.diagnostics~location:i.infometh.desc;return_statementi(Call({desc=StructGet(a',meth);info=([||],func.info)},args'))[||](* An array bulk method ([fill]/[copy]/[init]) on an array receiver but with the
wrong argument count — in practice the empty [a.fill()] an auto-closed call
leaves while being typed. The exact-arity forms are handled above; this types
the receiver and arguments and reports the arity, but keeps the method node so
recovery and editor features (signature help) still see the call. Gated on an
array receiver, so a struct field of the same name stays an indirect call. *)andtype_array_method_recoveryctxifuncrecvmethargs=let*recv'=instructionctxrecvinlet*args'=instructionsctxargsinletexpected=matchmeth.descwith"fill"->3|_->4inifList.lengthargs'<>expectedthenError.value_count_mismatchctx.diagnostics~location:i.info~expected~provided:(List.lengthargs');return_statementi(Call({desc=StructGet(recv',meth);info=([||],func.info)},args'))[||]andtype_binary_intrinsic_callctxifunci1methopargs=let*i1'=instructionctxi1inlet*args'=instructionsctxargsinletis_int=matchopwith"rotl"|"rotr"->true|_->falseinletcallargs''=Ast.Call({desc=StructGet(i1',meth);info=([||],func.info)},args'')inmatchargs'with|[i2']->letty1=expression_typectxi1'inletty2=expression_typectxi2'inletcheckty1ty2=ifis_intthencheck_int_bin_opctx~location:meth.infoty1ty2elsecheck_float_bin_opctx~location:meth.infoty1ty2in(* An abstract operand (a hole on the polymorphic stack of unreachable /
branch code) is unified onto the other operand's type; two abstract
operands take the operator's family default (int for [rotl]/[rotr],
float for [copysign]/[min]/[max]). [check_int_bin_op]/
[check_float_bin_op] leave the [Unknown]/[Error] arms to their caller,
as the [BinOp] arms of [type_arith] do. *)letty=match(Cell.getty1,Cell.getty2)with|(Unknown|Error),(Unknown|Error)->Cell.mergety1ty2(ifis_intthenIntelseFloat);ty1|(Unknown|Error),_->Cell.mergety1ty2(Cell.getty2);checkty1ty2|_,(Unknown|Error)->Cell.mergety1ty2(Cell.getty1);checkty1ty2|_->checkty1ty2inreturn_expressioni(call[i2'])ty|_->(* Wrong arity (e.g. the empty [x.min()] an auto-closed call being typed
leaves): report it, but still produce the method node with the receiver
typed — its result is the receiver's type — so recovery keeps the call
(editor features like signature help see it). *)Error.value_count_mismatchctx.diagnostics~location:i.info~expected:1~provided:(List.lengthargs');return_expressioni(callargs')(expression_typectxi1')andtype_unary_intrinsic_callctxifuncrecvmeth=let*recv'=instructionctxrecvinlet*!ty=letty=expression_typectxrecv'inmatch(Cell.getty,meth.desc)with|Valtype{typ=Ref{typ=Typet|Exactt;_};_},"length"->(let*@_,def=Tbl.find_optctx.type_context.typestinmatchdef.typwith|Array_->Somei32_cell|Struct_|Func_|Cont_->Error.expected_array_typectx.diagnostics~location:(sndrecv'.info);None)(* [array.len] accepts any subtype of [(ref null array)]: the abstract
array, a bare [null], and the bottom reference [&none] (which is below
[array]). A concrete array is handled above. *)|(Null|Valtype{typ=Ref{typ=Array|None_;_};_}),"length"->Somei32_cell|Valtype{typ=I32;_},"from_bits"->Somef32_cell|Valtype{typ=I64;_},"from_bits"->Somef64_cell|Valtype{typ=F32;_},"to_bits"->Somei32_cell|Valtype{typ=F64;_},"to_bits"->Somei64_cell(* An abstract numeric receiver (e.g. a bare float literal whose redundant
cast [simplify] dropped) defaults like any other operation: [to_bits] on a
[Float] is f64->i64, [from_bits] on an integer is i32->f32 (or i64->f64
for a [LargeInt]). The non-default widths keep their cast (load-bearing),
so they reach the concrete arms above. A fully-polymorphic [Unknown]
receiver (a value taken off the polymorphic stack of unreachable code) is
resolved the same way: the method alone fixes the int/float family, so it
defaults to that family's natural width rather than failing to compile.
[to_bits] needs a float receiver, so an integer-valued float constant
decompiled to a bare integer literal ([Number]/[LargeInt]) coerces to
[f64] too (like the [LargeInt] coercion in a float binop). A receiver
already committed to the integer family ([Int], e.g. the result of
[clz]/[extend8_s]) is *not* coerced: [to_bits] on an integer is
meaningless, and coercing its shared cell to [f64] would make the
integer-producing operation below it lower against an [f64] operand. It
falls through to the receiver-type error, mirroring [from_bits] rejecting
a [Float] receiver. *)|(Float|Number|LargeInt|Unknown),"to_bits"->Cell.setty(Valtypef64_valtype);Somei64_cell|(Number|Int|Unknown),"from_bits"->Cell.setty(Valtypei32_valtype);Somef32_cell|LargeInt,"from_bits"->Cell.setty(Valtypei64_valtype);Somef64_cell|(((Number|Int|LargeInt|Unknown|Valtype{typ=I32|I64;_})asty'),("clz"|"ctz"|"popcnt"|"extend8_s"|"extend16_s"))->ifty'=Number||ty'=UnknownthenCell.settyIntelseifty'=LargeIntthenCell.setty(Valtypei64_valtype);Somety|(((Number|Float|Unknown|LargeInt|Valtype{typ=F32|F64;_})asty'),("abs"|"ceil"|"floor"|"trunc"|"nearest"|"sqrt"))->(* A [LargeInt] receiver is a float here (a float intrinsic), like a
[Number]/[Unknown] one. *)ifty'=Number||ty'=Unknown||ty'=LargeIntthenCell.settyFloat;Somety|Error,_->Some(Cell.makeError)|(Unknown|UnknownRef),_->(* The receiver is only a reference (its method cannot be resolved), or it
is [Unknown] with a method that fixes no numeric family, so the call
cannot be compiled. *)Error.unknown_operand_typectx.diagnostics~location:(sndrecv'.info);Some(Cell.makeError)|_->Error.invalid_method_receiverctx.diagnostics~location:meth.infoty;Noneinreturn_expressioni(Call({desc=StructGet(recv',meth);info=([||],func.info)},[]))tyandtype_simd_vector_op_callctxifuncrecvmethargs=letop=Option.get(Simd.classifymeth.desc)inlet*recv'=instructionctxrecvinlet*args'=instructionsctxargsinletnimm=matchop.immwithNo_imm->0|Lane_->1|Shuffle->16inletnstack_extra=List.lengthop.operands-1inletnargs=List.lengthargs'inifnargs<>nimm+nstack_extrathenError.value_count_mismatchctx.diagnostics~location:i.info~expected:(nimm+nstack_extra)~provided:nargs;check_typectxrecv'(simd_cell(List.hdop.operands));letlane_bound=matchop.immwith|No_imm->None|Laneshape->Some(Simd.lane_countshape)|Shuffle->Some32inList.iteri(funka->ifk<nimmthen(* A lane immediate must be a constant integer in range. Unsigned
compare, and reject an [Ast.Int] too large even for [u64]
([int_literal] = [None]) — otherwise it reaches [to_wasm]'s
[int_of_string] and crashes (as for the memory lane index in
[type_simd_mem_method_call]). *)matcha.descwith|Ast.Int_->(let>@bound=lane_boundinmatchint_literalawith|SomelwhenWax_utils.Uint64.comparel(Wax_utils.Uint64.of_intbound)<0->()|_->Error.invalid_lane_indexctx.diagnostics~location:(snda.info)bound)|_->Error.constant_expression_requiredctx.diagnostics~location:(snda.info)elseletoperand=1+(k-nimm)inifoperand<List.lengthop.operandsthencheck_typectxa(simd_cell(List.nthop.operandsoperand)))args';letresult=matchop.resultwithSomet->[|simd_cellt|]|None->[||]inreturn_statementi(Call({desc=StructGet(recv',meth);info=([||],func.info)},args'))resultandtype_simd_free_intrinsic_callctxifuncnsnameargs=letfull=Simd.free_fullname.descinletcallee={desc=Path(ns,name);info=([||],func.info)}inlet*args'=instructionsctxargsinifnot(Simd.is_free_intrinsicfull)then(Error.unknown_intrinsicctx.diagnostics~location:i.infons.descname.desc;return_expressioni(Call(callee,args'))(Cell.makeError))else((matchSimd.const_shape_of_namefullwith|Someshape->letarity=Simd.const_arityshapeinifList.lengthargs'<>aritythenError.value_count_mismatchctx.diagnostics~location:i.info~expected:arity~provided:(List.lengthargs');(* Each lane of an integer shape must fit its width, accepting both the
signed and unsigned range [-2^(b-1), 2^b-1] (so an i8 lane is
[-128, 255]). Beyond rejecting a malformed const, this stops an
out-of-[int]-range literal from later crashing [V128.to_string]'s
[int_of_string] in the binary encoder. *)letbits=matchshapewith|I8x16->Some8|I16x8->Some16|I32x4->Some32|I64x2->Some64|F32x4|F64x2->NoneinList.iter(letlane_in_rangebnegl=matchint_literallwith|None->false(* exceeds u64 *)|Somev->letv=Wax_utils.Uint64.to_int64vinifnegthen(* magnitude <= 2^(b-1) *)Int64.unsigned_comparev(Int64.shift_left1L(b-1))<=0elseifb=64thentrueelseInt64.unsigned_comparev(Int64.sub(Int64.shift_left1Lb)1L)<=0infuna->match(bits,a.desc)with|Someb,Ast.Int_->ifnot(lane_in_rangebfalsea)thenError.lane_value_out_of_rangectx.diagnostics~location:(snda.info)b|(Someb,Ast.UnOp({desc=Neg;_},({desc=Ast.Int_;_}asl)))->ifnot(lane_in_rangebtruel)thenError.lane_value_out_of_rangectx.diagnostics~location:(snda.info)b|(Someb,(Ast.Float_|Ast.UnOp({desc=Neg;_},{desc=Ast.Float_;_})))->(* a float literal is not a valid integer lane *)Error.lane_value_out_of_rangectx.diagnostics~location:(snda.info)b|(None,(Ast.Int_|Ast.Float_|Ast.UnOp({desc=Neg;_},{desc=Ast.Int_|Ast.Float_;_})))->()(* a float shape accepts any numeric literal lane *)|_->Error.constant_expression_requiredctx.diagnostics~location:(snda.info))args'|None->List.iter(funa->check_typectxa(simd_cellTV128))args');return_expressioni(Call(callee,args'))(simd_cellTV128))(* Bidirectional checking mode: type [i] against an [expected] type and report
whether the contextual annotation is load-bearing (the keep-bool). A
construction literal can fill an omitted type name from [expected] and shed a
redundant one; every other expression delegates to [instruction] and reports
whether it determined its own type. [expected] is the [Unknown] sentinel when
[check_instruction] is entered from [instruction] with no context (synthesis). *)andcheck_instruction?(drop_supertype=false)ctxexpected(i:locationinstr)=(* The construction's type name: explicit, or inferred from an exact expected
type; [missing] reports a [cannot_infer_*] error and yields [None]. *)letresolve_namety~missing=matchtywith|Some_->ty|None->(matchexact_named_typeexpectedwith|Somename->Somename|None->missing();None)in(* The name is redundant precisely when [expected] pins the identical heap
type, so it can be dropped (and is, on output). *)letname_redundantname=matchexact_named_typeexpectedwith|Somen->n.desc=name.desc|None->falsein(* The type name to emit for a construction whose source name was [original]
and whose resolved name is [typ]. A name omitted in the source stays
omitted. A present name is dropped only when converting from Wasm
([simplify], so hand-written Wax is never rewritten) and the expected type
makes it redundant (or the fields alone pin the type). *)letemitted_nameoriginaltyp~field_unique=matchoriginalwith|None->None|Some_->ifctx.simplify&&(name_redundanttyp||field_unique)thenNoneelseSometypin(* The result reference type of a construction of [name]; validates it against
[expected] when there is one. *)letconstruction_resultname=(* A concrete allocator ([struct.new] / [array.new*]) yields an *exact*
reference at the Wasm level. We type it exact only when custom-descriptors
is enabled (exact reference types are part of that proposal); otherwise it
is the plain inexact reference, as before the proposal. *)letwant_exact=Wax_utils.Feature.is_enabledctx.type_context.featuresWax_utils.Feature.Custom_descriptorsinletresult=internalize?inline:(inline_comptypectxname)ctx(Ref{nullable=false;typ=(ifwant_exactthenExactnameelseTypename);})inOption.iter(funresult->ifhas_expectationexpectedthencheck_subtypectx~location:i.inforesultexpected)result;resultin(* A type carrying a [descriptor] clause must be allocated with a descriptor
([{descriptor(d) | …}]), not a plain [{T | …}]. *)letrequire_no_descriptortyp=matchTbl.find_optctx.type_context.typestypwith|Some(_,def)whenOption.is_somedef.descriptor->Error.descriptor_allocation_requiredctx.diagnostics~location:i.info|_->()inmatchi.descwith|Struct(ty,fields)->(* The unique struct type these fields name, if any: used to resolve an
omitted name and to drop a present one that the fields already pin. *)letfield_match=infer_struct_by_fieldsctxfieldsinlet*node=matchmatchtywith|Some_->ty|None->((* Field inference takes precedence over the expected type: the
fields name the exact struct constructed, whereas [expected]
may be a supertype. Fall back to [expected] only when the
fields are ambiguous. *)matchfield_matchwith|Somename->Somename|None->(matchexact_named_typeexpectedwith|Somename->Somename|None->Error.cannot_infer_struct_typectx.diagnostics~location:i.info;None))with|None->(* Unresolved: still type the field values for error recovery (and
so they consume their stack slots / holes), then recover with an
[Error] result. *)let*fields'=List.fold_left(funprev(name,written)->let*l=previnifwritten=Nonethenrecord_punctx.pun_spansname.info;let*fi'=instructionctx(field_valuenamewritten)inreturn((name,Option.map(fun_->fi')written)::l))(return[])fieldsinreturn_expressioni(Struct(None,List.revfields'))(Cell.makeError)|Sometyp->require_no_descriptortyp;let*!field_types=lookup_struct_typectxtypinifList.lengthfields<>Array.lengthfield_typesthenError.field_count_mismatchctx.diagnostics~location:i.info~expected:(Array.lengthfield_types)~provided:(List.lengthfields);let*fields'=Array.fold_left(funprevfield->letname,(f:fieldtype)=field.descinmatchList.find_opt(fun(idx,_)->name.desc=idx.desc)fieldswith|None->Error.missing_fieldctx.diagnostics~location:i.infoname;prev|Some(name,written)->let*l=previnifwritten=Nonethenrecord_punctx.pun_spansname.info;(* Check the field value against its declared type, so a
nested struct/array literal can drop its own name. *)let*checked=leti'=field_valuenamewritteninmatchinternalizectx(unpack_typef)with|Somecell->let*i',_=check_instructionctxcelli'inreturni'|None->instructionctxi'in(* Preserve punning: a punned field ([written = None]) stays
[None] so the printer re-emits [{x}]; the check above
still validates it and gives it its stack effect. *)return((name,Option.map(fun_->checked)written)::l))(return[])field_typesin(* The fields alone pin this type (re-parse re-resolves to it via
field inference, which takes precedence over the expected type),
so a present name is redundant. *)letfield_unique=matchfield_matchwith|Somen->n.desc=typ.desc|None->falseinletemitted=emitted_nametytyp~field_uniqueinlet*!result=construction_resulttypinreturn_expressioni(Struct(emitted,List.revfields'))resultin(* The outer binding annotation is redundant when the fields alone
re-infer this exact type — [field_match] names [node]'s own result heap
type, so the bare [{..}] re-resolves to it — and the annotation names
that identical type (so dropping it neither widens it nor changes its
nullability). Read back from [node] rather than the branch-local [typ],
so the keep-bool needs no mutable cell to escape the [let*!] arms.
Mirrors the scalar keep-bool [annotation_needed]; the drop itself stays
gated on [simplify] at the binding sites. *)letstandalone=standalone_valtypectx(expression_typectxnode)inletfields_pin_result=match(field_match,standalone)with|Somen,Some{typ=Ref{typ=Typet|Exactt;_};_}->t.desc=n.desc|_->falseinreturn(node,iffields_pin_resultthenannotation_needed~drop_supertypectxstandaloneexpectedelsetrue)|StructDefaultty->let*node=matchresolve_namety~missing:(fun()->Error.cannot_infer_struct_typectx.diagnostics~location:i.info)with|None->return_expressioni(StructDefaultNone)(Cell.makeError)|Sometyp->let*!fields=lookup_struct_typectxtypinifnot(Array.for_all(funfield->field_has_default(sndfield.desc))fields)thenError.not_defaultablectx.diagnostics~location:typ.info;require_no_descriptortyp;letemitted=emitted_nametytyp~field_unique:falseinlet*!result=construction_resulttypinreturn_expressioni(StructDefaultemitted)resultinreturn(node,true)|StructDesc(d,fields)->(* [{ descriptor(d) | fields }]: the struct type [X] is recovered from [d]
([d : (ref (exact Y))], [Y describes X]); the field values are then
checked against [X]'s fields. *)let*d,target=descriptor_targetctx~location:i.info~nullable:falsedinlet*node=matchOption.map(fun(t:reftype)->named_heaptypet.typ)targetwith|None|SomeNone->let*fields'=List.fold_left(funprev(name,written)->let*l=previnifwritten=Nonethenrecord_punctx.pun_spansname.info;let*fi'=instructionctx(field_valuenamewritten)inreturn((name,Option.map(fun_->fi')written)::l))(return[])fieldsinreturn_expressioni(StructDesc(d,List.revfields'))(Cell.makeError)|Some(Sometyp)->let*!field_types=lookup_struct_typectxtypinifList.lengthfields<>Array.lengthfield_typesthenError.field_count_mismatchctx.diagnostics~location:i.info~expected:(Array.lengthfield_types)~provided:(List.lengthfields);let*fields'=Array.fold_left(funprevfield->letname,(f:fieldtype)=field.descinmatchList.find_opt(fun(idx,_)->name.desc=idx.desc)fieldswith|None->Error.missing_fieldctx.diagnostics~location:i.infoname;prev|Some(name,written)->let*l=previnlet*checked=leti'=field_valuenamewritteninmatchinternalizectx(unpack_typef)with|Somecell->let*i',_=check_instructionctxcelli'inreturni'|None->instructionctxi'inreturn((name,Option.map(fun_->checked)written)::l))(return[])field_typesinlet*!result=construction_resulttypinreturn_expressioni(StructDesc(d,List.revfields'))resultinreturn(node,true)|StructDefaultDescd->let*d,target=descriptor_targetctx~location:i.info~nullable:falsedinlet*node=matchOption.map(fun(t:reftype)->named_heaptypet.typ)targetwith|None|SomeNone->return_expressioni(StructDefaultDescd)(Cell.makeError)|Some(Sometyp)->let*!fields=lookup_struct_typectxtypinifnot(Array.for_all(funfield->field_has_default(sndfield.desc))fields)thenError.not_defaultablectx.diagnostics~location:i.info;let*!result=construction_resulttypinreturn_expressioni(StructDefaultDescd)resultinreturn(node,true)|Array(ty,i1,i2)->let*node=matchresolve_namety~missing:(fun()->Error.cannot_infer_array_typectx.diagnostics~location:i.info)with|None->let*i1'=instructionctxi1inlet*i2'=instructionctxi2incheck_typectxi2'i32_cell;return_expressioni(Array(None,i1',i2'))(Cell.makeError)|Sometyp->(* Resolve the element type (pure) before typing the element value,
so a struct/array literal or null cast there can be inferred /
drop its name. The value is still typed first (then the count),
preserving the source order and hole consumption. *)letelt=matchlookup_array_typectxtypwith|Somefield'->internalizectx(unpack_typefield')|None->Noneinlet*i1'=matcheltwith|Somecell->let*i1',_=check_instructionctxcelli1inreturni1'|None->instructionctxi1inlet*i2'=instructionctxi2incheck_typectxi2'i32_cell;letemitted=emitted_nametytyp~field_unique:falseinlet*!result=construction_resulttypinreturn_expressioni(Array(emitted,i1',i2'))resultinreturn(node,true)|ArrayDefault(ty,n)->let*node=matchresolve_namety~missing:(fun()->Error.cannot_infer_array_typectx.diagnostics~location:i.info)with|None->let*n'=instructionctxnincheck_typectxn'i32_cell;return_expressioni(ArrayDefault(None,n'))(Cell.makeError)|Sometyp->let*n'=instructionctxnincheck_typectxn'i32_cell;(let>@field=lookup_array_typectxtypinifnot(field_has_defaultfield)thenError.not_defaultablectx.diagnostics~location:typ.info);letemitted=emitted_nametytyp~field_unique:falseinlet*!result=construction_resulttypinreturn_expressioni(ArrayDefault(emitted,n'))resultinreturn(node,true)|ArrayFixed(ty,instrs)->let*node=matchresolve_namety~missing:(fun()->Error.cannot_infer_array_typectx.diagnostics~location:i.info)with|None->let*instrs'=List.fold_left(funprevi'->let*l=previnlet*i'=instructionctxi'inreturn(i'::l))(return[])instrsinreturn_expressioni(ArrayFixed(None,List.revinstrs'))(Cell.makeError)|Sometyp->let*!field'=lookup_array_typectxtypinletelt=internalizectx(unpack_typefield')inlet*instrs'=List.fold_left(funprevi'->let*l=previn(* Check each element against the element type, so a nested
struct/array literal can drop its own name. *)let*i'=matcheltwith|Somecell->let*i',_=check_instructionctxcelli'inreturni'|None->instructionctxi'inreturn(i'::l))(return[])instrsinletemitted=emitted_nametytyp~field_unique:falseinlet*!result=construction_resulttypinreturn_expressioni(ArrayFixed(emitted,List.revinstrs'))resultinreturn(node,true)|ArraySegment(ty,seg,off,len)->let*node=matchresolve_namety~missing:(fun()->Error.cannot_infer_array_typectx.diagnostics~location:i.info)with|None->let*off'=instructionctxoffinlet*len'=instructionctxlenincheck_typectxoff'i32_cell;check_typectxlen'i32_cell;return_expressioni(ArraySegment(None,seg,off',len'))(Cell.makeError)|Sometyp->let*off'=instructionctxoffinlet*len'=instructionctxlenincheck_typectxoff'i32_cell;check_typectxlen'i32_cell;(* A reference element means [array.new_elem] (the segment is an
element segment); a numeric/packed element means [array.new_data]
(a data segment). *)(let>@field=lookup_array_typectxtypinmatchfield.typwith|Value(Refdst)->let>@src=Tbl.findctx.diagnosticsctx.elemssegincheck_elem_subtypectx~location:i.info~src~dst|_->ignore(Tbl.findctx.diagnosticsctx.datasseg:unitoption));letemitted=emitted_nametytyp~field_unique:falseinlet*!result=construction_resulttypinreturn_expressioni(ArraySegment(emitted,seg,off',len'))resultinreturn(node,true)|String(ty,s)->(* A string builds a byte array. Its natural type is the built-in
[<string>] ([mut i8]); it adopts a different array type only when the
context demands one — an explicit name, or one inferred from an exact
expected type — that is not structurally that default (e.g. an immutable
[chars]). As for the array literals a redundant name is dropped (on
conversion from Wasm); the annotation is kept only when a bare string
would not already take the expected type. *)letstring_typ={iwithdesc="<string>"}inletstring_valtype=internalize_valtypectx(Ref{nullable=false;typ=Typestring_typ})in(* The natural type a bare string re-infers to: the default [<string>]
array, allocated exactly as [construction_result] would (exact only
when custom-descriptors is enabled). This — not the always-inexact
[string_valtype], used only for the structural [is_default] check — is
what decides whether a binding annotation is redundant. *)letstring_valtype_natural=internalize_valtypectx(Ref{nullable=false;typ=(ifWax_utils.Feature.is_enabledctx.type_context.featuresWax_utils.Feature.Custom_descriptorsthenExactstring_typelseTypestring_typ);})inletis_defaultname=match(internalize_valtypectx(Ref{nullable=false;typ=Typename}),string_valtype)with|Somea,Someb->valtype_equalctxab|_->falseinlettyp=matchmatchtywithSome_->ty|None->exact_named_typeexpectedwith|Somenamewhennot(is_defaultname)->name|_->string_typin(let>@field=lookup_array_typectxtypinmatchfield.typwith|PackedI8->()|PackedI16->ifnot(String.is_valid_utf_8s)thenError.string_not_unicodectx.diagnostics~location:i.info|Value_->Error.invalid_string_element_typectx.diagnostics~location:i.info);letemitted=iftyp.desc=string_typ.descthenNoneelseemitted_nametytyp~field_unique:falseinlet*node=let*!result=construction_resulttypinreturn_expressioni(String(emitted,s))resultinreturn(node,annotation_needed~drop_supertypectxstring_valtype_naturalexpected)|Cast(e,typ)whenis_null_initializere->let*i'=instructionctxiin(* A cast of [null] is redundant when the checking context already
provides the very type it pins: drop it to bare [null], which
re-checks to the same type (the context re-supplies it) and lowers to
the same [ref.null]. Gated on [simplify] so hand-written casts are
kept; matched exactly so the lowered [ref.null] is unchanged. *)leti'=ifctx.simplify&&match(typ,Cell.getexpected)with|Ast.Valtypevt,Valtypeb->(matchinternalize_valtypectxvtwith|Somea->valtype_equalctxab|None->false)|_->falsethenmatchi'.descwith(* Only drop down to a *bare* null: it re-checks to [expected], which
the context re-supplies. A nested cast operand (e.g. [extern.convert_any]
over a typed [ref.null], decompiled as [(null as &?t) as &?extern])
pins a different type, so dropping the outer cast would change the
value's type — keep both casts. *)|Cast(({desc=Null;_}asinner),_)->{innerwithinfo=(fsti'.info,sndinner.info)}|_->i'elsei'inifhas_expectationexpectedthencheck_typectxi'expected;return(i',true)|If{label;typ;cond;if_block;else_block}whenhas_expectationexpected->(* The checking context supplies a result type. Drop a redundant [=> T]
(on [simplify]) when the context's [expected] is exactly the annotation
— then re-parse recovers it from the same source (a function's [-> T],
a typed binding, a call argument), so nothing is lost or loosened. On
re-parse the annotation is absent, so fill the result type back in from
[expected] for [to_wasm]. A [br] to the if's own label is fine: its
value is checked against the result like the branch tails. *)let*cond'=instructionctxcondincheck_typectxcond'i32_cell;ifArray.lengthtyp.params>0thenError.parameterized_block_expressionctx.diagnostics~location:i.info;letomitted=typ.results=[||]in(* Type the branches against the if's own declared result when annotated,
else against the context (a re-parsed, dropped annotation). *)letresult_cell=ifomittedthenexpectedelsematcharray_map_opt(internalizectx)typ.resultswith|Some[|c|]->c|_->expectedinletresults=[|result_cell|]inletif_block'={if_blockwithdesc=blockctxi.infolabel[||]resultsresultsif_block.desc;}inletelse_block'=matchelse_blockwith|Someb->Some{bwithdesc=blockctxi.infolabel[||]resultsresultsb.desc;}|None->ifnot(missing_else_okctx[||]results)thenError.if_without_elsectx.diagnostics~location:i.info;Nonein(* The if's result (its annotation, or [expected] when omitted) must fit
the context — catches e.g. an [=> i64] if where [i32] is expected. *)check_subtypectx~location:i.inforesult_cellexpected;lettyp=ifomittedthenmatchstandalone_valtypectxexpectedwith|Someiv->{typwithresults=[|iv.typ|]}|None->typelseifctx.simplify&&match(standalone_valtypectxexpected,standalone_valtypectxresult_cell)with|Somea,Someb->valtype_equalctxab|_->falsethen{typwithresults=[||]}elsetypin(* The caller's binding annotation (e.g. [let x: T = ..]) is redundant iff
the branches alone infer exactly [expected] — i.e. an unannotated [let]
would re-infer it. Read each branch's fall-through type (its lub) and
compare; a branch that diverges contributes none. *)letbranch_lastb=matchList.revbwith|last::_->(matchfstlast.infowith[|c|]->Somec|_->None)|[]->Noneinletcontents_lub=match(branch_lastif_block'.desc,matchelse_block'withSomeb->branch_lastb.desc|None->None)with|Somea,Someb->join_value_typesctxab|(Some_asr),None|None,(Some_asr)->r|None,None->Noneinletneeded=matchcontents_lubwith|Somev->(match(standalone_valtypectxv,standalone_valtypectxexpected)with|Somea,Someb->not(valtype_equalctxab)|_->true)|None->trueinlet*node=return_statementi(If{label;typ;cond=cond';if_block=if_block';else_block=else_block';})resultsinreturn(node,needed)(* A [do]/[loop]/[try]/[try_table] block in a checking context need not
annotate its own result: thread [expected] in as the result type so a
redundant annotation drops (on [simplify]) and re-parse recovers it from the
same context ([context_result_cell] / [context_block_typ]). Branches to the
block's own label, and (for [try]) the catch handlers, are checked against
[expected] like the fall-through value. The keep-bool is conservatively
[true]: unlike an [if], the value may arrive via a branch the cheap
fall-through test would miss, so a surrounding binding annotation is kept —
safe, at worst occasionally redundant. *)|Block{label;typ;block={desc=instrs;_}asblkloc}whenhas_expectationexpected->ifArray.lengthtyp.params>0thenError.parameterized_block_expressionctx.diagnostics~location:i.info;letresult_cell=context_result_cellctxtyp~expectedinletinstrs',r=block_keep_boolctxi.infolabel~result:result_cell~br_params:[|result_cell|]instrsinletneeded=block_keep_neededctx~loc:i.info~result:result_cellrincheck_subtypectx~location:i.inforesult_cellexpected;lettyp=context_block_typctxtyp~expected~result_cellinlet*node=return_statementi(Block{label;typ;block={blklocwithdesc=instrs'}})[|result_cell|]inreturn(node,needed)|Loop{label;typ;block={desc=instrs;_}asblkloc}whenhas_expectationexpected->ifArray.lengthtyp.params>0thenError.parameterized_block_expressionctx.diagnostics~location:i.info;letresult_cell=context_result_cellctxtyp~expectedin(* A [br] to a loop re-enters at its top with the loop's parameters, so it
carries no result; the loop's value is its fall-through. Hence the
branch-target type is the (empty) parameters, not the result, and a
branch to the loop's label does not deliver the value. *)letinstrs',r=block_keep_boolctxi.infolabel~result:result_cell~br_params:[||]instrsinletneeded=block_keep_neededctx~loc:i.info~result:result_cellrincheck_subtypectx~location:i.inforesult_cellexpected;lettyp=context_block_typctxtyp~expected~result_cellinlet*node=return_statementi(Loop{label;typ;block={blklocwithdesc=instrs'}})[|result_cell|]inreturn(node,needed)|TryTable{label;typ;block={desc=body;_}asblkloc;catches}whenhas_expectationexpected->ifArray.lengthtyp.params>0thenError.parameterized_block_expressionctx.diagnostics~location:i.info;letresult_cell=context_result_cellctxtyp~expectedin(* A [try_table]'s catches branch to other targets, not its own label, so
its value is the body's (the fall-through, or a [br] to its label). *)letbody',r=block_keep_boolctxi.infolabel~result:result_cell~br_params:[|result_cell|]bodyincheck_trytable_catchesctxcatches;letneeded=block_keep_neededctx~loc:i.info~result:result_cellrincheck_subtypectx~location:i.inforesult_cellexpected;lettyp=context_block_typctxtyp~expected~result_cellinlet*node=return_statementi(TryTable{label;typ;block={blklocwithdesc=body'};catches})[|result_cell|]inreturn(node,needed)|Try{label;typ;block={desc=body;_}asblkloc;catches;catch_all}whenhas_expectationexpected->assert(typ.params=[||]);letresult_cell=context_result_cellctxtyp~expectedin(* A catch handler also produces the try's value. Type the handlers against
the same inferring cell [r] as the body, so their values are collected too
(a [try] whose body diverges takes its value entirely from the handlers);
the keep-bool then sees every exit. *)letbody',r=block_keep_boolctxi.infolabel~result:result_cell~br_params:[|result_cell|]bodyinletcatches,catch_all=type_try_catchesctxilabel~results:[|r|]catchescatch_allinletneeded=block_keep_neededctx~loc:i.info~result:result_cellrincheck_subtypectx~location:i.inforesult_cellexpected;lettyp=context_block_typctxtyp~expected~result_cellinlet*node=return_statementi(Try{label;typ;block={blklocwithdesc=body'};catches;catch_all;})[|result_cell|]inreturn(node,needed)|Select(i1,i2,i3)whenhas_expectationexpected->(* The expression form of an annotated [if]: push the context's [expected]
type into both value branches, so a construction there can drop its
type name (re-parse re-pushes it through this same arm); the condition
[i1] is an [i32]. The branches are evaluated before the condition, as in
synthesis. The keep-bool is the disjunction of the branches' — the
surrounding binding annotation is load-bearing iff a branch relied on it
(e.g. to drop a name, or because its own type differs from [expected]). *)let*i2',needed2=check_instruction~drop_supertypectxexpectedi2inlet*i3',needed3=check_instruction~drop_supertypectxexpectedi3inlet*i1'=instructionctxi1incheck_typectxi1'i32_cell;(* The result is the branches' join, not [expected]: each branch is already
[<: expected], so this keeps the select's precise type (e.g. [&bytes]
rather than the [&eq] the context happened to ask for). *)letty=matchjoin_value_typesctx(expression_typectxi2')(expression_typectxi3')with|Somety->ty|None->expectedinlet*node=return_expressioni(Select(i1',i2',i3'))tyinreturn(node,needed2||needed3)|Hinted(h,inner)->(* The hint is advisory: check the wrapped branch against the same
expectation — so a trailing hinted [if] still receives the context's
result type and can drop a redundant annotation — and carry the result
and keep-bool through unchanged. *)let*inner',needed=check_instruction~drop_supertypectxexpectedinnerinlet*node=return_statementi(Hinted(h,inner'))(fstinner'.info)inreturn(node,needed)|_->let*i'=instructionctxiin(* Capture the value's own standalone-resolved type BEFORE [check_type]
mutates the cell, then decide whether the annotation is load-bearing
(see [annotation_needed]). *)letstandalone=standalone_valtypectx(expression_typectxi')inletneeded=annotation_needed~drop_supertypectxstandaloneexpectedinifhas_expectationexpectedthencheck_typectxi'expected;return(i',needed)(* Run [check_instruction] in statement (empty-stack) position, mirroring the expression
bridge in [toplevel_instruction]'s default arm: pop the hole operands off the
stack into the parameter list, run [check_instruction] on them, and surface its keep-bool.
Used for an annotated global initializer (a constant expression). *)andcheck_toplevel?(drop_supertype=false)ctxexpectedi=letcount=count_holesiinlet*args=pop_manyctxicount[]inletargs,(i',needed)=check_instruction~drop_supertypectxexpectediargsinassert(args=[]);(* A misplaced hole ([_] after a value) is reported by [check_hole_order];
it returns [false] only after reporting that error, so recover rather than
asserting. *)ignore(check_hole_orderctxi'count:bool);return(i',needed)(* Peek the parameter types of a call's callee syntactically, when it is a name
referring to a function or a funcref-typed variable. This reads no stack and
reports nothing, so the evaluation order (arguments, then callee) and hole
binding are unchanged; the callee is still typed normally afterwards. The
result is used only to check each argument against its parameter. *)andpeek_call_paramsctxcallee=(* The user heap-type name a hole-free callee resolves to, computed purely (no
typing, no stack effect): a function name, a funcref-typed variable, or a
chain of struct-field reads ending in a funcref field — e.g.
[cont.cont_func]. [None] for anything else. *)letreccallee_heaptypec=matchc.descwith|Getname->(matchresolve_variablectxnamewith|Func_ref(_,ty',_)->Some(Ast.no_locty')|Local(Some{typ=Ref{typ=Typet|Exactt;_};_})|Global(_,Some{typ=Ref{typ=Typet|Exactt;_};_})->Somet|Local_|Global_|Unbound->None)(* A cast target names the value's type directly; [from_wasm] inserts these
on a receiver before a field access (e.g. [(k as &cont_2).cont_func]). *)|Cast(_,Valtype(Ref{typ=Typet|Exactt;_}))->Somet|NonNulle->callee_heaptypee|StructGet(recv,field)->(matchcallee_heaptyperecvwith|None->None|Somestruct_name->(matchTbl.find_optctx.type_context.typesstruct_namewith|Some(_,{typ=Structfields;_})->Array.find_map(funf->letnm,(ftyp:fieldtype)=f.descinifnm.desc=field.descthenmatchftyp.typwith|Value(Ref{typ=Typet|Exactt;_})->Somet|Value_|Packed_->NoneelseNone)fields|_->None))|_->Noneinmatchcallee_heaptypecalleewith|None->None|Somet->(matchTbl.find_optctx.type_context.typestwith|Some(_,{typ=Funcft;_})->array_map_opt(funp->internalizectx(sndp.desc))ft.params|_->None)(* Type call arguments. When the callee's parameter types are known and the
arity matches, check each argument against its parameter (so a struct/array
literal argument can be inferred and have its name dropped); otherwise
synthesize them. Either way arguments are processed left-to-right, so hole
consumption matches [instructions]. *)andtyped_call_argsctxlparam_types=matchparam_typeswith|SomeparamswhenArray.lengthparams=List.lengthl->letrecgok=function|[]->return[]|a::r->let*a',_=check_instructionctxparams.(k)ainlet*r'=go(k+1)rinreturn(a'::r')ingo0l|_->instructionsctxl(* Type a value carried to a known result/branch type (a [return], [br], …).
When exactly one value is expected, check the operand against it so a
struct/array literal can be inferred and drop its name; otherwise synthesize
and check the whole tuple, as before. *)andcheck_againstctxexpectedi=matchexpectedwith|[|ty|]whenis_inferringty->(* The block's result type is being inferred: synthesize the branched
value and record it instead of checking it against the not-yet-known
result (a plain [check_instruction] would discard it, as [has_expectation] is false
for a [Collecting] cell). *)let*i'=instructionctxiinignore(subtype~location:(sndi'.info)ctx(expression_typectxi')ty:bool);returni'|[|ty|]->let*i',_=check_instructionctxtyiinreturni'|_->let*i'=instructionctxiincheck_subtypesctx~location:(sndi'.info)(fsti'.info)expected;returni'andtype_indirect_callctxii'l=letparam_types=peek_call_paramsctxi'inlet*l'=typed_call_argsctxlparam_typesinlet*i'=instructionctxi'inmatchCell.get(expression_typectxi')with|Valtype{typ=Ref{typ=Typety|Exactty;_};_}->let*!typ=lookup_func_typectxtyin(let>@param_types=array_map_opt(funp->internalizectx(sndp.desc))typ.paramsinifArray.lengthparam_types<>List.lengthl'thenError.value_count_mismatchctx.diagnostics~location:i.info~expected:(Array.lengthparam_types)~provided:(List.lengthl')elseArray.iter2(funity->check_typectxity)(Array.of_listl')param_types);let*!returned_types=array_map_opt(internalizectx)typ.resultsinreturn_statementi(Call(i',l'))returned_types|Error->(* The callee already failed to type (e.g. an unbound name); recover
silently rather than adding a spurious "expected function type". *)return_statementi(Call(i',l'))[|Cell.makeError|]|Unknown|UnknownRef->(* The callee's type is unknown (unreachable / branch code) or only a
reference (its function type cannot be resolved), so the call cannot be
compiled. *)Error.unknown_operand_typectx.diagnostics~location:(sndi'.info);return_statementi(Call(i',l'))[|Cell.makeError|]|_->Error.expected_func_typectx.diagnostics~location:(sndi'.info);return_statementi(Call(i',l'))[|Cell.makeError|]andcall_instructionctxi=(* Dispatches a [Call]: first the intrinsic method/free-function
forms (memory, table, segment, array, numeric, and SIMD
operations written as [recv.meth(..)] or [name(..)]), then an
ordinary call through a function reference. *)matchi.descwith|Call(({desc=StructGet(({desc=Getmemname;_}asrecv),meth);_}asfunc),args)whenWax_wasm.Atomics.of_method_namemeth.desc<>None&&memory_receiverctxmemname->letfamily=Option.get(Wax_wasm.Atomics.of_method_namemeth.desc)intype_atomic_method_callctxifuncrecvmemnamemethfamilyargs|Call(({desc=StructGet(({desc=Getmemname;_}asrecv),meth);_}asfunc),args)whenis_mem_methodmeth.desc&&memory_receiverctxmemname->type_mem_method_callctxifuncrecvmemnamemethargs(* SIMD memory accesses: mem.loadv128(addr), mem.storev128(addr, v),
mem.load8_lane(addr, v, lane), etc. Stack operands first, then the
constant lane immediate (if any), then the usual align/offset literals. *)|Call(({desc=StructGet(({desc=Getmemname;_}asrecv),meth);_}asfunc),args)whenSimd.is_mem_methodmeth.desc&&memory_receiverctxmemname->type_simd_mem_method_callctxifuncrecvmemnamemethargs(* Memory management: mem.size/grow/fill/copy/init, on a memory name. *)|Call(({desc=StructGet(({desc=Getname;_}asrecv),meth);_}asfunc),args)whenis_mgmt_methodmeth.desc&&memory_receiverctxname->type_mem_mgmt_callctxifuncrecvnamemethargs(* Table management: tab.size/grow/fill/copy/init, on a table name. *)|Call(({desc=StructGet(({desc=Getname;_}asrecv),meth);_}asfunc),args)whenis_mgmt_methodmeth.desc&&table_receiverctxname->type_table_mgmt_callctxifuncrecvnamemethargs(* data.drop / elem.drop, on a segment name. *)|Call(({desc=StructGet(({desc=Getname;_}asrecv),({desc="drop";_}asmeth));_;}asfunc),[])whensegment_receiverctxname->letrecv'={desc=Getname;info=([||],recv.info)}inreturn_statementi(Call({desc=StructGet(recv',meth);info=([||],func.info)},[]))[||](* Stack-switching methods on a continuation receiver: [c.resume(x)],
[c.resume_throw(exc(p))], [c.resume_throw_ref(e)], [c.switch(x, tag: t)];
an [on] handler clause arrives as a wrapping [On] node (see
[type_on_clause]). These were keywords before, so no struct field can be
shadowed by claiming the names. *)|Call({desc=StructGet(recv,({desc="resume"|"resume_throw"|"resume_throw_ref"|"switch";_;}asmeth));_;},args)->type_cont_method_callctxi~handlers:[]recvmethargs|Call(({desc=StructGet(a,({desc="fill";_}asmeth));_}asfunc),[j;v;n])->type_array_fill_callctxifuncamethjvn|Call(({desc=StructGet(a1,({desc="copy";_}asmeth));_}asfunc),[i1;a2;i2;n])->type_array_copy_callctxifunca1methi1a2i2n(* array.init_data / array.init_elem: arr.init(seg, dest, src, len). The
element type selects data vs elem (as for array.new). *)|Call(({desc=StructGet(a,({desc="init";_}asmeth));_}asfunc),arg1::([_;_;_]asrest))->type_array_init_callctxifuncametharg1rest(* An array bulk method with the wrong argument count (the exact forms are
above) — the empty [a.fill()] a call being typed leaves. Gated on an array
receiver so a struct field of the same name stays an indirect call. *)|Call(({desc=StructGet(recv,({desc="fill"|"copy"|"init";_}asmeth));_;}asfunc),args)whenreceiver_is_array_refctxrecv->type_array_method_recoveryctxifuncrecvmethargs(* A scalar binary intrinsic method, [x.min(y)] — reached only when the
receiver is numeric, not a reference: [s.min(a, b)] on a struct with a
function-pointer field [min] is an indirect call (below), disambiguated by
the receiver's type, not the argument count. Any argument count is accepted
so the empty form an auto-closed call leaves ([x.min()]) still yields the
method node (with an arity error) for recovery and editor features. *)|Call(({desc=StructGet(i1,({desc=("rotl"|"rotr"|"copysign"|"min"|"max")asop;_;}asmeth));_;}asfunc),args)whennot(receiver_is_refctxi1)->type_binary_intrinsic_callctxifunci1methopargs(* No-argument instruction methods on a value: [x.sqrt()], [x.clz()],
[x.to_bits()], [arr.length()]. Kept in call form so they print back with
their parentheses; the result type is read from the receiver. *)|Call(({desc=StructGet(recv,meth);_}asfunc),[])whenis_unary_methodmeth.desc->type_unary_intrinsic_callctxifuncrecvmeth(* SIMD vector op written as a method intrinsic, [recv.add_i32x4(b)]. The lane
shape is read from the method name (the receiver is always v128, or a scalar
for splat); arguments are the lane immediates (if any) followed by the
remaining stack operands. *)|Call(({desc=StructGet(recv,meth);_}asfunc),args)whenSimd.classifymeth.desc<>None->type_simd_vector_op_callctxifuncrecvmethargs(* Built-in intrinsics written as a qualified path, [i64::add128(...)] or
[v128::bitselect(...)]. *)|Call(({desc=Path(ns,name);_}asfunc),args)->type_path_intrinsic_callctxifuncnsnameargs|Call(i',l)->type_indirect_callctxii'l|_->assertfalse(* only invoked on [Call] *)(* A qualified-path intrinsic call [ns::name(args)]. The [v128] namespace holds
the SIMD free-function intrinsics ([const_<shape>], [bitselect]); the [i64]
namespace holds the wide-arithmetic instructions. *)andtype_path_intrinsic_callctxifuncnsnameargs=matchns.descwith|"v128"->type_simd_free_intrinsic_callctxifuncnsnameargs|"i64"->type_wide_arith_callctxifuncnsnameargs|"atomic"whenname.desc="fence"->let*args'=instructionsctxargsinifargs'<>[]thenError.value_count_mismatchctx.diagnostics~location:i.info~expected:0~provided:(List.lengthargs');return_statementi(Call({desc=Path(ns,name);info=([||],func.info)},args'))[||](* A declared continuation type is a namespace holding its constructors,
[k::new] / [k::bind] (the [T::] namespace constructs a [&T]). *)|_whenmatchTbl.find_optctx.type_context.typesnswith|Some(_,{typ=Cont_;_})->true|_->false->type_cont_construct_callctxifuncnsnameargs|_->let*args'=instructionsctxargsinError.unknown_intrinsicctx.diagnostics~location:i.infons.descname.desc;return_expressioni(Call({desc=Path(ns,name);info=([||],func.info)},args'))(Cell.makeError)(* The [i64::] wide-arithmetic intrinsics: [add128]/[sub128] take four i64
operands (each of the two 128-bit inputs as low/high) and
[mul_wide_s]/[mul_wide_u] take two, all returning two i64 results
(low, high). *)andtype_wide_arith_callctxifuncnsnameargs=let*args'=instructionsctxargsinletarity=matchname.descwith|"add128"|"sub128"->Some4|"mul_wide_s"|"mul_wide_u"->Some2|_->Noneinmatcharitywith|None->Error.unknown_intrinsicctx.diagnostics~location:i.infons.descname.desc;(* Recover with two [Error] results (the arity every wide-arithmetic
intrinsic has), so a typo does not cascade into a value-count error. *)return_statementi(Call({desc=Path(ns,name);info=([||],func.info)},args'))[|Cell.makeError;Cell.makeError|]|Somen->ifList.lengthargs'<>nthenError.value_count_mismatchctx.diagnostics~location:i.info~expected:n~provided:(List.lengthargs');List.iter(funa->check_typectxai64_cell)args';return_statementi(Call({desc=Path(ns,name);info=([||],func.info)},args'))[|valtype_celli64_valtype;valtype_celli64_valtype|]andinstructionsctxl:_->_*_list=matchlwith|[]->return[]|i::r->let*i'=instructionctxiinlet*r'=instructionsctxrinreturn(i'::r')(* Type a memory-access call's argument list: a [Labelled] immediate has its
payload typed and is re-wrapped — preserving the label for [check_memarg],
printing and lowering — while the other arguments are typed as ordinary
expressions. Only the memory-access typers accept labels; everywhere else
[instructions] sends a [Labelled] node to the catch-all error. *)andmem_call_argumentsctxl:_->_*_list=matchlwith|[]->return[]|i::r->(matchi.descwith|Ast.Labelled(lbl,e)->let*e'=instructionctxeinlet*r'=mem_call_argumentsctxrinreturn({desc=Labelled(lbl,e');info=(fste'.info,i.info)}::r')|_->let*i'=instructionctxiinlet*r'=mem_call_argumentsctxrinreturn(i'::r'))andtoplevel_instructionctxi:stack->stack*'b=ifdebugthenFormat.eprintf"%a@."Output.instri;matchi.descwith|Block{label;typ;block={desc=instrs;_}asblkloc}->let*!params=array_map_opt(funp->internalizectx(sndp.desc))typ.paramsinlet*!results=array_map_opt(internalizectx)typ.resultsinlet*()=pop_argsctx~location:i.infoparamsinletinstrs'=blockctxi.infolabelparamsresultsresultsinstrsinreturn_statementi(Block{label;typ;block={blklocwithdesc=instrs'}})results|Loop{label;typ;block={desc=instrs;_}asblkloc}->let*!params=array_map_opt(funp->internalizectx(sndp.desc))typ.paramsinlet*!results=array_map_opt(internalizectx)typ.resultsinlet*()=pop_argsctx~location:i.infoparamsinletinstrs'=blockctxi.infolabelparamsresultsparamsinstrsinreturn_statementi(Loop{label;typ;block={blklocwithdesc=instrs'}})results|If{label;typ;cond;if_block;else_block}->(* A statement-position [if] is void (a value-producing one is consumed by
its context, so it is typed in expression position). Like a
statement-position [block]/[loop] it is not inferred — only its
expression-position form is ([if_inference], from [type_block_construct]).
This also keeps a void [if] reached by a [br] to its own label working:
the label's branch-target is then the void result, not an inferred
single value. *)let*cond=toplevel_instructionctxcondincheck_typectxcondi32_cell;let*!params=array_map_opt(funp->internalizectx(sndp.desc))typ.paramsinlet*!results=array_map_opt(internalizectx)typ.resultsinlet*()=pop_argsctx~location:i.infoparamsinletif_block={if_blockwithdesc=blockctxi.infolabelparamsresultsresultsif_block.desc;}inletelse_block=matchelse_blockwith|Someb->Some{bwithdesc=blockctxi.infolabelparamsresultsresultsb.desc;}|None->ifnot(missing_else_okctxparamsresults)thenError.if_without_elsectx.diagnostics~location:i.info;Noneinreturn_statementi(If{label;typ;cond;if_block;else_block})results|Hinted(h,inner)->(* The hint is advisory: type the wrapped branch in the same statement
position — so a hinted statement [if] stays void rather than being
inferred as an expression — and carry its result through unchanged
(the expression-position counterpart is in [type_branch]). *)let*inner=toplevel_instructionctxinnerinreturn_statementi(Hinted(h,inner))(fstinner.info)|TryTable{label;typ;block={desc=body;_}asblkloc;catches}->let*!params=array_map_opt(funp->internalizectx(sndp.desc))typ.paramsinlet*!results=array_map_opt(internalizectx)typ.resultsinlet*()=pop_argsctx~location:i.infoparamsinletbody'=blockctxi.infolabelparamsresultsresultsbodyincheck_trytable_catchesctxcatches;return_statementi(TryTable{label;typ;block={blklocwithdesc=body'};catches})results|Try{label;typ;block={desc=body;_}asblkloc;catches;catch_all}->let*!params=array_map_opt(funp->internalizectx(sndp.desc))typ.paramsinlet*!results=array_map_opt(internalizectx)typ.resultsinlet*()=pop_argsctx~location:i.infoparamsinletbody'=blockctxi.infolabelparamsresultsresultsbodyinletcatches,catch_all=type_try_catchesctxilabel~resultscatchescatch_allinreturn_statementi(Try{label;typ;block={blklocwithdesc=body'};catches;catch_all;})results|TryCatch{label;typ;block={desc=body;_}asblkloc;arms}->(* A statement-position structured [try]; unlike the raw [TryTable] (a
from-Wasm shape) it takes no parameters. *)ifArray.lengthtyp.params>0thenError.parameterized_block_expressionctx.diagnostics~location:i.info;let*!results=array_map_opt(internalizectx)typ.resultsinletbody'=blockctxi.infolabel[||]resultsresultsbodyinletarms'=type_trycatch_armsctxilabel~resultsarmsinreturn_statementi(TryCatch{label;typ;block={blklocwithdesc=body'};arms=arms'})results|Nop->return_statementiNop[||]|Unreachable->return_statementiUnreachable[||]|>unreachable|Dispatch{index;cases;default;arms}->(* As a statement, type-check the lowering (see [Ast_utils.lower_dispatch])
as a sequence in the current stack — so a divergence in the trailing
case body (e.g. every case ends in [return]) propagates, as it would for
the equivalent blocks. *)letreccheck_dupsseen=function|[]->()|(l,_)::r->ifList.exists(funs->s=l.desc)seenthenError.dispatch_duplicate_armctx.diagnostics~location:l.infol;check_dups(l.desc::seen)rincheck_dups[]arms;letlowered=Ast_utils.lower_dispatch~block_info:i.info~index~cases~default~armsinlet*typed=block_contentsctx[||]loweredinletindex',arms'=rebuild_dispatchtypedarmsinreturn_statementi(Dispatch{index=index';cases;default;arms=arms'})[||]|Match{scrutinee;arms;default}->(* As a statement, type-check the lowering (see [Ast_utils.lower_match]) in
the current stack, so the void escape block's fall-through (the no-match
path through the default) propagates. The scrutinee is block-like (no
outer holes), so it is type-checked on its own to flag a non-reference. *)let_,scrut'=instructionctxscrutinee[]in(matchmatch_scrut_reftypectxscrut'with|Some_->()|None->Error.expected_refctx.diagnostics~location:(sndscrut'.info));letlabels=match_labelsi.infoarmsinletlowered=Ast_utils.lower_match~block_info:i.info~labels~scrutinee~arms~defaultinlet*typed=block_contentsctx[||]loweredinletarms',default'=rebuild_matchtypedarmsinreturn_statementi(Match{scrutinee=scrut';arms=arms';default={defaultwithdesc=default'};})[||]|TailCall_|Br_|Br_table_|Throw_|ThrowRef_|Return_->letcount=count_holesiinlet*args=pop_manyctxicount[]inletargs,res=instructionctxiargsin(* Should not fail *)assert(args=[]);(* [check_hole_order] reports a misplaced hole and returns [false]; recover
rather than asserting. *)ignore(check_hole_orderctxrescount:bool);returnres|>unreachable|_->letcount=count_holesiinlet*args=pop_manyctxicount[]inletargs,res=instructionctxiargsin(* Should not fail *)assert(args=[]);ignore(check_hole_orderctxrescount:bool);returnres(* Check that each [try_table] catch clause forwards the right value types to its
branch target. The handler is a separate block (the target label), so unlike
[try] the catch contributes nothing to the [try_table]'s own result. Reported
at the target label, framed as a handler/target mismatch. Shared by the
expression-, statement-, and checking-position [TryTable] cases. *)andcheck_trytable_catchesctxcatches=letcheck_catchtypeslabel=letparams=branch_targetctxlabelinifArray.lengthtypes<>Array.lengthparamsthenError.value_count_mismatchctx.diagnostics~location:label.info~expected:(Array.lengthparams)~provided:(Array.lengthtypes)elseArray.iter2(funprovidedexpected->ifnot(subtypectxprovidedexpected)thenError.catch_target_mismatchctx.diagnostics~location:label.infoprovidedexpected)typesparamsinList.iter(funcatch->matchcatchwith|Catch(tag,label)->let>@{params;results=r}=Tbl.findctx.diagnosticsctx.tagstaginifr<>[||]thenError.tag_with_resultsctx.diagnostics~location:tag.info;let>@params=array_map_opt(funp->internalizectx(sndp.desc))paramsincheck_catchparamslabel|CatchRef(tag,label)->let>@{params;results=r}=Tbl.findctx.diagnosticsctx.tagstaginifr<>[||]thenError.tag_with_resultsctx.diagnostics~location:tag.info;let>@params=array_map_opt(funp->internalizectx(sndp.desc))paramsinlet>@ref_exn=internalizectx(Ref{nullable=false;typ=Exn})incheck_catch(Array.appendparams[|ref_exn|])label|CatchAlllabel->check_catch[||]label|CatchAllReflabel->let>@ref_exn=internalizectx(Ref{nullable=false;typ=Exn})incheck_catch[|ref_exn|]label)catches(* Type a structured [try]'s arms with the fall-through rule: arm [k] is a
block entered on its tag's payload (plus the [&exn] for a [&] arm) whose
completion must be arm [k+1]'s entry stack — the last arm's the try's
[results]. The try's [label] (the join) is in scope in every arm with the
result as its branch type, so [br 'l] exits carrying the try's value.
Diverging arms are exempt as any block body is. Each arm's entry types are
recorded in the node ([arm_types]) for [To_wasm]'s re-lowering. *)andtype_trycatch_armsctxilabel~resultsarms=(* The arm's entry stack, as source types: the tag's payload, plus the
non-null [&exn] for a [&] arm ([[]] for the catch-all). Mirrors
[check_trytable_catches]' tag validation (a caught tag must have no
results). *)letentryarm=letpayload=matcharm.arm_tagwith|Sometag->(matchTbl.findctx.diagnosticsctx.tagstagwith|Some{params;results=r}->ifr<>[||]thenError.tag_with_resultsctx.diagnostics~location:tag.info;Array.map(funp->sndp.desc)params|None->[||])|None->[||]inifarm.arm_refthenArray.appendpayload[|Ast.Ref{nullable=false;typ=Exn}|]elsepayloadinletentries=List.mapentryarmsinletinternalizede=array_map_opt(internalizectx)einletrecgoarmsentries=match(arms,entries)with|[],[]->[]|arm::arms',e::entries'->letexit_types=matchentries'withe'::_->internalizede'|[]->Someresultsinletarm'=match(internalizede,exit_types)with|Someparams,Someexits->letbody'=blockctxi.infolabelparamsexitsresultsarm.arm_body.descin{armwitharm_types=e;arm_body={arm.arm_bodywithdesc=body'};}|_->(* A type in the chain failed to resolve (already reported);
recover by typing the body as a plain result-producing block
rather than cascading. *)letbody'=blockctxi.infolabel[||]resultsresultsarm.arm_body.descin{armwitharm_types=e;arm_body={arm.arm_bodywithdesc=body'};}inarm'::goarms'entries'|_->assertfalseingoarmsentriesandtrycatch_inferencectxilabeltyp~body~arms=infer_synthesizedctxityp~type_body:(fun~cs:_~r->letresults=[|r|]inletbody'=blockctxi.infolabel[||]resultsresultsbody.descinletarms'=type_trycatch_armsctxilabel~resultsarmsinfuntyp->TryCatch{label;typ;block={bodywithdesc=body'};arms=arms'})(* Type a [try_legacy]'s catch handlers (and catch-all) against [results] — each
handler is a block that produces the try's result, like the body. Shared by
the expression-, statement-, and checking-position [Try] cases; the body is
typed by the caller. *)andtype_try_catchesctxilabel~resultscatchescatch_all=letcatches=List.filter_map(fun(tag,body)->let*@{params;results=r}=Tbl.findctx.diagnosticsctx.tagstaginifr<>[||]thenError.tag_with_resultsctx.diagnostics~location:tag.info;let+@params=array_map_opt(funp->internalizectx(sndp.desc))paramsinletbody'=blockctxi.infolabelparamsresultsresultsbody.descin(tag,{bodywithdesc=body'}))catchesinletcatch_all=Option.map(funbody->{bodywithdesc=blockctxi.infolabel[||]resultsresultsbody.desc;})catch_allin(catches,catch_all)andblock_contentsctxresultsl=matchlwith|[]->return[](* A trailing instruction that produces the block's single value is routed
through [check_instruction] (against the result type) rather than synthesized,
so the result type flows into it: a nested block's own result annotation then
drops, a construction drops its name, and a [?:] propagates the type into
both its branches. [classify_trailing] decides which forms qualify (a
parameterized block does not — it stays on the statement path, which pops its
parameters off the stack, as expression position has no stack to take them
from). *)|[i]whenArray.lengthresults=1&&matchclassify_trailingctxi.descwith|false,false->false|_->true->funst->(matchstwith|Emptywhenis_inferringresults.(0)->(* The block's own result type is being inferred (synthesis), so the
result cell is a [Collecting] one: checking this trailing value
against it would discard it ([has_expectation] is false). Instead
synthesize the value — a nested block runs its own inference — and
push its type, to be collected by the enclosing block. *)letcount=count_holesiinlet*args=pop_manyctxicount[]inletargs,i'=instructionctxiargsinassert(args=[]);ignore(check_hole_orderctxi'count:bool);let*()=push_results(Array.to_list(Array.map(funty->(i.info,ty))(fsti'.info)))inreturn[i']|Empty->(* The stack is empty, so this trailing instruction must produce the
block's value: a construction literal (incl. a string) or null
cast, or a nested [if]/[do] block. Check it against the single
result type so it can be inferred / drop its name, redundant
cast, or its own result annotation, just like a [return].
[check_instruction] has already validated the value against [results.(0)]
(reporting any mismatch once), so push the result type itself
rather than the value's own type — that keeps the block's
[pop_args] from reporting the same mismatch a second time. *)let*i',_=check_toplevelctxresults.(0)iinlet*()=push_results(Array.to_list(Array.map(funty->(i.info,ty))results))inreturn[i']|Cons_|Unreachable->(* The block's value is already on the stack, produced by an earlier
instruction (or the code is unreachable); this trailing one is a
statement, not the result-producer, so type it as such rather
than routing it through [check_instruction]. *)let*i'=toplevel_instructionctxiinlet*()=push_results(Array.to_list(Array.map(funty->(i.info,ty))(fsti'.info)))inreturn[i'])st|i::r->funst->letst_after,i'=toplevel_instructionctxistin(* Dead code: the stack was reachable before [i], typing [i] left it
polymorphic ([Unreachable] — [i] is a [br]/[return]/[unreachable] or
the like), and a statement still follows. Report the first such
statement, pointing back at the divergence. The following statements
are then typed on the [Unreachable] stack, so this fires once, at the
point control is lost. *)(match(st,st_after,r)with|(Empty|Cons_),Unreachable,dead::_whenctx.warn_unused->Error.dead_codectx.diagnostics~location:dead.info~related:[{Wax_utils.Diagnostic.location=i.info;message=Wax_utils.Message.text"Control never returns from here.";};]|_->());letst_after,()=push_results(Array.to_list(Array.map(funty->(i.info,ty))(fsti'.info)))st_afterinletst_after,r'=block_contentsctxresultsrst_afterin(st_after,merge_let_tuplectxi'r')andblockctxloclabelparamsresultsbr_paramsblock=with_empty_stackctx~location:loc~kind:Block(let*()=push_results(Array.to_list(Array.map(funty->(loc,ty))params))inlet*block'=block_contents{ctxwithcontrol_types=(label,br_params)::ctx.control_types}resultsblockinlet*()=pop_argsctx~location:locresultsinreturnblock')(* Like [block] for a paramless block checked against a single [result] type, but
also report whether the surrounding binding annotation is needed — i.e. would
[let x = <block>] (no annotation) re-infer a different type? It is *not* needed
exactly when the value the block produces already has type [result] on its
own, without the context forcing it. The block's value is the join of the
values reaching its exit, all of which are checked to be subtypes of [result];
so when the fall-through's own natural type is already [result], that join is
[result] regardless of any value branched to the block's label — and the
annotation is redundant. Read the fall-through's natural type off the stack,
unconstrained, before [pop_args] coerces it to [result], and compare
([annotation_needed], as the leaf [check_instruction] arm does). Stay conservative
(needed) only when the trailing instruction is a construction — routed through
[result] to resolve a context-pinned type name, which hides its natural type. A
trailing nested block is instead synthesized (routed through the inferring cell)
so its type joins like any other exit value. Returns the typed body and that
keep-bool. *)andblock_keep_boolctxloclabel~result~br_paramsbody=(* The keep-bool is decided from every value reaching the exit: the fall-through
plus values branched to the label, collected at their natural types into [cs]
(the branch-target [r] is a [Collecting] cell), then joined. The trailing
instruction needs care: one that resolves its own type joins like any other
exit value (route it through [r] — it synthesizes), but one that needs the
context to pin its type must be routed through the concrete [result], which
hides its natural type, so keep the annotation for it. A nested block always
resolves itself; a struct does iff its fields name a unique type
([infer_struct_by_fields]) — then it synthesizes the same with or without the
context, so route it through [r]; a field-ambiguous struct needs the context
to pin its type (a named one still relies on it to drop its redundant name),
so keep the annotation. (Only structs are field-checked here; other
constructions stay conservative.) *)lettrailing_construction,trailing_nested_block=matchList.revbodywith|last::_->classify_trailingctxlast.desc|[]->(false,false)in(* A trailing construction is routed through [result], hiding its natural type,
so its annotation is load-bearing — mark the cell needed up front. *)letcs,r=fresh_collecting~needed:trailing_construction(Someresult)in(* Branches deliver the result for every kind but [loop] (where they re-enter):
mirror the caller's [br_params] arity with the [Collecting] cell so their
values are recorded. *)letbr=ifArray.lengthbr_params>0then[|r|]else[||]in(* Route a trailing nested block through the inferring cell so it synthesizes;
a construction or leaf is checked against the concrete result. *)letresult_routing=iftrailing_nested_blockthenrelseresultinwith_empty_stackctx~location:loc~kind:Block(let*block'=block_contents{ctxwithcontrol_types=(label,br)::ctx.control_types}[|result_routing|]bodyinfunst->(* Snapshot the fall-through's natural type before [pop_args] resolves it
to [result], so it joins with the branched values at its own type. *)(matchstwith|Cons(loc',tv,_)->cs.collected<-(Someloc',Cell.make(Cell.gettv))::cs.collected|Empty|Unreachable->());letst,()=pop_argsctx~location:loc[|result|]stin(* Return the cell: the caller may deliver more values to it (a [try]'s catch
handlers) before [block_keep_needed] reads the join. Every value reaching
the exit is already validated against [result] — the fall-through by
[pop_args], the branched/caught values per-delivery as they were
collected — so the join only decides the keep-bool. *)(st,(block',r)))(* The keep-bool for a checked block typed by [block_keep_bool]: keep the
annotation when a delivery relied on it ([cs.needed] — a trailing construction,
or a [resume] handler that read the cell) or the join of the values reaching the
exit differs from the context type [result]. Read after any extra deliveries
(a [try]'s catch handlers) have been collected. *)andblock_keep_neededctx~loc~resultr=matchCell.getrwith|Collectingcs->(cs.needed||matchjoin_collectedctx~location:loccs.collectedwith|Somej->annotation_neededctx(standalone_valtypectxj)result|None->true)|_->true(* From the [inferred] result of an inferring block (already joined across an
[if]'s branches) and the source [typ], produce the result-type cells for the
stack effect and the [typ] to store on the node. For an omitted annotation
([typ.results = [||]]) the inferred type fills it in; for an explicit single
result the annotation is dropped (cleared) when [simplify] and the inferred
type is a subtype of it, else kept. (When it is a strict subtype the block
re-infers to that subtype — a more precise but still valid result type that
the surrounding context, which accepted the declared supertype, still
accepts; the round-trip is then more precise than the source rather than
byte-identical, as elsewhere.) *)(* The concrete width an exact ([br_if]) exit value re-defaults to on re-parse
([resolve_omitted_valtype]: a flexible int/number/int8/int16 -> i32, large-int
-> i64, float -> f64, a concrete value -> itself). A polymorphic ([Unknown])
exact is a [br_if] value on the polymorphic stack of dead code, snapshotted here
before its own downstream context (an arithmetic op, a cast) could type it; with
the annotation dropped that context re-defaults it to i32 (the dead-code
default), so treat it as i32 rather than "no constraint" — otherwise a block
whose result is wider (an i64 fall-through alongside such a [br_if]) would drop
the load-bearing annotation and no longer re-infer. [None] only for [Error]
(recovery) and a bottom reference, which impose no width constraint. Used for the
drop decision: an annotation dropped here must be re-derivable. *)andexact_reparse_internalctxty=matchCell.gettywith|Int8|Int16|Unknown->Somei32_valtype.internal|_->Option.map(funv->v.internal)(standalone_valtypectxty)(* Whether an exact exit's re-parse type equals a candidate result [internal]. *)andexact_reparse_matchesctx~resultty=matchexact_reparse_internalctxtywithNone->true|Somee->e=result(* The width a flexible numeric literal re-defaults to on re-parse (int/number ->
i32, large-int -> i64, float -> f64); [None] for anything already concrete or
non-numeric. *)andflexible_default_internal=function|Int|Number|Int8|Int16->Somei32_valtype.internal|LargeInt->Somei64_valtype.internal|Float->Somef64_valtype.internal|_->None(* Whether keeping the result annotation is load-bearing because dropping it would
change the width on re-parse. [natural] are the exit types snapshotted before
[join_collected] pinned them. When the join settled to a concrete type only
because the declared annotation pinned a flexible exit to it (e.g. a bare float
literal reaching an [f32] block, which the annotation pins to f32 but which
re-defaults to f64 without it), dropping the annotation lets that exit re-infer
the block to a different width — so keep it. Gated on [inferred] being concrete:
when the join stayed flexible (an [if] over a [LargeInt] and an [Int] branch
joins to a [LargeInt]) it self-resolves the same way on re-parse and the
annotation is redundant. *)andnatural_width_forces_annotation~natural~inferred=matchOption.map(func->Cell.getc)inferredwith|Some(Valtyperv)->List.exists(funty->matchflexible_default_internaltywith|Somedef->def<>rv.internal|None->false)natural|_->false(* Snapshot the natural types of a block's collected exit values before
[join_collected] pins them (for [natural_width_forces_annotation]). *)andcollected_naturalcollected=List.map(fun(_,ty)->Cell.getty)collected(* A [br_if] value stays on the stack typed as the block's result; when the result
is fixed (a present annotation, a context type, or the inferred join, all of
which pin a flexible exact), only a *concrete* exact of a different type is a
genuine mismatch — a flexible one is coerced to the result. Report each such
exact against [result] (a concrete width). *)andreport_exact_mismatchesctx~location~resultexacts=matchstandalone_valtypectxresultwith|None->()|Somet->List.iter(fun(loc,ty)->matchCell.gettywith|Valtypevwhenv.internal<>t.internal->Error.br_if_result_mismatchctx.diagnostics~location~loc:(Option.valueloc~default:location)~resultty|_->())exactsandfinalize_inferred?(needed=false)?(exacts=[])?(natural=[])?locationctxtyp~inferred=iftyp.results=[||]thenmatchOption.bindinferred(resolve_omitted_valtypectx)with|Someiv->(* The inferred result pins a flexible exact, so only a concrete exact of a
different type is unsound; without an annotation there is nothing to make
it match, so report it. *)(matchlocationwith|Somelocation->report_exact_mismatchesctx~location~result:(valtype_celliv)exacts|None->());([|valtype_celliv|],{typwithresults=[|iv.typ|]})|None->([||],typ)elseletresult_cells=matcharray_map_opt(internalizectx)typ.resultswith|Somea->a|None->[||]inletdrop=ctx.simplify&&(notneeded)&&Array.lengthresult_cells=1(* Keep the annotation if any exit (a fall-through / [br_table] value, …)
would re-default to a different width on re-parse. *)&&(not(natural_width_forces_annotation~natural~inferred))(* Only drop the annotation when every exact ([br_if]) exit re-defaults to
exactly the result; otherwise re-parse would re-infer a different result
and the pass-through value would no longer match it. *)&&(matchstandalone_valtypectxresult_cells.(0)with|Somet->List.for_all(fun(_,ty)->exact_reparse_matchesctx~result:t.internalty)exacts|None->exacts=[])&&match(Option.bindinferred(standalone_valtypectx),standalone_valtypectxresult_cells.(0))with|Somev,Somet->Wax_wasm.Types.val_subtype(subtyping_infoctx)v.internalt.internal|_->falsein(result_cells,ifdropthen{typwithresults=[||]}elsetyp)(* Shared scaffold for the five expression-position synthesis inferers
([if]/[block]/[loop]/[try_table]/[try]). They are identical apart from the
guard, how the body is typed, and the node they rebuild: each types its body
against a fresh [Collecting] result cell, then joins the collected exits and
(on [simplify]) drops a redundant annotation the same way. [applies] is an
extra guard on top of [infer_block_applies] ([if] also requires an [else]);
[type_body ~cs ~r] types the body against the shared cell and returns whatever
[rebuild ~typ] needs to reconstruct the node. *)andinfer_synthesized?(applies=true)ctxityp~type_body=ifnot(infer_block_appliesctxtyp&&applies)thenNoneelseletcs,r=fresh_collecting(declared_resultctxtyp)in(* [type_body] types the body against the shared cell (its side effects on
[cs] are what the join below reads) and returns a [rebuild] closure that
reconstructs the node from the finalized result type. *)letrebuild=type_body~cs~rinletnatural=collected_naturalcs.collectedinletinferred=join_collectedctx~location:i.infocs.collectedinletresults,typ=finalize_inferred~needed:cs.needed~exacts:cs.exacts~natural~location:i.infoctxtyp~inferredinSome(rebuildtyp,results)(* Try to infer (and, on [simplify], drop) an [if]'s result type from the values
reaching its exit, returning the typed node when it applies and [None] to fall
back to the annotated path. Called from the expression-position [If] case
([type_block_construct]); a statement-position [if] is void and not inferred,
like a statement [block]/[loop]. [cond] is the already-typed condition.
Applies with an [else] and under the same conditions as the other block forms
([infer_block_applies]). Like them, both branches are typed against one shared
[Collecting] cell (via [collect_into]), so every value reaching the exit —
each branch's fall-through and any value branched to the [if]'s own label — is
recorded and then joined. A trailing construction still synthesizes its own
type (its natural type is what is collected), so [finalize_inferred] only
drops [=> T] when that synthesized type is a subtype of it (a tail that cannot
synthesize on its own, e.g. a bare [null], keeps it). *)andif_inferencectxilabeltyp~cond~if_block~else_block=infer_synthesized~applies:(Option.is_someelse_block)ctxityp~type_body:(fun~cs~r->letif_block'=collect_intoctxi.infolabel~cs~rif_block.descinletelse_block'=collect_intoctxi.infolabel~cs~r(Option.getelse_block).descinfuntyp->If{label;typ;cond;if_block={if_blockwithdesc=if_block'};else_block=Some{(Option.getelse_block)withdesc=else_block'};})(* The block's declared single result internalized to a cell, or [None] when the
result is omitted. *)anddeclared_resultctxtyp=matchtyp.resultswith[|t|]->internalizectxt|_->None(* A fresh [Collecting] result cell and its backing record: [declared] is the
annotation under test (or [None]), [needed] preset when it is already known to
be load-bearing. *)andfresh_collecting?(needed=false)declared=letcs={collected=[];exacts=[];declared;needed}in(cs,Cell.make(Collectingcs))(* Type one block body against the shared [Collecting] result cell [r] (backed
by [cs]), in synthesis, recording every value reaching its exit — the
fall-through plus each value branched to [label] — into [cs.collected] (via
[subtype]) rather than unifying. The label is bound to [r] so [br]/[br_on_*]
record their value; [r] is also passed as the body's result so a trailing
nested block is routed and synthesized (its value collected) rather than typed
as a void statement and lost — the fall-through is still read off the stack
below. Returns the typed body. Every inferring block routes through this; [if]
calls it once per branch with a shared cell so both branches' exits join. *)andcollect_intoctxloclabel~cs~rinstrs=with_empty_stackctx~location:loc~kind:Block(let*body'=block_contents{ctxwithcontrol_types=(label,[|r|])::ctx.control_types}[|r|]instrsinfunst->(* The fall-through value (if any) reaches the exit alongside the
branched ones. A single leftover is consumed; anything else is left
for [with_empty_stack] to report. A value sitting on an [Unreachable]
base is a dead fall-through (e.g. after a [br]): consume it just as
[pop_args] would in check position, leaving the unreachable base. *)matchstwith|Cons(loc,tv,Empty)->cs.collected<-(Someloc,tv)::cs.collected;(Empty,body')|Cons(loc,tv,Unreachable)->cs.collected<-(Someloc,tv)::cs.collected;(Unreachable,body')|Empty->(Empty,body')|Unreachable->(Unreachable,body')|Cons_->(st,body'))(* Join the values reaching a block's exit into its inferred result, or [None]
when none do (a void or fully divergent body). Incompatible exit types are
reported with a caret at each offending value (falling back to [location], the
block, when a value carries none); this is unreachable for well-typed input,
where every exit is a subtype of the declared result. One type is kept so a
single result is still produced. *)andjoin_collectedctx~locationcollected=(* [collected] is built in reverse (cons as each exit is met); fold in source
order so a mismatch points at the values that way and recovers with the
first. *)matchList.revcollectedwith|[]->None|(loc0,first)::rest->Some(snd(List.fold_left(fun(loc_acc,acc)(loc,ty)->matchjoin_value_typesctxacctywith|Somer->(loc_acc,r)|None->Error.block_exit_type_mismatchctx.diagnostics~location~loc1:(Option.valueloc_acc~default:location)~loc2:(Option.valueloc~default:location)accty;(loc_acc,acc))(loc0,first)rest))(* Whether to infer a block's result in expression (synthesis) position: only
for the single-result, parameterless forms, and only when the annotation is
omitted (a re-parse of a dropped one, which must be re-inferred) or [simplify]
is converting from Wasm (so a redundant annotation can be dropped). *)andinfer_block_appliesctxtyp=Array.lengthtyp.params=0&&(typ.results=[||]||(ctx.simplify&&Array.lengthtyp.results=1))(* Infer (and, on [simplify], drop) the result type of a [do]/labelled block
from the values reaching its exit. The single-branch counterpart of
[if_inference]: same [fresh_collecting] / [collect_into] / [join_collected]
shape, with one body. *)andblock_inferencectxilabeltyp~instrs=infer_synthesizedctxityp~type_body:(fun~cs~r->letbody'=collect_intoctxi.infolabel~cs~rinstrs.descinfuntyp->Block{label;typ;block={instrswithdesc=body'}})(* Expression-position synthesis inference for [loop]/[try]/[try_table], the
analogue of [block_inference] for [do]. Type the body (and, for [try], the
handlers) against a fresh [Collecting] result cell so every value reaching the
exit — the fall-through, and values branched to the block's label — is
recorded, then join them and (on [simplify]) drop a redundant annotation. A
[br] to a loop re-enters at its top (branch-target = the empty params), so a
loop's value is only its fall-through; the others deliver to their label. *)andloop_inferencectxilabeltyp~instrs=infer_synthesizedctxityp~type_body:(fun~cs:_~r->letinstrs'=blockctxi.infolabel[||][|r|][||]instrs.descinfuntyp->Loop{label;typ;block={instrswithdesc=instrs'}})andtrytable_inferencectxilabeltyp~body~catches=infer_synthesizedctxityp~type_body:(fun~cs:_~r->letresults=[|r|]inletbody'=blockctxi.infolabel[||]resultsresultsbody.descincheck_trytable_catchesctxcatches;funtyp->TryTable{label;typ;block={bodywithdesc=body'};catches})andtry_inferencectxilabeltyp~body~catches~catch_all=infer_synthesizedctxityp~type_body:(fun~cs:_~r->letresults=[|r|]inletbody'=blockctxi.infolabel[||]resultsresultsbody.descinletcatches,catch_all=type_try_catchesctxilabel~resultscatchescatch_allinfuntyp->Try{label;typ;block={bodywithdesc=body'};catches;catch_all})(*** Module type and constant checking ***)letcheck_type_definitionsctx=Tbl.iterctx.types(fun_(i,(st:subtype))->letty=Wax_wasm.Types.get_subtype(subtyping_infoctx)(def_idi)in(* A continuation type must wrap a function type. Point at the wrapped
type as the source wrote it. *)(match(ty.typ,st.typ)with|Contft,Contsrc_ref->(match(Wax_wasm.Types.get_subtype(subtyping_infoctx)ft).typwith|Func_->()|Struct_|Array_|Cont_->Error.expected_func_typectx.diagnostics~location:src_ref.info)|_->());(* Every check below is about the type's relationship to its declared
supertype, so the supertype reference [sup] is the place to point. *)match(ty.supertype,st.supertype)with|None,_|_,None->()|Somej,Somesup->letlocation=sup.infoinletty'=Wax_wasm.Types.get_subtype(subtyping_infoctx)jinifty'.finalthenError.final_supertypectx.diagnostics~locationsupelseletvalid_subtype=match(ty.typ,ty'.typ)with|(Func{params;results},Func{params=params';results=results'})->Array.lengthparams=Array.lengthparams'&&Array.lengthresults=Array.lengthresults'&&Array.for_all2(funpp'->Wax_wasm.Types.val_subtype(subtyping_infoctx)p'p)paramsparams'&&Array.for_all2(funrr'->Wax_wasm.Types.val_subtype(subtyping_infoctx)rr')resultsresults'|Structfields,Structfields'->Array.lengthfields'<=Array.lengthfields&&letrecloopk=k>=Array.lengthfields'||(field_subtypectxfields.(k)fields'.(k)&&loop(k+1))inloop0|Arrayfield,Arrayfield'->field_subtypectxfieldfield'|Contft,Contft'->Wax_wasm.Types.heap_subtype(subtyping_infoctx)(Typeft)(Typeft')|Func_,(Struct_|Array_|Cont_)|Struct_,(Func_|Array_|Cont_)|Array_,(Func_|Struct_|Cont_)|Cont_,(Func_|Struct_|Array_)->falseinletdescriptor_ok=(* If the supertype has a descriptor, the subtype must too, and its
descriptor must be a subtype of the supertype's. (A subtype may
add a descriptor that its supertype lacks.) *)matchty'.descriptorwith|None->true|Somedp->(matchty.descriptorwith|Someds->Wax_wasm.Types.heap_subtype(subtyping_infoctx)(Typeds)(Typedp)|None->false)inletdescribes_ok=(* A subtype has a described type iff its supertype does, and the
subtype's described type must be a subtype of the supertype's. *)match(ty.describes,ty'.describes)with|None,None->true|Someos,Someop->Wax_wasm.Types.heap_subtype(subtyping_infoctx)(Typeos)(Typeop)|Some_,None|None,Some_->falseinifnot(valid_subtype&&descriptor_ok&&describes_ok)thenError.invalid_subtypectx.diagnostics~locationsup)letreccheck_constant_instructionctxi=letlocation=sndi.infoinmatchi.descwith|Getidx->(matchTbl.find_optctx.globalsidxwith|Some(mut,_)->ifmutthenError.constant_global_requiredctx.diagnostics~location|None->(* ref.func *)())|Null|StructDefault_|ArrayDefault_|Int_|Float_|Char_|String_->()(* A punned field ([None], written [{x}]) is a [Get] of the like-named global,
so it must satisfy the same constant-global rule; check that implicit [Get].
The [storagetype] array of the fabricated node is unused by the [Get] arm. *)|Struct(_,l)->List.iter(check_constant_fieldctx)l|StructDesc(d,l)->check_constant_instructionctxd;List.iter(check_constant_fieldctx)l|StructDefaultDescd->check_constant_instructionctxd|ArrayFixed(_,l)->List.iter(check_constant_instructionctx)l|Array(_,i1,i2)->check_constant_instructionctxi1;check_constant_instructionctxi2(* [cont.new] allocates a fresh continuation from a (constant) function
reference, so it is itself constant; its operand must be constant too. This
tracks the open stack-switching spec PR (the spec does not list it yet). *)|ContNew(_,f)->check_constant_instructionctxf|BinOp({desc=Add|Sub|Mul;_},i1,i2)->(check_constant_instructionctxi1;check_constant_instructionctxi2;matchCell.get(expression_typectxi)with|Int|Valtype{internal=I32|I64;_}->()|_->Error.constant_expression_requiredctx.diagnostics~location)|Cast({desc=Null;_},Valtype(Ref{nullable=true;_}))->(* ref.null *)()|Cast(i',Valtype(Ref{typ=I31;_}))->((* ref.i31 *)check_constant_instructionctxi';matchCell.get(expression_typectxi')with|Valtype{internal=I32;_}->()|_->Error.constant_expression_requiredctx.diagnostics~location)|Cast(i',Valtype(Ref{typ=Extern;nullable}))->(* extern.convert_any *)check_constant_instructionctxi';ifmatch(Cell.get(expression_typectxi'):inferred_type)with|Valtype{internal;_}->not(Wax_wasm.Types.val_subtype(subtyping_infoctx)internal(Ref{nullable;typ=Any}))|_->truethenError.constant_expression_requiredctx.diagnostics~location|Cast(i',Valtype(Ref{typ=Any;nullable}))->(* any.convert_extern *)check_constant_instructionctxi';ifmatch(Cell.get(expression_typectxi'):inferred_type)with|Valtype{internal;_}->not(Wax_wasm.Types.val_subtype(subtyping_infoctx)internal(Ref{nullable;typ=Extern}))|_->truethenError.constant_expression_requiredctx.diagnostics~location|UnOp({desc=Pos;_},i')->check_constant_instructionctxi'|UnOp({desc=Neg;_},{desc=Float_|Int_;_})->()(* [v128::<shape>(..)] is a constant expression; its lanes are literals.
Other SIMD ops are not constant. *)|Call({desc=Path(ns,name);_},args)whenns.desc=Simd.free_namespace&&Simd.const_shape_of_name(Simd.free_fullname.desc)<>None->List.iter(check_constant_instructionctx)args|UnOp({desc=Neg|Not;_},_)|BinOp({desc=(Div_|Rem_|And|Or|Xor|Shl|Shr_|Eq|Ne|Lt_|Gt_|Le_|Ge_);_;},_,_)|Block_|Loop_|While_|If_|TryTable_|Try_|TryCatch_|Dispatch_|Match_|Unreachable|Nop|Hole|Path_|Set_|Tee_|Call_|TailCall_|Cast_|CastDesc_|Test_|NonNull_|StructGet_|GetDescriptor_|StructSet_|ArraySegment_|ArrayGet_|ArraySet_|Let_|Br_|Br_if_|Br_table_|Br_on_null_|Br_on_non_null_|Br_on_cast_|Br_on_cast_fail_|Br_on_cast_desc_eq_|Br_on_cast_desc_eq_fail_|Hinted_|Throw_|ThrowRef_|ContBind_|Suspend_|Resume_|ResumeThrow_|ResumeThrowRef_|Switch_|On_|Return_|Sequence_|Select_|If_annotation_|Labelled_->Error.constant_expression_requiredctx.diagnostics~location(* A struct-literal field in a constant expression. An explicit value is checked
directly; a punned field ([None]) is the implicit [Get] of the like-named
global, which must also be an immutable global. *)andcheck_constant_fieldctx(name,i)=matchiwith|Somei->check_constant_instructionctxi|None->check_constant_instructionctx{desc=Getname;info=([||],name.info)}(*** Globals, functions, and declarations ***)type('before,'after)phased=|Beforeof'before|Afterof'after|PhasedConditionalof{before:'before;then_:('before,'after)phasedlist;else_:('before,'after)phasedlistoption;}(* Type a data-segment offset as a constant expression of the memory address type. *)lettype_data_offsetctxaddress_typeoff=letoff'=with_empty_stackctx~location:off.info~kind:Expression(toplevel_instructionctxoff)incheck_typectxoff'(address_celladdress_type);check_constant_instructionctxoff';off'(*** Data segment contents (WAT numeric-values proposal) ***)letstoragetype_name:storagetype->string=function|PackedI8->"i8"|PackedI16->"i16"|ValueI32->"i32"|ValueI64->"i64"|ValueF32->"f32"|ValueF64->"f64"|Value(V128|Ref_)->"?"(* Whether a raw literal string is a valid value of the run's element type. Reuse
the same predicates the WAT numlist form validates with, so the two agree. *)letdata_run_element_valid(st:storagetype)s=matchstwith|PackedI8->Wax_wasm.Misc.is_int8s|PackedI16->Wax_wasm.Misc.is_int16s|ValueI32->Wax_wasm.Misc.is_int32s|ValueI64->Wax_wasm.Misc.is_int64s|ValueF32->Wax_wasm.Misc.is_float32s|ValueF64->Wax_wasm.Misc.is_float64s|Value(V128|Ref_)->false(* The lane count and per-lane validity of a [v128] run element's shape. *)letvec_lane_count:Wax_utils.V128.shape->int=function|I8x16->16|I16x8->8|I32x4|F32x4->4|I64x2|F64x2->2letvec_lane_name:Wax_utils.V128.shape->string=function|I8x16->"i8"|I16x8->"i16"|I32x4->"i32"|I64x2->"i64"|F32x4->"f32"|F64x2->"f64"letvec_lane_valid(shape:Wax_utils.V128.shape)s=matchshapewith|I8x16->Wax_wasm.Misc.is_int8s|I16x8->Wax_wasm.Misc.is_int16s|I32x4->Wax_wasm.Misc.is_int32s|I64x2->Wax_wasm.Misc.is_int64s|F32x4->Wax_wasm.Misc.is_float32s|F64x2->Wax_wasm.Misc.is_float64s(* Validate one data-segment element: string (nothing to check), scalar run (each
value in range for the element type), or [v128] run (each lane group has its
shape's lane count, and every lane is in range). Values are raw literal
strings — nothing is typed as an expression. *)lettype_data_elementctx(e:Ast.data_elem)=matchewith|Data_string_->()|Data_run(st,values)->List.iter(fun(v:(string,location)Ast.annotated)->ifnot(data_run_element_validstv.desc)thenError.data_run_bad_elementctx.diagnostics~location:v.info(storagetype_namest))values|Data_v128vs->List.iter(fun(v:(Wax_utils.V128.t,location)Ast.annotated)->let{Wax_utils.V128.shape;components}=v.descinifList.lengthcomponents<>vec_lane_countshapethenError.data_v128_arityctx.diagnostics~location:v.info(vec_lane_countshape);List.iter(func->ifnot(vec_lane_validshapec)thenError.data_run_bad_elementctx.diagnostics~location:v.info(vec_lane_nameshape))components)vslettype_data_initctxinit=List.iter(type_data_elementctx)initletrecglobalsctxfields=List.map(funfield->matchfield.descwith|Memory({address_type;data;_}asm)->check_limitsctx~location:field.info"memory"~shared:m.sharedaddress_typem.page_size_log2m.limitsmax_memory_size;letdata=List.map(fun(d:_Ast.memdata)->type_data_initctxd.init;{dwithoffset=type_data_offsetctxaddress_typed.offset})datainAfter{fieldwithdesc=Memory{mwithdata}}|Data({mode;_}asd)->letmode=matchmodewith|Passive->Passive|Active(mem,off)->letaddress_type=matchTbl.find_optctx.memoriesmemwith|Some(_,at)->at|None->letsuggestions=Wax_utils.Spell_check.f(funf->Tbl.iterctx.memories(funk_->fk))mem.descinError.unbound_namectx.diagnostics~location:mem.info~suggestions"memory"mem;`I32inActive(mem,type_data_offsetctxaddress_typeoff)intype_data_initctxd.init;After{fieldwithdesc=Data{dwithmode}}|Elem({reftype=rt;mode;init;_}ase)->letmode=matchmodewith|EPassive->EPassive|EActive(tab,off)->(* The offset indexes [tab], whose address type may be i64. *)letaddress_type=matchTbl.find_optctx.tablestabwith|Some(at,_)->at|None->letsuggestions=Wax_utils.Spell_check.f(funf->Tbl.iterctx.tables(funk_->fk))tab.descinError.unbound_namectx.diagnostics~location:tab.info~suggestions"table"tab;`I32inEActive(tab,type_data_offsetctxaddress_typeoff)inletelem_typ=internalizectx(Refrt)inletinit=List.map(funi->leti'=with_empty_stackctx~location:i.info~kind:Expression(toplevel_instructionctxi)in(let>@typ=elem_typincheck_typectxi'typ);check_constant_instructionctxi';i')initinAfter{fieldwithdesc=Elem{ewithmode;init}}|Table({reftype=rt;init;_}ast)->check_limitsctx~location:field.info"table"~shared:falset.address_typeNonet.limitsmax_table_size;(* Without an initializer the table is filled with the element type's
default value, which a non-nullable reference does not have. *)ifOption.is_noneinit&¬rt.nullablethenError.non_nullable_tablectx.diagnostics~location:field.info;(* A table initializer may reference only imported globals. *)letinit_ctx={ctxwithglobals=ctx.import_globals}inletinit=Option.map(fune->lete'=with_empty_stackinit_ctx~location:e.info~kind:Expression(toplevel_instructioninit_ctxe)in(let>@typ=internalizectx(Refrt)incheck_typectxe'typ);check_constant_instructioninit_ctxe';e')initinAfter{fieldwithdesc=Table{twithinit}}|Global({name;mut;typ;def;_}asg)->lettyp,def'=matchtypwith|Someannot->((* Type the initializer in checking mode against the annotation,
mirroring a [let] binding: an omitted struct/array name is
inferred from it, and the keep-bool decides whether the
annotation is redundant (dropped only when converting from
Wasm, and never for a [null] whose bare form would re-infer a
floating type — see [is_null_initializer]). An immutable
([const]) global additionally drops an annotation that is a
mere supertype of the initializer's type ([drop_supertype]),
narrowing the global to that subtype — sound since nothing
reassigns it (see [annotation_needed]). *)matchinternalize_valtypectxannotwith|None->letdef'=with_empty_stackctx~location:def.info~kind:Expression(toplevel_instructionctxdef)in(Someannot,def')|Someity->(* Type the initializer before registering the global, so a
self-reference (an initializer mentioning this global) is
still reported as an unknown name. *)letdef',needed=with_empty_stackctx~location:def.info~kind:Expression(check_toplevel~drop_supertype:(notmut)ctx(valtype_cellity)def)inTbl.addctx.diagnosticsctx.globalsname(mut,Someity);letdrop=ctx.simplify&&(notneeded)&¬(is_null_initializerdef')in((ifdropthenNoneelseSomeannot),def'))|None->(* No annotation: the global takes the initializer's type, the
way a [let] binding without an annotation does. An
[Unknown]/[Error] initializer makes the global poison
([None]) rather than defaulting to [i32], so its uses do not
cascade; an [Unknown] one is additionally reported (see
[bound_value_type]). *)letdef'=with_empty_stackctx~location:def.info~kind:Expression(toplevel_instructionctxdef)inletity=bound_value_typectx~location:def.info(expression_typectxdef')inTbl.addctx.diagnosticsctx.globalsname(mut,ity);(None,def')incheck_constant_instructionctxdef';After{fieldwithdesc=Global{gwithtyp;def=def'}}|Conditional{cond;then_fields;else_fields}->PhasedConditional{before=field;then_=with_condctx~location:field.infocondtrue(fun()->globalsctxthen_fields.desc);else_=Option.map(fune->with_condctx~location:field.infocondfalse(fun()->globalsctxe.desc))else_fields;}|_->Beforefield)fieldsletrecfunctionsctxfields=List.filter_map(funfield->matchfieldwith|Before({desc=Func{name;sign;body=label,body;typ;attributes};info=location;}asf)->let*@func_typ=let*@ty=let*@_,tname,_=Tbl.findctx.diagnosticsctx.functionsnameinTbl.findctx.diagnosticsctx.types{namewithdesc=tname}inmatchtywith|_,{typ=Functyp;_}->Sometyp|_->Error.expected_func_typectx.diagnostics~location:name.info;Nonein(* A [#[start]] function must have no parameters and no results. *)ifList.exists(fun(k,_,_)->k="start")attributes&¬(Array.lengthfunc_typ.params=0&&Array.lengthfunc_typ.results=0)thenError.start_function_signaturectx.diagnostics~location:name.info;let*@return_types=array_map_opt(funtyp->internalizectxtyp)func_typ.resultsinletlocals=refStringMap.emptyin(matchsignwith|Some{params;_}->Array.iter(funp->letid,typ=p.descinmatchidwith|Someid->let>@typ=internalize_valtypectxtypinlocals:=StringMap.addid.desc(Sometyp,id.info)!locals|None->())params|_->());ifdebugthenFormat.eprintf"=== %s@."name.desc;letctx={ctxwithlocals=!locals;(* Parameters are always initialized. *)initialized_locals=StringMap.fold(funk_s->StringSet.addks)!localsStringSet.empty;(* Fresh per-function tracking of declared and read locals. *)read_locals=refStringSet.empty;local_decls=ref[];(* Fresh per-function tracking of branched-to labels, and the
labels declared in the body (collected once, up front). *)used_labels=refStringSet.empty;label_decls=List.fold_leftcollect_labels[]body;(* Locals a later assignment writes, collected up front so a
fused [let]'s drop can spot a write-once binding (linear: one
traversal per function, not per binding). *)assigned_locals=List.fold_leftcollect_assigned_localsStringSet.emptybody;control_types=[(label,return_types)];return_types;}in(* The syntactic lints (constant conditions, dropped pure values) read
the source body, before typing shadows [body] with the typed one. *)ifctx.warn_unusedthenList.iter(lint_sourcectx)body;letbody=with_empty_stackctx~location~kind:Function(let*body=block_contentsctxreturn_typesbodyinlet*()=pop_argsctx~locationreturn_typesinreturnbody)in(* A local or label whose name starts with [_] is intentionally
unused. *)ifctx.warn_unusedthenbeginList.iter(funname->letn=name.descinif(not(StringSet.memn!(ctx.read_locals)))&¬(String.lengthn>0&&n.[0]='_')thenError.unused_localctx.diagnostics~location:name.infoname)(List.rev!(ctx.local_decls));List.iter(funname->letn=name.descinif(not(StringSet.memn!(ctx.used_labels)))&¬(String.lengthn>0&&n.[0]='_')thenError.unused_labelctx.diagnostics~location:name.infoname)(List.revctx.label_decls)end;Some{fwithdesc=Func{name;sign;body=(label,body);typ;attributes};}|PhasedConditional{before={desc=Conditional{cond;then_fields=tf;else_fields=ef};info;};then_;else_;}->Some{info;desc=Conditional{cond;then_fields={tfwithdesc=with_condctx~location:infocondtrue(fun()->functionsctxthen_);};else_fields=(match(ef,else_)with|Someef,Somee->Some{efwithdesc=with_condctx~location:infocondfalse(fun()->functionsctxe);}|None,None->None|_->assertfalse);};}|PhasedConditional_|Before{desc=Global_|Conditional_|Memory_|Data_|Elem_|Table_;_;}->assertfalse|Afterf->Somef|Before({desc=Type_|Module_annotation_|Import_|Import_group_|Tag_;_;}asf)->Somef)fieldsletfunsigctxsign=check_unique_param_namesctx.diagnosticssign.params;sign(* A function or tag may give both a type reference and an inline signature
(e.g. [fn f: T (i32) -> i32]); the inline signature must then match the
referenced function type [referenced]. The two are compared in canonical
[Internal] form. Mirrors [Validation.check_inline_type]. *)letcheck_inline_typectx~locationreferencedsign=matchsignwith|None->()|Somesign->(match(internal_functypectxreferenced,internal_functypectxsign)with|Somef,Somef'->iff<>f'thenError.inline_function_type_mismatchctx.diagnostics~location|_->())letfundeclctxnametypsign=ifTbl.existsctx.diagnosticsctx.functionsnamethenNoneelsematchtypwith|Sometyp->(let*@info=Tbl.findctx.diagnosticsctx.typestypin(* The referenced type must be a function type (as for tags below); if
an inline signature is also given, it must match. *)matchsndinfowith|{typ=Funcft;_}->check_inline_typectx~location:typ.infoftsign;Some(def_id(fstinfo),typ.desc)|_->Error.expected_func_typectx.diagnostics~location:typ.info;None)|None->(matchsignwith|Somesign->letname={namewithdesc="<func:"^name.desc^">"}inlet+@i=(* [add_type] runs the [functype] converter, which already checks
parameter-name uniqueness, so [sign] needs no separate
[funsig] pass here (that would report duplicates twice). *)add_typectx.diagnosticsctx.type_context[|Ast.no_loc(name,{supertype=None;typ=Funcsign;final=true;descriptor=None;describes=None;});|]in(i,name.desc)|None->assertfalse)letfield_attributes(field:_modulefield)=matchfieldwith|Func{attributes;_}|Global{attributes;_}|Tag{attributes;_}|Memory{attributes;_}|Data{attributes;_}|Table{attributes;_}|Elem{attributes;_}|Module_annotationattributes->attributes(* An import's attributes hang off each [import_decl]; they are validated
while walking the import, not through [field_attributes]. *)|Type_|Conditional_|Import_|Import_group_->[](* Reject unknown attributes and validate the value shape of the ones that are
allowed on the entity carrying them. [import_ok] is set for the declarations
inside an [import "module" { ... }] block, where a name-only
[#[import = "name"]] overrides the imported name. *)letcheck_attribute_listdiagnostics~export_ok~start_ok~module_ok~import_ok~default_locationattributes=List.iter(fun(name,value,guard)->letlocation=matchvaluewithSomev->v.info|None->default_locationin(* A per-attribute [if <cond>] guard is only meaningful on [export] and
[start]; blame its own [if] keyword. *)(matchguardwith|Somegwhenname<>"export"&&name<>"start"->Error.guard_not_alloweddiagnostics~location:g.infoname|_->());matchnamewith|"export"->(* A bare [#[export]] (no value) reuses the entity's Wax name as the
export name; an explicit name must be a string. *)(matchvaluewith|None|Some{desc=String_;_}->()|_->Error.annotation_value_mismatchdiagnostics~location"export""a string");ifnotexport_okthenError.annotation_not_alloweddiagnostics~location"export"|"start"->(matchvaluewith|None->()|Some_->Error.annotation_value_mismatchdiagnostics~location"start""no value");ifnotstart_okthenError.annotation_not_alloweddiagnostics~location"start"|"module"->(matchvaluewith|Some{desc=String_;_}->()|_->Error.annotation_value_mismatchdiagnostics~location"module""a string");ifnotmodule_okthenError.annotation_not_alloweddiagnostics~location"module"|"feature"->(matchvaluewith|Some{desc=String_;_}->()|_->Error.annotation_value_mismatchdiagnostics~location"feature""a string");(* Allowed exactly where [module] is: as an inner attribute. *)ifnotmodule_okthenError.annotation_not_alloweddiagnostics~location"feature"|"import"->(matchvaluewith|Some{desc=String_;_}->()|_->Error.annotation_value_mismatchdiagnostics~location"import""a string");ifnotimport_okthenError.annotation_not_alloweddiagnostics~location"import"|_->Error.unknown_annotationdiagnostics~locationname)attributes(* Validate the annotations on a module field: reject unknown ones, check the
value shape of [export] / [start] / [module], and allow each only where it is
meaningful. *)letcheck_attributesdiagnosticsfield=letexport_ok,start_ok,module_ok=matchfield.descwith|Func_->(true,true,false)|Global_|Memory_|Table_|Tag_->(true,false,false)|Module_annotation_->(false,false,true)|Data_|Elem_|Type_|Import_|Import_group_|Conditional_->(false,false,false)incheck_attribute_listdiagnostics~export_ok~start_ok~module_ok~import_ok:false~default_location:field.info(field_attributesfield.desc)(*** Type-checking a configuration ***)lettype_configuration?(warn_unused=false)?(build=true)?(resolve_links=None)?(pun_spans=None)?(member_completions=None)?(features=Wax_utils.Feature.default())~simplifydiagnosticsfields=letcond=refCond.true_inletcond_env=Cond.create()inletlinks=resolve_linksinlettype_context={internal_types=Wax_wasm.Types.create();types=Tbl.make~hover:hover_of_type(Namespace.make~linkscond)"type";features;subtyping_info_cache=None;}in(* Walk module fields, recursing into groups and threading the branch
assumption through conditionals so each [Type]/declaration is registered
under the assumption of the branch it appears in. *)letrecwalk_fieldsffields=List.iter(fun(field:(_modulefield,_)annotated)->matchfield.descwith|Conditional{cond=c;then_fields;else_fields}->with_cond_refcondcond_envdiagnostics~location:field.infoctrue(fun()->walk_fieldsfthen_fields.desc);Option.iter(fune->with_cond_refcondcond_envdiagnostics~location:field.infocfalse(fun()->walk_fieldsfe.desc))else_fields|_->ffield)fieldsinwalk_fields(fun(field:(_modulefield,_)annotated)->matchfield.descwith|Typerectype->let_:Wax_wasm.Types.Id.toption=add_typediagnosticstype_contextrectypein()|_->())fields;(* Index the struct types by their field set, so a literal whose name is
omitted can be resolved from its fields. All types are registered above, so
this is complete; a later distinct name for the same key marks it ambiguous
([None]), while a conditional variant of the same name does not. *)letstructs_by_fields=Hashtbl.create16inTbl.itertype_context.types(funname(_,(st:subtype))->matchst.typwith|Structsfields->(letkey=field_set_key(Array.to_list(Array.map(funf->(fstf.desc).desc)sfields))inmatchHashtbl.find_optstructs_by_fieldskeywith|None->Hashtbl.replacestructs_by_fieldskey(Some(Ast.no_locname))|Some(Somen)whenn.desc=name->()|Some_->Hashtbl.replacestructs_by_fieldskeyNone)|Func_|Array_|Cont_->());letctx=letnamespace=Namespace.make~linkscondin{diagnostics;type_context;types=type_context.types;structs_by_fields;functions=Tbl.makenamespace"function";globals=Tbl.make~hover:hover_of_globalnamespace"global";import_globals=Tbl.make~hover:hover_of_globalnamespace"global";memories=Tbl.makenamespace"memory";datas=Tbl.make(Namespace.make~linkscond)"data segment";tables=Tbl.makenamespace"table";elems=Tbl.make(Namespace.make~linkscond)"element segment";tags=Tbl.make(Namespace.make~linkscond)"tag";locals=StringMap.empty;warn_unused;read_locals=refStringSet.empty;local_decls=ref[];used_labels=refStringSet.empty;label_decls=[];assigned_locals=StringSet.empty;initialized_locals=StringSet.empty;control_types=[];return_types=[||];cond;cond_env;resolve_links=links;pun_spans;member_completions;simplify;}incheck_type_definitionsctx;letmemory_index=ref0in(* Register a tag's type from its [typ]/[sign], shared by imported and defined
tags. *)letregister_tagnametypsign=let>@typ=match(typ,sign)with|Sometyp,_->(let*@info=Tbl.findctx.diagnosticsctx.typestypinmatchsndinfowith|{typ=Funcft;_}->check_inline_typectx~location:typ.infoftsign;Someft|_->Error.expected_func_typectx.diagnostics~location:typ.info;None)|None,Somesign->Some(funsigctxsign)|None,None->assertfalseinTbl.adddiagnosticsctx.tagsnametypin(* Register an imported entity under its Wax name. *)letregister_import(decl:Ast.import_decl)=matchdecl.kindwith|Import_func{typ;sign;exact}->let>@i,n=fundeclctxdecl.idtypsigninTbl.adddiagnosticsctx.functionsdecl.id(i,n,exact)|Import_global{mut;typ}->let>@typ=internalize_valtypectxtypinTbl.adddiagnosticsctx.globalsdecl.id(mut,Sometyp)|Import_tag{typ;sign}->register_tagdecl.idtypsign|Import_memory{address_type;_}->leti=!memory_indexinincrmemory_index;Tbl.adddiagnosticsctx.memoriesdecl.id(i,address_type)|Import_table{address_type;reftype=rt;_}->Tbl.adddiagnosticsctx.tablesdecl.id(address_type,rt)inwalk_fields(funfield->matchfield.descwith|Memory{name;address_type;data;_}->leti=!memory_indexinincrmemory_index;Tbl.adddiagnosticsctx.memoriesname(i,address_type);List.iter(fun(d:_Ast.memdata)->Option.iter(funn->Tbl.adddiagnosticsctx.datasn())d.data_name)data|Import{decl;_}->register_importdecl.desc|Import_group{decls;_}->List.iter(fund->register_importd.desc)decls|Func{name;typ;sign;_}->(* A module-defined function has exactly its declared type, so a
reference to it is exact — but exact reference types are part of
custom-descriptors; without it, type it as the plain inexact
reference, as before the proposal. *)let>@i,n=fundeclctxnametypsigninletexact=Wax_utils.Feature.is_enabledctx.type_context.featuresWax_utils.Feature.Custom_descriptorsinTbl.adddiagnosticsctx.functionsname(i,n,exact)|Tag{name;typ;sign;_}->register_tagnametypsign|Data{name;_}->Option.iter(funn->Tbl.adddiagnosticsctx.datasn())name|Table{name;address_type;reftype=rt;_}->Tbl.adddiagnosticsctx.tablesname(address_type,rt)|Elem{name;reftype=rt;_}->Tbl.adddiagnosticsctx.elemsnamert|Conditional_|Type_|Global_|Module_annotation_->())fields;(* A module may not export the same name twice. Each [#[export = "..."]]
attribute is one export; [walk_fields] descends into groups and resolves
conditionals per branch, so exports in mutually exclusive branches do not
clash. *)letexports=Hashtbl.create16in(* The conditions under which a [#[start]] has been seen; like [exports], a
second start clashes only when its condition can hold at the same time. *)letstarts=ref[]inletmodule_seen=reffalsein(* The Wax name a bare [#[export]] reuses as its export name. *)letfield_namefield=matchfield.descwith|Func{name;_}|Global{name;_}|Memory{name;_}|Table{name;_}|Tag{name;_}->Somename|Data_|Elem_|Import_|Import_group_|Conditional_|Type_|Module_annotation_->Nonein(* Process the [export]/[start]/[module] attributes carried by an entity whose
Wax name is [default_name] (used as the export name of a bare [#[export]]).
[location] blames the entity when an attribute carries no value. *)letprocess_attrs~default_name~locationattributes=List.iter(fun(key,v,guard)->(* The condition under which this attribute is actually present: the
field's own branch assumption ([!cond]) narrowed by an optional
per-attribute [if <cond>] guard (only [export]/[start] carry one). *)letcond=matchguardwith|None->!cond|Someg->Cond.and_!cond(Cond.of_condcond_envdiagnostics~location:g.infog.desc)inmatch(key,Option.map(fun(v:_instr)->v.desc)v)with|"export",((Some(String_)|None)asvalue)->(* The export name and the location to blame: the explicit string
for [#[export = "nm"]], the entity's own name for a bare
[#[export]]. *)letentry=matchvaluewith|Some(String(_,name))->Some(name,(Option.getv).info)|_->(matchdefault_namewith|Some(id:ident)->Some(id.desc,id.info)|None->None)inOption.iter(fun(name,location)->(* Two exports of the same name clash only when the conditions
guarding them can hold at once; the same name in mutually
exclusive branches is fine. Each remembered guard is the
condition under which an export was seen. *)letguards=Option.value~default:[](Hashtbl.find_optexportsname)inifList.exists(fung->Cond.is_satisfiable(Cond.and_gcond))guardsthenError.duplicated_exportdiagnostics~locationname;Hashtbl.replaceexportsname(cond::guards))entry|"start",_->(* A module may name at most one start function per configuration;
starts in mutually exclusive branches are fine. *)ifList.exists(fung->Cond.is_satisfiable(Cond.and_gcond))!startsthenError.multiple_startdiagnostics~location;starts:=cond::!starts|"module",_->(* A module may carry at most one name annotation. *)if!module_seenthenError.multiple_modulediagnostics~locationelsemodule_seen:=true|_->())attributesin(* Validate and process the attributes on one imported declaration: a
name-only [#[import = "name"]] override and [#[export]] (a re-export) are
meaningful there. *)letcheck_import_decl(decl:(Ast.import_decl,location)annotated)=check_attribute_listdiagnostics~export_ok:true~start_ok:false~module_ok:false~import_ok:true~default_location:decl.infodecl.desc.attributes;(matchList.filter(fun(k,_,_)->k="import")decl.desc.attributeswith|_::(_,value,_)::_->letlocation=matchvaluewithSomev->v.info|None->decl.infoinError.multiple_importdiagnostics~location|_->());(* An imported memory/table still has size limits to validate, the same as
a defined one. *)(matchdecl.desc.kindwith|Import_memory{address_type;limits;page_size_log2;shared}->check_limitsctx~location:decl.info"memory"~sharedaddress_typepage_size_log2limitsmax_memory_size|Import_table{address_type;limits;_}->check_limitsctx~location:decl.info"table"~shared:falseaddress_typeNonelimitsmax_table_size|Import_func_|Import_global_|Import_tag_->());process_attrs~default_name:(Somedecl.desc.id)~location:decl.infodecl.desc.attributesinwalk_fields(funfield->check_attributesdiagnosticsfield;matchfield.descwith|Import{decl;_}->check_import_decldecl|Import_group{decls;_}->List.itercheck_import_decldecls|_->process_attrs~default_name:(field_namefield)~location:field.info(field_attributesfield.desc))fields;let_:_option=letname=Ast.no_loc"<string>"inadd_typectx.diagnosticsctx.type_context[|Ast.no_loc(name,{supertype=None;typ=Array{mut=true;typ=PackedI8};final=true;descriptor=None;describes=None;});|]inletctx={ctxwith(* Only imports are registered at this point; snapshot them as the global
scope visible to table initializers. *)import_globals={ctx.globalswithtbl=Hashtbl.copyctx.globals.tbl};}inletphased_fields=globalsctxfieldsinlettyped_fields=functionsctxphased_fieldsin(* Report module fields — functions and globals — that are defined but never
referenced (the module-level analog of an unused local). A field is exempt
if its name starts with [_], if it is exported or is the start function
(both externally reachable), or if it is an import (an external contract,
not a definition; those are [Fundecl]/[GlobalDecl] and never reach the
arms below). Uses are collected by [Tbl.resolve] as names are looked up
while typing the globals and function bodies above. *)ifwarn_unusedthenbeginletexemptfield=List.exists(fun(k,_,_)->k="export"||k="start")(field_attributesfield)inletunusedtbl(name:ident)=(not(String.lengthname.desc>0&&name.desc.[0]='_'))&¬(Tbl.is_usedtblname.desc)in(* An imported function or global that is never referenced (and not
re-exported) is reported, the same way an unused definition is. *)letcheck_unused_import(decl:(Ast.import_decl,location)annotated)=letexempt=List.exists(fun(k,_,_)->k="export")decl.desc.attributesinifnotexemptthenmatchdecl.desc.kindwith|Import_func_whenunusedctx.functionsdecl.desc.id->Error.unused_importctx.diagnostics~location:decl.desc.id.info"function"decl.desc.id|Import_global_whenunusedctx.globalsdecl.desc.id->Error.unused_importctx.diagnostics~location:decl.desc.id.info"global"decl.desc.id|_->()inwalk_fields(funfield->matchfield.descwith|Func{name;_}when(not(exemptfield.desc))&&unusedctx.functionsname->Error.unused_fieldctx.diagnostics~location:name.info"function"name|Global{name;_}when(not(exemptfield.desc))&&unusedctx.globalsname->Error.unused_fieldctx.diagnostics~location:name.info"global"name|Import{decl;_}->check_unused_importdecl|Import_group{decls;_}->List.itercheck_unused_importdecls|_->())fieldsend;(ctx.type_context.types,(* The cell-annotated tree ([inferred_module_annotation]); [f] resolves it to
storage types for the deferred Wasm/WAT conversion, while the editor reads
the cells directly. A validation-only pass ([~build:false]) runs the
checking above for its diagnostics and discards it. *)ifnotbuildthen[]elsetyped_fields)(* Resolve the inference cells at each node to concrete storage types — the
projection [f] applies before handing the typed tree to the Wasm conversion.
[Unknown]/[Error]/[Collecting] have no concrete type ([None]); a flexible
numeric literal takes its default width. *)letproject_annotation(types,loc)=(Array.map(funty->matchCell.gettywith|Unknown|Error|Collecting_->None|Null->Some(Value(Ref{nullable=true;typ=None_}))|UnknownRef->Some(Value(Ref{nullable=false;typ=None_}))|Number->Some(ValueI32)|Int8->Some(PackedI8)|Int16->Some(PackedI16)|Int->Some(ValueI32)|LargeInt->Some(ValueI64)|Float->Some(ValueF64)|Valtype{typ;_}->Some(Valuetyp))types,loc)letproject_module(m:inferred_module_annotationAst.module_):typed_module_annotationAst.module_=List.map(funf->{fwithdesc=Ast_utils.map_modulefieldproject_annotationf.desc})m(* Conditional annotations denote mutually-exclusive branches, so they are
type-checked by exploring every reachable configuration (as the WAT validator
does), rather than checking both branches as if they coexisted. *)(*** Conditional compilation and entry points ***)letrecinstr_has_conditional(i:(_instr_desc,_)annotated)=letany=List.existsinstr_has_conditionalinletopt=Option.fold~none:false~some:instr_has_conditionalinmatchi.descwith|If_annotation_->true|Block{block;_}|Loop{block;_}|TryTable{block;_}->anyblock.desc|While{cond;step;block;_}->instr_has_conditionalcond||Option.fold~none:false~some:instr_has_conditionalstep||anyblock.desc|If{cond;if_block;else_block;_}->instr_has_conditionalcond||anyif_block.desc||Option.fold~none:false~some:(funb->anyb.desc)else_block|Try{block;catches;catch_all;_}->anyblock.desc||List.exists(fun(_,l)->anyl.desc)catches||Option.fold~none:false~some:(funb->anyb.desc)catch_all|TryCatch{block;arms;_}->anyblock.desc||List.exists(funa->anya.arm_body.desc)arms|Sequencel->anyl|ArrayFixed(_,l)->anyl|Dispatch{index;arms;_}->instr_has_conditionalindex||List.exists(fun(_,body)->anybody.desc)arms|Match{scrutinee;arms;default}->instr_has_conditionalscrutinee||List.exists(fun(_,body)->anybody.desc)arms||anydefault.desc|ContBind(_,_,l)|Suspend(_,l)|Resume(_,_,l)|ResumeThrow(_,_,_,l)|ResumeThrowRef(_,_,l)|Switch(_,_,l)|Throw(_,l)->anyl|Call(a,l)|TailCall(a,l)->instr_has_conditionala||anyl(* A punned field ([None]) is a [Get] and carries no conditional. *)|Struct(_,l)->List.exists(fun(_,i)->Option.fold~none:false~some:instr_has_conditionali)l|StructDesc(d,l)->instr_has_conditionald||List.exists(fun(_,i)->Option.fold~none:false~some:instr_has_conditionali)l|CastDesc(a,_,b)|Br_on_cast_desc_eq(_,_,a,b)|Br_on_cast_desc_eq_fail(_,_,a,b)|BinOp(_,a,b)|Array(_,a,b)|ArraySegment(_,_,a,b)|ArrayGet(a,b)|StructSet(a,_,b)->instr_has_conditionala||instr_has_conditionalb|ArraySet(a,b,c)|Select(a,b,c)->instr_has_conditionala||instr_has_conditionalb||instr_has_conditionalc|Set(_,_,i)|Tee(_,i)|Labelled(_,i)|Cast(i,_)|Test(i,_)|NonNulli|UnOp(_,i)|StructGet(i,_)|GetDescriptori|StructDefaultDesci|ArrayDefault(_,i)|Br_if(_,i)|Hinted(_,i)|On(i,_)|Br_table(_,i)|Br_on_null(_,i)|Br_on_non_null(_,i)|Br_on_cast(_,_,i)|Br_on_cast_fail(_,_,i)|ThrowRefi|ContNew(_,i)->instr_has_conditionali|Let(_,i)|Br(_,i)|Returni->opti|Unreachable|Nop|Hole|Null|Get_|Path_|Char_|String_|Int_|Float_|StructDefault_->falseletfield_has_conditional(f:(_modulefield,_)annotated)=matchf.descwith|Conditional_->true|Func{body=_,instrs;_}->List.existsinstr_has_conditionalinstrs|Global{def;_}->instr_has_conditionaldef|_->false(* Resolve every conditional against the assumption [asm], inlining the selected
branch to produce a conditional-free module (groups are kept and recursed
into). For an undetermined conditional, select [then], [enqueue] the [else]
configuration, and [record] the chosen literal. *)letspecialize_fieldsenvdiagnostics~enqueue~recordasm0fields=letmoduleS=Wax_wasm.Cond_solverin(* Resolve one conditional and return both the specialized branch and the
assumption that holds afterwards. Each branch is taken only if it is
reachable under [asm] (its conjunction with the branch condition is
satisfiable); an unreachable branch is pruned, so we never explore an
infeasible configuration. The surviving assumption is threaded into the
following siblings, so e.g. once [cond1] forces [$wasi], a sibling
[#[if(not wasi)]] has its [@then] pruned. *)letchooseasmcond~location~then_branch~else_branch=letc=S.of_condenvdiagnostics~locationcondinletthen_asm=S.and_asmcandelse_asm=S.and_asm(S.not_c)inifnot(S.is_satisfiablethen_asm)then(record(S.not_c);(else_branchelse_asm,else_asm))elseifnot(S.is_satisfiableelse_asm)then(recordc;(then_branchthen_asm,then_asm))else(enqueueelse_asm;recordc;(then_branchthen_asm,then_asm))in(* Instruction-level specializer: resolve each [If_annotation] by splicing the
selected branch into the enclosing list; recurse into every sub-instruction
and nested block body. [sone] is for single-instruction positions, where an
[If_annotation] cannot appear (it is statement-only). *)letrecsinstrsasml=matchlwith|[]->[]|i::rest->letinstrs,asm=sinstrasmiininstrs@sinstrsasmrestandsinstrasm(i:(_instr_desc,_)annotated)=matchi.descwith|If_annotation{cond;then_body;else_body}->chooseasmcond~location:i.info~then_branch:(funasm'->sinstrsasm'then_body.desc)~else_branch:(funasm'->matchelse_bodywithSomee->sinstrsasm'e.desc|None->[])|desc->([{iwithdesc=sdescasmdesc}],asm)andsoneasmi=matchsinstrasmiwith[x],_->x|_->assertfalseandsdescasm(desc:_instr_desc):_instr_desc=matchdescwith|Block{label;typ;block}->Block{label;typ;block={blockwithdesc=sinstrsasmblock.desc}}|Loop{label;typ;block}->Loop{label;typ;block={blockwithdesc=sinstrsasmblock.desc}}|While{label;cond;step;block}->While{label;cond=soneasmcond;step=Option.map(soneasm)step;block={blockwithdesc=sinstrsasmblock.desc};}|If{label;typ;cond;if_block;else_block}->If{label;typ;cond=soneasmcond;if_block={if_blockwithdesc=sinstrsasmif_block.desc};else_block=Option.map(funb->{bwithdesc=sinstrsasmb.desc})else_block;}|TryTable{label;typ;catches;block}->TryTable{label;typ;catches;block={blockwithdesc=sinstrsasmblock.desc};}|Try{label;typ;block;catches;catch_all}->Try{label;typ;block={blockwithdesc=sinstrsasmblock.desc};catches=List.map(fun(t,l)->(t,{lwithdesc=sinstrsasml.desc}))catches;catch_all=Option.map(funb->{bwithdesc=sinstrsasmb.desc})catch_all;}|TryCatch{label;typ;block;arms}->TryCatch{label;typ;block={blockwithdesc=sinstrsasmblock.desc};arms=List.map(funa->{awitharm_body={a.arm_bodywithdesc=sinstrsasma.arm_body.desc};})arms;}|Set(idx,op,v)->Set(idx,op,soneasmv)|Tee(idx,v)->Tee(idx,soneasmv)|Labelled(l,v)->Labelled(l,soneasmv)|Call(t,args)->Call(soneasmt,List.map(soneasm)args)|TailCall(t,args)->TailCall(soneasmt,List.map(soneasm)args)|Cast(v,t)->Cast(soneasmv,t)|CastDesc(v,t,d)->CastDesc(soneasmv,t,soneasmd)|Test(v,t)->Test(soneasmv,t)|NonNullv->NonNull(soneasmv)|Struct(idx,fields)->Struct(idx,List.map(fun(i,v)->(i,Option.map(soneasm)v))fields)|StructDesc(d,fields)->StructDesc(soneasmd,List.map(fun(i,v)->(i,Option.map(soneasm)v))fields)|StructDefaultDescd->StructDefaultDesc(soneasmd)|StructGet(v,idx)->StructGet(soneasmv,idx)|GetDescriptorv->GetDescriptor(soneasmv)|StructSet(v,idx,w)->StructSet(soneasmv,idx,soneasmw)|Array(idx,a,b)->Array(idx,soneasma,soneasmb)|ArrayDefault(idx,v)->ArrayDefault(idx,soneasmv)|ArrayFixed(idx,l)->ArrayFixed(idx,List.map(soneasm)l)|ArraySegment(idx,d,a,b)->ArraySegment(idx,d,soneasma,soneasmb)|ArrayGet(a,b)->ArrayGet(soneasma,soneasmb)|ArraySet(a,b,c)->ArraySet(soneasma,soneasmb,soneasmc)|BinOp(op,a,b)->BinOp(op,soneasma,soneasmb)|UnOp(op,v)->UnOp(op,soneasmv)|Let(bs,body)->Let(bs,Option.map(soneasm)body)|Br(l,v)->Br(l,Option.map(soneasm)v)|Br_if(l,v)->Br_if(l,soneasmv)|Hinted(h,v)->Hinted(h,soneasmv)|On(v,h)->On(soneasmv,h)|Br_table(ls,v)->Br_table(ls,soneasmv)|Dispatch{index;cases;default;arms}->Dispatch{index=soneasmindex;cases;default;arms=List.map(fun(l,body)->(l,{bodywithdesc=sinstrsasmbody.desc}))arms;}|Match{scrutinee;arms;default}->Match{scrutinee=soneasmscrutinee;arms=List.map(fun(pat,body)->(pat,{bodywithdesc=sinstrsasmbody.desc}))arms;default={defaultwithdesc=sinstrsasmdefault.desc};}|Br_on_null(l,v)->Br_on_null(l,soneasmv)|Br_on_non_null(l,v)->Br_on_non_null(l,soneasmv)|Br_on_cast(l,t,v)->Br_on_cast(l,t,soneasmv)|Br_on_cast_fail(l,t,v)->Br_on_cast_fail(l,t,soneasmv)|Br_on_cast_desc_eq(l,t,v,d)->Br_on_cast_desc_eq(l,t,soneasmv,soneasmd)|Br_on_cast_desc_eq_fail(l,t,v,d)->Br_on_cast_desc_eq_fail(l,t,soneasmv,soneasmd)|Throw(idx,v)->Throw(idx,List.map(soneasm)v)|ThrowRefv->ThrowRef(soneasmv)|ContNew(ct,v)->ContNew(ct,soneasmv)|ContBind(src,dst,l)->ContBind(src,dst,List.map(soneasm)l)|Suspend(tag,l)->Suspend(tag,List.map(soneasm)l)|Resume(ct,h,l)->Resume(ct,h,List.map(soneasm)l)|ResumeThrow(ct,tag,h,l)->ResumeThrow(ct,tag,h,List.map(soneasm)l)|ResumeThrowRef(ct,h,l)->ResumeThrowRef(ct,h,List.map(soneasm)l)|Switch(ct,tag,l)->Switch(ct,tag,List.map(soneasm)l)|Returnv->Return(Option.map(soneasm)v)|Sequencel->Sequence(sinstrsasml)|Select(c,t,e)->Select(soneasmc,soneasmt,soneasme)|If_annotation_->assertfalse(* handled in [sinstr] *)|(Unreachable|Nop|Hole|Null|Get_|Path_|Char_|String_|Int_|Float_|StructDefault_)asx->xin(* Resolve each per-attribute [if <cond>] guard against the configuration.
A guard gates the presence of just this export, so it partitions the space
exactly like an [#[if]] block: [choose] prunes the export in configurations
where the guard cannot hold and enqueues the complementary configuration
where it does not, threading the surviving assumption into later fields. The
guard itself is dropped -- in each explored configuration the export is
unconditionally present or absent. *)letsattrsasm(attrs:attributes):attributes*S.t=List.fold_left(fun(acc,asm)(k,v,guard)->matchguardwith|None->(acc@[(k,v,None)],asm)|Someg->letkept,asm=chooseasmg.desc~location:g.info~then_branch:(fun_->[(k,v,None)])~else_branch:(fun_->[])in(acc@kept,asm))([],asm)attrsinletsdeclasm(decl:(Ast.import_decl,location)annotated)=letattributes,asm=sattrsasmdecl.desc.attributesin({declwithdesc={decl.descwithattributes}},asm)inletrecsdeclsasm=function|[]->([],asm)|d::rest->letd,asm=sdeclasmdinletds,asm=sdeclsasmrestin(d::ds,asm)inletrecsfieldsasmfl=matchflwith|[]->[]|f::rest->letfields,asm=sfieldasmfinfields@sfieldsasmrestandsfieldasm(f:(_modulefield,_)annotated)=letsaattributes=sattrsasmattributesinmatchf.descwith|Conditional{cond;then_fields;else_fields}->chooseasmcond~location:f.info~then_branch:(funasm'->sfieldsasm'then_fields.desc)~else_branch:(funasm'->matchelse_fieldswithSomee->sfieldsasm'e.desc|None->[])|Func({body=lbl,instrs;attributes;_}asr)->letattributes,asm=saattributesin([{fwithdesc=Func{rwithbody=(lbl,sinstrsasminstrs);attributes};};],asm)|Global({def;attributes;_}asg)->letattributes,asm=saattributesin([{fwithdesc=Global{gwithdef=soneasmdef;attributes}}],asm)|Tag({attributes;_}asr)->letattributes,asm=saattributesin([{fwithdesc=Tag{rwithattributes}}],asm)|Memory({attributes;_}asr)->letattributes,asm=saattributesin([{fwithdesc=Memory{rwithattributes}}],asm)|Table({attributes;_}asr)->letattributes,asm=saattributesin([{fwithdesc=Table{rwithattributes}}],asm)|Import{module_;decl}->letdecl,asm=sdeclasmdeclin([{fwithdesc=Import{module_;decl}}],asm)|Import_group{module_;decls}->letdecls,asm=sdeclsasmdeclsin([{fwithdesc=Import_group{module_;decls}}],asm)|Module_annotationattrs->letattrs,asm=saattrsin([{fwithdesc=Module_annotationattrs}],asm)|Type_|Data_|Elem_->([f],asm)insfieldsasm0fields(* [let] bindings are not allowed inside a conditional branch: branches are
transparent and mutually exclusive, so a binding declared in one would leak
past the conditional and clash with the other branch. *)letreccheck_let_in_conditionalsdiagnostics(i:(_instr_desc,_)annotated)=(matchi.descwith|If_annotation{then_body;else_body;_}->letcheck_branch=List.iter(fun(s:(_instr_desc,_)annotated)->matchs.descwith(* Only a binding that introduces a name would leak; an anonymous
[Let] ([_ = e], a drop) binds nothing, so it is allowed. *)|Let(bindings,_)whenList.exists(fun(name,_)->Option.is_somename)bindings->Error.let_in_conditionaldiagnostics~location:s.info|_->())incheck_branchthen_body.desc;Option.iter(funb->check_branchb.desc)else_body|_->());List.iter(check_let_in_conditionalsdiagnostics)(Ast_utils.sub_instrsi)letcheck_let_bindingsdiagnosticsfields=Ast_utils.iter_fields(fun(field:(_modulefield,_)annotated)->matchfield.descwith|Func{body=_,instrs;_}->List.iter(check_let_in_conditionalsdiagnostics)instrs|Global{def;_}->check_let_in_conditionalsdiagnosticsdef|_->())fields(* Apply the module's [#![feature = "…"]] declarations to [features]: each
declared feature is enabled, in union with the command-line configuration —
unless the command line explicitly disabled it, which is a conflict reported
once, at the attribute. Runs at the entry points, before anything consults
[is_enabled]. Only top-level attributes count: the attribute states a fact
about the whole module, so it takes no guard and lives at the top of the
file. An ill-shaped value (no string) is reported by [check_attribute_list]
and ignored here. *)letapply_declared_featuresdiagnosticsfeaturesfields=List.iter(fun(field:(_modulefield,_)annotated)->matchfield.descwith|Module_annotationattrs->List.iter(fun(key,value,_)->match(key,value)with|"feature",Some{desc=String(_,name);info=location}->(matchWax_utils.Feature.of_namenamewith|None->Error.unknown_featurediagnostics~locationname|Somefeature->ifWax_utils.Feature.explicitly_disabledfeaturesfeaturethenError.feature_conflictdiagnostics~locationfeature;(* Enable it even on a conflict: the error has been
reported once, at the attribute; without this every
gated construct below would error too. *)Wax_utils.Feature.declarefeaturesfeature)|_->())attrs|_->())fields(* Check every reachable configuration of a conditional module: each is
specialized to be conditional-free and typed independently, so a diagnostic
is reported once with the assumption under which it is reachable. Only the
diagnostics matter here, so the typed module is not built ([~build:false]). *)letcheck_configurations~warn_unused~features~simplifydiagnosticsfields=Wax_wasm.Cond_explore.check_alldiagnostics?truncation_location:(matchfieldswithhd::_->Somehd.info|[]->None)~explain:(funenvc->Wax_wasm.Cond_solver.explainenv~style:`Waxc)~specialize:(funenvasm~enqueue~record->specialize_fieldsenvdiagnostics~enqueue~recordasmfields)~check:(functxm->ignore(type_configuration~build:false~warn_unused~features~simplifyctxm:_*_))()letf_infer?(simplify=false)?(warn_unused=false)?(resolve_links=None)?(pun_spans=None)?(member_completions=None)?(features=Wax_utils.Feature.default())diagnosticsfields=Wax_utils.Debug.timed"type-check"@@fun()->apply_declared_featuresdiagnosticsfeaturesfields;check_let_bindingsdiagnosticsfields;ifnot(List.existsfield_has_conditionalfields)thentype_configuration~warn_unused~resolve_links~pun_spans~member_completions~features~simplifydiagnosticsfieldselsebegincheck_configurations~warn_unused~features~simplifydiagnosticsfields;(* Build the typed module (consumed only by the deferred WAT conversion and
the editor; validation-only paths use [check] and never reach here) by
typing the module with conditionals preserved. [type_configuration]
resolves names per branch (condition-aware tables), so each branch is
typed under its own assumption. Diagnostics are discarded —
[check_configurations] above did the real checking; references are
recorded here, off the single tree the editor consumes. *)type_configuration~resolve_links~pun_spans~member_completions~features~simplify(Wax_utils.Diagnostic.collector())fieldsendletf?(simplify=false)?(warn_unused=false)?(features=Wax_utils.Feature.default())diagnosticsfields=lettypes,typed=f_infer~simplify~warn_unused~featuresdiagnosticsfieldsin(types,project_moduletyped)letcheck?(warn_unused=false)?(features=Wax_utils.Feature.default())diagnosticsfields=Wax_utils.Debug.timed"type-check"@@fun()->apply_declared_featuresdiagnosticsfeaturesfields;check_let_bindingsdiagnosticsfields;ifnot(List.existsfield_has_conditionalfields)thenignore(type_configuration~build:false~warn_unused~features~simplify:falsediagnosticsfields:_*_)elsecheck_configurations~warn_unused~features~simplify:falsediagnosticsfieldsleterase_typesm=List.map(funm->{mwithdesc=Ast_utils.map_modulefieldsndm.desc})m