12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970797179727973797479757976797779787979798079817982798379847985798679877988798979907991799279937994799579967997799879998000800180028003800480058006800780088009801080118012801380148015801680178018801980208021802280238024802580268027802880298030803180328033803480358036803780388039804080418042804380448045804680478048804980508051805280538054805580568057805880598060806180628063806480658066806780688069807080718072807380748075807680778078807980808081808280838084808580868087808880898090809180928093809480958096809780988099810081018102810381048105810681078108810981108111811281138114811581168117811881198120812181228123812481258126812781288129813081318132813381348135813681378138813981408141814281438144814581468147814881498150815181528153815481558156815781588159816081618162816381648165816681678168816981708171817281738174817581768177817881798180818181828183818481858186818781888189819081918192819381948195819681978198819982008201820282038204820582068207820882098210821182128213821482158216821782188219822082218222822382248225822682278228822982308231823282338234823582368237823882398240824182428243824482458246824782488249825082518252825382548255825682578258825982608261826282638264826582668267826882698270827182728273827482758276827782788279828082818282828382848285828682878288828982908291829282938294829582968297829882998300830183028303830483058306830783088309831083118312831383148315831683178318831983208321832283238324832583268327832883298330833183328333833483358336833783388339834083418342834383448345834683478348834983508351835283538354835583568357835883598360836183628363836483658366836783688369837083718372837383748375837683778378837983808381838283838384838583868387838883898390839183928393839483958396839783988399840084018402840384048405840684078408840984108411841284138414841584168417841884198420842184228423842484258426842784288429843084318432843384348435843684378438843984408441844284438444844584468447844884498450845184528453845484558456845784588459846084618462846384648465846684678468846984708471847284738474847584768477847884798480848184828483848484858486848784888489849084918492849384948495849684978498849985008501850285038504850585068507850885098510851185128513851485158516851785188519852085218522852385248525852685278528852985308531853285338534853585368537853885398540854185428543854485458546854785488549855085518552855385548555855685578558855985608561856285638564856585668567856885698570857185728573857485758576857785788579858085818582858385848585858685878588858985908591859285938594859585968597859885998600860186028603860486058606860786088609861086118612861386148615861686178618861986208621862286238624862586268627862886298630863186328633863486358636863786388639864086418642864386448645864686478648864986508651865286538654865586568657865886598660866186628663866486658666866786688669867086718672867386748675867686778678867986808681868286838684868586868687868886898690869186928693869486958696869786988699870087018702870387048705870687078708870987108711871287138714871587168717871887198720872187228723872487258726872787288729873087318732873387348735873687378738873987408741874287438744874587468747874887498750875187528753875487558756875787588759876087618762876387648765876687678768876987708771877287738774877587768777877887798780878187828783878487858786878787888789879087918792879387948795879687978798879988008801880288038804880588068807880888098810881188128813881488158816881788188819882088218822882388248825882688278828882988308831883288338834883588368837883888398840884188428843884488458846884788488849885088518852885388548855885688578858885988608861886288638864886588668867886888698870887188728873887488758876887788788879888088818882888388848885888688878888888988908891889288938894889588968897889888998900890189028903890489058906890789088909891089118912891389148915891689178918891989208921892289238924892589268927892889298930893189328933893489358936893789388939894089418942894389448945894689478948894989508951895289538954895589568957895889598960896189628963896489658966896789688969897089718972897389748975897689778978897989808981898289838984898589868987898889898990899189928993899489958996899789988999900090019002900390049005900690079008900990109011901290139014901590169017901890199020902190229023902490259026902790289029903090319032903390349035903690379038903990409041904290439044904590469047904890499050905190529053905490559056905790589059906090619062906390649065906690679068906990709071907290739074907590769077907890799080908190829083908490859086908790889089909090919092909390949095909690979098909991009101910291039104910591069107910891099110911191129113911491159116911791189119912091219122912391249125912691279128912991309131913291339134913591369137913891399140914191429143914491459146914791489149915091519152915391549155915691579158915991609161916291639164916591669167916891699170917191729173917491759176917791789179918091819182918391849185918691879188918991909191919291939194919591969197919891999200920192029203920492059206920792089209921092119212921392149215921692179218921992209221922292239224922592269227922892299230923192329233923492359236923792389239924092419242924392449245924692479248924992509251925292539254925592569257925892599260926192629263926492659266926792689269927092719272927392749275927692779278927992809281928292839284928592869287928892899290929192929293929492959296929792989299930093019302930393049305930693079308930993109311931293139314931593169317931893199320932193229323932493259326932793289329933093319332933393349335933693379338933993409341934293439344934593469347934893499350935193529353935493559356935793589359936093619362936393649365936693679368936993709371937293739374937593769377937893799380938193829383938493859386938793889389939093919392939393949395939693979398939994009401940294039404940594069407940894099410941194129413941494159416941794189419942094219422942394249425942694279428942994309431943294339434943594369437943894399440944194429443944494459446944794489449945094519452945394549455945694579458945994609461946294639464946594669467946894699470947194729473947494759476947794789479948094819482948394849485948694879488948994909491949294939494949594969497949894999500950195029503950495059506950795089509951095119512951395149515951695179518951995209521952295239524952595269527952895299530953195329533953495359536953795389539954095419542954395449545954695479548954995509551955295539554955595569557955895599560956195629563956495659566956795689569957095719572957395749575957695779578957995809581958295839584958595869587958895899590959195929593959495959596959795989599960096019602960396049605960696079608960996109611961296139614961596169617961896199620962196229623962496259626962796289629963096319632963396349635963696379638963996409641964296439644964596469647964896499650965196529653965496559656965796589659966096619662966396649665966696679668966996709671967296739674967596769677967896799680968196829683968496859686968796889689969096919692969396949695969696979698969997009701970297039704970597069707970897099710971197129713971497159716971797189719972097219722972397249725972697279728972997309731973297339734973597369737973897399740974197429743974497459746974797489749975097519752975397549755975697579758975997609761976297639764976597669767976897699770977197729773977497759776977797789779978097819782978397849785978697879788978997909791979297939794979597969797979897999800980198029803980498059806980798089809981098119812981398149815981698179818981998209821982298239824982598269827982898299830983198329833983498359836983798389839984098419842984398449845984698479848984998509851985298539854985598569857985898599860986198629863986498659866986798689869987098719872987398749875987698779878987998809881988298839884988598869887988898899890989198929893989498959896989798989899990099019902990399049905990699079908990999109911991299139914991599169917991899199920992199229923992499259926992799289929993099319932993399349935993699379938993999409941994299439944994599469947994899499950995199529953995499559956995799589959996099619962996399649965996699679968996999709971997299739974997599769977997899799980998199829983998499859986998799889989999099919992999399949995999699979998999910000100011000210003100041000510006100071000810009100101001110012100131001410015100161001710018100191002010021100221002310024100251002610027100281002910030100311003210033100341003510036100371003810039100401004110042100431004410045100461004710048100491005010051100521005310054100551005610057100581005910060100611006210063100641006510066100671006810069100701007110072100731007410075100761007710078100791008010081100821008310084100851008610087100881008910090100911009210093100941009510096100971009810099101001010110102101031010410105101061010710108101091011010111101121011310114101151011610117101181011910120101211012210123101241012510126101271012810129101301013110132101331013410135101361013710138101391014010141101421014310144101451014610147101481014910150101511015210153101541015510156101571015810159101601016110162101631016410165101661016710168101691017010171101721017310174101751017610177101781017910180101811018210183101841018510186101871018810189101901019110192101931019410195101961019710198101991020010201102021020310204102051020610207102081020910210102111021210213102141021510216102171021810219102201022110222102231022410225102261022710228102291023010231102321023310234102351023610237102381023910240102411024210243102441024510246102471024810249102501025110252102531025410255102561025710258102591026010261102621026310264102651026610267102681026910270102711027210273102741027510276102771027810279102801028110282102831028410285102861028710288102891029010291102921029310294102951029610297102981029910300103011030210303103041030510306103071030810309103101031110312103131031410315103161031710318103191032010321103221032310324103251032610327103281032910330103311033210333103341033510336103371033810339103401034110342103431034410345103461034710348103491035010351103521035310354103551035610357103581035910360103611036210363103641036510366103671036810369103701037110372103731037410375103761037710378103791038010381103821038310384103851038610387103881038910390103911039210393103941039510396103971039810399104001040110402104031040410405104061040710408104091041010411104121041310414104151041610417104181041910420104211042210423104241042510426104271042810429104301043110432104331043410435104361043710438104391044010441104421044310444104451044610447104481044910450104511045210453104541045510456104571045810459104601046110462104631046410465104661046710468104691047010471104721047310474104751047610477104781047910480104811048210483104841048510486104871048810489104901049110492104931049410495104961049710498104991050010501105021050310504105051050610507105081050910510105111051210513105141051510516105171051810519105201052110522105231052410525105261052710528105291053010531105321053310534105351053610537105381053910540105411054210543105441054510546105471054810549105501055110552105531055410555105561055710558105591056010561105621056310564105651056610567105681056910570105711057210573105741057510576105771057810579105801058110582105831058410585105861058710588105891059010591105921059310594105951059610597105981059910600106011060210603106041060510606106071060810609106101061110612106131061410615106161061710618106191062010621106221062310624106251062610627106281062910630106311063210633106341063510636106371063810639106401064110642106431064410645106461064710648106491065010651106521065310654106551065610657106581065910660106611066210663106641066510666106671066810669106701067110672106731067410675106761067710678106791068010681106821068310684106851068610687106881068910690106911069210693106941069510696106971069810699107001070110702107031070410705107061070710708107091071010711107121071310714107151071610717107181071910720107211072210723107241072510726107271072810729107301073110732107331073410735107361073710738107391074010741107421074310744107451074610747107481074910750107511075210753107541075510756107571075810759107601076110762107631076410765107661076710768107691077010771107721077310774107751077610777107781077910780107811078210783107841078510786107871078810789107901079110792107931079410795107961079710798107991080010801108021080310804108051080610807108081080910810108111081210813108141081510816108171081810819108201082110822108231082410825108261082710828108291083010831108321083310834108351083610837108381083910840108411084210843108441084510846108471084810849108501085110852108531085410855108561085710858108591086010861108621086310864108651086610867108681086910870108711087210873108741087510876108771087810879108801088110882108831088410885108861088710888108891089010891108921089310894108951089610897108981089910900109011090210903109041090510906109071090810909109101091110912109131091410915109161091710918109191092010921109221092310924109251092610927109281092910930109311093210933109341093510936109371093810939109401094110942109431094410945109461094710948109491095010951109521095310954109551095610957109581095910960109611096210963109641096510966109671096810969109701097110972109731097410975109761097710978109791098010981109821098310984109851098610987109881098910990109911099210993109941099510996109971099810999110001100111002110031100411005110061100711008110091101011011110121101311014110151101611017110181101911020110211102211023110241102511026110271102811029110301103111032110331103411035110361103711038110391104011041110421104311044110451104611047110481104911050110511105211053110541105511056110571105811059110601106111062110631106411065110661106711068110691107011071110721107311074110751107611077110781107911080110811108211083110841108511086110871108811089110901109111092110931109411095110961109711098110991110011101111021110311104111051110611107111081110911110111111111211113111141111511116111171111811119111201112111122111231112411125111261112711128111291113011131111321113311134111351113611137111381113911140111411114211143111441114511146111471114811149111501115111152111531115411155111561115711158111591116011161111621116311164111651116611167111681116911170111711117211173111741117511176111771117811179111801118111182111831118411185111861118711188111891119011191111921119311194111951119611197111981119911200112011120211203112041120511206112071120811209112101121111212112131121411215112161121711218112191122011221112221122311224112251122611227112281122911230112311123211233112341123511236112371123811239112401124111242112431124411245112461124711248112491125011251112521125311254112551125611257112581125911260112611126211263112641126511266112671126811269112701127111272112731127411275112761127711278112791128011281112821128311284112851128611287112881128911290112911129211293112941129511296112971129811299113001130111302113031130411305113061130711308113091131011311113121131311314113151131611317113181131911320113211132211323113241132511326113271132811329113301133111332113331133411335113361133711338113391134011341113421134311344113451134611347113481134911350113511135211353113541135511356113571135811359113601136111362113631136411365113661136711368113691137011371113721137311374113751137611377113781137911380113811138211383113841138511386113871138811389113901139111392113931139411395113961139711398113991140011401114021140311404114051140611407114081140911410114111141211413114141141511416114171141811419114201142111422114231142411425114261142711428114291143011431114321143311434114351143611437114381143911440114411144211443114441144511446114471144811449114501145111452114531145411455114561145711458114591146011461114621146311464114651146611467114681146911470114711147211473114741147511476114771147811479114801148111482114831148411485114861148711488114891149011491114921149311494114951149611497114981149911500115011150211503115041150511506115071150811509115101151111512115131151411515115161151711518115191152011521115221152311524115251152611527115281152911530115311153211533115341153511536115371153811539115401154111542115431154411545115461154711548115491155011551115521155311554115551155611557115581155911560115611156211563115641156511566115671156811569115701157111572115731157411575115761157711578115791158011581115821158311584115851158611587115881158911590115911159211593115941159511596115971159811599116001160111602116031160411605116061160711608116091161011611116121161311614116151161611617116181161911620116211162211623116241162511626116271162811629116301163111632116331163411635116361163711638116391164011641116421164311644116451164611647116481164911650116511165211653116541165511656116571165811659116601166111662116631166411665116661166711668116691167011671116721167311674116751167611677116781167911680116811168211683116841168511686116871168811689116901169111692116931169411695116961169711698116991170011701117021170311704117051170611707117081170911710117111171211713117141171511716117171171811719117201172111722117231172411725117261172711728117291173011731117321173311734117351173611737117381173911740117411174211743117441174511746117471174811749117501175111752117531175411755117561175711758117591176011761117621176311764117651176611767117681176911770117711177211773117741177511776117771177811779117801178111782117831178411785117861178711788117891179011791117921179311794117951179611797117981179911800118011180211803118041180511806118071180811809118101181111812118131181411815118161181711818118191182011821118221182311824118251182611827118281182911830118311183211833118341183511836118371183811839118401184111842118431184411845118461184711848118491185011851118521185311854118551185611857118581185911860118611186211863118641186511866118671186811869118701187111872118731187411875118761187711878118791188011881118821188311884118851188611887118881188911890118911189211893118941189511896118971189811899119001190111902119031190411905119061190711908119091191011911119121191311914119151191611917119181191911920119211192211923119241192511926119271192811929119301193111932119331193411935119361193711938119391194011941119421194311944119451194611947119481194911950119511195211953119541195511956119571195811959119601196111962119631196411965119661196711968119691197011971119721197311974119751197611977119781197911980119811198211983119841198511986119871198811989119901199111992119931199411995119961199711998119991200012001120021200312004120051200612007120081200912010120111201212013120141201512016120171201812019120201202112022120231202412025120261202712028120291203012031120321203312034120351203612037120381203912040120411204212043120441204512046120471204812049120501205112052120531205412055120561205712058120591206012061120621206312064120651206612067120681206912070120711207212073120741207512076120771207812079120801208112082120831208412085120861208712088120891209012091120921209312094120951209612097120981209912100121011210212103121041210512106121071210812109121101211112112121131211412115121161211712118121191212012121121221212312124121251212612127121281212912130121311213212133121341213512136121371213812139121401214112142121431214412145121461214712148121491215012151121521215312154121551215612157121581215912160121611216212163121641216512166121671216812169121701217112172121731217412175121761217712178121791218012181121821218312184121851218612187121881218912190121911219212193121941219512196121971219812199122001220112202122031220412205122061220712208122091221012211122121221312214122151221612217122181221912220122211222212223122241222512226122271222812229122301223112232122331223412235122361223712238122391224012241122421224312244122451224612247122481224912250122511225212253122541225512256122571225812259122601226112262122631226412265122661226712268122691227012271122721227312274122751227612277122781227912280122811228212283122841228512286122871228812289122901229112292122931229412295122961229712298122991230012301123021230312304123051230612307123081230912310123111231212313123141231512316123171231812319123201232112322123231232412325123261232712328123291233012331123321233312334123351233612337123381233912340123411234212343123441234512346123471234812349123501235112352123531235412355123561235712358123591236012361123621236312364123651236612367123681236912370123711237212373123741237512376123771237812379123801238112382123831238412385123861238712388123891239012391123921239312394123951239612397123981239912400124011240212403124041240512406124071240812409124101241112412124131241412415124161241712418124191242012421124221242312424124251242612427124281242912430124311243212433124341243512436124371243812439124401244112442124431244412445124461244712448124491245012451124521245312454124551245612457124581245912460124611246212463124641246512466124671246812469124701247112472124731247412475124761247712478124791248012481124821248312484124851248612487124881248912490124911249212493124941249512496124971249812499125001250112502125031250412505125061250712508125091251012511125121251312514125151251612517125181251912520125211252212523125241252512526125271252812529125301253112532125331253412535125361253712538125391254012541125421254312544125451254612547125481254912550125511255212553125541255512556125571255812559125601256112562125631256412565125661256712568125691257012571125721257312574125751257612577125781257912580125811258212583125841258512586125871258812589125901259112592125931259412595125961259712598125991260012601126021260312604126051260612607126081260912610126111261212613126141261512616126171261812619126201262112622126231262412625126261262712628126291263012631126321263312634126351263612637126381263912640126411264212643126441264512646126471264812649126501265112652126531265412655126561265712658126591266012661126621266312664126651266612667126681266912670126711267212673126741267512676126771267812679126801268112682126831268412685126861268712688126891269012691126921269312694126951269612697126981269912700127011270212703127041270512706127071270812709127101271112712127131271412715127161271712718127191272012721127221272312724127251272612727127281272912730127311273212733127341273512736127371273812739127401274112742127431274412745127461274712748127491275012751127521275312754127551275612757127581275912760127611276212763127641276512766127671276812769127701277112772127731277412775127761277712778127791278012781127821278312784127851278612787127881278912790127911279212793127941279512796127971279812799128001280112802128031280412805128061280712808128091281012811128121281312814128151281612817128181281912820128211282212823128241282512826128271282812829128301283112832128331283412835128361283712838128391284012841128421284312844128451284612847128481284912850128511285212853128541285512856128571285812859128601286112862128631286412865128661286712868128691287012871128721287312874128751287612877128781287912880128811288212883128841288512886128871288812889128901289112892128931289412895128961289712898128991290012901129021290312904129051290612907129081290912910129111291212913129141291512916129171291812919129201292112922129231292412925129261292712928129291293012931129321293312934129351293612937129381293912940129411294212943129441294512946129471294812949129501295112952129531295412955129561295712958129591296012961129621296312964129651296612967129681296912970129711297212973129741297512976129771297812979129801298112982129831298412985129861298712988129891299012991129921299312994129951299612997129981299913000130011300213003130041300513006130071300813009130101301113012130131301413015130161301713018130191302013021130221302313024130251302613027130281302913030130311303213033130341303513036130371303813039130401304113042130431304413045130461304713048130491305013051130521305313054130551305613057130581305913060130611306213063130641306513066130671306813069130701307113072130731307413075130761307713078130791308013081130821308313084130851308613087130881308913090130911309213093130941309513096130971309813099131001310113102131031310413105131061310713108131091311013111131121311313114131151311613117131181311913120131211312213123131241312513126131271312813129131301313113132131331313413135131361313713138131391314013141131421314313144131451314613147131481314913150131511315213153131541315513156131571315813159131601316113162131631316413165131661316713168131691317013171131721317313174131751317613177131781317913180131811318213183131841318513186131871318813189131901319113192131931319413195131961319713198131991320013201132021320313204132051320613207132081320913210132111321213213132141321513216132171321813219132201322113222132231322413225132261322713228132291323013231132321323313234132351323613237132381323913240132411324213243132441324513246132471324813249132501325113252132531325413255132561325713258132591326013261132621326313264132651326613267132681326913270132711327213273132741327513276132771327813279132801328113282132831328413285132861328713288132891329013291132921329313294132951329613297132981329913300133011330213303133041330513306133071330813309133101331113312133131331413315133161331713318133191332013321133221332313324133251332613327133281332913330133311333213333133341333513336133371333813339133401334113342133431334413345133461334713348133491335013351133521335313354133551335613357133581335913360133611336213363133641336513366133671336813369133701337113372133731337413375133761337713378133791338013381133821338313384133851338613387133881338913390133911339213393133941339513396133971339813399134001340113402134031340413405134061340713408134091341013411134121341313414134151341613417134181341913420134211342213423134241342513426134271342813429134301343113432134331343413435134361343713438134391344013441134421344313444134451344613447134481344913450134511345213453134541345513456134571345813459134601346113462134631346413465134661346713468134691347013471134721347313474134751347613477134781347913480134811348213483134841348513486134871348813489134901349113492134931349413495134961349713498134991350013501135021350313504135051350613507openAstmoduleCond=Wax_wasm.Cond_solvermoduleNz=Wax_wasm.Types.Normalized(* The Printer-native output printers, captured before [open Infer] shadows
[Output] with its [Format]-based wrappers. *)modulePrinter_output=OutputopenInferopenTyping_envtypetyped_module_annotation=Typing_env.typed_module_annotationtypeinferred_module_annotation=Typing_env.inferred_module_annotationtypehover_target=Typing_env.hover_target=|Value_typeofinferred_valtype|Type_defofsubtypetypereference=Typing_env.reference={use:Ast.location;definitions:Ast.locationlist;hover:hover_targetoption;}(*** Diagnostics ***)letloc_first_charloc=letloc_start=loc.loc_startin{locwithloc_end={loc_startwithpos_cnum=loc_start.pos_cnum+1}}letloc_last_charloc=letloc_end=loc.loc_endin{locwithloc_start={loc_endwithpos_cnum=loc_end.pos_cnum-1}}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.Wax_utils.Ast.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]/[short_stack] cascade guards. *)letwarn?warning?universal?hint?edit?relatedcontext~locationmessage=ifnot(Wax_utils.Diagnostic.in_recoverycontext)thenDiagnostic.reportcontext~location~severity:Warning?warning?universal?hint?edit?related~message()(* A local declared by a [let] but never read. Prefix its name with [_] to
silence the warning — offered as a quick fix by a zero-width [edit] that
inserts the [_] at the name's start. *)letunused_localcontext~locationx=warn~warning:Wax_utils.Warning.Unused_local~universal:truecontext~location~edit:{Wax_utils.Diagnostic.edit_location={locationwithloc_end=location.loc_start};new_text="_";}(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 field 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.")(* A [let]-declared (mutable) global that is never assigned, so it could be a
[const]. An import (whose mutability is part of the linking contract) and an
exported global (which the host may assign) are exempt, as is a name starting
with [_]. *)letunnecessary_mutcontext~locationx=warn~warning:Wax_utils.Warning.Unnecessary_mut~universal:truecontext~location~hint:(text"Declare it with 'const' instead of 'let'.")(text"The global"++namex++text"is mutable but is never assigned.")(* 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 "Trojan Source" bidirectional control character in a string (an
export/import name, a string literal, a data segment, a feature or
conditional string) that can make the source read differently than it
runs. *)letconfusable_unicodecontext~locationu=warn~warning:Wax_utils.Warning.Confusable_unicode~universal:truecontext~location(text(Printf.sprintf"This string contains a bidirectional control character (U+%04X) \
that can make the displayed text read differently than it runs."(Uchar.to_intu)))(* A cast/test whose operand already has the target type. A redundant cast (not
a test, whose value cannot be dropped safely) carries an [edit] that removes
it, so the editor can offer a quick fix. *)letredundant_cast?editcontext~location~is_test=warn?edit~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. As a quick fix, offer deleting the whole ['name:]
prefix: [location] already spans ['name] (the leading quote through the
name), and a short source scan extends it over the trailing [:] and the
same-line whitespace up to the keyword. Bails (no edit) if the [:] is not
found where expected. *)letunused_labelcontext~locationx=letedit=matchWax_utils.Diagnostic.sourcecontextwith|None->None|Somesrc->letn=String.lengthsrcinletis_wsc=c=' '||c='\t'inleti=reflocation.loc_end.Lexing.pos_cnuminwhile!i<n&&is_wssrc.[!i]doincridone;if!i<n&&src.[!i]=':'then(incri;while!i<n&&is_wssrc.[!i]doincridone;Some{Wax_utils.Diagnostic.edit_location={locationwithloc_end={location.loc_endwithpos_cnum=!i};};new_text="";})elseNoneinwarn?edit~warning:Wax_utils.Warning.Unused_label~universal:truecontext~location(text"The label"++namex++text"is never used.")(* 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 conditional-annotation branch no configuration selects: its condition
cannot hold together with the enclosing conditionals' (or at all). *)letdead_branchcontext~location~side=warn~warning:Wax_utils.Warning.Dead_code~universal:truecontext~location(text(ifsidethen"The then-branch of this conditional is unreachable:"else"The else-branch of this conditional is unreachable:")++text"no configuration selects it.")letshort_stackcontextkind~location~actual~expected=(* 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. The reporters ([pop], and [report_missing_hole]/[with_holes]
for a hole) 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. *)letvalues=matchkindwith|`Input->"argument(s)"|`Output->"returned value(s)"|`Holes->"value(s)"inifnot(Wax_utils.Diagnostic.in_recoverycontext)thenreportcontext~location(text"Expecting "++Message.intexpected++textvalues++text"from the stack, but there are"++Message.intactual^^text".")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:(loc_last_charlocation)(text"Some values remain on the stack:"^^Message.rawrender^^text".")(* 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.")letexpected_structcontext~location=reportcontext~location(text"Expected struct.")letexpected_arraycontext~location=reportcontext~location(text"Expected array.")letexpected_funccontext~location=reportcontext~location(text"Expected function.")(* 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 packed ([i8]/[i16]) array or field read whose signedness was never
resolved: WebAssembly has no unsigned-by-default read of a packed value
([array.get]/[struct.get] on one is invalid — only the [_s]/[_u] forms
exist), so the value cannot take the [i32] default an omitted annotation
would give it. The Wasm validator's mirror of this is its
"cannot be used on packed arrays" rejection; without this the typer
accepted the binding and the conversion produced a module that failed
its own validation (a wax-mutation-fuzzer finding). *)letpacked_read_needs_signednesscontext~location=reportcontext~location(text"This value is read from a packed (i8/i16) array or field; specify \
the sign extension with 'as i32_s' or 'as i32_u'.")(* 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~location~currentty'ty=reportcontext~location(text"Argument"++Message.intcurrent++text"should have type"++typty++text"but has type"++typty'^^text".")letnot_an_expressioncontext~locationn=(* Suppress in error-recovery mode, like [short_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".")(* [expected_at] points a secondary caret at whatever imposes [expected] when
that is elsewhere and would otherwise be guesswork — a [br_table] target
label, whose block's result type is what the value must satisfy, and which
tells apart two reports on the same value from differently-typed targets.
The validator's [instruction_type_mismatch] labels its own the same way. *)letexpression_type_mismatch?expected_atcontext~location~provided~expected=reportcontext~location?related:(Option.map(funloc->[{Wax_utils.Diagnostic.location=loc;message=text"expected here";};])expected_at)(text"This expression has type"++typprovided++text"but is expected to have type"++typexpected^^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.")letoperand_count_mismatchcontext~location~expected~provided=reportcontext~location(text"This instruction expects"++Message.intexpected++text"operand(s) but"++Message.intprovided++text"was/were provided.")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 "++kw"if"++text" must produce a value and so requires an "++kw"else"++text" 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~prev_loc=reportcontext~location~related:[{Wax_utils.Diagnostic.location=prev_loc;message=text"other start function here";};](text"A module can have at most one start function.")letmultiple_modulecontext~location~prev_loc=reportcontext~location~related:[{Wax_utils.Diagnostic.location=prev_loc;message=text"other name annotation here";};](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~prev_loc=reportcontext~location~related:[{Wax_utils.Diagnostic.location=prev_loc;message=text"other import-name annotation here";};](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 "++kw"open"^^text".")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_finality_mismatchcontext~location=reportcontext~location((text"A type and its descriptor must both be"++kw"open")^^text", or neither.")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".")letfeature_declaration_in_conditionalcontext~location=reportcontext~location(text"A"++kw"#![feature = \"…\"]"++text"declaration states a fact about the whole module and must appear \
at the top level, not inside a conditional.")letmodule_name_in_conditionalcontext~location=reportcontext~location(text"A"++kw"#![module = \"…\"]"++text"name annotation applies to the whole module and must appear at the \
top level, not inside a conditional.")(* 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 "++kw"br_if"++text" 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.")(* Two targets of the same [br_table] take different numbers of values;
[first] is the reference target (the first bound one). *)letbranch_arity_mismatchcontext~location~first_locfirst~expected~provided=reportcontext~location~related:[{Wax_utils.Diagnostic.location=first_loc;message=text"other branch target here";};](text"This branch target expects"++Message.intprovided++text"value(s), while branch target"++namefirst++text"expects"++Message.intexpected++text"value(s).")letname_already_boundcontext~location~prev_lockindx=reportcontext~location~related:[{Wax_utils.Diagnostic.location=prev_loc;message=text"previously bound here";};](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.")(* Compilation-hints proposal, mirroring [Validation]'s checks on the same hint:
a call-target list only means something for a call whose callee is not already
known, and the listed frequencies must leave room for the unlisted ones. *)letcall_targets_direct_callcontext~location=reportcontext~location(text"A call-target hint may only prefix an indirect call. This callee is \
a function, so the call is direct and its target is already known.")(* Compilation-hints proposal: an optimization priority is stated only alongside
a compilation one, so either spelling of it needs [#[priority]] too. *)letpriority_requiredcontext~locationwhich=reportcontext~location(text"The"++kw("#["^which^"]")++text"attribute needs a"++kw"#[priority = n]"^^text".")letconflicting_optimizationcontext~location~prev_loc=reportcontext~location~related:[{Wax_utils.Diagnostic.location=prev_loc;message=text"the other one here";};](text"A function states at most one optimization priority:"++kw"#[optimization = n]"++text"or"++kw"#[run_once]"^^text", not both.")letcall_targets_over_100context~location~total=reportcontext~location(((text"The call-target frequencies add up to"++Message.inttotal)^^text"%, more than 100%.")++text"A shortfall is how the hint says other, unlisted targets take the \
remainder.")letbefore_holecontext~location=reportcontext~location((text"This expression occurs before a hole "++kw"_")^^text".")lethole_in_control_operandcontext~location~construct~role=reportcontext~location(text"A hole"++kw"_"++text"cannot be used as a"++kwconstruct++textrole^^text".")letduplicated_fieldcontext~location~prev_locx=reportcontext~location~related:[{Wax_utils.Diagnostic.location=prev_loc;message=text"other field here";};]((text"Several fields have the same name"++namex)^^text".")letsplice_without_supertypecontext~location=reportcontext~location(kw".."++text" requires a supertype to inherit fields from (write "++kw"type t: super = { .., ... }"++text").")letsplice_non_structcontext~locationx=reportcontext~location(kw".."++text" can only inherit fields from a struct supertype;"++namex++text"is not a struct.")letduplicated_parametercontext~location~prev_locx=reportcontext~location~related:[{Wax_utils.Diagnostic.location=prev_loc;message=text"other parameter here";};]((text"Several parameters have the same name"++namex)^^text".")letconstant_expression_requiredcontext~location=reportcontext~location(text"Only constant expressions are allowed here.")letinteger_literal_requiredcontext~location=reportcontext~location(text"Only integer literals are allowed here.")letnumber_literal_requiredcontext~location=reportcontext~location(text"Only number literals 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.")letbecome_on_stack_switchingcontext~location=reportcontext~location(kw"become"++text"cannot apply to a stack-switching operation.")letunknown_argument_labelcontext~location~suggestionsx=report?hint:(did_you_meansuggestions)context~location((text"Unknown argument label"++namex)^^text".")letduplicate_argument_labelcontext~location~prev_locx=reportcontext~location~related:[{Wax_utils.Diagnostic.location=prev_loc;message=text"previously given here";};](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~location~prev_locname=reportcontext~location~related:[{Wax_utils.Diagnostic.location=prev_loc;message=text"previously exported here";};]((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"Expected reference.")letdispatch_duplicate_armcontext~location~prev_locx=reportcontext~location~related:[{Wax_utils.Diagnostic.location=prev_loc;message=text"other arm here";};]((text"This dispatch has several cases named"++namex)^^text".")(* The Wasm-to-Wax conversion recorded the type a node's value must have (see
[Ast.instr]'s [expected]) and this run resolved another one, so the Wax about
to be printed would recompile at a different opcode width — a silent
miscompile. [pinnable] says whether a grounding pin could have corrected it
(the value is a flexible literal tree), which is exactly what the conversion's
default [`Repair] mode does instead of reporting; see {!reconcile_widths}:
- pinnable: this report IS the [--debug width-check] mode of a repair, so it
is purely a fault of the tool — the pin [From_wasm] should have placed.
- not pinnable: the value's type is fixed by its context, so no cast can
correct it (one would convert the value) — either the input is invalid, or
the conversion is wrong. Reported in BOTH modes.
The expression is spelled out because a synthesized dead-code node has no real
source span to point at. Both wordings share the leading phrase, which the
fuzz harness greps for (oracle 5c, drop-width.sh). *)letwidth_invariant_violatedcontext~location~inferred~required~pinnableexpr=reportcontext~location~hint:(text(ifpinnablethen"This is an internal invariant of the WebAssembly-to-Wax \
conversion, not a problem with the input; without '--debug \
width-check' the conversion repairs it by pinning the \
expression."else"Either this WebAssembly is invalid — a binary input is trusted, \
never validated, so check it with 'wax check' — or the \
WebAssembly-to-Wax conversion is wrong. A cast here would \
convert the value rather than pin it, so the conversion does \
not repair it."))(text"Decompiler width invariant violated for"++kwexpr++(matchinferredwith|Someinferred->text"recompiling it would infer"++kw(Infer.Output.valtype_stringinferred)|None->text"recompiling it would leave its type unresolved")++text"but the WebAssembly it came from requires"++kw(Infer.Output.valtype_stringrequired)^^text(ifpinnablethen"."else": its type is fixed by context, not defaulted, so no pin can \
correct it."))end(*** Symbol tables and namespaces ***)moduleNamespace=structincludeTyping_env.Namespaceletmake?(links=None)()={tbl=Hashtbl.create16;links}letregisterdnskind(x:Ast.ident)=(matchHashtbl.find_optns.tblx.descwith|Some(kind',prev_loc)->Error.name_already_boundd~location:x.info~prev_lockind'x|None->());Hashtbl.replacens.tblx.desc(kind,x.info)letexistsdns(x:Ast.ident)=matchHashtbl.find_optns.tblx.descwith|Some(kind',prev_loc)->Error.name_already_boundd~location:x.info~prev_lockind'x;true|None->falseendmoduleTbl=structincludeTyping_env.Tblletmake?(hover=fun_->None)~currentnamespacekind={kind;namespace;tbl=Hashtbl.create16;used=Hashtbl.create16;current;hover;}(* Every context that referenced [name]: [None] for a module-level one (a
root), [Some f] for the body of function [f]. One entry per reference. The
unused-field lint asks this rather than a plain "is it referenced", so a
reference made from dead code can be discounted. *)letreferrersenvname=Hashtbl.find_allenv.usedname(* [f referrer name] for every reference recorded in this table. *)letiter_referencesenvf=Hashtbl.iter(funnamereferrer->freferrername)env.used(* Record a reference to [name] from [referrer], for a use that names no
declaration syntactically (see [canonical_type_references]). Same one
entry per (name, origin) pair as [resolve]. *)letmark_referenceenvnamereferrer=ifreferrer<>Ignored&¬(List.memreferrer(Hashtbl.find_allenv.usedname))thenHashtbl.addenv.usednamereferrer(* [f name value] for every declaration in this table. *)letiter_entriesenvf=Hashtbl.iterfenv.tblletadddenv(x:Ast.ident)v=Namespace.registerdenv.namespaceenv.kindx;Hashtbl.replaceenv.tblx.descvletexistsdenvx=Namespace.existsdenv.namespacex(* Replace a name's binding (added by [add]); used by [add_type] to fix up
rectype indices in place. *)letoverrideenv(x:Ast.ident)v=Hashtbl.replaceenv.tblx.descv(* Look a reference up. 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]). *)letresolveenv(x:Ast.ident)=letr=Hashtbl.find_optenv.tblx.descin(matchrwith|Somev->(* One entry per (name, origin) pair, not per reference: a helper called
a thousand times from one function is one edge. Keeps [used] bounded by
the reference graph rather than by the instruction count. *)mark_referenceenvx.desc!(env.current);(* Link this use to the definition of the name; [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(matchHashtbl.find_optenv.namespace.tblx.descwith|Some(_,loc)->[loc]|None->[])|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=resolveenvx(* Look up a name's binding without counting it as a reference. Used by the
typer's own internal lookups (e.g. a function resolving its own declared
type while it is being checked) that must not mark the name used, so the
unused-field lint still fires on a defined-but-unreferenced function. *)letfind_no_markenv(x:Ast.ident)=Hashtbl.find_optenv.tblx.descletiterenvf=Hashtbl.iterfenv.tbl(* Drop a binding (the temporary [add_type] placeholder). *)letremoveenv(x:Ast.ident)=Hashtbl.removeenv.tblx.descendtypetype_table=Typing_env.types(* The type tables the lowering resolves names through: [current] is the table
in force — the primary run's at the top level and, inside a conditional
branch, the table of the run that owns it (see [in_branch]), since a name
declared in two branches has a different definition in each. *)typetypes={mutablecurrent:type_table;by_branch:(int*int*bool,type_table)Hashtbl.t;}letget_type_definitiondtypesnm=Option.mapsnd(Tbl.finddtypes.currentnm)letin_branchtypes(location:location)sidef=matchHashtbl.find_opttypes.by_branch(location.loc_start.pos_cnum,location.loc_end.pos_cnum,side)with|None->f()|Somet->letsaved=types.currentintypes.current<-t;Fun.protect~finally:(fun()->types.current<-saved)f(* 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_refd(ctx:type_context)name=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->matchparam_namepwith|None->s|Somename->(matchList.assoc_optname.descswith|Someprev_loc->Error.duplicated_parameterd~location:name.info~prev_locname|None->());(name.desc,name.info)::s)[]params:(string*location)list)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(param_typep))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_storagetypedctxtyletcomptyped(ctx:type_context)(ty:comptype):Nz.comptypeoption=matchtywith|Functy->let+@ty=n_functypedctxtyin(Functy:Nz.comptype)|Structfields->let_:(string*location)list=Array.fold_left(funs(field:(ident*fieldtype,location)Ast.annotated)->letname=field_namefieldin(matchList.assoc_optname.descswith|Someprev_loc->Error.duplicated_fieldd~location:name.info~prev_locname|None->());(name.desc,name.info)::s)[]fieldsinlet+@fields=array_map_opt(funfield->n_fieldtypedctx(field_typefield))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<currentletsubtyped(ctx:type_context)current{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). Drop the offending supertype so
the subtype chain stays acyclic and later subtype queries terminate. *)ifdefined_beforecurrentrthenSomerelse(Error.unbound_named~location:sup.info"type"sup;None)in(* [descriptor]/[describes] may refer mutually within the rec group, so no
declared-before restriction applies. *)letresolve_opt=function|None->SomeNone|Some(idx:Ast.ident)->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}(* Each member's components (its supertype, field and element types, a descriptor
clause) are references made *by that member*, so they only keep their targets
alive if the member itself is: a rec group nothing else names is dead as a
whole, cycle and all. *)letrectyped(ctx:type_context)ty=letouter=!(ctx.types.current)inletr=array_mapi_opt(funielt->ifouter<>Ignoredthenctx.types.current:=From_type(member_nameelt).desc;subtypedctxi(member_typeelt))tyinctx.types.current:=outer;r(* 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_splicesd(ctx:type_context)ty=letexpanded=Array.copytyinArray.iteri(funielt->letname=member_nameeltandsub=member_typeeltinmatchsub.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(member_typeexpanded.(j))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_typed(ctx:type_context)ty=Array.iteri(funielt->letname=member_nameeltandtyp=member_typeeltinifList.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(member_nameelt))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)->((* The descriptor must describe this type back, and share its
finality: an [open] type whose descriptor is final (or the
reverse) could never be extended, since a subtype would need a
descriptor extending a final one. Reported here only, on the
described type, so the reciprocal pair yields a single error. *)matchity.(pos).describeswith|Some(Reco)wheno=i->ifsub.final<>ity.(pos).finalthenError.descriptor_finality_mismatchd~location|_->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=member_nameeltandtyp=member_typeeltin(* Normalization drops a supertype the spec forbids — a forward or self
reference, which is not "declared before" (see [subtype]/
[defined_before]). Drop it from the source type stored here too, so
the source-level walkers ([heap_lub] via [immediate_supertype]) never
follow the cyclic edge and loop; the error was already reported. *)lettyp=matchity.(i).supertypewith|None->{typwithsupertype=None}|Some_->typinTbl.overridectx.typesname(Wax_wasm.Types.Def(Wax_wasm.Types.Id.addi'i),typ))ty;Somei'(*** The module context ***)(* 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 ***)(* The typed form of a conditional branch this run does not select: the source
as written, each node carrying no cells. [f_infer]'s stitching replaces it
with the branch as typed by the run that owns it, so none survives into the
tree a consumer reads. *)letplaceholder_instrsl=List.map(Ast_utils.map_instr(funloc->([||],loc)))lletplaceholder_fieldsfields=List.map(fun(f:(_modulefield,location)annotated)->{fwithdesc=Ast_utils.map_modulefield(funloc->([||],loc))f.desc;})fields(* The [lookup_*_type] family resolves a type NAME to its composite type of the
expected kind. An unbound name is REPORTED (at the reference, with
spell-check suggestions — [Tbl.find]); silently returning [None] here let a
construction literal naming an unbound type be accepted and lowered to
[unreachable]. A bound name of the wrong kind gets the kind-specific
error. *)letlookup_func_type?locationctxname=let*@ty=Tbl.findctx.diagnosticsctx.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.findctx.diagnosticsctx.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:Ast.ident),_)->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;hints=Wax_wasm.Hints.none;expected=Unset;}letlookup_array_type?locationctxname=let*@ty=Tbl.findctx.diagnosticsctx.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.findctx.diagnosticsctx.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?location?(pin=true)ctxtyty'=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?location~pinctxtyd|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;_};_}->(* Settle the flexible value AT the concrete expected type — by SETTING
its own cell, never by union-ing it into the expected cell (as the
[Unknown]/[UnknownRef] pins below already do). The expected side is
often one of the SHARED base cells ([i32_cell] and kin), whose safety
argument is that their contents never change; a union would alias the
value's tree onto the shared cell, and a later [Cell.set] on that tree
— the width reconciliation's repair pin re-grounding a mis-captured
dead-code hole tree — would then rewrite the shared cell and retype
every node in the module holding it (a backing-scan (@if) grid
finding: one repaired [i64.add] turned every [ref.is_null] result
[i64]). *)Cell.settyity';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,(Valtype_ast)->(* A polymorphic value (a hole taken off the [Unreachable] stack of dead
code) genuinely takes whatever concrete type consumes it: pin it, so
[To_wasm] sees a definite type instead of [None] — which it can only
lower as [unreachable], dropping the enclosing instruction. The
universal-bottom counterpart of the [UnknownRef] reference pin below,
and of [join_value_types]'s pin of an [Unknown] block exit. Not pinned
under [~pin:false] (a [br_table], whose one value is checked against
several targets of legitimately different types — pinning it to the
first would wrongly reject the rest); it stays [Unknown] and [To_wasm]
passes the hole through the polymorphic stack unchanged. *)ifpinthenCell.settyt;true|(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]. Not
pinned under [~pin:false] for the same [br_table] reason as [Unknown]
above: a bottom reference reaching targets of different reference types
(a [&func] and a [&t] label) is a subtype of each, so pinning it to the
first-checked one would reject the others. *)ifpinthenCell.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. [&extern] is the same with
[extern.convert_any] appended, as [i64 as &extern] below. *)|LargeInt,Ref{typ=I31|Extern;_}->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]; [i64 as &extern]
wraps to [i32] first, as [i64 as &i31] above. *)|Valtype{internal=I32|I64;_},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(* An operand already known to be a REFERENCE cannot be cast to a numeric type,
exactly as the concrete-reference case above says — [UnknownRef] is "some
reference, type not yet resolved", not "unknown whether a reference". Left in
the blanket-accepting arm below, the check passed here and [to_wasm] was later
handed a ref->float cast it has no lowering for, hitting its [assert false]
rather than reporting anything (a wax-mutation-fuzzer crash). A genuinely
unresolved [Unknown] hole must stay polymorphic and is still accepted: it is a
dead-code stack value that unifies with whatever its block needs. *)|UnknownRef,(I32|I64|F32|F64|V128)->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|`I64)->(* As for a concrete any-hierarchy reference above ([null] is a valid
[&?i31]): [ref.cast (ref i31)] + [i31.get], widened for [i64] — traps
at runtime, like the reference case. Pin the operand to [&?any] so
[to_wasm] takes that path. *)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)(* An any-hierarchy reference (or [null]) to [i64] is accepted above
([i31.get] + extend); to a float it is rejected — no reference-to-float
conversion exists. *)|((Null|Valtype{internal=Ref{typ=Type_|Exact_|None_|Struct|Array|I31|Eq|Any;_;};_;}),(`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|Poisoned(* The poison of an already-reported failure whose stack effect is
unknown (a producer that did not resolve, an underflow): pops yield
[Error] silently — unlike [Unreachable] (dead code), whose pops yield
[Unknown] and re-default, and which the dead-code lint keys on. *)|Consoflocationoption*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"|Poisoned->Wax_utils.Printer.spacepp.SP.printer();SP.print_styledppWax_utils.Colors.Keyword"poisoned"|Cons(_,ty,st)->Wax_utils.Printer.spacepp.SP.printer();output_inferred_type_styledppty;output_stackppstletprint_stackst=Wax_utils.Printer.run_err(funp->letpp=Wax_utils.Styled_printer.create~printer:p~theme:Wax_utils.Colors.no_color~trivia:(Wax_utils.Trivia.empty())()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);hints=Wax_wasm.Hints.none;expected=Unset;}(* Pop the top operand's type. An [Unreachable] (polymorphic) stack yields a
fresh [Unknown] and consumes nothing; [Empty] is a genuine stack underflow.
No diagnostic is emitted here: the placeholder cell is recorded in
[ctx.missing_holes] with the counts, so the hole that ends up consuming it
reports the underflow at its own location ([report_missing_hole]) — sparing
the caller any knowledge of how the pending values are distributed — and
[with_holes] covers a placeholder that recovery drops. An underflow turns
the stack unreachable (mirroring the Wasm validator's [pop_any]), so one
missing value is tracked once rather than once per subsequent pop. *)letpop_anyctxbatchcurrentexpectedst=matchstwith|Unreachable->(st,Cell.makeUnknown)|Poisoned->letcell=Cell.makeErrorin(* Poisoned by this very run's underflow (below): this value is missing
too, so track it under the same batch — the report lands on the FIRST
hole without a value. A stack poisoned before this run keeps plain,
silent placeholders. *)(match!batchwith|Someb->ctx.missing_holes:=(cell,b)::!(ctx.missing_holes)|None->());(st,cell)|Cons(_,ty,r)->(r,ty)|Empty->letcell=Cell.makeErrorinletb={hole_reported=false;hole_actual=current;hole_expected=expected;}inbatch:=Someb;ctx.missing_holes:=(cell,b)::!(ctx.missing_holes);(Poisoned,cell)(* Pop [count] pending values, returning them together with the underflow
batch, if one occurred (for [with_holes]'s fallback report). *)letpop_manyctxcount=letbatch=refNoneinletrecloopnaccu=ifn=countthenreturn(accu,batch)elselet*ty=pop_anyctxbatchncountinloop(n+1)(ty::accu)inloop0[]letpopctxkind~locationcurrentexpectedtyst=matchstwith|Unreachable|Poisoned->(st,())|Cons(loc_opt,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)thenmatchloc_optwith|Someloc->Error.expression_type_mismatchctx.diagnostics~location:loc~provided:ty'~expected:ty|None->Error.type_mismatchctx.diagnostics~location~currentty'ty);(r,()))|Empty->Error.short_stackctx.diagnosticskind~location:(matchkindwith|`Input->loc_first_charlocation|`Holes->location|`Output->loc_last_charlocation)~actual:(expected-current-1)~expected;(* As in [pop_any]: an underflow poisons the stack, so one missing value
is reported once, not once per remaining pop. *)(Poisoned,())letpop_argsctxkind~locationargs=letlen=Array.lengthargsinletreclooppos=ifpos=0thenreturn()elseletpos=pos-1inlet*()=popctxkind~locationposlenargs.(pos)inloopposinlooplen(* Pushing an [Error] value poisons the whole stack ([Unreachable]): the failed
producer's true arity is unknown (a call that did not resolve may have
produced any number of values), so later consumers must absorb any count
silently — reporting an underflow there would anchor a derived error away
from the original fault. This is the push-side twin of [with_empty_stack]'s
rule that an [Error] anywhere on the stack suppresses the leftover report,
and of [pop_any]'s underflow-turns-unreachable. *)letpushloctyst=matchCell.gettywith|Error->((matchstwithUnreachable->Unreachable|_->Poisoned),())|_->(Cons(loc,ty,st),())letpush_results~locresults=letlen=Array.lengthresultsinletloc=iflen=1thenSomelocelseNoneinletrecloopi=ifi=lenthenreturn()elselet*()=pushlocresults.(i)inloop(i+1)inloop0typeempty_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=matchlocwithNone->locs|Someloc->loc::locsinscanhas_errorlocsst|Empty|Unreachable|Poisoned->(has_error,List.revlocs)in(matchstwith|Empty|Unreachable|Poisoned->()|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(fun(l:Ast.ident)->fl.desc)l)ctx.control_types)label.Annot.descinError.unbound_namectx.diagnostics~location:label.info~suggestions"label"label;ctx.unresolved_label:=true;[||]|(Somelabel',res)::_whenlabel.desc=label'.Annot.desc->ctx.used_labels:=IntSet.addlabel'.info.loc_start.pos_cnum!(ctx.used_labels);record_referencectx.resolve_linkslabel.info[label'.info];res|_::rem->findremlabelinfindctx.control_typeslabel(* Whether [label] resolves to an in-scope control label. Unlike
[branch_target], reports nothing and records no use; used to tell an unbound
label (already diagnosed) from a legitimately void target when both present as
[[||]]. *)letlabel_in_scopectx(label:Ast.ident)=List.exists(fun((l:Ast.identoption),_)->matchlwithSomel'->l'.desc=label.desc|None->false)ctx.control_types(* 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*Ast.location|Globalofbool(* mutable *)*inferred_valtypeoption|Func_refofWax_wasm.Types.Id.t*string*bool|Poisoned(* A function whose signature failed to resolve: bound, already reported
at its definition, reads as [Error] with no further report. *)|Unboundletresolve_variablectx(idx:Ast.ident)=matchStringMap.find_optidx.descctx.localswith|Some(ty,def)->record_reference~hover:(hover_of_valtypety)ctx.resolve_linksidx.info[def];Local(ty,def)|None->(matchTbl.find_optctx.globalsidxwith|Some(mut,ty)->Global(mut,ty)|None->(matchTbl.find_optctx.functionsidxwith|Some(Some(ty,ty',exact))->Func_ref(ty,ty',exact)|SomeNone->Poisoned|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_receiverctx(name:Ast.ident)=(not(StringMap.memname.descctx.locals))&&Tbl.find_optctx.memoriesname<>Nonelettable_receiverctx(name:Ast.ident)=(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_receiverctx(name:Ast.ident)=(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)(* The typed node for [i]: its hints ride along, being advisory metadata the
typer neither reads nor changes, and so does its [expected] type — the
decompiler's record of the type this node must have, which the width check
compares against the cells recorded here. *)letreturn_statement(i:locationinstr)(desc:(inferred_typeCell.tarray*location)instr_desc)(ty:_array)st=(st,{desc;info=((ty:_array),i.info);hints=i.hints;expected=i.expected;})letreturn_expressionidescty=return_statementidesc[|ty|]letexpression_typectx(i:_Ast.instr)=lettyp,location=i.infoinmatchtypwith|[|ty|]->ty|_->(* Once per rendered diagnostic: several consumers may query the same node,
and nested value-less expressions share a start column (e.g. the inner
and outer of [a.m().m()], both value-less), so a full-span key would let
two identical "returns N values" errors print at the same location. Key
on the rendered position (start column) and the reported count — exactly
what the diagnostic shows — so a genuine second error with a different
count still surfaces (see the [not_expression_reported] field). *)letkey=(location.loc_start.Lexing.pos_cnum,Array.lengthtyp)inifnot(Hashtbl.memctx.not_expression_reportedkey)then(Hashtbl.addctx.not_expression_reportedkey();(* An unresolved label in this function makes the value shape
unreliable (see [unresolved_label]); stay quiet then. *)ifnot!(ctx.unresolved_label)thenError.not_an_expressionctx.diagnostics~location(Array.lengthtyp));Cell.makeErrorletcheck_subtype?(pin=true)?expected_atctx~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~location~pinctxty'ty)thenError.expression_type_mismatch?expected_atctx.diagnostics~location~provided:ty'~expected:ty(* [~pin:false] checks the subtypes without resolving a polymorphic left-hand
value against the (single) right-hand type — for a [br_table], whose one set
of values is checked against every target label, so pinning a bottom value to
the first target's type would wrongly reject a later, differently-typed one
(see {!subtype}). *)letcheck_subtypes?(pin=true)?expected_atctx~locationtypes'types=ifArray.lengthtypes'<>Array.lengthtypesthenError.value_count_mismatchctx.diagnostics~location~expected:(Array.lengthtypes)~provided:(Array.lengthtypes')elseArray.iter2(funty'ty->check_subtype~pin?expected_atctx~locationty'ty)types'typesletcheck_typectxity=letty'=expression_typectxiinletok=subtypectxty'tyinifnotokthenError.expression_type_mismatchctx.diagnostics~location:(sndi.info)~provided:ty'~expected:ty(* [standalone_valtype] is context-independent (its only reference result is the
built-in [None_] bottom), so the typer's ctx-threading call sites delegate to
the pure {!Typing_env.standalone_valtype}; the [ctx] is kept for call-site
uniformity. *)letstandalone_valtype_ctxty=Typing_env.standalone_valtypety(* 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 rests on the {!reinfer} value [check_instruction] returns
alongside each checked node: what an unannotated binding ([let x = <node>])
would re-infer the node to be, standalone. The binding/construct site then
drops the annotation precisely when [simplify] is on and that re-inference
already equals the annotation ([reinfer_needed] says it is not load-bearing).
The pieces, by where the annotation lives:
- a scalar value vs. its annotation: the leaf [check_instruction] arm returns
[Typ] of the value's own snapshot; [reinfer_needed]/[annotation_needed]
compare its standalone type to the expected one;
- control constructs ([if]/[?:]/block/loop/try) join their sub-nodes'
re-inference ([join_reinfer]) so a nested tail reports its own type rather
than being read through a cell the expected type flowed into;
- a block/loop/try result type of its own: [block_keep_bool] /
[block_keep_reinfer], with [context_block_typ] / [finalize_inferred] filling
an omitted result from context or dropping a redundant declared one;
- [drop_supertype] is the one relaxation (an immutable binding may drop a
mere-supertype annotation), applied at the binding site.
--------------------------------------------------------------------------- *)(* 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.
The keep decision now does exactly compare against what omitting the
annotation re-infers to: the [Cast] arm of [check_instruction] reports [Typ]
of the floating [&?none] for an elided [null], and the leaf arm the same for a
bare one, so the ordinary [reinfer_needed] comparison keeps the annotation
without a special case at the binding site. This predicate remains for the two
places that still key on the syntactic shape rather than the re-inferred type:
[classify_trailing] (routing a trailing [null] through the result) and the
[Cast] arm's own guard (deciding whether the cast can be elided). *)letrecis_null_initializer(i:_instr)=matchi.descwith|Null->true|Cast(e,_)->is_null_initializere|_->false(* Whether an operand's PRINTED form re-parses type-ADAPTIVELY: with no type of
its own, it takes the hierarchy the enclosing context suggests. A bare [null]
and a [Hole] (a dead-code stack value, or one reconnecting to a bottom null)
are the base cases; a [select]/[?:] is adaptive when both its arms are (its
result type is its arms'), and likewise an [if]/[do]-block through its
tail(s). It is the criterion behind [restore_inner] (below): only an adaptive
operand collapses a cross-hierarchy [extern.convert_any]/[any.convert_extern]
into a plain [ref.null] when its inner any/extern cast is dropped, so only for
it must the inner cast be re-grounded. An anchored operand (a concrete
reference value) fixes the convert regardless; re-grounding it would be inert
but noisy, so it is excluded. A [Cast] node never reaches here (the caller
guards on the inner having been dropped, so the operand is no longer a cast).
Extensible: further adaptive tails (a [Let] body's tail, a labelled block)
could be added if a sweep surfaces them. *)letrecreparse_adaptive(i:_instr)=matchi.descwith|Null|Hole->true|Select(_,a,b)->reparse_adaptivea&&reparse_adaptiveb(* A block ([do]/[if]) coming from [From_wasm] carries an explicit result type,
which pins it, so it never re-parses adaptively and needs no case here. *)|_->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.
The one exception: a *bottom* reference ([&?none] and friends), the standalone
type of a bare [null]. Narrowing an annotation down to it is not a useful
subtype — it drops all type information and changes the emitted [ref.null $t]
to [ref.null none] — so a [null] whose annotation is a strict supertype keeps
it regardless of [drop_supertype], matching the documented "a null initializer
keeps its annotation" rule. Equality still drops ([const g: &?none = null]). *)letannotation_needed?(drop_supertype=false)ctx(standalone:inferred_valtypeoption)expected=letis_bottom_ref(v:inferred_valtype)=matchv.typwith|Ref{typ=None_|NoFunc|NoExtern|NoExn|NoCont;_}->true|_->falseinmatch(standalone,Cell.getexpected)with|Somev,Valtypeb->ifdrop_supertype&¬(is_bottom_refv)thennot(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(* Whether a block's declared result type equals the type its context already
pins, so re-parsing recovers it from that context and the annotation can be
dropped. Shared by [context_block_typ] (the [simplify] rewrite) and the
quick-fix suggestion. *)letblock_result_redundantctxtyp~expected~result_cell=typ.results<>[||]&&match(standalone_valtypectxexpected,standalone_valtypectxresult_cell)with|Somea,Someb->valtype_equalctxab|_->false(* 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(* Report a read of the not-yet-initialized local [idx], or — while a trailing
operand is being typed out of emission order — defer it into the innermost
active collector, to be re-checked at that operand's emission slot (see
[type_trailing_operand]). Both the [Get] arm and a deferred read's re-check go
through here, so a re-check that still fails under an outer deferral re-defers
rather than reports. *)letreport_uninitializedctxidx=matchctx.deferred_uninitwith|collector::_->collector:=idx::!collector|[]->Error.uninitialized_localctx.diagnostics~location:idx.infoidx(* 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]. *)(* Whether the value's own printed form is a packed AGGREGATE read — an
array/struct access on an [i8]/[i16] element or field. Only those require an
explicit signedness ([as i32_s]/[as i32_u], see the language docs: "no
implicit widening"); a narrow MEMORY load ([mem.load8], the atomic loads)
carries the same [Int8]/[Int16] cell but legitimately defaults to the
unsigned read when the cast is omitted, so the CELL alone cannot make the
distinction. Shallow on purpose: a [Labelled] wrapper is looked through, an
exotic join that keeps a packed type is left to the compiled module's own
validation. *)letrecpacked_aggregate_source(i:_instr)=matchi.descwith|ArrayGet_|StructGet_->true|Labelled(_,e)->packed_aggregate_sourcee|_->falseletbind_let_value?initctx~locationresult_ty(name,typ)=matchtypwith|Sometyp->(* The type the value would take on its own, captured before
[check_subtype] constrains it. *)letstandalone=standalone_valtypectxresult_tyin(* Whether the annotation equals what the value would infer on its own, so
it is redundant. Returned to the caller so it can offer a quick fix; the
binding itself is dropped only under [simplify]. *)letredundant=Option.value~default:false(let+@ity=internalize_valtypectxtypincheck_subtypectx~locationresult_ty(valtype_cellity);Option.iter(funname->ctx.locals<-StringMap.addname.Annot.desc(Someity,name.info)ctx.locals;ctx.local_decls:=name::!(ctx.local_decls);mark_initializedctxname.desc)name;Option.fold~none:false~some:(funv->valtype_equalctxvity)standalone)in((name,ifctx.simplify&&redundantthenNoneelseSometyp),redundant)|None->(* A packed AGGREGATE read bound (or dropped: [name] may be anonymous)
without a signedness: WebAssembly has no unsigned-by-default
[array.get]/[struct.get] on a packed element or field, so the i32
default the omitted annotation gives it below has no lowering — the
typer used to accept it and the conversion failed its own output
validation (a wax-mutation-fuzzer under-reject finding). The cell is
still resolved below so the report does not cascade. *)(match(init,Cell.getresult_ty)with|Someinit,(Int8|Int16)whenpacked_aggregate_sourceinit->Error.packed_read_needs_signednessctx.diagnostics~location|_->());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.Annot.desc(ity,name.info)ctx.locals;ctx.local_decls:=name::!(ctx.local_decls);mark_initializedctxname.desc)name;((name,None),false)(* 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(* The [let] is a statement: it produces no value, so it carries no
[expected] type of its own (the bound [head] keeps its). *){desc=Let(List.revbindings,Somehead);info;hints=head.hints;expected=Unset;}::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.operand_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(param_typep)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(* When the label is unbound, [branch_target] has already reported
it and returned [[||]]; skip the contract check so the same
label is not flagged a second time (as the unknown-tag arm
above skips it). *)ifnot(label_in_scopectxlabel)then()elsebegin(* 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->lett=param_typepinmatch(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()endend)|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"|_->())))handlers(* The type a method call's receiver statically has, resolved PURELY: no typing,
so the dispatch in {!call_instruction} can consult it without recording a
use, reporting an error, or grounding an inference cell. Only the shapes
whose type is written down are resolved — a name (its local / global
declaration) and a field access (the field's declared type, its own receiver
resolved recursively) — through the wrappers that leave the type unchanged.
[None] means "cannot tell" (a literal, a call, a cast), which every caller
reads as "not a reference": the answer that leaves the dispatch as it was.
A static approximation rather than the receiver's real type because the
receiver cannot be typed BEFORE the arm is chosen: the arms disagree about
where it sits in emission order, so they draw its holes from different
slices of the pending list. An intrinsic pushes the receiver first and takes
the front of the slice; [type_indirect_call] emits the callee LAST and types
it against the tail (see its [front_holes] split). Typing the receiver above
the dispatch would fix one of the two orders and desync the other. *)letrecreceiver_valtypectxrecv=matchrecv.Ast.descwith|Ast.Getname->(letdeclared=function|Some({typ;_}:inferred_valtype)->Sometyp|None->NoneinmatchStringMap.find_optname.descctx.localswith|Some(ity,_)->declaredity|None->(matchTbl.find_no_markctx.globalsnamewith|Some(_,ity)->declaredity|None->None))|Ast.NonNullrecv'->receiver_valtypectxrecv'(* A field read used as a receiver ([o.f.min(..)], [o.f.copy(..)]): the
field's DECLARED type, off the struct definition the inner receiver
resolves to. A packed field, a name the struct does not declare, and a
receiver of any other kind of definition are all unresolvable. *)|Ast.StructGet(recv',field)->(match(receiver_type_definitionctxrecv':Ast.comptypeoption)with|Some(Structfields)->(matchArray.find_map(funf->if(field_namef).desc=field.descthenSome(field_typef)elseNone)fieldswith|Some{typ=Valuetyp;_}->Sometyp|Some{typ=Packed_;_}|None->None)|Some(Func_|Array_|Cont_)|None->None)|_->None(* The definition of the named reference type a receiver has, for the guards
that need the shape of the type and not just the fact of a reference. Read
from the type table without marking it used, like everything here. *)andreceiver_type_definitionctxrecv:Ast.comptypeoption=matchreceiver_valtypectxrecvwith|Some(Ref{typ=Typen|Exactn;_})->(matchTbl.find_no_markctx.type_context.typesnwith|Some(_,sub)->Somesub.typ|None->None)|_->None(* Whether the receiver of a scalar-intrinsic-method call [recv.min(..)] is a
reference (e.g. a struct) rather than a numeric value. 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]. *)letreceiver_is_refctxrecv=matchreceiver_valtypectxrecvwithSome(Ref_)->true|_->false(* Whether the receiver of an array-op method call ([a.fill(..)]) has a type
that is a reference to an array type. 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=matchreceiver_type_definitionctxrecvwith|Some(Array_)->true|_->false(* Whether a method call's receiver is a reference to a STRUCT that declares a
field of the method's name — the one condition under which [recv.m(args)]
must be an indirect call through a function-pointer field rather than the
built-in intrinsic [m]. Every intrinsic-method arm of {!call_instruction}
whose receiver is a general expression is gated on its negation, so a struct
that happens to name a field [copy], [length], [switch] or [add_i32x4] keeps
its indirect call. That is what [To_wasm]'s [receiver_is_array] /
[receiver_is_value] already lower, and what the decompiler emits for such a
field: field names come from the name section, which is under no obligation
to avoid the intrinsic names.
Stated as "is definitely a struct field" rather than "is definitely not an
array / not a value" so that a receiver {!receiver_valtype} cannot resolve
keeps the arm it has always taken: the guard only ever DIVERTS a call it can
prove belongs elsewhere. *)letmethod_is_struct_fieldctxrecv(meth:Ast.ident)=matchreceiver_type_definitionctxrecvwith|Some(Structfields)->Array.exists(funf->(field_namef).desc=meth.desc)fields|_->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)|_->false(* The threaded state of the expression typer's monad ([return]/[let*]):
[pending] holds the stack values a [Hole] may consume, handed to each
subexpression as an exact slice at every distribution point (see [with_slice]),
so consumption no longer depends on the textual order the children are typed
in — a sibling that errors, recovers or is typed out of order (a [StructDesc]
descriptor, a call callee — see [type_trailing_operand]) cannot desynchronise
the rest. [value_loc]
and [reported] fold in the hole-order check: [value_loc] is the source location
of the first value-producing operand emitted in the current distribution, so
reaching a hole-bearing operand after it means a hole occurs after a value on
the stack (unencodable) — reported at that value, with [reported] guarding a
duplicate. *)type'ahole_st={pending:'alist;value_loc:locationoption;reported:bool;}(* Split [l] after [n] elements, tolerating [n] past the end. *)letreclist_splitnl=ifn<=0then([],l)elsematchlwith|[]->([],[])|x::r->leta,b=list_split(n-1)rin(x::a,b)(* Whether an operand pushes a value onto the stack (so a following hole would
consume it rather than the intended pending value). A [Hole] pushes nothing (it
names an already-present pending value); a transparent cast (see
[cast_is_transparent]) lowers to no instruction, so it pushes exactly what its
operand does; every other operand emits at least one value-producing
instruction. Static receivers (memory/table/segment names, a [tab[..]] table)
are immediates, not operands, and never reach here. *)(* An ASCRIBED bare hole, possibly under further casts: the ascription is a
static assertion, so the chain lowers to no instruction. *)letrecis_ascribed_hole(node:_Ast.instr)=matchnode.descwith|Cast({desc=Hole;_},Ascribed_)->true|Cast(inner,_)->is_ascribed_holeinner|_->falseletrecemits_valuectx(node:_Ast.instr)=matchnode.descwith|Hole->false(* An ascribed hole lowers to no instruction, so — like the bare hole — it
emits nothing a following hole's value could hide behind. *)|Cast_whenis_ascribed_holenode->false|Cast(inner,_)whencast_is_transparentctx~cast:node~operand:inner->emits_valuectxinner|_->true(* Consume the pending value a [Hole] stands for. Per-subexpression slicing
([with_slice]) hands each hole its own slot, so an empty [pending] here is a
recovery-only path: a hole-bearing sibling was skipped or an operand
underflowed (already reported). Recover with an [Error] value rather than the
former [assert false] (exit 125). *)letpop_parameterst=matchst.pendingwith|x::r->({stwithpending=r},x)|[]->(st,Cell.makeError)(* Report the underflow behind the value a hole just consumed, if it was one of
the placeholders [pop_any] recorded: the report points at the hole itself,
the most precise anchor for a missing value. One report per underflow — the
batch is marked, and [with_holes] falls back to the whole expression only
when the placeholder never reached a hole (a recovery path). *)letreport_missing_holectx~locationty=matchList.find_opt(fun(cell,_)->cell==ty)!(ctx.missing_holes)with|Some(_,batch)->ctx.missing_holes:=List.filter(fun(cell,_)->cell!=ty)!(ctx.missing_holes);ifnotbatch.hole_reportedthenbeginbatch.hole_reported<-true;Error.short_stackctx.diagnostics`Holes~location~actual:batch.hole_actual~expected:batch.hole_expectedend|None->()let_print_arg_stackl=Wax_utils.Printer.run_err(funp->letpp=Wax_utils.Styled_printer.create~printer:p~theme:Wax_utils.Colors.no_color~trivia:(Wax_utils.Trivia.empty())()inList.iteri(funity->ifi>0thenWax_utils.Printer.spacep();output_inferred_type_styledppty)l)(* 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.operand_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(* The [typ] to store for a do/loop/try/try_table block 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.
Under [ctx.suggest] the same redundant result type is offered as an editor
quick fix (see [suggest_block_result]). Both the [simplify] drop and the
suggestion key on [block_result_redundant], so they cannot drift.
[keyword]/[block_start]/[brace_start] locate the '<keyword> t {' the source
scan trims the type from. *)letcontext_block_typctx~keyword(block_start:Lexing.position)(brace_start:Lexing.position)typ~expected~result_cell=letredundant=block_result_redundantctxtyp~expected~result_cellinifctx.suggest&&redundantthenTyping_suggest.suggest_block_resultctx~keywordblock_startbrace_start;iftyp.results=[||]thenmatchstandalone_valtypectxexpectedwith|Someiv->{typwithresults=[|iv.typ|]}|None->typelseifctx.simplify&&redundantthen{typwithresults=[||]}elsetyp(* 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's CAST is skipped: it is
load-bearing (dropping it loses the type the value stands in for). A TEST
deletes nothing, so a bottom operand is linted like any other — mirroring the
Wasm validator's [lint_cast], which has no bottom exclusion.
[operand_location] is the source span of the cast's operand, used (under
[ctx.suggest]) to offer a quick fix that removes a redundant cast by deleting
the ' as t' suffix running from the operand's end to the cast's end. *)letlint_ref_cast?operand_locationctx~location~is_testop_naturaltarget_natural=letinfo=subtyping_infoctxin(* Report the INNERMOST always-trapping cast of a chain only: a cast or test
over a value that can never be produced is unreachable, and whatever it says
about that value merely follows from the inner verdict — the fix belongs at
the inner cast (see [cast_traps_reported]). The span is recorded whether or
not the report came out, so a longer chain stays quiet past its second cast.
Only the always-trapping verdict is chained: a REDUNDANT outer cast is an
independent claim about the cast itself (its target is the type the operand
already has, whatever that operand does at run time) with its own fix, and
the Wasm validator's [lint_cast] reports it on the lowered form — a source
chain lowers to one [ref.cast] per cast, so suppressing it here left the wat
form of [g as &t as &t] linted and the wax form silent. *)letspan_key(l:Ast.location)=(l.loc_start.Lexing.pos_cnum,l.loc_end.Lexing.pos_cnum)inletoperand_traps=matchoperand_locationwith|Someol->Hashtbl.memctx.cast_traps_reported(span_keyol)|None->falseinifoperand_trapsthenHashtbl.replacectx.cast_traps_reported(span_keylocation)();letcast_always_fails()=Hashtbl.replacectx.cast_traps_reported(span_keylocation)();ifnotoperand_trapsthenError.cast_always_failsctx.diagnostics~location~is_testinletredundant_cast?edit()=Error.redundant_cast?editctx.diagnostics~location~is_testinmatch(op_natural,target_natural)with|(Valtype{typ=Ref{typ=op_src;_};internal=Refop;_},Valtype{internal=Reftgt;_})whenis_test||not(is_bottom_heaptypeop_src)->(* [any] <-> [extern] across hierarchies is the lossless
[extern.convert_any] / [any.convert_extern] conversion (the surface
spells it [as &extern] / [as &any]), not a [ref.cast]: it never traps
and, since it changes hierarchy, is never redundant. Don't lint it —
reporting it as an always-trapping (or redundant) cast is a false
positive. *)letbridged=letopenWax_wasm.Typesinletin_hierhtop=heap_subtypeinfohtopin(in_hierop.typInternal.Any&&in_hiertgt.typInternal.Extern)||(in_hierop.typInternal.Extern&&in_hiertgt.typInternal.Any)inletrelated=Wax_wasm.Types.heap_subtypeinfoop.typtgt.typ||Wax_wasm.Types.heap_subtypeinfotgt.typop.typinifbridgedthen()elseif(notrelated)&¬(op.nullable&&tgt.nullable)thencast_always_fails()elseifWax_wasm.Types.ref_subtypeinfooptgtthenletedit=matchoperand_locationwith|Some(ol:Ast.location)whenctx.suggest&¬is_test->Some(deletion_edit(spanol.loc_endlocation.loc_end))|_->Noneinredundant_cast?edit()|(Valtype{typ=Ref{typ=op_src;_};internal=Refop;_},Valtype{internal=I32|I64;_})when(notis_test)&&(not(is_bottom_heaptypeop_src))&&Wax_wasm.Types.heap_subtypeinfoop.typInternal.Any&¬(Wax_wasm.Types.heap_subtypeinfoop.typInternal.I31||Wax_wasm.Types.heap_subtypeinfoInternal.I31op.typ)->(* [ref as iN_s/u] extracts an i31 payload: it lowers to a [ref.cast (ref
i31)] then an [i31.get] (see [To_wasm.default_cast]). An [any]-hierarchy
reference that can never be an [i31] — a [struct]/[array], not
[any]/[eq]/[i31] — makes that [ref.cast] always trap, exactly as the
Wasm validator reports on the lowered form. *)cast_always_fails()|_->()(* 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)letsimd_cellt=valtype_cell(Members.simd_valtypet)(* 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)=Output.valtype_stringtinletsign=let*@inner=lookup_cont_innerctxctinlookup_func_typectxinnerinletparams,results=matchsignwith|Somesg->(Array.to_list(Array.map(funp->render(param_typep))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(param_typep))sg2.params))|_->Nonewith|Somers->rs|None->[]inMembers.R_cont(Members.cont_method_candidates~params~results~switch_results)(* 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))elsematchList.assoc_optl.descseenwith|Someprev_loc->Error.duplicate_argument_labelctx.diagnostics~location:l.info~prev_locl;(seen,acc)|None->(matche.Ast.descwith|Ast.Int_->((l.desc,l.info)::seen,(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.integer_literal_requiredctx.diagnostics~location:(snde.Ast.info);((l.desc,l.info)::seen,acc))inlet_,acc=List.fold_lefttake([],[])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)positionalinletnimms=ifhas_lanethen3else2in(* The extras are the pre-labelled positional-immediate syntax only when they
are all integer literals and no more than the immediate count; otherwise
they are an ordinary arity error and must not be read as immediates (else a
non-literal extra, e.g. a local, cascades into a bogus memarg error). *)letmigration=extra<>[]&&List.lengthextra<=nimms&&List.for_all(funa->matcha.Ast.descwithAst.Int_->true|_->false)extrain(ifnargs<nstackthenError.operand_count_mismatchctx.diagnostics~location~expected:nstack~provided:nargselsematchextrawith|[]->()|a::_->ifmigrationthenError.positional_memory_immediatectx.diagnostics~location:(snda.Ast.info)~exampleelseError.operand_count_mismatchctx.diagnostics~location~expected:nstack~provided:nargs);letpicknamek=matchfindnamewith|Somee->Somee|None->ifmigrationthenList.nth_optextrakelseNoneinifhas_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(* The instruction methods whose result has the width of their RECEIVER: the
integer and float unary operators plus the two-operand rotates and float
pairs, as opposed to those that fix a width of their own ([to_bits]/
[from_bits], whose result is the receiver's bit width in the other family, and
[length]). A cast on such a call's RESULT propagates back through it to the
receiver — which is how a width pin on [(5).clz()] makes it an [i64.clz], the
very mechanism [From_wasm] relies on when it tags a method result with its
receiver's flexibility. Used by {!defaulting_tree}. *)letis_width_preserving_methodm=matchmwith|"clz"|"ctz"|"popcnt"|"extend8_s"|"extend16_s"|"abs"|"ceil"|"floor"|"trunc"|"nearest"|"sqrt"|"rotl"|"rotr"|"min"|"max"|"copysign"->true|_->false(* Whether the node's printed form takes its numeric type by DEFAULTING: a tree of
numeric literals, holes and width-PRESERVING operators with nothing in it that
fixes a width — no local, call result, memory read or cast. A pin around such a
tree grounds every literal in it and converts nothing, whereas a pin around a
tree holding a
real datum would be a numeric CONVERSION of that datum. Mirrors [From_wasm]'s
anchor-free notion ([is_anchor]/[reparse_adaptive]), and is needed beside
{!flexible_literal} because a cast FOLDS a still-flexible literal into its
target type ([cast] above), so by the end of inference such a tree's cell looks
concrete even though its width was never anchored by anything. *)letrecdefaulting_tree?(holes_only=false)(i:_instr)=letdefaulting_tree=defaulting_tree~holes_onlyinmatchi.descwith(* A hole — a value the Wasm side left polymorphic — or, unless [holes_only], a
numeric literal. NOT a [Char], whose i32 value a cast would convert rather
than ground. *)|Hole->true|Int_|Float_->notholes_only(* The operators whose result type IS their operands': a pin on the result
grounds them. A comparison or [!] yields i32 whatever its operands, and a
cast fixes its own type, so neither continues the tree. *)|UnOp({Annot.desc=Neg|Pos;_},a)->defaulting_treea|BinOp({Annot.desc=Add|Sub|Mul|Div_|Rem_|And|Or|Xor|Shl|Shr_;_;},a,b)->defaulting_treea&&defaulting_treeb|Select(_,a,b)->defaulting_treea&&defaulting_treeb|Sequence(_::_asl)->defaulting_tree(List.nthl(List.lengthl-1))(* A method whose result width is its receiver's ([(5).clz()],
[(1).rotl(40)]): the pin on the result reaches the receiver through it. *)|Call({desc=StructGet(recv,meth);_},args)whenis_width_preserving_methodmeth.Annot.desc->defaulting_treerecv&&List.for_alldefaulting_treeargs(* A NARROW atomic RMW takes its i32/i64 family from its value operand, whose
cell the typer merges with the result's ([type_atomic_method_call]), so a pin
on the result grounds it — its method name carries the access width only
([atomic_rmw_add8] spells both the i32 and the i64 op). It continues the tree
through the value operands and not the memory receiver or the address, which
fix no width of the result. A [`W64] RMW states its own width, and a narrow
atomic LOAD resolves through its own [as iN_u] cast, so neither belongs here.
Without this the width repair could not place the pin an [i64] narrow RMW in
dead code needs — the value it merges with is a hole, so the printed form
re-parsed at the i32 default and narrowed the opcode — and reported the
disagreement as unrepairable instead (a wat-mutation-fuzzer finding). *)|Call({desc=StructGet(_,meth);_},args)whenmatchWax_wasm.Atomics.of_method_namemeth.Annot.descwith|Some(Rmw(_,(`W8|`W16|`W32)))->true|Some(Rmw(_,`W64)|Load_|Store_|Wait_|Notify)|None->false->((* The address is the first positional argument, the value operands follow
(two for a [cmpxchg]); the memarg immediates are labelled. *)matchList.filter(fun(a:_instr)->matcha.descwithLabelled_->false|_->true)argswith|_addr::(_::_asvalues)->List.for_alldefaulting_treevalues|_->false)|_->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(param_typep);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_origwithAnnot.desc=outer_body})::List.map2(fun(l,(orig:(_instrlist,location)Ast.annotated))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), the
typed default, and the typed scrutinee (the innermost operand of the test
chain) — [None] when there are no arms, so the scrutinee never appears in the
lowering. *)(* Raised by [rebuild_match] when the type-checked lowering is not the block
nesting the lowering produces. That shape is guaranteed only when typing
SUCCEEDS; an erroneous scrutinee (e.g. a hole that underflows, or one whose
type failed to resolve) can make the checker recover into a different shape,
so the callers catch this and fall back rather than crashing. *)exceptionMatch_shapeletrebuild_matchtyped_listarms=matcharmswith|[]->([],typed_list,None)|_->letblock_bodyblk=matchblk.descwith|Ast.Block{block;_}->block.desc|_->raiseMatch_shapein(* 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)|_->raiseMatch_shapeinletescape,default=matchtyped_listwithx::r->(x,r)|[]->raiseMatch_shapein(* The innermost block's body is [drop chain; br escape]; the chain wraps
the scrutinee in one [br_on_cast]/[br_on_null] per arm, so descend that
many levels to recover the typed scrutinee. *)letscrutinee_of_innerblk=matchblock_bodyblkwith|{desc=Ast.Let(_,Somechain);_}::_->letrecdescendkc=ifk=0thencelsematchc.descwith|Ast.Br_on_cast(_,_,operand)|Ast.Br_on_null(_,operand)->descend(k-1)operand|_->cindescend(List.lengtharms)chain|_->raiseMatch_shapeinletrecpeelblk=function|[]->(* [blk] is the innermost block (test chain + escape). *)([],scrutinee_of_innerblk)|(pat,orig)::rest_rev->letinner,arm_body=unwrappat(block_bodyblk)inletrest,scrut=peelinnerrest_revin((pat,{origwithAnnot.desc=arm_body})::rest,scrut)inletarms_rev,scrut=peelescape(List.revarms)in(List.revarms_rev,default,Somescrut)(* 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, a
struct named unambiguously by its fields, or a descriptor construction, whose
type the descriptor pins). 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)->(matchinfer_struct_by_fieldsctxfieldswith|Some_->(false,true)|None->(true,false))(* A descriptor construction ([{descriptor(d) | ..}], [descriptor(d)::default])
takes its type from the descriptor [d], not the surrounding context, and
carries no droppable type name — so it resolves its own type regardless of
whether its fields are unique, unlike the plain [Struct] above. *)|StructDesc_|StructDefaultDesc_->(false,true)|StructDefault_|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)|_->(false,false)(* The re-inference of a checked node: what an unannotated binding
([let x = <node>]) would infer its initializer to be, standalone — the
information a surrounding binding annotation is redundant against.
[check_instruction] returns it as its second component (the old keep-bool is
[reinfer_needed] applied to it), and the [If]/[Select]/[Block] arms join their
sub-nodes' compositionally. That is the fix the keep-bool needed: a nested
construct reports its own re-inference upward rather than being read through
its result cell, which the expected type has already flowed into — so a tail
whose type came from the context no longer looks redundant. *)typereinfer=|Diverges(** A [br]/[return]/[unreachable] tail: delivers no value, so it drops out
of a join (the sibling arm decides). *)|Uninferrable(** Cannot be typed at all without the annotation — an un-named
construction (an array literal, a field-ambiguous struct) whose type
name has been dropped. Poisons a join: no sibling can rescue it. *)|Typofinferred_typeCell.t(** Re-infers to this cell standalone, and safely narrows — an immutable
binding may drop a mere-supertype annotation down to it
([drop_supertype]). Holds for scalars (a call, a literal) and
constructions that re-infer their type structurally, without a written
name (a field-unique struct, a descriptor construction): dropping the
annotation then leaves a form that decompiles back to itself.
A *snapshot* (a fresh copy, taken before any unification the expected
type drove), holding the value's still-flexible type so a flexible
literal absorbs into a concrete sibling under [join_reinfer] exactly
as re-inference would; [join_reinfer] merges it, so it must not be
shared with the typed AST. *)|Namedofinferred_typeCell.t(** Re-infers to this cell via a *written type name* (an array literal
[[t| ..]], a named struct-default) that the surrounding annotation
does not itself pin. Such a construction drops its annotation only
when the annotation is exactly its type, never by narrowing: narrowing
an immutable binding to the (strict-subtype) construction type would
make the written name redundant on the next cycle and the
decompilation flip between "name, no annotation" and "annotation, no
name" — so a strict-supertype annotation is load-bearing for
round-trip stability. Same snapshot discipline as [Typ]. *)(* Snapshot a cell into a fresh, mutation-safe [Typ]. *)letreinfer_of_cellty=Typ(Cell.make(Cell.getty))(* Snapshot a cell into a name-dependent [Named]. *)letnamed_reinfer_of_cellty=Named(Cell.make(Cell.getty))(* Join two branches' re-inference (an [if]'s arms, a [?:]'s values, a block's
exits): a diverging branch drops out, an uninferrable one poisons the whole,
and two typed branches join by [join_value_types] (a failed join is
uninferrable). [join_value_types] absorbs a flexible literal into a concrete
sibling, so a bare [1] alongside a typed [i64] joins to [i64] — the annotation
is then redundant — while two flexible literals stay flexible and re-default
the same on re-parse. A join is [Named] (equality-only) when either branch is:
if a branch relies on a written name, narrowing the whole would flip it. *)letjoin_reinferctxab=match(a,b)with|Diverges,x|x,Diverges->x|Uninferrable,_|_,Uninferrable->Uninferrable|(Typta|Namedta),(Typtb|Namedtb)->(letnamed=match(a,b)withNamed_,_|_,Named_->true|_->falseinmatchjoin_value_typesctxtatbwith|Somec->ifnamedthenNamedcelseTypc|None->Uninferrable)(* Whether a binding annotation [expected] is load-bearing given its
initializer's re-inference: a diverging/uninferrable initializer keeps it (an
unannotated binding could not re-derive the type); a typed one keeps it iff
its standalone (re-defaulted) type differs from [expected]. [drop_supertype]
loosens the test for an immutable binding to allow narrowing (see
[annotation_needed]) — but only for a [Typ]; a [Named] drops only on exact
equality, never by narrowing. This derives the old scalar keep-bool from the
compositional re-inference. *)letreinfer_needed?(drop_supertype=false)ctxreinferexpected=matchreinferwith|Diverges|Uninferrable->true|Typc->annotation_needed~drop_supertypectx(standalone_valtypectxc)expected|Namedc->annotation_neededctx(standalone_valtypectxc)expected(*** 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)typesparamselseparams(* Once a value-producing operand has been seen, record its location so a later
hole in the same distribution can be flagged against it. *)letbump_value_locctxstnode=matchst.value_locwith|Some_->st|None->ifemits_valuectxnodethen{stwithvalue_loc=Some(sndnode.info)}elsestletreccount_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)|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(* Type one operand of a distribution point against exactly its own hole-slice —
the front [count_holes child] pending values — and fold in the hole-order
check (see [hole_st]). [run] is the child-typer applied to the child (an
[instruction]/[check_instruction] action), [node_of] extracts the typed
instruction from its result (identity, resp. [fst]). The child's own leftover
is dropped: it receives its slice and nothing more, so it cannot desynchronise
its siblings. Fast path — no pending values means the whole subtree is
hole-free, so skip counting (but still note whether a value was emitted, for a
later sibling's hole). *)lethole_childctxchildnode_ofrunst=matchst.pendingwith|[]->letst',r=runstin(bump_value_locctxst'(node_ofr),r)|pending->letn=count_holeschildinletreported=if(notst.reported)&&n>0&&st.value_loc<>Nonethen(Error.before_holectx.diagnostics~location:(Option.getst.value_loc);true)elsest.reportedinletslice,rest=list_splitnpendinginletst',r=run{stwithpending=slice;reported}in(bump_value_locctx{st'withpending=rest}(node_ofr),r)(* Type a trailing operand out of emission order, for a construct whose type flow
runs backwards from it: [run ()] types the operand now — before the operands
that precede it in emission — so its type can direct their checking (a call
callee's parameters, a [StructDesc] descriptor's struct type).
The hole slices already make this sound for the stack; this makes it sound for
the initialized-locals analysis, which threads in emission order. Within a
straight-line operand sequence that set only grows, so the state [run] sees is
a SUBSET of the true state at the operand's emission slot: a read that succeeds
now is sound; a read that fails is DEFERRED (collected, not reported); and the
operand's own straight-line writes are captured and WITHHELD from
[initialized_locals] until the emission slot, so an earlier operand — which
runs first — cannot see them. Returns the typed operand and a [replay] thunk to
run at that slot: it re-checks each deferred read against the now-current state
(an earlier operand may have initialized the local), reporting or, under an
outer deferral, re-deferring the survivors, then applies the withheld writes. *)lettype_trailing_operandctxrun=letsaved=ctx.initialized_localsinletcollector=ref[]inctx.deferred_uninit<-collector::ctx.deferred_uninit;letresult=Fun.protect~finally:(fun()->ctx.deferred_uninit<-List.tlctx.deferred_uninit)runinletdelta=ctx.initialized_localsinctx.initialized_locals<-saved;letdeferred=List.rev!collectorinletreplay()=List.iter(funidx->ifnot(StringSet.memidx.Annot.descctx.initialized_locals)thenreport_uninitializedctxidx)deferred;ctx.initialized_locals<-StringSet.unionctx.initialized_localsdeltain(result,replay)(* Fold one already-typed operand [node] (untyped form [child]) into the running
hole-order state, as [hole_child] does inline, but for an operand typed out of
its emission position — a call callee or [StructDesc] descriptor, emitted last
but typed first for its type. [st.value_loc] must already reflect the operands
emitted before it. *)letfold_operandctxchildnodest=letst=if(notst.reported)&&count_holeschild>0&&st.value_loc<>Nonethen(Error.before_holectx.diagnostics~location:(Option.getst.value_loc);{stwithreported=true})elsestinbump_value_locctxstnode(* Bridge from the statement (stack) monad to the expression monad: pop the
[count_holes i] hole operands off the operand stack into the pending list and
run [f] (an expression-monad action) with a fresh hole state, returning its
result in the stack monad. Typing hands each hole its slice and the hole-order
check runs inline ([hole_child]); the final expression state is discarded (a
leftover pending is a recovery-only artefact, see [pop_parameter]). *)letwith_holesctxibuild=let*pending,batch=pop_manyctx(count_holesi)inlet_st,r=build(){pending;value_loc=None;reported=false}in(* An underflow placeholder normally reaches a hole, which reports it at its
own location ([report_missing_hole]); when recovery dropped it instead,
report here, against the whole expression. *)(match!batchwith|Someb->ctx.missing_holes:=List.filter(fun(_,b')->b'!=b)!(ctx.missing_holes);ifnotb.hole_reportedthenbeginb.hole_reported<-true;Error.short_stackctx.diagnostics`Holes~location:i.info~actual:b.hole_actual~expected:b.hole_expectedend|None->());returnr(* A [match] scrutinee, [dispatch] index or [while] condition is evaluated inside
the blocks the construct lowers to, whose stack excludes the enclosing
statement's pending values — so a hole there has nothing to consume (and could
not be re-consumed each iteration of a [while]). Reject it with a clear error
rather than the stack-underflow cascade it would otherwise trigger, and replace
the whole operand with [Unreachable] so the rest of the construct still
type-checks for recovery. Returns the (possibly replaced) operand and whether
it was rejected, so the caller can skip a follow-up check that would cascade.
[from_wasm] never emits such a hole — a recovered [match] scrutinee is
re-readable (see [Recover_match.same_scrut], never a hole) and a while
condition sits in the leading test of a void loop — so this only rejects
hand-written code. *)letreject_control_holesctx~construct~role~recoveryoperand=ifcount_holesoperand>0then(Error.hole_in_control_operandctx.diagnostics~location:operand.info~construct~role;(* Replace the whole operand with a hole-free value of the shape the
construct expects ([recovery]: a null reference for a scrutinee, [0] for
an [i32] index/condition), so the lowering type-checks without a
stack-underflow or wrong-shape cascade on top of the reported error. *)({operandwithdesc=recovery},true))else(operand,false)(* The lattice type of a float(-valued) literal [s]: the flexible [Float] (either
width, pinned by context) when [s] rounds to a valid f32, else concrete [f64].
An out-of-f32-range magnitude must never be pinned to f32 — it would print as
an out-of-range [f32.const] — so a later [as f32] lowers to a real [f64->f32]
demote instead of folding into the literal. *)letfloat_literal_lattices=ifWax_wasm.Misc.is_float32sthenFloatelseValtypef64_valtypeletrecinstructionctxi:_hole_st->_hole_st*(_array*_)instr=ifdebugthenWax_utils.Printer.run_err(funp->Printer_output.instrpi);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_parameterinreport_missing_holectx~location:i.infoty;return_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'))[||]|Unreachable->(* Typing the call already failed: a [let*!] on a [None] lookup yields
an [Unreachable] node typed [Error] (with the error already
reported). There is no tail call to form; propagate the failed
result rather than re-reporting or [assert false]. *)returntyped|_->(* The call type-checked but is not a [Call]: it is a stack-switching
operation ([resume]/[resume_throw]/[resume_throw_ref]/[switch] on a
continuation receiver, or [k::new]/[k::bind]), to which no tail call
can apply. Report it rather than silently dropping the [become]
marker (which would also skip the return-type check); recover with
the plain operation. (A well-formed direct or indirect call is
always a [Call], via [type_indirect_call].) *)Error.become_on_stack_switchingctx.diagnostics~location:i.info;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_literal_lattices|SomevwhenInt64.unsigned_comparev0xFFFFFFFFL>0->LargeInt|Some_->Numberinreturn_expressionidesc(Cell.makelattice)|Floatsasdesc->return_expressionidesc(Cell.make(float_literal_lattices))|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_->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)->(* Emission order is the two branch values then the condition (the [select]
pops the condition last), so type them in that order for the hole
slices and hole-order check. *)let*i2'=typedctxi2inlet*i3'=typedctxi3inlet*i1'=typedctxi1incheck_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] is emitted last, on top
of the value operand (see [CastDesc]/[Br_on_cast_desc_eq]), so [typed] slices
and hole-order-checks it as the trailing operand. Returns the typed operand
and the recovered target reftype. ([StructDesc] instead types the descriptor
first — for the struct type — and folds it in last itself, so it uses
[descriptor_reftype] directly.) *)let*d'=typedctxdinreturn(d',descriptor_reftypectx~location~nullabled')(* Recover the target reference type of a descriptor cast/branch/allocation from
the (typed) descriptor operand [d' : (ref null? (exact_1 Y))] with
[Y describes X]: the target is [(ref nullable (exact_1 X))] — the described
type [X] and the exactness [exact_1] come from [d'], only the result
nullability is written. [None] (with [type_without_descriptor] reported) when
[d'] is not a reference to a descriptor type. *)anddescriptor_reftypectx~location~nullabled'=lettarget=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);target(* [typed]/[typed_check] wrap the two child-typers ([instruction]/
[check_instruction]) in [hole_child], the slice + hole-order machinery defined
before the recursion. *)andtypedctxchild=hole_childctxchildFun.id(instructionctxchild)andtyped_checkctxexpectedchild=hole_childctxchildfst(check_instructionctxexpectedchild)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_targetctxlabelin(* An unbound label was already reported by [branch_target]; its [[||]]
params are not a real arity, so skip the checks that would anchor
derived errors here (see [label_in_scope]). *)letbound=label_in_scopectxlabelinlet*i'=matchi'with|Somei'whenbound->let*i'=check_againstctxparamsi'inreturn(Somei')|Somei'->let*i'=instructionctxi'inreturn(Somei')|None->ifbound&¶ms<>[||]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_targetctxlabelin(* Unbound label (already reported): the fall-through passes the values
through unchanged, with no checks against the [[||]] pseudo-params. *)letresult=iflabel_in_scopectxlabelthendeliver_to_branch_targetctx~loc~types~paramselsetypesinreturn_statementi(Br_if(label,i'))result|Br_table(labels,i')->let*i'=instructionctxi'inletloc=sndi'.infoinletty,types=split_on_last_typectx~location:loci'incheck_subtypectx~location:loctyi32_cell;(* Resolve every target (an unbound one reports at its own span, and
used-label marking runs per occurrence); check arity and types only
against the bound ones — an unbound label's [[||]] is not a real
arity — and only ONCE per DISTINCT target: the check is purely
per-target, so a repeated one would only repeat an identical report
(mirrors the validator's [Br_table] dedup). Labels are names within
the one enclosing scope — there is no numeric spelling on the Wax
side — so identical spellings resolve to the same frame and the name
is the target's identity. The reference arity is the first BOUND
target's. *)lettargets=List.map(funlabel->(label,label_in_scopectxlabel,branch_targetctxlabel))labelsin(matchList.find_opt(fun(_,bound,_)->bound)targetswith|Some(first_label,_,first)->letlen=Array.lengthfirstin(* The count of values the [br_table] itself provides is a single fact,
checked once against the reference arity: a per-target check (via
[check_subtypes] below) would repeat an identical "provides N but M
expected" report for every distinct target. *)ifArray.lengthtypes<>lenthenError.value_count_mismatchctx.diagnostics~location:i.info~expected:len~provided:(Array.lengthtypes);letseen=Hashtbl.create8in(* The type checks below are keyed by the target's PARAMETER TYPES, not
by its name: two distinct targets imposing the same types on the same
values yield the same report at the same span (the values'), which
would render as one repeated [line:col: message]. Their arity reports
are still per name — those carry the label's own span. Compared
through [standalone_valtype], which is pure; a parameter with no
resolved type yet compares unequal, so it is checked rather than
silently skipped. *)letchecked=ref[]inletsame_requirementab=Array.lengtha=Array.lengthb&&Array.for_all2(funxy->match(standalone_valtypectxx,standalone_valtypectxy)with|Somex,Somey->valtype_equalctxxy|_->false)abinList.iter(fun((label:Ast.ident),bound,params)->ifbound&¬(Hashtbl.memseenlabel.desc)thenbeginHashtbl.addseenlabel.desc();(* A target whose arity disagrees with the reference one — a
distinct fact per target, so reported here. *)ifArray.lengthparams<>lenthenError.branch_arity_mismatchctx.diagnostics~location:label.info~first_loc:first_label.infofirst_label~expected:len~provided:(Array.lengthparams);(* Type-check the provided values against this target only when
the counts line up (the count mismatch is already reported
once, above). [~pin:false]: the same values are checked
against every target, so a polymorphic (bottom) value must
not be pinned to one target's type — it is a subtype of each
legitimately-different target (see {!check_subtypes}). *)ifArray.lengthtypes=Array.lengthparams&¬(List.exists(same_requirementparams)!checked)thenbeginchecked:=params::!checked;(* The value is what has the wrong type, so it keeps the
primary span; the label says which target wants what, so two
reports on the same value from differently-typed targets read
apart. *)check_subtypes~pin:false~expected_at:label.infoctx~location:loctypesparamsendend)targets|None->());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. An unbound label (already reported) delivers
nothing; the values pass through unchecked. *)letresult=iflabel_in_scopectxidxthendeliver_to_branch_targetctx~loc~types~paramselsetypesinreturn_statementi(Br_on_null(idx,i'))(Array.appendresult[|typ'|])|Br_on_non_null(idx,i')->let*i'=instructionctxi'inletparams=branch_targetctxidxinletbound=label_in_scopectxidxinlettyp,types=split_on_last_typectx~location:(sndi'.info)i'inlettyp=Cell.gettypin(* An unbound label (already reported): skip the checks against the
[[||]] pseudo-params; the fall-through keeps the below-values. *)(matchtypwith|_whennotbound->()|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. For an
unbound label the below-values pass through unchanged. *)(ifboundthenArray.subparams0(max0(Array.lengthparams-1))elsetypes)|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_targetctxlabelinletbound=label_in_scopectxlabelin(* Unbound label (already reported): no check against the [[||]]
pseudo-params, and the fall-through keeps the below-values. *)(ifboundthenlet>@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(ifboundthenArray.subparams0(max0(Array.lengthparams-1))elsetypes)[|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_targetctxlabelinletbound=label_in_scopectxlabelin(* Unbound label: as in [Br_on_cast] above. *)ifboundthencheck_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(ifboundthenArray.subparams0(max0(Array.lengthparams-1))elsetypes)[|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))]). Type the value before the descriptor, as
they are evaluated and lowered ([to_wasm]) and as the sibling [CastDesc]
arm does, so hole ordering and uninitialized-local tracking match. *)let*i'=typedctxi'inlet*d,target=descriptor_targetctx~location:i.info~nullabledinlet*!ty=targetinifis_cont_heaptypectxty.typthenError.invalid_cast_typectx.diagnostics~location:i.info;lettyp',types=split_on_last_typectx~location:(sndi'.info)i'inletparams=branch_targetctxlabelinletbound=label_in_scopectxlabelin(* Unbound label (already reported): no check against the [[||]]
pseudo-params, and the fall-through keeps the below-values. *)(ifboundthenlet>@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)->(* Type the value before the descriptor, matching evaluation/lowering order
and the [Br_on_cast_desc_eq] arm above. *)let*i'=typedctxi'inlet*d,target=descriptor_targetctx~location:i.info~nullabledinlet*!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(param_typep))(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)->(* Fill an omitted result of a block-construct operand from the tag's
parameter types so it lowers like the annotated form — the resume-family
materialization (see [type_cont_method_call]) applied to [suspend], whose
tag is an immediate so no operand reordering is needed. *)letl=annotate_omitted_blocksl(cont_operand_source_typesctx~meth:"suspend"~tag:(Sometag)None)inlet*l'=instructionsctxlinlet*!{params;results}=Tbl.findctx.diagnosticsctx.tagstagin(let>@ptypes=array_map_opt(funp->internalizectx(param_typep))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(param_typep))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(param_typep))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(param_typep))(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(param_typep))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(* The source types of a stack-switching call's value operands — everything
preceding the receiver. Used to fill in an omitted result of a block-construct
operand (see [annotate_omitted_block]) so it types exactly like the annotated
form: [resume_throw_ref]'s single operand is always [exnref]; [resume_throw]'s
come from the invoked tag; [resume]/[switch]'s from the receiver's continuation
signature ([ct], from [cont_operand_type]). [None] (an unresolved receiver /
tag, or a form that takes no anchoring operand) leaves the operands as written.
Silent — [finish_*] re-derives and checks the internalized types and remains
the sole reporter — so it uses the non-reporting [_opt] lookups. *)andcont_func_paramsctxct=(* The parameters of continuation type [ct]'s underlying function type, looked
up silently ([None] if [ct] is unresolved or not a continuation). *)let*@_,sub=Tbl.find_optctx.type_context.typesctinmatchsub.typwith|Contft->(matchTbl.find_optctx.type_context.typesftwith|Some(_,{typ=Funcf;_})->Somef.params|_->None)|_->Noneandcont_operand_source_typesctx~meth~tagct:Ast.valtypearrayoption=letcont_params()=let*@ct=ctincont_func_paramsctxctinmatchmethwith|"resume_throw_ref"->Some[|Ref{nullable=true;typ=Exn}|]|"resume_throw"|"suspend"->(* Both take the invoked tag's parameters as their value operands. *)let*@tag=taginlet+@{params;_}=Tbl.find_optctx.tagstaginArray.map(funp->param_typep)params|"resume"->let+@params=cont_params()inArray.map(funp->param_typep)params|"switch"->let+@params=cont_params()inletnp=Array.lengthparamsinArray.map(funp->param_typep)(Array.subparams0(max0(np-1)))|_->None(* The types of the bound (leading) operands of a [cont.bind] from source
continuation [src] to result [dst]: the first [|src| - |dst|] parameters of
[src] (the ones bound away). Non-reporting — [finish_cont_bind] re-derives and
checks these and stays the sole reporter — so a still-unresolved type, or a
negative difference (a malformed bind [finish_cont_bind] will reject), yields
[None] and leaves the operands as written. *)andbind_bound_typesctx~src~dst=let*@sp=cont_func_paramsctxsrcinlet*@dp=cont_func_paramsctxdstinletnp=Array.lengthsp-Array.lengthdpinifnp<0thenNoneelseSome(Array.map(funp->param_typep)(Array.subsp0np))(* Fill in an omitted result type of a block-construct operand from the type its
consumer expects, so it types through the annotated block path (a concrete
result flowing into the body — [type_block_construct]'s non-inference branch)
rather than the checking / synthesis paths, which route a trailing nested
block through an inferring cell and so never materialize the result its
consumer needs. This is what makes an unannotated stack-switching operand
['h: do { … }] lower identically to the explicitly annotated ['h: do &?t { … }]
(see the repros in the resume-family typers). Non-block operands and blocks
whose result is already written are returned unchanged. Polymorphic in the
node's info so [restore_leftover_block_result] can apply it to a TYPED node
as well as to a parsed operand. *)andannotate_omitted_block:'a.valtype->'ainstr->'ainstr=funsrcoperand->letfilltyp=iftyp.results=[||]then{typwithresults=[|src|]}elsetypinletdesc=matchoperand.descwith|Blockb->Ast.Block{bwithtyp=fillb.typ}|Loopb->Loop{bwithtyp=fillb.typ}|TryTableb->TryTable{bwithtyp=fillb.typ}|Tryb->Try{bwithtyp=fillb.typ}|TryCatchb->TryCatch{bwithtyp=fillb.typ}|Ifb->If{bwithtyp=fillb.typ}|d->din{operandwithdesc}(* Put back the result type of a block-like STATEMENT whose value is left on the
stack for a later consumer. Nothing in that position pins a type on a
re-parse — [toplevel_instruction] types such a statement against its own
declared result — so an omitted one strands the body's value ("This value
remains on the stack").
[simplify] reaches that shape by dropping two annotations that are each
redundant on their own: the block's result type, redundant because the cast
wrapping it pinned the same type ([context_block_typ]), and then the cast
itself, redundant because the block already had that type. Together they
leave a bare [do { … }] that no longer states what its body yields (a
wasm-smith round-trip finding on [block (result anyref) … end ; ref.cast
anyref], its value dropped several instructions later). Restoring the block's
own annotation is the spelling the same block already reaches when no cast
wrapped it, so the two inputs converge rather than diverge. *)andrestore_leftover_block_resultctx(i':(_array*_)instr)=matchfsti'.infowith|[|cell|]->(matchstandalone_valtypectxcellwith|Someiv->annotate_omitted_blockiv.typi'|None->i')|_->i'(* Fill in each omitted block-construct operand's result from the expected
operand types, when they are known and their count matches (a mismatched
count is left for [finish_*] to report as an arity error). *)andannotate_omitted_blocksargs=function|SomesrcswhenArray.lengthsrcs=List.lengthargs->List.mapi(funka->annotate_omitted_blocksrcs.(k)a)args|_->args(* 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~handlersrecv(meth:Ast.ident)args=(* [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~prev_loc:t.info{dupwithdesc="tag"};(Somet,rest)|[]->(* Anchor at the [switch] method, not the whole call expression: a
chained [c.switch().switch()] would otherwise report this at the
shared chain-start column twice — two genuine (each switch needs a
tag), identically-rendered errors. *)Error.switch_needs_tagctx.diagnostics~location:meth.info;(None,rest))|"resume_throw"->(matchargswith|[{desc=Call({desc=Gett;_},payload);_}]->(Somet,payload)|_->(* At the method, not the call expression: as for [switch_needs_tag]
above, a chained [c.resume_throw().resume_throw()] would otherwise
report this twice at the shared chain-start column. *)Error.resume_throw_needs_tagctx.diagnostics~location:meth.info;(None,args))|_->(None,args)in(* Emission order: the payload arguments, then the continuation receiver (on
top), then the resume/switch. The receiver is TYPED first (out of emission
order, made sound for the stack by the explicit hole slices and for the
initialized-local analysis by [type_trailing_operand]) so its continuation
signature supplies the value operands' expected types; an omitted result of
a block-construct operand is then filled from that type so it lowers exactly
like the annotated form ([annotate_omitted_blocks]). The operands are still
typed in emission order over the front hole slice and the receiver is folded
in last, exactly as [type_indirect_call] handles a callee. *)funst->letfront_holes=List.fold_left(funacca->acc+count_holesa)0argsinletfront_pending,tail_pending=list_splitfront_holesst.pendinginletrecv',replay=type_trailing_operandctx(fun()->let_,recv'=instructionctxrecv{pending=tail_pending;value_loc=None;reported=false}inrecv')inletct=cont_operand_typectxrecv'inletargs=annotate_omitted_blocksargs(cont_operand_source_typesctx~meth:meth.desc~tagct)inlettype_body=let*args'=instructionsctxargsinletl'=args'@[recv']inlet*!ct=ctinmatchmeth.descwith|"resume"->finish_resumectxicthandlersl'|"resume_throw"->let*!tag=taginfinish_resume_throwctxicttaghandlersl'|"resume_throw_ref"->finish_resume_throw_refctxicthandlersl'|_->let*!tag=taginfinish_switchctxicttagl'inletst1,node=type_body{stwithpending=front_pending}inletst2=fold_operandctxrecv'recv'{st1withpending=[]}inreplay();(st2,node)(* 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_callctxifuncns(name:Ast.ident)args=match(name.desc,List.revargs)with|"bind",cont_arg::rev_bound->(* [cont.bind]'s bound (leading) operands take their types from the SOURCE
continuation, which is the last operand. Type it first (out of emission
order, via the same hole-slice / [type_trailing_operand] / [fold_operand]
machinery as [type_cont_method_call] / [type_indirect_call] — the
emission order stays bound-operands then continuation) so its signature
lets [annotate_omitted_blocks] fill an omitted block-operand result,
exactly as for the resume family. *)letbound=List.revrev_boundinfunst->letfront_holes=List.fold_left(funacca->acc+count_holesa)0boundinletfront_pending,tail_pending=list_splitfront_holesst.pendinginletc',replay=type_trailing_operandctx(fun()->let_,c'=instructionctxcont_arg{pending=tail_pending;value_loc=None;reported=false}inc')inletsrc=cont_operand_typectxc'inletbound=annotate_omitted_blocksbound(let*@src=srcinbind_bound_typesctx~src~dst:ns)inlettype_body=let*bound'=instructionsctxboundinlet*!src=srcinfinish_cont_bindctxisrcns(bound'@[c'])inletst1,node=type_body{stwithpending=front_pending}inletst2=fold_operandctxc'c'{st1withpending=[]}inreplay();(st2,node)|_->(let*args'=instructionsctxargsinletrecover()=return_statementi(Call({desc=Path(ns,name);info=([||],func.info);hints=Wax_wasm.Hints.none;expected=Unset;},args'))[|Cell.makeError|]inmatchname.descwith|"new"->(matchargs'with|[f']->finish_cont_newctxinsf'|_->Error.operand_count_mismatchctx.diagnostics~location:func.info~expected:1~provided:(List.lengthargs');recover())|"bind"->(* Reached only with no operands — the with-operands case is handled
above; [cont.bind] needs at least the continuation. *)Error.operand_count_mismatchctx.diagnostics~location:func.info~expected:1~provided:0;recover()|_->Error.unknown_intrinsicctx.diagnostics~location:func.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'=typedctxi1inlet*i2'=typedctxi2in(* Snapshot BEFORE the arms below: they unify an [Error] operand cell onto
the other operand's type (recovery), which erases the poison. *)letpoisoned_operand=letpoisonedc=matchCell.get(expression_typectxc)with|Error->true|_->falseinpoisonedi1'||poisonedi2'inletty=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()(* Two nulls compare as [ref.eq (ref.null none) (ref.null none)],
both bottom (hence [eqref]); accept them like the
bottom-reference cases rather than falling into the numeric
comparison below. *)|UnknownRef,(UnknownRef|Null)|Null,(UnknownRef|Null)->()(* 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_unusedthenbegin(* Deferred: the shift lint reads the operand width from [ty], which a
later context can still widen (e.g. [1 << 40] pinned [i64]). *)ctx.deferred_lints:=(fun()->Typing_lint.lint_shiftctxoptyi2')::!(ctx.deferred_lints);Typing_lint.lint_divisionctxopi2';Typing_lint.lint_comparisonctxopi1'i2';Typing_lint.lint_redundantctxopi1'i2'end;(* An operand that already FAILED poisons the result. The arms above treat
[Error] like [Unknown] on purpose — unifying it onto the other operand's
type so the operand cells still get a usable recovery type — but the
VALUE this produces is derived from a reported failure, so a consumer
must not report about it again: without this, calling the result of
[0x1p+1() - 1] said "Expected function" a second time, at the same start
column as the inner call's own report (a duplicated diagnostic the
mutation fuzzer caught). A callee, receiver or argument typed [Error] is
absorbed silently, as it is for a failed call or cast. *)letty=ifpoisoned_operandthenCell.makeErrorelsetyinreturn_expressioni(BinOp(op,i1',i2'))ty|UnOp(op,i')->let*i'=instructionctxi'inlettyp=expression_typectxi'inletty=matchCell.gettypwith|Error->(matchop.descwithNot->i32_cell|Neg|Pos->Cell.makeNumber)|Unknown->(matchop.descwith|Not->i32_cell|Neg|Pos->(* Unify the result with the operand's own cell (as the committed
case below and [Add]/[Sub]/[Mul] do), rather than handing back
a fresh [Number]. [-e] preserves width, so a later pin on the
result — e.g. an [as f64] promote consuming the negation of a
[select] of holes on the polymorphic dead-code stack — must pin
the operand too. A disconnected result cell lets the operand
stay [Unknown] (so [to_wasm] lowers it at the i32 default)
while the result is pinned to another width, an incoherent
negation that lowers to [i32.sub] annotated as that width. *)Cell.settypNumber;typ)|_->(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.expression_type_mismatchctx.diagnostics~location:(sndi'.info)~provided:typ~expected:(Cell.makeInt));i32_cell|Neg|Pos->(matchCell.gettypwith|Valtype{internal=I32|I64|F32|F64;_}|Int|LargeInt|Float|Number->()|_->Error.expression_type_mismatchctx.diagnostics~location:(sndi'.info)~provided:typ~expected:(Cell.makeNumber));typ)inifctx.warn_unusedthenTyping_lint.lint_redundant_unopctxopi';return_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',(Ascribedtastyp))->(* The parenthesized ascription [(e : t)]: a static assertion, not an
operation — the operand must already be a subtype of [t] (subsumption
only, never a conversion or a [ref.cast]) and the expression takes
type [t]. It lowers to no instruction ([To_wasm] emits the operand
alone) and is never simplified away. *)let*!ty=internalizectxtinlet*i'=matchi'.descwith|Hole->(* A bare hole is GROUNDED at the ascribed type ([check_type]
settles the [Unknown] cell) instead of going through
[pop_parameter]. It is counted by [count_holes] like any other
hole, so a pending value is reserved for it — but consuming
that value would also TYPE it, and the value is a residual some
other consumer reconnects to on the re-parse. Under an UNEQUAL
conditional annotation ([(@if $dbg (@then drop))] over a dead
[ref.null extern], then [ref.is_null]) the configurations
disagree on what the hole stands for: the polymorphic floor in
one, the [&?extern] residual in the other, and no single pin
type fits both — typing the value rejects a valid module (the
dead-code-cond-annot-reconnect [uneq] cell; the backing-scan
grid's [ScondNe] cells, 52 of them at depth 3). Even where the
capture is well-typed it re-spells the value: an untyped
[select] grounded this way re-emits with a declared type
([(select (result nullref))] for a bare [(select)], the
[pushblk] cell). So the hole takes its type from the ascription
and leaves the value alone. (The reserved-but-unconsumed pending
is the leftover [with_holes] tolerates.) *)let*i'=return_expressioni'Hole(Cell.makeUnknown)incheck_typectxi'ty;returni'|_->(* [check_instruction] IS the ascription's rule: its fallback arm
is exactly [instruction] then [check_type], and for a
construction / [null] / nested block / [?:] it flows the
ascribed type inward, which is what asserting that type should
do. Its re-inference snapshot is for a binding's join; an
ascription states the type itself, so it is dropped. *)let*i',_=check_instructionctxtyi'inreturni'inreturn_expressioni(Cast(i',typ))ty|Cast(i',typ)->(* An inner cast [(e as t) as u] that [simplify]/[--faithful] would drop as
redundant, but which is load-bearing: dropping the NODE collapses the
PRINTED form to [e as u], and a re-parse then re-defaults [e] and loses
the instruction the double cast lowered to. Remember the inner cast's
type [t] here (the only thing captured before the inner is typed — not its
operand's shape, which may be a [select], a call, …); if the inner cast is
dropped below, decide from the RESULT whether to re-ground it:
- a NULL whose cast type differs from the expected outer type
([(null as &?any) as &?extern] and its mirror): the inner cast types the
null as an anyref so the outer lowers to the cross-hierarchy
[extern.convert_any]; dropped (a bare null satisfies any any-hierarchy
consumer) it collapses to [null as &?extern] = [ref.null extern],
dropping the convert. Per the rule "keep a null's cast when the expected
type differs from the cast type": [is_null_initializer] on the RESULT is
robust (a [select] etc. is not a null), and [t <> u] is the difference.
- [f32.demote_f64] of a width-flexible float ([(sqrt() as f64) as f32]):
the inner [as f64] pins the operand f64 so the outer [as f32] is a
genuine demote; dropped as redundant (f64 IS the float re-parse
default), the width-flexible operand re-defaults toward the outer [f32]
and the demote collapses into an [f32.sqrt] (a precision change). A
bare-literal result is excluded here: its demote is value-inert
([f64.const] then demote equals the [f32.const]). That exclusion is now
inert in practice — the width reconciliation
({!reconcile_widths}) re-grounds such a literal from the width
[From_wasm] recorded on it, so the inner cast comes back anyway and the
demote round-trips exactly (which is the better answer: rounding a
decimal once to f64 and then demoting is not always the f32 rounding of
that decimal). *)letinner_cast_type=matchi'.descwithCast(_,t)->Somet|_->Noneinlet*i'=instructionctxi'in(* The inner cast type to RE-INSERT if it was dropped below (i.e. the result
is no longer a cast) and dropping it would lose the outer instruction. The
wrap is applied at the final kept-cast return, NOT here, so it does not
perturb the cast-fusion / redundancy logic in between. *)letrestore_inner=(* [any] <-> [extern] are different hierarchies: a null cast to one, then
cast to the other, is the cross-hierarchy [extern.convert_any] /
[any.convert_extern] — dropping the inner would re-default the null and
collapse the convert to a plain [ref.null]. A same-hierarchy "different
type" null cast is instead a [ref.cast] (which [--faithful] does not
compare and [simplify] legitimately prunes), so only the cross-hierarchy
case is kept. *)letcrossab=match(top_heap_typectxa,top_heap_typectxb)with|SomeAny,SomeExtern|SomeExtern,SomeAny->true|_->falseinmatchinner_cast_typewith|Someinner_twhenmatchi'.descwithCast_->false|_->true->(match(inner_t,typ)with|(Valtype(Ref{typ=inner_ht;_}),Valtype(Ref{typ=outer_ht;_}))whenreparse_adaptivei'&&crossinner_htouter_ht->(* The operand re-parses type-adaptively (a null, a dead-code hole,
a [select]/[if] of adaptives): under the outer cast it would
take the extern/any hierarchy and collapse the convert into a
plain [ref.null], so keep the inner any/extern cast. The operand
is an anyref by validity, so this is always type-valid. An
anchored (concrete-ref) operand fixes the convert on its own and
is excluded, so the DEFAULT path is not perturbed with a
spurious pin; a wrongly-restored inner on such an operand would
be inert but noisy. *)Someinner_t|ValtypeF64,ValtypeF32->(matchi'.descwithInt_|Float_->None|_->Someinner_t)|_->None)|_->Noneinifctx.warn_unusedthenTyping_lint.lint_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_->None(* Intercepted by the dedicated arm above. *)|Ascribed_->assertfalsein(* 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_|Ascribed_->assertfalse))inletcast_failed=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;false|Somet->ifcastctxty'tthenfalseelsebeginError.invalid_castctx.diagnostics~location:(sndi'.info)ty';trueend|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()");false|_->ifsigned_castctxty'targetthenfalseelsebeginError.invalid_castctx.diagnostics~location:(sndi'.info)ty';trueend)|Valtype_|Functype_|Ascribed_->assertfalse)in(* Poison the result of a failed cast (or one whose operand a prior failed
cast already poisoned) with [Error]. A chain of casts each anchors its
"cannot be cast" error at the shared leftmost operand location, so
without this a single unlowerable operand reports one identical error
per cast in the chain; [Error] is castable to anything ([cast] /
[signed_cast] return [true] for it), so only the first failure is
reported and the rest are absorbed. *)letpoisoned=cast_failed||matchty'_naturalwithError->true|_->falseinifpoisonedthenCell.settyError;(* 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_cast~operand_location:(sndi'.info)ctx~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 on a tree of HOLES is load-bearing whatever its target, even when
that target is the operand's own default width. The rule above reasons that
a still-flexible operand "re-parses at its default", which holds for a
literal tree — but a HOLE carries no value of its own: on a re-parse it
re-connects to the value stranded above it, and what the two settle on is
whatever the ENCLOSING context grounds them to. Dropping the cast hands
that decision to the context: [(_ as f64).floor() as f32] without the
[as f64] re-parses with the demote grounding the whole chain, i.e. as an
[f32.floor] of an [f32.const] — the f64 operation AND its demote both lost
(a wasm-smith round-trip finding, where an interposed [data.drop] made the
receiver a hole). This cast is the only thing stating the type in the
printed form, so it stays. *)letload_bearing_hole=matchnatural_typwith|Some_->defaulting_tree~holes_only:truei'|None->falsein(* So is a cast whose operand is pending a width repair: [From_wasm]
recorded a width for it ([Ast.instr]'s [expected]) that its own defaulting
does not give, so {!reconcile_widths} will pin it — and the pin lands
INSIDE this cast. Dropping the cast as a no-op (its operand having just
folded to the target) would both lose the instruction it lowers to (the
[i32.wrap_i64] of an unpinned [i64] tree) and leave the repair nowhere to
attach. Never true on a decompile whose own pins are in place: the
operand's recorded width is then the width it settles on. *)letoperand_pin_pending=match(i'.expected,natural_typ)with|Ast.Recordedw,Somed->w<>d|_->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.
[--faithful] ([ctx.faithful]) keeps [simplify] off so a redundant
*source* [ref.cast] survives and re-emits, but the decompiler also
inserts type-pin SCAFFOLDING casts — a member-access receiver, a
[call_ref] callee — that [simplify] would drop and that otherwise
re-lower to a spurious [ref.cast] the original lacked. Those pins are
nullable ([cast_ref] / the callee pin in [From_wasm] use
[nullable = true]); a hand-visible redundant up-cast worth keeping is
the non-null form ([ref.cast (ref any)] -> [_ as &any]). So under
[--faithful] the drop still fires for a redundant cast to a NULLABLE ref
target, pruning the common scaffolding, while a non-null redundant cast
is kept. (Compiler-inserted pins and source casts cannot be told apart
in general without provenance — see the [FAITHDRIFT] leg, which does not
compare the cast family for this reason.) *)lettarget_nullable_ref=matchCell.gettywith|Valtype{typ=Ref{nullable=true;_};_}->true|_->falseinletunnecessary_cast=(* A poisoned cast (it failed, or its operand was already poison) is
never redundant, and its [ty] is now [Error] — a type that must not
reach [subtype]'s expected side (whose right-hand assertion excludes
it). This is only reachable on the from-Wasm paths: a hand-written
cast is not simplified, and a failed one exits on the diagnostic. *)(notpoisoned)&&(ctx.simplify||(ctx.faithful&&target_nullable_ref))&&(notload_bearing_literal)&&(notload_bearing_hole)&&(notoperand_pin_pending)&&(notload_bearing_null)&&(notload_bearing_bottom_ref)&&(notload_bearing_cont)&&(not(is_unknown_or_errorty'))&&subtypectxty'tyinifunnecessary_castthenreturn{i'withinfo=([|ty|],sndi'.info)}else(* Re-insert a dropped-but-load-bearing inner cast (see [restore_inner]):
the outer cast is kept here, so wrap its operand back in the inner cast
the drop removed, keeping the printed double cast. [i'] already carries
the inner cast's (result) type in its [info], so the wrapper is
consistent. *)leti'=matchrestore_innerwith|Someinner_t->{i'withdesc=Cast(i',inner_t)}|None->i'inreturn_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))]). The value is pushed first, the
descriptor on top of it, so type them in that (emission) order. *)let*value'=typedctxvalueinlet*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(operand,ty)->let*i'=instructionctxoperandinifis_cont_heaptypectxty.typthenError.invalid_cast_typectx.diagnostics~location:i.info;(* The operand's natural type, before the check below concretises it. *)letop_natural=Cell.get(expression_typectxi')in(* Check the operand, and poison the result on failure (below), as the
chained SIMD lane op does: [is] yields an [i32], so a chain
[(x is &s) is &s] hands the outer [is] a non-reference operand of its
own and — both anchored at the shared leftmost operand — reports an
identical error at the same location. The innermost is the one to fix.
An operand that is ALREADY poison satisfies the check silently and
poisons the result too, so the chain stays quiet past its first link. *)letoperand_ok=matchlet*@typ=top_heap_typectxty.typininternalizectx(Ref{nullable=true;typ})with|Sometyp->letty'=expression_typectxi'inletok=subtypectxty'typinifnotokthenError.expression_type_mismatchctx.diagnostics~location:(sndi'.info)~provided:ty'~expected:typ;ok|None->trueinletpoisoned=(notoperand_ok)||matchop_naturalwithError->true|_->falsein(ifctx.warn_unused&¬ctx.simplifythenlet>@target=internalizectx(Refty)inlint_ref_cast~operand_location:(sndi'.info)ctx~location:i.info~is_test:trueop_natural(Cell.gettarget));return_expressioni(Test(i',ty))(ifpoisonedthenCell.makeErrorelsei32_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(Members.R_memoryat)|Getnamewhentable_receiverctxname->letat,rt=Option.get(Tbl.find_optctx.tablesname)inrecord_membersctx.member_completionsfield.info(Members.R_table(at,rt))|_->(matchMembers.numeric_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(Members.R_structfields);matchArray.find_map(funf->letnm=field_namefandtyp=field_typefinifnm.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(Members.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_structctx.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_structctx.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_structctx.diagnostics~location:(sndi'.info);Noneinreturn_expressioni(GetDescriptori')ty|StructSet(i1,field,i2)->(* Emission order: the struct receiver, then the stored value. *)let*i1'=typedctxi1in(* 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(Members.R_structfields);matchArray.find_map(funf->letnm=field_namefinifnm.desc=field.descthenSome(field_typef)elseNone)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_structctx.diagnostics~location:i1.info;Noneinlet*i2'=matchexpectedwith|Somecell->let*i2',_=typed_checkctxcelli2inreturni2'|None->typedctxi2inreturn_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);hints=Wax_wasm.Hints.none;expected=Unset;},i2'))typ|ArrayGet(i1,i2)->((* Emission order: the array, then the index. *)let*i1'=typedctxi1inlet*i2'=typedctxi2incheck_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_arrayctx.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)in(* The table name is a static immediate; the index then the value are the
emitted operands. *)let*i2'=typedctxi2incheck_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',_=typed_checkctxcelli3inreturni3'|None->typedctxi3inreturn_statementi(ArraySet({desc=Gettabname;info=([||],recv.info);hints=Wax_wasm.Hints.none;expected=Unset;},i2',i3'))[||]|ArraySet(i1,i2,i3)->((* Emission order: the array, the index, then the stored value. *)let*i1'=typedctxi1inlet*i2'=typedctxi2incheck_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',_=typed_checkctxcelli3inreturni3'|None->typedctxi3inreturn_statementi(ArraySet(i1',i2',i3'))[||]|Error->(* Receiver already failed to type; recover silently (still type the
value so its holes are consumed). *)let*i3'=typedctxi3inreturn_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'=typedctxi3inError.unknown_operand_typectx.diagnostics~location:i1.info;return_statementi(ArraySet(i1',i2',i3'))[||]|_->let*i3'=typedctxi3inError.expected_arrayctx.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|Local(ty,def)->ctx.read_locals:=IntSet.adddef.loc_start.pos_cnum!(ctx.read_locals);ifnot(StringSet.memidx.descctx.initialized_locals)thenreport_uninitializedctxidx;(* 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;})|Poisoned->(* Already reported at the definition; the Error poison keeps the
use quiet. *)Cell.makeError|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,{desc=Getidx;info=idx.info;hints=Wax_wasm.Hints.none;expected=Unset;},i');}inlet*checked=matchresolvedwith|Local(Someity,_)|Global(_,Someity)->let*c,_=check_instructionctx(valtype_cellity)to_checkinreturnc|Local(None,_)|Global(_,None)|Func_ref_|Poisoned|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"else(* The only place a global is written, so also where a [mut] global is
recorded as actually assigned (for [unnecessary-mut]). *)Hashtbl.replacectx.assigned_globalsidx.desc()|Func_ref_->Error.not_assignablectx.diagnostics~location:idx.infoidx|Poisoned->(* already reported at the definition *)()|Unbound->(* A compound assignment's desugared read (the [Get idx] injected
into [to_check] above) already reported the unbound name at this
same span; reporting the write too would duplicate it. *)ifop=NonethenError.unbound_namectx.diagnostics~location:idx.info~suggestions:(set_suggestionsctxidx.desc)"variable"idx);(ifctx.suggest&&op=Nonethenmatchresolvedwith|Local_|Global_->Typing_suggest.suggest_compound_assignmentctx~location:i.infoidxi'|Func_ref_|Poisoned|Unbound->());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|Local(None,_)->(* 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')|Poisoned->(* Already reported at the definition; recover like a poison local. *)let*i'=instructionctxi'inreturn_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',reinfer=check_instructionctx(valtype_cellity)i'inletneeded=reinfer_needed~drop_supertypectxreinfer(valtype_cellity)inOption.iter(funname->ctx.locals<-StringMap.addname.Annot.desc(Someity,name.info)ctx.locals;ctx.local_decls:=name::!(ctx.local_decls);mark_initializedctxname.desc)name_opt;letdrop=ctx.simplify&¬neededin(* The same redundancy, offered as a quick fix for hand-written Wax:
delete the ': t', underlining just the type. The name's end anchors
the deletion; for the anonymous [_: t = e] drop the name is the
single-character [_] at the statement's start. *)(ifctx.suggest&¬neededthenletname_end=matchname_optwith|Somename->name.info.loc_end|None->{i.info.loc_startwithpos_cnum=i.info.loc_start.pos_cnum+1;}inTyping_suggest.suggest_redundant_annotationctx~name_end~boundary:(sndi'.info).loc_start);return_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. A single binding with
an annotation is handled by the branch above, so any annotation
here is absent — no redundancy to suggest. *)[fst(bind_let_value~init:i'ctx~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:(sndi'.info)~expected:n~provided:(Array.lengthresult_types);letsrc=Array.of_listbindingsinList.mapi(funidxbinding->letresult_ty=ifidx<Array.lengthresult_typesthenresult_types.(idx)elseCell.makeErrorinletbinding',redundant=bind_let_valuectx~location:(sndi'.info)result_tybindingin(* Suggest dropping a redundant annotation in a tuple binding
([let (a: t, b) = e] -> [let (a, b) = e]). The span after this
binding's type is the next binding's name (or, for the last,
the initializer); [annotation_spans] finds where the type ends
before that boundary. *)(ifctx.suggest&&redundantthenmatchfstbindingwith|Somename->letboundary=ifidx+1<nthenmatchfstsrc.(idx+1)with|Somenm->Somenm.info.loc_start|None->NoneelseSome(sndi'.info).loc_startinOption.iter(funboundary->Typing_suggest.suggest_redundant_annotationctx~name_end:name.info.loc_end~boundary)boundary|None->());binding')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.Annot.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(param_typep))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.operand_count_mismatchctx.diagnostics~location:tag.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(param_typep))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:Ast.ident),_)::r->(matchList.assoc_optl.descseenwith|Someprev_loc->Error.dispatch_duplicate_armctx.diagnostics~location:l.info~prev_locl|None->());check_dups((l.desc,l.info)::seen)rincheck_dups[]arms;letindex,_=reject_control_holesctx~construct:"dispatch"~role:"index"~recovery:(Ast.Int"0")indexin(* 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]. The scrutinee is threaded into the lowering and typed
there (so a hole draws its type from the enclosing test); it is then
recovered from the typed form rather than typed a second time. *)letscrutinee,scrut_had_holes=reject_control_holesctx~construct:"match"~role:"scrutinee"~recovery:Ast.Nullscrutineeinletlabels=match_labelsi.infoarmsinletlowered=Ast_utils.lower_match~block_info:i.info~labels~scrutinee~arms~defaultinlettyped=blockctxi.infoNone[||][||][||]loweredinletarms',default',scrut_opt=(* On an erroneous scrutinee the typed lowering may not peel apart into
the expected block nesting; recover with empty arm/default bodies (the
module is already being rejected — the rebuilt node only feeds the
formatter/editor) and type the scrutinee on its own, rather than
crashing. *)tryrebuild_matchtypedarmswithMatch_shape->(List.map(fun(pat,(orig:(_instrlist,location)Ast.annotated))->(pat,{origwithdesc=[]}))arms,[],None)inletscrut'=match_recover_scrutineectxscrutineescrut_optin(* 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).
Skip it when the scrutinee was a rejected hole — the replacement
[Unreachable] is not a reference and would cascade a spurious error. *)(ifnotscrut_had_holesthenmatchmatch_scrut_reftypectxscrut'with|Some_->()|None->Error.expected_refctx.diagnostics~location:(sndscrut'.info));return_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(param_typep))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]. *)letcond,_=reject_control_holesctx~construct:"while"~role:"condition"~recovery:(Ast.Int"0")condinletlowered=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(param_typep))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}->(* A conditional annotation in expression position (the statement path,
[toplevel_instruction], intercepts the statement-level ones and types
the selected branch spliced): the selected branch is typed as an
isolated block, the other left for the run that owns it. *)letsel_then=ctx.selecti.infoinletbranchselected(body:_Annot.annotated)={bodywithAnnot.desc=(ifselectedthenblockctxi.infoNone[||][||][||]body.Annot.descelseplaceholder_instrsbody.Annot.desc);}inreturn_statementi(If_annotation{cond;then_body=branchsel_thenthen_body;else_body=Option.map(branch(notsel_then))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(fun(p:(_,location)Ast.annotated)->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_armsctxlabel~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_catchesctxlabel~resultscatchescatch_allinreturn_statementi(Try{label;typ;block={blklocwithdesc=body'};catches;catch_all;})results)|_->assertfalse(* only invoked on a block-like construct *)andtype_mem_method_callctxifuncrecvmemname(meth:Ast.ident)args=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.expression_type_mismatchctx.diagnostics~location:(sndvalue'.info)~provided:vty~expected:(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);hints=Wax_wasm.Hints.none;expected=Unset;},meth);info=([||],func.info);hints=Wax_wasm.Hints.none;expected=Unset;},args'))resultandtype_atomic_method_callctxifuncrecvmemname(meth:Ast.ident)familyargs=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. An [Unknown] operand (a hole on the polymorphic dead-code
stack) is pinned to the flexible [Int] rather than left [Unknown]: the RMW
is a concrete op that [To_wasm] must emit, so an [Unknown] result — unlike a
flexible literal tree — cannot be re-parsed at a cast's width and would drop
the cast ([(m.atomic_rmw32(_, _) as i64_u)] losing its extend). As [Int] it
defaults to i32 like any flexible integer, yet a consumer can still pin it to
i64 (e.g. an i64 memory address), so both round-trip. [Error] (already
reported) stays the untouched bottom. *)letcheck_valuev=letvty=expression_typectxvinmatchCell.getvtywith|Unknown->Cell.setvtyInt;vty|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);hints=Wax_wasm.Hints.none;expected=Unset;},meth);info=([||],func.info);hints=Wax_wasm.Hints.none;expected=Unset;},args'))resultandtype_simd_mem_method_callctxifuncrecvmemname(meth:Ast.ident)args=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:meth.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);hints=Wax_wasm.Hints.none;expected=Unset;},meth);info=([||],func.info);hints=Wax_wasm.Hints.none;expected=Unset;},args'))resultandtype_mem_mgmt_callctxifuncrecvname(meth:Ast.ident)args=let_,at=Option.get(Tbl.find_optctx.memoriesname)inletaddr()=address_cellatinleti32()=i32_cellinletrecv'={desc=Getname;info=([||],recv.info);hints=Wax_wasm.Hints.none;expected=Unset;}inletmkargs'=Ast.Call({desc=StructGet(recv',meth);info=([||],func.info);hints=Wax_wasm.Hints.none;expected=Unset;},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);hints=Wax_wasm.Hints.none;expected=Unset;}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);hints=Wax_wasm.Hints.none;expected=Unset;}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_callctxifuncrecvname(meth:Ast.ident)args=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);hints=Wax_wasm.Hints.none;expected=Unset;}inletmkargs'=Ast.Call({desc=StructGet(recv',meth);info=([||],func.info);hints=Wax_wasm.Hints.none;expected=Unset;},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);hints=Wax_wasm.Hints.none;expected=Unset;}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);hints=Wax_wasm.Hints.none;expected=Unset;}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_callctxifunca(meth:Ast.ident)jvn=(* Emission order: the array receiver, then index, value, count. *)let*a'=typedctxainlet*j'=typedctxjinlet*v'=typedctxvinlet*n'=typedctxnincheck_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.expression_type_mismatchctx.diagnostics~location:(sndv'.info)~provided:ty'~expected: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_arrayctx.diagnostics~location:a.info);return_statementi(Call({desc=StructGet(a',meth);info=([||],func.info);hints=Wax_wasm.Hints.none;expected=Unset;},[j';v';n']))[||]andtype_array_copy_callctxifunca1(meth:Ast.ident)i1a2i2n=(* Emission order: dest array, dest index, src array, src index, count. *)let*a1'=typedctxa1inlet*i1'=typedctxi1inlet*a2'=typedctxa2inlet*i2'=typedctxi2inlet*n'=typedctxnincheck_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_arrayctx.diagnostics~location:a1.info);return_statementi(Call({desc=StructGet(a1',meth);info=([||],func.info);hints=Wax_wasm.Hints.none;expected=Unset;},[i1';a2';i2';n']))[||]andtype_array_init_callctxifunca(meth:Ast.ident)arg1rest=(* Emission order: the array receiver, then the dest/src/len operands (the
segment [arg1] is a static immediate typed below). *)let*a'=typedctxainmatcharg1.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_arrayctx.diagnostics~location:a.info);letseg'={desc=Getseg;info=([||],sinfo);hints=Wax_wasm.Hints.none;expected=Unset;}inreturn_statementi(Call({desc=StructGet(a',meth);info=([||],func.info);hints=Wax_wasm.Hints.none;expected=Unset;},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)in(* Not when the RECEIVER is already poison: a failed call recovers with an
[Error] value, so a chained [m.init(…).init(…)] would report the same
rejection once per link — and, sharing the chain's start column, the
reports render as one repeated [line:col: message]. The innermost
failure is the one to fix; the rest follow from it. This is the poison
convention the cast chain uses, read through the error-free
[expression_type_opt] so the check itself reports nothing. *)(matchOption.mapCell.get(Typing_env.expression_type_opta')with|SomeError->()|_->Error.invalid_management_callctx.diagnostics~location:i.infometh.desc);return_statementi(Call({desc=StructGet(a',meth);info=([||],func.info);hints=Wax_wasm.Hints.none;expected=Unset;},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_recoveryctxifuncrecv(meth:Ast.ident)args=let*recv'=typedctxrecvinlet*args'=instructionsctxargsinletexpected=matchmeth.descwith"fill"->3|_->4inifList.lengthargs'<>expectedthenError.operand_count_mismatchctx.diagnostics~location:func.info~expected~provided:(List.lengthargs');return_statementi(Call({desc=StructGet(recv',meth);info=([||],func.info);hints=Wax_wasm.Hints.none;expected=Unset;},args'))[||]andtype_binary_intrinsic_callctxifunci1(meth:Ast.ident)opargs=(* A scalar binary intrinsic on a value receiver ([x.min(y)]): the receiver is
pushed first, then the operand. *)let*i1'=typedctxi1inlet*args'=instructionsctxargsinletis_int=matchopwith"rotl"|"rotr"->true|_->falseinletcallargs''=Ast.Call({desc=StructGet(i1',meth);info=([||],func.info);hints=Wax_wasm.Hints.none;expected=Unset;},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.operand_count_mismatchctx.diagnostics~location:func.info~expected:1~provided:(List.lengthargs');return_expressioni(callargs')(expression_typectxi1')andtype_unary_intrinsic_callctxifuncrecv(meth:Ast.ident)=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_arrayctx.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);hints=Wax_wasm.Hints.none;expected=Unset;},[]))tyandtype_simd_vector_op_callctxifuncrecv(meth:Ast.ident)args=letop=Option.get(Simd.classifymeth.desc)inletnimm=matchop.immwithNo_imm->0|Lane_->1|Shuffle->16in(* Emission order: the v128 (or scalar, for splat) receiver, then the trailing
stack operands. The leading [nimm] lane immediates are static — not pushed,
never holes — so type them plainly, between the two, without a hole slice or
a hole-order contribution. *)let*recv'=typedctxrecvinletimms,stack_args=list_splitnimmargsinlet*imms'=plain_instructionsctximmsinlet*stack_args'=instructionsctxstack_argsinletargs'=imms'@stack_args'inletnstack_extra=List.lengthop.operands-1inletnargs=List.lengthargs'inifnargs<>nimm+nstack_extrathenError.operand_count_mismatchctx.diagnostics~location:func.info~expected:(nimm+nstack_extra)~provided:nargs;(* Check the receiver, and poison the result on failure (below). A chained
lane op [x.extract_lane_i32x4(0).extract_lane_s_i16x8(7)] anchors each
receiver mismatch at the shared leftmost operand, so without poisoning both
the inner receiver (x) and the outer receiver (the inner call's result)
report an identical error at the same location. *)letrecv_ty=expression_typectxrecv'inletrecv_expected=simd_cell(List.hdop.operands)inletrecv_ok=subtypectxrecv_tyrecv_expectedinifnotrecv_okthenError.expression_type_mismatchctx.diagnostics~location:(sndrecv'.info)~provided:recv_ty~expected:recv_expected;letrecv_poisoned=(notrecv_ok)||matchCell.getrecv_tywithError->true|_->falseinletlane_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.integer_literal_requiredctx.diagnostics~location:(snda.info)elseletoperand=1+(k-nimm)inifoperand<List.lengthop.operandsthencheck_typectxa(simd_cell(List.nthop.operandsoperand)))args';letresult=ifrecv_poisonedthen[|Cell.makeError|]elsematchop.resultwithSomet->[|simd_cellt|]|None->[||]inreturn_statementi(Call({desc=StructGet(recv',meth);info=([||],func.info);hints=Wax_wasm.Hints.none;expected=Unset;},args'))resultandtype_simd_free_intrinsic_callctxifuncns(name:Ast.ident)args=letfull=Simd.free_fullname.descinletcallee={desc=Path(ns,name);info=([||],func.info);hints=Wax_wasm.Hints.none;expected=Unset;}inlet*args'=instructionsctxargsinifnot(Simd.is_free_intrinsicfull)then(Error.unknown_intrinsicctx.diagnostics~location:func.infons.descname.desc;return_expressioni(Call(callee,args'))(Cell.makeError))else((matchSimd.const_shape_of_namefullwith|Someshape->letarity=Simd.const_arityshapeinifList.lengthargs'<>aritythenError.operand_count_mismatchctx.diagnostics~location:func.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.number_literal_requiredctx.diagnostics~location:(snda.info))args'|None->(* The only non-const free intrinsic is [bitselect], which takes exactly
three v128 operands; check its arity as the const branch checks
theirs, so an under/over-application is rejected here rather than
slipping through to an unrelated stack error during lowering. *)ifList.lengthargs'<>3thenError.operand_count_mismatchctx.diagnostics~location:func.info~expected:3~provided:(List.lengthargs');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
its {!reinfer} — what an unannotated binding would re-infer it to, standalone
— so the binding/construct site can decide whether the annotation is
load-bearing ([reinfer_needed]). A construction literal can fill an omitted
type name from [expected] and shed a redundant one; every other expression
delegates to [instruction] and snapshots its own type. [expected] is the
[Unknown] sentinel when [check_instruction] is entered from [instruction] with
no context (synthesis). *)andcheck_instructionctxexpected(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.Annot.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|Somename->letredundant=name_redundanttyp||field_uniqueinifctx.simplify&&redundantthenNoneelsebeginifctx.suggest&&redundantthenTyping_suggest.suggest_drop_type_namectxname;Sometypendin(* 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|_->()in(* The re-inference of a construction node (array / default struct /
descriptor construction). It re-infers to its own result standalone when its
output still names the type — an emitted array/struct-default *name*, or a
descriptor construction whose descriptor [d] pins the type. A name-less form
(the name dropped as redundant, or absent, and no descriptor) cannot
re-infer without the context, so it is [Uninferrable]. A name-carrying array
or struct-default is [Named] (its written name, which a mere-supertype
annotation does not pin, is load-bearing for round-trip stability, so the
annotation drops only on exact equality); a descriptor construction, which
re-infers structurally through [d] with no written name, is a narrowable
[Typ]. The [Struct] arm computes its own re-inference inline (field-unique,
hence structural and narrowable). *)letconstruction_reinfernode=letnamed=matchnode.descwith|Array(name,_,_)|ArrayDefault(name,_)|ArrayFixed(name,_)|ArraySegment(name,_,_,_)|StructDefaultname->Option.is_somename|_->falseinmatchstandalone_valtypectx(expression_typectxnode)with|Some_whennamed->named_reinfer_of_cell(expression_typectxnode)|Some_->(matchnode.descwith|StructDesc_|StructDefaultDesc_->reinfer_of_cell(expression_typectxnode)|_->Uninferrable)|None->Uninferrableinmatchi.descwith|Struct(ty,fields)->ifctx.suggestthenList.iter(fun(name,written)->Typing_suggest.suggest_punningctxnamewritten)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:Ast.ident),written)->let*l=previnifwritten=Nonethenrecord_punctx.pun_spansname.info;let*fi'=typedctx(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=field_namefieldandf=field_typefieldinmatchList.find_opt(fun((idx:Ast.ident),_)->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',_=typed_checkctxcelli'inreturni'|None->typedctxi'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(* A source field with no counterpart in the declaration (a
field-count/name mismatch, already reported) is skipped by the
fold above and left untyped: with per-field hole slices its slot in
the pending values is simply dropped (no threaded arg list to keep
balanced, no separate hole-order pass to feed), and the whole
construction is being rejected anyway. *)(* 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(* What a bare [{..}] re-infers to: when the fields alone name this exact
type — [field_match] names [node]'s own result heap type — the bare
construction re-resolves to it standalone, so it reports [Typ] of its own
result and the binding site compares that to its annotation ([let x: T =
{..}] drops the [: T] when [T] is that type). When the fields are
ambiguous a name-less [{..}] cannot re-infer at all, so it is
[Uninferrable] and any surrounding annotation is load-bearing. Read the
result back from [node] rather than the branch-local [typ], so no mutable
cell need escape the [let*!] arms. *)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_resultthenreinfer_of_cell(expression_typectxnode)elseUninferrable)|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(field_typefield))fields)thenError.not_defaultablectx.diagnostics~location:typ.info;require_no_descriptortyp;letemitted=emitted_nametytyp~field_unique:falseinlet*!result=construction_resulttypinreturn_expressioni(StructDefaultemitted)resultinreturn(node,construction_reinfernode)|StructDesc(d,fields)->(* [{ descriptor(d) | fields }] lowers to [struct.new_desc], which pushes
the field values then the descriptor on top. But the descriptor's type
[Y] (with [Y describes X]) fixes the struct type [X] the fields are
checked against, so the descriptor must be typed FIRST — out of emission
order, which the explicit hole slices make sound for the stack and
[type_trailing_operand] makes sound for the initialized-local analysis
(the descriptor, emitted last, may read a local a field [local.tee]s, and
its own tees must not leak back into the fields). Split the pending
values: the fields take the front slice, the descriptor the tail; type
the descriptor against the tail with a fresh hole state, the fields in
emission order over the front slice, then fold the descriptor into the
hole-order check and replay its init-locals effects as the last
operand. *)funst->ifctx.suggestthenList.iter(fun(name,written)->Typing_suggest.suggest_punningctxnamewritten)fields;letfront_holes=List.fold_left(funacc(_,w)->acc+Option.fold~none:0~some:count_holesw)0fieldsinletfront_pending,tail_pending=list_splitfront_holesst.pendinginletd,replay=type_trailing_operandctx(fun()->let_,d=instructionctxd{pending=tail_pending;value_loc=None;reported=false}ind)inlettarget=descriptor_reftypectx~location:i.info~nullable:falsedinlettype_body=matchOption.map(fun(t:reftype)->named_heaptypet.typ)targetwith|None|SomeNone->let*fields'=List.fold_left(funprev((name:Ast.ident),written)->let*l=previnifwritten=Nonethenrecord_punctx.pun_spansname.info;let*fi'=typedctx(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=field_namefieldandf=field_typefieldinmatchList.find_opt(fun((idx:Ast.ident),_)->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',_=typed_checkctxcelli'inreturni'|None->typedctxi'inreturn((name,Option.map(fun_->checked)written)::l))(return[])field_typesin(* A source field absent from the declaration is left untyped, as in
the [Struct] arm: its hole-slice slot is dropped and the
construction is rejected regardless. *)let*!result=construction_resulttypinreturn_expressioni(StructDesc(d,List.revfields'))resultinletst1,node=type_body{stwithpending=front_pending}inletst2=fold_operandctxdd{st1withpending=[]}inreplay();(st2,(node,construction_reinfernode))|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(field_typefield))fields)thenError.not_defaultablectx.diagnostics~location:i.info;let*!result=construction_resulttypinreturn_expressioni(StructDefaultDescd)resultinreturn(node,construction_reinfernode)|Array(ty,i1,i2)->let*node=matchresolve_namety~missing:(fun()->Error.cannot_infer_array_typectx.diagnostics~location:i.info)with|None->let*i1'=typedctxi1inlet*i2'=typedctxi2incheck_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 emission order and hole slices. *)letelt=matchlookup_array_typectxtypwith|Somefield'->internalizectx(unpack_typefield')|None->Noneinlet*i1'=matcheltwith|Somecell->let*i1',_=typed_checkctxcelli1inreturni1'|None->typedctxi1inlet*i2'=typedctxi2incheck_typectxi2'i32_cell;letemitted=emitted_nametytyp~field_unique:falseinlet*!result=construction_resulttypinreturn_expressioni(Array(emitted,i1',i2'))resultinreturn(node,construction_reinfernode)|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,construction_reinfernode)|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'=typedctxi'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',_=typed_checkctxcelli'inreturni'|None->typedctxi'inreturn(i'::l))(return[])instrsinletemitted=emitted_nametytyp~field_unique:falseinlet*!result=construction_resulttypinreturn_expressioni(ArrayFixed(emitted,List.revinstrs'))resultinreturn(node,construction_reinfernode)|ArraySegment(ty,seg,off,len)->let*node=matchresolve_namety~missing:(fun()->Error.cannot_infer_array_typectx.diagnostics~location:i.info)with|None->let*off'=typedctxoffinlet*len'=typedctxlenincheck_typectxoff'i32_cell;check_typectxlen'i32_cell;return_expressioni(ArraySegment(None,seg,off',len'))(Cell.makeError)|Sometyp->let*off'=typedctxoffinlet*len'=typedctxlenincheck_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,construction_reinfernode)|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:Ast.ident={desc="<string>";info=i.info}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(* A bare string builds the canonical [mut i8] array, naming no source type
at all, so record a use of that canonical index — every definition the
array deduplicates onto is used by the literal. The mirror of the
validator's [string_type_reference]; resolved in the unused-field pass.
A string that adopted a named array type resolved (and so marked) that
name itself. *)ifctx.warn_unused&&typ.desc=string_typ.descthenOption.iter(funid->letr=(!(ctx.origin),id)inifnot(List.memr!(ctx.canonical_type_references))thenctx.canonical_type_references:=r::!(ctx.canonical_type_references))(resolve_type_namectx.diagnosticsctx.type_contextstring_typ);(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))resultin(* A bare string re-infers to its natural [<string>] type (or the emitted
non-default name resolves the same); it never fails to type, so it
reports [Typ] of that natural type and the binding site drops a
redundant annotation exactly as before. *)return(node,matchstring_valtype_naturalwith|Someiv->Typ(valtype_celliv)|None->Uninferrable)|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;(* The elided form is a bare [null], which re-infers the *floating* [&?none]
(and would lower to [ref.null none]) — not the type the cast pinned — so
report that: a surrounding annotation stays load-bearing (its join with a
concrete sibling still rescues it, per [join_reinfer]). A cast kept in the
output re-infers its own pinned type. This discharges what
[is_null_initializer] used to special-case at the binding sites. *)letreinfer=matchi'.descwith|Null->Typ(valtype_cell(ref_none_valtype~nullable:true))|_->reinfer_of_cell(expression_typectxi')inreturn(i',reinfer)|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 delivers a value
to its exit like the branch tails, but that value is invisible to the
per-branch re-inference below (a branch ending in such a [br] reads as
[Diverges]); see [label_delivers] for how the annotation is kept for it. *)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|]in(* Each branch reports its own fall-through re-inference (see
[block_with_keep]); the if's is their join. *)(* Each arm is anchored at ITS OWN span, not the [if]'s: an output underflow
is reported at the block's closing token, so two arms sharing the [if]'s
span rendered their two distinct reports as one [line:col: message] twice
over (a fuzz DIAG_DUP). *)letif_desc,if_reinfer=block_with_keepctxif_block.infolabel[||]resultsresultsif_block.descinletif_block'={if_blockwithdesc=if_desc}inletelse_block',else_reinfer=matchelse_blockwith|Someb->letelse_desc,else_reinfer=block_with_keepctxb.infolabel[||]resultsresultsb.descin(Some{bwithdesc=else_desc},else_reinfer)|None->ifnot(missing_else_okctx[||]results)thenError.if_without_elsectx.diagnostics~location:i.info;(* No else: the missing branch delivers no value, so it drops out of
the join and the [then] branch decides (recovery for what is
already an [if_without_else] error). *)(None,Diverges)in(* 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;(* An annotated [=> t] equal to what the context pins is redundant: dropped
on [simplify] (Wasm->Wax), and offered as a quick fix under [ctx.suggest]
(deleting the [=> t]). Both key on the same test. *)letredundant=block_result_redundantctxtyp~expected~result_cellinifctx.suggest&&redundantthenTyping_suggest.suggest_if_resultctxcond.info.loc_endif_block.info.loc_start;lettyp=ifomittedthenmatchstandalone_valtypectxexpectedwith|Someiv->{typwithresults=[|iv.typ|]}|None->typelseifctx.simplify&&redundantthen{typwithresults=[||]}elsetypin(* The caller's binding annotation (e.g. [let x: T = ..]) is redundant iff
an unannotated [let] would re-infer it — i.e. iff the join of the
branches' own re-inference already equals it (decided at the binding
site by [reinfer_needed]). Reading each branch's re-inference upward,
rather than its result cell (which the annotation flowed into), is what
makes a context-typed tail — a flexible literal, a nested [if], a bare
[null] rescued by a sibling — no longer look spuriously redundant.
A value delivered by a [br] to the if's own label also reaches the exit
but is invisible to the fall-through join above ([from_wasm] does emit
this — a [br 0] inside an [if (result T)] round-trips to [br 'l ..]).
Its re-inference is not tracked, and reading its resolved cell would miss
an un-named construction whose name was dropped because the label pinned
the type (it would recompile only under the annotation). So keep the
annotation whenever the if's label was branched to: [ctx.used_labels]
records the label's definition site when [branch_target] resolved a [br]
to it, and the label is unique and in scope only within these branches.
Conservative (it keeps even for an inferrable delivery), but a [br] to an
[if]'s own label is rare in decompiled code, and this never wrongly
drops. *)letlabel_delivers=matchlabelwith|Somel->IntSet.meml.info.loc_start.pos_cnum!(ctx.used_labels)|None->falseinletreinfer=iflabel_deliversthenUninferrableelsejoin_reinferctxif_reinferelse_reinferinlet*node=return_statementi(If{label;typ;cond=cond';if_block=if_block';else_block=else_block';})resultsinreturn(node,reinfer)(* 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 block's re-inference (for a
surrounding binding annotation) is the join of every value reaching its exit
— the fall-through plus branched/caught values, all collected by
[block_keep_bool] — or, when its own result annotation survives in the
output, that annotation (the block pins its type itself); see
[block_keep_reinfer]. *)|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|]instrsinletkept_annotation=typ.results<>[||]&¬(ctx.simplify&&block_result_redundantctxtyp~expected~result_cell)inletreinfer=block_keep_reinferctx~loc:i.info~result:result_cell~kept_annotationrincheck_subtypectx~location:i.inforesult_cellexpected;lettyp=context_block_typctx~keyword:"do"i.info.loc_startblkloc.info.loc_starttyp~expected~result_cellinlet*node=return_statementi(Block{label;typ;block={blklocwithdesc=instrs'}})[|result_cell|]inreturn(node,reinfer)|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:[||]instrsinletkept_annotation=typ.results<>[||]&¬(ctx.simplify&&block_result_redundantctxtyp~expected~result_cell)inletreinfer=block_keep_reinferctx~loc:i.info~result:result_cell~kept_annotationrincheck_subtypectx~location:i.inforesult_cellexpected;lettyp=context_block_typctx~keyword:"loop"i.info.loc_startblkloc.info.loc_starttyp~expected~result_cellinlet*node=return_statementi(Loop{label;typ;block={blklocwithdesc=instrs'}})[|result_cell|]inreturn(node,reinfer)|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;letkept_annotation=typ.results<>[||]&¬(ctx.simplify&&block_result_redundantctxtyp~expected~result_cell)inletreinfer=block_keep_reinferctx~loc:i.info~result:result_cell~kept_annotationrincheck_subtypectx~location:i.inforesult_cellexpected;lettyp=context_block_typctx~keyword:"try"i.info.loc_startblkloc.info.loc_starttyp~expected~result_cellinlet*node=return_statementi(TryTable{label;typ;block={blklocwithdesc=body'};catches})[|result_cell|]inreturn(node,reinfer)|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_catchesctxlabel~results:[|r|]catchescatch_allinletkept_annotation=typ.results<>[||]&¬(ctx.simplify&&block_result_redundantctxtyp~expected~result_cell)inletreinfer=block_keep_reinferctx~loc:i.info~result:result_cell~kept_annotationrincheck_subtypectx~location:i.inforesult_cellexpected;lettyp=context_block_typctx~keyword:"try"i.info.loc_startblkloc.info.loc_starttyp~expected~result_cellinlet*node=return_statementi(Try{label;typ;block={blklocwithdesc=body'};catches;catch_all;})[|result_cell|]inreturn(node,reinfer)|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. Like an [if], the [?:]'s re-inference is the join of its two
value branches' (via [join_reinfer]) — so a bare [null] alongside a typed
sibling gains the same precision the [if] arm has (the join rescues it),
rather than the coarser disjunction of per-branch keep-bools. *)let*i2',reinf2=typed_checkctxexpectedi2inlet*i3',reinf3=typed_checkctxexpectedi3inlet*i1'=typedctxi1incheck_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,join_reinferctxreinf2reinf3)|_->let*i'=instructionctxiin(* Snapshot the value's own type BEFORE [check_type] mutates the cell: this
is what an unannotated binding would re-infer it to. Held flexible so a
join with a concrete sibling absorbs it (see [reinfer_of_cell]). *)letreinfer=reinfer_of_cell(expression_typectxi')inifhas_expectationexpectedthencheck_typectxi'expected;return(i',reinfer)(* 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
re-inference. Used for an annotated global initializer (a constant expression). *)andcheck_toplevelctxexpectedi=with_holesctxi(fun()->check_instructionctxexpectedi)(* 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',_=typed_checkctxparams.(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 (a plain [check_instruction] would discard it, as
[has_expectation] is false for a [Collecting] cell). When the cell
carries a declared result (an annotation under test, or the type the
surrounding context pins), [subtype] validates the value against it
per-delivery, so [check_subtype] reports a [br]/catch carrying the wrong
type precisely at its site; a fully-inferred cell ([declared = None])
records without constraint, so this never fires spuriously. *)let*i'=instructionctxiincheck_subtypectx~location:(sndi'.info)(expression_typectxi')ty;returni'|[|ty|]->let*i',_=check_instructionctxtyiinreturni'|_->let*i'=instructionctxiincheck_subtypesctx~location:(sndi'.info)(fsti'.info)expected;returni'andtype_indirect_callctxii'l=(* Arguments are pushed first, then the callee reference, then [call_ref]. The
callee's function type gives the parameter types the arguments are checked
against (so a struct/array literal argument can be inferred and drop its
name), so the callee is typed FIRST — out of emission order, made sound for
the stack by the explicit hole slices (front for the arguments, tail for the
callee) and for the initialized-local analysis by [type_trailing_operand]
(the callee, emitted last, may read a local an argument [local.tee]s, and its
own tees must not leak back into the arguments). Then the arguments are typed
in emission order over the front slice and the callee is folded in and its
init-locals effects replayed as the last operand, matching [to_wasm]. The
error arms still synthesize the arguments for recovery. *)funst->letfront_holes=List.fold_left(funacca->acc+count_holesa)0linletfront_pending,tail_pending=list_splitfront_holesst.pendinginleti',replay=type_trailing_operandctx(fun()->let_,i'=instructionctxi'{pending=tail_pending;value_loc=None;reported=false}ini')in(* Query the callee's expression type once: [expression_type] reports a
zero/multi-value callee ("an expression is expected here"), so asking
twice — here and in [type_body] below — would report it twice. *)letcallee_type=expression_typectxi'inletfunctype=matchCell.getcallee_typewith|Valtype{typ=Ref{typ=Typety|Exactty;_};_}->(* At the CALLEE's own location, not [ty]'s: [ty] is the type
reference the callee's declared type points at (a local's
annotation, say), so every call of that local reported "Expected
function type" at the SAME spot — indistinguishable duplicates when
there are two such calls, and never pointing at the call that is
wrong (a mutate-wax DIAG_DUP finding). *)lookup_func_type~location:(sndi'.info)ctxty|_->Noneinletparam_types=Option.bindfunctype(funtyp->array_map_opt(fun(p:(_,location)Ast.annotated)->internalizectx(sndp.desc))typ.params)inlettype_body=let*l'=typed_call_argsctxlparam_typesinmatchCell.getcallee_typewith|Valtype{typ=Ref{typ=Type_|Exact_;_};_}->(matchfunctypewith|None->(* [lookup_func_type] already reported "expected function type" (the
named type is not a function); recover with the [Unreachable]/
[Error] node the [let*!] on that lookup used to yield, so a
wrapping [become] treats it as a failed call rather than forming a
tail call with an [Error] result. *)return{desc=Ast.Unreachable;info=([|Cell.makeError|],(Ast.no_loc()).info);hints=Wax_wasm.Hints.none;expected=Unset;}|Sometyp->(matchparam_typeswith|Someparam_typeswhenArray.lengthparam_types<>List.lengthl'->Error.operand_count_mismatchctx.diagnostics~location:(sndi'.info)~expected:(Array.lengthparam_types)~provided:(List.lengthl')|_->());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_funcctx.diagnostics~location:(sndi'.info);return_statementi(Call(i',l'))[|Cell.makeError|]inletst1,node=type_body{stwithpending=front_pending}inletst2=fold_operandctxi'i'{st1withpending=[]}inreplay();(st2,node)(* Compilation-hints proposal: what a [#[targets(f: 0.73, …)]] hint on this call
needs beyond the parser's check that it prefixes a call at all. The mirror of
[Validation.check_hints]' [call_targets] arm, which the Wax typer owes because
[wax check] never converts, so a problem only the lowering would hit would
otherwise pass. Each target is resolved but NOT marked used: naming a function
in advisory metadata is not a use, so it must not keep an otherwise-dead
function out of the unused-field lint. *)andcheck_call_targets_hintctx(i:locationinstr)=matchi.hints.Wax_wasm.Hints.targetswith|None->()|Someh->(matchi.descwith(* Direct-call test, mirroring [To_wasm]: a bare name that denotes a module
function and is not shadowed by a local lowers to [call], whose target is
already known, so a target list says nothing. *)|(Call({desc=Getname;_},_)|TailCall({desc=Getname;_},_))when(not(StringMap.memname.Annot.descctx.locals))&&Tbl.find_no_markctx.functionsname<>None->Error.call_targets_direct_callctx.diagnostics~location:h.Wax_wasm.Hints.loc|_->());List.iter(fun((f:Ast.ident),_)->ifStringMap.memf.Annot.descctx.locals||Tbl.find_no_markctx.functionsf=NonethenError.unbound_namectx.diagnostics~location:f.Annot.info"function"f)h.Wax_wasm.Hints.value;lettotal=List.fold_left(funacc(_,pct)->acc+pct)0h.Wax_wasm.Hints.valueiniftotal>100thenError.call_targets_over_100ctx.diagnostics~location:h.Wax_wasm.Hints.loc~totalandcall_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. *)check_call_targets_hintctxi;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);hints=Wax_wasm.Hints.none;expected=Unset;}inreturn_statementi(Call({desc=StructGet(recv',meth);info=([||],func.info);hints=Wax_wasm.Hints.none;expected=Unset;},[]))[||](* 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 hand-written struct
field can claim the names — but a decompiled one can, its fields named by
the name section, so the receiver is checked like every other method's. *)|Call({desc=StructGet(recv,({desc="resume"|"resume_throw"|"resume_throw_ref"|"switch";_;}asmeth));_;},args)whennot(method_is_struct_fieldctxrecvmeth)->type_cont_method_callctxi~handlers:[]recvmethargs(* The array bulk operations, at their exact argument counts. Each is gated
on the receiver not being a struct with a field of that name, which is an
indirect call through a function pointer instead. *)|Call(({desc=StructGet(a,({desc="fill";_}asmeth));_}asfunc),[j;v;n])whennot(method_is_struct_fieldctxameth)->type_array_fill_callctxifuncamethjvn|Call(({desc=StructGet(a1,({desc="copy";_}asmeth));_}asfunc),[i1;a2;i2;n])whennot(method_is_struct_fieldctxa1meth)->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))whennot(method_is_struct_fieldctxameth)->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&¬(method_is_struct_fieldctxrecvmeth)->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&¬(method_is_struct_fieldctxrecvmeth)->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.operand_count_mismatchctx.diagnostics~location:func.info~expected:0~provided:(List.lengthargs');return_statementi(Call({desc=Path(ns,name);info=([||],func.info);hints=Wax_wasm.Hints.none;expected=Unset;},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:func.infons.descname.desc;return_expressioni(Call({desc=Path(ns,name);info=([||],func.info);hints=Wax_wasm.Hints.none;expected=Unset;},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:func.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);hints=Wax_wasm.Hints.none;expected=Unset;},args'))[|Cell.makeError;Cell.makeError|]|Somen->ifList.lengthargs'<>nthenError.operand_count_mismatchctx.diagnostics~location:func.info~expected:n~provided:(List.lengthargs');List.iter(funa->check_typectxai64_cell)args';return_statementi(Call({desc=Path(ns,name);info=([||],func.info);hints=Wax_wasm.Hints.none;expected=Unset;},args'))[|valtype_celli64_valtype;valtype_celli64_valtype|]andinstructionsctxl:_->_*_list=(* A run of operands emitted left-to-right (call/constructor arguments, a
[throw] payload, a resume/switch operand list, …): each takes its own
hole-slice in this same (emission) order, so [typed] also folds in the
hole-order check across them. *)matchlwith|[]->return[]|i::r->let*i'=typedctxiinlet*r'=instructionsctxrinreturn(i'::r')(* Like [instructions] but for static immediate operands (SIMD lane indices) that
are not pushed on the stack: type them plainly, so they take no hole slice and
do not count as values in the hole-order check. They are constant integers and
never carry a hole. *)andplain_instructionsctxl=matchlwith|[]->return[]|i::r->let*i'=instructionctxiinlet*r'=plain_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)->(* A labelled memarg ([offset: N]) is a static immediate, not a stack
operand: type it plainly (it never carries a hole, and must not count
as a value in the hole-order check — see [typed]). *)let*e'=instructionctxeinlet*r'=mem_call_argumentsctxrinreturn({desc=Labelled(lbl,e');info=(fste'.info,i.info);hints=Wax_wasm.Hints.none;(* Carry the producer's marker through the re-wrap (as the
general path does): [From_wasm] marks the label [Contextual]. *)expected=i.expected;}::r')|_->let*i'=typedctxiinlet*r'=mem_call_argumentsctxrinreturn(i'::r'))(* Recover a [match]'s typed scrutinee. [rebuild_match] returns it when there is
at least one arm (the scrutinee is threaded into the lowering and typed
there). With no arms the lowering is the default alone and the scrutinee is
discarded, so type it in isolation, giving each hole an [Error] cell so a bare
hole scrutinee does not crash [pop_parameter]. *)andmatch_recover_scrutineectxscrutinee=function|Somescrut'->scrut'|None->letpending=List.init(count_holesscrutinee)(fun_->Cell.makeError)insnd(instructionctxscrutinee{pending;value_loc=None;reported=false})andtoplevel_instructionctxi:stack->stack*'b=ifdebugthenWax_utils.Printer.run_err(funp->Printer_output.instrpi);matchi.descwith(* A conditional annotation: the branch this run selects (fixed ahead of
typing by the module's [Cond_plan], see [ctx.select]) is typed SPLICED
against the enclosing pending stack, exactly as the source's own validation
and the checking passes pair them: its holes claim the enclosing values and
its leftovers stay pending for later claimers, so the types this run
resolves for the ENCLOSING statements are those of a configuration that
exists. The other branch is not typed here at all: [f_infer]'s stitching
fills it from the run that owns it. *)|If_annotation{cond;then_body;else_body}->letsel_then=ctx.selecti.infoinletspliced(body:_Annot.annotated)st=letst,(typed,_)=block_contentsctx[||]body.Annot.descstin(st,{bodywithAnnot.desc=typed})inletskipped(body:_Annot.annotated)={bodywithAnnot.desc=placeholder_instrsbody.Annot.desc}infunst->letst,then_body,else_body=match(sel_then,else_body)with|true,_->letst,then_body=splicedthen_bodystin(st,then_body,Option.mapskippedelse_body)|false,Someb->letst,b=splicedbstin(st,skippedthen_body,Someb)|false,None->(st,skippedthen_body,None)inreturn_statementi(If_annotation{cond;then_body;else_body})[||]st|Block{label;typ;block={desc=instrs;_}asblkloc}->let*!params=array_map_opt(fun(p:(_,location)Ast.annotated)->internalizectx(sndp.desc))typ.paramsinlet*!results=array_map_opt(internalizectx)typ.resultsinlet*()=pop_argsctx`Input~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(fun(p:(_,location)Ast.annotated)->internalizectx(sndp.desc))typ.paramsinlet*!results=array_map_opt(internalizectx)typ.resultsinlet*()=pop_argsctx`Input~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(fun(p:(_,location)Ast.annotated)->internalizectx(sndp.desc))typ.paramsinlet*!results=array_map_opt(internalizectx)typ.resultsinlet*()=pop_argsctx`Input~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|TryTable{label;typ;block={desc=body;_}asblkloc;catches}->let*!params=array_map_opt(fun(p:(_,location)Ast.annotated)->internalizectx(sndp.desc))typ.paramsinlet*!results=array_map_opt(internalizectx)typ.resultsinlet*()=pop_argsctx`Input~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(fun(p:(_,location)Ast.annotated)->internalizectx(sndp.desc))typ.paramsinlet*!results=array_map_opt(internalizectx)typ.resultsinlet*()=pop_argsctx`Input~location:i.infoparamsinletbody'=blockctxi.infolabelparamsresultsresultsbodyinletcatches,catch_all=type_try_catchesctxlabel~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_armsctxlabel~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:Ast.ident),_)::r->(matchList.assoc_optl.descseenwith|Someprev_loc->Error.dispatch_duplicate_armctx.diagnostics~location:l.info~prev_locl|None->());check_dups((l.desc,l.info)::seen)rincheck_dups[]arms;letindex,_=reject_control_holesctx~construct:"dispatch"~role:"index"~recovery:(Ast.Int"0")indexinletlowered=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 threaded into the
lowering and typed there; it is then recovered from the typed form
rather than typed a second time (which reported every scrutinee error
twice and crashed on a bare hole scrutinee). *)letscrutinee,scrut_had_holes=reject_control_holesctx~construct:"match"~role:"scrutinee"~recovery:Ast.Nullscrutineeinletlabels=match_labelsi.infoarmsinletlowered=Ast_utils.lower_match~block_info:i.info~labels~scrutinee~arms~defaultinlet*typed,_=block_contentsctx[||]loweredinletarms',default',scrut_opt=(* On an erroneous scrutinee the typed lowering may not peel apart into
the expected block nesting; recover with empty arm/default bodies (the
module is already being rejected — the rebuilt node only feeds the
formatter/editor) and type the scrutinee on its own, rather than
crashing. *)tryrebuild_matchtypedarmswithMatch_shape->(List.map(fun(pat,(orig:(_instrlist,location)Ast.annotated))->(pat,{origwithdesc=[]}))arms,[],None)inletscrut'=match_recover_scrutineectxscrutineescrut_optin(ifnotscrut_had_holesthenmatchmatch_scrut_reftypectxscrut'with|Some_->()|None->Error.expected_refctx.diagnostics~location:(sndscrut'.info));return_statementi(Match{scrutinee=scrut';arms=arms';default={defaultwithdesc=default'};})[||]|TailCall_|Br_|Br_table_|Throw_|ThrowRef_|Return_->let*res=with_holesctxi(fun()->instructionctxi)inreturnres|>unreachable|_->let*res=with_holesctxi(fun()->instructionctxi)inreturnres(* 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(fun(p:(_,location)Ast.annotated)->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(fun(p:(_,location)Ast.annotated)->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_armsctxlabel~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->param_typep)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->(* The ARM's own span, not the try's: an arm is a block of its own,
and anchoring its reports at the enclosing try makes them collide
with the try body's — an arm that completes with nothing (an
empty [t => {}]) reported the missing value at the try's closing
token, exactly where the body's own report already sat, so the
same line printed twice (a duplicated diagnostic the mutation
fuzzer caught). Same fix as [type_try_catches] for a legacy
[try]'s handlers. *)letbody'=blockctxarm.arm_body.Annot.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'=blockctxarm.arm_body.Annot.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_armsctxlabel~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_catchesctxlabel~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(fun(p:(_,location)Ast.annotated)->internalizectx(sndp.desc))paramsin(* The HANDLER's own span, not the [try]'s: a handler is a block of its
own, and its parameters — the tag's payload, which the handler entry
pushes — are pushed at that span. Anchored at the [try] instead, a
payload the handler never consumes was reported as a value remaining
on the stack *at the try's opening*, which is both misleading and
indistinguishable from the enclosing construct's own leftover report
(a duplicated diagnostic the mutation fuzzer caught). *)letbody'=blockctxbody.Annot.infolabelparamsresultsresultsbody.Annot.descin(tag,{bodywithdesc=body'}))catchesinletcatch_all=Option.map(funbody->{bodywithAnnot.desc=blockctxbody.Annot.infolabel[||]resultsresultsbody.Annot.desc;})catch_allin(catches,catch_all)andblock_contentsctxresultsl=(* Alongside the typed body, report the trailing value's re-inference for a
caller that keeps it ([block_with_keep] / [block_keep_bool]): [Some r] when
this function itself produced the fall-through value by routing the trailing
instruction through [check_instruction] (the [Empty] case below, where the
pushed cell is the coerced result and so hides the natural type); [None]
otherwise, leaving the caller to snapshot the fall-through off the stack (the
value came from an earlier instruction, or was synthesized and pushed at its
own natural type). *)matchlwith|[]->return([],None)(* 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. The pushed
cell is the value's own natural type, so the caller can snapshot it
([None]). *)let*i'=with_holesctxi(fun()->instructionctxi)inlet*()=push_results~loc:i.info(fsti'.info)inreturn([i'],None)|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. The
pushed cell is that coerced result, so surface the value's own
re-inference ([Some]) for the caller instead. *)let*i',reinfer=check_toplevelctxresults.(0)iinlet*()=push_results~loc:i.inforesultsinreturn([i'],Somereinfer)|Cons_|Unreachable|Poisoned->(* The block's value is already on the stack, produced by an earlier
instruction (or the code is unreachable / the stack poisoned);
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~loc:i.info(fsti'.info)inreturn([i'],None))st|i::r->funst->letst_after,i'=toplevel_instructionctxistin(* Statements follow, so [i'] is not the block's trailing value-producer:
anything it leaves on the stack is a leftover a later statement
consumes, and a block-like [i'] must therefore state its own result
(see [restore_leftover_block_result]). A TRAILING one needs no
annotation — [block_contents] routes it through [check_instruction],
which fills the result back in from the block's own. *)leti'=ifr=[]theni'elserestore_leftover_block_resultctxi'in(* 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~loc:i.info(fsti'.info)st_afterin(* The fall-through is the tail's; propagate its re-inference. *)letst_after,(r',reinfer)=block_contentsctxresultsrst_afterin(st_after,(merge_let_tuplectxi'r',reinfer))(* Like [block] but also report the fall-through value's re-inference — what an
unannotated [let x = <block>] would re-infer it to, standalone — for the [if]
arm's per-branch join. Two sources feed it: the routed trailing instruction's
own re-inference ([block_contents] returns [Some]), else a snapshot of the
top-of-stack cell before [pop_args] coerces it to the result (the fall-through
came from an earlier instruction). A branch that delivers no value ([Empty] /
[Unreachable] / [Poisoned] at the exit) [Diverges], dropping out of the join.
A value branched to the block's own label is not seen here (it is not on the
exit stack) — [block_keep_bool] collects those for the block forms; the [if]
arm reads only fall-throughs, sound because [from_wasm] never emits a [br] to
an [if]'s own label carrying an uninferrable value (see IF-KEEP-BOOL.md). *)andblock_with_keepctxloclabelparamsresultsbr_paramsbody=with_empty_stackctx~location:loc~kind:Block(let*()=push_results~locparamsinlet*body',reinf_opt=block_contents{ctxwithcontrol_types=(label,br_params)::ctx.control_types}resultsbodyinfunst->letkeep=matchreinf_optwith|Somer->r|None->(matchstwith|Cons(_,tv,_)->reinfer_of_celltv|Empty|Unreachable|Poisoned->Diverges)inletst,()=pop_argsctx`Output~location:locresultsstin(st,(body',keep)))andblockctxloclabelparamsresultsbr_paramsbody=fst(block_with_keepctxloclabelparamsresultsbr_paramsbody)(* Like [block] for a paramless block checked against a single [result] type, but
collect every value reaching its exit — the fall-through plus values branched
to its label — at their natural types into a [Collecting] cell, so
[block_keep_reinfer] can later read the block's own re-inference (the join of
those, when the block's result annotation is not itself kept). The trailing
instruction needs care: one that resolves its own type joins like any other
exit value (route it through the inferring cell — 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 the annotation stays load-bearing 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 the cell; 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.) Returns the typed body and the [Collecting]
cell for [block_keep_reinfer]. *)andblock_keep_boolctxloclabel~result~br_paramsbody=(* The re-inference 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<-(loc',Cell.make(Cell.gettv))::cs.collected|Empty|Unreachable|Poisoned->());letst,()=pop_argsctx`Output~location:loc[|result|]stin(* Return the cell: the caller may deliver more values to it (a [try]'s catch
handlers) before [block_keep_reinfer] 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 re-inference. *)(st,(block',r)))(* The re-inference of a checked block typed by [block_keep_bool] — what an
unannotated [let x = <block>] would re-infer it to. When the block's own result
annotation survives in the output ([kept_annotation]) it pins its type itself,
so the block re-infers to [result] regardless of its contents. Otherwise the
annotation is dropped/omitted and the block re-infers from its contents: the
join of every value reaching the exit (the fall-through plus branched/caught
values collected into [cs]). A delivery that relied on the context ([cs.needed]
— a trailing construction, or a [resume] handler that read the cell) cannot be
re-derived, so it is [Uninferrable]; a body that delivers nothing [Diverges].
Read after any extra deliveries (a [try]'s catch handlers) have been collected. *)andblock_keep_reinferctx~loc~result~kept_annotationr=(* The re-inference from the block's contents: the join of every value reaching
its exit. Always computed — [join_collected] reports a genuine exit-type
mismatch (values with no common supertype) as a side effect, which must fire
regardless of the keep decision — but discarded below when the block's own
annotation is kept (it then pins the type itself). *)letcontents_reinfer=matchCell.getrwith|Collectingcs->(ifcs.neededthenUninferrableelsematchjoin_collectedctx~location:loccs.collectedwith|Somej->reinfer_of_cellj|None->Diverges)|_->Uninferrablein(* A kept [=> T] is a *written* result annotation the surrounding context does
not pin, so — like a [Named] construction — the block re-infers to it only by
exact equality, never by narrowing: narrowing an immutable outer binding down
to a kept block result would flip the decompilation between "outer
annotation, no block result" and "block result, no outer annotation" on the
next cycle. *)ifkept_annotationthennamed_reinfer_of_cellresultelsecontents_reinfer(* 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~rin(* Every exit that delivered nothing is an error once another delivered a
value (see [collect_into]); in source order, the list being built by
consing. *)ifcs.collected<>[]thenList.iter(funlocation->Error.short_stackctx.diagnostics`Output~location~actual:0~expected:1)(List.revcs.empty_exits);letnatural=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->(* Each arm anchored at its own span, as on the annotated path: the exits
they record are reported at the block's closing token, and the [if]'s own
span would render two arms' reports as the same line twice. *)letelse_b=Option.getelse_blockinletif_block'=collect_intoctxif_block.infolabel~cs~rif_block.descinletelse_block'=collect_intoctxelse_b.infolabel~cs~relse_b.descinfuntyp->If{label;typ;cond;if_block={if_blockwithdesc=if_block'};else_block=Some{else_bwithdesc=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;empty_exits=[]}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<-(loc,tv)::cs.collected;(Empty,body')|Cons(loc,tv,(Unreachable|Poisoned))->cs.collected<-(loc,tv)::cs.collected;(Unreachable,body')(* A REACHABLE fall-through delivering nothing, while some exit delivered a
value, leaves the block yielding a result its own exit does not produce
— the lowering would emit a block whose declared result the body never
leaves. The annotated paths ([block_with_keep], [block_keep_bool]) catch
this through their [pop_args ~`Output]; an inferred result must be
delivered by every exit just as a declared one is. Only RECORD it here,
anchored at the block's closing token as [pop] anchors every other
[`Output] underflow: whether it is an error depends on the exits still
to come, and an [if] types one arm before the other, so deciding it here
reported the empty arm only when it came SECOND ([cs.collected] was
still empty for an empty THEN arm, and nothing revisited it once the
else arm delivered a value — the typer then accepted a module the
lowering's validation rejected). [infer_synthesized] reports once the
whole body is typed. An [Unreachable] fall-through (the case above)
needs no value: nothing reaches the exit that way. *)|Empty->cs.empty_exits<-loc_last_charloc::cs.empty_exits;(Empty,body')|(Unreachable|Poisoned)asst->(st,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_catchesctxlabel~resultscatchescatch_allinfuntyp->Try{label;typ;block={bodywithdesc=body'};catches;catch_all})(*** Module type and constant checking ***)(* A subtype has an optional descriptor/described type exactly when its
supertype does, and the former must be a subtype of the latter. *)letoptional_ref_subtypectxchildparent=match(child,parent)with|None,None->true|Somechild,Someparent->Wax_wasm.Types.heap_subtype(subtyping_infoctx)(Typechild)(Typeparent)|Some_,None|None,Some_->falseletcheck_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=optional_ref_subtypectxty.descriptorty'.descriptorinletdescribes_ok=optional_ref_subtypectxty.describesty'.describesinifnot(valid_subtype&&descriptor_ok&&describes_ok)thenError.invalid_subtypectx.diagnostics~locationsup)(* Check that [i] is a constant expression. The recursion returns whether the
SUBTREE already reported a violation: an enclosing construct whose own shape
test fails only because a nested offender was already reported (a
non-constant leaf poisons every level of a nested [BinOp] chain) must not
re-report — one root cause, one diagnostic, at the innermost offender. *)letreccheck_constant_instructionctxi=ignore(constant_instructionctxi:bool)andconstant_instructionctxi=letlocation=sndi.infoinletrequired()=Error.constant_expression_requiredctx.diagnostics~location;trueinmatchi.descwith|Getidx->(matchTbl.find_optctx.globalsidxwith|Some(mut,_)->ifmutthen(Error.constant_global_requiredctx.diagnostics~location;true)elsefalse|None->(* ref.func *)false)|Null|StructDefault_|Int_|Float_|Char_|String_->false(* [array.new_default] fills with the field default, but its length is an
arbitrary expression that must itself be constant (like the sibling [Array]
/ [ArrayFixed] / [ContNew] constructors). *)|ArrayDefault(_,len)->constant_instructionctxlen(* 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.fold_left(funrf->constant_fieldctxf||r)falsel|StructDesc(d,l)->letr=constant_instructionctxdinList.fold_left(funrf->constant_fieldctxf||r)rl|StructDefaultDescd->constant_instructionctxd|ArrayFixed(_,l)->List.fold_left(funri->constant_instructionctxi||r)falsel|Array(_,i1,i2)->letr1=constant_instructionctxi1inconstant_instructionctxi2||r1(* [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)->constant_instructionctxf|BinOp({desc=Add|Sub|Mul;_},i1,i2)->(letr1=constant_instructionctxi1inletr2=constant_instructionctxi2inifr1||r2thentrueelsematchCell.get(expression_typectxi)with(* [Error] is the poison of an already-reported operand (e.g. a hole);
a second report here would duplicate it. *)|Int|Valtype{internal=I32|I64;_}|Error->r1||r2|_->required())|Cast({desc=Null;_},Valtype(Ref{nullable=true;_}))->(* ref.null *)false|Cast(i',Valtype(Ref{typ=I31;_}))->(if(* ref.i31 *)constant_instructionctxi'thentrueelsematchCell.get(expression_typectxi')with(* [Error]: already reported (see the [BinOp] arm). *)|Valtype{internal=I32;_}|Error->false|_->required())|Cast(i',Valtype(Ref{typ=Extern;nullable}))->(* extern.convert_any. An [i32] operand is first wrapped in [ref.i31]
([i32 -> i31 -> any -> extern], as the non-constant typer lowers
[x as &extern]), which is itself constant — so accept it like the
[ref.i31] arm above rather than demanding the operand already be an
[any] reference. *)ifconstant_instructionctxi'thentrueelseifmatch(Cell.get(expression_typectxi'):inferred_type)with|Valtype{internal=I32;_}->false|Valtype{internal;_}->not(Wax_wasm.Types.val_subtype(subtyping_infoctx)internal(Ref{nullable;typ=Any}))(* [Error]: already reported (see the [BinOp] arm). *)|Error->false|_->truethenrequired()elsefalse|Cast(i',Valtype(Ref{typ=Any;nullable}))->(* any.convert_extern *)ifconstant_instructionctxi'thentrueelseifmatch(Cell.get(expression_typectxi'):inferred_type)with|Valtype{internal;_}->not(Wax_wasm.Types.val_subtype(subtyping_infoctx)internal(Ref{nullable;typ=Extern}))(* [Error]: already reported (see the [BinOp] arm). *)|Error->false|_->truethenrequired()elsefalse|UnOp({desc=Pos;_},i')->constant_instructionctxi'|UnOp({desc=Neg;_},{desc=Float_|Int_;_})->false(* [v128::<shape>(..)] is a constant expression; its lanes are literals.
Other SIMD ops are not constant. The lanes are NOT re-walked here: the
intrinsic's own typing already rejects any non-literal lane (with this
same "constant expression required" report, at the lane's span), so
re-checking each argument would report every bad lane twice. *)|Call({desc=Path(ns,name);_},_)whenns.desc=Simd.free_namespace&&Simd.const_shape_of_name(Simd.free_fullname.desc)<>None->false|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_|Throw_|ThrowRef_|ContBind_|Suspend_|Resume_|ResumeThrow_|ResumeThrowRef_|Switch_|On_|Return_|Sequence_|Select_|If_annotation_|Labelled_->required()(* 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. *)andconstant_fieldctx(name,i)=matchiwith|Somei->constant_instructionctxi|None->constant_instructionctx{desc=Getname;info=([||],name.info);hints=Wax_wasm.Hints.none;expected=Unset;}(*** 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(fun(field:(_modulefield,location)Ast.annotated)->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 its re-inference ([reinfer_needed])
decides whether the annotation is redundant (dropped only when
converting from Wasm). 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',reinfer=with_empty_stackctx~location:def.info~kind:Expression(check_toplevelctx(valtype_cellity)def)inTbl.addctx.diagnosticsctx.globalsname(mut,Someity);(* A [null] initializer no longer needs a special case: the
[Cast]/leaf arms report its floating [&?none] re-inference,
so [reinfer_needed] keeps the annotation on its own. *)letneeded=reinfer_needed~drop_supertype:(notmut)ctxreinfer(valtype_cellity)inletredundant=notneededin(* Offer dropping the redundant annotation as a quick fix for
hand-written Wax, exactly as a [let] binding does; the
[simplify] drop below is the Wasm->Wax mirror. *)ifctx.suggest&&redundantthenTyping_suggest.suggest_redundant_annotationctx~name_end:name.info.loc_end~boundary:def.info.loc_start;letdrop=ctx.simplify&&redundantin((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{then_fields;else_fields;_}->(* Only the branch this run selects is typed; the other is a
placeholder [f_infer]'s stitching replaces. *)letsel_then=ctx.selectfield.infoinletskippedfields=List.map(funf->Afterf)(placeholder_fieldsfields)inPhasedConditional{before=field;then_=(ifsel_thenthenglobalsctxthen_fields.descelseskippedthen_fields.desc);else_=Option.map(fune->ifsel_thenthenskippede.Annot.descelseglobalsctxe.Annot.desc)else_fields;}|_->Beforefield)fieldsletrecfunctionsctxfields=List.filter_map(funfield->matchfieldwith|Before({Annot.desc=Func{name;sign;body=label,body;typ;attributes};info=location;}asf)->(* Attribute everything this definition resolves — its declared type as
much as its body — to the function itself, so nothing a dead function
names looks externally referenced. Reset after the body below. *)ctx.origin:=From_functionname.desc;letfunc_typ=let*@ty=(* Resolve the function's own declared type without marking the
function name used — its definition site is not a reference, so
the unused-field lint can still flag it if nothing calls it.
A poison entry ([Some None] — the signature failed, reported at
registration) yields [None] here; the body is still checked
below, with the failed types as Error poison. *)let*@entry=Tbl.find_no_markctx.functionsnameinlet*@_,tname,_=entryinTbl.findctx.diagnosticsctx.types{namewithdesc=tname}inmatchtywith|_,{typ=Functyp;_}->Sometyp|_->Error.expected_func_typectx.diagnostics~location:name.info;Nonein(* For a poisoned signature, the source [sign] is re-resolved with
MUTED diagnostics — its failure was already reported at
registration — and whatever fails again becomes Error poison (a
poison local / an Error result cell), so the body's own errors
still surface without cascades. *)letmctx=matchfunc_typwith|Some_->ctx|None->{ctxwithdiagnostics=Wax_utils.Diagnostic.collector~parent:ctx.diagnostics();}in(* A [#[start]] function must have no parameters and no results. *)(matchfunc_typwith|Somefunc_typ->ifList.exists(funa->a.Ast.attr_name="start")attributes&¬(Array.lengthfunc_typ.params=0&&Array.lengthfunc_typ.results=0)thenError.start_function_signaturectx.diagnostics~location:name.info|None->());letreturn_types=matchfunc_typwith|Somefunc_typ->(matcharray_map_opt(funtyp->internalizectxtyp)func_typ.resultswith|Somer->r|None->[||](* a resolved type's results resolve *))|None->(matchsignwith|Some{results;_}->Array.map(funtyp->matchinternalizemctxtypwith|Somec->c|None->Cell.makeError)results|None->[||])inletlocals=refStringMap.emptyin(matchsignwith|Some{params;_}->Array.iter(funp->letid=param_namepandtyp=param_typepinmatchidwith|Someid->(* A parameter type that does not resolve still binds the
name, as a poison local (read as [Error]), so the
body's uses of it do not cascade. *)lettyp=internalize_valtypemctxtypinlocals:=StringMap.addid.Annot.desc(typ,id.info)!locals|None->())params|_->());ifdebugthenPrintf.eprintf"=== %s\n%!"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. *)missing_holes=ref[];unresolved_label=reffalse;read_locals=refIntSet.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=refIntSet.empty;label_decls=List.fold_leftTyping_lint.collect_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_leftTyping_lint.collect_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(Typing_lint.lint_sourcectx)body;letbody=with_empty_stackctx~location~kind:Function(let*body,_=block_contentsctxreturn_typesbodyinlet*()=pop_argsctx`Output~locationreturn_typesinreturnbody)inctx.origin:=Root;(* The body is fully typed, so the deferred lints (shift-count widths)
can now read their pinned cells; run them here, in this function, so
they stay in source order among the other diagnostics. *)Typing_lint.flush_deferred_lintsctx;(* A local or label whose name starts with [_] is intentionally
unused. *)ifctx.warn_unusedthenbeginList.iter(fun(name:Ast.ident)->letn=name.descinif(not(IntSet.memname.info.loc_start.pos_cnum!(ctx.read_locals)))&¬(String.lengthn>0&&n.[0]='_')thenError.unused_localctx.diagnostics~location:name.infoname)(List.rev!(ctx.local_decls));List.iter(fun(name:Ast.ident)->letn=name.descinif(not(IntSet.memname.info.loc_start.pos_cnum!(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=functionsctxthen_};else_fields=(match(ef,else_)with|Someef,Somee->Some{efwithdesc=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|_->())(* Resolve a function declaration's type (a named reference or an inline
signature). Reports resolution failures; returns [None] then. *)letfundecl_typctxnametypsign=matchtypwith|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={(name:Ast.ident)withdesc="<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)(* Register a function (defined or imported) under [name]. A signature that
fails to resolve still CLAIMS the name, as a poison entry ([None]): its
uses resolve quietly to [Error] instead of cascading into unbound-name
reports, and its body is still checked (see [functions]) — the Wax mirror
of the validator's poisoned index entries. A duplicate name registers
nothing (the first entry stands; [Tbl.exists] reports the clash). *)letregister_functionctxdnametypsign~exact~import=ifnot(Tbl.existsdctx.functionsname)thenbegin(* A defined function's signature is a reference *it* makes, so attribute the
types it names to the function rather than letting a dead function's type
look externally referenced. An IMPORT has no body: its signature is a
module-level reference, hence a root — as in the validator, and as for an
imported global's or tag's type here. *)letouter=!(ctx.origin)inif(notimport)&&outer<>Ignoredthenctx.origin:=From_functionname.desc;letentry=Option.map(fun(i,n)->(i,n,exact))(fundecl_typctxnametypsign)inctx.origin:=outer;Tbl.adddctx.functionsnameentryendletfield_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?(priority_ok=false)(attributes:Ast.attributes)=List.iter(fun({attr_name=name;attr_value=value;attr_guard=guard;attr_span;}:Ast.attribute)->(* The whole [#[...]]. A valueless attribute ([#[start]], [#[run_once]]) has
no value span to fall back on, and the field's span would underline the
entire definition. A synthesized attribute carries the entity's span. *)letlocation=attr_spanin(* 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"(* Compilation-hints proposal: the function-level compilation priority. It
needs a body to attach to, so it is allowed on a defined function only —
an imported one has no code-section entry to key an offset-0 hint in. *)|"priority"|"optimization"->(* In range as well as an integer: the section stores the priority as a
ULEB, and an over-long literal would otherwise reach [to_wasm]'s
[int_of_string] and crash it (as for the SIMD lane index above; the
WAT grammar range-checks the same payload with [priority_of_nat]). *)(matchvaluewith|Some({desc=Int_;_}asv)whenOption.fold~none:false~some:(funl->Wax_utils.Uint64.comparel(Wax_utils.Uint64.of_string"0x1_0000_0000")<0)(int_literalv)->()|_->Error.annotation_value_mismatchdiagnostics~locationname"an integer in the u32 range");ifnotpriority_okthenError.annotation_not_alloweddiagnostics~locationname|"run_once"->(matchvaluewith|None->()|Some_->Error.annotation_value_mismatchdiagnostics~location"run_once""no value");ifnotpriority_okthenError.annotation_not_alloweddiagnostics~location"run_once"|_->Error.unknown_annotationdiagnostics~locationname)attributes;(* The section states an optimization priority only alongside a compilation
one, and [run_once] is just a spelling of a particular optimization value, so
either without [#[priority]] would have nowhere to go. Reject rather than
invent a compilation priority the author did not choose. *)ifpriority_okthenbeginletfindk=List.find_opt(fun(a:Ast.attribute)->a.attr_name=k)attributesinletlocate(a:Ast.attribute)=a.attr_spaniniffind"priority"=NonethenList.iter(funk->Option.iter(funa->Error.priority_requireddiagnostics~location:(locatea)k)(findk))["optimization";"run_once"];match(find"optimization",find"run_once")with|Somea,Someb->Error.conflicting_optimizationdiagnostics~location:(locatea)~prev_loc:(locateb)|_->()end(* 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_attributesdiagnostics(field:(_modulefield,location)Ast.annotated)=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)inletpriority_ok=matchfield.descwithFunc_->true|_->falseincheck_attribute_listdiagnostics~export_ok~start_ok~module_ok~import_ok:false~priority_ok(field_attributesfield.desc)(*** Type-checking a configuration ***)lettype_configuration?(warn_unused=false)?(build=true)?(suggest=false)?(resolve_links=None)?(pun_spans=None)?(member_completions=None)?(faithful=false)?(features=Wax_utils.Feature.default())?(select=fun(_:location)->invalid_arg"Typing: unplanned conditional")?(guard=fun(_:location)->true)~simplifydiagnosticsfields=(* [simplify] (the Wasm->Wax rewrite that drops redundant annotations) and
[suggest] (offering those same drops as editor quick fixes on hand-written
Wax) are mutually exclusive: [simplify] removes the very nodes [suggest]
would flag. The [suggest_*] helpers rely on this. *)ifsimplify&&suggesttheninvalid_arg"Typing: simplify and suggest are exclusive";letlinks=resolve_linksin(* Shared by every table below, so a name resolution is attributed to the
function whose body made it (see [Tbl.current]). *)letcurrent=refRootinlettype_context={internal_types=Wax_wasm.Types.create();types=Tbl.make~hover:hover_of_type~current(Namespace.make~links())"type";features;subtyping_info_cache=None;}in(* Walk module fields, descending at each conditional into the branch this
run selects, so only that branch's declarations are registered. *)letrecwalk_fieldsffields=List.iter(fun(field:(_modulefield,_)annotated)->matchfield.descwith|Conditional{then_fields;else_fields;_}->ifselectfield.infothenwalk_fieldsfthen_fields.descelseOption.iter(fune->walk_fieldsfe.Annot.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]). *)letstructs_by_fields=Hashtbl.create16inTbl.itertype_context.types(funname(_,(st:subtype))->matchst.typwith|Structsfields->(letkey=field_set_key(Array.to_list(Array.map(funf->(field_namef).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~links()in{diagnostics;type_context;types=type_context.types;structs_by_fields;not_expression_reported=Hashtbl.create16;functions=Tbl.make~currentnamespace"function";globals=Tbl.make~hover:hover_of_global~currentnamespace"global";import_globals=Tbl.make~hover:hover_of_global~currentnamespace"global";assigned_globals=Hashtbl.create16;cast_traps_reported=Hashtbl.create16;canonical_type_references=ref[];origin=current;memories=Tbl.make~currentnamespace"memory";datas=Tbl.make~current(Namespace.make~links())"data segment";tables=Tbl.make~currentnamespace"table";elems=Tbl.make~current(Namespace.make~links())"element segment";tags=Tbl.make~current(Namespace.make~links())"tag";locals=StringMap.empty;warn_unused;missing_holes=ref[];unresolved_label=reffalse;read_locals=refIntSet.empty;local_decls=ref[];used_labels=refIntSet.empty;deferred_lints=ref[];label_decls=[];assigned_locals=StringSet.empty;initialized_locals=StringSet.empty;deferred_uninit=[];control_types=[];return_types=[||];resolve_links=links;pun_spans;member_completions;simplify;suggest;select;faithful;}incheck_type_definitionsctx;letmemory_index=ref0inletregister_memorynameaddress_type=leti=!memory_indexinincrmemory_index;Tbl.adddiagnosticsctx.memoriesname(i,address_type)in(* 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(* A table's element type is stored as written, so resolve it here for its two
side effects: an unbound type name is reported by the typer itself (rather
than only later, by the lowering, which is what the whole type-checking pass
exists to pre-empt), and a type named only as a table's element type counts
as used — as it does in the validator. *)letresolve_table_reftypert=ignore(internalize_valtypectx(Refrt))inletregister_tablenameaddress_typereftype=resolve_table_reftypereftype;Tbl.adddiagnosticsctx.tablesname(address_type,reftype)in(* Register an imported entity under its Wax name. *)letregister_import(decl:Ast.import_decl)=matchdecl.kindwith|Import_func{typ;sign;exact}->register_functionctxdiagnosticsdecl.idtypsign~exact~import:true|Import_global{mut;typ}->let>@typ=internalize_valtypectxtypinTbl.adddiagnosticsctx.globalsdecl.id(mut,Sometyp)|Import_tag{typ;sign}->register_tagdecl.idtypsign|Import_memory{address_type;_}->register_memorydecl.idaddress_type|Import_table{address_type;reftype=rt;_}->register_tabledecl.idaddress_typertinwalk_fields(funfield->matchfield.descwith|Memory{name;address_type;data;_}->register_memorynameaddress_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(fun(d:(Ast.import_decl,location)Ast.annotated)->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. *)letexact=Wax_utils.Feature.is_enabledctx.type_context.featuresWax_utils.Feature.Custom_descriptorsinregister_functionctxdiagnosticsnametypsign~exact~import:false|Tag{name;typ;sign;_}->register_tagnametypsign|Data{name;_}->Option.iter(funn->Tbl.adddiagnosticsctx.datasn())name|Table{name;address_type;reftype=rt;_}->register_tablenameaddress_typert|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 only into the branch this
run selects, so exports in mutually exclusive branches do not clash. A
guarded export ([#[export = "nm", if(c)]]) is present in the configurations
[guard] selects: a checking run resolves it from its plan, a build run
counts it present (its diagnostics are discarded). *)letexports=Hashtbl.create16inletstarts=refNoneinletmodule_seen=refNonein(* The Wax name a bare [#[export]] reuses as its export name. *)letfield_name(field:(_modulefield,location)Ast.annotated)=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({attr_name=key;attr_value=v;attr_guard;_}:Ast.attribute)->letpresent=matchattr_guardwithNone->true|Someg->guardg.infoinifpresentthenmatch(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)->(matchHashtbl.find_optexportsnamewith|Someprev_loc->Error.duplicated_exportdiagnostics~location~prev_locname|None->());Hashtbl.replaceexportsnamelocation)entry|"start",_->(* A module may name at most one start function per configuration. *)(match!startswith|Someprev_loc->Error.multiple_startdiagnostics~location~prev_loc|None->());starts:=Somelocation|"module",_->((* A module may carry at most one name annotation. *)match!module_seenwith|Someprev_loc->Error.multiple_modulediagnostics~location~prev_loc|None->module_seen:=Somelocation)|_->())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)=(* An imported function may be the module's start function; other imported
kinds cannot. *)letstart_ok=matchdecl.desc.kindwithImport_func_->true|_->falseincheck_attribute_listdiagnostics~export_ok:true~start_ok~module_ok:false~import_ok:truedecl.desc.attributes;(* A [#[start]] import, like a defined start function, must have no
parameters and no results (the import was registered above, so its type
is resolvable). *)ifstart_ok&&List.exists(funa->a.Ast.attr_name="start")decl.desc.attributesthenbeginletname=decl.desc.idinletfunc_typ=let*@entry=Tbl.findctx.diagnosticsctx.functionsnamein(* A poison entry: the signature failure was already reported. *)let*@_,tname,_=entryinlet*@_,ty=Tbl.findctx.diagnosticsctx.types{namewithdesc=tname}inmatchty.typwithFunctyp->Sometyp|_->Noneinmatchfunc_typwith|SomeftwhenArray.lengthft.params=0&&Array.lengthft.results=0->()|Some_->Error.start_function_signaturectx.diagnostics~location:name.info|None->()end;(matchList.filter(funa->a.Ast.attr_name="import")decl.desc.attributeswith|first::(a:Ast.attribute)::_->Error.multiple_importdiagnostics~location:a.attr_span~prev_loc:first.attr_span|_->());(* 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=globalsctxfieldsin(* Global initializers are fully typed now; run their deferred lints (see
[ctx.deferred_lints]) before the function bodies, keeping every diagnostic
in source order. *)Typing_lint.flush_deferred_lintsctx;lettyped_fields=functionsctxphased_fieldsin(* Report module fields 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.
Then report the mutable ([let]) globals never assigned, which could be
[const] instead. *)ifwarn_unusedthenbegin(* Resolve the by-canonical type references (a string literal's [mut i8]
array) against the definitions that deduplicated onto them, once, before
any of the reference graphs below are read. *)(match!(ctx.canonical_type_references)with|[]->()|refs->Tbl.iter_entriesctx.types(funname(r,_)->match(r:Wax_wasm.Types.ref_index)with|Defid->List.iter(fun(origin,id')->ifWax_wasm.Types.Id.equalidid'thenTbl.mark_referencectx.typesnameorigin)refs|Rec_->()));letexemptfield=List.exists(fun(a:Ast.attribute)->(a.attr_name="export"||a.attr_name="start")&&matcha.attr_guardwithNone->true|Someg->guardg.info)(field_attributesfield)in(* A leading [_] marks a declaration as deliberately unused (and, for a
global, deliberately as written), exempting it from these lints. *)letintentional(name:ident)=String.lengthname.desc>0&&name.desc.[0]='_'in(* Close a name-keyed reference graph: [live] is everything reachable from
[seeds] through the edges [edges_of] yields for each node. *)letclosure~edges_ofseeds=letlive=Hashtbl.create16inletrecvisitn=ifnot(Hashtbl.memliven)thenbeginHashtbl.replaceliven();List.itervisit(edges_ofn)endinList.itervisitseeds;livein(* The functions that can actually run: those reachable from outside
(exported, or the start function) or referenced from a module-level context
(a global or table initializer, a segment — recorded as [Root]), plus
everything those transitively call or take a [&f] of.
Reachability, not the mere presence of a reference, is what lets the lint
see a dead *cycle*: two functions that only call each other reference one
another, so a presence check finds both used, yet neither can ever run —
and the same goes for anything only such a cycle reaches. Taking a
function reference counts as calling it, since where the reference ends up
is not tracked, so the analysis never reports a function that might run. *)letlive_functions=letcalls=Hashtbl.create16inletseeds=ref[]inTbl.iter_referencesctx.functions(funreferrercallee->matchreferrerwith|From_functioncaller->Hashtbl.addcallscallercallee|Root->seeds:=callee::!seeds(* Only types reference types, so a type definition never names a
function; treat it as a root rather than losing the reference. *)|From_type_->seeds:=callee::!seeds|Ignored->());walk_fields(funfield->matchfield.descwith|Func{name;_}whenexemptfield.desc->seeds:=name.desc::!seeds|_->())fields;closure~edges_of:(Hashtbl.find_allcalls)!seedsin(* Referenced by a module-level context, or by something that can run. A
reference from dead code keeps nothing alive. *)letlive_origin=function|Root->true|From_functionf->Hashtbl.memlive_functionsf|From_type_|Ignored->falseinletusedtbl(name:ident)=List.existslive_origin(Tbl.referrerstblname.desc)in(* The types anything reachable names, closed over the references a type
definition makes through its own components (its supertype, field and
element types, a descriptor clause) — so a rec group nothing outside it
names is dead as a whole, its mutual references notwithstanding. *)letlive_types=letcomponents=Hashtbl.create16inletseeds=ref[]inTbl.iter_referencesctx.types(funreferrertarget->matchreferrerwith|From_typesrc->Hashtbl.addcomponentssrctarget|Root|From_function_->iflive_originreferrerthenseeds:=target::!seeds|Ignored->());closure~edges_of:(Hashtbl.find_allcomponents)!seedsinletunusedtbl(name:ident)=(not(intentionalname))&¬(usedtblname)in(* A defined field of any kind: report it at its name unless exempt. *)letcheck_unusedfieldtblkind(name:ident)=if(not(exemptfield))&&unusedtblnamethenError.unused_fieldctx.diagnostics~location:name.infokindnamein(* An import 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(a:Ast.attribute)->a.attr_name="export"||a.attr_name="start")decl.desc.attributesinletreporttblkind=ifunusedtbldecl.desc.idthenError.unused_importctx.diagnostics~location:decl.desc.id.infokinddecl.desc.idinifnotexemptthenmatchdecl.desc.kindwith|Import_func_->reportctx.functions"function"|Import_global_->reportctx.globals"global"|Import_memory_->reportctx.memories"memory"|Import_table_->reportctx.tables"table"|Import_tag_->reportctx.tags"tag"inwalk_fields(funfield->matchfield.descwith|Func{name;_}->check_unusedfield.descctx.functions"function"name|Global{name;mut;_}->check_unusedfield.descctx.globals"global"name;(* A global not used at all is already reported just above, so do not
pile a second diagnostic on the same declaration; an exported one
may be assigned by the host. *)ifmut&&(not(intentionalname))&&(not(exemptfield.desc))&&usedctx.globalsname&¬(Hashtbl.memctx.assigned_globalsname.desc)thenError.unnecessary_mutctx.diagnostics~location:name.infoname|Memory{name;_}->check_unusedfield.descctx.memories"memory"name|Table{name;_}->check_unusedfield.descctx.tables"table"name|Tag{name;_}->check_unusedfield.descctx.tags"tag"name(* An active segment runs at instantiation, so only a passive one can be
unused: it is reachable solely through [mem.init]/[tab.init] and
[seg.drop]. *)|Data{name=Somename;mode=Passive;_}->check_unusedfield.descctx.datas"data segment"name|Elem{name;mode=EPassive;_}->check_unusedfield.descctx.elems"element segment"name|Import{decl;_}->check_unused_importdecl|Import_group{decls;_}->List.itercheck_unused_importdecls(* Each member of a rec group is reported on its own; the group as a whole
is dead only when nothing outside it names any member. *)|Typerectype->Array.iter(funelt->letname=member_nameeltinif(not(intentionalname))&¬(Hashtbl.memlive_typesname.desc)thenError.unused_fieldctx.diagnostics~location:name.info"type"name)rectype|Data_|Elem_|Module_annotation_|Conditional_->())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)(* The concrete storage type an inference cell finally takes — the resolution
behind {!project_annotation}, factored out so the width pass below settles a
cell exactly as the projection does (the two disagreeing would make the width
comparison read a type the tree never takes). [Unknown]/[Error]/[Collecting]
have no concrete type ([None]); a flexible numeric literal takes its default
width. *)letresolved_storagetype(ty:inferred_type)=matchtywith|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)(* Resolve the inference cells at each node to concrete storage types — the
projection [f] applies before handing the typed tree to the Wasm conversion. *)letproject_annotation(types,loc)=(Array.map(funty->resolved_storagetype(Cell.getty))types,loc)letproject_module(m:inferred_module_annotationAst.module_):typed_module_annotationAst.module_=List.map(funf->{fwithAnnot.desc=Ast_utils.map_modulefieldproject_annotationf.Annot.desc;})m(* The numeric width a type states, [None] for a reference or vector (the width
pass covers the scalars only). A packed narrow read ([i8]/[i16] — a [load8], an
[i8] field) is an i32 value the typer tracks narrow, so it counts as [i32]:
the pass is about the value's numeric width, and a narrow read has none of its
own. *)letnumeric_width(ty:Ast.valtype)=matchtywithI32|I64|F32|F64->Somety|Ref_|V128->Noneletinferred_width(ty:Ast.storagetype)=matchtywith|Valuety->numeric_widthty|Packed(I8|I16)->SomeAst.I32(* Whether an inferred type is a FLEXIBLE numeric literal — one with no type of
its own, which a context narrows and, with none, {!resolved_storagetype}
defaults. Only such a value can be repaired by a pin: an identity cast grounds
it AT the pinned width, exactly as [From_wasm]'s [Stack.pin_width] does. Every
other numeric type is ANCHORED — resolved from a local, a call result, a merged
context — and there the same cast would be a numeric CONVERSION changing the
value, not a pin, so a disagreement is reported instead of repaired. The packed
narrow reads ([Int8]/[Int16]) are anchored in exactly that sense: their value
comes from a [load8]/[load16], and a cast on one fuses into the load rather than
grounding a literal. *)letflexible_literal(ty:inferred_type)=matchtywith|Number|Int|LargeInt|Float->true|Unknown|Error|UnknownRef|Null|Int8|Int16|Valtype_|Collecting_->false(* Which family a numeric type belongs to. A pin only ever settles a value's WIDTH:
taking it across this divide is a CONVERSION (in Wax it even needs a signage,
[as f32_s]), so no repair may cross it. *)letnumeric_family(t:Ast.valtype)=matchtwithI32|I64->`Int|F32|F64|Ref_|V128->`Float(* The family a numeric CAST accepts for its operand, which is what bounds a pin
inserted there ([None] for a cast that is not numeric): an identity/width cast
([as i64], [as f32] — a wrap, extend, promote or demote) takes its own family; a
signed conversion takes the OTHER one ([as i64_s] is a truncation, so its operand
is a float; [as f32_s] a convert, so its operand is an integer). *)letcast_operand_family(t:Ast.casttype)=matchtwith|Valtype((I32|I64|F32|F64)ast)->Some(numeric_familyt)(* An ascription converts nothing: its operand is in its own type's family. *)|Ascribed((I32|I64|F32|F64)ast)->Some(numeric_familyt)|Signedtype{typ=`I32|`I64;_}->Some`Float|Signedtype{typ=`F32|`F64;_}->Some`Int|Valtype(Ref_|V128)|Functype_|Ascribed_->None(* Whether a pin to [required] keeps the value in the family its own inferred type
commits it to. A literal still free of a family ([Number], or the float-capable
[LargeInt]) narrows either way; one an operator committed ([Int], [Float]) does
not; and a value whose cell a cast already folded keeps the family it folded to.
(Under a cast the bound comes from {!cast_operand_family} instead, which is
sharper: it is the consumer, not the fold, that constrains the operand there.) *)letpin_stays_in_family(inferred:inferred_type)~resolved~required=letintegralt=numeric_familyt=`Intinmatchinferredwith|Number|LargeInt->true|Int->integralrequired|Float->not(integralrequired)|_->integralresolved=integralrequired(* The inference lattice's entry for a numeric base type. [None] for the types the
width pass does not handle, which {!numeric_width} has already excluded. *)letnumeric_valtype(ty:Ast.valtype)=matchtywith|I32->Somei32_valtype|I64->Somei64_valtype|F32->Somef32_valtype|F64->Somef64_valtype|Ref_|V128->None(* The offending expression, elided past a line's worth: a disagreement is
reported against a whole operand tree, which can be large. *)letwidth_expr(i:_instr)=letexpr=Infer.Output.instr_stringiinifString.lengthexpr<=40thenexprelseString.subexpr040^"..."(* Reconcile the type each node's producer recorded on it (see [Ast.instr]'s
[expected] — only {!Wax_conversion.From_wasm} records any) with the type this
run resolves for it.
Compares against the RESOLVED type, the one the node finally takes: a flexible
numeric literal is only pinned to a width by {!resolved_storagetype}'s
defaulting (int/number -> i32, large number -> i64, float -> f64), which is
exactly the width a re-parse of the printed form would settle on. Reading the
cell as-is would take a flexible literal for "no type yet" and miss every
drift.
Two outcomes, by whether a pin CAN fix the disagreement (see
{!flexible_literal}):
- a flexible tree resolving to the wrong width is REPAIRED under
[`Repair] — the node is wrapped in an identity cast to the recorded type, the
grounding pin [From_wasm] should have placed, so the printed Wax re-infers the
width the Wasm states. The cell is grounded with it ([Cell.set]), which is
what makes one pass converge: the cells of a flexible tree are UNIFIED, so
grounding the outermost node's cell settles every node beneath it and their
own (identical) expectations are then met — no second pin inside the tree the
first one already grounds. Under [`Report] it is reported instead, which is
what keeps the fuzz harness able to see a missing [From_wasm] pin
([--debug width-check], oracle 5c) rather than a silently healed one.
- an ANCHORED type that disagrees is reported in BOTH modes: no cast can fix it
(one would convert the value), so it is either an invalid input — a binary is
trusted, never validated, so its operands need not be what its opcodes name —
or a conversion bug that must be fixed at the source.
Runs after the whole module is typed and after [simplify]'s rewrites, which is
also why an inserted pin cannot oscillate with [simplify]'s redundant-cast
pruning: pruning has already happened, in the typing pass proper, and nothing
re-enters it. A repair is by construction NOT redundant — the cast is what
changes the tree's resolved type — so a later re-type of the printed Wax keeps
it (and if it ever did become redundant, the [simplify] of a fresh decompile
would drop it and this pass would put it back only if it were still needed).
A node with no resolved type ([Unknown]/[Error] — a hole in unreachable code
the typer left polymorphic) is left alone: nothing was resolved to disagree
with, and pinning it would invent a type where the Wasm side is polymorphic
too. So is a node that leaves no value or several. *)(* The type a cast ASCRIBES to its operand in the printed form: only an
identity/width cast states its operand's type ([_ as i64] makes the operand an
i64), and that is what a re-parse reads. A signed conversion states its RESULT's
([_ as i64_s] is a truncation of a float), so it ascribes nothing to the operand
and a value under it still needs its own record honoured. *)letascribed_type(t:Ast.casttype)=matchtwith|Valtype((I32|I64|F32|F64)ast)|Ascribed((I32|I64|F32|F64)ast)->Somet|_->Noneletrecreconcile_widthsmodediagnostics~under_cast~ascribed(i:_instr):_instr=(* The recording-gap census ([--debug width-record]). A numeric-valued node
whose expectation is [Unset] — as opposed to a deliberate [Contextual] —
was never seen by any of [From_wasm]'s recording choke points: the one
class of width bug that is INVISIBLE to the reconciliation below by
construction (nothing recorded, nothing to disagree with), so it can only
drift silently. This census makes the class enumerable: run a decompile
under the flag and every line is either an emission path that must record
what its opcode states, or a node synthesized after the conversion (whose
width the synthesizing pass itself guarantees) to be marked [Contextual]
at its construction site. Reports [v128] too: a record there is what tells
{!Wax_conversion.From_wasm}'s [Stack.effective_backing] the value is not a
reference, so an unrecorded one is a gap even though no width check ever
fires on it. *)(ifWax_utils.Debug.is_enabledWidth_recordthenmatch(i.expected,i.info)with|Unset,([|cell|],location)->(matchresolved_storagetype(Cell.getcell)with|Some(Value((I32|I64|F32|F64|V128)ast))->letp=location.loc_startinPrintf.eprintf"width-record: %s: unrecorded %s value: %s\n%!"(ifp.Lexing.pos_lnum=0then"<no loc>"elsePrintf.sprintf"%s:%d:%d"p.Lexing.pos_fnamep.Lexing.pos_lnum(p.Lexing.pos_cnum-p.Lexing.pos_bol+1))(Infer.Output.valtype_stringt)(width_expri)|Some(Value(Ref_))|Some(Packed_)|None->())|_->());(* This node first: a repair here grounds the cell its whole flexible subtree
shares, so the recursion below sees the settled type. *)letpin=match(i.expected,i.info)with|Recordedrequired,([|cell|],location)->(letinferred=Cell.getcellinmatch(numeric_widthrequired,resolved_storagetypeinferred)with|Somerequired,Someresolved->(match(inferred_widthresolved,numeric_valtyperequired)with|Someinferred_w,Somerequired_vwheninferred_w<>required->(* Can a pin correct this disagreement? Two ways in:
- the node is the OPERAND of a numeric cast and is a literal
tree ({!defaulting_tree}). The cast folded that tree to its
own target, so nothing but the cast ever fixed its width, and
re-grounding it there is inert to the consumer — the cast
becomes the conversion the Wasm had. Its one constraint is the
family it accepts ({!cast_operand_family}): a truncation takes
a float operand, a wrap an integer one, and a pin that crossed
that would not even type-check.
- otherwise the node's own type must be unfixed: a
still-flexible literal, or a tree of HOLES alone — a value the
Wasm side left polymorphic, so whatever type the typer settled
on came from its own defaulting rather than from the module.
(That is the dead-code case, and the type is often CONCRETE —
an operator pins an [Unknown] operand outright — so the cell
alone cannot recognise it.) A hole holds no value to convert,
so it is exempt from the family bound, unless it sits under a
cast whose own family bound applies. *)lethole_only=defaulting_tree~holes_only:trueiinletpinnable=(* Under a cast the family it accepts bounds EVERY pin placed
there, whatever made the value pinnable. *)(matchunder_castwith|Someaccepted->accepted=numeric_familyrequired|None->true)&&((under_cast<>None&&defaulting_treei)||(flexible_literalinferred||hole_only)&&((hole_only&&under_cast=None)||pin_stays_in_familyinferred~resolved:inferred_w~required))inifpinnable&&mode=`RepairthenbeginCell.setcell(Valtyperequired_v);Some(required,required_v)endelsebeginError.width_invariant_violateddiagnostics~location~inferred:(Someinferred_w)~required~pinnable(width_expri);Noneend|_->None)(* The cell is UNRESOLVED: nothing in the module fixed this value's type.
Only the genuinely polymorphic [Unknown] is repairable here (see below
for the other two), and only when the printed form does not already
state the recorded type — a hole under [_ as i64] needs nothing, the
cast IS the pin. Where nothing states it, the value's type is decided by
the LOWERING's default for its position, and the one family of positions
that reads an operand's type to pick an opcode — a narrow store or
atomic RMW, whose method name carries only the access width — defaults
to i32 ([To_wasm.atomic_op], the [StoreS] arm), so an [i64] record there
silently narrows the store. The pin is what states it.
Only a tree of HOLES is pinned: it holds no value, so grounding it
invents no conversion (the same argument as in the resolved case above),
and it is the only shape that reaches here — a live operand is typed by
its consumer, and a dead one is a hole. Anything else falls through
unrepaired, as before.
[Error] is skipped: this node's own typing already failed and was
reported, so a pin would only decorate a rejected module. [Collecting]
is skipped too: it is not a value's cell but a block's
result-under-inference, and [From_wasm]'s [forget_expected] clears the
expectations of the values feeding one, so an expectation reaching it
would mean that clearing has a hole — worth leaving visible as an
unrepaired case rather than papering over with a pin. (Measured over
300+ corpus modules: no expectation reaches either.) *)|Somerequired,None->(match(inferred,numeric_valtyperequired)with|Unknown,Somerequired_vwhenascribed<>Somerequired&&(matchunder_castwith|Someaccepted->accepted=numeric_familyrequired|None->true)&&defaulting_tree~holes_only:truei->ifmode=`RepairthenbeginCell.setcell(Valtyperequired_v);Some(required,required_v)endelsebeginError.width_invariant_violateddiagnostics~location~inferred:None~required~pinnable:true(width_expri);Noneend|_->None)|_->None)|_->Noneinletsub=reconcile_widthsmodediagnosticsinleti={iwithdesc=(matchi.descwith(* A numeric cast constrains its operand only by FAMILY, so a pin inside
it stays type-correct — unlike an operand a call or method signature
fixes, where a pin would make the call ill-typed. That is the one
position where a folded literal tree is still repairable, and the family
it accepts travels with the recursion. *)|Cast(e,t)whencast_operand_familyt<>None->Cast(sub~under_cast:(cast_operand_familyt)~ascribed:(ascribed_typet)e,t)|desc->Ast_utils.map_desc~instr:(sub~under_cast:None~ascribed:None)~block:(Ast_utils.smart_map(sub~under_cast:None~ascribed:None))desc);}inmatchpinwith|None->i|Some(required,required_v)->(* The pin takes the node's span (as [From_wasm]'s own pins do, so the source
trivia still lands on it) and the recorded type as its own; the
instruction's own hints stay on the instruction. It carries no claim
of its own — the one it was inserted for is now met — and is
[Contextual], not [Unset]: this pass guarantees its width itself, so
the recording-gap census must not report it. *){desc=Cast(i,Valtyperequired);info=([|valtype_cellrequired_v|],sndi.info);hints=Wax_wasm.Hints.none;expected=Contextual;}letreconcile_module_widthsmodediagnostics(m:inferred_module_annotationAst.module_)=ifmode=`OffthenmelseList.map(fun(f:(_modulefield,location)Ast.annotated)->{fwithAnnot.desc=Ast_utils.map_modulefield_instr(reconcile_widthsmodediagnostics~under_cast:None~ascribed:None)f.Annot.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 ***)(* [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)=(matchi.descwith|If_annotation{then_body;else_body;_}->letcheck_branch=List.iter(fun(s:_instr)->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.Annot.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(a:Ast.attribute)->match(a.attr_name,a.attr_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(* A feature declaration or a module name nested in a conditional is only
seen here, never applied: both state a module-wide fact resolved before
any branch is specialized (a guarded module name is dropped by
[to_wasm]'s top-level scan; a guarded feature leaves its gated
constructs erroring). Diagnose the misplacement rather than accepting it
silently; [check_attribute_list] otherwise allows the annotation in a
conditional. *)|Conditional{then_fields;else_fields;_}->letrecrejectfields=List.iter(fun(field:(_modulefield,_)annotated)->matchfield.descwith|Module_annotationattrs->List.iter(fun(a:Ast.attribute)->letkey=a.attr_nameandlocation=a.attr_spaninifkey="feature"thenError.feature_declaration_in_conditionaldiagnostics~locationelseifkey="module"thenError.module_name_in_conditionaldiagnostics~location)attrs|Conditional{then_fields;else_fields;_}->rejectthen_fields.desc;Option.iter(fune->rejecte.Annot.desc)else_fields|_->())fieldsinrejectthen_fields.desc;Option.iter(fune->rejecte.Annot.desc)else_fields|_->())fields(* The shape of the module's conditionals for {!Wax_wasm.Cond_plan}: the
field-level conditionals in order, each holding its nested ones, and the
bodies holding statement-level ones — every initializer (which
[type_configuration]'s [globals] pass types first) at rank 0, function
bodies at rank 1. The statement order is the typing order,
[Ast_utils.sub_instrs] listing operands and block bodies in source order.
With [guards], a per-attribute [if <cond>] guard is a conditional with two
empty branches (present / absent), at its field's position: the checking
plan partitions on it, so an export clash or an unused definition is
qualified by the guard like by any [#[if]]. The build plan leaves guards
out — they gate no type, and [From_wasm.plan_shape], which mirrors this
over the source text so the two sides agree on every decision, has no
counterpart for them. *)letplan_shape~guards(fields:locationmodule_):Wax_wasm.Cond_plan.itemlist=letmoduleP=Wax_wasm.Cond_planinletguard_items(attrs:attributes)=ifnotguardsthen[]elseList.filter_map(fun(a:Ast.attribute)->Option.map(fun(g:(Wax_wasm.Ast.cond,location)annotated)->P.Cond{key=g.info;cond=g.desc;then_=[];else_=Some[]})a.attr_guard)attrsinletfield_guards(desc:_modulefield)=guard_items(field_attributesdesc)@matchdescwith|Import{decl;_}->guard_itemsdecl.desc.attributes|Import_group{decls;_}->List.concat_map(fun(d:(import_decl,_)annotated)->guard_itemsd.desc.attributes)decls|_->[]inletrecinstrsl=List.concat_mapinstrlandinstr(i:_instr)=matchi.descwith|If_annotation{cond;then_body;else_body}->[P.Cond{key=i.info;cond;then_=instrsthen_body.desc;else_=Option.map(funb->instrsb.Annot.desc)else_body;};]|_->instrs(Ast_utils.sub_instrsi)inletbodyrankl=matchinstrslwith[]->[]|items->[P.Body{rank;items}]inletrecfields_l=List.concat_map(fun(field:(_modulefield,location)annotated)->matchfield.descwith|Conditional{cond;then_fields;else_fields}->[P.Cond{key=field.info;cond;then_=fields_then_fields.desc;else_=Option.map(fune->fields_e.Annot.desc)else_fields;};]|Func{body=_,l;_}asdesc->field_guardsdesc@body1l|desc->field_guardsdesc@body0(Ast_utils.field_rootsdesc))linfields_fields(* Check every reachable configuration of a conditional module: one run of an
exhaustive [Cond_plan] per configuration, each typed on the preserved tree
under the run's own selection (its attribute guards resolved the same way),
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~simplify~suggest~faithfuldiagnostics(fields:locationmodule_)shape=letmoduleP=Wax_wasm.Cond_planinletplan=P.make~exhaustive:truediagnosticsshapein(* A branch no configuration reaches is a property of the module, not of a
configuration, so it is reported directly (mirrored in the validator). *)ifwarn_unusedthenList.iter(fun(location,side)->Error.dead_branchdiagnostics~location~side)(P.dead_branchesplan);letconfigurations=List.map(funrun->(* Each configuration is checked in its own collector, derived from the
parent so it inherits its error-recovery mode: the [unbound_name]
cascade suppression then applies when type-checking a module
recovered past syntax errors, just as on the conditional-free path. *)letcctx=Wax_utils.Diagnostic.collector~parent:diagnostics()inletselect=P.selectplanruninignore(type_configuration~build:false~warn_unused~suggest~features~faithful~simplify~select~guard:selectcctxfields:_*_);(Wax_utils.Diagnostic.collectedcctx,P.assumptionplanrun))(P.runsplan)inWax_wasm.Cond_explore.reportdiagnostics?truncation_location:(matchfieldswithhd::_->Somehd.info|[]->None)~explain:(P.explainplan~style:`Wax)~truncated:(P.truncatedplan)configurations(* Stitch the runs' typed trees into one: the primary run's tree, each branch it
did not select replaced by that branch as typed by the run that owns it
(recursively — that copy has holes of its own). Also assembles the lowering's
type tables: the primary's, plus each branch's owner's. *)letstitchplanresults=letmoduleP=Wax_wasm.Cond_planinletkey(l:location)side=(l.loc_start.pos_cnum,l.loc_end.pos_cnum,side)inletfills=Hashtbl.create16inletby_branch=Hashtbl.create16inletelse_desc=function|Some(b:_Annot.annotated)->b.Annot.desc|None->[]in(* Collect, from each run's tree, the branches it owns; only the branch a run
selects is typed in its tree, so only that one is descended. *)List.iter(fun(run,(table,tree))->letownlocationsidefill=ifP.ownerplanlocationside=SomerunthenbeginHashtbl.replacefills(keylocationside)fill;Hashtbl.replaceby_branch(keylocationside)tableendinletrecinstr(i:_instr)=matchi.descwith|If_annotation{then_body;else_body;_}->letlocation=sndi.infoinletsel=P.selectplanrunlocationinletbody=ifselthenthen_body.descelseelse_descelse_bodyinownlocationsel(`Instrsbody);List.iterinstrbody|_->List.iterinstr(Ast_utils.sub_instrsi)inletrecfield(f:(_modulefield,location)annotated)=matchf.descwith|Conditional{then_fields;else_fields;_}->letsel=P.selectplanrunf.infoinletside=ifselthenthen_fields.descelseelse_descelse_fieldsinownf.infosel(`Fieldsside);List.iterfieldside|desc->ignore(Ast_utils.map_modulefield_instr(funroot->instrroot;root)desc:_modulefield)inList.iterfieldtree)results;letmissing(location:location)=failwith(Printf.sprintf"Typing: no typed form for the conditional branch at %d-%d"location.loc_start.pos_cnumlocation.loc_end.pos_cnum)inletrecinstrsrunl=List.map(instrrun)landinstrrun(i:_instr)=matchi.descwith|If_annotation{cond;then_body;else_body}->letlocation=sndi.infoinletsel=P.selectplanrunlocationinletbranchside(body:_Annot.annotated)=ifside=selthen{bodywithAnnot.desc=instrsrunbody.Annot.desc}elsematch(P.ownerplanlocationside,Hashtbl.find_optfills(keylocationside))with|Someowner,Some(`Instrsl)->{bodywithAnnot.desc=instrsownerl}|_->missinglocationin{iwithdesc=If_annotation{cond;then_body=branchtruethen_body;else_body=Option.map(branchfalse)else_body;};}|desc->{iwithdesc=Ast_utils.map_desc~instr:(instrrun)~block:(instrsrun)desc;}andfieldsrunl=List.map(fieldrun)landfieldrun(f:(_modulefield,location)annotated)=matchf.descwith|Conditional{cond;then_fields;else_fields}->letsel=P.selectplanrunf.infoinletbranchside(b:_Annot.annotated)=ifside=selthen{bwithAnnot.desc=fieldsrunb.Annot.desc}elsematch(P.ownerplanf.infoside,Hashtbl.find_optfills(keyf.infoside))with|Someowner,Some(`Fieldsl)->{bwithAnnot.desc=fieldsownerl}|_->missingf.infoin{fwithdesc=Conditional{cond;then_fields=branchtruethen_fields;else_fields=Option.map(branchfalse)else_fields;};}|desc->{fwithdesc=Ast_utils.map_modulefield_instr(instrrun)desc}inmatchresultswith|(primary,(table,tree))::_->({current=table;by_branch},fieldsprimarytree)|[]->assertfalse(* The editor sinks were filled by every run, so a use in code common to several
runs was recorded once per run. Keep one entry per use span, a reference's
definitions merged (a name declared in two branches resolves to a different
definition per run). *)letdedupe_sinks~resolve_links~pun_spans~member_completions=letspan(l:location)=(l.loc_start.pos_cnum,l.loc_end.pos_cnum)inOption.iter(fun(links:referencelistref)->lettbl=Hashtbl.create16inletorder=ref[]inList.iter(fun(r:reference)->letk=spanr.useinmatchHashtbl.find_opttblkwith|None->Hashtbl.replacetblkr;order:=k::!order|Somer'->letfresh=List.filter(fund->not(List.exists(fund'->spand'=spand)r'.definitions))r.definitionsinHashtbl.replacetblk{r'withdefinitions=r'.definitions@fresh})!links;links:=List.rev_map(Hashtbl.findtbl)!order)resolve_links;Option.iter(fun(l:locationlistref)->l:=List.sort_uniq(funab->compare(spana)(spanb))!l)pun_spans;Option.iter(fun(l:(location*Members.member_receiver)listref)->l:=List.sort_uniq(fun(a,_)(b,_)->compare(spana)(spanb))!l)member_completionsletf_infer_with_shape?(simplify=false)?(warn_unused=false)?(suggest=false)?(resolve_links=None)?(pun_spans=None)?(member_completions=None)?(faithful=false)?(features=Wax_utils.Feature.default())diagnosticsfieldsshape=lethas_conditional=shape<>[]inifnothas_conditionalthenlettypes,typed=type_configuration~warn_unused~suggest~resolve_links~pun_spans~member_completions~faithful~features~simplifydiagnosticsfieldsin({current=types;by_branch=Hashtbl.create0},typed)elsebegincheck_configurations~warn_unused~features~simplify~suggest~faithfuldiagnosticsfieldsshape;(* Build the typed module (consumed only by the deferred WAT conversion and
the editor; validation-only paths use [check] and never reach here) with
the conditionals preserved: one run per configuration the module's
[Cond_plan] needs, each typing the branches it selects spliced into a
world that exists, then stitched so every branch comes from the run that
owns it. Diagnostics are discarded — [check_configurations] above did
the real checking; references are recorded here, off the runs' trees,
and deduplicated across them. *)letplan=Wax_wasm.Cond_plan.make(Wax_utils.Diagnostic.collector())(plan_shape~guards:falsefields)inletresults=List.map(funrun->(run,type_configuration~select:(Wax_wasm.Cond_plan.selectplanrun)~resolve_links~pun_spans~member_completions~faithful~features~simplify(Wax_utils.Diagnostic.collector())fields))(Wax_wasm.Cond_plan.runsplan)indedupe_sinks~resolve_links~pun_spans~member_completions;stitchplanresultsend(* Report a "Trojan Source" bidirectional control character in any string the
module carries — an export/import name or feature (in an attribute), a string
literal, a data segment, or a conditional string. Purely syntactic; runs
whenever the module is type-checked, shown or hidden by the warning policy. *)letlint_confusablediagnosticsfields=letcheck_str~locations=matchWax_utils.Unicode.first_confusableswith|Someu->Error.confusable_unicodediagnostics~locationu|None->()inletcheck(s:(string,location)annotated)=check_str~location:s.infos.descinletreccheck_cond(c:Wax_wasm.Ast.cond)=matchcwith|Cond_strings->checks|Cond_var_|Cond_version_->()|Cond_andl|Cond_orl->List.itercheck_condl|Cond_notc->check_condc|Cond_cmp(_,a,b)->check_conda;check_condbinletcheck_instr(i:_instr)=matchi.descwith|String(_,s)->check_str~location:i.infos|If_annotation{cond;_}->check_condcond|_->()inletcheck_bodyinstrs=List.iter(Ast_utils.iter_instrcheck_instr)instrsinletcheck_attrs(attrs:attributes)=List.iter(fun(a:Ast.attribute)->Option.iter(Ast_utils.iter_instrcheck_instr)a.attr_value;Option.iter(fun(g:(Wax_wasm.Ast.cond,location)annotated)->check_condg.desc)a.attr_guard)attrsinletcheck_data~locationinit=List.iter(function|Data_strings->check_str~locations|Data_run(_,l)->List.itercheckl|Data_v128_->())initinletrecwalkfields=List.iter(fun(field:(_modulefield,location)annotated)->letlocation=field.infoinmatchfield.descwith|Type_->()|Func{body=_,instrs;attributes;_}->check_attrsattributes;check_bodyinstrs|Global{def;attributes;_}->check_attrsattributes;check_body[def]|Tag{attributes;_}->check_attrsattributes|Memory{data;attributes;_}->check_attrsattributes;List.iter(fun(m:_memdata)->check_data~locationm.init)data|Data{init;attributes;_}->check_attrsattributes;check_data~locationinit|Table{init;attributes;_}->check_attrsattributes;Option.iter(funi->check_body[i])init|Elem{init;attributes;_}->check_attrsattributes;check_bodyinit|Import{module_;decl}->checkmodule_;check_attrsdecl.desc.attributes|Import_group{module_;decls}->checkmodule_;List.iter(fun(d:(import_decl,_)annotated)->check_attrsd.desc.attributes)decls|Module_annotationattrs->check_attrsattrs|Conditional{cond;then_fields;else_fields}->check_condcond;walkthen_fields.desc;Option.iter(funf->walkf.Annot.desc)else_fields)fieldsinwalkfields(* Perform the common entry-point work exactly once: feature declarations affect
every type check, while the conditional shape selects the direct or
configuration-aware path. *)letprepare_module_check~warn_unuseddiagnosticsfeaturesfields=apply_declared_featuresdiagnosticsfeaturesfields;ifwarn_unusedthenlint_confusablediagnosticsfields;letshape=plan_shape~guards:truefieldsinifshape<>[]thencheck_let_bindingsdiagnosticsfields;shapeletf_infer?(simplify=false)?(warn_unused=false)?(suggest=false)?(resolve_links=None)?(pun_spans=None)?(member_completions=None)?(faithful=false)?(features=Wax_utils.Feature.default())diagnosticsfields=Wax_utils.Debug.timed"type-check"@@fun()->letshape=prepare_module_check~warn_unuseddiagnosticsfeaturesfieldsinf_infer_with_shape~simplify~warn_unused~suggest~resolve_links~pun_spans~member_completions~faithful~featuresdiagnosticsfieldsshapeletf?(simplify=false)?(warn_unused=false)?(suggest=false)?(faithful=false)?(width_check=`Off)?(features=Wax_utils.Feature.default())diagnosticsfields=lettypes,typed=f_infer~simplify~warn_unused~suggest~faithful~featuresdiagnosticsfieldsin(* Reconcile the recorded widths BEFORE projecting: the pass settles the
inference cell of whatever it pins, so the projection then reports the pinned
width both at that node and at every node its tree shares a cell with. *)lettyped=reconcile_module_widthswidth_checkdiagnosticstypedin(types,project_moduletyped)letcheck?(warn_unused=false)?(suggest=false)?(features=Wax_utils.Feature.default())diagnosticsfields=Wax_utils.Debug.timed"type-check"@@fun()->letshape=prepare_module_check~warn_unuseddiagnosticsfeaturesfieldsinlethas_conditional=shape<>[]inifnothas_conditionalthenignore(type_configuration~build:false~warn_unused~suggest~features~simplify:falsediagnosticsfields:_*_)elsecheck_configurations~warn_unused~features~simplify:false~suggest~faithful:falsediagnosticsfieldsshapeleterase_typesm=List.map(fun(m:(_modulefield,location)Ast.annotated)->{mwithdesc=Ast_utils.map_modulefieldsndm.desc})m